pax_global_header00006660000000000000000000000064146720607270014525gustar00rootroot0000000000000052 comment=f285ccb45a3769b28aff0282b84475b93a153cf7 Fiona-1.10.1/000077500000000000000000000000001467206072700126415ustar00rootroot00000000000000Fiona-1.10.1/.coveragerc000066400000000000000000000000541467206072700147610ustar00rootroot00000000000000[run] plugins = Cython.Coverage omit = *pxd Fiona-1.10.1/.github/000077500000000000000000000000001467206072700142015ustar00rootroot00000000000000Fiona-1.10.1/.github/dependabot.yml000066400000000000000000000004271467206072700170340ustar00rootroot00000000000000version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: # Check for updates to GitHub Actions every week interval: "weekly" groups: actions: patterns: - "*" Fiona-1.10.1/.github/workflows/000077500000000000000000000000001467206072700162365ustar00rootroot00000000000000Fiona-1.10.1/.github/workflows/rstcheck.yml000066400000000000000000000015461467206072700205750ustar00rootroot00000000000000name: rstcheck # Run this workflow for commits to doc files on: push: paths: - ".github/workflows/rstcheck.yml" - "README.rst" - "docs/**" - "ci/rstcheck/*" pull_request: paths: - ".github/workflows/rstcheck.yml" - "README.rst" - "docs/**" - "ci/rstcheck/*" permissions: contents: read jobs: rstcheck: name: rstcheck runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4.1.3 - name: Set up Python uses: actions/setup-python@v5 with: python-version: 3.11 - name: Install Python dependencies run: | python -m pip install -r ci/rstcheck/requirements.txt - name: Run rstcheck run: | rstcheck -r --ignore-directives automodule --ignore-substitutions version,release,today . Fiona-1.10.1/.github/workflows/scorecard.yml000066400000000000000000000056431467206072700207360ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '41 21 * * 0' push: branches: [ "main" ] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write # Uncomment the permissions below if installing in a private repository. # contents: read # actions: read steps: - name: "Checkout code" uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecard on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: sarif_file: results.sarif Fiona-1.10.1/.github/workflows/test_gdal_latest.yml000066400000000000000000000046251467206072700223120ustar00rootroot00000000000000name: Test GDAL Latest on: push: branches: [ main, 'maint-*' ] schedule: - cron: '0 0 * * 0' pull_request: # also build on PRs touching this file paths: - ".github/workflows/test_gdal_latest.yml" - "ci/gdal-compile.sh" concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} cancel-in-progress: true permissions: contents: read jobs: test_gdal_latest: name: GDAL Latest runs-on: ubuntu-latest container: osgeo/proj:9.1.0 env: GDAL_DIR: ${{ github.workspace }}/gdal_install GDAL_DATA: ${{ github.workspace }}/gdal_install/share/gdal LD_LIBRARY_PATH: "${{ github.workspace }}/gdal_install/lib/:${LD_LIBRARY_PATH}" steps: - uses: actions/checkout@v4.1.3 - name: Update run: | apt-get update apt-get -y install software-properties-common add-apt-repository -y ppa:deadsnakes/ppa apt-get update - name: Set up Python run: | apt-get install -y --no-install-recommends \ python3.10 \ python3.10-dev \ python3.10-venv \ python3-pip \ g++ - name: Install GDAL shell: bash run: | apt-get update apt-get install -qq \ libcurl4-gnutls-dev \ libgeos-dev \ libjpeg-dev \ libnetcdf-dev \ libhdf4-alt-dev \ libhdf5-serial-dev \ libssl-dev \ libsqlite3-dev \ libexpat-dev \ libxerces-c-dev \ libpng-dev \ libopenjp2-7-dev \ libzstd-dev \ libwebp-dev \ cmake \ curl \ git bash ci/gdal-compile.sh git - name: Install dependencies run: | export PATH="${GDAL_DIR}/bin/:${PATH}" python3.10 -m venv testenv . testenv/bin/activate python -m pip install --upgrade pip python -m pip wheel -r requirements-dev.txt python -m pip install -r requirements-dev.txt python setup.py clean python -m pip install --no-deps --force-reinstall -e .[test] - name: Test shell: bash run: | export PATH="${GDAL_DIR}/bin/:${PATH}" . testenv/bin/activate python -m pytest -v -m "not wheel or gdal" -rxXs --cov fiona --cov-report term-missing Fiona-1.10.1/.github/workflows/tests.yml000066400000000000000000000113561467206072700201310ustar00rootroot00000000000000name: Tests on: push: branches: [ main, 'maint-*' ] paths: - '.github/workflows/tests.yml' - 'requirements*.txt' - 'setup.py' - 'setup.cfg' - 'MANIFEST.in' - 'pyproject.toml' - 'fiona/**' - 'tests/**' pull_request: branches: [ main, 'maint-*' ] paths: - '.github/workflows/tests.yml' - 'requirements*.txt' - 'setup.py' - 'setup.cfg' - 'MANIFEST.in' - 'pyproject.toml' - 'fiona/**' - 'tests/**' schedule: - cron: '0 0 * * 0' permissions: contents: read jobs: docker_tests: runs-on: ubuntu-latest name: Docker | GDAL=${{ matrix.gdal-version }} | python=${{ matrix.python-version }} container: ghcr.io/osgeo/gdal:ubuntu-small-${{ matrix.gdal-version }} env: DEBIAN_FRONTEND: noninteractive strategy: fail-fast: false matrix: include: - python-version: '3.8' gdal-version: '3.4.3' - python-version: '3.9' gdal-version: '3.5.3' - python-version: '3.10' gdal-version: '3.6.4' - python-version: '3.11' gdal-version: '3.7.3' - python-version: '3.12' gdal-version: '3.8.3' steps: - uses: actions/checkout@v4.1.3 - name: Update run: | apt-get update apt-get -y install software-properties-common add-apt-repository -y ppa:deadsnakes/ppa apt-get update - name: Set up Python ${{ matrix.python-version }} run: | apt-get install -y --no-install-recommends \ python${{ matrix.python-version }} \ python${{ matrix.python-version }}-dev \ python${{ matrix.python-version }}-venv \ python3-pip \ g++ - name: Install dependencies run: | python${{ matrix.python-version }} -m venv testenv . testenv/bin/activate python -m pip install --upgrade pip python -m pip wheel -r requirements-dev.txt python -m pip install -r requirements-dev.txt python setup.py clean python -m pip install --no-deps --force-reinstall -e .[test] - name: Run tests run: | . testenv/bin/activate python -m pytest -v -m "not wheel or gdal" -rxXs --cov fiona --cov-report term-missing conda_test: name: Conda | ${{ matrix.os }} | python=${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: true matrix: include: - os: macos-13 python-version: '3.12' - os: macos-14 python-version: '3.12' - os: windows-latest python-version: '3.12' steps: - uses: actions/checkout@v4 - name: Conda Setup uses: conda-incubator/setup-miniconda@v3 with: miniforge-variant: Mambaforge miniforge-version: latest use-mamba: true auto-update-conda: true use-only-tar-bz2: false python-version: ${{ matrix.python-version }} - name: Install Env (OSX) if: matrix.os == 'macos-13' || matrix.os == 'macos-14' shell: bash -l {0} run: | conda config --prepend channels conda-forge conda config --set channel_priority strict conda create -n test python=${{ matrix.python-version }} libgdal geos=3.12 cython=3 conda activate test python -m pip install -e . || python -m pip install -e . python -m pip install -r requirements-dev.txt - name: Install Env (Windows) if: matrix.os == 'windows-latest' shell: bash -l {0} run: | conda config --prepend channels conda-forge conda config --set channel_priority strict conda create -n test python=${{ matrix.python-version }} libgdal geos=3.12 cython=3 conda activate test GDAL_VERSION="3.7" python setup.py build_ext -I"/c/Users/runneradmin/miniconda3/envs/test/Library/include" -lgdal -L"/c/Users/runneradmin/miniconda3/envs/test/Library/lib" install python -m pip install -r requirements-dev.txt - name: Check and Log Environment shell: bash -l {0} run: | conda activate test python -V conda info - name: Test with Coverage (Windows) if: matrix.os == 'windows-latest' shell: bash -l {0} run: | conda activate test pytest -v -m "not wheel" -rxXs --cov fiona --cov-report term-missing - name: Test with Coverage (OSX) if: matrix.os == 'macos-13' shell: bash -l {0} run: | conda activate test python -m pytest -v -m "not wheel" -rxXs --cov fiona --cov-report term-missing Fiona-1.10.1/.gitignore000066400000000000000000000016611467206072700146350ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg .libs # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # IDE's etc. .idea/ venv/ venv2/ # fiona VERSION.txt fiona/*.c fiona/*.cpp fiona/ograpi.pxd tests/data/coutwildrnp.json tests/data/coutwildrnp.tar tests/data/coutwildrnp.gpkg .DS_Store .ipynb_checkpoints .pytest_cache MANIFEST wheels/ Fiona-1.10.1/.mailmap000066400000000000000000000020461467206072700142640ustar00rootroot00000000000000Alan D. Snow Hannes Gräuler Hannes Gräuler Hannes Gräuler Kevin Wurster Kevin Wurster Kevin Wurster Matthew Perry Micah Cochran Michael Weisman Patrick Young Patrick Young René Buffat René Buffat Sean Gillies Sean Gillies Sean Gillies Sean Gillies Fiona-1.10.1/.readthedocs.yaml000066400000000000000000000002611467206072700160670ustar00rootroot00000000000000version: 2 build: os: "ubuntu-22.04" tools: python: "mambaforge-22.9" conda: environment: environment.yml python: install: - method: setuptools path: . Fiona-1.10.1/CHANGES.txt000066400000000000000000001320521467206072700144550ustar00rootroot00000000000000Changes ======= All issue numbers are relative to https://github.com/Toblerity/Fiona/issues. 1.10.1 (2024-09-16) ------------------- Bug fixes: - Logging in the CRS class no longer tries to print representations of objects that may be NULL when searching for authority matches (#1445). 1.10.0 (2024-09-03) ------------------- The package version, credits, and citation file have been updated. There have been no other changes since 1.10.0rc1. Fiona is the work of 73 contributors, including 25 new contributors since 1.9.0. 1.10.0rc1 (2024-08-21) ---------------------- This is the first release candidate for 1.10.0. Changes: - Mutable item access to Feature, Geometry, and Properties instances has been restored (reported in #1430). This usage should be avoided as instances of these classes will be immutable in a future version. - The setup.cfg duplicates project configuration in pyproject.toml and has been removed. 1.10b3 (2024-07-29) ------------------- Bug fixes: - The sketchy, semi-private Python opener interfaces of version 1.10b2 have been replaced by ABCs that are exported from fiona.abc (#1415). - The truncate VSI plugin callback has been implemented (#1413). 1.10b2 (2024-07-10) ------------------- Bug fixes: - The Pyopener registry and VSI plugin have been rewritten to avoid filename conflicts and to be compatible with multithreading. Now, a new plugin handler is registered for each instance of using an opener (#1408). Before GDAL 3.9.0 plugin handlers cannot not be removed and so it may be observed that the size of the Pyopener registry grows during the execution of a program. - A CSLConstList ctypedef has been added and is used where appropriate (#1404). - Fiona model objects have a informative, printable representation again (#1380). Packaging: - PyPI wheels include GDAL 3.9.1 and curl 8.8.0. 1.10b1 (2024-04-16) ------------------- Bug fixes: - Fiona can again set fields with values that are instances of classes derived from date, time, and datetime (#1377). This was broken by changes in 1.10a2. 1.10a2 (2024-04-05) ------------------- Deprecations: - The FIELD_TYPES, FIELD_TYPES_MAP, and FIELD_TYPES_MAP_REV attributes of fiona.schema are no longer used by the project and will be removed in version 2.0 (#1366). - The Python style of rio-filter expressions introduced in version 1.0 are deprecated. Only the parenthesized list type of expression will be supported by version 2.0. New features: - All supported Fiona field types are now represented by classes in fiona.schema. These classes are mapped in FIELD_TYPES_MAP2 and FIELD_TYPES_MAP2_REV to OGR field type and field subtype pairs (#1366). - The filter, map, and reduce CLI commands from the public domain version 1.1.0 of fio-planet have been incorporated into Fiona's core set of commands (#1362). These commands are only available if pyparsing and shapely (each of these are declared in the "calc" set of extra requirements) are installed. Bug fixes: - Fiona's python opener VSI plugin prefix has been changed to "vsifiopener" to not conflict with Rasterio (#1368). - Add a 16-bit integer type "int16" based on OGR's OSFTInt16 integer sub-type (#1358). - Allow a GeoJSON collection's layer name to be set on opening in write mode (#1352). - The legacy crs.py module which was shadowed by the new crs.pyx module has been deleted (#1344). - Python 3.8 has been added back to the list of supported versions and a dependency on Numpy added in 1.10a1 has been removed. - An implementation of the VSI flush callback has been added to _vsiopener.pyx. - Openers are now registered only by urlpath. The mode is no longer considered as OGR drivers may use a mix of modes when creating a new dataset. Other changes: - Feature builder and field getter/setter instances are reused when reading and writing features (#1366). 1.10a1 (2024-03-01) ------------------- Python version: Fiona 1.10 will require Python version 3.9 or higher. Deprecations: The fiona.path module will be removed in version 2.0 and a deprecation warning is issued when the module is imported (#1334). Additionally, members of that module are no longer exported from the top level module. New features: Python openers can now support discovery of auxiliary "sidecar" files like .aux.xml, .msk, and .tfw files for GeoTIFFs (#1331). Additionally, filesystem objects, such as those from fsspec, can be used as openers. This will become the recommended usage, supplanting the use of single file openers. Bug fixes: - Use of pkg_resources in test_rio_info.py has been eliminated. - gzip, tar, and zip archive URIs containing drive letters were not always parsed properly on Windows, but are now (#1334). 1.9.6 (2024-03-07) ------------------ - Ensure that geometry types in a schema are translated to a linear type, as geometry instances are (#1313). - Fix broken stable API documentation on Read The Docs (#). - Remove install requirement of setuptools, a regression introduced in 1.9.5. 1.9.5 (2023-10-11) ------------------ Bug fixes: - Expand keys in schema mismatch exception, resolving #1278. - Preserve the null properties and geometry of a Feature when serializing (#1276). Packaging: - The distribution name is now officially "fiona", not "Fiona". The import name remains "fiona". - Builds now require Cython >= 3.0.2 (#1276). - PyPI wheels include GDAL 3.6.4, PROJ 9.0.1, and GEOS 3.11.2. - PyPI wheels include curl 8.4.0, addressing CVE-2023-38545 and CVE-38546. - PyPI wheels are now available for Python 3.12. 1.9.4.post1 (2023-05-23) ------------------------ Extraneous files were unintentionally packaged in the 1.9.4 wheels. This post1 release excludes them so that wheel contents are as in version 1.9.3. 1.9.4 (2023-05-16) ------------------ - The performance of Feature.from_dict() has been improved (#1267). - Several sources of meaningless log messages from fiona._geometry about NULL geometries are avoided (#1264). - The Parquet driver has been added to the list of supported drivers and will be available if your system's GDAL library links libarrow. Note that fiona wheels on PyPI do not include libarrow as it is rather large. - Ensure that fiona._vendor modules are found and included. - Bytes type feature properties are now hex encoded when serializing to GeoJSON (#1263). - Docstrings for listdir and listlayers have been clarified and harmonized. - Nose style test cases have been converted to unittest.TestCase (#1256). - The munch package used by fio-filter and fio-calc is now vendored and patched to remove usage of the deprecated pkg_resources module (#1255). 1.9.3 (2023-04-10) ------------------ - Rasterio CRS objects are compatible with the Collection constructor and are now accepted (#1248). - Enable append mode for fio-load (#1237). - Reading a GeoJSON with an empty array property can result in a segmentation fault since version 1.9.0. This has been fixed (#1228). 1.9.2 (2023-03-20) ------------------ - Get command entry points using importlib.metadata (#1220). - Instead of warning, transform_geom() raises an exception when some points can't be reprojected unless the caller opts in to partial reprojection. This restores the behavior of version 1.8.22. - Add support for open options to all CLI commands that call fiona.open (#1215). - Fix a memory leak that can occur when iterating over a dataset using strides (#1205). - ZipMemoryFile now supports zipped GDB data (#1203). 1.9.1 (2023-02-09) ------------------ - Log a warning message when identically named fields are encountered (#1201). - Avoid dependence on listdir order in tests (#1193). - Prevent empty geometries arrays from appearing in __geo_interface__ (#1197). - setuptools added to pyproject.toml. Its pkg_resources module is used by the CLI (#1191). 1.9.0 (2023-01-30) ------------------ - CITATION.txt has been replaced by a new CITATION.cff file and the credits have been updated. - In setup.py the distutils (deprecated) logger is no longer used. 1.9b2 (2023-01-22) ------------------ - Add Feature.__geo_interface__ property (#1181). - Invalid creation options are filtered and ignored (#1180). - The readme doc has been shortened and freshened up, with a modern example for version 1.9.0 (#1174). - The Geometry class now provides and looks for __geo_interface__ (#1174). - The top level fiona module now exports Feature, Geometry, and Properties (#1174). - Functions that take Feature or Geometry objects will continue to take dicts or objects that provide __geo_interface__ (#1177). This reverses the deprecation introduced in 1.9a2. - Python ignores SIGPIPE by default. By never catching BrokenPipeError via `except Exception` when, for example, piping the output of rio-shapes to the Unix head program, we avoid getting an unhandled BrokenPipeError message when the interpreter shuts down (#2689). 1.9b1 (2022-12-13) ------------------ New features: * Add listdir and listlayers method to io.MemoryFile (resolving #754). * Add support for TIN and triangle geometries (#1163). * Add an allow_unsupported_drivers option to fiona.open() (#1126). * Added support for the OGR StringList field type (#1141). Changes and bug fixes: * Missing and unused imports have been added or removed. * Make sure that errors aren't lost when a collection can't be saved properly (#1169). * Ensure that ZipMemoryFile have the proper GDAL name after creation so that we can use listdir() (#1092). * The fiona._loading module, which supports DLL loading on Windows, has been moved into __init__.py and is no longer used anywhere else (#1168). * Move project metadata to pyproject.toml (#1165). * Update drvsupport.py to reflect new format capabilities in GDAL 3.6.0 (#1122). * Remove debug logging from env and _env modules. 1.9a3 (2022-10-17) ------------------ Packaging: * Builds now require Cython >= 0.29.29 because of * https://github.com/cython/cython/issues/4609 (see #1143). * PyPI wheels now include GDAL 3.5.2, PROJ 9.0.1, and GEOS 3.11.0. * PyPI wheels are now available for Python 3.11. 1.9a2 (2022-06-10) ------------------ Deprecations: - Fiona's API methods will accept feature and geometry dicts in 1.9.0, but this usage is deprecated. Instances of Feature and Geometry will be required in 2.0. - The precision keyword argument of fiona.transform.transform_geom is deprecated and will be removed in version 2.0. - Deprecated usage has been eliminated in the project. Fiona's tests pass when run with a -Werror::DeprecationWarning filter. Changes: - Fiona's FionaDeprecationWarning now sub-classes DeprecationWarning. - Some test modules have been re-formatted using black. New features: - Fiona Collections now carry a context exit stack into which we can push fiona Envs and MemoryFiles (#1059). - Fiona has a new CRS class, like rasterio's, which is compatible with the CRS dicts of previous versions (#714). 1.9a1 (2022-05-19) ------------------ Deprecations: - The fiona.drivers() function has been deprecated and will be removed in version 2.0. It should be replaced by fiona.Env(). - The new fiona.meta module will be renamed to fiona.drivers in version 2.0. Packaging: - Source distributions contain no C source files and require Cython to create them from .pyx files (#1096). Changes: - Shims for various versions of GDAL have been removed and are replaced by Cython compilation conditions (#1093). - Use of CURL_CA_BUNDLE environment variable is replaced by a more specific GDAL/PROJ_CURL_CA_BUNDLE (#1095). - Fiona's feature accessors now return instances of fiona.model.Feature instead of Python dicts (#787). The Feature class is compatible with code that expects GeoJSON-like dicts but also provides id, geometry, and properties attributes. The last two of these are instances of fiona.model.Geometry and fiona.model.Properties. - GDAL 3.1.0 is the minimum GDAL version. - Drop Python 2, and establish Python 3.7 as the minimum version (#1079). - Remove six and reduce footprint of fiona.compat (#985). New features: - The appropriate format driver can be detected from filename in write mode (#948). - Driver metadata including dataset open and dataset and layer creations options are now exposed through methods of the fiona.meta module (#950). - CRS WKT format support (#979). - Add 'where' SQL clause to set attribute filter (#961, #1097). Bug fixes: - Env and Session classes have been updated for parity with rasterio and to resolve a credential refresh bug (#1055). 1.8.22 (2022-10-14) ------------------- Builds now require Cython >= 0.29.29 because of https://github.com/cython/cython/issues/4609 (#1143). 1.8.21 (2022-02-07) ------------------- Changes: - Driver mode support tests have been made more general and less susceptible to driver quirks involving feature fields and coordinate values (#1060). - OSError is raised on attempts to open a dataset in a Python file object in "a" mode (see #1027). - Upgrade attrs, cython, etc to open up Python 3.10 support (#1049). Bug fixes: - Allow FieldSkipLogFilter to handle exception messages as well as strings (reported in #1035). - Clean up VSI files left by MemoryFileBase, resolving #1041. - Hard-coded "utf-8" collection encoding added in #423 has been removed (#1057). 1.8.20 (2021-05-31) ------------------- Packaging: - Wheels include GDAL 3.3.0 and GEOS 3.9.1. Bug fixes: - Allow use with click 8 and higher (#1015). 1.8.19 (2021-04-07) ------------------- Packaging: - Wheels include GDAL 3.2.1 and PROJ 7.2.1. Bug fixes: - In fiona/env.py the GDAL data path is now configured using set_gdal_config instead by setting the GDAL_DATA environment variable (#1007). - Spurious iterator reset warnings have been eliminatged (#987). 1.8.18 (2020-11-17) ------------------- - The precision option of transform has been fixed for the case of GeometryCollections (#971, #972). - Added missing --co (creation) option to fio-load (#390). - If the certifi package can be imported, its certificate store location will be passed to GDAL during import of fiona._env unless CURL_CA_BUNDLE is already set. - Warn when feature fields named "" are found (#955). 1.8.17 (2020-09-09) ------------------- - To fix issue #952 the fio-cat command no longer cuts feature geometries at the anti-meridian by default. A --cut-at-antimeridian option has been added to allow cutting of geometries in a geographic destination coordinate reference system. 1.8.16 (2020-09-04) ------------------- - More OGR errors and warnings arising in calls to GDAL C API functions are surfaced (#946). - A circular import introduced in some cases in 1.8.15 has been fixed (#945). 1.8.15 (2020-09-03) ------------------- - Change shim functions to not return tuples (#942) as a solution for the packaging problem reported in #941. - Raise a Python exception when VSIFOpenL fails (#937). 1.8.14 (2020-08-31) ------------------- - When creating a new Collection in a MemoryFile with a default (random) name Fiona will attempt to use a format driver-supported file extension (#934). When initializing a MemoryFile with bytes of data formatted for a vector driver that requires a certain file name or extension, the user should continue to pass an appropriate filename and/or extension. - Read support for FlatGeobuf has been enabled in the drvsupport module. - The MemoryFile implementation has been improved so that it can support multi-part S3 downloads (#906). This is largely a port of code from rasterio. - Axis ordering for results of fiona.transform was wrong when CRS were passed in the "EPSG:dddd" form (#919). This has been fixed by (#926). - Allow implicit access to the only dataset in a ZipMemoryFile. The path argument of ZipMemoryFile.open() is now optional (#928). - Improve support for datetime types: support milliseconds (#744), timezones (#914) and improve warnings if type is not supported by driver (#572). - Fix "Failed to commit transaction" TransactionError for FileGDB driver. - Load GDAL DLL dependencies on Python 3.8+ / Windows with add_dll_directory() (#851). - Do not require optional properties (#848). - Ensure that slice does not overflow available data (#884). - Resolve issue when "ERROR 4: Unable to open EPSG support file gcs.csv." is raised on importing fiona (#897). - Resolve issue resulting in possible mixed up fields names (affecting only DXF, GPX, GPSTrackMacker and DGN driver) (#916). - Ensure crs_wkt is passed when writing to MemoryFile (#907). 1.8.13.post1 (2020-02-21) ------------------------- - This release is being made to improve binary wheel compatibility with shapely 1.7.0. There have been no changes to the fiona package code since 1.8.13. 1.8.13 (2019-12-05) ------------------- - The Python version specs for argparse and ordereddict in 1.8.12 were wrong and have been corrected (#843). 1.8.12 (2019-12-04) ------------------- - Specify Python versions for argparse, enum34, and ordereddict requirements (#842). 1.8.11 (2019-11-07) ------------------- - Fix an access violation on Windows (#826). 1.8.10 (2019-11-07) ------------------- Deprecations: - Use of vfs keyword argument with open or listlayers has been previously noted as deprecated, but now triggers a deprecation warning. Bug fixes: - fiona.open() can now create new datasets using CRS URNs (#823). - listlayers() now accepts file and Path objects, like open() (#825). - Use new set_proj_search_path() function to set the PROJ data search path. For GDAL versions before 3.0 this sets the PROJ_LIB environment variable. For GDAL version 3.0 this calls OSRSetPROJSearchPaths(), which overrides PROJ_LIB. - Remove old and unused _drivers extension module. - Check for header.dxf file instead of pcs.csv when looking for installed GDAL data. The latter is gone with GDAL 3.0 but the former remains (#818). 1.8.9.post2 (2019-10-22) ------------------------ - The 1.8.9.post1 release introduced a bug affecting builds of the package from a source distribution using GDAL 2.x. This bug has been fixed in commit 960568d. 1.8.9.post1 (2019-10-22) ------------------------ - A change has been made to the package setup script so that the shim module for GDAL 3 is used when building the package from a source distribution. There are no other changes to the package. 1.8.9 (2019-10-21) ------------------ - A shim module and support for GDAL 3.0 has been added. The package can now be built and used with GDAL 3.0 and PROJ 6.1 or 6.2. Note that the 1.8.9 wheels we will upload to PyPI will contain GDAL 2.4.2 and PROJ 4.9.3 as in the 1.8.8 wheels. 1.8.8 (2019-09-25) ------------------ - The schema of geopackage files with a geometry type code of 3000 could not be reported using Fiona 1.8.7. This bug is fixed. 1.8.7 (2019-09-24) ------------------ Bug fixes: - Regression in handling of polygons with M values noted under version 1.8.5 below was in fact not fixed then (see new report #789), but is fixed in version 1.8.7. - Windows filenames containing "!" are now parsed correctly, fixing issue #742. Upcoming changes: - In version 1.9.0, the objects yielded when a Collection is iterated will be mutable mappings but will no longer be instances of Python's dict. Version 1.9 is intended to be backwards compatible with 1.8 except where user code tests `isinstance(feature, dict)`. In version 2.0 the new Feature, Geometry, and Properties classes will become immutable mappings. See https://github.com/Toblerity/fiona-rfc/blob/main/rfc/0001-fiona-2-0-changes.md for more discussion of the upcoming changes for version 2.0. 1.8.6 (2019-03-18) ------------------ - The advertisement for JSON driver enablement in 1.8.5 was false (#176), but in this release they are ready for use. 1.8.5 (2019-03-15) ------------------ - GDAL seems to work best if GDAL_DATA is set as early as possible. Ideally it is set when building the library or in the environment before importing Fiona, but for wheels we patch GDAL_DATA into os.environ when fiona.env is imported. This resolves #731. - A combination of bugs which allowed .cpg files to be overlooked has been fixed (#726). - On entering a collection context (Collection.__enter__) a new anonymous GDAL environment is created if needed and entered. This makes `with fiona.open(...) as collection:` roughly equivalent to `with fiona.open(...) as collection, Env():`. This helps prevent bugs when Collections are created and then used later or in different scopes. - Missing GDAL support for TopoJSON, GeoJSONSeq, and ESRIJSON has been enabled (#721). - A regression in handling of polygons with M values (#724) has been fixed. - Per-feature debug logging calls in OGRFeatureBuilder methods have been eliminated to improve feature writing performance (#718). - Native support for datasets in Google Cloud Storage identified by "gs" resource names has been added (#709). - Support has been added for triangle, polyhedral surface, and TIN geometry types (#679). - Notes about using the MemoryFile and ZipMemoryFile classes has been added to the manual (#674). 1.8.4 (2018-12-10) ------------------ - 3D geometries can now be transformed with a specified precision (#523). - A bug producing a spurious DriverSupportError for Shapefiles with a "time" field (#692) has been fixed. - Patching of the GDAL_DATA environment variable was accidentally left in place in 1.8.3 and now has been removed. 1.8.3 (2018-11-30) ------------------ - The RASTERIO_ENV config environment marker this project picked up from Rasterio has been renamed to FIONA_ENV (#665). - Options --gdal-data and --proj-data have been added to the fio-env command so that users of Rasterio wheels can get paths to set GDAL_DATA and PROJ_LIB environment variables. - The unsuccessful attempt to make GDAL and PROJ support file discovery and configuration automatic within collection's crs and crs_wkt properties has been reverted. Users must execute such code inside a `with Env()` block or set the GDAL_DATA and PROJ_LIB environment variables needed by GDAL. 1.8.2 (2018-11-19) ------------------ Bug fixes: - Raise FionaValueError when an iterator's __next__ is called and the session is found to be missing or inactive instead of passing a null pointer to OGR_L_GetNextFeature (#687). 1.8.1 (2018-11-15) ------------------ Bug fixes: - Add checks around OSRGetAuthorityName and OSRGetAuthorityCode calls that will log problems with looking up these items. - Opened data sources are now released before we raise exceptions in WritingSession.start (#676). This fixes an issue with locked files on Windows. - We now ensure that an Env instance exists when getting the crs or crs_wkt properties of a Collection (#673, #690). Otherwise, required GDAL and PROJ data files included in Fiona wheels can not be found. - GDAL and PROJ data search has been refactored to improve testability (#678). - In the project's Cython code, void* pointers have been replaced with proper GDAL types (#672). - Pervasive warning level log messages about ENCODING creation options (#668) have been eliminated. 1.8.0 (2018-10-31) ------------------ This is the final 1.8.0 release. Thanks, everyone! Bug fixes: - We cpdef Session.stop so that it has a C version that can be called safely from __dealloc__, fixing a PyPy issue (#659, #553). 1.8rc1 (2018-10-26) ------------------- There are no changes in 1.8rc1 other than more test standardization and the introduction of a temporary test_collection_legacy.py module to support the build of fully tested Python 2.7 macosx wheels on Travis-CI. 1.8b2 (2018-10-23) ------------------ Bug fixes: - The ensure_env_with_credentials decorator will no longer clobber credentials of the outer environment. This fixes a bug reported to the Rasterio project and which also existed in Fiona. - An unused import of the packaging module and the dependency have been removed (#653). - The Env class logged to the 'rasterio' hierarchy instead of 'fiona'. This mistake has been corrected (#646). - The Mapping abstract base class is imported from collections.abc when possible (#647). Refactoring: - Standardization of the tests on pytest functions and fixtures continues and is nearing completion (#648, #649, #650, #651, #652). 1.8b1 (2018-10-15) ------------------ Deprecations: - Collection slicing has been deprecated and will be prohibited in a future version. Bug fixes: - Rasterio CRS objects passed to transform module methods will be converted to dicts as needed (#590). - Implicitly convert curve geometries to their linear approximations rather than failing (#617). - Migrated unittest test cases in test_collection.py and test_layer.py to the use of the standard data_dir and path_coutwildrnp_shp fixtures (#616). - Root logger configuration has been removed from all test scripts (#615). - An AWS session is created for the CLI context Env only if explicitly requested, matching the behavior of Rasterio's CLI (#635). - Dependency on attrs is made explicit. - Other dependencies are pinned to known good versions in requirements files. - Unused arguments have been removed from the Env constructor (#637). Refactoring: - A with_context_env decorator has been added and used to set up the GDAL environment for CLI commands. The command functions themselves are now simplified. 1.8a3 (2018-10-01) ------------------ Deprecations: - The ``fiona.drivers()`` context manager is officially deprecated. All users should switch to ``fiona.Env()``, which registers format drivers and manages GDAL configuration in a reversible manner. Bug fixes: - The Collection class now filters log messages about skipped fields to a maximum of one warning message per field (#627). - The boto3 module is only imported when needed (#507, #629). - Compatibility with Click 7.0 is achieved (#633). - Use of %r instead of %s in a debug() call prevents UnicodeDecodeErrors (#620). 1.8a2 (2018-07-24) ------------------ New features: - 64-bit integers are the now the default for int type fields (#562, #564). - 'http', 's3', 'zip+http', and 'zip+s3' URI schemes for datasets are now supported (#425, #426). - We've added a ``MemoryFile`` class which supports formatted in-memory feature collections (#501). - Added support for GDAL 2.x boolean field sub-type (#531). - A new ``fio rm`` command makes it possible to cleanly remove multi-file datasets (#538). - The geometry type in a feature collection is more flexible. We can now specify not only a single geometry type, but a sequence of permissible types, or "Any" to permit any geometry type (#539). - Support for GDAL 2.2+ null fields has been added (#554). - The new ``gdal_open_vector()`` function of our internal API provides much improved error handling (#557). Bug fixes: - The bug involving OrderedDict import on Python 2.7 has been fixed (#533). - An ``AttributeError`` raised when the ``--bbox`` option of fio-cat is used with more than one input file has been fixed (#543, #544). - Obsolete and derelict fiona.tool module has been removed. - Revert the change in 0a2bc7c that discards Z in geometry types when a collection's schema is reported (#541). - Require six version 1.7 or higher (#550). - A regression related to "zip+s3" URIs has been fixed. - Debian's GDAL data locations are now searched by default (#583). 1.8a1 (2017-11-06) ------------------ New features: - Each call of ``writerecords()`` involves one or more transactions of up to 20,000 features each. This improves performance when writing GeoPackage files as the previous transaction size was only 200 features (#476, #491). Packaging: - Fiona's Cython source files have been refactored so that there are no longer separate extension modules for GDAL 1.x and GDAL 2.x. Instead there is a base extension module based on GDAL 2.x and shim modules for installations that use GDAL 1.x. 1.7.11.post1 (2018-01-08) ------------------------- - This post-release adds missing expat (and thereby GPX format) support to the included GDAL library (still version 2.2.2). 1.7.11 (2017-12-14) ------------------- - The ``encoding`` keyword argument for ``fiona.open()``, which is intended to allow a caller to override a data source's own and possibly erroneous encoding, has not been working (#510, #512). The problem is that we weren't always setting GDAL open or config options before opening the data sources. This bug is resolved by a number of commits in the maint-1.7 branch and the fix is demonstrated in tests/test_encoding.py. - An ``--encoding`` option has been added to fio-load to enable creation of encoded shapefiles with an accompanying .cpg file (#499, #517). 1.7.10.post1 (2017-10-30) ------------------------- - A post-release has been made to fix a problem with macosx wheels uploaded to PyPI. 1.7.10 (2017-10-26) ------------------- Bug fixes: - An extraneous printed line from the ``rio cat --layers`` validator has been removed (#478). Packaging: - Official OS X and Manylinux1 wheels (on PyPI) for this release will be compatible with Shapely 1.6.2 and Rasterio 1.0a10 wheels. 1.7.9.post1 (2017-08-21) ------------------------ This release introduces no changes in the Fiona package. It upgrades GDAL from 2.2.0 to 2.2.1 in wheels that we publish to the Python Package Index. 1.7.9 (2017-08-17) ------------------ Bug fixes: - Acquire the GIL for GDAL error callback functions to prevent crashes when GDAL errors occur when the GIL has been released by user code. - Sync and flush layers when closing even when the number of features is not precisely known (#467). 1.7.8 (2017-06-20) ------------------ Bug fixes: - Provide all arguments needed by CPLError based exceptions (#456). 1.7.7 (2017-06-05) ------------------ Bug fixes: - Switch logger `warn()` (deprecated) calls to `warning()`. - Replace all relative imports and cimports in Cython modules with absolute imports (#450). - Avoid setting `PROJ_LIB` to a non-existent directory (#439). 1.7.6 (2017-04-26) ------------------ Bug fixes: - Fall back to `share/proj` for PROJ_LIB (#440). - Replace every call to `OSRDestroySpatialReference()` with `OSRRelease()`, fixing the GPKG driver crasher reported in #441 (#443). - Add a `DriverIOError` derived from `IOError` to use for driver-specific errors such as the GeoJSON driver's refusal to overwrite existing files. Also we now ensure that when this error is raised by `fiona.open()` any created read or write session is deleted, this eliminates spurious exceptions on teardown of broken `Collection` objects (#437, #444). 1.7.5 (2017-03-20) ------------------ Bug fixes: - Opening a data file in read (the default) mode with `fiona.open()` using the the `driver` or `drivers` keyword arguments (to specify certain format drivers) would sometimes cause a crash on Windows due to improperly terminated lists of strings (#428). The fix: Fiona's buggy `string_list()` has been replaced by GDAL's `CSLAddString()`. 1.7.4 (2017-02-20) ------------------ Bug fixes: - OGR's EsriJSON detection fails when certain keys aren't found in the first 6000 bytes of data passed to `BytesCollection` (#422). A .json file extension is now explicitly given to the in-memory file behind `BytesCollection` when the `driver='GeoJSON'` keyword argument is given (#423). 1.7.3 (2017-02-14) ------------------ Roses are red. Tan is a pug. Software regression's the most embarrassing bug. Bug fixes: - Use __stdcall for GDAL error handling callback on Windows as in Rasterio. - Turn on latent support for zip:// URLs in rio-cat and rio-info (#421). - The 1.7.2 release broke support for zip files with absolute paths (#418). This regression has been fixed with tests to confirm. 1.7.2 (2017-01-27) ------------------ Future Deprecation: - `Collection.__next__()` is buggy in that it can lead to duplication of features when used in combination with `Collection.filter()` or `Collection.__iter__()`. It will be removed in Fiona 2.0. Please check for usage of this deprecated feature by running your tests or programs with `PYTHONWARNINGS="always:::fiona"` or `-W"always:::fiona"` and switch from `next(collection)` to `next(iter(collection))` (#301). Bug fix: - Zipped streams of bytes can be accessed by `BytesCollection` (#318). 1.7.1.post1 (2016-12-23) ------------------------ - New binary wheels using version 1.2.0 of sgillies/frs-wheel-builds. See https://github.com/sgillies/frs-wheel-builds/blob/master/CHANGES.txt. 1.7.1 (2016-11-16) ------------------ Bug Fixes: - Prevent Fiona from stumbling over 'Z', 'M', and 'ZM' geometry types introduced in GDAL 2.1 (#384). Fiona 1.7.1 doesn't add explicit support for these types, they are coerced to geometry types 1-7 ('Point', 'LineString', etc.) - Raise an `UnsupportedGeometryTypeError` when a bogus or unsupported geometry type is encountered in a new collection's schema or elsewhere (#340). - Enable `--precision 0` for fio-cat (#370). - Prevent datetime exceptions from unnecessarily stopping collection iteration by yielding `None` (#385) - Replace log.warn calls with log.warning calls (#379). - Print an error message if neither gdal-config or `--gdalversion` indicate a GDAL C API version when running `setup.py` (#364). - Let dict-like subclasses through CRS type checks (#367). 1.7.0post2 (2016-06-15) ----------------------- Packaging: define extension modules for 'clean' and 'config' targets (#363). 1.7.0post1 (2016-06-15) ----------------------- Packaging: No files are copied for the 'clean' setup target (#361, #362). 1.7.0 (2016-06-14) ------------------ The C extension modules in this library can now be built and used with either a 1.x or 2.x release of the GDAL library. Big thanks to René Buffat for leading this effort. Refactoring: - The `ogrext1.pyx` and `ogrext2.pyx` files now use separate C APIs defined in `ogrext1.pxd` and `ogrex2.pxd`. The other extension modules have been refactored so that they do not depend on either of these modules and use subsets of the GDAL/OGR API compatible with both GDAL 1.x and 2.x (#359). Packaging: - Source distributions now contain two different sources for the `ogrext` extension module. The `ogrext1.c` file will be used with GDAL 1.x and the `ogrext2.c` file will be used with GDAL 2.x. 1.7b2 (2016-06-13) ------------------ - New feature: enhancement of the `--layer` option for fio-cat and fio-dump to allow separate layers of one or more multi-layer input files to be selected (#349). 1.7b1 (2016-06-10) ------------------ - New feature: support for GDAL version 2+ (#259). - New feature: a new fio-calc CLI command (#273). - New feature: `--layer` options for fio-info (#316) and fio-load (#299). - New feature: a `--no-parse` option for fio-collect that lets a careful user avoid extra JSON serialization and deserialization (#306). - Bug fix: `+wktext` is now preserved when serializing CRS from WKT to PROJ.4 dicts (#352). - Bug fix: a small memory leak when opening a collection has been fixed (#337). - Bug fix: internal unicode errors now result in a log message and a `UnicodeError` exception, not a `TypeError` (#356). 1.6.4 (2016-05-06) ------------------ - Raise ImportError if the active GDAL library version is >= 2.0 instead of failing unpredictably (#338, #341). Support for GDAL>=2.0 is coming in Fiona 1.7. 1.6.3.post1 (2016-03-27) ------------------------ - No changes to the library in this post-release version, but there is a significant change to the distributions on PyPI: to help make Fiona more compatible with Shapely on OS X, the GDAL shared library included in the macosx (only) binary wheels now statically links the GEOS library. See https://github.com/sgillies/frs-wheel-builds/issues/5. 1.6.3 (2015-12-22) ------------------ - Daytime has been decreasing in the Northern Hemisphere, but is now increasing again as it should. - Non-UTF strings were being passed into OGR functions in some situations and on Windows this would sometimes crash a Python process (#303). Fiona now raises errors derived from UnicodeError when field names or field values can't be encoded. 1.6.2 (2015-09-22) ------------------ - Providing only PROJ4 representations in the dataset meta property resulted in loss of CRS information when using the `fiona.open(..., **src.meta) as dst` pattern (#265). This bug has been addressed by adding a crs_wkt item to the` meta property and extending the `fiona.open()` and the collection constructor to look for and prioritize this keyword argument. 1.6.1 (2015-08-12) ------------------ - Bug fix: Fiona now deserializes JSON-encoded string properties provided by the OGR GeoJSON driver (#244, #245, #246). - Bug fix: proj4 data was not copied properly into binary distributions due to a typo (#254). Special thanks to WFMU DJ Liz Berg for the awesome playlist that's fueling my release sprint. Check it out at https://wfmu.org/playlists/shows/62083. You can't unhear Love Coffin. 1.6.0 (2015-07-21) ------------------ - Upgrade Cython requirement to 0.22 (#214). - New BytesCollection class (#215). - Add GDAL's OpenFileGDB driver to registered drivers (#221). - Implement CLI commands as plugins (#228). - Raise click.abort instead of calling sys.exit, preventing surprising exits (#236). 1.5.1 (2015-03-19) ------------------ - Restore test data to sdists by fixing MANIFEST.in (#216). 1.5.0 (2015-02-02) ------------------ - Finalize GeoJSON feature sequence options (#174). - Fix for reading of datasets that don't support feature counting (#190). - New test dataset (#188). - Fix for encoding error (#191). - Remove confusing warning (#195). - Add data files for binary wheels (#196). - Add control over drivers enabled when reading datasets (#203). - Use cligj for CLI options involving GeoJSON (#204). - Fix fio-info --bounds help (#206). 1.4.8 (2014-11-02) ------------------ - Add missing crs_wkt property as in Rasterio (#182). 1.4.7 (2014-10-28) ------------------ - Fix setting of CRS from EPSG codes (#149). 1.4.6 (2014-10-21) ------------------ - Handle 3D coordinates in bounds() #178. 1.4.5 (2014-10-18) ------------------ - Add --bbox option to fio-cat (#163). - Skip geopackage tests if run from an sdist (#167). - Add fio-bounds and fio-distrib. - Restore fio-dump to working order. 1.4.4 (2014-10-13) ------------------ - Fix accidental requirement on GDAL 1.11 introduced in 1.4.3 (#164). 1.4.3 (2014-10-10) ------------------ - Add support for geopackage format (#160). - Add -f and --format aliases for --driver in CLI (#162). - Add --version option and env command to CLI. 1.4.2 (2014-10-03) ------------------ - --dst-crs and --src-crs options for fio cat and collect (#159). 1.4.1 (2014-09-30) ------------------ - Fix encoding bug in collection's __getitem__ (#153). 1.4.0 (2014-09-22) ------------------ - Add fio cat and fio collect commands (#150). - Return of Python 2.6 compatibility (#148). - Improved CRS support (#149). 1.3.0 (2014-09-17) ------------------ - Add single metadata item accessors to fio inf (#142). - Move fio to setuptools entry point (#142). - Add fio dump and load commands (#143). - Remove fio translate command. 1.2.0 (2014-09-02) ------------------ - Always show property width and precision in schema (#123). - Write datetime properties of features (#125). - Reset spatial filtering in filter() (#129). - Accept datetime.date objects as feature properties (#130). - Add slicing to collection iterators (#132). - Add geometry object masks to collection iterators (#136). - Change source layout to match Shapely and Rasterio (#138). 1.1.6 (2014-07-23) ------------------ - Implement Collection __getitem__() (#112). - Leave GDAL finalization to the DLL's destructor (#113). - Add Collection keys(), values(), items(), __contains__() (#114). - CRS bug fix (#116). - Add fio CLI program. 1.1.5 (2014-05-21) ------------------ - Addition of cpl_errs context manager (#108). - Check for NULLs with '==' test instead of 'is' (#109). - Open auxiliary files with encoding='utf-8' in setup for Python 3 (#110). 1.1.4 (2014-04-03) ------------------ - Convert 'long' in schemas to 'int' (#101). - Carefully map Python schema to the possibly munged internal schema (#105). - Allow writing of features with geometry: None (#71). 1.1.3 (2014-03-23) ------------------ - Always register all GDAL and OGR drivers when entering the DriverManager context (#80, #92). - Skip unsupported field types with a warning (#91). - Allow OGR config options to be passed to fiona.drivers() (#90, #93). - Add a bounds() function (#100). - Turn on GPX driver. 1.1.2 (2014-02-14) ------------------ - Remove collection slice left in dumpgj (#88). 1.1.1 (2014-02-02) ------------------ - Add an interactive file inspector like the one in rasterio. - CRS to_string bug fix (#83). 1.1 (2014-01-22) ---------------- - Use a context manager to manage drivers (#78), a backwards compatible but big change. Fiona is now compatible with rasterio and plays better with the osgeo package. 1.0.3 (2014-01-21) ------------------ - Fix serialization of +init projections (#69). 1.0.2 (2013-09-09) ------------------ - Smarter, better test setup (#65, #66, #67). - Add type='Feature' to records read from a Collection (#68). - Skip geometry validation when using GeoJSON driver (#61). - Dumpgj file description reports record properties as a list (as in dict.items()) instead of a dict. 1.0.1 (2013-08-16) ------------------ - Allow ordering of written fields and preservation of field order when reading (#57). 1.0 (2013-07-30) ----------------- - Add prop_type() function. - Allow UTF-8 encoded paths for Python 2 (#51). For Python 3, paths must always be str, never bytes. - Remove encoding from collection.meta, it's a file creation option only. - Support for linking GDAL frameworks (#54). 0.16.1 (2013-07-02) ------------------- - Add listlayers, open, prop_width to __init__py:__all__. - Reset reading of OGR layer whenever we ask for a collection iterator (#49). 0.16 (2013-06-24) ----------------- - Add support for writing layers to multi-layer files. - Add tests to reach 100% Python code coverage. 0.15 (2013-06-06) ----------------- - Get and set numeric field widths (#42). - Add support for multi-layer data sources (#17). - Add support for zip and tar virtual filesystems (#45). - Add listlayers() function. - Add GeoJSON to list of supported formats (#47). - Allow selection of layers by index or name. 0.14 (2013-05-04) ----------------- - Add option to add JSON-LD in the dumpgj program. - Compare values to six.string_types in Collection constructor. - Add encoding to Collection.meta. - Document dumpgj in README. 0.13 (2013-04-30) ----------------- - Python 2/3 compatibility in a single package. Pythons 2.6, 2.7, 3.3 now supported. 0.12.1 (2013-04-16) ------------------- - Fix messed up linking of README in sdist (#39). 0.12 (2013-04-15) ----------------- - Fix broken installation of extension modules (#35). - Log CPL errors at their matching Python log levels. - Use upper case for encoding names within OGR, lower case in Python. 0.11 (2013-04-14) ----------------- - Cythonize .pyx files (#34). - Work with or around OGR's internal recoding of record data (#35). - Fix bug in serialization of int/float PROJ.4 params. 0.10 (2013-03-23) ----------------- - Add function to get the width of str type properties. - Handle validation and schema representation of 3D geometry types (#29). - Return {'geometry': None} in the case of a NULL geometry (#31). 0.9.1 (2013-03-07) ------------------ - Silence the logger in ogrext.so (can be overridden). - Allow user specification of record field encoding (like 'Windows-1252' for Natural Earth shapefiles) to help when OGR can't detect it. 0.9 (2013-03-06) ---------------- - Accessing file metadata (crs, schema, bounds) on never inspected closed files returns None without exceptions. - Add a dict of supported_drivers and their supported modes. - Raise ValueError for unsupported drivers and modes. - Remove asserts from ogrext.pyx. - Add validate_record method to collections. - Add helpful coordinate system functions to fiona.crs. - Promote use of fiona.open over fiona.collection. - Handle Shapefile's mix of LineString/Polygon and multis (#18). - Allow users to specify width of shapefile text fields (#20). 0.8 (2012-02-21) ---------------- - Replaced .opened attribute with .closed (product of collection() is always opened). Also a __del__() which will close a Collection, but still not to be depended upon. - Added writerecords method. - Added a record buffer and better counting of records in a collection. - Manage one iterator per collection/session. - Added a read-only bounds property. 0.7 (2012-01-29) ---------------- - Initial timezone-naive support for date, time, and datetime fields. Don't use these field types if you can avoid them. RFC 3339 datetimes in a string field are much better. 0.6.2 (2012-01-10) ------------------ - Diagnose and set the driver property of collection in read mode. - Fail if collection paths are not to files. Multi-collection workspaces are a (maybe) TODO. 0.6.1 (2012-01-06) ------------------ - Handle the case of undefined crs for disk collections. 0.6 (2012-01-05) ---------------- - Support for collection coordinate reference systems based on Proj4. - Redirect OGR warnings and errors to the Fiona log. - Assert that pointers returned from the ograpi functions are not NULL before using. 0.5 (2011-12-19) ---------------- - Support for reading and writing collections of any geometry type. - Feature and Geometry classes replaced by mappings (dicts). - Removal of Workspace class. 0.2 (2011-09-16) ---------------- - Rename WorldMill to Fiona. 0.1.1 (2008-12-04) ------------------ - Support for features with no geometry. Fiona-1.10.1/CITATION.cff000066400000000000000000000025241467206072700145360ustar00rootroot00000000000000cff-version: 1.2.0 message: "Please cite this software using these metadata." type: software title: Fiona version: "1.10.0" date-released: "2024-09-03" abstract: "Fiona streams simple feature data to and from GIS formats like GeoPackage and Shapefile." keywords: - cartography - GIS - OGR repository-artifact: https://pypi.org/project/Fiona repository-code: https://github.com/Toblerity/Fiona license: "BSD-3-Clause" authors: - given-names: Sean family-names: Gillies alias: sgillies orcid: https://orcid.org/0000-0002-8401-9184 - given-names: René family-names: Buffat alias: rbuffat orcid: https://orcid.org/0000-0002-9836-3314 - given-names: Joshua family-names: Arnott alias: snorfalorpagus - given-names: Mike W. family-names: Taves alias: mwtoews orcid: https://orcid.org/0000-0003-3657-7963 - given-names: Kevin family-names: Wurster alias: geowurster orcid: https://orcid.org/0000-0001-9044-0832 - given-names: Alan D. family-names: Snow alias: snowman2 orcid: https://orcid.org/0000-0002-7333-3100 - given-names: Micah family-names: Cochran alias: micahcochran - given-names: Elliott family-names: Sales de Andrade alias: QuLogic orcid: https://orcid.org/0000-0001-7310-8942 - given-names: Matthew family-names: Perry alias: perrygeo Fiona-1.10.1/CODE_OF_CONDUCT.md000066400000000000000000000036521467206072700154460ustar00rootroot00000000000000 # Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery * Personal attacks * Trolling or insulting/derogatory comments * Public or private harassment * Publishing other's private information, such as physical or electronic addresses, without explicit permission * Other unethical or unprofessional conduct Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.2.0, available at https://www.contributor-covenant.org/version/1/2/0/code-of-conduct.html Fiona-1.10.1/CREDITS.txt000066400000000000000000000065421467206072700145060ustar00rootroot00000000000000Credits ======= Fiona is written by: - Adam J. Stewart - Alan D. Snow - Alexandre Detiste - Ariel Nunez - Ariki - Bas Couwenberg - Brandon Liu - Brendan Ward - Chad Hawkins - Chris Mutel - Christoph Gohlke - Dan "Ducky" Little - daryl herzmann - Denis - Denis Rykov - dimlev - Efrén - Egor Fedorov - Elliott Sales de Andrade - Even Rouault - Ewout ter Hoeven - Filipe Fernandes - fredj - Gavin S - Géraud - Hannes Gräuler - Hao Lyu <20434183+IncubatorShokuhou@users.noreply.github.com> - Herz - Ian Rose - Jacob Wasserman - James McBride - James Wilshaw - Jelle van der Waa - Jesse Crocker - joehuanguf <51337028+joehuanguf@users.noreply.github.com> - Johan Van de Wauw - Joris Van den Bossche - Joshua Arnott - Juan Luis Cano Rodríguez - Keith Jenkins - Kelsey Jordahl - Kevin Wurster - lgolston <30876419+lgolston@users.noreply.github.com> - Loïc Dutrieux - Ludovic Delauné - Martijn Visser - Matthew Perry - Micah Cochran - Michael Weisman - Michele Citterio - Mike Taves - Miro Hrončok - Oliver Tonnhofer - Patrick Young - Phillip Cloud <417981+cpcloud@users.noreply.github.com> - pmav99 - qinfeng - René Buffat - Reuben Fletcher-Costin - Ryan Grout - Ryan Munro - Sandro Mani - Sean Gillies - Sid Kapur - Simon Norris - Stefan Brand - Stefano Costa - Stephane Poss - Tim Tröndle - wilsaj - Yann-Sebastien Tremblay-Johnston The GeoPandas project (Joris Van den Bossche et al.) has been a major driver for new features in 1.8.0. Fiona would not be possible without the great work of Frank Warmerdam and other GDAL/OGR developers. Some portions of this work were supported by a grant (for Pleiades_) from the U.S. National Endowment for the Humanities (https://www.neh.gov). .. _Pleiades: https://pleiades.stoa.org Fiona-1.10.1/Dockerfile000066400000000000000000000017251467206072700146400ustar00rootroot00000000000000ARG GDAL=ubuntu-small-3.6.4 FROM ghcr.io/osgeo/gdal:${GDAL} AS gdal ARG PYTHON_VERSION=3.10 ENV LANG="C.UTF-8" LC_ALL="C.UTF-8" RUN apt-get update && apt-get install -y software-properties-common RUN add-apt-repository -y ppa:deadsnakes/ppa RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ g++ \ gdb \ make \ python3-pip \ python${PYTHON_VERSION} \ python${PYTHON_VERSION}-dev \ python${PYTHON_VERSION}-venv \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements*.txt ./ RUN python${PYTHON_VERSION} -m venv /venv && \ /venv/bin/python -m pip install -U pip && \ /venv/bin/python -m pip install build && \ /venv/bin/python -m pip install -r requirements-dev.txt && \ /venv/bin/python -m pip list FROM gdal COPY . . RUN /venv/bin/python -m build -o wheels RUN /venv/bin/python -m pip install --no-index -f wheels fiona[test] ENTRYPOINT ["/venv/bin/fio"] CMD ["--help"] Fiona-1.10.1/FAQ.rst000066400000000000000000000010531467206072700140010ustar00rootroot00000000000000Frequently asked questions and answers ====================================== What does "ValueError: Invalid field type " mean? ------------------------------------------------------------------------ Fiona maps the built-in Python types to `field types of the OGR API `__ (``float`` to ``OFTReal``, etc.). Users may need to convert instances of other classes (like ``cx_Oracle.LOB``) to strings or bytes when writing data to new GIS datasets using fiona. Fiona-1.10.1/ISSUE_TEMPLATE.md000066400000000000000000000037621467206072700153560ustar00rootroot00000000000000 ## Expected behavior and actual behavior. For example: I expected to read 10 features from a file and an exception occurred on the 3rd. ## Steps to reproduce the problem. For example: a script with required data. ## Operating system For example: Mac OS X 10.12.3. ## Fiona and GDAL version and provenance For example: the 1.7.10.post1 manylinux1 wheel installed from PyPI using pip version 9.0.1. For example: GDAL 2.1.0 installed via Homebrew Fiona-1.10.1/LICENSE.txt000066400000000000000000000027571467206072700144770ustar00rootroot00000000000000 Copyright (c) 2007, Sean C. Gillies All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Sean C. Gillies nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Fiona-1.10.1/MANIFEST.in000066400000000000000000000010761467206072700144030ustar00rootroot00000000000000global-exclude .DS_Store global-exclude *.pyc recursive-exclude docs/data * recursive-exclude docs/_build * recursive-exclude _build * recursive-exclude venv * exclude *.txt *.py recursive-include docs *.rst *.txt recursive-include tests *.py recursive-include tests/data * exclude tests/data/coutwildrnp.gpkg exclude tests/data/coutwildrnp.json exclude tests/data/coutwildrnp.tar recursive-include fiona *.pyx *.pxd *.pxi recursive-exclude fiona *.c *.cpp include CHANGES.txt CITATION.cff CREDITS.txt LICENSE.txt README.rst include pyproject.toml setup.py requirements.txt Fiona-1.10.1/Makefile000066400000000000000000000053171467206072700143070ustar00rootroot00000000000000PYTHON_VERSION ?= 3.12 GDAL ?= ubuntu-small-3.9.2 all: deps clean install test .PHONY: docs install: python setup.py build_ext pip install -e .[all] deps: pip install -r requirements-dev.txt clean: pip uninstall -y fiona || echo "no need to uninstall" python setup.py clean --all find . -name '__pycache__' -delete -print -o -name '*.pyc' -delete -print touch fiona/*.pyx sdist: python setup.py sdist test: python -m pytest --maxfail 1 -v --cov fiona --cov-report html --pdb tests docs: cd docs && make apidocs && make html doctest: py.test --doctest-modules fiona --doctest-glob='*.rst' docs/*.rst dockertestimage: docker build --target gdal --build-arg GDAL=$(GDAL) --build-arg PYTHON_VERSION=$(PYTHON_VERSION) -t fiona:$(GDAL)-py$(PYTHON_VERSION) . dockertest: dockertestimage docker run -it -v $(shell pwd):/app -v /tmp:/tmp --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY --entrypoint=/bin/bash fiona:$(GDAL)-py$(PYTHON_VERSION) -c '/venv/bin/python -m pip install -vvv --editable .[all] --no-build-isolation && /venv/bin/python -B -m pytest -m "not wheel" --cov fiona --cov-report term-missing $(OPTS)' dockershell: dockertestimage docker run -it -v $(shell pwd):/app --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY --entrypoint=/bin/bash fiona:$(GDAL)-py$(PYTHON_VERSION) -c '/venv/bin/python -m pip install --editable . --no-build-isolation && /bin/bash' dockersdist: dockertestimage docker run -it -v $(shell pwd):/app --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY --entrypoint=/bin/bash fiona:$(GDAL)-py$(PYTHON_VERSION) -c '/venv/bin/python -m build --sdist' dockergdb: dockertestimage docker run -it -v $(shell pwd):/app --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY --entrypoint=/bin/bash fiona:$(GDAL)-py$(PYTHON_VERSION) -c '/venv/bin/python -m pip install --editable . --no-build-isolation && gdb -ex=r --args /venv/bin/python -B -m pytest -m "not wheel" --cov fiona --cov-report term-missing $(OPTS)' dockerdocs: dockertestimage docker run -it -v $(shell pwd):/app --entrypoint=/bin/bash fiona:$(GDAL)-py$(PYTHON_VERSION) -c 'source /venv/bin/activate && cd docs && make clean && make html' dockertestimage-amd64: docker build --platform linux/amd64 --target gdal --build-arg GDAL=$(GDAL) --build-arg PYTHON_VERSION=$(PYTHON_VERSION) -t fiona-amd64:$(GDAL)-py$(PYTHON_VERSION) . dockertest-amd64: dockertestimage-amd64 docker run -it -v $(shell pwd):/app -v /tmp:/tmp --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY --entrypoint=/bin/bash fiona-amd64:$(GDAL)-py$(PYTHON_VERSION) -c '/venv/bin/python -m pip install tiledb && /venv/bin/python -m pip install --editable .[all] --no-build-isolation && /venv/bin/python -B -m pytest -m "not wheel" --cov fiona --cov-report term-missing $(OPTS)' Fiona-1.10.1/README.rst000066400000000000000000000123141467206072700143310ustar00rootroot00000000000000===== Fiona ===== .. image:: https://github.com/Toblerity/Fiona/actions/workflows/tests.yml/badge.svg :target: https://github.com/Toblerity/Fiona/actions/workflows/tests.yml .. image:: https://github.com/Toblerity/Fiona/actions/workflows/test_gdal_latest.yml/badge.svg :target: https://github.com/Toblerity/Fiona/actions/workflows/test_gdal_latest.yml .. image:: https://img.shields.io/pypi/v/fiona :target: https://pypi.org/project/fiona/ .. image:: https://api.securityscorecards.dev/projects/github.com/Toblerity/Fiona/badge :target: https://securityscorecards.dev/viewer/?uri=github.com/Toblerity/Fiona Fiona streams simple feature data to and from GIS formats like GeoPackage and Shapefile. Fiona can read and write real-world data using multi-layered GIS formats, zipped and in-memory virtual file systems, from files on your hard drive or in cloud storage. This project includes Python modules and a command line interface (CLI). Fiona depends on `GDAL `__ but is different from GDAL's own `bindings `__. Fiona is designed to be highly productive and to make it easy to write code which is easy to read. Installation ============ Fiona has several `extension modules `__ which link against libgdal. This complicates installation. Binary distributions (wheels) containing libgdal and its own dependencies are available from the Python Package Index and can be installed using pip. .. code-block:: console pip install fiona These wheels are mainly intended to make installation easy for simple applications, not so much for production. They are not tested for compatibility with all other binary wheels, conda packages, or QGIS, and omit many of GDAL's optional format drivers. If you need, for example, GML support you will need to build and install Fiona from a source distribution. It is possible to install Fiona from source using pip (version >= 22.3) and the `--no-binary` option. A specific GDAL installation can be selected by setting the GDAL_CONFIG environment variable. .. code-block:: console pip install -U pip pip install --no-binary fiona fiona Many users find Anaconda and conda-forge a good way to install Fiona and get access to more optional format drivers (like GML). Fiona 1.10 requires Python 3.8 or higher and GDAL 3.4 or higher. Python Usage ============ Features are read from and written to file-like ``Collection`` objects returned from the ``fiona.open()`` function. Features are data classes modeled on the GeoJSON format. They don't have any spatial methods of their own, so if you want to transform them you will need Shapely or something like it. Here is an example of using Fiona to read some features from one data file, change their geometry attributes using Shapely, and write them to a new data file. .. code-block:: python import fiona from fiona import Feature, Geometry from shapely.geometry import mapping, shape # Open a file for reading. We'll call this the source. with fiona.open( "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip" ) as src: # The file we'll write to must be initialized with a coordinate # system, a format driver name, and a record schema. We can get # initial values from the open source's profile property and then # modify them as we need. profile = src.profile profile["schema"]["geometry"] = "Point" profile["driver"] = "GPKG" # Open an output file, using the same format driver and coordinate # reference system as the source. The profile mapping fills in the # keyword parameters of fiona.open. with fiona.open("centroids.gpkg", "w", **profile) as dst: # Process only the feature records intersecting a box. for feat in src.filter(bbox=(-107.0, 37.0, -105.0, 39.0)): # Get the feature's centroid. centroid_shp = shape(feat.geometry).centroid new_geom = Geometry.from_dict(centroid_shp) # Write the feature out. dst.write( Feature(geometry=new_geom, properties=f.properties) ) # The destination's contents are flushed to disk and the file is # closed when its with block ends. This effectively # executes ``dst.flush(); dst.close()``. CLI Usage ========= Fiona's command line interface, named "fio", is documented at `docs/cli.rst `__. The CLI has a number of different commands. Its ``fio cat`` command streams GeoJSON features from any dataset. .. code-block:: console $ fio cat --compact tests/data/coutwildrnp.shp | jq -c '.' {"geometry":{"coordinates":[[[-111.73527526855469,41.995094299316406],...]]}} ... Documentation ============= For more details about this project, please see: * Fiona `home page `__ * `Docs and manual `__ * `Examples `__ * Main `user discussion group `__ * `Developers discussion group `__ Fiona-1.10.1/SECURITY.md000066400000000000000000000025701467206072700144360ustar00rootroot00000000000000# Security Policy ## Supported Versions | Version | Supported | | ------- | ------------------ | | 1.9.x | :white_check_mark: | | < 1.9 | :x: | ## Reporting a Vulnerability Fiona includes C extension modules that link [GDAL](https://gdal.org/), which in turn links a number of other libraries such as libgeos, libproj, and libcurl. The exact list depends on the features included when GDAL is built and varies across distributions. The Fiona team publishes binary wheels to the Python Package Index for 4 different platforms. The wheels contain 27-35 libraries. The exact list depends on the platform and the versions of package managers and tooling used for each platform. Details can be found at https://github.com/sgillies/fiona-wheels. To report a vulnerability in fiona or in one of the libraries that is included in a binary wheel on PyPI, please use the GitHub Security Advisory "Report a Vulnerability" tab. In the case of a vulnerability in a dependency, please provide a link to a published CVE or other description of the issue. The Fiona team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement at https://github.com/Toblerity/Fiona/discussions, and may ask for additional information or guidance. Fiona-1.10.1/appveyor.yml000066400000000000000000000131041467206072700152300ustar00rootroot00000000000000# Based on appveyor.yml from https://github.com/PDAL/PDAL and https://github.com/ogrisel/python-appveyor-demo platform: x64 environment: global: # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the # /E:ON and /V:ON options are not enabled in the batch script interpreter # See: http://stackoverflow.com/a/13751649/163740 CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd" GDAL_HOME: "C:\\gdal" PYTHONWARNINGS: "ignore:DEPRECATION::pip._internal.cli.base_command" ENABLE_DEPRECATED_DRIVER_GTM: "YES" matrix: # PYTHON_VERSION and PYTHON_ARCH are required by run_with_env.cmd. # The 4-digit number in the GISInternals archives is the MSVC version used to build # the libraries. It does not need to match the version of MSVC used to build Python. # https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering # Test all supported gdal minor versions (except latest stable) with one python version - PYTHON: "C:\\Python38-x64" PYTHON_VERSION: "3.8" PYTHON_ARCH: "64" GDAL_VERSION: "2.4.3" GIS_INTERNALS: "release-1911-x64-gdal-2-4-3-mapserver-7-4-2.zip" GIS_INTERNALS_LIBS: "release-1911-x64-gdal-2-4-3-mapserver-7-4-2-libs.zip" - PYTHON: "C:\\Python38-x64" PYTHON_VERSION: "3.8" PYTHON_ARCH: "64" GDAL_VERSION: "3.0.4" GIS_INTERNALS: "release-1911-x64-gdal-3-0-4-mapserver-7-4-3.zip" GIS_INTERNALS_LIBS: "release-1911-x64-gdal-3-0-4-mapserver-7-4-3-libs.zip" PROJ_LIB: "C:\\gdal\\bin\\proj6\\share" # Test all supported python versions with latest stable gdal release - PYTHON: "C:\\Python36-x64" PYTHON_VERSION: "3.6" PYTHON_ARCH: "64" GDAL_VERSION: "3.1.2" GIS_INTERNALS: "release-1911-x64-gdal-3-1-2-mapserver-7-6-1.zip" GIS_INTERNALS_LIBS: "release-1911-x64-gdal-3-1-2-mapserver-7-6-1-libs.zip" PROJ_LIB: "C:\\gdal\\bin\\proj6\\share" - PYTHON: "C:\\Python37-x64" PYTHON_VERSION: "3.7" PYTHON_ARCH: "64" GDAL_VERSION: "3.1.2" GIS_INTERNALS: "release-1911-x64-gdal-3-1-2-mapserver-7-6-1.zip" GIS_INTERNALS_LIBS: "release-1911-x64-gdal-3-1-2-mapserver-7-6-1-libs.zip" PROJ_LIB: "C:\\gdal\\bin\\proj6\\share" - PYTHON: "C:\\Python38-x64" PYTHON_VERSION: "3.8" PYTHON_ARCH: "64" GDAL_VERSION: "3.1.2" GIS_INTERNALS: "release-1911-x64-gdal-3-1-2-mapserver-7-6-1.zip" GIS_INTERNALS_LIBS: "release-1911-x64-gdal-3-1-2-mapserver-7-6-1-libs.zip" PROJ_LIB: "C:\\gdal\\bin\\proj6\\share" install: - ECHO "Filesystem root:" - ps: "ls \"C:/\"" - ECHO "Installed SDKs:" - ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\"" # Install Python (from the official .msi of http://python.org) and pip when # not already installed. # - ps: if (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1 } # Prepend newly installed Python to the PATH of this build (this cannot be # done from inside the powershell script as it would require to restart # the parent CMD process). - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "SET PYTHONPATH=%PYTHON%\\Lib\\site-packages;%PYTHONPATH%" # Check that we have the expected version and architecture for Python - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - ps: mkdir C:\build | out-null - ps: mkdir C:\gdal | out-null - curl http://download.gisinternals.com/sdk/downloads/%GIS_INTERNALS% --output gdalbin.zip - 7z x gdalbin.zip -oC:\gdal - curl http://download.gisinternals.com/sdk/downloads/%GIS_INTERNALS_LIBS% --output gdallibs.zip - 7z x gdallibs.zip -oC:\gdal - "SET PATH=C:\\gdal;C:\\gdal\\bin;C:\\gdal\\data;C:\\gdal\\bin\\gdal\\apps;%PATH%" - "SET GDAL_DATA=C:\\gdal\\bin\\gdal-data" - "SET PACKAGE_DATA=1" - ECHO "Filesystem C:/GDAL:" - ps: "ls \"C:/GDAL\"" - cd C:\projects\fiona # Upgrade to the latest version of pip to avoid it displaying warnings # about it being out of date. - cmd: python -m pip install --disable-pip-version-check --user --upgrade pip - cmd: python -m pip --version # Install the build dependencies of the project. If some dependencies contain # compiled extensions and are not provided as pre-built wheel packages, # pip will build them from source using the MSVC compiler matching the # target Python version and architecture - "%CMD_IN_ENV% pip install -r requirements-dev.txt" # Install coverage testing dependencies # - ps: python -m pip install coveralls>=1.1 --upgrade build_script: # Build the compiled extension - cmd: echo %PATH% - cmd: echo %PYTHONPATH% # copy gisinternal gdal librarys into .libs - cmd: xcopy C:\gdal\bin\*.dll fiona\.libs\ - cmd: xcopy C:\gdal\*.rtf fiona\.libs\licenses\ # build fiona and create a wheel - "%CMD_IN_ENV% python setup.py build_ext -IC:\\gdal\\include -lgdal_i -LC:\\gdal\\lib bdist_wheel --gdalversion %GDAL_VERSION%" # install the wheel - ps: python -m pip install --upgrade pip - ps: python -m pip install --no-deps --ignore-installed (gci dist\*.whl | % { "$_" }) - ps: python -m pip freeze - ps: move fiona fiona.build test_script: # Run the project tests - cmd: SET - ps: python -c "import fiona" # Our Windows GDAL doesn't have iconv and can't support certain tests. - "%CMD_IN_ENV% python -m pytest -m \"not iconv and not wheel\" --cov fiona --cov-report term-missing" artifacts: - path: dist\*.whl name: wheel Fiona-1.10.1/appveyor/000077500000000000000000000000001467206072700145065ustar00rootroot00000000000000Fiona-1.10.1/appveyor/install.ps1000066400000000000000000000160331467206072700166040ustar00rootroot00000000000000# Sample script to install Python and pip under Windows # Authors: Olivier Grisel, Jonathan Helmus, Kyle Kastner, and Alex Willmer # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ $MINICONDA_URL = "http://repo.continuum.io/miniconda/" $BASE_URL = "https://www.python.org/ftp/python/" $GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" $GET_PIP_PATH = "C:\get-pip.py" $PYTHON_PRERELEASE_REGEX = @" (?x) (?\d+) \. (?\d+) \. (?\d+) (?[a-z]{1,2}\d+) "@ function Download ($filename, $url) { $webclient = New-Object System.Net.WebClient $basedir = $pwd.Path + "\" $filepath = $basedir + $filename if (Test-Path $filename) { Write-Host "Reusing" $filepath return $filepath } # Download and retry up to 3 times in case of network transient errors. Write-Host "Downloading" $filename "from" $url $retry_attempts = 2 for ($i = 0; $i -lt $retry_attempts; $i++) { try { $webclient.DownloadFile($url, $filepath) break } Catch [Exception]{ Start-Sleep 1 } } if (Test-Path $filepath) { Write-Host "File saved at" $filepath } else { # Retry once to get the error message if any at the last try $webclient.DownloadFile($url, $filepath) } return $filepath } function ParsePythonVersion ($python_version) { if ($python_version -match $PYTHON_PRERELEASE_REGEX) { return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro, $matches.prerelease) } $version_obj = [version]$python_version return ($version_obj.major, $version_obj.minor, $version_obj.build, "") } function DownloadPython ($python_version, $platform_suffix) { $major, $minor, $micro, $prerelease = ParsePythonVersion $python_version if (($major -le 2 -and $micro -eq 0) ` -or ($major -eq 3 -and $minor -le 2 -and $micro -eq 0) ` ) { $dir = "$major.$minor" $python_version = "$major.$minor$prerelease" } else { $dir = "$major.$minor.$micro" } if ($prerelease) { if (($major -le 2) ` -or ($major -eq 3 -and $minor -eq 1) ` -or ($major -eq 3 -and $minor -eq 2) ` -or ($major -eq 3 -and $minor -eq 3) ` ) { $dir = "$dir/prev" } } if (($major -le 2) -or ($major -le 3 -and $minor -le 4)) { $ext = "msi" if ($platform_suffix) { $platform_suffix = ".$platform_suffix" } } else { $ext = "exe" if ($platform_suffix) { $platform_suffix = "-$platform_suffix" } } $filename = "python-$python_version$platform_suffix.$ext" $url = "$BASE_URL$dir/$filename" $filepath = Download $filename $url return $filepath } function InstallPython ($python_version, $architecture, $python_home) { Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return $false } if ($architecture -eq "32") { $platform_suffix = "" } else { $platform_suffix = "amd64" } $installer_path = DownloadPython $python_version $platform_suffix $installer_ext = [System.IO.Path]::GetExtension($installer_path) Write-Host "Installing $installer_path to $python_home" $install_log = $python_home + ".log" if ($installer_ext -eq '.msi') { InstallPythonMSI $installer_path $python_home $install_log } else { InstallPythonEXE $installer_path $python_home $install_log } if (Test-Path $python_home) { Write-Host "Python $python_version ($architecture) installation complete" } else { Write-Host "Failed to install Python in $python_home" Get-Content -Path $install_log Exit 1 } } function InstallPythonEXE ($exepath, $python_home, $install_log) { $install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home" RunCommand $exepath $install_args } function InstallPythonMSI ($msipath, $python_home, $install_log) { $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home" $uninstall_args = "/qn /x $msipath" RunCommand "msiexec.exe" $install_args if (-not(Test-Path $python_home)) { Write-Host "Python seems to be installed else-where, reinstalling." RunCommand "msiexec.exe" $uninstall_args RunCommand "msiexec.exe" $install_args } } function RunCommand ($command, $command_args) { Write-Host $command $command_args Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru } function InstallPip ($python_home) { $pip_path = $python_home + "\Scripts\pip.exe" $python_path = $python_home + "\python.exe" if (-not(Test-Path $pip_path)) { Write-Host "Installing pip..." $webclient = New-Object System.Net.WebClient $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH) Write-Host "Executing:" $python_path $GET_PIP_PATH & $python_path $GET_PIP_PATH } else { Write-Host "pip already installed." } } function DownloadMiniconda ($python_version, $platform_suffix) { if ($python_version -eq "3.4") { $filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe" } else { $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe" } $url = $MINICONDA_URL + $filename $filepath = Download $filename $url return $filepath } function InstallMiniconda ($python_version, $architecture, $python_home) { Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return $false } if ($architecture -eq "32") { $platform_suffix = "x86" } else { $platform_suffix = "x86_64" } $filepath = DownloadMiniconda $python_version $platform_suffix Write-Host "Installing" $filepath "to" $python_home $install_log = $python_home + ".log" $args = "/S /D=$python_home" Write-Host $filepath $args Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru if (Test-Path $python_home) { Write-Host "Python $python_version ($architecture) installation complete" } else { Write-Host "Failed to install Python in $python_home" Get-Content -Path $install_log Exit 1 } } function InstallMinicondaPip ($python_home) { $pip_path = $python_home + "\Scripts\pip.exe" $conda_path = $python_home + "\Scripts\conda.exe" if (-not(Test-Path $pip_path)) { Write-Host "Installing pip..." $args = "install --yes pip" Write-Host $conda_path $args Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru } else { Write-Host "pip already installed." } } function main () { InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON InstallPip $env:PYTHON } main Fiona-1.10.1/appveyor/run_with_env.cmd000066400000000000000000000064461467206072700177140ustar00rootroot00000000000000:: To build extensions for 64 bit Python 3, we need to configure environment :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) :: :: To build extensions for 64 bit Python 2, we need to configure environment :: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) :: :: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific :: environment configurations. :: :: Note: this script needs to be run with the /E:ON and /V:ON flags for the :: cmd interpreter, at least for (SDK v7.0) :: :: More details at: :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows :: http://stackoverflow.com/a/13751649/163740 :: :: Author: Olivier Grisel :: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ :: :: Notes about batch files for Python people: :: :: Quotes in values are literally part of the values: :: SET FOO="bar" :: FOO is now five characters long: " b a r " :: If you don't want quotes, don't include them on the right-hand side. :: :: The CALL lines at the end of this file look redundant, but if you move them :: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y :: case, I don't know why. @ECHO OFF SET COMMAND_TO_RUN=%* SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf :: Extract the major and minor versions, and allow for the minor version to be :: more than 9. This requires the version number to have two dots in it. SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1% IF "%PYTHON_VERSION:~3,1%" == "." ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1% ) ELSE ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2% ) :: Based on the Python version, determine what SDK version to use, and whether :: to set the SDK for 64-bit. IF %MAJOR_PYTHON_VERSION% == 2 ( SET WINDOWS_SDK_VERSION="v7.0" SET SET_SDK_64=Y ) ELSE ( IF %MAJOR_PYTHON_VERSION% == 3 ( SET WINDOWS_SDK_VERSION="v7.1" IF %MINOR_PYTHON_VERSION% LEQ 4 ( SET SET_SDK_64=Y ) ELSE ( SET SET_SDK_64=N IF EXIST "%WIN_WDK%" ( :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/ REN "%WIN_WDK%" 0wdf ) ) ) ELSE ( ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" EXIT 1 ) ) IF %PYTHON_ARCH% == 64 ( IF %SET_SDK_64% == Y ( ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture SET DISTUTILS_USE_SDK=1 SET MSSdk=1 "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ELSE ( ECHO Using default MSVC build environment for 64 bit architecture ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ) ELSE ( ECHO Using default MSVC build environment for 32 bit architecture ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) Fiona-1.10.1/ci/000077500000000000000000000000001467206072700132345ustar00rootroot00000000000000Fiona-1.10.1/ci/gdal-compile.sh000066400000000000000000000027041467206072700161300ustar00rootroot00000000000000#!/bin/bash # Example usage: # GDAL_DIR=$PWD/gdal bash gdal_compile.sh 3.6.0rc2 set -e pushd . echo "Building GDAL ($1) from source..." BUILD_GDAL_DIR=gdal-${1:0:5} # Download PROJ if [[ $1 == "git" ]]; then git clone https://github.com/OSGeo/GDAL.git ${BUILD_GDAL_DIR} else curl https://download.osgeo.org/gdal/${1:0:5}/gdal-$1.tar.gz > ${BUILD_GDAL_DIR}.tar.gz tar zxf ${BUILD_GDAL_DIR}.tar.gz rm ${BUILD_GDAL_DIR}.tar.gz fi cd ${BUILD_GDAL_DIR} mkdir build cd build # build using cmake cmake .. \ -DCMAKE_INSTALL_PREFIX=$GDAL_DIR \ -DBUILD_SHARED_LIBS=ON \ -DCMAKE_BUILD_TYPE=Release \ -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF \ -DGDAL_ENABLE_DRIVER_MBTILES=OFF \ -DOGR_BUILD_OPTIONAL_DRIVERS=OFF \ -DOGR_ENABLE_DRIVER_CSV=ON \ -DOGR_ENABLE_DRIVER_DGN=ON \ -DOGR_ENABLE_DRIVER_DXF=ON \ -DOGR_ENABLE_DRIVER_FLATGEOBUF=ON \ -DOGR_ENABLE_DRIVER_GEOJSON=ON \ -DOGR_ENABLE_DRIVER_GML=ON \ -DOGR_ENABLE_DRIVER_GMT=ON \ -DOGR_ENABLE_DRIVER_GPKG=ON \ -DOGR_ENABLE_DRIVER_GPX=ON \ -DOGR_ENABLE_DRIVER_OPENFILEGDB=ON \ -DGDAL_ENABLE_DRIVER_PCIDSK=ON \ -DOGR_ENABLE_DRIVER_S57=ON \ -DOGR_ENABLE_DRIVER_SHAPE=ON \ -DOGR_ENABLE_DRIVER_SQLITE=ON \ -DOGR_ENABLE_DRIVER_TAB=ON \ -DOGR_ENABLE_DRIVER_VRT=ON \ -DBUILD_CSHARP_BINDINGS=OFF \ -DBUILD_PYTHON_BINDINGS=OFF \ -DBUILD_JAVA_BINDINGS=OFF cmake --build . -j$(nproc) cmake --install . # cleanup cd ../.. rm -rf ${BUILD_GDAL_DIR} popd Fiona-1.10.1/ci/rstcheck/000077500000000000000000000000001467206072700150425ustar00rootroot00000000000000Fiona-1.10.1/ci/rstcheck/requirements.in000066400000000000000000000000301467206072700201060ustar00rootroot00000000000000rstcheck[sphinx]==6.1.2 Fiona-1.10.1/ci/rstcheck/requirements.txt000066400000000000000000000030461467206072700203310ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile --strip-extras requirements.in # alabaster==0.7.13 # via sphinx babel==2.12.1 # via sphinx certifi==2024.7.4 # via requests charset-normalizer==3.2.0 # via requests click==8.1.7 # via typer colorama==0.4.6 # via typer commonmark==0.9.1 # via rich docutils==0.19 # via # rstcheck-core # sphinx idna==3.7 # via requests imagesize==1.4.1 # via sphinx jinja2==3.1.4 # via sphinx markupsafe==2.1.3 # via jinja2 packaging==23.1 # via sphinx pydantic==1.10.13 # via rstcheck-core pygments==2.16.1 # via # rich # sphinx requests==2.32.0 # via sphinx rich==12.6.0 # via typer rstcheck==6.1.2 # via -r requirements.in rstcheck-core==1.0.3 # via rstcheck shellingham==1.5.3 # via typer snowballstemmer==2.2.0 # via sphinx sphinx==7.2.5 # via # rstcheck # sphinxcontrib-applehelp # sphinxcontrib-devhelp # sphinxcontrib-htmlhelp # sphinxcontrib-qthelp # sphinxcontrib-serializinghtml sphinxcontrib-applehelp==1.0.7 # via sphinx sphinxcontrib-devhelp==1.0.5 # via sphinx sphinxcontrib-htmlhelp==2.0.4 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.6 # via sphinx sphinxcontrib-serializinghtml==1.1.9 # via sphinx typer==0.7.0 # via rstcheck types-docutils==0.19.1.9 # via rstcheck-core typing-extensions==4.7.1 # via pydantic urllib3==2.2.2 # via requests Fiona-1.10.1/docs/000077500000000000000000000000001467206072700135715ustar00rootroot00000000000000Fiona-1.10.1/docs/Makefile000066400000000000000000000130621467206072700152330ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: apidocs $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Fiona.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Fiona.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Fiona" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Fiona" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." apidocs: sphinx-apidoc -f -o . ../fiona @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." Fiona-1.10.1/docs/README.rst000066400000000000000000000001251467206072700152560ustar00rootroot00000000000000.. include:: ../README.rst .. include:: ../CHANGES.txt .. include:: ../CREDITS.txt Fiona-1.10.1/docs/cli.rst000066400000000000000000000527731467206072700151100ustar00rootroot00000000000000Command Line Interface ====================== Fiona's new command line interface is a program named "fio". .. code-block:: console Usage: fio [OPTIONS] COMMAND [ARGS]... Fiona command line interface. Options: -v, --verbose Increase verbosity. -q, --quiet Decrease verbosity. --aws-profile TEXT Select a profile from the AWS credentials file --aws-no-sign-requests Make requests anonymously --aws-requester-pays Requester pays data transfer costs --version Show the version and exit. --gdal-version Show the version and exit. --python-version Show the version and exit. --help Show this message and exit. Commands: bounds Print the extent of GeoJSON objects calc Calculate GeoJSON property by Python expression cat Concatenate and print the features of datasets collect Collect a sequence of features. distrib Distribute features from a collection. dump Dump a dataset to GeoJSON. env Print information about the fio environment. filter Evaluate pipeline expressions to filter GeoJSON features. info Print information about a dataset. insp Open a dataset and start an interpreter. load Load GeoJSON to a dataset in another format. ls List layers in a datasource. map Map a pipeline expression over GeoJSON features. reduce Reduce a stream of GeoJSON features to one value. rm Remove a datasource or an individual layer. It is developed using the ``click`` package. bounds ------ The bounds command reads LF or RS-delimited GeoJSON texts, either features or collections, from stdin and prints their bounds with or without other data to stdout. With no options, it works like this: .. code-block:: console $ fio cat docs/data/test_uk.shp | head -n 1 \ > | fio bounds [0.735, 51.357216, 0.947778, 51.444717] Using ``--with-id`` gives you .. code-block:: console $ fio cat docs/data/test_uk.shp | head -n 1 \ > | fio bounds --with-id {"id": "0", "bbox": [0.735, 51.357216, 0.947778, 51.444717]} calc ---- The calc command creates a new property on GeoJSON features using the specified expression. The expression is evaluated in a restricted namespace containing 4 functions (`sum`, `pow`, `min`, `max`), the `math` module, the shapely `shape` function, type conversions (`bool`, `int`, `str`, `len`, `float`), and an object `f` representing the feature to be evaluated. This `f` object allows access in javascript-style dot notation for convenience. The expression will be evaluated for each feature and its return value will be added to the properties as the specified property_name. Existing properties will not be overwritten by default (an `Exception` is raised). .. code-block:: console $ fio cat data.shp | fio calc sumAB "f.properties.A + f.properties.B" .. note:: ``fio calc`` requires installation of the "calc" set of extra requirements that will be installed by ``pip install fiona[calc]``. cat --- The cat command concatenates the features of one or more datasets and prints them as a `JSON text sequence `__ of features. In other words: GeoJSON feature objects, possibly pretty printed, optionally separated by ASCII RS (\x1e) chars using `--rs`. The output of ``fio cat`` can be piped to ``fio load`` to create new concatenated datasets. .. code-block:: console $ fio cat docs/data/test_uk.shp docs/data/test_uk.shp \ > | fio load /tmp/double.shp --driver Shapefile $ fio info /tmp/double.shp --count 96 $ fio info docs/data/test_uk.shp --count 48 The cat command provides optional methods to filter data, which are different to the ``fio filter`` tool. A bounding box ``--bbox w,s,e,n`` tests for a spatial intersection with the geometries. An attribute filter ``--where TEXT`` can use an `SQL WHERE clause `__. If more than one datasets is passed to ``fio cat``, the attributes used in the WHERE clause must be valid for each dataset. collect ------- The collect command takes a JSON text sequence of GeoJSON feature objects, such as the output of ``fio cat`` and writes a GeoJSON feature collection. .. code-block:: console $ fio cat docs/data/test_uk.shp docs/data/test_uk.shp \ > | fio collect > /tmp/collected.json $ fio info /tmp/collected.json --count 96 distrib ------- The inverse of fio-collect, fio-distrib takes a GeoJSON feature collection and writes a JSON text sequence of GeoJSON feature objects. .. code-block:: console $ fio info --count tests/data/coutwildrnp.shp 67 $ fio cat tests/data/coutwildrnp.shp | fio collect | fio distrib | wc -l 67 dump ---- The dump command reads a vector dataset and writes a GeoJSON feature collection to stdout. Its output can be piped to ``fio load`` (see below). .. code-block:: console $ fio dump docs/data/test_uk.shp --indent 2 --precision 2 | head { "features": [ { "geometry": { "coordinates": [ [ [ 0.9, 51.36 ], You can optionally dump out JSON text sequences using ``--x-json-seq``. Since version 1.4.0, ``fio cat`` is the better tool for generating sequences. .. code-block:: console $ fio dump docs/data/test_uk.shp --precision 2 --x-json-seq | head -n 2 {"geometry": {"coordinates": [[[0.9, 51.36], [0.89, 51.36], [0.79, 51.37], [0.78, 51.37], [0.77, 51.38], [0.76, 51.38], [0.75, 51.39], [0.74, 51.4], [0.73, 51.41], [0.74, 51.43], [0.75, 51.44], [0.76, 51.44], [0.79, 51.44], [0.89, 51.42], [0.9, 51.42], [0.91, 51.42], [0.93, 51.4], [0.94, 51.39], [0.94, 51.38], [0.95, 51.38], [0.95, 51.37], [0.95, 51.37], [0.94, 51.37], [0.9, 51.36], [0.9, 51.36]]], "type": "Polygon"}, "id": "0", "properties": {"AREA": 244820.0, "CAT": 232.0, "CNTRY_NAME": "United Kingdom", "FIPS_CNTRY": "UK", "POP_CNTRY": 60270708.0}, "type": "Feature"} {"geometry": {"coordinates": [[[-4.66, 51.16], [-4.67, 51.16], [-4.67, 51.16], [-4.67, 51.17], [-4.67, 51.19], [-4.67, 51.19], [-4.67, 51.2], [-4.66, 51.2], [-4.66, 51.19], [-4.65, 51.16], [-4.65, 51.16], [-4.65, 51.16], [-4.66, 51.16]]], "type": "Polygon"}, "id": "1", "properties": {"AREA": 244820.0, "CAT": 232.0, "CNTRY_NAME": "United Kingdom", "FIPS_CNTRY": "UK", "POP_CNTRY": 60270708.0}, "type": "Feature"} info ---- The info command prints information about a dataset as a JSON object. .. code-block:: console $ fio info docs/data/test_uk.shp --indent 2 { "count": 48, "crs": "+datum=WGS84 +no_defs +proj=longlat", "driver": "ESRI Shapefile", "bounds": [ -8.621389, 49.911659, 1.749444, 60.844444 ], "schema": { "geometry": "Polygon", "properties": { "CAT": "float:16", "FIPS_CNTRY": "str:80", "CNTRY_NAME": "str:80", "AREA": "float:15.2", "POP_CNTRY": "float:15.2" } } } You can process this JSON using, e.g., `underscore-cli `__. .. code-block:: console $ fio info docs/data/test_uk.shp | underscore extract count 48 You can also optionally get single info items as plain text (not JSON) strings .. code-block:: console $ fio info docs/data/test_uk.shp --count 48 $ fio info docs/data/test_uk.shp --bounds -8.621389 49.911659 1.749444 60.844444 load ---- The load command reads GeoJSON features from stdin and writes them to a vector dataset using another format. .. code-block:: console $ fio dump docs/data/test_uk.shp \ > | fio load /tmp/test.shp --driver Shapefile This command also supports GeoJSON text sequences. RS-separated sequences will be detected. If you want to load LF-separated sequences, you must specify ``--x-json-seq``. .. code-block:: console $ fio cat docs/data/test_uk.shp | fio load /tmp/foo.shp --driver Shapefile $ fio info /tmp/foo.shp --indent 2 { "count": 48, "crs": "+datum=WGS84 +no_defs +proj=longlat", "driver": "ESRI Shapefile", "bounds": [ -8.621389, 49.911659, 1.749444, 60.844444 ], "schema": { "geometry": "Polygon", "properties": { "AREA": "float:24.15", "CNTRY_NAME": "str:80", "POP_CNTRY": "float:24.15", "FIPS_CNTRY": "str:80", "CAT": "float:24.15" } } } The underscore-cli process command is another way of turning a GeoJSON feature collection into a feature sequence. .. code-block:: console $ fio dump docs/data/test_uk.shp \ > | underscore process \ > 'each(data.features,function(o){console.log(JSON.stringify(o))})' \ > | fio load /tmp/test-seq.shp --x-json-seq --driver Shapefile filter ------ For each feature read from stdin, filter evaluates a pipeline of one or more steps described using methods from the Shapely library in Lisp-like expressions. If the pipeline expression evaluates to True, the feature passes through the filter. Otherwise the feature does not pass. For example, this pipeline expression .. code-block:: console $ fio cat zip+https://s3.amazonaws.com/fiona-testing/coutwildrnp.zip \ | fio filter '< (distance g (Point -109.0 38.5)) 100' lets through all features that are less than 100 meters from the given point and filters out all other features. *New in version 1.10*: these parenthesized list expressions. The older style Python expressions like .. code-block:: 'f.properties.area > 1000.0' are deprecated and will not be supported in version 2.0. Note this tool is different from ``fio cat --where TEXT ...``, which provides SQL WHERE clause filtering of feature attributes. .. note:: ``fio filter`` requires installation of the "calc" set of extra requirements that will be installed by ``pip install fiona[calc]``. map --- For each feature read from stdin, ``fio map`` applies a transformation pipeline and writes a copy of the feature, containing the modified geometry, to stdout. For example, polygonal features can be roughly "cleaned" by using a ``buffer g 0`` pipeline. .. code-block:: console $ fio cat zip+https://s3.amazonaws.com/fiona-testing/coutwildrnp.zip \ | fio map 'buffer g 0' *New in version 1.10*. .. note:: ``fio map`` requires installation of the "calc" set of extra requirements that will be installed by ``pip install fiona[calc]``. reduce ------ Given a sequence of GeoJSON features (RS-delimited or not) on stdin this prints a single value using a provided transformation pipeline. The set of geometries of the input features in the context of these expressions is named ``c``. For example, the pipeline expression .. code-block:: console $ fio cat zip+https://s3.amazonaws.com/fiona-testing/coutwildrnp.zip \ | fio reduce 'unary_union c' dissolves the geometries of input features. *New in version 1.10*. .. note:: ``fio reduce`` requires installation of the "calc" set of extra requirements that will be installed by ``pip install fiona[calc]``. rm -- The rm command deletes an entire datasource or a single layer in a multi-layer datasource. If the datasource is composed of multiple files (e.g. an ESRI Shapefile) all of the files will be removed. .. code-block:: console $ fio rm countries.shp $ fio rm --layer forests land_cover.gpkg Expressions and functions ------------------------- ``fio filter``, ``fio map``, and ``fio reduce`` expressions take the form of parenthesized lists that may contain other expressions. The first item in a list is the name of a function or method, or an expression that evaluates to a function. The second item is the function's first argument or the object to which the method is bound. The remaining list items are the positional and keyword arguments for the named function or method. The list of functions and callables available in an expression includes: * Python operators such as ``+``, ``/``, and ``<=`` * Python builtins such as ``dict``, ``list``, and ``map`` * All public functions from itertools, e.g. ``islice``, and ``repeat`` * All functions importable from Shapely 2.0, e.g. ``Point``, and ``unary_union`` * All methods of Shapely geometry classes * Functions specific to Fiona Expressions are evaluated by ``fiona.features.snuggs.eval()``. Let's look at some examples using that function. .. note:: The outer parentheses are not optional within ``snuggs.eval()``. .. note:: ``snuggs.eval()`` does not use Python's builtin ``eval()`` but isn't intended to be a secure computing environment. Expressions which access the computer's filesystem and create new processes are possible. Builtin Python functions ------------------------ ``bool()`` .. code-block:: python >>> snuggs.eval('(bool 0)') False ``range()`` .. code-block:: python >>> snuggs.eval('(range 1 4)') range(1, 4) ``list()`` .. code-block:: python >>> snuggs.eval('(list (range 1 4))') [1, 2, 3] Values can be bound to names for use in expressions. .. code-block:: python >>> snuggs.eval('(list (range start stop))', start=0, stop=5) [0, 1, 2, 3, 4] Itertools functions ------------------- Here's an example of using ``itertools.repeat()``. .. code-block:: python >>> snuggs.eval('(list (repeat "*" times))', times=6) ['*', '*', '*', '*', '*', '*'] Shapely functions ----------------- Here's an expression that evaluates to a Shapely Point instance. .. code-block:: python >>> snuggs.eval('(Point 0 0)') The expression below evaluates to a MultiPoint instance. .. code-block:: python >>> snuggs.eval('(union (Point 0 0) (Point 1 1))') Functions specific to fiona --------------------------- The fio CLI introduces four new functions not available in Python's standard library, or Shapely: ``collect()``, ``dump()``, ``identity()``, and ``vertex_count()``. The ``collect()`` function turns a list of geometries into a geometry collection and ``dump()`` does the inverse, turning a geometry collection into a sequence of geometries. .. code-block:: python >>> snuggs.eval('(collect (Point 0 0) (Point 1 1))') >>> snuggs.eval('(list (dump (collect (Point 0 0) (Point 1 1))))') [, ] The ``identity()`` function returns its single argument. .. code-block:: python >>> snuggs.eval('(identity 42)') 42 To count the number of vertices in a geometry, use ``vertex_count()``. .. code-block:: python >>> snuggs.eval('(vertex_count (Point 0 0))') 1 The ``area()``, ``buffer()``, ``distance()``, ``length()``, ``simplify()``, and ``set_precision()`` functions shadow, or override, functions from the shapely module. They automatically reproject geometry objects from their natural coordinate reference system (CRS) of ``OGC:CRS84`` to ``EPSG:6933`` so that the shapes can be measured or modified using meters as units. ``buffer()`` dilates (or erodes) a given geometry, with coordinates in decimal longitude and latitude degrees, by a given distance in meters. .. code-block:: python >>> snuggs.eval('(buffer (Point 0 0) :distance 100)') The ``area()`` and ``length()`` of this polygon have units of square meter and meter. .. code-block:: python >>> snuggs.eval('(area (buffer (Point 0 0) :distance 100))') 31214.451487413342 >>> snuggs.eval('(length (buffer (Point 0 0) :distance 100))') 627.3096977558143 The ``distance()`` between two geometries is in meters. .. code-block:: python >>> snuggs.eval('(distance (Point 0 0) (Point 0.1 0.1))') 15995.164946207413 A geometry can be simplified to a tolerance value in meters using ``simplify()``. There are more examples of this function later in this document. .. code-block:: python >>> snuggs.eval('(simplify (buffer (Point 0 0) :distance 100) :tolerance 100)') The ``set_precision()`` function snaps a geometry to a fixed precision grid with a size in meters. .. code-block:: python >>> snuggs.eval('(set_precision (Point 0.001 0.001) :grid_size 500)') Feature and geometry context for expressions -------------------------------------------- ``fio filter`` and ``fio map`` evaluate expressions in the context of a GeoJSON feature and its geometry attribute. These are named ``f`` and ``g``. For example, here is an expression that tests whether the input feature is within 62.5 kilometers of the given point. .. code-block:: lisp < (distance g (Point 4 43)) 62.5E3 ``fio reduce`` evaluates expressions in the context of the sequence of all input geometries, named ``c``. For example, this expression dissolves input geometries using Shapely's ``unary_union``. .. code-block:: lisp unary_union c Coordinate Reference System Transformations ------------------------------------------- The ``fio cat`` command can optionally transform feature geometries to a new coordinate reference system specified with ``--dst_crs``. The ``fio collect`` command can optionally transform from a coordinate reference system specified with ``--src_crs`` to the default WGS84 GeoJSON CRS. Like collect, ``fio load`` can accept non-WGS84 features, but as it can write files in formats other than GeoJSON, you can optionally specify a ``--dst_crs``. For example, the WGS84 features read from docs/data/test_uk.shp, .. code-block:: console $ fio cat docs/data/test_uk.shp --dst_crs EPSG:3857 \ > | fio collect --src_crs EPSG:3857 > /tmp/foo.json make a detour through EPSG:3857 (Web Mercator) and are transformed back to WGS84 by fio cat. The following, .. code-block:: console $ fio cat docs/data/test_uk.shp --dst_crs EPSG:3857 \ > | fio load --src_crs EPSG:3857 --dst_crs EPSG:4326 --driver Shapefile \ > /tmp/foo.shp does the same thing, but for ESRI Shapefile output. Sizing up and simplifying shapes -------------------------------- The following examples use the program ``jq`` and a 25-feature shapefile. You can get the data from from `rmnp.zip `__ or access it in a streaming fashion as shown in the examples below. Counting vertices in a feature collection +++++++++++++++++++++++++++++++++++++++++ The builtin ``vertex_count()`` function, in conjunction with ``fio map``'s ``--raw`` option, prints out the number of vertices in each feature. The default for fio-map is to wrap the result of every evaluated expression in a GeoJSON feature; ``--raw`` disables this. The program ``jq`` provides a nice way of summing the sequence of numbers. .. code-block:: console fio cat zip+https://github.com/Toblerity/Fiona/files/14749922/rmnp.zip \ | fio map 'vertex_count g' --raw \ | jq -s 'add' 28915 Here's what the RMNP wilderness patrol zones features look like in QGIS. .. image:: img/zones.png Counting vertices after making a simplified buffer ++++++++++++++++++++++++++++++++++++++++++++++++++ One traditional way of simplifying an area of interest is to buffer and simplify. There's no need to use ``jq`` here because ``fio reduce`` prints out a sequence of exactly one feature. The effectiveness of this method depends a bit on the nature of the data, especially the distance between vertices. The total length of the perimeters of all zones is 889 kilometers. .. code-block:: console fio cat zip+https://github.com/Toblerity/Fiona/files/14749922/rmnp.zip \ | fio map 'length g' --raw \ | jq -s 'add' 889332.0900809917 The mean distance between vertices on the edges of zones is 889332 / 28915, or 30.7 meters. You need to buffer and simplify by this value or more to get a significant reduction in the number of vertices. Choosing 40 as a buffer distance and simplification tolerance results in a shape with 469 vertices. It's a suitable area of interest for applications that require this number to be less than 500. .. code-block:: console fio cat zip+https://github.com/Toblerity/Fiona/files/14749922/rmnp.zip \ | fio reduce 'unary_union c' \ | fio map 'simplify (buffer g 40) 40' \ | fio map 'vertex_count g' --raw 469 .. image:: img/simplified-buffer.png Counting vertices after dissolving convex hulls of features +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Convex hulls are an easy means of simplification as there are no distance parameters to tweak. The ``--dump-parts`` option of ``fio map`` turns the parts of multi-part features into separate single-part features. This is one of the ways in which fio-map can multiply its inputs, printing out more features than it receives. .. code-block:: console fio cat zip+https://github.com/Toblerity/Fiona/files/14749922/rmnp.zip \ | fio map 'convex_hull g' --dump-parts \ | fio reduce 'unary_union c' \ | fio map 'vertex_count g' --raw 157 .. image:: img/convex.png Counting vertices after dissolving concave hulls of features ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Convex hulls simplify, but also dilate concave areas of interest. They fill the "bays", so to speak, and this can be undesirable. Concave hulls do a better job at preserving the concave nature of a shape and result in a smaller increase of area. .. code-block:: console fio cat zip+https://github.com/Toblerity/Fiona/files/14749922/rmnp.zip \ | fio map 'concave_hull g :ratio 0.4' --dump-parts \ | fio reduce 'unary_union c' \ | fio map 'vertex_count g' --raw 301 .. image:: img/concave.png Fiona-1.10.1/docs/conf.py000066400000000000000000000221711467206072700150730ustar00rootroot00000000000000# # Fiona documentation build configuration file, created by # sphinx-quickstart on Mon Dec 26 12:16:26 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('..')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx_click', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Fiona' copyright = '2011, Sean Gillies' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. try: release = fiona.__version__ except: with open('../fiona/__init__.py') as f: for line in f: if line.find("__version__") >= 0: version = line.split("=")[1].strip() version = version.strip('"') version = version.strip("'") continue # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. #html_theme = 'default' #html_theme = 'sphinxdoc' html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Fionadoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Fiona.tex', 'Fiona Documentation', 'Sean Gillies', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'fiona', 'Fiona Documentation', ['Sean Gillies'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Fiona', 'Fiona Documentation', 'Sean Gillies', 'Fiona', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = 'Fiona' epub_author = 'Sean Gillies' epub_publisher = 'Sean Gillies' epub_copyright = '2011, Sean Gillies' # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. #epub_identifier = '' # A unique identification for the text. #epub_uid = '' # A tuple containing the cover image and cover page html template filenames. #epub_cover = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files that should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. #epub_exclude_files = [] # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 # Allow duplicate toc entries. #epub_tocdup = True Fiona-1.10.1/docs/encoding.txt000066400000000000000000000036251467206072700161260ustar00rootroot00000000000000========================= Fiona and String Encoding ========================= Reading ------- With Fiona, all 'str' type record attributes are unicode strings. The source data is encoded in some way. It might be a standard encoding (ISO-8859-1 or UTF-8) or it might be a format-specific encoding. How do we get from encoded strings to Python unicode? :: encoded File | (decode?) OGR (encode?) | (decode) Fiona E_f R E_i The internal encoding `E_i` is used by the ``FeatureBuilder`` class to create Fiona's record dicts. `E_f` is the encoding of the data file. `R` is ``True`` if OGR is recoding record attribute values to UTF-8 (a recent feature that isn't implemented for all format drivers, hence the question marks in the sketch above), else ``False``. The value of E_i is determined like this:: E_i = (R and 'utf-8') or E_f In the real world of sloppy data, we may not know the exact encoding of the data file. Fiona's best guess at it is this:: E_f = E_u or (R and E_o) or (S and 'iso-8859-1') or E_p `E_u`, here, is any encoding provided by the programmer (through the ``Collection`` constructor). `E_o` is an encoding detected by OGR (which doesn't provide an API to get the detected encoding). `S` is ``True`` if the file is a Shapefile (because that's the format default). `E_p` is locale.getpreferredencoding(). Bottom line: if you know that your data file has an encoding other than ISO-8859-1, specify it. If you don't know what the encoding is, you can let the format driver try to figure it out (Requires GDAL 1.9.1+). Writing ------- On the writing side:: Fiona (encode) | (decode?) OGR (encode?) | encoded File E_i R E_f We derive `E_i` from `R` and `E_f` again as above. `E_f` is:: E_f = E_u or (S and 'iso-8859-1') or E_p Appending --------- The diagram is the same as above, but `E_f` is as in the Reading section. Fiona-1.10.1/docs/fiona.fio.rst000066400000000000000000000044571467206072700162050ustar00rootroot00000000000000fiona.fio package ================= Submodules ---------- fiona.fio.bounds module ----------------------- .. automodule:: fiona.fio.bounds :members: :undoc-members: :show-inheritance: fiona.fio.calc module --------------------- .. automodule:: fiona.fio.calc :members: :undoc-members: :show-inheritance: fiona.fio.cat module -------------------- .. automodule:: fiona.fio.cat :members: :undoc-members: :show-inheritance: fiona.fio.collect module ------------------------ .. automodule:: fiona.fio.collect :members: :undoc-members: :show-inheritance: fiona.fio.distrib module ------------------------ .. automodule:: fiona.fio.distrib :members: :undoc-members: :show-inheritance: fiona.fio.dump module --------------------- .. automodule:: fiona.fio.dump :members: :undoc-members: :show-inheritance: fiona.fio.env module -------------------- .. automodule:: fiona.fio.env :members: :undoc-members: :show-inheritance: fiona.fio.filter module ----------------------- .. automodule:: fiona.fio.filter :members: :undoc-members: :show-inheritance: fiona.fio.helpers module ------------------------ .. automodule:: fiona.fio.helpers :members: :undoc-members: :show-inheritance: fiona.fio.info module --------------------- .. automodule:: fiona.fio.info :members: :undoc-members: :show-inheritance: fiona.fio.insp module --------------------- .. automodule:: fiona.fio.insp :members: :undoc-members: :show-inheritance: fiona.fio.load module --------------------- .. automodule:: fiona.fio.load :members: :undoc-members: :show-inheritance: fiona.fio.ls module ------------------- .. automodule:: fiona.fio.ls :members: :undoc-members: :show-inheritance: fiona.fio.main module --------------------- .. automodule:: fiona.fio.main :members: :undoc-members: :show-inheritance: fiona.fio.options module ------------------------ .. automodule:: fiona.fio.options :members: :undoc-members: :show-inheritance: fiona.fio.rm module ------------------- .. automodule:: fiona.fio.rm :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: fiona.fio :members: :undoc-members: :show-inheritance: Fiona-1.10.1/docs/fiona.rst000066400000000000000000000043361467206072700154250ustar00rootroot00000000000000fiona package ============= Subpackages ----------- .. toctree:: fiona.fio Submodules ---------- fiona.collection module ----------------------- .. automodule:: fiona.collection :members: :undoc-members: :show-inheritance: fiona.compat module ------------------- .. automodule:: fiona.compat :members: :undoc-members: :show-inheritance: fiona.crs module ---------------- .. automodule:: fiona.crs :members: :undoc-members: :show-inheritance: fiona.drvsupport module ----------------------- .. automodule:: fiona.drvsupport :members: :undoc-members: :show-inheritance: fiona.env module ---------------- .. automodule:: fiona.env :members: :undoc-members: :show-inheritance: fiona.errors module ------------------- .. automodule:: fiona.errors :members: :undoc-members: :show-inheritance: fiona.inspector module ---------------------- .. automodule:: fiona.inspector :members: :undoc-members: :show-inheritance: fiona.io module --------------- .. automodule:: fiona.io :members: :undoc-members: :show-inheritance: fiona.logutils module --------------------- .. automodule:: fiona.logutils :members: :undoc-members: :show-inheritance: fiona.ogrext module ------------------- .. automodule:: fiona.ogrext :members: :undoc-members: :show-inheritance: fiona.path module ----------------- .. automodule:: fiona.path :members: :undoc-members: :show-inheritance: fiona.rfc3339 module -------------------- .. automodule:: fiona.rfc3339 :members: :undoc-members: :show-inheritance: fiona.schema module ------------------- .. automodule:: fiona.schema :members: :undoc-members: :show-inheritance: fiona.session module -------------------- .. automodule:: fiona.session :members: :undoc-members: :show-inheritance: fiona.transform module ---------------------- .. automodule:: fiona.transform :members: :undoc-members: :show-inheritance: fiona.vfs module ---------------- .. automodule:: fiona.vfs :members: :undoc-members: :show-inheritance: fiona module ------------ .. automodule:: fiona :members: :undoc-members: :show-inheritance: Fiona-1.10.1/docs/img/000077500000000000000000000000001467206072700143455ustar00rootroot00000000000000Fiona-1.10.1/docs/img/concave.png000066400000000000000000001311471467206072700165000ustar00rootroot00000000000000PNG  IHDRG%C pHYsod IDATxw|wH9d{e'h]aPA϶Z uԶZn $ae''`~\劬$3^# sA}>'dpppPAb:I# QF9j# QF9j# QF9j# QF9j# QF9j6QOOv"""dZ82#'k*++Ӧͥjm\k֬U|b>߶S}}=&^M8Q94i󕔔d:>#!C٩2 tܚuB&0EؿMh[}Z{USMY,vhf1/Pvv 0Z[[qF TVVgӘq;9vbKzk*w(;gf8FG)??_CYF9MccTZAmX7C".JJ ک-)7)<}fr =EglGڸqBB4vBR5fCc9o?^UlnWME]QNt58(??_c@9YEEʴS~e[1JMPƌs(2~PNUmRuŠ7R|| f hhĉca@9ƍw!>цOicR"5f|R>ީN9Rw[[:TW9-jiP^~ff̘Yf$~ic*-]M4aRF+m>ޥYm!9-ҥFM[ty颋.~r ޾~Rm#mP]vVk՘N?1{Pu9g+b nǎ_6nܨ;Z5~bRdžhX'86cݚmZZFzMrݭZm߾]|ў"dzz5vBRjPw*>)t܀ k: 8H#VyQuVT~ɇO+##tp#թV55U)W]]jd[R|MJH+!)TIvEMGǗtvEM:kq7`Uuuk*TWW/wKWBC IV& *>Ѯ0%$@3c{|YQ~P0L6oެ TA;wzP&H!JH S|]I @v-[lϾe: # z'ר^ҥHB Aj`@ڰtHEEY(ogN==\c9MGWeZQP0,Q,DiҴpqvӽ UR(`nuZ4at$E6eE@PX _U^^q4Q2FԷRz T@ʕ%ZFetڹYUlILG (Pڶm۴`jTiH'Hx)?%t h۵xK:}(̈4?^~Mu@@@y嗔-ORtDO<QhLƆ tsuKy 5nO~k짳MG Q^gg,^? 4vt$:|aa}jooWbb*]0\(G/=zHg^Y'DjѥU'mQ0{=}Z7ߝ$GxwWnڥiq3zRSSİi  -+Z oԌcY|Àk挣MGajiiQQ2-Y>o֩'id~?ӧ>hю^hTkW͍PJJRRR1c+-5MiiiJIIQRR~_(GV ^z>7J#CuWA&M6iƍ**Zwm۶)gF2ʙ9tBtW;zw(Mj~hҎ%)55Yiic4:mF+--MJKKj<O(GfύVF.hEoQl:Aqq]//RdEsmʞ2vԣ-}jiю^hGsZzUtLRR'c5zؽvr <*+I͉3>5_2{477=.bjHTfU93"=f:AIZ{Y{CVUJMMSZjLDeee)11t|8l#jo[ٿԷouQƍl }v̈\rgLv6G]lJ?mSllN<,QPڎ;|d(M[j?VX%KT"EF[gWvSA&K׷ӵl:4_C_4(u]zMG X_ywZbg&)#ߪŌ篳ȣ)sCANǟa: `nSR)e?WΌ(eٔ3#0 m*z=Z=(p@#曯ּKFoޮMG[˗/ҥoiY2EXoWv+ wmIK-E9;: >-ߠ֬3o4559.󺊋SQIgUΌEp\f+s/jiע]͹8LcLG|ʚmj<[w(>TKKRcSr "Hޝ7lӕWܬ|TQ--=}t'f]p:SLGEEEZ-)*ƢPe+}<~h8cvp,-yXaaL0_SyVM@sJJJ2eH466jY2-YV|5=עQ5?kg ϿS QʙgL;SRR[٥^t~tCӣoںT}feq@s t)?2@bͦ)>踝z31hmmUUUU^Y[nTUe[Z5:=ZIv% j|]c:ar)~J򗗘 19+=SǞCӲ#LGƥnҧ^}vUTTB[b*+k58Чp%ٔ24Bj$'{69Zt}2#r/FeQ냕mj{Ӱ>Oee*++L|.%HIiV%*eCjluW"X dg窿1rQS'uѳ豺LT\[RUU>JɩKt SrY3A0w"ܡNKJ$ky?ǘyN>ںRԭT^IUjQH%ٕ<0%*%1̟ͮ} l0(G~k/S4QƋJO.ʽkhh=RQMڲuj% (utRmJLTrjajNMG(G~N7tv[ӧ婾VUUq(uK )RRE)iaJN Ux$;xld`xPUW_e:hQ9ڔPP3mrw QRMM]x ezŚvmX&C ~iر9Je: `p}Np, Pe^n:`MU 5cflb:A9[&LPfFֽf: `'K׻e:@9k_v~k:L?CϦs#6qDMF"Ve/ƛ6/G HE}9(MRI ,Ueec593D9սZY0PР~|Ffn:6\ңG&Ob:@mmޣ2W55۪N:´.#+/d;W`%͍zf'g7tNZJ+V,ժU%P85-+TsÇtSޯިS(G#7]ku_إ2} %&&~z/׊%ںRymp'^sQRdd=FΝ;u?׉߷)FӣUS٩]rɕ:LG jzzJ}*}FIf;ɓG,+.C9 wyK7|,LUrZ8 oU'YyIiOIUdL&Msj”ÿ7HqC4e :&Gn0θ Fe6+ Wwu~SMG k֬ooNNrWFhNӱpXn:`XtvvꬳOSQ0b2r#SSğ;.=l:Rڵk~{5޴QDߚq,xpuÍ7j9f7-EuDwwzzz_Nv[=]QwOzz8?ˏߧޞ^OKl6Vlnn(eC?>ǩX~prOpDn WGwNxi|~tOvU6Vle""=D6[>?J6d jlAY{J!Zw1xmcm{ofQH;C͗w. QpL۝x͞8q R]u{R57v-v  l6b6\lN!iwaϭVv߶).02cl @98d/޲@?=3I<0tΘqKHQ0,՘@98h}n.N)e `.jyQJ0t$pZBVK~o_ձ1$OpN)Gk-_^}J˟OL'p&P8\Vyc p*\0_=u8V&ӢN1p(G{qmzW5{n2Y pD.^^|ydcH ̼ӑS500`: bׯͅ)1KmlӑJ}]c: N-4{n$yeq ַuKu-Fp<8ˊēƊt4(ӑa5p_j^pQ`UfϋQF.yF)@z畝Zphe䲚PǠM09O?UJݥ"ռ&MP@Pa\NύRxVAI۷7X G}H?K JuDw=.v4@9~j鲥:cyI:xV" Y}jթp|uW.GLGn_t,y]ύRFn88˪6V<x甕bޗHӑpN:XռcVV*#>r|P{{n.ʏu(j^aS|Q IDATSc5cy:c4~\1X !*Pc:r|yԾX ,Ymuwwo0V wAWߒQ|i$.]^Waaa`??p0w/Вohe䰚 x<30QVVz%ZpheDapX:_O>X WJzTY3urFͅUQN JcŘpok0~O:c2q\1㜦#`'c: %Kf%I:8V^ob555*\0_} ŌyL7!t -͞٬j4*.T!!cps=곭Rij^pX*__?H Wڸ^񩩲XBLG HUcPaxܹt m6S%Տpj1c{91A9_isF3X P{{3K=3+ҭLWHӑr8$t #ڵj-7*m\|ӑr[Ɲ#r_hnn[n{[μ(JF0E&&G|j~iBf.)Q:LGeU[.1#|/9jES1\ a4`:c5|Χ~+!S<^az\PE ۶3vڥܠO_G*}BHp8,h0˱|O'|R&n֕ >i:0d.)G|/~MG*P]k :8@qz;M~pA\wx;$UwC~O;.UYY_&Y̔ {D^񘎁A9ޮ,UIIAMqhwC5i8ƌIܵ]X׭ճ/UcƤ(++[9=ap8GԫiN6p09(GS֯_ZQDU)]}KG?iud[5轏?ҋWU.%''(33+&.Xt֢koK6)Am"555X+Jje*״~8ۥ'7$R”&{ݭ ZSj*v)6n23Cyy1c؇l͚՚w<]y8M͊69<615V{喝H+jȍԴkvFVvOԇkuꜳU\oq=%7$7_nTuW}P`Ԩde/[W}cl>GPem}Zԣ%s\?tz:ӕYЫe:Ӗ"KIZPx(\ooJJJ|R,Ye:5-ۮXBLG;hn5m9gϞ~MpuiJc_UڐEk: A9!qFЊw~FΈהeF(>1tԣK*^C'p;B͜9ss\xׅŎsjZ6G~truƇiZ]ӳ49#tv.^֭09g>l{mtg#@]oF^0A[nJJVh;ְ]Yњ(EDZM Z]ZC+isyg?ޭKoLVb2@ҋЫ~trР+KT^O,2r]1V/o՚eݲZt3ǽʋѻYg_3${mZtr_QRRT\\.2r\cWFnBCwͮ3Mvq5y:u!?Ζ-uc5[f/O75LG>(G\yy2o>VDMͲ(#7\Iaa,٩UK9s.~?=tI@ߠ`6|Vf: A9d:::T\\e*)Y!Sad@ کU:u3gƎ‹(b66'zSs7O>e 84#A`*)Y+UEyc5%3Dy_`;wwivչs~k?Լyk(3CPZ-~mbbϗP&\YKd\mSFK's*v^֥y_o>KnLb:n^O%3t| c~n``@uuuھ}xvhQFnett9]753\S3ч^7'4*c:A9vܩ:թVR}[M;iъMљ4z?Haշg:NM>(G|5+hv.ݢdmMTlU'e).!NɦA_Yμ(L._D9c*uw{LG 0 j|t|չ睥KoRLDǠ<61rUΉVtt0p,xv}Pg:{7k"LG gULPWW_g"t0 suWsIci䈯1k/ՏNШ8@tY*_C9b /> uB(pZ*_C9b[gw(tYPC:U흦c`#̿ 3BMG ˢv/刯aji[cN00B,!tt| ھ}w2t!kc: BzMP nJGk:G8\k(GQV>k{ p爏FyQc|HDT:MP 'z\GFMG{h[>7_B92Ln}|idzt | 0ӟלM ˢ6_B92Z[[ 'GAp"MMMvj 1Aʑ!РD?T[[؃rd׳pP\.<;M#CWL?pZ񴚎=(GH]]5# m؃rdWpP.<#Cn*_B92D54)>1t p8zM#CIQY!3"t A924'.+trdH?`:CGP j#zc@#C5C 19#(G䈏j:8\&G|jjjRTCVk(?tPʑ#РD?X9Bn[l0v29bnwbMˢc@#GJ Li/9BLgUm #GnP\k|29#(GжF'Rea9͊ b: 8Vyc@#Gv+>e:9-z;Lǀ(GVjRʑ#v+&nt  _=ʑ#P[[X&Gʥ>rԻko3CzMz#GnP\k|ncrPm O9L͊ b: O9]!Lʑv2#>r0nűp#WLܠ?p i3#Q*298NEmmc=ʑTQ\t s,jPF9r%p.v(GӶF'R#>r0477+"2LV[(?tY)G|ap݊OtsE^oAr0n%p+8]V@9rz s.U^oAr0V)6j:υYݫ^7r0Իkϱ 1#QAq 9Wr0ʑðm[`H\#Qf1v(GV|t @p:-LF9rv#v1 1re`h81rV).?6s i3#]!wװ0dyxv?G9*+@Rl6ʑ.j: *44pPN?{_uy:䜓q0T"E⪭=,:KUZA(3 $';g$'g`Ԫ@9缞-&4>ϕq8#9)i˗ jrT$&_k5p[MD`͙#ѕH&JssXRn7HN tFTHR{{r$b1999$)GrI8gZѶgp8,_봵Ř%Hjh\tلH$"ri5#&ay<|֜7曎VLHker$ǴFeIkj|>iE9 HYX+M29KZ[[Xm IDATM kGSLH$"ru$H. L kG#$G9zѶ8H.immUa:Y-΁dZ _"ik19K0VŦ#ZM]S~|qـH$`#ǜ]ǜ]U C*}:#OƜ޽{}hT}w:.֙1Y?u'_vmg:q9pX/t ڽHg4xotƙ'i֬$ǣzR;O`L>4HnW%j#k9RTtl4m48~Q7QXjaHM_V6MriU`NL^?_F{zX=ve8Y D"L9bE{RykN^рCw}ȑ)p/>cٿt Yg7^̌Vh:mL̃ wײeoҩ09 XQU zZ ?L)39a=t*]tǛP+5D4ÝȼhTK}7=|tJ#ȴߴju/Qli^[OF9=9J 3&5j5vEZh8؄.GXӬ5:lٕ㠃rv]*%-[2zQJhźR$g'GX9KOh-i>FM}As>|>ӑT*e: 1FY]r*Euı]t%䄹o5jK:lkMGVɵ`0ŋ(F5z,PnnZvӑlw+ 7ň d9J |Ǻҿ;[4}RH-TnzPnLBZ +5 k3k.yMꔒɔsA e/]qu~Mǂr :ࡺrQ>cUWWuIcu ͍q-EUZ<^'9Z? ig9W x9(` Tiٲ/$I={aqQ78pwmZ4/ş+Ԓ!PeehȐ+GX Zww7o$)Wqz9W-jgQ-Vn5r5r d: ȩr$ itcLG55u G?t뭷̋{j ˬHB > >Nk9R={4T9J \zeTOתcHD\zZ.Q}֒ayX#F רQGjȑr\!T9J \L&|צOW^sn!c?ZY|&ӧQFkô>,3+5rٗKތB=K?ܥ*qQ{mdRZȈi'MczQ:ׯx$ra@[ixg{ٺOSkmkQ,C~#F0Ȭ)GX9 ;n?qWW}}> +p듼)y}) \TP%_sZsNJo۴h^HKjIGh!G’Cnɉrތ(S7p/~~ժUF BjiiQ0T0T(R0آ4%ج`E`H`HPD`|*(p-omqRכǗRAS%_Su.p_ZҢymZYX]u!Ш.d˅ /7a$i԰J]u_ڑHD`P---k E͍jniT0جfACaa­jkjn_]:J#GT=ҞX''Vj禽R>G /3R)b1盎4jPH-8wEZ4=A1}95iwj`^~yQcl_L#lPS3_70+51g-ӣEsk: q.GRv]ujMG2ȴ'iX?(HdmˑP( k8B=ccrd׵:RO9ard듴r::=d-Vj`~\7t #lYR[r萣zꡇ0-Vj`ql؟ʑp8 XvhȣXvO~CYt8w߽c]9J O~St2*GX;RG`k*GX8T9ףU9J XcKt߽4mVj:^S(ӓOt lSR:{=cigrdY \հ=ier$Lj=jiX u1ӑ?Dn1rR2gzsuƯը̣K**N!I%y*.SqK%%yr8@ғ:`t)ʑjllT]]{ PںժU}}P߬"J˼kK )S*.ɓ4O~J*.qI C O+чLGhjjYR[Pmj֨N joQAaJJH),ND*.SIRyr(R O4rsuɧl5ʑ-ܼ,iDJmjkT7Y[e>K8UTR?b̭bRŕGsW҇0=Ώrb`PVxU[X_֮HiVIO})}**v#/zty3dQB9Ed-Z@ Ӓ%vK} ԧ/ ׭s?1*#YZ/ )LdoC׉'1b#/&[-cZy#Q-F9bW֒%K6Rxշ + ֤>5p‰[r~\,X;_k}`aMj颰-j6ݺ'inο޻Ls1+LAھ'-7_FtaD_.IhwA >Z+/Ѝwo?죍:dN0l#X_%g]aCyXaR:eBjh~,װQ=c?B]W&G;iw>4ly^zW^5j^z3OO=I>Bq}8"z 5t0} }x</W PIvݔuuk]=ztqǛl&Ga?.LT͗Wa#5thzݡט0aޟ?idZC]LԠ~t`#|tغ C9d_#Q?TEnn֤Ic50SEF^]ܦca{2,#Ȩ@ZR#_vɓMG:rTZ`gS{t (GQuu*):n=շܩW_}tC(G1x\p Y4iӦcB9 *Zh: =_5dQMA֪t xu1MA9o]\zwL61k)Xm[z{F߮\˭5~#ȘڀI1X=i5^vzBt2fu*6@լCxNN?ty|r:dL Z™#|$GPg]c:(G1@@%eLه67Ե˶4|A#[rSWר.}LfOkW5l0 . 2"H(jUak5@gFL>,1,2"t -2YÇ'(c:`dD Pi6ۯZ5iB&<7m#AF_J l¥{d 2V)T,Ԥ Z0k^Gj:q#Ȉ@FťI1Ȍɍ>VW_}4qc(G5?ߝo7 }ILө 40UdDMjt -,[*;7t$ +P #ʷ`ŠoZ5ybP]c<~dV@J1)͍qMФ?d%X.L*VQ1WJʄF-M\yMGt_ Pc9ͩMeizo(F`rUit >zYLqNUUL:X.o5 U ClWzcƍ*++3 txbjkkU\j:`/+Wi}N;d:iQr@)1[}E'6p8LGlr Uڥ@҈&MhNФW=z,JRo5`SjVE5yb;nK6 ES&6hާ7*??t$ QR&M櫈L j޻h37jm5 Z#Tm*L kʳ-K{i:VWҞܦcFmJ]qFm: jURJ9 't+MG N`o@J{&Ol5\g:*.@nYMǟ`:  ڭt L ko6@19֪;[ +Ϲn: brQic@nO Za: ,'8s5ebP>1?ׇe8svѻMm}; e֬п~&Mlo4@ $I̞֬c=^]t1@09x2H^eƙ >,S_,aIu2@ \ {hniu湦H3X&lb]s54Q2xByy|65|:ߘ\&Olui:c}X&Z l`JWtIēLӛlA1XX&H@;{U^^n: 19Kqc@7z cOD,S^t `͜ҨǜQXX")M)^{1ŋ1@ Kq\|{sz]uc^a5k5|{iiѧQvꙦ^a jYMy6kt +,Z :ߴP~(2Ya5k512ybP㮻t Əax<.gY8E=А! Ø%t#D<h:x|%X@g2fpAaLG`#k5L^~Vs hoogrŒM:TXXh: C%\k:qXR'p0X"I9,7iB1F9Kp +]]]uɧ0X"V6٠CXP"XL.N@Z}ѣMG(G`D"!3e:ⱔ[c:,A9KVlO6@%S~KTVD}T/_?qRPP``Q\L=_]kX,QUKʡU1U WS߾۩Nq]D$I׾--ڕHOĕǔL&H$}"˕'))өáwH--A]|ѥiPvmUR֎ MGå]1QGt`}X"b3Uc/9t -(G`xY;n l#<`: (G`XLn7^vLz1Zt` Kb9̋j%0*#D<gv,Pa=ģ[rbcrǞR:Q-B9Kbr3]{`r-;RMӦr pf;l: Y(G`x<.o/ לǞ^NWX"V]v+F/(@Qs7ߤVQ%9Kzq{ : ^a nrۮ{)ASL2$X"OVS/Sգ=d: Q#D< ]p_xV:H% ӑ%≤8sy.CG\{Y+SnV(2 XWX"tJGW{؛6LG(G`X,Z _4R9 D˗/7 9riL&tRظa}UBqjҥc!9RTtK4վ{tg6ZջW. ^#!GP **]0@'x~PDTT.r Za:lriԤQGMGЉ}U֬"_#GAq]em\ߝQ*mlߴj攰yt$ ,K/oQ9FYj:Nn6M{)vk8X[iO gi׽MЉC xMG]sz4mu LGI$%d: lrѻwo=36gMGIS˗AFUVTsSi[ͦ#doҷ~k:lrFyZ0Zѭ׭_DLGItҷ~m:lrnw=zb|X6=KMǀP脣R_<֣W;1`##Gk}g(]}7aH6=Z|dK^7fKuM\f:,şիWAV*++?yX㮹[>s7(L Klj ҆rYmȐ}5m:xy{z{WtP m(G)9d-YTPxZ2 A=q믿06A9N]O=>wW=|Wc#0G/K1`#t~{=t74YrM^Z{1`#FwPJy$@3_VJqq:sϽ@|Wƭw lWot l[>ǛLG`USSc:lr2x`{EZMtz#z_7몛zb_JPt l#J.0@x|)-c(G`wu90t 9­z#6=2Qd֣-y-i\+p*dr[r^ss&O |.AZPӻNb: [39B9G9NVfT(F\ Mǀ PS_t  R`QJ&^{_SPt :-ө|"(  Aݶ;iἠ +az[rڰʃ )(p+:y[BgS#(:'T_WaCނ|ITPRQQ ]*,rq8OMHpM9ҳg`K#8`oU-נ݋LG+5LJMMMjiiQccTSӠ/بn݋xԽGj>~mzp:35\.U\\BEM|uuߛݨz+ӡ`>PQQ!1Vϒњ,KI-wd(&G` Çs/2]s j+-z5xyOקX,-,vi.# -d:RɔN {i3VHRYiW?8j0#bU;LrOz?e|=_?u(G`Æ)G PUU ~_mG_7P^CMMM*--M#V>`-[7s/7H6k^[VZe͋#09k⻠ڣݕsZ]ޚ=G~t~E_;Pgk# [9+}YMZA~ňֈ'J:R]u j,y}dAGk٢v1[=E֣le -[j:` pBw-bA;ׯH?Y҂BSO9hQV#O~jVGMG: K8T\\H$f{!,}C9Vy. tZSO>>{c:F55gW1P-4a־ 6B_,@&EtnퟦtXQOu玬3ziң䓏-/dlB>-`s͚ڨ!{ѝҢKe‹UsssFA9q\kb 5xcr.`:_۶G(G`K ?TKpk Qsf7Sqq(Hm+GJJ:2vƉ{OXrTQQ/[{b5`'^ /1cg̑[Ҩߔ7(G`KTScL(@ft-RRRrDZj'f^# -4zFtŊKՖYj%zZ`GzPUҗKؘ%Cݫ\4eY{OQk+vV#J-b:ޜх\f:V)))QkqG2[rUZZ^ъoLG7_Evvѐ!CLG*{VsChuE:S 6RKMқuI5j8\S5;9^vD~wzF`P*+9w?DT0%-]wvxh%tE.=MfAQw_gYq#v@ysjH7t: t[tyߨ)f,GqIN9T }7r(G`{OX1o6%@WiR} 2䄊 -}i3_muם랛v斯(Kfi# uםҰ(sf77(-kFG91|h-[f:`ĿGtaV|X7(WCvdFZsYɑ}^@} 䔊JU-d%-YYr)f=+̙3fAN֭vsk rG^Sv[gm+4wCdcA6l- d!\%$T[ۤD\9B-YaKrΰʃlQ .\.% Ee9[krﯥ28FdZۥh4j:FVp幌NHҮ{5 n#I2ܑ:.#{CX#I ?TKҚ wV_nwYp>ЬYLǀ(G***l1rGZZNIm<͘jM6AN߿N5LG2drdqmʔ)ڮY`89pD'G$i-_BЍ7_m:e~Zׯk-79HU0q藚'N/N{|zJ)C*66VV={uwRLcv]%O5Ls7#]wSLTT:~iSF͙3w`W^y}l 9*Tr)C/=@z7a '#~ܢ^)1-vwS]ߣ|vZw7;"8|An:0jFUtʠr}v|Ю䄉Z!I#W>}cUt `̣c3rxoL G8Н[u"85Wߤh17c~b?^j:eP=Gwr8ANNfZW0GsjDJïM7ݮ^ڮNUoQs[]>_ɚk?@-%R`~&Ou (Km-7i:e0qKFLAg͇ݪX{t W89 F#W9xS~l:+#tҷf^N Xn %''߯@ }}| G ?/~=ŗ7}|'׫y} }S L_>5wܯ@@@_v\yeu%ݼRF؎_e:؅#ll3"ËІgFPTU7OAp#5'3FNWEYvsN+=$Ux/ 'GSO;I~ |(N 0i~9t B"qq@Ȼ;í3,Vn8 M0AG1Oo/o30\Aq@X?7nڭX,3]GnYz;/f:؅q@8o=ZŝS858 rz#A+l6EpW$rt≧h2^,`uuuNvav.z?hb&нa:؅q@X_WXpb1.#Ҽy426Ok?n7pwMg$K sN{\yS>]YV}#Wbb~ӳ\^lFtsϚNGsϽPk?m:_0z{Mg#-7߮'Ѿ&-ӮTV|t "waӔ=Ef:_0Xw@cn:qiԚ5kL 1111~gi:_pz#0q@Y?Ѷmm:9hj+yf)P#"7UO>̣}ͼ<0(&M֡;`2}^V,_Vg 0&M:@w?Q(b:VSct " ٩kjzj?L1ݜwzJKLg 0;---ڸq*++U^QbUUmVN^߮'TTHڴZYىJϴkt_MkWFVVF|]VSL10{vkƍ/}#ƍm+{2c4&#b0 69 DKΕjX9q7Bgbp8p8C񊉉Qll^bbbqwATV^*55+{]Pˮ4]i{~_ӥz甝m:aq_qG~t=~{r듻"*[- (.ήNSoWn~9v9cr:uh}:w*#k]JM(eWZ]) ~Ѣo508V`n}p젼?@`WoO@^FG+19zP>0X.;Ro|WS|e˖Y9V%$FkL]y0tIIzMg 1!O3#E108M6YcS#,V0Ft  ,]ũ,v=  ,Y1tV?F>)q#@Y~Ƥ_`o}b|o3G g4uF dLjɒ%SG l2M1tRⱾ8 H[?!^X dd( H,~)Mm:IJVo:!q>OxS9t )0]P8%K٣Mg!]q?NwBV00#a۷oWUU 'řNBV YQ#a˖-a3FBZ Y-|W`i#~ bq0T@2Sga1-Yc:yO1-]Di:y@6+_b`+3ۡQQSdᆬ ~<:h@X=G0`#!/,{EL0_|ALwociʋ<c%Xm:;^o@v;c`GaoâLga %`|s’kQ q&Eʕ;c~Pz18 ۷kdZLaqjijkU ,EG 0L?NVw’+N`Ga2mX6%{`Ga2i$k:;>'G0p#0;w}f:;<qFu~@x3Ga4w\}a x<~N`GadXtQhM cGս3J48 ysi3zU1i68 4%$$zK ,xSw 0SN>Mݦ3WCSr4i$)a#'}l B.}>|t 6oşi78r[p`V, ` 4iFĎVUy jgS͛o:aq0/ջscV`_C"#AgQS}m6^U; /^}t2^]ڪ`yնө-ܜ&=~}N8L)##@;LgA%-@aɮ+Ks308/G/~f:aq,8F;Ǩv[ (Yq#V  p૽|Nr:SG rܱǩ>Ju5vVw.F04G sEիwohƌY=z) j'ڲN%3G tEi.q?lSa308/ś- $lЩ οt۷)]ߡlM4t RRRtG[LFxKrjÀqbgc/N]tGNA`W*uk|ހ`XBBo/ei:2u5=zJ)Bļy~}o/j7 _Թt"bn>yպʴG WɦSAG4s,}hXf:4.myt" bbb3/*)ntzZv~óL 0!Af]xf*˸ksMg  L|>q);C~ {zJMg qr6MO?Tѽ75;s~f:#@Z1SrO+{tNAbTGGf>J/o:ZoSL BqY F/+/b:Z+s/281cjL_郷[5}LNAcرtםtS/ymY?p#@<@]{_u<cGm*(<)p#@8#uۛM˺tGH2wyz)pe:ɓM#@9?SZM xS##@igȩk?L mV;JGq@~_0olDזqjq`^䞤7_ ^554ѦS]G7c}v D^y^l: pjmk:azK]IwO6|͚5Sִ@++Ң;[cϛNq>Fe3[٩w^כ+3|%={tв|ijS=b ө±,208dtS8@4wα*^Sk08nN?:: L߈q$i*Y1կRxDtWLpZvxU(~k*w6?45Nإ3>|S<a!q.s>1yM[ ̿_0 ]f͚u<w*+xv뽦ScKllTU#}nA=WWN ϙ;XH_|n~MotG|sUm:Aj]yf-uL9t0(x^'ϙ8qv6{UH~^ա7_O֚N'G|ɬY3U#}U!G˖,7 :_2wα*呾Ԣ%ѽ_@\U:|s!_icTt %?.08+=c:lçznmP1cLÂqW4iƬW:TSa8`f͚0xټЃ4 ;{4g1*[7!_uġ?Wh:0q͚5KW7պ~f:0GأxhSEcM`5mw몋6W_UnN(NZsݦ306t[ZTZZ0q7={JH_pzwH|])@`&OlN~Zdw't TG|YfhÚlo7ߨ>y>|)@Pbj:ūK~\{|L,Xh:Z<79rrrycrH_PPU֭Ew7iժOp8LA#9ǨGۡWw?f2{\5gԸPO{ d0+xwۡ{M!q^9kִWz c:9#>]>UN1${GUݺ-ZMt9@QڨQnshWu͗?^k:yO9FkLgDڴ(G˖,7d*Y#}Ly%=w?d:\V`tAjVWOq69שt V89`͜5Cx/y-1Cq>3{MgD^]r=e>9@Xbf͚ H_=q6/Vj9@b4{c: l|]E4=Sct@XzEЃt xZ W3U[Wk?nWj>miTrSQWbUIJhŏP/ BnmҷO:S H^8f11 ~Դݭnu{*aTƍ=\睳PgVSSvTlQom֩JJ+1ŦQI>%$E)9%Z)}%n_^<'; ۯt/TO_ܺS5-l2m΋UFt=ӥtn̙sL#:]p:w\_XRsGHsW-V5oi{Z{"+CS>D3tSu|U}}jkkU[[-ݦ:544bG*)9Z IҨRh%/{}/n<<y܁;nnn֮qܽW^y>ѲcdGp+*:J^]pH?dU^$IDATݺ:TEE*.Z ڼFʉVFEYye:s 4mʋʵ| d"۲uN:iο,S nntkGoV}jjVsS3R\err)33S'5٬j57~b<ڡ(L (1٢%&E)itFۆ7ttٷ c-yy=-r|v >xdZ%.=Fg:_r8r:ct錕+))..ӹ8d?}á=~x:aPzoeVo׳OURRbm(Z*+R8e:PV]YN.!AN=hx])@b~SCǝ2x٣F{QKE;jjUC}S,WRff\.c8l߾}V5[USEuuuoTggF))ŮdF%KwRco{WvѪ;OW ,' !5x7l8[N#F~XV;M7u@oyd6Viid++7VRfN=fσ}X{t G ;Uο%?\~霠Ë v]8ByI5?Y12m޼Y|JE%T^Vb%cu*5rnVZ-8x)1t@!ZB._J|X<4FSƛNT=~FVգT'i…ڋ/G[I0GZ4m\]uMsVCCJJJTRR*--Ul2rc+ǩh᭵ţ/ޤgYB9v8vkzezs{:dZ&bSCmON;7n鬐j*=]ԜcuԜIݫm֣ꀜ, E|QYYJJhJJ6|FɕkWKrڕԨ=.=r;s|\RW_Ci85Z:"l轕mzNN?{qB^ee|^:}bܼuy=nQWT_P.P8Y&Lرc5uTӹCbӦM*++{ZNUmTWY9qȱ(#+JYNefsB˪;=2)qOV^+^֊(19JvGIԴd76zW 5^v:3j)Gy\s'oR]MjUW 5lQsSƍք 5p ѣG5*.Y"UnV$edE)3"WS Wչcr뽦S| ԨX[nڼR[TyG22Gj#ev+eL2\%&G c{z獝9(ӇcQ=4 F){tRPQnU}Eu<ڲirrUPP p󕝝m:7?bxK׫tQ΍U+YC)cזn-?я߀q0^l٢:WL=u[LR(F52 Q6Jګ~Z[^C?8,v銍_套^҃-[L.tjuC=lڭݪymkFP8YBnZ=DGbm(Zr57TvHed[%WCYl7PS]n]/0`/0ݎ;Рf566Q TF555E=n%&jTBF%DiHiHFGkFz^޷~)S?P&9vJ0{I B[[kt}8ZG *?oIII1}TUURlPQU(+7NefG++שtO7XکiՌG뚫o4`GkmmuNi:iMl/HCE[ںEy/RAA e:ChǎIWX[6*gl2se,)3DV> )wo@O-ڡ$t<"a#`X=ܿuꌳӆr߮:jmnӨQ/ `®Kb/$kU\^eU+ǩ\9vrJI毑ZsMO뤓c:~bʫ~MQfCɩъGz<~l}Ajҩ@<L}AN}WSS2]SZZ6+=˪(e:ujeh ';GZtܱ忿tA8)++ӫ_Ҧͥz$_#4:-Jɩ>ە112V*ʭjvkǎn‰/HrrII)(ߢ He(=K}8?*t.QgOGKM7ev@aAE[l͛isʴefmR#gl &$*)&Gjk,q(9)S}ĸ\.ӟ9*))QqzUYF^ePﲜs`V|UF{qs m6ܹSv]3 HssS&>UOoySeRfn22&Г~}oMB#@|S\NETQV#cpWRRM熕jv[ cׁ.UjjDCqb_kYYvhQVHeX^&(nuG^WT_WNI6P8I'NĉŽaH8S&*.Y (߬1i#ʱ+%r1yLz{k-wOc媠`®6?^III8*d6QyYnr㔑e?e+a:uPx<~:uux4v\ &` G.'#@kii;eRZ*)-VEVMRfvҳ7dRnUnG R]uvرY*/Ԅ S+==t28DRxתJRVnҳ\vG Gu5n5 ;5vKhBa0<a Iھ}JJJeFkTNިKrq(;שۯnlv-UmszkƎT~~& IW`Gn[*--Qi߰F^o6nbb"[g"") ŢqcAcǧ^Y:hAw2qƙtD(v+KҮWHjfS||tѬLbqD4G@DcqD4G@DcqD4G@DcqD4G@DcqD4G@D:X_r+IENDB`Fiona-1.10.1/docs/img/convex.png000066400000000000000000001266061467206072700163700ustar00rootroot00000000000000PNG  IHDR+Df pHYsod IDATxwxs )*FN hk[{޶N ڥ]]QF $,22N֙? zՋ‹m^~D"Ғm:@Hc?i Q14F(@Hc?i Q14F(@Hc?i Q144 Tu0};w^N[VLG8.+H$L0e߾}Tڴn6mؠ-RҠhDg*VIDBDBns¢"MI=zPܬJUTTbUNU5Rr%OR׍wS3fЕMkhڶ{`ҠpXC v:՗ǗS}Ѹ䣺4F@ڽ{%ڵظQm40˯\S9;l v:ûr̳;-mz{Y(H1z/тVMC}>cʍ<>~oM\x(Q"l٢;oUњ?Q.z.NѸΝ6tЍ(H?&׿4ike:RnAM4tM$+X%^{E?{(~pڮw;o~SUnPʕ*;Wg55iӒ>d74^CMG]$RWWysjkyf a]!`k: B?$S=fy\q Zb|~(P`/oQs_.8@Wޫ͛M]C*++UR7qŵ7ӾXLNp8/מV%,iP3!gÆ}gY@?O>[nY_SIeGS/|t۷=KhŲeڷF}há>tƍUr/|[e5l/{\N:؎KN Ƶ?W˭45_v !gՐy2tb(77W>oZBxG;ӽp(8L,;^ zz +/([轲xEc:rimk,=4`F>j 0aFa::twv?.q#@y=[LGXuuuZrV.KVczekX$[#.gY(o:ӞX\.mƵ/ܦ<} zM.A/[_|{+}>oq:gTQ2֭[׳X+-W]}Fk۩ay'յ&DTtks(3sr2ENUaaj:&t 0fh=4@'~]ּ(iiYի4аhDn r"Yg*VU$DB'j+PX@ +OQH]?/~Q߱McXNʊHT5 0%E"YPYK$4B tͺUiU:Pázi4s4`1(8F۞yZ;?)SLR;l߾}*++ӊRXX[5W"pvIR*Qۣ69=^ 4U(??tD?HZ]V\sݜNw`tT[[^,^+^O$Q.a*Qe)51cT4} LQ P^L(4~XۿLpD/S/h:JRXnݡzV+˶4XT#. t^OI TvE}zV`dM@a!3 ]yeFnr|,Q_6[}^…*߰AggԈx\yJhۭ6W2ѮhLHD5bw'33Q2ڵk|[$biֈ} { ҥZYT5۶kLvGph%hޟTrkscHg樰h&O@ g>(@{*t7qZ?Ϯ]>^ϻwiTVG"th+\8|f*U3@tD?YVewA/񫖕ZUVelebmmqkx$.]_Eg<@F}T[39@;rf~sCUXT©TXX<1(8|PzP_rqT|å`3! L)sGCH;[#UGLSycR~~'[.NqU#*:M)SGJ8U>'7_d`8`$ZGUT4}7n@Ppǎ=a:03ƪhz| PvvT؟y`f>[Mק#iPc~e8C3Ni30箻8NBK"Z[ :{3E</| \)g]~gkCnYR`$M9|̣Qp>Y\:S? ?3_-KնCuu?rNU9稠ydbͻ:G#6d`8` fq(gk&ʹ`$C3ꄥm&<:z5Te: E~:*SۭM߷'33@X/n&}ӧ]mg4438 lܸQ%ofڒϲLGt- :]̟瞫@ |dh4y] 5Jh8] ü?3_v+~hf>PX(?Qwq.xܦ$C3jVE]f8 1q(imEwu2W)PXiӘOQ?{nnsfY t;)`fޡꄴ-R|#ѣx@lC\TQI7jʷ\c::ϫpDտw 8% q:X_o: :@۰aJg>:;K϶XWg::"J՚E4J(2 #,K92HI? 'NT uIЍ|ʟ@JYp]&bq2ϲh::vڥb5VVVB^0kYjjn6DHz³j֡vt0g 55cS@s)0a~t;@ٖZZL@qt֯_b;_w)7}ϲjm5DHmmm7w.YY4ł@r,o: Nk_hJ KF \NR'F-|mͻ:Muoަ.B! 8t;w9swp:LGIٶB@孷M]Mt"xmT0aB|Q?Q0ɟ@[~~|:SvTM$8"(]U;Kj->}ON~KtIfѨve::tzK^ttet tSرC%s樹Z߷t;@ZٖPi孷,C.q |mcxpʞ{YM?^Mn{)~2ײXJu9j@}~ۻLdM*(ҢysjRͲXX^RSs}:Ӵ"ls;)~2߲I ;Z@%]"%t78k[jji1@8۷xZkjt-u0x,Km,ހLj?p-7^, 8HRYnm: 7g4)?_m/T(2'֮]ѠzO\@NSdkeLymKMMMc{)S4v",}M27ԼKC*@b*3Gm5Oo<ε@ඟD4͂:7O c_Hc>&/g~:7PcC8N@ZS^b46z{t m1p|?iI+4J.,W_o:Nk_&n}s5hy?r>VC]83oh\]L-K>';HQ۶mM^-0'ײ2'@)gY| ^0k[jjn6'B~I;VW^֭>[B?I?ޟnml=a: gYjnm5'@I, i^q4NL'@pZ≄b>LV\$u]wS*wr?HJYnYJf?dx }sTtWlM]$1өPůd$l٢9s߶U׹lf)pp'Q@Dof:n@jZ'+AO?Ə~zOz*-PHCt*nبbU*W-i |qK IDAT 5j߾}c8=>6}PI`:<w:a{t%;0A]t.z98l cIZ[k:-?QofږƱ4,7Љzq+->Ҟ߶H8`UYJ̦&ׯl2 >Rcc88 im)_˙n@ٖBMMc8N8< []9L C-'QIz4H[x\SSxg[ cj蠚̙#kvT/YBc8(nF꫚ƺ9vIYYY(ѣen)~r9YJb([tvgРA((0uuu7wvBCXNg[ Bcꢏ~TVZCC?@G,k_I@{W5mdmyqWK$(kX*3GwwR6)jjj2@ #| Zk42HiXT c@Fy4fhٯ[7?@'Kj3a*;WÚЀ~ig۪?pt VWWbm_FW ft>Үz1p | muz,[_ze|ʟś?+/S3;KS(~.m|Nb6J̑w[=nϲb=iQH ?z-yߚi3t7MX@J{Q|ߚe0oY 57cbr̝f=<8@FٶBc(dXNWRa:,I^K---|\2~_k(om~v(I$ ˥F1pAFK$*--+{VՄI^׿tM8,C0`(82Nyy,ZԪwkB>j,ĢZ[5mڶG)S?a󕛛k:~FRɜ9rک,ӑ׶ LQP mܸQK.բ7^׊޳Fb5QnIR.E TmF=-!9R4 !CZƛFݦ8es+IYD"a:ЙjjjTt\F%b-i}Zg(זHT[b1mw%U(# 3<~;'ЏK>t@x0̛o_l: ;wTK,ҥK5ait 5Gj} mDUjۭ-v55itpM, ƍQFڭ6-}E}eϦf o: @TZZ% je۩!9M$%ՖHT[.m'#Ҙ_Є@@3f騝YW_vFln./t-?H ZdV[ }hT41N2P\RM8Ѩ\j5fpM((ЄBM8QG6W5G?{4,|tUM?_MG(`ƍTx]Wְ=5:h| vr6}Ҭ~U&IUm}inl{QQptC+V [ QBc.9-^{9YFcz;ƐӟWJǏ7vڥ}xMv>(8Į]Wiiܱ8l4:8βU -}.WLGgzR/C[yHK[Q=ٳ^xߦ?ϯ.^PHc=nF4Voʞn%„^} }kut7-XtUG~Fqi` AjPρu7K?nͰB_=K_q9u׼UܦeW#PÖ-[%ik˩|s#Q-KC/kW_Г>ɩ/ # }k_oh: @cZf,^okjB>há<RN$ЂְNHgu?7fV}k^@ƹz_V^-c: CC6mthkU:g4SmV͜9KB999sX+u}'&H64oPdZ-{krs4Z >8b~=5Z۱F˿M}ⓟ<緿z9=M|Y 6t'޽"|-]DX\FŢq+Y2ںUGcկj׿~֦˿EܿW8d[#5aQp^McZt.ZPH5V54IKo=W]? P˿:ǿwko[jll4GOikkSiivޭ,F4f~'mu[X mXf_qf]~z[nA upQ;姚1c(8 O[l.^"^k D{c1iAKOk^>/m]Xnۺz<׹}(~7PC}8%W_|Q%橯˥QJ@q>NHB~Rc?I诏}iSRϲh׾v~ĵ.@ ۶L(ȕ_۳(,5twݥ3oSe: k[jjn6GIk׬K=Ha~R'P7$͞6mt o̞{feYpZ"(8 Awzot:E˥F1pCֿC4rR$C6{~ lB̽'ufO^mq'PtGGy*7ka: 'Q$ʟnTi~] =qʟ$CӍ6k~ }ycqI2?W.e%/oVc]8 O{յnN2߶@T(/oUy{ cx-K!־ Dٱc^z9]̩@SIt ,>Vt v ,|-e|WnQV>Rt  |㍺ j:oY =q2dO=FŢp@sYb(8=C2c.}N"?{հy,@;) O'Zku/@f6O${ѦZN2߶ LA?Z9U?ITWWk`) O'ڴI"Q10q+PtMgLA?fKr\N10ojRc]8TWW+7o:IgYjI?Z9HOc=o$ NP j`8b:IgYL'ʟNP~ږ$m)d:;$e)l:[}WNf$m+b:9M۷oW/$y,KhTxtUWW+2vs9MFcTN'O9M Lp ʟ$As*7mR/-B!1 ʟVurXC|8$(NC}}bѨzؖ($o"ɟ$Asjjj@)idq#ycQ564QJ M -K Q5K|Mǀ(NKuur\,}p$mk_ɀ4l۳G1H:>Rc}) m:Io[jll49e555ĩg 55Q`P1HJ>Rt UmܨAJ@R[Z[Mǀ(NYqr XLǀ(NYq2d<(N{'mkYNʟ$@s jjj@R9 Bcd<ʟSP]]$5mQ$ʟSPiƢc|ŵ$@s 6i/ǗHP$ʟSP]['ǹ(NR8uxLx4') jhc|PWg:Fƣ9I555hYcMx?')XU$=e)Po:Fƣ9IceDz1$y|ŃI$ء3϶jj2#Q;v+Noٔ?I$(6-ZZLx?':Ԁht Rϲjm5#Qʍ5(7vtFs7k/s:h:FF8 5[(st1n +K@Gl[PtFA555M eq0ʟ t RO (:(XYO<ε/(:j8cRLh?TSSA\ z12mFռ.3$m~~12OhHve)(:F.eq0ʟuK_4ma?Pq)a:)gY 55(:js1IZMh?PmK_m)Dc ۷ON/+89~V?W}8J$|2 <ӚMju8rۡE3f}NSL1-mQ?Z.@Z9kUkXk\.mmmՅ.R]pʟjlSYyGu.E3fi9O7]ߍtht6q8AS u?fPvvx)8Ѩ &L@Z1B֧.H 2-%PGEEue: Tku\ZKukᦣ%-ʟxW?7(["QŴ:aɑ?Y]0!ʟ?Rtp;QDrѶ.bB ʟ5he:1tX\a;\֪O}B2vB8.1C3Ltp qlmZ[thƥꢋ.ʘ yʟ㘔ɖ˲LGh/<1ܹS]r~5tma[ʚ[4zH]34p@=N{ݙ&. ,B%*Ⅻ^O24OSZ/)^ o"*Yh,r].,;H T؝||M:">z 5kTcsH^sO477W"C@Apy{igϝ?aب*5 㹜 ZU P ;Bey]Ŋ_Ps|I1_ /W" 'dNmmm"o Pf t:2@lay՘ X_,1OzrUf33@(^/by%)5'd1M)K@x+<< UYo=ǓdAƟe:Wb<tZ|= x6O1jln@^>6bf]9>ٶ"!x[xI? ڿW P2T%5466:ȭmTYQROC} P2 T7}I6֪&ZR?2 ?Py9'NC}=yjiߚ=6OMW_=xZ;h_ߘN%sgڧ.t\]KYz' 1%466jPPIw8=(:}霼*'Nkdt0lVmѭܢc;tNޕ30b=ARN1$n@nX\UNtȓlVԮ/KxJO%nY3@CIi\F5LgxZ֧f2SPBQm::OeNgمПillԠxta8^%t霂?UfyaumUē… ?"'kk5P 긚x㮻t!) ?Slwx=V/ɟ+US4ҫYGݲUqƎg:zٸq,Iht wzz;4;>CBVPu=Pg~{tNOSsyv]]Kÿ =tNIɟ,o:3Y~(',n[72Y񥡇{BC 1TrB;5/LXjNauەWꓟ餒i*m:$*?qJ 6tR vZN$DLPe=*~z:M'aP?IMgz YGO(*f_]~yᦓ/B;T{ds3zB94+tGNnrY^[jϕ3@v]#ҧ^qƍo: "Oҥ:\Os%˯б&NGʅi /m _j/׉'h: {(tOgg:uo_^j8.:Uq$Ѝ? tI60mge: =,t%5ԲLgP:zfV}uAnIӚk7[}_ofZdի&~szLg!B4^3(k\Woe]-lfū :sM4t$TOssjzU(b:YKozS׏O8Ar4񧱱Qc1z+w,[oO~RO>YS'MMp?鴪]WSȩ}ŵtM=ZL'MR*2񧾶VUg:n*;ae "wG ;KVT?{.guGNC N'B#vNzUU{N*R:u4,d4j:/gd"z+먪ZM'OСCMHD LG#9DL"RCH .ӳgkՐ2}jd">Vq':v[kȑ:Stĉ4"񧭭MǏ{\#YϽ+zj F5stx"~Er8`lVo[90ALĉ)гB3 i1t z%_ozKO?^YPu+V()U*T*]y#Q%G"JF;Qy4hƗ<_oRY^x|1bDN~(]fΜ7\2@IhVTCљ3MƄb|5Ŋn0SO}]t@,}25GMFS[2(`'E9UOcsjb {|OϚe:Ȼmۦ-[_ @nz@jN5(2(!9gNǟ@_w z9I74*1(ՖsNǟZUG"3E䤨4t7=/h[7ѼyLyQO5d3g#P2vYj$dصMO= v;ٴiMIiךrhǟO:KuiNYٖ_u.g|Aʮm3SA^xAuKNzXJJʊjUjRh7/@Z]y\t 3v%ɨIZܬt]bjmTM*hD"Uۖ-K CPJz:g?ӤɓM9l۶m0ԤƺjoЊիf5P4*RE$b:V:fT 'Jr [n0بeРkqՔ*}FwJrb֯O߼MqU*=oB-KQN ^ir\=wzi)@a)Rܬej\L+Vhu_VxLJF ,NWVuYG5$Y&ᖬ ^cǍ3ƟZfnꊔ*=WUQ*Y+iq&@ LҘ1c4v$=x:)O'9H;ߧRΛg:?%fժUjjjRsSah25XUڷW,[ZwS&O[48eטcMb 575NM˗yJ]A**Tm[ສVP'Z]OKڶmqN5r=v|N1ի'1?1u^Sc֮^~@{ `0׫G@;03=N)7iٰA}|RI=; `lPoEbdkh:cO&MgGtgѫb:c?0U}c|ŮYrŗM{gn`ĺu/ʛy^oz9)^cׯW/3/?t9t -?0ݵksLg.KOkot m\#Z׬5 ^=W_YS#֮^]iǛNz hmiQ—4mV=cSs#Z7nT?m|]M ^ 08}È 7O5GYi]dR)@ 򮥥ER)Jk9//l:)]kk:`?в4'M9kiiQ?2D `z=S򮵵UOl^#OuESaAޭ[NGL(!781mNMykYJ}"?Wvv[oӨ2kt1tnڬi>f:0yҲN}\ V7P"0[qbe)+Sxy]},>zr׋Lg)@A8򪵵UIB@8rya)@`A^/xRN/O7A^}?z^)瞧ӿ )@aA^CgzGgxSjYN{|I6o3fh>e:(X|G^jF8{6z.ئ? e}&15z%KNG0W֭S?.|^s=7@ye)@`A^ߴI}8`/,eCiON j}{;zf{1Aެ_^Mg(2#O;Mߺ")@QbA޴jDt"rith:(Z?ț5>+;tiQGN򦥥E}tiΛ1 oZ[ZHjmJ$sP`A޼jDx;][zz,W_|t *\YfFw/x^ϦS8eZ f/m9J< 77nT_KO_Ln:-A޴oU ^;_y 6lP2 WӮZLl:=EKKFAKtװM%yڪ޼ (iK/NJ򢫣CU|䓦Sq5k$ELgȣǂ4~noM%;4%nO:u΅1<8LGȋ_g]zNLqy;zWvfݦÏA^p3yN9J}!)9纮lFD4`7Ngۼ@wN;L) `A9o:kфIL09纮8/6|1 \=tŦZ >s~3}zGQS[k:@9rqY LghnJM( ?ȹ\/4\ϝk:@aAq[Rt1~]S A9 ȑ}诜s.t wr'ܸu~)j:@cAιh@O3+5t g8A6Ps#n~[Xt "۾sMg5ӥ&ru]zKPDsp!Y-]t"s]WQ ,վ E sȎDLg0ض`Q^sJR*++Syy' `Ae=w4oz?6JFrlKN$l$@JA.e% IDATSqq]'*',K,Qc *S*Ry*TE/RJUT(YQ)L},//eY{ɑsn6@x,2A}=eu(ӹE@}e ~шѨQKHD|e}DTH,W'I%S)RJҮ%N-=9dc_@!HD"JD"գ m$GI'LKM(PԵc4HhDN>0"A=x|簔H$Tq)R2~R)Rw|ǸIzZI*++{7(8?9ɪ;г$%Q%n^hק2pji(QKU6U&)S_}oӾ9:.}xDG2v K.Zd~w &#\Ǒ +_7%)@N0 lV1P&F#:zEqmf:Q?9Ɋ>(tcY5rYtcsw(XQݶO]vٚ5sG9:(}('l=u/uOj:68<ڟu)@0 Ǒ-N(>'X}zy& L{9x-}jС_tNsG6zPĪmKwUg|EOϝk:#?9zn0|d|'Gι'?B[Qic5|$?9uy @,z[Ll:P?9uyeEuzG>=lV\i:-yLG@ mڿ8Te9.1 <ϓp%]L+]tosr@}NJh^|}s3( _J}_yI_r`'y @cEu:|mذts<rmTN8Q/8#tԩSB?Ȼ<\qFQ}.fpGަCG4a1|NQБe 9`'Nm ЏMSk鴨4@JbKq~S"?(o~t]Qy=<5˗UsSܸp=T]ܶEy+ؽh;NAHpڟTs{LQ> %+4mVۦS?(Xk׮UNS:-*UY$ț7W q}SPP_PW\ JܸAZ)(|?F_7:%3ƜhEK/NA`A:~` 0{vTgy J*RֶMg@A_}t cA1Bo)=bj֮7d:1 t>LtE#zoN!?cN8AK1vT_WMg1cMg@E":WJb*I?}WTJ\t l 71LuYt )vTx@ͦS?wx1Kw Bk̸q]X֐6{]Sc|3FhtAzsvKvT>]6m2bA=Z2v眲{3C?q'ke5,W٪zlL)ȑH Wtwt oқɶ/Æ?}j`ut s{xWH1 {8 (h%j_X|Y)a?q'67l;_l:=wᇫ}Z度S(koid:JҸcbvT|{30$8Qm:V"їbT)?(IGuikדi ګN`A:CN&k:ڷ쨮r?(Y?^K|@wF=6sJ֘cU (zC=Oצ3?(Yp2o:1[]mT__o:6v8ɘ7*4GLg`PMvt#q͞ Jرchk (zXQ^{t JZ2Nwjփ`A?8vTy|i?(yc'LR6E/%Z`Jۺ )P]=3?ѣFiQ&k:e -X<3I;N=^3{ ]@qP8\f H8p*~d맛j>8a/ƍM@?Nc'L,b+вЦ' ő< iܱj3`})n^O?*eݲA3YJY>h:bv3fjk7`/|3e}wZZW!g|M^IfeuiH֍NM<`H$?-Ya ǔ\FyDtwwjku7۷行(F)3Qp ZCҙvT_qgoXH#}hFUa2*fk?11fX-QttN^z~Yz5;L~3mw HYP`V͂1[~jbILLb4M%[$&!X" ,*"UvS~̲RfLykq{Y}s\JֶSV;ѬYLG)k?jkkEit$e3իϟ7/Н;N,/#ܬLd:FY|5..:lu+ڿ"j: |]Li3O<~w-Jpy|Kx.%6?0|7;LSCA]7ኯ}Gg^zI']yپ~u0m2`&/l:J٢dלdt ;pH<ϛU?lX=.;鎌J|ṚQXlwǎq֨_ X-u=XNO6צM]7ݤjH$ )1ʒ=a„ CO?Y3Uz_hui:ߦ?[ݷw ^z~[Z\G;<wנMG);,6aAi~(vdžl?h>ͨթI<==Ω_F ،aC,QtFvU}~뮛nRìY:5:CqFotUUURV6c5 7$Vh=6y׽=e#FDJӘj++ܳϚQv(ᇫ8$4Ѕ[cta,ә~cQv(4ϣJũ:3kG~<}K笋)JMy[p套*SVe(`괣~UN2]ܢݾiW>ÆoRNLG*[L[p!h~(l:<;UNEE.2͚3G;wNtfrdbbJ]|](~ `̘1nhl8>VZmHQDKl*pƙz5bHӑ `SG%AKm5ǝyfϝ?.j_GMצZKC&+S9 t~d:6`O0a@!˺9]mRTtmH>{}qy8UGREj|A޺t3 V4HuQ `Kmٗ_6'C&[oՠ#,_mXmD&&\}?q?V_,ΜLFw/Q4u}-:<MGڤRiMLgu'g:9eSYW\rwLG$=S%/T%rտ<몛nRΝMGV/{_3C?@)$5w|Q6ם7ިuti@(d,ĬJ]u㍜U(mpqiE 5$6@Mdjhx]|ū;oQgyntFm]mu_?6u kB(5e\]x9z(4}tujZDGp]4d2kѕ7j_mF]p^yYa<]oE3f]7ި~#k?欵Mza4ut`0+^\)S*#:Mv !Gtty6,B=䓦;e_Zn3Fwlg: l<ϓ{<_\y'屗7|ߗ(`YhR#ݷܱ-ӗ>Z7x{eTGj\o)]<הdy^}_7Ns6سd}D4rk}uss;0&-KY&P(C9J/z^^<%_my^6s'wu8Nr]7q]y+yͯ6?u7 o˖/ E#yg=P(ٶP(d8Mqܣaatے1" P_K?6 Q(=]f.hYLQ(k'}ѮBmlK0Gl~ tvc$l,eNBa\੐ IDATP^}e1u ȗzc[D$ uU;&h2F|#5}(%e@YԁɄz9 ;.(q f*%i1P"lI뚎lN{ ??(p?J6Q(@Ų/>e%7GPb@YIR YL ?lb<e%NS o<7"e%I+H< fcESi8 ylFtAU< le21͢PVO?Ge1RA5#ÚlG0` swɴ(MFtQYt tq kKӣR._Fl]wg:ݪDRl5| 1R3v/ɶm1͢P֮v1f: FNeͶm]z% }&P(#-mAgQP:F#FcEljsO?m:Y? N>X/3`@L6M:Mj_ ߦDk ?`f;L66~W/lڴi_A_rwoL`{LZG{ΰnV-X3؈o:Q{參*jLG@x3֢t?tҏ`덌FΛoO>1hAqff3]w`l5k4mۙ"2nZ5c"(?%;w~sMeglë*uo: *￿~fәe|_gmҬ9sLGqA6V [TU>h: lڵklٲےE /LJ!P>w=z馣MtҖrgEZGZtV|-NΎ. 4 \=S:#LAc@IjjjڨYGZ#-[L?TP7;..mdۊXo%㬣I:3eE)m\,Z͓;W|_;WVP7W MNݰ }#(iBA֭k:? R<JdB-[LWQQn:7;l[]w*@OgڮI2#qԽB;Auru`Rv lP@ k4| SiuT..lnrǶRc˯tLX˗YrzTVkVUWQW;l[]ڱ2N_V̜p}+v(d6.w/-ZXWT"T*ueYNV] m]rȷSU$y٦P%L+W*HGU_ZP7V{em.:wmfΙc: G%bڴirnWHH*"|6 CƲȉXĿ]?8$qP&?P"&>p]ԁ`M@ (tZFjEyiQP&(ԩڿ};1lf:P&OQbvX ZNӧO7e(r+VвŋUÆ@Q9wu?h?@MFZjhh0%(rS~Xl@1:T1PbX}}ɄzkOMGA ؔ(3 wm:J>GEj^{ʨ@1Ū59 2ՏV#P^}UD(~pD7%FHM8Q#]t ypPӟL@z4*1@D,K+ĉQE;  X ("h皎 v ڪ\=3PEfZа@E¦ȳo7%(2M}?@)%T`[PEfʤIeYch%zp뭦cPEdʬ]PtdHXK?x_((?@#%t Pӽwi:JPD2EC!1*߫hժUPEb(uwU$P$_fMFF4if<1P&1E(O?FvGe:;(Nc:6;(pMMM1kE¦hc@ǦN~c0P&sFY@9};(`-ҺO?Ӯ( `@>?=M`?@͘1C\GlQ`GQjI麦c0c(POPVU)y(@'N(1A1ߗ;.h:iAN0׬2Y@ǦNڛ7)yuCf: Jc@ω{c0dߊ̙+VA˗k% LG`QF1P"(M`ш^yE\tL}a9?@+Hg}HVDxYt9@Ly!\19˖*"G\#l YS86E(gVu4oI_p:FOgwYcPEK.#˩;0utH 6Lwӌtt[Ӹs1e(b'\ɮo:f*P޽MGA.QҦ/;t !]xz4ͱ@!{/Q 4t!uYwz,qcLYaP|(fQeFSY3A5:,?;╬MQ- 4ŷL㮯 tkQehSE߾Θ]_~;QBuiGw:}~PD(25d sJu1]{}:cL>ejҥwkcխ%ut`1>}h+lۮI:Y(~P\SSo?٩(@AyԴP?&2׾}{xz&5(sGEIҠZ=Уqd\xY~`: L$9z4㘎.%uwP0=UQUP^;~IϿa:W\hqt\=s)~P(8~0\tM?\70%&4]ޫ}F4(jLn;SbRksQyL&]t\=UQ'_xtd0`TTT'?9YOe5{`-gLȋuuC4.]_}9D_[Qu8H1EO;h%Lț{ a+`\O;~cƘ,ͅ\.}2J)?NZ?j7S3I MG@w\=dg: PW]~(P/9^ І(շ>Xnݴ0îcILG ?JMfA)6{?>YWt(@١w k4;1`B2S&\~(@Ybg'+*¦5jOh`M8@b@ѰQz*m: hp\ݐu5s0+Дk:WW|K`tQh5ݻw#ԋeTZ\t mdkQ8u(VN5b􇮝LGpkU~hf/UlFv@}Aڦ#ȣ\O;^}(6@~-f8]P6R+)]wmsAիWk޼y[_wܹs矫_U=O}|OB! e`;\.K(W^|QgW}zJBmz9ܳJ4\!4s̚ګo ^LK!TaQ (Q$ :t@q}t`*1@I.]ܡY`͛YT?s w6j;~:0ia^xUUkg:͠.=\}6 @IyȓڍknU^ٲeecsh;h^CҩUT먏| Ջ SMLw\MY>(#tۈ6Gzi:%klRSSDdR4NS RMMza:nH&-Κٳ`"\Uj;^Ln٘TGec(.ORd(]ymA?I }y["P<SQM*)֔Lb")T^BojR7>GGV:&Rat-}#@QH-Mb֭S"SScFeR"T"R"V@jY ߡ>'Vn:)蹊zBH ov$`垻tRnh%8nne-r}Lj@u_ Sݞ{ӵkWq B+z8>57߬F (e)nT$i566,KR)WI&%b1%1c)ռ-NBPHQV8Pn.,KaSDRus'j)O*7[bߥ@eoeb,Z.iq,^;ښ c Smmg:.ZѲer5w`mlTJ=r] Tdy-у=SMJ(ȣt:R %I%M>N4oȝX_,%7ž(i%I2%iIRdIA["+ ^7妓~Kєf qhDƗdZZ&K+l[K)aÆiСՠAds8$/͜ٳ%ռtleK&zQt-(QP\mYLdM gޗPlFK}z@=hi曵Ǟ{ PP澮TZ.+بXSRǕHsRJEl[@@@n9[e)B),}Q$]O kiQcvSn=5h`֪wަĭZ*W͛wtJҾ-K )LS[6^Ng׵+t .ԚcaDo|Cfq@-\׼-nMJcO报TJK%pHȳm-d,~4h-Kڵkgo ],Y;ml͝9C(kJrUWPP}X6Vt8vrl:C(mn yP߾}ս{w1VԲ3\ZM~:W&>l.]@ShTXA rB][Yu곀tZ.K=Lgt<vАKw5|-^BthjRo'>CAUlZ/-uZu,,Q5o_i>hРAړ| r]esygkZpF"N:N6SBr|_77#;bTWAﮚZ 8 6|\)4wxW jRSoSuV`P$-:Z8Z$ֲɤvG֠=PMMjjjԳgOӑ1$ܲs5w,}=hUTo(^u.ͥq]}hhE,}Vrr`uuJhѢܔP}gPÂJRWYr}ı-V6<\O+a-s]}Q{RMMmnVsɳ뮻 P( ֭SCCCjܹZKYU7Bllii6<r_Zب^;ja#f)t@3Ќ14c?@3Ќ14c?@3Ќ14c?@3Ќ14c?@3Ќ14c?@3ЌYMYsk.e8i!>g: ,YD<>NjbWNaݿSzVӔ9hg:~d͚5z'd/=___Ӻ|TRvnK8]o߾@Rnn3VNimIrjiÁ=1O> \ pb? 'i75:2k|)lUԦC߫_^<4 O?X'v~?FNc}w8O{RkAٳߏ S? zwuCr_hǭO<]VcЀ^5|K?NQNk=lri< W<]fG@3iS<1mu}B/YBBM'SOƠ| G3ս 4=?'=,^z*n:TjsU@z=3(3Sݺu3|WzzX%TJ4UuZݧ<]C߻cP׮]M'NZQQv%!)%!nkÆ zqwHc{3hOC9hcaG]{6t?***3iGjTR5Unfa829rEta8r:?|'٣g7HEKE]yyukM'NV/<&?Q׶죋yQy]͑QZժQYm*jTQW^^Wʐ:VUW֨JU*Teuv9ÏJv.e…:=u#U lPOout 0}^o[צե n0JժhHt+c]SS?WWN1cz套ҿ21-)ީi!k6 brŗdzew8p:dwprápnDZ>oZ%Io^z%][$4nqkveN0D۷O?ۿuUTSU֨WZUըROUuOQe]jZ5ժRNvQ*!KH ?j M[0GIIIS?4x|>3O7'Ѡ9O>Ud 4ÜEW,t 0 M4YݺtmV7 6HRB~tGO>l:@g۷ghnktq<ƩQۗEMFe_|!-}n6f5jmN7'i9%EA35qT)pMΚhjkkU^U)f:eFlg/[f:  yug Ex*jWeUL*:9< h0?+ H34Y]R5w\8IIu㵣u!&$cRQeM0 "LG` uߟnN=B6էoI8oV?_ş~RW"RByƝj6mt h]K,! ƒzp[%?:N zԒKŊeD LU=t^:'3~Z:t'n04;;թOO}jٲ餠vZ-YH?\+3:M“4 *]P鼀7:7KORzz@ܱ>;tl-s=؜ݫ sN)/;藸mذAK-?ݻJlʌJ ǵC{OܫӇ5~Z];w^)4hh3mT7rJݫ '4*\CFci㖗kɒ%Z%ܨٝ;nʫtb)?4{*55tI[t{-mۼUEwШβY65S:<zj۶mںuvoݮ9sbUG w{D %sW׆MohA[G41ޡ#FN9\MxMϚZvE128A+;nӥ]f: N^yeګSZ~t4Ԋ5o4qdei›Uuث"iD\F=>CezPVx_S}L'q1^MM1f>7_zAܿwnyA 2As!M|=M0Ac2tad;ur%41cOVZi34w|Oי)tެݢiLBV3fgURKRC%tr%(%*((PbbAGv*٢SG(2hfDӏ?ѕW_e:@?Hzqr-ə ?1ĝAW^g>@33j{4}t) 7|S鋷讄SAWZ{mUz1ۯ$A}&{Aj:dr鵃k!C<񘒓M'hAi5z⶿SAly.^Zg<æs4StvءqC3^uQ=tf3AX#8Kӻ^f:Fj"%$%*1)11QS6oެW_|Y{BѦsPO?ҩCG}JpU k*tW"UBt̫*G*>6V JhJHLa(W˖-M4G~L^%BusOhPOV4A}kzLg#gtLu8L{tzvTWyHMݏ>z6R=}NO?[ZI`sg3V5t D[6 =v;|(7I:y;N]/0y<e8Y]6귺#QShTV~\_AZz$4˖.yg7oNהuϢCu%8Ь|hVL01묡wAz1rss5GU_c(q:d34[qi;t ٜz.}bV~!27ߘNB())Mj.*gw$&OaQ#T1<:;ʨ؎zz΁a/94^_\>)'&o1g4%\hv+*[F0@@sᚷvd: Z*))Qiiʎݧ%unWkB)*nb43L{,t MBOP^zLG;UVV0x)QǫRoJKJUZZҲ2\e .&GhVZGr9!!VE)zk89B[&GLarXm(}.M^﯋Ù?faɢl)4YeU\i}iϘ J?I#c_h/L3ڻw^5]0\}; ?"Ŧ)g"S_,Yb:)h,^H P~$m tNgj;c/:#"CmhV*j4.ozu?^|tNU\\[;ugE[Uiw3PO^̟fiǎ:#^ 6@ejLl6M/>tVkoW_])58y霓ⴅT.t 3J׼>GgƵ>Wy۵s5辻QfXnHe:z M-ZNA=p r/5,~H#]/7@P Z}uF=tVtߝ(uz(qQs~1W]^r㵣2ףy=]-_T$p԰MV9MSԹc's##I.+{oJZkSNaQJ>]}wשsNpڶm ߚٱL'Ӕ0ƴSt^lF}Gkγ?$n'zqz!nO`:ǯ\ax<3PO?1I:7 pk1D߇kΚ?w需G_޲خ[_DI~U%%%3PO/_H%ܦSI:=>3ZeEXlz٦8pnYibL'5"O1g4S's3)x2}Yn IDAT-MFKo+-OkÏt/oq -_BCcۘN趄~{jŗS_}aCмAߩdp70ntn$7z9y5rp &_>꾽JO G\VQWVDMQS.9g K9%yyyQuڳq{55rx{0jo@|{j:4E[tg =SKYYّ3zUY^QWWvөpu#-+)@:i}zFƴ3ר?kkk=9^Z9:p:FP{k:cDH_SSYWK6Lڜl).4m۶RFMm:4{IYJkJFhmڴI99YFkjRb_TZ2CUmm,Q24v֙2d%e]k~9-6upū%J =ZLuzkW~]};eD;A(jZSsP+ykWZ#y[xE䭀quci:'< kJ0ڣj(IJy}HŇLg-6y<_-[L] ?t&k:j:мL8I#304?Ԣ_hhLk)h`.E^.j~3s t"LΎ`ഄ[ę?M/rssUvX\S\pyMg~1s4"n{4?9sFƴ5FxW8e-RM-6yKKLgl5‘n:i SIi XB˿RŴ2F䶄[XMui9h%vߚN@# r99?zւZ0NnOыi(4C"z?p\999Z0g>7_vH i#2ಅ*11t ~lذA ӂbPGKbSI O t?mܸQ Ӈs GI>K640WMtN:e͞];w)"*RG>#""{7mڤׇsQ=4T- 5`&hɒ%=e>^nYi!NRYHjU[SJy+(SۭHw"\p8TNsꉄՒ+0@i34g\uli~oMJjTR[)”ѯk > Ol޽3kϔ&DÜz嶆kEXa Y>wHq0/~e͚i3d-љTBEYFw(**t NBuufV֔ڵcEֽQ}c: 0m20 *kt-\D#:Rwztk: 0Ǻu4{t͙7WbRu=UwtP\VJJxW cٽ{fR֌,G>{9B%p,Pg8~5 蕔h,͚ }8Ak;Tìs&%<Ŝb:|GMM+4L"|KzO49@-,6?N&O1O cz@O34;.K\٪կ.;^sfmYڰ~zt{#jxTk9@ t~}hv~0IM}:)@--1YyT:W/gk: nM2?KGFgSa %$T 7c?gND7w]^tW_yE[l1Xtܱ>{aftB] yW/|]U$0UIqrupԅp5{ ͝;W"u=Mm: z.M%%9P1N?OK4GFƒztRjjԠw^e͜93fQ# wfh|aZb P,6. `?zf8 .wq';IIy 7Y*]]q1rPZZ9YY=y謈 =7H(iB|>t`BlZݑ:HCZ{VՂXZˮBW\}64[nշVjxB PgW,'Rʼ2]r%Sp /s4rZlQr͟.[Wp pAJݯ4$8e̟ƙ?*+յܩk5Wʫ+\+R/L4DϥPe:I`Z9~=@qzG4_]kGGghlʙ8T|!Ck֬1~J4QnMŇ Mg88Aᑿ߯-Oo}~(W Jw+V_yEVA^!ŭӅL>?]zN10Y۳gj]n_Ŵo,/Լ\m@I|>|yyG_f7ޤѷ&W|Np .ڭJ1ch&MƥS# 1_:yk*H:>3I B~ ?>|׫u6;E˗-M3M8E.M^O [{<")$KH&!c{ꝏֿkj绛;{~k:8-6qpg4+WRnunMg:\]E O>m:^=l.09mlsz{5f:]MK71L_s^hwlMFbSIY 7|v#w]vlUtDbx&&(&.V111SLLbbb+?zueWj@m.jEߩo{Q)B44"t ?;{nf84i&[ZO5TREe*GZu*PqU*J()UΝqG+[ 6ӣz )?xT([W0o+vNO0Q;UinOVE5T\E5ϕ\VtP̾8pNy×ߓkа2K̙d) F+.p$[E6m,'rad{}xxzhSR5hx`eff*<Z7/M;յ}G >s2RffBBBN5\;_@s[mx<3p ~UWW˗k߼C] :/{4J $O5`zw鮁gTK.U-բjg;רQL'8lƍZt}Pr֩_bktDGC=uv';QKR׺Z쭁gO>?Z~e+.YObif^o߮]vgϞ1 =/V(>$]j'N0 B#7i:oX''֦C߫>G 4P={&*l"'qWm۶iǎnmުvRq%res;5s4sԭ[_t.ZdJ=k<5 Wu<++Pvm-׀}յwOH>t"F6}jGv_O۵}6m۴Y[scnE;UjKi1JG)&_߿H+wkEEזkȑy4hwÆ G/Z5s/%OD Uj֛+=uB9 f}7Icg:?tPhuJK;nVx_wL w髪|m(ܧg#GjZl/\e_P G[zDpv}*T/Omkŵmׯ*_זT}#vR.7rNª⽚yX'M4R.Zz^`ǰZ5<} 4ر͟ӾTiSx ?S-)1c` @Sޛs#ۘJJKMgLNNej5@6y8'1i'\W+TSSc:?`Κ;E؝x<3?di:? ~wO D?d{wk".Ɵ rss=pX݉SEOb S&~"x;r2"Ɵ1} t;$> Ɵ` o%{t SO0\h\><Ŧ3?ݻW{wRψS8%nk@c ՙQa_=O=gtTy8+PdɊ+4v,Y`TP_t F%OWDt'+z)j:y#RTO<1{'(`̘1l1a_@}}}c@?YqL of ʟ,:mmkB?Oɒ /DK(2(OLpNd:Ɣy/(r%wk~10* +Pn׉eͦc`TQʟ,FZJ~ŭL>}ʟ,{?J.*Qh,im`2_@}e0R)=3f(䥰/(aŚ?p1[!Ϟ?Q ÒI1[?Q;wjݺu7U&/{Fs,^L4OcAtrd䵐'.1AXn]QM*6(=0=p a_=c5ʟ?뤊 cB{F3D/((佐7^N2g~N,m2Oc}=:z7XEg-[#LG R3,dCZ=N_iLE*+)(+(~KJJLGU,u]! /1Ҟ sRw-[}VRI/P_2!(,S8R,4PG)P\c*(D M̟A{tJ1(8u%!UR+R?wRi9QVfOVz@J wG@\ PQ(zˌрgvN+k6Q+K~WKۑR_{L1GNJe+&G1,R % 1e~T$2+P B__LG`RWD1NI3kQOOO?5w/Yb:aA_I']{Q ŸoN 7*++3Q-[Ա]Sµ$3Fsήn:0 qMʟkI:mNFX"cta.}Hg9rMG#(I8zŧ妣}̻D3dj>ڹtc ']ǼK?w( [eBsW?bgVvr$ h~}}H3C˖W0@I,LSBnr_MGYؔ?L=BڗMGY2 r;$3i]/$ GM} ݽmRtI9_LmƍQ{W-JQd\WNRQ;>'^{I)j'Ԗј1cLʟ,kWGOy;uu%&# OԏSGڔVڶM6|΁fmO)aeMʟhЪNdtaɁ?*.: Bc O466j3 Sܵ)Nڑ3 SK(qr%3GɌTV2c+fo^JYQr՗I ʟAxo1@ ]׾B>#W\Kr%e\Wq3e)~u <6ҕ5UcSw;hd:VWi @%%%* |_*x U D%R, T||D~Y%᷿>>t n1^ .eYxvxˣT6|#O>z<ב]89nf8r]}ݏ~|~ir]r-v?D77@9~dduI۷|޲wo7\^~~;̟AyX,v(FmYD--f\M2E;̀( t@6uql1B>˓ ɓS02vK? ʟ|ږ` *٥/:kpuUScwݏ}Sg:* (wv#Oy^y^QdlU:1hўtB---ښdqZaC{fd23LFNQq93P yz<|y!+wwa󻷖H^W>_~_~OO@@^:cv$رc-7xjkkMG)*?Ң-cUͧZG3pq݁ͼѕtFN*#[KݿOgkZW錣tV|WΎa5U8i?  +gj!Q+K^YmfԱ:u=5+cTL(:l͍ښu3xqf\:X0=٫ cfGĉRǙ%q6gڴi5}iT)`yxU"J,<*GJ":ЬkĉcʟDm{nPpUHQG[EQ%|%I%ɁqC* }cY I_#g?ZǏJ믦c@Aq܌YZu>=Fvzd:Fѡُ ]g E1`T0쫼\L~iʴ~z1Pz$554=٧@t(xוw믿 6hy}߯#uSmב%I-r}լ Vї?--j+@@rMHkS,{TmUkUK+ ֱ=;#u9nw=ީt#4s.1L{Vk20E_4OhLQyCO]%W;)=Ӧz6Er9i>}ҧu}y^} rCi`^_lCUOcs(a: w9S 5*]Kϻ@MuH$kU}LV=ڮn9s'sOPmm;U6}ʳ _2NFݪx`}Ԥvt Tjh\_+|oÊoӞXʕЊ]5ea:#'89瞣}u5_:Z6vȹ3q6nHUOccQ1`Tp崯]TcIyKP_C8֎D~mߦ߫9's矧QiiƓt-[R?[^_I35W=2?=~ ͘=*򧾾^ݦcq]y.vmߡZʗO٬SӢЩ OTirؓ鐩NPkkѼy4o}$P5amwz2MQ͟?_O_sf3]=1eSvU?J*QQ~$5k{ݎzܵK!TsRh턞xnnx{+t suꙧk YHp~gt%8X>?v͗5]ZXs(P:R}-hî| ,)ӵsT(}zsJ5Zp:e|e古_Gua,Q`԰݌G)Qu䳼 }Gto=TU3VO_ NI/C?Qo: `vZڻQӕ:缏Ş?Ֆw &UkRYW6n00n^yU3LdѮL\u c̟Ƿuu1Jt .>ɋ4eFjkkM+e_TJ̙Hdq9ЙT˫ViQqsuLt@Td˾qnugrI)t)~iߗօrlW*j1Pg/T]Ss(ЙfX10qG 杤w1SL~3 Yj9m~ 7Av\KE IDATcyaT.˒;"Q,۬tQcYʒG-p}=n6Z .C/n}tF{ι:ښoa} y9mS6[MGʟj*=R-{aZS3^|5]Ѥ@9)۹FSCk>v:G ucd'G~ Sݛpm\zQ쥠7|.SNԩSu>d2\>Q)iP=MZoB6wnԹ瞫O`0\]G~_Q>;=mz6ա;7IN=YA]+5s]Z'VMGji&}趺Vn3 Nܹs߿o.}Uln-]nukˉgh?^;=lC;7I'vtOn:0*PRɔN?T.+-KALO"Mů~I3gԜ_D@gmkVмyT^^n:P(0h&};֧[Ӈ1=m^Vekk4cƌ7?oQ-`>!}нKf}nEO~߽JUu[J=N88]׺PU\F0 ^A=:4ԁJ{{_k63G=uK*Ф/|wݽ i3ܨVxiӦ 3+ѷMJtFx~YNlA wͧ:@ڿCDQ0g`VмyDLAVu]zGtԩS:ٶgVг=k&WP(`\{C+vmOD߲WЁfmܸQ;wG=BiQs{~ܼ@`DwoҳZk&iMY5^kk6>\꒐ȹ瘎EW:oJ&j/ׄ*(uK/48(>DPSє1k5%\הx|#.Y/0<|'Ft_eܨyicKubJ  FY%/`4T, O߿y|-Gt&:iXWlX0l; u%i߿Wo>yIoJ}տ +_LRHQ+TfQ {(˲Ģ/Ŝ"0l}vJr1ڽ$`bNS?asRTV`(`>CLʙ%(`(r;(`/g(|3P:Rڕ`NZ>HkGrqRĵ3կ>Ս NɓMG?0ʒ%U_@޹a Z%5Ԫ^uM oi ׸qT__`0h:*Ye{Owt駛`uV6o6@P(K}yVM?w/hM?w+h˶ceYTjVfO ejkk30Q(˲$_@>)f{ `YB<>"z1 / ՗Fac&>Ԭ3M0L?0ʲ,2} Eg5_&ў{oڵԢ0@P$Iv莿ޣZqd s]|LI y&7衟>j: ,"vݱc^ӹg:QdtlN议Wuj] K.VMMXr#nڵ[?q.xK+MG³=msZm3HF FȝwܡةEZT}J<@ݷsQV}sќ明`Q R.>u02QSBZӦǫ77D!o@Q;v;N?C]~&M .shT6ow&m: ҮkYMw^{}=Q_>l[% {K*s Y~}QW72o@zEn].A2(sme=k%]h]5㨷Wh=_}}}vwsW6t(ӣhOTյ:u>0@>A ?U@q܌\e-Zs_ ?%z׫JUVrH 'r\:fCV=ձ^/'?|n8$ӱ)mZ?(Jmy^ߜ|8@ܳm[~t `Dݡ:$c lۖGzmt xrζmX"H @9rn`P? ȑstZ^N@Z,_(s,@1Yi1 |"Gq; >#Xb8@~A9#k:02X P ńe_t~,@1QQ/|Qz+z_d_9gyp_|yRZuS*>#RGhxwy/؃?R>#TZ^}HxeN{nb1=~C'ri_(">yd^5͞`w\*/57[o|y<%:|x|?9YqLE~ZV%;QFUq]!Wʪ5~CCK{c#f?) 1sT=,)H$LP@DȚJRc( |"GvZ^3 ò/CBӶ|dߥ04?ȹt:Ͳ/ K( ȑsms;%f">#Ȟ0َi_ȣ'?U<:r"9#u帎ו㸊D#C_\7"uG # + t?@q@h5{;<.;;φ;ytgg;{zO>O(N}NuZMW(?(PAnr̨/nqԿP X @ê`<5FRRt›W7U?N:]}m|y^ӽ?( bAAϖ/r^׬\R6NlWlfT? P ͭjc?׬xzuw-lW6óQ8+GAy'aw!;(դ׮霢m~IlW\nAb8q=c|~OkT;j9XY9 }337R-tA^ԬD;f;PpV}m_FmnI! $iŊ:es\e Y9 (ZQag(?Pb=Pվ娽t \P<ϓվ?y}P}MujWc$7MRcSب͜9Sզc#B}_."zΝ)I?ε^VOWg>.?#&[\,f# J }(HBHBJ5箺Y===J #QPlPNMF@yH1 m_I‰h``t `D8+GA @9IVP|ߗN#iGC;3!i5QP^.Ƕ/e#{eK3t7c3 a>.viƔi :`_wfΜ3fi?(0/Mqp"uK/[,P/eiK6R~饁N岚1efΚ;w&$?(.\]J'THƍ։VQՄґjܘhbNʉjSϮ˟kjVAd[IO"e6e#Ѝ7/>\ܨl?(9Z>$S;޷uomSG[:;;ݥWu*Fu㪵n(P ND==|~5iU}ⶫ9I6hed͍gL`-cթn_߯n]]joݪhkWGuuK1_Z@K:yzDkGZuۏk4`(PRROW)N8Jrk֬ٵG= :uok4` .Zz֙mW;{>׆M/jgV͟?<;EO^~t e,DݭgyF^ճfi[w?F5`:24) u3/m۪V4jgV0V(PVο}Peäö?3HҪL?]۾PVΝ+JBlW)I6ȱFv 1Ay%N?(6i޼ycQsu+CQ`Vj @t9jY3c.ۢGa:*.Z7v|I£AYoTT}Nݢ @?;Y 9MN7h+MGA(_:O}bk: +Ys?J_MAc瞧ךovk߷h{3 e?(k6l@?.Q`2y_WEf6G?F㘎23gj>W IDATLG]ۮ9KϿt?!=ܿt أgo6ee-R;t #DzeR.3eeNSf%MGRt ]-J5Zu41PF(P\LU鉴(גnT}}}cPl]jQ!cT9QV`\Q,-iДX(0");+=-cғ\+nM MjV# ]f`\QעCMQIYzCrM7騪)j&MGQIQuwt2Brtq|1`RN?W?(+=Hu}:,q0A(P@7xJ5ׯn}>\.'n-3)z>\.or |A*rmGQUqqȲo*"[,͍Oqu3&gq*u7S/M1ak:P(gy{h= QL Dzض<̂k{Iڂi'y?\zMU<$luO}B@u{ӕ'[?@%q,[~Lȱ蘇(/v +K5jc8ئrA)j`(&#KA>o:^SO㹖i5 Zn݄fQu_z1L v'fxkW7q$׊+&0e}ϙ`9&lxH7ԫN185ZSzLCNuzk(&c%a$u'/٨ }?({.Dv?۶<^ІNa8ʟzO1M5jzLվP7ЃփϮ k^R]JSiMub%4%V)jJԛ `,K PeGtZ&qUSFׯ׬Y&ؘx??۲e6nܨ 6húuiCIv8({TWW #I&rJʟ @ܬfy{饗tQ%α,_Ommz'ṞkO3Θccb1xiӦ[3NWwvpjC+:L0m'vhiue'%j ?.&:Kn1m%'ʶme[V%b)mܸqBGF%GU5'~~ZjE}@VQR)Z]dOj_뾿I8<ѾUmk:Pq0 凁0(/?y@A*놟ʇZNoWk?6Ģv"ew?m}i'om8TrG(⸊C7q:\בr]WXtבhThDH---WOįբY:c䞻N'< ؍O~3zķ~P9tͯ*x[גTMM""\NUճ#> ѠLN]6]uu̱ɁawYFF_ѯ۟6(+HLXL555J$YHR}Sz |ަ!-~~n, KvN;Rxt,F400`:qu̟g|sIzu?e: 0Wg=uh:P叆>91$IW4.~ Si: 0B;LO\{ӽ q a\:VO8Iz{l!{AZ:q0F?(_j9'V?Srt% _̟7٠;|P]tݱt0r}WП6媿t ԓ+Gl5?oQ蠃#?Gֿ8۾$ ?]c[/tBe^C(SX'}8qö/IDBs?P?XѤ܄ꬨUW}$9x$:7!ǚG{6c{VmmgPkݷޮN9tdCg/ojkk֭[֦֭zzںu^P{Ob S$UUFTU& ׸qxdT ^P?OtG00 C!Rtjf3()7Rg}㦣rh[QM[lW6kVS}jLh^T}[ٗi /d` =OZ_s7N6(9 'v1JN:V:־y}@2ݚl ߚv~z˽U;n`>VߟտN>tdm"ϛGI׼y京W/p jyG֧o?\RW\|.tNI1( [Z3Цg2Zwju&;c-yo:\~g:f:]gbUyK[koAf*cl(7K$LںvߪAlӦkUW_g:^Qڰam=f6{^^ܨח[NPC$Y޸Jq>O16?q;ɪ/9K%^..>SW| qd2Zx_֯}V^xA7}&ijVS픦UմxmJm^ [cAa%0/GWꗬ&\cۗx"7 NMђ~k:^9shu'7IdR Lx λ6Cҧ?~w1җ0GR<gcAvuxt=5(Qjii[޶Pw1qQK$ʄ5gӴV~` WOT Dt0T߶/IzklߛpMJYmms{WƃMJ3[qS~V}zC_z3 gp@sEWHۦK@?}sG‹n ]puX}iԨV;|mYE!L*xc[ӵP `3NHԙ0 ؖ5b1tۗ$U;]xF+bihrr?(DmG\tL&5LحeLǀ(1_\S3MJRryŻ}X% e#I fhͷQc200zklQ4WD"Q+/VQ*07I(ʟK&lR1*07FV;t dEmG(18R*šZzoMǨx?(=裪G4-^k: P,YmGAPW*VhL|ql@g֯_o:JEF_A8Y5RXL"/$U3ŦcT4`2~?|c]mRhY6ESM>sfi6mNYfɲ,ӱK$xc앺HBZ|:(q*0\N^}te9V[ ^Wׯ]~P/g5o}?6x$ e}b|v;?|zgce/D5'٠9ɆߚקT%J) 6wߤ~Tf#pӯ~k;`Jv1ZɄra`:^ۮOQ*|I%yHԙT $)U]gxan?^hFFӱ*۾NK2hQQ6W:sn )LoWPyO}s < k 444e5ee[RRɤRqI{F t=?v(@ŋGd2㦣H&F_}orߩ~/\V~+O(O*R2P*RJU5C5jRUUNwgcnߛ= F588HH(~Mr Il/lb8&D`nV͠g݈M0.@5>})~O=OЫ+*]"P6G^>?|:59t âL&c:qDB1 |¼qʟJ@FS7L&twVT`7,}MG0,nl̟}@Qʟ@)>CSߨ.,}I31 }U`/,<$z]nd5ü TG3ܨ>5(͓c'. P()χm[eɱ9_ֶl9Pضi:Fe+g̟0P-۾*0BW]]>.ݿ>i+;H%,1F"Z?eGƢŊN`ٶ-۶{G˱la~R%.}IR$QDai; dV0R190($bqe~ֳ?0ım>?\zMG) hL`ts|D"ňm_!e)U0ة}Z6utAhE!+=RPضm_v}[z`V]Z4MGIo= }4k,1J<:'?!@zlIr8}7iJ$*ںB-*%lWI'*aGp"J:-W 7QvXC`XS9(C,K -`mV I='MG)9?Ol2jO}@ߧLFf4ȱl%15Ԥ5uTӿ&`վIRvF455K.r9 }566( `m1$G|tsk0rh^j0ĶmHrmG7˕3z$(Clf$Dz@٢ qlZD2X"x2X,6H$b((>ClW(M9Q}@ P.|岁r~@0PCyy+f|6(允l){ ءpE;NXLP.≄b;<Sv0Ķl.DvOW'hQk pQi豜ӊL|Q?(PX0O kF 0~ ¥2 {=`8ʛ`~P(C,bP{=J`۾rQ(C6~̒Gb۶Bt cF"GbY۾gYk=`m%<4z)e:PJ @*ց}~Vޠ:A 3̪K:AuՑS_3uz`ӱcJ2PXed`ϩP+̩PG_=JjWcCf4efN555Q&MR:$ukW# (5P,YAѣ m+tioӧ̸nL&7ݩO69 X,{;WjuMnh)"[ۑk G,{#kǢ~vSgu[J=r}.䃊'ߤ+-1bc`ȿ״xby\.'/S.U.@.\v\.+/S.y\6'r'=y}X$"v5VyJR/nSWgIlYZPr,{^Ͳe˒kowd˒cY^e˱,9#[crd~~/Dzd[֎m޶ mzlb(c?!UUU z $iB:]g(Y[A wxg:%Ev^~t 3؉zu{cY3P(`'9g:D*R6yQPĘR`k5ׯU6 |er@L)bsLG7]Xx≺toƔŕŔH%H&O$LjŊc`Yb)1EItL}NtMGv?0<ϓkTcg*0|ϓk;cP8y"%= F]dwNGcB;߯kh^u((bA)xtl,1`(`'^l:QǦgQ츄 PF~zٶ-۶庮lۖ8r 5U;~aR?UQ1ƌ(#šdێB nAW>W+T> @ȱm9нm"khl9ΫҶRqܡ]Guٮ3>#zr\WHDse=__9;na+P(2j*z]=~AW8| P" p^*ヲ|R8\< <a5z67| {i(spme8պqs(2䷷q{? ZvŒ=2tm:PeDґ(@E,fQe`[L! ,.ܰ@aJPc:pYq@ɓ'MY1k\ cFm#[ 0%(qMMMOV"\ (qtLPŶ/PH'k:q*_|Pn]nk|{b|PE'}*m]ί9@oe:ȇ޻r;[0("C@-zoA:vH&kK/G?qqP&("迾}[sU>۶U(]StMT?QuʩC1+QE蚫ͿyP7/0nܪZP>̀TH)]]tZuuuklPäF56*]7X}}PH?@G;t.xN>䡢mZ(: |P")']5nؖ8@sQ}}=P.-]g:Pͩ5PH|J4t*dP.eZv!(P("v;ߩ'z_V5N'(rB-n_m:E'(r>xQ((PEF'potg3JP]~qw0O(P͛'$@(^#A  >t (&\(>Ly (QP%dх.|gP,"-nt bP444ȣ҃ϛ@lBiJ̅W\%?@AJ̛&e T@ Cc?(|BvGk ;/?380eYcE++:Wߪ_tpw>@8SO*}Q:uW;>@O|\am?I[/=;>@I$귿kSף+__MR#{DYf~+j)}wcem_(|B2w1Gkw뤏.'LGQ ̳_{TS>A?K-i[m:PB1ŏO(PaȇOw:_0 (Y0eYcET uůE~Ғӧ7ާ5[MJNȶ/>@6mk|KkWOӱg>toê9}4KEM-ť\RKʹ_45-LSSSM͚-_4j( ⾱ ~8|\8q]'ρs>py>]OMywk&ӑ= +&_6 hלNIQ?nm22U7*^7e~v.~  _=zu_{# g_%c{# gY,tq80Y?N*&*Z+M'} ऺuﮢ*1vi_(T?p)OO.BGऺCtJAGbbbt8C`t|JT޽Ud:Ю8NCpR111zuwVo:.8O)S2`@m۱]+4qҫ#2t (2///6of/UFӑ6wLO$C<'t:CxJJJt4!@sLZ]NooUB DcƎ5 8%?Z䯯?XO\n>A⍢-4G<9tPh=9{t׼[5/Vׄ3pGvjI=wۭg=X|x}JzkA޷יȊ,4\gfPCcڼi=LcLNKr~YM.?`ӑ*]fVwy]MGNjWa-9Mgj)66t$e,pO t g-ӒUALrŪT}kIO0˥)!Ozh-ݣ~H/1K]^c'qlrGmQ\.m}EH:nlccogl;:Vp2-Q!K)ӒvH}tciQpTQkI6E>cQp I)f::Ƕ? !k۾M5z76UXXnltMX?qTe8˵pѣm\ -Տ0 Ē-m?u7h?1)uz b3h]zW 4M+s c.%բ4qVgi两ovU@_S=(7tǶ?# aК(EEElM:lU׳Iƶmb: 22R:thekCUC4:87w\olSuM/~@n;*k\xoQS+,OudsjSJn]a4<,1n懱r-\Ǝg:)qv@7MwΞf: 6ʕZ~@+F_tN1c( effꅧQ6P %E[Mixx9tS.LlQo(@R Z] O_olMaÆ[6oO=- x.mn>9Sߟv`5٥3p,Oi |$W1&h츱ٳsuzw(ޢ[$(;ũ諒,-9Y3fЃ=b:P0j떭ݽ=.70ז*"_uF_rr1c|b^|9 vX\wlalЋFhGC?`P{-ަڦ|m9U Иk܄5xV?'/KϿ1q%bl^~N@^U]0h(}R]˜i?}9WQ' ָ5zuHw|K53jD2:Ƕ?Em:F|+}_HHQ: ʫ-S^m.3?aOM6Th}Aѣ5vx;V^^g͟^yM'?t@ZRUut0?\cc$ ҲA7jvՖ*L*Q^mˋ׭飔-=:QqA{~\;r2 0tp83Oꖨ!ܥHVH2ů4Qhs̺QS"4$(tVyu+ytRyե+{9[U|ի ᗕ频#Asd4& IF/|~JEE^xYY'h\XHVJa.gfΜi: d1@[(Į!?ѤϏ+P+^OUȓ+$4D[wl u f1)u gtAZ_'| 6 bJ6u:(m&??_oz4Q:Z}TKˊu}ÇW^ lq-vޥK8_V7ŢW1Y/E$8P]8 :"h3w:Umvu٦cɓ{LȁrgS~}VJ~ݟ>.Н<ϿZ 'gK*J++9g#Yc)WOӚ1.RP_g QhU q(ʒݑ/ZE>X.e:Jiӗ٦UysMh5Ç׾*יpgzfQQh5;vP;,(ʗ)S?D cM{[p)L꣹c\ 1@GFGk2h@GΏ'˿󯽬v|=-Q}VQΗU{4c)ӧjM{H;8PQpg?ZTPc:1:E7.J\';utSч5B^tQP=e;n(.!!A4% 5 (VuV*0͕4Ci!}DYZo:i(az(teYf^?iUKcIDATWju>1-__سz(^TPWn:F9P[込o4pTmڶUwsX-v-st77֚}^Soc:F2*9`: ۛ=b:^ڻFeVe*՝ݫWO3定qnRkXQ+'Of3W_j7tdw:TaSMC)`:&pR}ιt -@UM>]~TgzlE{yrttaWөFgS5:}?|حP~.NPC/5Z=c_ ݻzDq:eUzncӮh QF+kYy*hQE}*( P UphBS~/Ph M :5͞t:Mʴk.޹K;7oݻio^,Q+ KaMxYdXd%bjl6,Y,ټ[ڬZzde[ViĈauvyt]}/>3߿_μAKzy UNs`Z5_[6jxxoаj~ʼ/gG8C?YpϽSq]zkrjKETֆ#ʯ*ՅCiԸѺ%"OO4km4st-w(³twjLG1bɚ)/SW(i\xV˿C}#500J^mye:g_{r:Zv|WQCc4/JÂC.H TL33L?/w WͤWiKAmk,ֶ#uA9Rh;6JRVZF?͑_r{nR6bܽШ*U%>eM:=/l*XaoVo|<;z)պB9I&YjƍJMLRM |MK|RZӭr/M8QWLiiZf6nޤ`2(BfsYJw6\ˎy2IIIJ~_j P( 9UW9NyzGJ~Z?t-@6~:\_m \^pxF^zF!ooo1q F?LBVQڝw~׭UP{ШactVUeWb^%(DO䫧j}={hcF&S4KAg­}zxG6@ P0-S}J/c˞PӱpLV 劵)80j:Z˭99Zvh^U]:'0}d`5Akj]}*h <}.gJې IڐA& Rk >٨3.;nsiJMMUu*˗6}}+[>>w}ycs|||)ZrR6h Ehp@F~KR^sfΜi: `FSS6oެ,eܥٷWVo WOB80tbőL}UWM6]wMu:vywιEJ"7oj>k YQ&iW$idmP}uEk%LuO pFϝkgf۫-[()qW'j{nSx?^`wzg4tPQ:tHYYYR]TXPWOs!ͧ]SS9Z~pOY7ߤi,zavhNX:Z]G ^~gȑIjޗmÆ ڰ.Y֧\ 1o SG&=GO5ݱJIIQD]EE] .MQ hOf/՗}.](2`Lnn#+l!\ 7uZ^W^A~5wZV?ZK\XpP; 5q|s3j(NѣePRF&N0C(++k.VQJRh`h ZWmSnHDo5@ Qݮ[z2wRv*60B=-5_:&/[ˬ>{pfLos=M3}S} XQNP+hhGY&^>AWLJ]th0 ??_IJ^F)7H ޑUcZuRp6U}rQCPvvc37W^Vwi^%d Vo|C] TZV;,Hnײe{.YO>JQ$Iolӗ;4|T]|Ŧ#IOOWJr_տKw kp@DN#UYBCLB?22O1 U4?5H+r([NZξQsN+J=1j}xLʰ=p;藚0 clܸyUdA14LեZ\A̿Ew^q-۷Olt,;[y U=m PcS"l?ѤřZQ.~M>;K&/ &ݒ^:|X^J^VɉkUZZ!ݕ` אn'ڣz&uGO>O"hjjj^%͛L䨪JqAJOnҵuoT{|8c/miH`LooQ"{]R]dURR\JTrrli_FjHP7+q9Ciԓ>o:V@jҤI]wFVh|H|'>җ}ݪ~~$'+ijmP7 h j?-z|fx8Zp{r2uCyrJ3SF8vRrRVVS p Q_Ўl9SolSSSSq~?>}?"6([.D/yK]1a#W(N7kuW~H^^^#$֭[ߖh]:]OS*k:$iyq.ڪSh#t$8B>x=+{k``,Gqc_?P#9d(tH_*.8+{kJ9mrҽzd@z̔Op(th999zo[45f >.?OJ~[Sg'yH={t95Pp NS[6MSNMrh:CMNrIN5yH9t!5!jr:;rȩР`w>>}~pZ(]J555XX-Xڬ͏-Ǿf߂4p@o 4Q17F(pc?nQ17F(pc?nQ17F(_z}IENDB`Fiona-1.10.1/docs/img/zones.png000066400000000000000000004101551467206072700162170ustar00rootroot00000000000000PNG  IHDR+Df pHYsod IDATxwtTuɤBHB]4 bAņ{Â]]{,H54U 6iS :g٣ɝgp{s? HX2@DDDDDDDDj0GDDDDDDD$ c*DDDDDDDD˜0GDDDDDDD$ c*DDDDDDDD˜0GDDDDDDD$ c*DDDDDDDD˜0GDDDDDDD$ c*DDDDDDDD˜0GDDDDDDD$ c*DDDDDDDD˜0GDDDDDDD$Y """rssٶmeaIIM'55 ZjETTQEDDD F1ZVVsfdլ[ Gx-SKQ^>p;'Hց RR8餾? GDDD"Z ๧etHsӳ-47dgemM>'/e!\DDDzTH)[{ _3K!fn2Wfg:)mw #Fc ?"""&^ys(S|Fy{h_|:t+*DDD$ݥ\rHn8n9K|0%q֘ќ2t[g/"""K又D 5sWU,H&7EpUg>gL#N֋xl6$''S^^l&112HGDDD"a{hP3Ԅ-lRXdw;yE6;-pФi+HVzFGzB又DW]ѻCS: +v:K.&DÆ '"""!L又O?Ԩo>Oc•7bt$ A*DDD$m޼kOﭻŝ/k}[p FGDDDZvv6EEEx^ILL4:wߠ { ]i7:Qj]%ywZ÷GDDDm߾<|TURPM8ͥiZCJ8k'tFttR' 3\Rпs>}>Oi*8"""TH7M+#eUT(rCiy}yJʂʱbE3sag΅_C+'++ݻwSPP(gulذVMi'AFuS8Iowɭ=\Ta^%\cc8߱m˜W1ֽ;7 呓޽{ٻgb-de%=Өpӡ 8W6:[g>7ش?9TcɄf̛^b>-7dWV"R'q`>%rv VDq&и%9Z ~ /.^Қ47:@又H=f#/ZLbm;s>#a"==hbcc% |Aq8z|f$ə=j}x}A~Y eEPV䅯mw 28"""RT#wO{+ݛб?F¬e{`vsN㰛je$[׷7#ai'7n6&\lcμFG:GDDlFkf؟d62g/,6lhtCVȑ]vh&ɦa\ͭcB&VZ7@E0欱FG:UEDDBݻؿ;@8H}7E?0:1M QSzUK3i(%0}8]o4:?"""!fSh'4-5L3v*DDDBȽo%o YIJ^ڱv6|etZGDD$lڸS #ثGjU=){z#"""L#DDD z@@eH8@DD$|5}}|4Y_ -N ŋ&..8"""R4GDDdffsBl[<7U#tԼQ+23!"""@}Ԑ}sNVd_q>ahw ueAmO("""RT^y?&)Nb~ڧ{B[dߞ|("""RTeKc2GVpoM8뜋lذ6m3|pvŮ]X'v-;q8t։&Ii܄5kF-ZHP#""r }~lύ}ĸTHxY7V '`Ix+YYvE-at"::"+( lt %mڴa6SX3R,d[HI4c`ͬ}'yYϵTD *DDD]]hLDېv[DVVQiӆ U/s-o,Z(""B又txYKb("!cZ! ~kt %Ndžؽe3`t ZGDD_V伳p)R#~D܁6}15`~dw֩o[`t9ٳfͬ~'c1Hrh#$ƚv -Ӭx ͑g "I又DN:ѩlۨ9ZN{Ǖqc0N:$4?z3>#9 H\$k_>"vTH۱m CN)QXJ5s|$".'$9U\i,Z _|& ;y`X?2Dz#'qF1:Hkڲ[ng[-,rr[Ίg^9d\pF1mݺ}_|ɩc)@mGaw^?"Vೈpե#N\.;V~v"M(""5F又Cӳֺ?"5UA*3[r\***3{6};%A)tRfؾ};̙ŏf0KKsB++Q^WAE3[b1:Hд/a2IHmhgF8&6mb|;+PΨ>zcQlVfN?ͷL ;v`,?vegoxR7Įywx;ζmزi=;!>vfeӣC畓oCS/ZZ1{M=9F)UĵWȳ.]ct4?"""?*grѦ}w^?9YD~~&rf.@\{mFG_MUWңm͎ˎVrx9-4mdjh"v8)I~Czhڴict<#R#""r]>cӬ 6YvޒA'h.=ktGʫ`z/;9exёDDKA/>!y;{goR?r\ƽd_~H""GDD/$%ƲvjDj &n1,{SfCٹr okk%>^SF9,?"""aQq^4:Hx2';wl;n_e]0>xȅ\{fDd+NSbt5DDDG'1r7P0I7xr D6"11*+JY'+-Zd0 8گ#?{ QUtH/ahOf,`:lsm""TAff&v;E$G"S^f~htP`04:H(yYe=*+˴nHu}C{#wxm ㍎""HEDD`̙;18H//@Zw6G 1D$B@ /ҡ{9vsqճ:J&R%ƘڀD]mݱ 6ED"য়~+r߸|lXv~wg|tbQD u?֭("a揈D`9CBMMvw)Gj)Hxx  ׍EPU ބq/3:Dpsޣ.~8R4 L/]7&WAHMiWx _W$=QS\:ftWYvyu0@i)AIq&lEe~,&UAV.M&Lf(FzW獟ș2H>G/+1_-nt,DDDҼog0S,ܯ'`&b6{\GHFvս1w>} K~˵7Gfj!DM^K+p!0TR]ZGDD"/#G5a'عXnx^_$uje%ϾQDžn3v%s+~]jt$&q. Dr4THDSv13'wa7{VC$bibᑋyI|qy9vNb&JKT*DD$|>N5: :G1u \y>/""u|6}Q嗜=Tfӱuhv/TH rn83;gFё8w3C1b`厳+= ^yqH> .hK}O,gx/Lӄ6  -,""a)??{n@ELj e]SJ))PZ &ʄaDR,4K"Ռzg5n2Uo*_< > b\f2̌8AK.'5C$]t+cy8w_#1瓟Oee%`xRRRڵ+QQeyf~7of͠.((32cX_h9n@ IDATw ;kVekSBs^^(. [d"(@nR? v ݄n&:L#HB 4Jf 9Æ v -xLجebkVb1HNЭ'igAR~ZxXD`^ 2ܹ+Wr`f͚E'%%$ƚhIمழbsDc 8͎+:rHf#6,y}Kh#\~&D5Y%eAK+PS9EAl63C۩(~l4qR;T˷=|ۓ\؛>73sQ tie!NM㮀2sXNS D4NC{#?Hu+g Z#GL.eU8zrTHcpݔsZEvxrtS iBgף^~Z5851s`ZκIR0Ev3|*ѣ'b= /i|3oU|5sFG#P#""ʦgI]%'^IFVSHA8"NlmKg6`{dx-D 'Vn\//:%9恇g^FG_=7^y==?(4KYOVZE@又 ~u1۹zU\![lc2/- -Ly9Ig8""Gi>Ͳ$=FG [K/}Z׏ 7'ҥKTQQo 7;{x񻨪pKy2ޝSݍ`M3ؚ!)Fvf.$.D+1֕ϖNR0%|Yc&p'TUUcRU3{<<<%__~FG#P#""!'//GliŬ=7W+Oj%2X`['ugJY#b&c=E?9.* خ\zmt8ޏ?彷ἾjWNOo >(r%""aߟW|NE(~urzk7Đpz_x2~hjVF. f;,n=ĺWr2`H&M~H֍_óg.R_ocAJKK!ՠ/""RzN҃s|⡁4DIYۭωb tS,'2m˟.ceFG:r$pͳϵέlq 7 ׋B#+\qHNiIxs\dA又 -/G,\`h3LpP'S团ij2nY>#;<8!oW_P$\k7Gv)q59 G|dWI `tj*npr Wqh<-+p9D94Ҿi9=gs9v_/~z&\qqBۘ|^>|"Lس"GGDDBFIqv.sxJ޸-|?Z |dyIq H޶=~YGnQRi mtneSKIfR̤%YY9~K,ZU4K3 ã y/n 'ޜ*DINrb['C{iJfV,yg@ZC3iں|Hwx(0{2Ƙ~vF(qDH$v,ٸ}dt+~\N%FǐjO[ .2o6 "ΐv O7Њf4e"qf S $i7 DjCw؛ 0HiEev4In甮&RM&Hm`_v*]ޘQA6lep.vpXM w};xkg<ɑ9},[{xT+bLTHx;9o!sd?MSGTow`/TRZ 4LQP#e&1Bbm+I#+ZxhdhsD$M<~ǷtSgV\ɛ>2ap9MZ軞К?""^}otG&d~#DUnQ%_cݎ* g`Gr7.vѶi䖼"OyiҤQjUff&,crҹ-"KؓO\|7wQ")""!ﷵ? f FǐZ`#hh}w"Ψ>Ţc\f>' O\]IyyRc &/[矾MR^Fv6"6;e匜[鿒n_h\H]d4R^'X0]ދ0m554O KXįG$mj%qvf3G5:13gu+z*),.]VnN=O|]7>rs!W""RoLC~%+wDzξ\?3xr*=ХOw*5|.tL[w$i< >K~HJJ2:Q9cNyhfv6 M H$c氙z}s揈bҥ|KtMϨ#{?yyN$w?Ê ғ3ʰ^ /"O%"n~?_j }jt#ڹs'GKVpiҒ,Gub⌎"3 ÜY_2,[_̠~v(?J7s9s++[Y\'vx|.rҶi{h.}M^q4 5S$eYоK/͇it~f͚uKٸq#g-fb]f@Dn?!_5H[z5S_rV-Cx"bw(ь0:-O V{R:g8i FvW?gXP#!kh)``roI|!u7bʞrZ{=nU?̝pEsVhJ?r-KG0Ӽ]-3WR Dfqk.u>Đ!uسglټ}́{Ft]F.]54QVVft 9}EDּ?5~ƍ#=ѩlVj辇y}|[3 hI6&& ""R,xzO.v;Ѽys222l޼;wur6nDbQPTJ y=$Vp ZZxxD;ȟz@W""R+VX֕q(-|4]f,m.GesPVd&/szr[S lb.c^$R] X]nf*ڽ7r hӦ ٔg>ݻ[,u{$XIM$ƙIiiwze=5):JO}GDDjy3iD9a?-RuS,G/:ĐvtX^ܖO;9T'q. JMb1ÜeU>H553q @d?b6ﭤmCD:t4̽cJm4ME$>r*DD߷*t9z ,lS#n31ax},XSż~ιL8+Mlogp71BW|#{KHavRơkSZ#!//r" yhMX%[ٱN"f= bA?[؜dR/O+#:BT;ЧH}:ڙϜe>F֥H}r())2:ΰ""Rc~?w$b05X'؛Upv5olyc˿ʠCEl?{}Zop8']2jr_F Pg@b.MED(%Fǐ#VDDjDbb"]ps~Sm"5L@b3NQ`^{}Z=Ԏh r*DD6jyx@ܕZ3IBCF[΋冿kIH}4QZ'ԩ3 w#,joe~?:w2igE]᧨T又H}e:?""Rcʼ:ׄͭU9X?O V ĺlΪR29<}EDs !GGDDj'7g?s~퍴9Z6יִ}ڙXU#DDh wY1Tq|\w?a:הAlɳC;%뽵rkǸucաDDްL~Me*DDL9?q/8aeЉ6TtCla=mlS{kQXXSkiGDDٴax:6-3:NX|/ IfRXfYUV&쬕CH-qYqFǐP#""O>⛙ MFG k]3tkyŶ8kjgakFG|C%U%"RD;ⴐŋ}vbcc1޵@ @=޹~+e5 e<\<$"0oU;Q?V1qƏ-""#iȟGD$}嗬^ E)--"6. $qX4hn!AO]8J$T~[,#.OG5L3]?""TT`0th՞={(++yDESz}?g :Yik]Onq6st+'ft03(aߓ!>7K/gkALŨ9lq:kܢE ĺLNrB}6olLW""sO? +pZ[S\RBz^4kّ(W4 4jԈd7o\OT?n")Ψw"i.mt o0B&r2OL:s+*]_J>ݙNn"aQh H;babN;)>ZZhB[^^E~~~'*DD+k(~߱0eh`ҘCOq]WUN?֝Nax}Al#d"7?("*).Ȕ2f-Dq{ Z)( p OjC@""3Dsk}H4SfWw).c`63 b-&&iB0 ii(hM͸>JKKjJ0=LDDBޔ)op:gt+bVf,A<*G5r3 \5Hmi_CnWnvױ~&NYCDDجy8bBhSK$_č7it z""w~%M &u< =nvN/0s3eL#10|z_t3+ɨlT_rbb]qBpaۮFǐYTD$lذ7^yF ڢؿs%_UYi]>7f}N"r R\妗xc_QNX4KUD"гUгIo4emv(--5:1Y4R\=~.;Dgt cv(^{s;L+NŏD{}⍎rXQ*B:cn"5G.L&kjҫO/œqEG_v%׌#5I߅"K?4v!GDdeem]MAZ%s[3wLʍ)!j5.Na͙UG{|_IϘ#"RPZCt r*DDjIqq16]-XHbT)Vұſz@8oY#~fMwr>ߠvB&ٺ4.0:o}J w|.]>LR"eɺJ޹3~g“Ev[ho!"RC6fԏ(3Jca.DD>`*.#ɼ;5{mW^޹#tw9Y>:IO%/bhDߟ"^_L2:FM`YtPGD8|l޸m OfVTؓ7|"("ef.[?8f?U"^S81׳]c\6JKKU(YED ?+.aB_r J ڣzexNaXi\YV7O'EGYp$''ERDD+VOHUrsvI*٣>?ٚ۫W\\)W˩DDB_T?3@*SU{r #9#(fE@ňs#F "("IEP@ :z?"({{>o:\vKA.U{OaRBPZZa(TпSmI]"N,zo8mPOzB^|}sǥH'7BEbغ@F_?.lմKQ8(i\'8=* %}w^N"Hb/[VQ-[ ܹsiڴ) 'Ǝ sl;Ek@z)_AU #ۿDaI{CIA>6B\~\c۴QLTN9"};0eeeV{!!D\rn"sh_f(4&n0; v +kQ=1}mNT713Dx~Bu4^!)r !c:ylJaRB:ϨqwrY;Ǭ!{(oQgN;^ /V!ޛiCCG#: BӦMn.]Yk$7 IDATYi@ޟ"=mf+\>/⌛ڳ28l"LlDPQ7O[RM\W?1|4|UV{!!D=z 8Brn18 n_xLz=f0\ Q#mazS5uvVil0mMEQh\aWX1D4nbSMZ89F&6d>qB1T᯴: )!jd"Gx]*RN9sx⊚qc#ULoٸàg`r%k4m!S  8ظ=΄Q&y*'vqөBҕW6nvaq[b/#.OIaL)8 {U\9ÝUtAso|A~ v pĦlq K!P0q)S),"&I lC l"&I0bbP3*өnKlk7 (0Qyq8=KqͬEQϏ2'M;ɱtnaa?^^5u)dORL4!R^^ΕUZ" x15: 5&BkH*~w-&Ae"&lnpTɰu〺y h4 B(bdrIe@SUJw&ʆmQNr35ֈ TΊV0znu$!Y\["D3Ec<9ϵg{|8fXR1tBhФnӮ8IC\5ACg)k=|^n+7^fT:4QtV-HI]?HN%6ZElƂ]PX9BI=;;hX!ve_LajTv |޲u1^HGQcL+.kau![$jb1q`W' C.æP'o(mhZ`޽Ü",^gf2BU Nn7o͐ 3{e h%4+ۍWݻT2bHWk ~zQށ }(3x+mn0oyjP@ ]B;M5Z4RRSbx&WQsHl6cl.S[;:M8Ц2`6۰ۅ~[gfSqo",XSeWXCim̙ :oV;&DAf"UY㕉a.{t75ʧ3C4+VTlh*8l*Ɏ*m&@MShPhY͊8FA[{.xϘkϖ̭o}uUAek#T6% Gsxk{8w缓3ta(Q~m=fҙ_}F6x wY0].HBYTa8w"D6m\tjb;h/|}qbqP뻖nhNloQ^sft"&l.3(ݦSbx\*Y&iơͤ#F(:>Չ6p8j#|ԭ[(b7Rޚߗԃ9cle9ұa[Ond_{d ;Utj.EQ\|~GiTWV#ثmPs=n Mp9lئ?[a8Ϊ8n I*M4w>ENo]E sœ?8l(g_MZEF?V[h<W43pxsqhҴ9;v(^x9W(B$Պ q~#weYE.ܜ{ƄieUū ~]6m*Y6<.*Das.Ð8 tEGx :B7Y5gW6szVG⏨?Uu{f@E$-.5iACh߾}5'=&N1lذ6m0L%y[9h^OBFX*olgQ?Y}@q( D0PTT`MtO|(΂a r*Q9Mm˶hbf(Ϡf|u4ʏKusH}']&7ԧ6#8(b7R{o]Too_:Q~Y3 䵛FG!F6ne iva^HU^ kuc9ݮqQi]ұ .7xNn<~V[ _ŊRѩѢE=4mc8( ;*=}E߭区" 䩽HoG9(լr ΪR՛unr?&L\;A\7(*t0rl?gE s~D&^Y&?бi K`1^Z5HFb֢0]-S`GJ:;*MU2nƎ8%mh2)cGq}+gyQn#j[6s@_B^te:[+3x1lN7Uj_{{r~o1$YP{AbʝC{MVd\q&ߵX>D 5TBlnpϛ! UspH}o+~q~Y&?dq* YlBmܬ;'/Lk9"v#wV<+ƾKk6(hP 9Hx3{rJ9v{9ajKY8ƦeI]F+8 s! L_SK/w}LB'17v4ѸF~kFN$Zq+7xy!mr)m`X!]39p2iR]4:ƤI#&=[o(b7R5V `ܹ|=iK,ద1W˟]3xKMEUжe*.x+9JFRSuSҢc_.j$[(Gy.;V #&}o)cVG/6l7xqBūd( #8rOu[ ޝeAA.X5Ws|C LӤO7%us ?΃\)m:l.WmMh 7tҺ)]u*&͉:r>Qn#jof=uف͢f`d`\v,œ#Df."):16ѥZ|:{3r^!:9;DDoз]mTK8jQ>$/ƙ=: TgQ.1L(qd[1$3&0"&ia;n-tk련+]凍yfkVG⏨Q.vNc=vѡiz?>ގZ/]yQȷN7F1J3OtuA׃dƇތ %2߽צ #MٸlՑHe]#3b]X[6T9?7卷>:؍􆊔F)--Eu7o?_UUeP m@XcGc|Bc~qen#?Cn;7C guQrm1^%O׫^ڨ('^_yeH:mM|82AX/ӭtC*a˗zj֭]K8WUɖ-P@y WұU>n _/dA{|m[p3yjx̲]V=5B Տ{on'/1x*޾= YTU wW!"1y~BT! !Dߪs{&Miu)jWbX#Gv((7C ҨJ,fQ(.B~UL1}UW_ೇ۶u <;WYWJqzZe|>7of˖qWב)7B?û"jqEx .ϤECyҞeN/SɵgayVG⏨/: -Gh؎֎>~%Yz1ws#/'>pp.~:MKMxxlz"xl>tsqJǏk(?z90i 7Cl١rx{ t=I: ;Ȃ"vZwF 9tmspxr3U? şyA$Q1ӞM9o t=7=CRs7m}VkJ=iR]鼎 l,<8PY[tcZGthj[&8DK HGT.B$4ME)h{u.O'qѽ~v!=Wd{MJN* ~}ydW=o;vLj?|[l`!i,Dr]HswrT)kͣiɓqʒmDE,^_MgvQ{Ү)@`DEx4lօ>E.]_~>Ӄ8r*:IDMhu;rhP(S R?Bm~)#" rgs[?_v619s6c1 I=(M!޹=(pzTꮱ^ARB$ǿ컴l(OG vuXJ:flZf^Aî1tns Xy%_z_yN8q8Xߟa"gQ˟ 52CGޏ~kOȱ:H3{ #H&na9'H'ƍEgS^ߖ&5 SRW!eb8P'WE;6y^KrЮC':ރN73gLᓷAjH*D5oY 2l48p$-H!x8@GarѳgO?O QVVӱltޝosqh=HC4V2lGWGSNhs!}_}wDqB$_u^(n^dxd( WWZEQ|ԳN:)ǍegoP_F#3>ۇ3!MʧWYFIT-mwެ,vȇg$m2gYSZ:Vy`<1"&Z_<^Թ\Q5S8ě Х|f }ϻw0!Dbx] ;rbfLFqv%Zk4+?*R:$^Tʧ}Kb =*?[>}oUr/Yü\􈟣۫\[ !ǥV?)GS̄qpBPYur8v~X嬻xL)BrT#EW^9Q&6Kr9Aq2*~_1n-Ÿ]ldD?;ΏLgu!nr3"#lQ^zs9tpE7u=$g.[CF?)[|QX%Nt&>ku !^eG 2\*ݕmuqе l-1g;S?_ VGB8[!SS&~)q?; CޓB*6Mag. а0 eˎ88usU`=oഛ)Ɍ1!e|~)I!SLf ?U"9ӴX?u©GȥBTצuj'ujk q\gC"qLl>!\4k !.z1NV)" ?U>$"91̳\~֗(w%9`:N+vqXK)䩛_#a$lu,!D 4PUh4ju'RIO?q?!jhޛ.9?q̓n8 22QD B,*7"*\N˜[3p9m\u[{]m߷΢zHf شi_~&OlEr\wn1g (}ImXtpU}G1DPCE5Tb7 1.-\\v%N*{͔Qdž#//(?j&~63{ x_x,1}
    Ⱉ<~ux#Z)&k6mB=ӆPtQޘz Oqה1<.Ü}ZyCKc|=7m~B-N(bUL<.pTiSVTQ"mU<ˑGiuRa ;D8OCĸKFt'u|W&FaP\h6l#Hw.Ƥ0=y'$,Tg0OZEpS~1e×;* 6C[ B,J l+86Q]{iX(B498㭎"C{sGs=;7uిỤBҳ*ƘLdy橶y0-<|K4ޛ'c̘f2^tu|>c?ՠitm#q.{2@4c.!jCyr;ظUo_T n('//I1vBO6(T?:X>N6ՉeDtuWiu 'LſΧwW_g]O/ցW/\4Ѥ8h\_z !W' өyɋ٬훹ʖrQ}r3Um%йƇ "x\ V"IcFs-guV7x׎p9y]v/XѮ ObQcJ T +;QԷ'}7ًIym!Dx\ ~Jdu`&|./0 3$ !_v6mk u zphk6'~ޔ۩0 }_H'^B şT"by,Ebp~ݤ_q`:t1fR("f-tpXEBk.9:ou2Gtu+RM%RII_N(c*">1 Z"[N֯#\OmHخ) G0!҅ץʼT"ş1 >~ NUEb {'!!wRU:SkLj zvaV%6P`{ErGD$"]xvA:NRIUUaiI1~.B&2!Dd(ZE{"j!;Q,i͙.)y/H,ێ/{B? TAsJJG7 Ѿe"N8W?IjMpXKg[aډ -M{@c^#[IH ]RI!RIf-ڳvg1D8NVBDYx.bksC]ZU^pO-;unfB:C|V!şӧk+VGijW *t!FqK&zF/ώbG d(糥#DLB`Ѷ\щ}w[(CO!Dr8NGirbNMtT[;Z8xkAs`~BMB~3x~HeNS:Rg_8("M>)B s9n!jۆzxi.AVmV:bݖķ!C? VTTDY [&x29WQ}UN9]Չa.xǵ\4+֬#4/VU ],ʼN_U#şs4oՁEk$٥4$BZN, S![^4nps!.}2DUHctdtfdx?rڡt[4+H[?+VMno8 ]a|&B&ȿ]\7HSD;#BnUz[Tq*|V!ş$GTUeȉKaw`SM>Ss+C4qnZ#ā0MxoJ{Arfj"!DTxV!ş$hִ wȷ{ VFA1!uP~!'|,ELz6uqƍĻh .[HYZ?ETdػ-eA~|:FnKNH7Nvi)0aA:Ƀ'9;C#"cHY^"GLβR{͓ BpYNϏGiEC岔coQ. g*!RǩɢBV I8j޽99LfRBWz:ODKH nB(\}n*oB0[O EA*%DbVhB;(29<1"54gcpytxl\HU^?P}`iq˯UiN^O =8AХ?YE w7'uKT+qt9/%TչXhX݌qF_cO`j)$aa:u BòɠQµ+pp7u~uh$}'+ |xO6W>dƸq1N]:Rd֬Y4:H3rS!Al4y+a7Qu>^ZmGv2x;Hofh$ 6 ^)QGyt q lQvLbu$Q m0<7Tfz\p)N^Ž|5OgUEUmp]bj(y*@Clْ`,0HGjj5/VxibBc6lrL W]zJ&/S:6Upߘ*zyr;M~Ü%Q.Wpv:U1RIL2RINl'Ol5r;iS4vz/iCnu$!?ᖵ?A׶TcW[l\9v 44U!tʚJRI PL@_͵~!!D8+tzuD"T {+B$3r/ٲg8) 4a?rtqL^/I'`Uɕ.JEh8Bύp1}7deik̠gG!ܼ.rSY&V\tZt-\gʀ?dC<bKSP=8MSQ?AƈL>Ng=U>V52$/ZniB:.nth"'Æ X YU%1hTIU@T8(_TФNQ!E6?QguZOnE褓O1O3W("lnд8|5OÜstO0 g /SxBRROA492:6wѢB΃YYg8Kƨ nu8:MZNJ:Pr)^/ U; vV2 FLmEktVlY:/S7׎BBӺڥhXy] ~܉&1ryh Ỷ]*ne1*&vqusRP0vaϳ:JӠPA#2kQyMn{5@ߠ]mh'۫ T4XEg6TLnJN$j hVlk[:>𢒪@~5\2XY䥉!^*F 7㕮Z4`99|4~:ܛw( hHebݭN"q!.-OpOK쬈qR9Y8lL&] qj^ iTQ l%ӣR'Fb/W[{8X [CvYWDzDb4(x..8OTr+!ðFQUXn1j=)$7 KbV)\vcQ٬~^s׸*~X,}dsnarʍH_7⩏d~H:9\oe;0QT^' I'>25x{yNb*Du4ƨnkfr4j'wE8b!mP,=3yCiw1v>OW>ü;%ĪR JW^WeuZO)㹰 Qw? s|gƓE9ʓWAʲ8]z eِO]{oY^yX$SW3 `9:z(wW' Vlڡ*7&ykJYj^J*>uC[ս)IW*yY U I[04)h\_NN2<Ҝ׭<\*.6l؀j^rؤF(DRM9Kt?M|n~1̍g*n5%7v sf'.3N="vKEvc3nɑrdk3?yedN-e:0W( x\ ׭][o<*UAahBo 0̌m؎ }u)rl5)$Џ?Ρu Kb|H ,#ek6 BnQc(v3kb}(ի^ ᾋ\+Q} ^%}ۅQ&„:>TA[aVރZ8omKVwK1 L K{U5pwʜI'! BM`X+zk˵`{ņ{C (ɔS?b~H2mϓ2kq3N4A<@۸SXfDr2rCn mĥ\wj8r.P/8>J$" X'zY md#r!on{nfԐ'woK b4B'xecgPjm_TJrM$ěeg| T.=6;0t7NpMULuhqu,jjO,I̟?ݛ cc=V,\fqa4Cr+S"xM6#GۆMf›Qθ@o=}&JYUb rk@]Zi<,ɣme_$'Ecy`LGq7TrKձ-nE%)S'?0 bk 'T0uB^C")=IۯX6p9h0F i"\d+Nʢuՠ14O?ï¬-vZ`3H.T}0}x!gM IDATH^{0ثܠ3qJࢣ-ߥT%9Kr4֓_}OXG!ה MbI6/mq.[K_ⰥơUNV:Ltn3oa}[ʸ4sVU!?G թa>m)lp >}4oǦ:>]g"xlv![f:s~ޘa1 crIĘuK9 y;umwE^v= ֗8\pG.~M3Xc>uU6_d՛,t6pl9?R };[%~>eUU2wgY[i aū3Q{qѣ~i͏+lXQcM,̀ ~O>L֗4ocAQD#7+1?_.tJ}m6f|ԡ&TϐޠХ&2FW;9)ghoaZO79r3fu_HRcRJAhc 3wL6~{/*vN&cS(=Vot>~A=a`7{|opGqi"cgĸ4ם\?ӮZÔU>lпfy5⟄#6eR>(RWW$b4䬻**}F=$bLJA0$-hSԿ_p9ńӿ3>E!t:m4?CWTMQ֗}TzuP}8e&K79Xj/3ׅ+S^]Q #rTTHnppY L>E$$KP-˾bJFr 33MY>Q%%݄Y[*\]llӪiYhpןK߷K?o{Of/&3x~]ʪ!;jR"(D`P/XZ㕩\RJ~Yn/y*Ұ;'#z0Twx{_FxW%B%klwa˼_dgPpI;s1n5cKN>sxgźy~,%ͯM|*Y0JfuHf{uq%@^CMƴR}bgTj.yYZx|V$)ͯ1en,O Z'k7|MqxiIz J|T5h<>tϫSo-k'?ѣGBije."#;A3sA~}gGy'~>?"+p֔:88\T0D2*>kKRH]f,XfyARvJWQq`AhDs1.4XNN{54ZѢ@5y!>ژK|w1g|%Fa0m^cu$w"|Cf!J/}fҧ1Nă _e-uN?~1Wg7Kʟ1 -g,D}+p9 >$֡*]^nzvrQt7k6;{!'g:%ɟ]p-W5cwٌTE|0 ~ ijtŌ.f(jeSؽFn&k&<"&DMayHC6eHo~uPctq\/.cHp>L=4qV%D;'!ŔyD)S9*k\No3bJʒ.Xp'"oa*yY^#0s|W?FhY˽3bYs9|/'?3;*- rq"Ѩ¬ls4IU!gߚLQ1'E9j8O6JW)v)$yf\!?}W wE^< UQ dѼAvlZ\s R?k<7?ݣz o0{{ +2cIϭJ"ьg'Yjr۞*vY*8.9t,QwTSQQAvvvk'TVVrq~IIxNCܟ5+68h3gT]Ben: L-CSŏD\vE=* WW?5hBǖ~4.YRH֮]˯,PY*f̷KErɔf|&~4ci2]kC|'lFQ^5=tkTUej%k~X%L:C짉l.DxN S͋uB-tJWJ{)r) yWT\V*4S٣B|MZPXXXEH]=z л l%#ϷXr An͛sJduA~5(yV_k+r2u rR }:l .= x!˔oM:Q%DRTŕGZIђ3X!bhfҡEj= ب |U8!~t1ޟOZw*hthPJ_KGLSG$bkCF=bEru0m4zT) Oec2b`?P]TVr֝tn2,I:8_,VSYL6N8v _uB$:|zF(H<5atyIAVVX!blw#ȩ#-/D8z>$/1mPT6:qG%^G, lbWD<~5̄ Gm%ɟe_uЯ_?>(ʒ_ӱXfLSզRfyR(r e.˱&vCoՐW9jǹGѮL`lKfӅU?ዤb"G U$uz&>, `"+0S촋Igy#;!-çʽ]L"!+HO Eʟ:Xb[6L򶥪ި} M&E=*gg[|$Bi|>;kxblvBıp0|,]Mu_UU皦!ꏌr[HTZ/v݆9?[E5lЧʽbHoLvk3<~,L+ ,4ll(ġ(O᰽ZP:乏̳$#Nx(- ZH}9*Zsqp\q=\>h׺)؂躆ǯZ:UT VTRUMPĒMB}|C}3NAA#G[d.8NrcKeACA|]L1Y١]sQgVIdJ "c 6W+pp]X٥MarǦ΋ԇ&rΡA6Ņ{׭`e.xkcec!K(\{KRW[`;Cmup\]p]pݒI,yx(nmrjz&]<(8`9`; 65(IE?Ѻ"".Uai*YQ^isK4p%bs:7< c7T>0˟P5a0ߛ{wH7C87&ЭTZU.$εjhӦFq6H{ D=Nΐ !DjHܼ<*/0DP5;+^IcY\_?/e])1)׹1A=|$ӡeaGQ %*tk S"\xt&왜=HT晴mg;97=I$U@Ŵg\4WfX!v<|ĭvУG6WPV%K}c^{}9?s.UOiry`-8Fǣ 8v?Ƒ 1r$~W?.XcQrB+}Ig\yݝyvq.WC?8]A\ƌJܙ<kJ5 {tMLHaj.##"ضT$;PfJէWe0He}/*Qv;qd1 wӸ`BӢ .o}sd)lJBv'\tuYݕ/b#-z?;/I:0i Wg:!2<|QM*g{Üww 8]t{GxBĻfPRr8$ !Di:?qooeQu3xHU]/ƌq׹gpǹAnx2uh\zCefX\L !R_6n_{+/bΒ XpD(>qOG8a?R#+]%\Pg}Y6APhһAfZG!B$ԹiI!Ri9 G?;^wcK->yΡŴ*zʛG9\wõ3BA=4v2{WOk<>6BMأemg6Nz9qNB]Tq\Y%HJ'xlXŒaHX#bĴuvk/Q2桷AQ*M4QhpiU( _5(X{"[5{uH3Ҫ㈘.;&`JT)f0Q8.hzbw͑]p3R}]=Tiߙ64).~˳i)HMyY C{T4OLW_rCMDP/I3-F "YOO*eI />΁Lq/Nr.6oy^)w p!uɷӲ΁tkCVe˽SD(`U !Dr>uKqb㽏 447FRRasI$<6B6O`@Y =Ο~+;6(! VX>RvB$?{"ɟz( g3VFA`vVAI6+6j\}rFC" 5'8+6\ɆRk&Mb*DV\k~ !D]=uuHB$n5@c@ep/CS)v2fʦJ'\&^JP3FfHe&]BSQ풓[/u˲zwO$-'S ڪNmCipPj/O1xNlsy#> (* *keUAk|MдmpUCWϧ|:~: 5UdUP^RQQrCeC( apBd(9Ǟ=:Q46Kҙ& Z{4q-QEuB;^vtGBU<|)Ft)ӂ ) G]4%DFJnʑCĴx˪6[GvB$v8i͞A?}b3ؾѵ,J;ɗ?Dr0ss綇~n;m]~`9 aٵzU{}?˶1-p-qkv!/KvaPi AlVv;^"}o?1D6:gFB1\+v x:>fyY,PMf~)䤹4QiT%'Ӈ@8 ~?^5\ecMaV:Z[ Ο~YzM})z$ *Į\бyǶIJO=4t-F u(Dj-D|DYfPOoGj?@#9`O8 24tQA ]Aʰ>[ OU?u%i-JjƇ\; tfMV3IQJ\Z4M'!Dr]s]O:u+`Si% z7XƋub;~]rcUʨ>&(Wǖ[j`bX[Nq^*g,VEbQUJv~|Q{4TT9B8`5$WpŸy\~TUC dpO~QQ8$=?%.d@Ժ|ciL6_RJ"Cq^,גU hLx<ߥ8_iF6;|Ņi](KX呗ӦH!DMOE<)_hO< Κ8l/yLF~bRqȃq<qi+QsMdN>(Z$j*8٢RR鲥ŧyds>J~c ҤфH)O~`tgKŨ"|4;ʲr=HOQu?5Qɫ%zǯ IDATē;&:4Ѹ(I쪠_᰽ Z&$ r2r2tuj>bmxφY_jѦHp\I PsKAydӱ~̃ e c"q8Mii)a0 EIg4 OGQU^zpHCii g~G]B2 |X{~͊FqKQ&tB,KH(E=ax[m<4M!;p%H]c ѭa"Xelbw`Cih :vf]/HDP"Mc6̬cJʸD?mrC&\ O!Dy|l:sy}ASl{ϢKk :l!Սg988֑!ĮQ>?'l YU$@?0#:BZ@v{_]j!G2ᬻjxbBӿNĂA55ث9)U<]SأA,E-6kX:&rj'a!Dxd7uD? $''p4Qt rbOd&K+c}5tK5/^p5c ֗,^cƬ=j"AB wjRh wVBgrk}K/K+ $ 0k K+>R e.<}Tȁ9AB QEy*Ey:x MesYZcBIQ ]%;#;ݣG[I-YBQľIu ] U1cF: +ɆxWw329r޻-A O hCcP rP갩eseo|^yd#ٮh|gYEbG!D *=,ɟ?0f$~UY"cF7yɫL2tԡ'w7bVF呋8`l)ޕpHJMT uxyP^§>d2&QQq 7*CNW#+K}_~G42֡[~皼'R&o01ta}1jWySK"_$%(r~Vn@ * Э8| p-sSb89bB4H!L읞%@zIIMb]}b:SdQ*(ߙK~Ύ]&\ƫSWK5ɮ G=λ{.ȢW_ҙ8CO>?_rݳn8-oB$vE*7b:o,S囟-"@vy>M{dl/asdb7<B1ImÌa#U}>b@WSY j(3buޗ][w*pA~ι' mO9σ,.|0Ą D:`U!gGqJ~Jp:hD< st#'usREe=/bgD"V'dD=;w./>|!-%ө[M<\םe$ [+NEy;=^aFO_`޼ÜL,"^df&P_9xƞT.<&=!F[;p !Riy~M Pv$G6ҵVk\n{9ĿGevl]p 0b'^`\CdM*y, ]*HdS5**.|3:#aT^qe|,ۼ !RHeimf]C%2g>I1mTޜYCKOOLNp  jK *^_VL&#Ɩ\i-D"[aC)dk,^csХeK4 xy򶛅 !D2Z'~WIԳ'{C4N 2SM8竼qc/d߶ť  h$UfS'QOHT\v|#+4yu,;cz !Re{t:a2)KGM| o)]ZIT93h]X{ t(|a{zƵOU3uBÔo,Yѱ,W_m/E9^$HDY*}:ݯi*3+jf)di|0JqJ^F~w-aF]t㔃, .iUƓV ϏuBxBQX,>ڣ2ª Q,ò=Z7k4 Q sUc I 'Dž*Y%H#Lj%[ F-NR@̣(Te6OOZ۪xGwl-0Coo Fto#j&c4ו"uHO 8qǸd Vr[5י ҄xq~N F8ڵyb&G!opPƣ[ T+ZNk 4 4a%x 'R8h=G=Ka9][G/΢MlB캠gw=i#j2'7gEQ^utκ3Dq3N-}/$?M)BRH'GlۆRektmu2㶗i[S#OI-.K\8=6o8-+l}CJr97wxlN p_n+?*~5Oyǒ56)Ԯ>zH[ 'REǖ:$;Eqeٗ"ń.iyї:AA?b{^,Le/^ڥfY.?!vEu4B]WhҴ0a2IA Z{W} y*a sW=*pth_ѕ'}5`DhB.1o7;HqιwW2Sefëw*?BMTԁm۴j*4m~8tV|͑}}}uI;y8!vӻ}b^j2_/m;1dֺPct-|bsAi"vc.>J*|,\zUޚ&-`QuTr2TtӯGUHu~5] s5T2MU覲O eMs2*!,qS%H)I5:uyi(KtV4˦G;r2T:ymZcmF[+{GgL<|qmtκ'oh›&Z6"OOLIı|4.O19r:4oLBԒ1޿Xp= t[ f`1g*jlBQfIgѳ wxsT!RJJL) m>$Hi7?[;wn=+Osy*W{UM"U՘4~{ =O@N>j"2Mn\t3Rn|.됄bVV8bmgO]+x &ߝCq s q[i,H)QX%ɟ:PE-Yމu("NWu#+npU;4xG_=elr2RS)Dq~iBfxmI !_nʙx4ڷ480]EuBOﵝ%ɟ:Zj Ņmk0e^ZqipS%+65eZ~)P z:KV:쑗F։f:o(D]hEGxlVmUW[BcѸT?N2yi4|Bfy餧:]}'ʖ u?F^*Y?w&@mV)bw^թa/<ǵ:3yLf, s}LN R !ߺ:zU9'a6) &' W.XNzXf珑A= 7𗪞_?m ١fѱ2'3\X5 oH/n}|Y W[]JH҃ C|K9; Btevgg쫎z֛g8TlIaInf$=:5adycɫSCSe:1j=5}ڤE94NA:7~U9sɱTQ!eH/Cz.8>{Q?Yr<3tr24w\;i !0cB5% i~-3"MH &xڭ8pw#ϘߟרYq^Iec~SQ#ؽ$aS3grb{&ik:=e2(H(XDzus+,b(grYfK\H5cKCs l(XfrW MauN9P& HK.i_:]&ɟ:p=/!Aiz,XREV>tأFN4YO~n͵&O8j.;!R lqk!~[L(OB5}O Gk6mb]#|`j:9a_Mcx(KX,[PQC\@@cSg{ν7i%>cr5ticpÃBl˫LޘO:yƸ_.x(lc喳;tUIfyv&ɟBZx15H]q|wCDW ΘY90-L. Ц=yOw;~nkiΡ4l"ңVDEXtkdx,mb2m~&Gp9%s ~ u?wXa":g4tл+G S4k:`_oѦ;.$:16jM/gϞ(tVc\UHUU4Mò۟(L:mw>DnF Qvy>X 1ja9˲uwLӤyDEQ2A>M.a o8) _u"TJ:ZpH1&u"qhpy_Wc0HIj Y&T/$ IDAT\- !Dhڴ7Y:Rש)o}fPŖ:w)|`84JqVS1g1uи_>P^Zݡu^ U)|o7MYWtVmm%lXw'k̺*(DR]⻳yS !lZDzB73РM'm՞#Xc/>1U\$*4ҩOڴSvn hlE=7cqFOQ,d!JN@G?$ɟBa% ҉Uv/Pr1z6>\!EzNɘrxj*I&9\=I*=oLgʈ($ !=EJQDwӧ⇝A3ؼ5T͂($UrU&)4AW&a@&d7喾zaH~?VrE8Ϥ^OPHɕg}9]Oɭxyrlll1_ !lпK4o4hYHu'8 _di;pبк颪`m0A7,t = zô0aϠQa*膉i)~L0 a"JeT nI=rH̷; QeJY;/b܃\31+gsy\r8mx4~ڭӼHS[ba3sxcF! !<ŋxۯb܌vU_(V&6l  n`npj84tjԭS2Cst:PUÁpr(JÉD8N?k'[m:\=hB\O)~\.Z~mвAD 5LH4_]3Lf#߯_1,]U/t&\R]khHiLENrJQv.qK:cIx*$v7W^3X䍦ihZ&^z%~]L?Yy&wQH$s }mi@PD;aP%bSuR3Jw¯ʥR? S+Syr*G ^k\p&I.nÄ #XONcWywij#?/'Wˁ&wjr(*hBFʵR\ <18[$NYS!geE.ҭp0O5X%Ym7Gý a@gyn+(5]|6Ĺ9Ԩ▋=4H΁쵪 ;I#JoO(΢F9[]N6iB 41 X] ]x9sG†AzqzO`<4hYORZzM@5ҵsm7s[S3ү" b`ptf)r-BXQ䧅*^o.8ET]6Z;rKZr  ?h./'.QEެ+"r@\J3 ^at=psv3$VŀN.3$:8MO+BT 8pvpF!2IFR-$/ݺuE7#=͇_:ȅ]ujSs*tF+QYn ~)h'i@.BӠYLy:G;q6ɟ<"dڗ89VGU(TBU4R3LNۜiAG78D̻)wfѥy Ue)oQ Zc6QoK7<#4nMBߘ>NfhFKPV" ^0DVv?+nmv|r)f`Z i|4!&J2 <[HS01 4Q=Qnziwb'bT?40D9CM\S]Ћ|OGځn:@#zQ%eHɝ??53bT9CGҩ_watu4-tNib&iqɈ 1!^-_ }R"^XcPHzZ&*( jǞ_.qMT^}O~e\?fC 97^ԟbXW#|kOO>3zq4qBϨ9䋢%,2tC#SD5%Fc:)۸f,]Yu*wIII>X]wġ$Um}S9zlxK?J,8]?IJpPqt^{7V XDȒTOX0#g> 0tF^CBl5OQ纹l޹;p*8GwE (۷8 gғ<7ZI'qA $~:pD<5xfY^Q.»4/I뷈 "oI1{d6je9 lGӦMOA>q)\zR)(_734.|"rJGGbWNIƋ14 !*}u@E>&( DDFq~I{qry^~ٱdaNvH3`T4h0qHj&LOcP),wX830D9a V L~*Ͽ5=>4B#L.9AXT4^2#8dשLX5LhRQI#>F!g]OnnٌI&T\E!șԙbѽMhx8/!&Rq8LTj"G+0-W|{&3EѺB!Dp~^/[~^.!R~(djt6wf6M6~_ٸh{n;:(&9ӞMSbM{X! %>*(($Qx.JTҹ!gt][jLCfiBwio7/ H}F7,ALܐ=]T٘aÆ(E)8}vbpMk#ΩfXv!qխxb"a]dJ!qh0n@;Qr#4|~|Ҿ:ʱ3SC2I)W󪇸t6HWƭ:"Q<"Ǐq7 pۥ\tg&O;!D97YR@?)p81yuR1^oaR&ɟ1 uWoS'Z"H5w9D(TK"iٕ8aqBsnydJ߳YKs8 ̎&uʒd6岑MH Qtٹ&9(|ArIctQ@B(&=!ٟT; 4Ձ!9Nm5liwI?/Y$"p;8(?Ҭ(9~D 'KJ`:7ɳ;!D9ս,_M `ݡ1tbauRK֭C2rsfsMRQ)hw8 -\2hLT:4!ɢ|r;M3 55v$(.=}(ŠW i_bZ Hڑ2{ 99#&?<7%LOD?X j$)Ysú%$(s۹YI?w~] i_ì<_=^}ݢӻOL"o0B!JF h79D9?8^Bg;? BЦ(pQ<9mQ04 #FL3,,R,,´(x X*&h*(((Xv |jC/ˆG_SCSTBӔZ^m 4ML@X MTN⼢DzP~MI&uCbɺ"nG+2xz&zBgՏR?<2 +d YYYѾ,"<)4 ӉFӤ.:S^6CT ӹdE<:Ú. ;TBy;1E\p.\'8|>SaZ!T8 '?tɷۃ|-mA6l ~k:soԯ'%Q=$d.NϬ81\i튊'07NNcv$a/ϐ˴; QDat Q=GVŴ=Ώ{ _xؘśjJHG&4٤JdJc3#W? ;'4.r|0fV.]EXM&*~Co0$^mbTӘ<<1ry( ,N0BgO0'$|κ=uxwuX~ w}jj*C }EQE=!JBXQό; QAO ҥt\DyjR$ȓ%EvH]˟zvBnmʹgv" I?.n?sݑ3yw=[qTt@f~?iQ?`v񉴉 &.̜lcκ(%_YAlGDGטw_5q{yFeLxR1p2IMmZoy#_Swpo,70t%۩0sl_ɽ2'.΁u"nC5hL:)PN[ /GyXEStEiZ/ƁQ OEN.#\#B!]ݼ&hw8 41}>/S IxJ?]k0.K\^zj*Wˆϗ"B$^z}KfSBڶWAK*=shɣL$csrUQ 3BB/S?C[8Pdrz39N4'$/Oj q*a? *h(ҏ>zimokhT7iYwcQ&,]Osݣy<=9̡˵e(q:j&vv.'.ˉrvvunZu.]79|0irp:Ǿ [tZpXw ٗʒ┎d/Odw("}Ϫ 5zP4D!]ǹMZrKyypy?j|WyC0M7=% .tleYhB|tXwgD!R煨.UǷ~7Wi2FȥYJؽ Fݗ]% :pL&gPRxvN5gc9|!*ƉS+Xz^?BM%KXBCJRy`0z i|nS 9^DkE.n~:@35uo 1*öcB?ܓÂ;bIqDMiɴ/! 2~)RŽA-4K-p[ =\LzxF)v!i>MF ScxCgd8"&WخLOivZTU1Lp9 wUӡC4W7LdڗTO̙5GyEc714L(rAc98f\?h -E28pTa/aXǞu^o>>o>A9/>GRb,~;@0  N0hgHf8*Jˆپ;EEQ XQɑ|'5kִmv. }X%CEeIv"DF3!*u5x,Oq<ewHBB8Ӧh EYyۜ1cJl~`0H0DuLIJ|X(eY\.jԨQbQ^U/R=Q;!B֣GΊ g2wB )e*-RqI'$e4HL*}nni q:*T%shm_^7M:7pQK'JGS9baN6/B&CUIBBa!œ5-+>zq}|v"*i:HG?ӼN9 QVX`ipyv#ӂ_l.#+zݘƺ<{K<ΑvZ5e!mzuY:s@{-*iFVb  MS:4r-bB9h9'8J0}?U~mYET U}(T_ϩ,Zcɺ /L+"'b!+N7yeOչU/ûyfi|p_r?N\Ud/sȳkth(  g^.= "zg>obw(B)ɟ|OX\Jb8 y9t:?#ر ṡi֢i=7'7?HrkգQ$''عckW~D_OtDwbq_=5kp!4d]xX~JLF\FLEJ5}ibZ0~Tj׮ah_CsMCOmZ{"m4;_xnAN6mJ ?ݷ]Ifif x%u[T+)ouʄҜwlɅ[HUj P+NqcYq$(䩛h"#}*i&_t5v"I5k ϥ_}Pu.$lAţaţJ=N˲P5(-yAYoGӪu+$-NЛ~ANaG>vFT<c_|uUnD>#Bţ[1J@Bm3_YxE?^2Ij$ce>Lv\TWX9zQ~}~?ԭ[:xNRa:9Sgי *`hبWes4Γ9nr8Yr]W~>^oMv"D2m?JTxw>Q|=W#v"ciKseϞ=,[9ʕߋ+ M%7qၷ] j;5ˆǎ;v 3N QVvvsKA\ QX7w3 `$8gJSPHowB㐪j6KII+bsیKcFrso/ӂ8~|-`4ڵki8sNwWx6R:Bn;Bcn_!kdZꟇm:y:e7pϣK~5߿1عs'\X.qdD!l(bP'Fc,Ppt& d\?b#b"U^xdddo$#N)337&1Cf7Z_^scm?}'}X{˩TVPйf͚sF-Xp,/~Msoyjo6>}U4M!5c(fИ:B{eΆiw("=vYۜaҶ9tEYBIbZ6Y:gE}IQ~<~H7ceCCS%$*#Ɋ^,`ہh]yrVٓ$B8-^WPr"{_<0'g/DQzݏө2e$DŐc lQ{ď8=YYu*WNwu.M6;4!=I q:ט?B/o~4'7KV"u?n)K^볍/}CFAJN@Nȴ`Qvgu[d+\0B>7ڵ;4!%9KV(87BSŔ{BK,`tޘ! '#739.N&(]j9hT .?R3M&o>1ѶCz{>M4;L! I[YEP7#Dy w,'qY2R CUTI2W%^_G7w^Il|UҢM{oL퉊;\!BB~; !ʵ]n ņaB*2mQ #tizQ畧oᜳ;rA{ϝX,"d!r<=9sio2)W!c~஗򨑨3gm2?%jnt0usI;o/CxFqP+w_0q~0fMR6@RRqqq$&&hKTIga+),Dt$Sׂ@&  r(_ё$Ęv-DZE?^aw8BW[< wf&g9eY]5dW+𨙴бELBz7`q8$[?ur{ QNNO @Ӥ"I[IGm!7yIM[DxTAN3GBPV̗9nQ$%F+-䃵djIG0-ݒÖpdž H=rk?#/(~'90+{4MvǏ߯ם[iۮ#7M$_eB?V"<\pwP+I>BA{9=+SD[s+;@z.R]~9&1YշݻwYcnep νGu˷l;c=4HvS%:R4.(_$#l%!u1N]v"DĻ~} "A슲1g_Xo2tnEh˓y.| 4n>i]Ϣe=S6ec |Ab.2X!DkHʩ7B3B7s'r?Wε;{4|'z$#BRFa  T CEBχk}Y#׭.v BE& [9N"=?BrRixk, ?{fuq o;vɄ@ +[(M]̖YP @ieY+0- iqbY}}?YF~tK9 f9vNʥe-?wP]>%%6wygS͆@L`y[MH?&0#'vbvD2E#p(uiL}w];y<ɔDCD~])ޓЋj\0N6^C2d\kpub-2.m^mCHI}Gwf?\+ʡS`/ucw&\^5H3 ̜Ʋ,hrf\p[=GļL%YBx_JPVlӥ+K2]i)eaIw2y͊OsօS2dF/FᏈU>4@dSˋ=Ϯd鲤 \AfFNsFaF޽F߿s :צMgy,\G򪐜Iarnclˋ2]tPfĢ]BR]``[de#=6Hz$R:bB~< ܐ!\瑿u "EᏈkNǻ\ޙ3l7$3k iLYlC y?@a b=dz)p.GomRRRBO*<| Jayd4ihhX{뺄am[^H^+9ӥ%ґ)bOxrB}7Otȍ[$R>]Jx~4(d({7<zb]4lU]Ыϼ`H\"-˙TTTPVVRD(JRh)gho[v-vi˨(opIn6a#,x'10SWg$Ѩ^E"?""r>D~xG۞Mq50.ii;Ee]^F[Kx D*?QadiN/M4Gh[\۟MkL6E|[){Ne4UOC^e E"?qa D-9tc;FHKlf.dz)&["~h^KPf-4` {|aP(םT@nKH!t"#"e~Dtr HShh4sY<Ɲ;*v29PԒ*kf/㛅!3,YQR`SRhQQ0vhO.Nɐ\uU $ P珈K'ߝkn%!FEؚ:*Rznf9q {Yɘa1.9V[30s.yqhԤw3r@; (~$Ք/JdTSGD~.#2)>N3x]FE8`.%&8'Lq)v=Ÿ8@߷*`2y,Xp>%KW@AEy O4(Mb&c7(5(7)ȁ[x$)72 #2 LLL\sy. Dd)VW._0ݦgRAc`s'- l38>c J btd& bǨ(8ij5&Қvz{~UV x,Yܥ0Ϣ עРgCw2j`]J4J$|%˷`뭷\<<#||'âvfQdVi./`r}bXfH ׫0wA=(1\lѣsy?~b_7UB T#!'ex_gUqo aSϊ+|f/ eYGcl50ΊdzSH;Waɨx)B>.CDZ^%RL!"i+\~n|=&DxNu: UN:EΝ3]ME@"s8(Z?N¤5A2 ?e=R(ؼtm'~}e-pioulИ(,,t)"B4╌J&q#ӥH ˃ t+50s+ƣp)q~BFȒuy9>6ktT <H,% *:)iOHxF d~-epQf.Y!U L*cҀ)'4u%/xw^5w[DIif,>dpG2J}/{RVvb@7#}*LfrCIN?TH9fHcНq'VWLt"҂\DD^U=dzo Cv?e{-^Oq=ڕOrAe iARHi]%D#=Dx+1C2?eM=|C&fO]c@}"N-ڼg.+W8ӥH Q#"".7r3]F99~Ÿ^0ɔs"S7!~~!!&AA!a)CB 0K}l Ə qJL6?C%s?t)"BHFDߟHq<1ƔhdH[ȍo =GĆ<[Mrv{K>4дik?L4| 05_3T`QS5004E Fr6}+ t݀U]J H[3WqgqWgi DDd|5ס!dm{Xn:%wRRh0nc[q >k~{X|-ϸw~ <"9"J&_iW*MG^)2]l"?Qx(fH~ӿ[vw|''fp>'./}GIvV=~x_1-Im2B}L#dl&m.=8Bqq)r-$j\n[G3Zϰfz:>eyv;Lt9"HFR)VU+fB;9ad ##Xp;|jFz6}غ]{2ԃb:v&/wS׃W]#}K 9_.tI it'Җ|6/UpoNc]2]?QK$%.ti5fsl)ʶCuܫyȁy^<2$; 9p>xo},KVBmO.Q&r|g@L!"k|<郸ಳ1bDG2NُHvj5J…G;-2svx'TRڝ\YB!!}x#}.}TvSX ?HQ^f{ht*0iH|U!0EM0P}~]Dm8LWOC촛Raz S&_Y]gkBL2}^ȲU.=#"°]JLYٵr:j](ڭ/4RSHy b'ٴyXڡGgxU ~%ŝ)..a劥=)//oşPD*?*94S0vxӻy=0wE4 "melf1&3L%i1Ǣ>!c0f;n(t"-. ){g{F=ƴiѣs!Hзo_w޲HpUA@EEn:yi}= }jbQBZqQ 2헻E|e [iWϾbbXax8㷊͠\z+ݞ}7L!bCmF->ߞrF?ވ#(-U@*"ͧᣴ u-K*6\$pUn3j(=/Ti)i+Õ&H 'ɇ,YRkno͠Le?o⥏\,myvhRE3eRZ=bϒ.} %%2]Z9JMu 8d8w׳[,sέ)\.i]qYS#Y϶HۣGZf2Dڕū Bѡ\6ܟ~紛9x'G71.p٢_1f|d|1ǣ:P >HWXi:DZ]b.C<^YYrq.=&<ۥeLпOlK}HۣGZt"-e< S\}BAмQ۟M788[MOqҡM0e$V8.CDd= U~m&R@ 4 z ap_4ŭ8V0zv Cc"%i_"l\Za[3]HV 8t-($IYs?yKH!'g0=$J LJ L{#Ł;j:d7k#Ÿ yea˲0 c-"a: #/YiKAdc l?#i Y  ;x>R˸-#甃w[zL ?#. B ? A6]BAH='}DcS#u}l*QWfr (ÈN>r)ScV } i3{۞Ip>ZGWX\mܹ raF?"Ht"i3+yN.Cډ; vaT.m;83űdWJ67l-uDYHm%|{Uſ.*&QV+'zXچ&Ͽ !aa8VvXQtTŐ}SM=[.P?ҲN?4\3]~KId1yo8CibiQHyf9RDۑ8:(.rI>f$c3k|3gy-/ hFJ& LuH3iڗyؖrF uE\6GCbLr) iKIf]yltw2oU'S)+i߸8L`!ˈM!;_:|9D,0 ~5/I&)if&iGDEᏤ]U$3]H5kǹ6p w$31;YU W>q]eЯMi IDATANLiҺK-K.o3h=}' ]0?c-tZ܈ ;~ŬyS/ 3I dѢEeYa6ib&N*I?5Dy9Kыl+Vp{F C>pT&/m+xs9aRXc??gH{ I;15k@Õ'0*;62f Kq?45sd>#o"mA߮a1*fho$-cD{7=@F ,wEd)}_kWOnIFf&+Y#\|Wwt%jr1.Gdx~H$RD6FivGdO9 _)׿=,kr2]HţeTYuv<՘D~))]Sr]pYL/MBξ%5H% M/=1o9L>/'2]ȏrhTiȆS#iG:^JǙUu;)T^iN=(Ɓ,NHCBIH)-2\Nݯ|C-[!=*fњ?v%#r8EAڿD90p¤ot,Q)-I48zֶ4'3c-O8ep/tlq@Ꮘ4%>snb fFqv7{2]H\}:&$Z~dyOqEiErت̈́Qq⚉#-, u785|ͷ.MDI?.?kt%"'y=sA|SϧH>Z1s1C~E1:l?,®[w7/ ̵!노^8n燸n6a~M-aAh!yMt1 3GMU$R>)' ؖAQEB,N& MJ LM߳)p0 5!w _a/+t)\$"ͣDvWl7y%w'hLxvh.V)wߟqn?J?Y&l/"bW\Xgɪ>; =΢D#ģ&An$7fc Th ɍC~N))0t7f!L}/,t)x!Ѩ"< $\N9N=8ݷtI"Y}!'ѧBX*JM*Jxp& f3U=;zrH!ig&6grm e/Sâ L`pMaE{q=@$}V"Yzut"v JzmfR-DZRn qv>.GD:۞u1G얛R6JmC5KDGᏤj[rdz = xNԱimW3\ο=L#"HC2d\idSVqٍ!#t9"etJ.i8+V:gMqsIu3 YYrѯbG]: jҥ$wEII"i"򿾘ҭ"7nbd\>2}Ns5J3]&UET7 :fi@YIYI"R"8Ģ6+{TE1 (+aP!s?s& j2> ,2L41Lү3}d4 bx}1or0\t.-`ʚHӿo|L. C,)1 ,^SmĢG!A0lz w?LYYN;7;kk~v K]K֭&_it#iL& Yw$8<y`1}_{iÀm-Oy&UPQjClۦ!ᰢҥk(cA͠SI߮6Dʅw$Y԰d7>wH'LjLZfri&6H:!)7tX`Ẏ_ \ c%!FlPM'NcŊ|ga> 6,?qjj?Ү)s]GjisN N:0g@ʚ]KߞW+|ޝ\˴X]$1zryV& #vscyK5S6e=M˄A^y?_za!#jo7@? \'E\4i#οy|.ԓ;ƠQDl7l9/T &8\qu節]:ɲPiFrb1m!&K9NJtJ]y_#ne&]PjQ?NѯkJk 31ڡ<v;_縮G󏔣G?E$\'݌HKF^ Ŏ#{u2 }hZ iC9G=?view7Rl?YNDd}+=`xE MruKy$Wu'зBO>aizwXDZϼe>aʺ)0?f|8O4%}S#i״ۗF֒^}O(3̙Edf$!bjE+=zE"6BuC?uiH`>\noL>`jX jCaƍjBC!Uu>Ii%6ȋmF7?KPoڥ4ƲPZamLqß'r]eqCb1#ퟎ$vE~y;""ҲLa_gRG"iqxH2ce%<*ʢT:Tzt*410R SP]RUe$  >K,tGMbQ (1ɉA@^NHr~_S.s3hk>\+וfv/G:?vh/iq'__KрHD_pI&܎ۄ '3\n6r>1vQm2oǼe>|uCsBK)abNr;l_iڹHymkgi9Wc)TV¶Bn}ᴃt ˹? &9x6aFmZ[U]'3`Yϧ3n8o,-B|[ C0$^zQ^^EEᏤ]eD+3]L( ^E_>WcdfLFD9tgu5u.f8zm e65ߝ|a````bt]4<+.]YH}"LS ɦk٢u3)>bPڮ =>7i0v^7۲wt*}visoJ2]Rӹ~l \,e8?vѴ/D;3]t ^']`po[t9"?hJy"e>[ 0Zw=|e1|5i[l d*Hy-e}t%"㉷]y-ſΥ_Wu~>Uz;] ]^ワ\r!{w0wǸQn:zepEյ}M:NޕmL@d?VcG6o&fp91M{ً = ӥϨk 8/uQ{D$.28aTcoc(eEl7$BjC\O,^VUTTAc"`^T\uFq-n8E#m_8TfV63 LW!| $!b 4}+lt"Y M3o=f/ XYX,ZbPg2<ȍt+;`Q-X3=ዛ=`o~έ(-1caD@2D#&X 1(-4-B,E蹙M `8ܺCsݿj9pGѶo\n|,AA~{Ƙ0R Ʒe>7HQ#i8Qu&ڲqc]DwB߾cg~7'`6+d FungTq"kIW{P]rC{.'{"tI֟~qQ?ťL2e(G_Y(+& t)17jͥ$(8zF?<nf,hڐ3~+Xy $(r]؃hi}ƙgp/D$=|#flm13pul?%.[ ڇ3,3+֭OsM W~.̘kj"kt1'M}LOjm7g帽ms[M9{pMEqv9zF|ךd7l&k#={3 ÐphaȺˬ[1- aPrdm>h 0 >+_\aXe ݅hQ08c [p'϶ f iU\Fn=,yqꬼtedQۤ>ٷ$Cr8:`P/]FF^tmH0V#׳Y?9sΥb (..XSǤ$i={?IģNٻ𨪭ÿsL:!^T RD k`^{ް`ǂb **^ tK ei\$L&yLsV)ڟPy}:ei+MQO*ѹ߉9iº[/4+t3"nHv( $]\]8^}4h [tʂ1422G/j+I+S{1Kƥ .mϗŶbj>E94.!Dmns)='R^KlbZQ򿽁}ߧ*4qhS|M1SCd ߧ6BPth&'<.{&4"eddfq}pǍ吊W%6nfs9ZQҟ 4+ّ!RyMٸٸs61_cY >ż/UN-5>ep|'1KVwM:X,ZqyNzz^-Vm޽{bUKzq5f`,/Z`ZOMձʧ%; !D*7=B^rS30NMF$!p~_/o|Q_4.pd,h(-hae Vm2Ybf-M =`۴(JKQ0~&38qN=H9 5eXR^v/%$~j~$;!DwO`Zih)Tm>&ť&#HOvH)g4n^N8juжƴl,KŰ*>w9L]$`qzn^BgAJN(xں墖r9{I@yMHIPn],X@ާzxT րj IDAT#2pBď]㦗<{mO,`4i ´nS\ܗRIGhGYd*UuJm4MAS-ơ)dǸ4:rUQ灷hwFVҰY"W݈4YbpAb$#R/£^Ms L\}l/gGdvMS( F ҽ FNN<…ߗ DbT^!(On*wK9d۠?na,wbFmLv۷=;G  4/حG$G f4wrT[''tqhLY`\t޿7ԩU6Y`>';;,06,*L! Qil*`cms: ~&?ǸIwҰMnZv:ٶ$Szaw&FPK+=I,m%qU /!DuLGVE6F3^0ki) ,l6,ʼn]CĘB^Q_Vvetm##2Gxr?~J l4`~vM]L]aV'vppRAǻykl1ҠMYТ|!C\I!o\ ҾF0b66~Û'TTxd;t^%3 s*5Wo(*q.JKş6wbGE#Gj/ 1;|n%?lĦC ':E۔,ʂ6AҠϣ㰽?7y]vUm$#RsO?–-ڣϱ 'Ӵ~rgj!LpYr'i~fmII&v,hk;WܫzB3!gJYcB6͋TtU=}éQ޾+#!Q70i!]tjйFah'dn a5sJ a M =I8S9L!f&a0E8I5rHycWH]/Y#->.28x+q+6]nzʬ84JwЬB|c;h\p`X .}2'xө%ys mɲ@ێiln**-7~]&-8^ד׾azw0j WałU[MTitk'Q=ͱũPDHYgf;wp)aU库vP} -י\g&ekIV>v}TE]&k7[lm3G򀅪)4.P-K!'CNBvCFM9$u-5Y%kwЬ@C!*'Bx<*~Dc[lE-L r]f8iZXnɳcc%m]ISo';`*ÂosdSP,eA~aC$DJs9 s4a?}qѓ(UfЩu^E*-4 rTWᒀ&vl/nph*>!E"=8KQ}M;yiQ(~ mo7!JV^3-ޛ䉫l {pd}fFWI#fWGtL rZO?ל*c Q}|0 5w%E)/?gᤇoű,4QmOǦeQ%ydPo֍lZ0fZ 3~١+<^:ِôjlfyXK~[s_QPPPD- L"|)hؖ4LRFX:YfepHď& D,%~4mU9".,bML |30m84Sl3c|>}tnU{w˝7GW.4b| tSc ߴvMr'sMWR'>W]s#MQF j:'%()7) ٔ-A@C"XmTW 1_7,, K > gz:BQdwK;#G6qc' _|5/XX\%?_/sw K%39*wws}q)sa޸]*D%Nۓs2w2?k:uUDf͒H1R#j7F.P( V$eʃM d)#&XN-T| acY`֯IӲq9KKVyT|n.yRV+n] -jI!j׿i1{HuaN[I'#`1V_\kpnҠQ+6eώMYТ,dѬ7x{΢U:O_ǕH=6K:3:7-^zCBQ}>;sTrbߴw٣9.vĨ4+r('Q*?3ֹԿo7.j oɜa4o0WUC}{Α.&MPD HiofMnNN.e !qrK:O\]@Y.xOJ\`Фy:Dy{@!TNC gE ذ]gnCUiZrѢHe eA05gAC)D?"eEQ./8j'[wYtk Gz-!8xrW;J,n{%DN9WTw-|֌Z$fsK!B:q4~\KkVJ2-ט>LN%K%;B BaCP 0{6 !p@0K^ʈ[l٥qDW\x)/Ts'cG Ot3oӶLNF*8t0'ߗ/K ?R[(RҌ3w/٫LuL:GQxB!Q@Mg:XZi\A%C@47qRPG}¼34UzwӳHKvHhANĹQv@# PDH9i|rAQ.9 #!c6\ѡ›wP5Z5Zs+w9Bw",\%uTU<e͒: *g};;tOOO i>TUp~inP(Dii)c_ز{'%)))Ҳe!.s+i}(iuu+x ^BqEd*֮]Kff&8r.bv i|m@-%.FN}XBUO剿zdT-;ZS lsƐL|*'١53{ jF>qEn rd*Nm7ݚNa$*uF5F%; 唕QVVF,`0H8&     eFBhFY@,p##OFF:YdffKAB\.7~t[M$DuKX978/>[B)rɉEi$_/4rΓWIW-gy9␚mar ޻7s}63ʄ91J6E,wt2Ͼl6fX)'3屔*.xJ7dqбcdr@&NoR6!M dCyȢ}Da\L_OQFNŏr9~BR] lQl2N⤨FQJQ^gx+/Dپ+Tl'8E$^!{H:K_8a0K4﹝ L:y=Pp^bM b x5̜9SO=5ޡjJ~E`kW/4{ⷭB7Ea]'`&<}TiM*N}j1N흼1!1<*龊 Ja]3z9hw+t,ۃaؘvE2Ӷ7˹,\*AFBKxꚌdSeڵ+a0CԈ1]NBN:dU$DҦ`{_!u,̵L,`Nϖ_tQc̊|4=|},7j=횹hjs$>??)[j*eܱ#a0K$jktS*jIZaРA\HlEr?B!Sa7E1,e}ZGVkgŻbCK7<ߗ*ӂEtjg=k _n2wT^3~9O#5qn>eؒ0![T,ۊ`$ &آz䏨f̘A$~B~G3uLi†&'k7/pT;SU`Oalۦyf2 ݧR7[%&IjZsT6E[8h݉Q.:i d,۰5a4 2$h4M}6)϶m>x%.f{)NNBpp{Ŀ:GӠI%Lޭx69!:vpjyC|@EY hLP)ȱyLWꌖ } ڮfğjrlGITFnB(T0XNNs~ XF jM.uES¢"h#)Nƻ˂ٴhә?]J}me B?cZ<~EP7wrsa6vS?z' %۬`voc :Zng`?>{{;OvX YZYY*7oOvvӤa]B>O/4WУ6䏨2Xf '~Μ k˫s#n?>XL$x&B*^2u.NC5^<̽aF ~fٺ;KL2d{]Eçꛜ_]۹E/gE8kG$nnWzTV_edgǴlFj&L%=q.3a:v5ȗPuCGI͟1fצsS!9(S_ɟtFB+7wp׈D"<YjԒ 0=y8*.,NjݦT !:S7Y vdk⃯pyL\R.yn[_yj@ѓ؇TăyCFȎM{kt'-=P4Ggԩ =jB*Ե[O6Ħ\sptG,[| _Gu45˾B[`Z.?c r\R=Vm4ywb;^på\bpIn>}$WѾyj_t)/ܘ0yEF|Jv(bf~nޏ IDATeS?EeIUV]N/#ZFeW-Bzp߿Y[XS\Cŋ#oӽm5}Bꥱ|CTܕס: wV ;T`O1f,1.BzNtvI y#)G v̙^o s)ɎDT߫0T7W\hǮ9,s}':.P?ӊ_۶M;Ldߟhb'szQ=(v3CƌÙg e NrTB!aci]n7XUnq6ЧJUZdRium֩& g(v$@~3~xs!]kS{[9yltMʗceԐi{!&Ϗ2,֕Y<lޝc;xiZJֻ)dv{^O%ݫUH*} ng|%*3yW^MdժUQXX#!lcX3IH/Rvп]tޛ)A rv[v탩1,4)_p9nEk"]H>:{Lv(a>娶n=MF$M7ͯtFM,㈶>n?MnXΌzDYm߾ŋSZZJyy9e%SV(/P^,fuYNҼ ^:M.{έT2 >6r0qpݏW ~ "$#D^qMgziX0vzpt;'r &L^ Nӓx4o|D"HG83f K}+N%;!j72wYok΋,~ZgZ+ce) BQ4/J~ZT ôph*~d;`ٺۤE/rK3Gyrg\ SN'ކe/Ua6aF\l2T7tjɷ1l2yx2@*7eb;G4efG&B=>qu!/$ND۷ عs'a7s(JL!˟}Om7 <-?UƋwԥ0cI'r,'+7iOI@\tn%*/*:?iotnA?{S#]@Px_ _=Pjt ˩r%>sQ4XY'ݧpzwTi8xzPx?'ﶸe$)+ѹS{zgeӜg^z/Diьx5hlCRg!JQvYs3%qNѨ_G9v9Y<]|ÛQ!4qn>uk\ve0L 3tr6'tqoq,^dA*x~&kۚO'>|T$Ĺ1C+.$çP7S#bWENdUtlf-^0bs1ۍ(6(뤉SO˯KO:,vmT|&>N-zZOė$s姮g=RJvhBJSxܝPÔlJMr2ɀڪ:/-Q]s\i8QV-8Mse]t3w^vЇڣ_ҀEzпCK"h]hqs|gI& **7uQMyȢNvW{'s,| _F5ϯ-ʂem5&>)Sf$G,BU+9u+ ˊ˹Wl0rY{6".%[t†q =Y=S$frnH\p`gin_np%tmKjgE/ep+}3tnؘnoƯ}r?9;ذ%&i-TW5߫QLvpndjmqlGN:]0 T-usNc\.'nӁrrpݸ=nn/us0-gmZOcŸ b?1pK.ѯr1@-B$C0bv7Gv0ԻJT'ϕ vY/vF7/^g"=x4\BO|haNqDK+G5qֱgbލb>E"OɿJ(4OTU?7yaN{J'& ӧhp)ò1L6QF7Lbz$fT|?f¹]$~ߗC?B m\N2h"YBQ$3hf(wpɀKt fknK Vm(S+CίyXLSTmg)d}" Ӄa OJ&zhFU>ֽ?G,Ӹgd/`{Xb$gQhmuNzOIrT['gv##4*phM[{@JB5áaC!'9U223F$8@ 4 ' +7.e\| aѸI|Wo`θnMz34w:”grQX\_L~ H ci}wUXM[ P+_-sQ=>^@r* ٶb2N~arE?i'iAV>+EjkcW8XwKLy=*{}9-8x @gˎ( :׽q:5tƙeчqW%bd,Axd Л"q0wLF IKv(ZǖN:tzشäEpa4yFBrywR +.NS=o& ?[z gߩtk NGV1.1p9Ⴞ{4O'B63ccn޹+M?Sv2, +^ncg84'!'uw*cx]O3|ʰ6iݸz?lRh'=m WŲ0U-`K3,fOyN뽼U7+(?j'Y Tnݔ󋽓!·~azC")Fo={]O9*v,fXLKJ\zM b(@4JyE'(H%/;cth` WʤdkTWeԖhè2FGTǥe8~ڵkiڴiR&!¼gбqۗu0ǸZ[ e9Uv:YIscZab&HŀvR@D(e<{Caȅn2*. F~eNrҢ>V\n-lF?I,͋YdWI#Xg}2^U>mӱar:Fr_ߎ>$ʂ￑O5#!qqXF(%BUO9/%P/We5^vL8=ezzED@AE+k/WW׺{X.* dک?+`$gܟ530y29>|1oCgq)FlH./~gp`u4MyFgmCS>䥱9^&RT)i5m|τaHn+5:Rio&t NҺ,+9~W˚Cѫ^dQB_{yT PV_⩷4|So܊Kxaaːf`(i%E\_)c}§|ki-3RY|5ɳǾ[XQ#!.\DgIMjzl nu%d?anc+!WOþZe%T?.9\.u)2DQ6 Ew.I9,;mf@/"-f ><;n,Ji]Z,iJd0kKu2ed+yոu-,[u(KDʉBeW>vϟvUCn16adi=e\n4o/>A2fVl4u9"k8p+?"m>L"ɟojQpB.PO{8w\r6m+Xah.`6^ 5QM<72M,Lf,`BV$,iheV-#9 IDATq6iS OMS'^đ;iޙrҵ\zF޽{ԥtxþJWS8.KS293ϊN/,yu?"r캉ɩ{SzF){w핖wM׮bu硽\twMG&S2A6N!LE|ط_.O"w4Ȳ:a φMVi2aj@6gG9ateEfznS]nJh3).^r)?&HR(uhU>K,N;'PdyffֲV w\{#cD sniளʣ.E:L47Yk'R8^ Z`핚x'.KS#{=D.8 Kj,ē廙Nlu2.'u):O%U1Hk5,t"PJR EŸ ˏKmLZGd)UWWӐU#?çS<\?"f Yzo}~zךxrƸ\JF]t@xa_$P('7{֨K鲴ڗRz{Xv@}>S$6вO׃?( Aܦzl11D f54,U7ڣd)iɓ'3IRpo=?"kq>0|`ԕt[OIs.k}2 ~"0iDžw>U{Iq.$hY2sVO8E))8庸KqѭĝZGpxv36"Wp-ޞ|.7 dQ%шb!2JR?-lԿ+_D E\qy%cYF맽)i!=3`P_^O<>zGdy]rx](YV~DdlF zk;\u)]o8\H#ۭ *.Gx u%)L,Ű_ e'l&TTJsu DZ`ϽGCC=6bȀ^ɨKN.fk Ȳ38ƣsoŐNck{mi`M\pHyJQϢ4-;AmYyK1ItX|xcy8nx @ "/=3"jԪ1>jp9:ͻP7/`yj 9jx.D]blQs,J2-2p3jrvI8ܞiZBJ?LJXiZeejWWG;$|~n>K} Ð0 Ð aИq\8eq6KTvv0۟9Sj5F)= 'qܰEL4soi]ĮSrǝDI᪣gKsgy\v3x F KEy`n)=e8fq1k}c޻ ,ˢwLe.hdͪk3 sio?➗ q)#Nˤi>_^Q;ifRӽҤPxsFI9,QZc*.> Pt>u ? vF7ql*Di7C6u%p+3*]kE],FmI`ꔱmhŊb^ZlD:??" 0.CcA:i0r4O_VM"׺HgT;yϾrjrIi[B !BB gs utEDea_]4?QW!{o}>=+x52 g)UeI&zl62vzz4`enσxb-Ql6-.m膜sE< 41 4mਣeY6z 2)b>c..AHD#>98_LJgRwa`.Ϗ I]ڹKa|}Td,1ҫ{?m#-FrS=VY׻gޔ=lxG P\@Ycҹ!Lǟu)GɝFnLl6Gc6G>PIdRAY \/$V]D"wqysڮ.EI<y=GcdbW x<2qjT+h\>Lxۄj\1ࣉ| Yt"dm6\`p_Q+\Ǥi.' BLӠ"[b́ۦI|ᓯ<8Z˸w47?]i.i۟ȸϾN&f?5j=z'ϕdHke#0*:iYE u~u">E".!3Ҿry8E6=Iӌ `}mn|k2 \pHj~#"lz՘rOp5YfIm<~q5ܚYg&G]H8寥U8֡!!_z;̪w4jjq=ϧl8LSi]»<9NI:apv1N1]g0M,M>,8% LR[aI|힠n6 Tl8VgS|rj0w$Xx<{lۤؖ:Dڒ=E"814,3gǾ[&v_Ͽ/V3>4A:+)-䘫982Q#-+Y\u)XiHH[Q#q ~ez-2zC50XaCDDJu'.so/r lFYnz4[U֢Eڎ4OuRjOg"B )cI.88FNH'rq^k l>`19GC2|b`)_U7y ***~"D0 5K<"l8<[XFHޅ}M%"V,X⦧||a c >b8TA&]Qy"KG]tR8Q!ii)|>OqJxB4d Mv9e26eL6wǬ$_D8kC~fѫ3=72Y6-HD @]?]-Oy"avmX&ؖŐ&=(Et{_|<o{l0bL *mU(VD},8FSlXp6[DDb>v$;;}NCնJTyP#҆4D$"aӣr?i$'f~DDlZw5\eR6lK?"mIHD|԰N+8aN&۬kq5^u9VMAq_66G }uZ2:u""{`F]FЫ&))4LT)iuҬә8g5z6'q}"""miι%u)-Df[(CO839 7RFYJ=G<|iCe}uIiSHD|4Y ~DDJP"frW>Мl˪_,`[\ҦaTSεw>w0Շ00WD0|Y4)!/ۂ5:Ý^==ɈEDJY&YWg3: ,li2'.G$r )m D">Vdw gAca8+ /0 $Y^FnlxCy<\.!WMg, G\jKvTCvHE]H, |_saIiS#;౷bf _ W?r2d+R &pthv,z\xg^6;cuu "xr\zC1;8y ˳M ҧG$"Amn~+ ?}AA*kr.q۝3yONJӭBG ""F}G:_\TQQdnC?4zwCH~Z?ɰʼnuai? ^Q>KiS#"*LL&.MM0;=(F],'e+j:C7" &xI,DD‹/#.EDDS&oM /_U˘qgu9"¶ |#%N?""-ԓOѯ&Zu6SD󃐢D]Ft)λ-cЫ[#v*hYP,\?RШug*a(tigzoǬ^J7>P#¤!pK>wPoQzc[^e,ñtԨYn@OG4!O>2`ڊ8T@S> b1ׁͧ\uY &C2nгGMe,j}K%z1} 6WKHg26 Nѷ_ ـl!dL (&=+]!EYzwyF [Qs/IDG>ĭw<u)"GDd10g[ ;_&}q} 7mՍ0uϷ3}n~ڡ)SSihmm9 1lq.Cd#"]K/YgD2ќ}1׍ ^cR[GD+x#;˳1ٲ@\|xyn߇3oΑ+\aED-ֲhx97⨖n/:q-.;<˲#Ҽ'ֱVUt͝?ZKJhDK):&K촑QW\%rcYN+7֒s,co.Id1բ RH:1?""W$_09O]6j/]c[N8b,VQ#MᏈt)SP#""-IyLVN6 1]MOt!aW?RdI:u)&B#_`ЀQ!DYDST珈,t`Q _Lx IDAT#ȳRnU&}X)FnUφkiw.췥EY*^5S*(͜JeS.CdH8Ebf874M|?$ I*TIJⶉㅸ!\LAeA*a`}gf}oBfՇ4|>#WJ3CVCm,|1+~n1bwbiNWUYGBKK, <Ϗ %!t)Bxe{a!""|14C 6[RSaS䓈iH&51 ~Ҕ^'70 z(>Д,Qg6CWJ0i3} H>q{/>>NGGdy62yχƎwq+MR[ѱ V]榓mn{eϿcԪ1ط46:l&k?y,CUIVy;~羗i|;@z4R,}=fu&$bNL*&eUeP]nҽʤ{EEd%s/AUU-'2_,B&aС-o]]6HDD~˔ F؟#V8I-> >׃E:eI8ǥOmJXe^=MR BH6b*ٚ~B/"5Bu8r5qvKQU]GK\ThACΠ14f .|@PpBbAnmc  (OG nU&m9qgncȼlH}S@c.\*-v6,Y,f8Ne,Ũ܊ .oL V]? zk0o_ϽfC0`ޤ!z?>乱bLг&F*itYsP@j/|u't̓Mֈ#,\9,j̇ !M|HҔi4fSrB@ !Ta!I"E2nNd&I:\z cI6XcL,u.Cd,Fcc# ڐD2 :WKe?W3y5Ӗu:oǺgMu|#m%0H' /߇Ƽ`4O$na̿6_ѼalZD}s͍tѫ`A[g:#NᏈbL>G"" B,\&mNW/xޱuUeK ]Zgu ˄uXwhsǏ&[ϧx|U#o407bwLܻ.ш,Y3NRD$:bzN ?x^%[< 9f6m fIn~:|1h1sn .Cd:fTDd̚R}c@&TdLNu)F;\e`SrƍM7)Xa?lŨɰ-؍yِlȼs!АiYi du=G""Rf3)'/axڨˑ.b6j31Ƀqn=zqCIz[nƜww }e&XUAU>CR#"\ϹKeEr*+5zwnVUMMM g.gѭ3f_DDynwNd{-{)BjM1;&x"~2yz&|eA2f&86ۂx b$bxt=f̓AvgBmÐ) whADz\xPԕt&S.:'q߹tËzFqAizTӯ]O.g"^3z|7ӣb1Rq^{[<˔ݫbnyɗݻw&#"J  i9S[^mNMV1g~"˲,v]m׭7A}\xhpϿ(?B&MM~ 5/d*6bH?V԰/)i DDZIs%"PtuWD,rA+٭>(%R|?4ux-KNV-E̹^Ozl\y_t=_S$بUSR.2DGJV~Ÿfvynߧy3;PHi.4u)zÚ~H(:!_;;2DHᏈH+}ZrWXCcߏJ.W>g4-+"3l4\]EJ21 χUGJu""X,r} mB:xˤXmԒß, ƌsxį'4|ș7e|dyCKVu=0e."Q6|/u)5<ޢ#"nw?̍ %R#"s<|%kd``!}ۮt0,klwěr=6%='1' Õ䨭x83y5l>$49f &bHALZdHmk 3bɪ+Xhz]g||K*aqvq_םJ""Տ=F܂Zka6ROh_IJ%EWɱ|fO$W0MTlvO,i^۟;A' S됈,rfoWm6] Z)M8^H߯X,@<'H$?}-L&r EhMLMsos3Yv4[ũ(D,VkgkD(th-MPN7^eUbTKc~Zy^?B1$9 N|q6rŀspyKsC7_ I8Vi.y&1'   78!x + o °a\eB35p\qC\/"Y%iGg gĞfum՞N1ό:F`ŚCS2I%_^{[lzQ#%y~z)qztD&X7˄L Z +/\@g -f9V[)AY6Om)ch.~0+b`I۫oIZk dD$R珔8mqE:uyɻcS|Kt>}e.S7grI6fV`L(F]t| +԰rž|Y62D$ꞕҦGG;4^P#m.+ϟ2iB;ߞ广нg(~]OrOp8mTԥt;ogURxu9"]JZKJN`קɭ)VIK9 59&.׉\|V,>k1y]mҶ [ND]JsN&gO=v9{7<$.!fAnݢ.Cdt\"t.n =dS,?:ؘ"ܐgfύ'/y>CO3G<:Һra8{?mګĸ2.="ôY\|R?!2DPcED:}޽}63>Knel=J*. O\W" 4ʟ:7RDKH'pAs1߾HGwY9iDԥYĘ#,eVy3b1`[bRZ>g_OC^,rYV}PW5aXk&owP!x^93.EdI/ҁ9N'ٻ(㯙ٖz HGPzQ, (ŎqԳٻg;gb K ҷL~EH2}/lvf) /iX=%Y0m<~YξYp D&W~}s8RnEO vooi⪓jFn.g͓Ղ6W>^>=\}< WI;U6ϊh-l.I1-̀:s@{wHEvWK<~%6 10--3pӹ‹"T0qd\:vͲڧZXcұOȇsJ/P@~vjcwMQ53oI)_Yc8yŢw w+6훺u`jkyfr%k;?P$j2M /;ȯJ["SX3"_!zwg ۹\zh&;͡o 7ކb럭]ޘz~M.Aui |UG߶\RAogޝuv^$- .;6(k6G b; i6_F">H {ث6@k)T4-0F}]A;wsx/M2MF''EL=" fѯKݜ1e̓WEYmCz!S7q>6Ζeob0?nsVQRRZRV(/븴nMSVM72hYhѪ 9bEMN!ŵXl+ Gq{gpɚ&s;* BU~?iڗH ˍѷWdgyw%dкMnIvr2Mr2 25hf%}0:BN}h3Yqw~dg'Ev /Rw+=6nӽ] Ԩz}rDccj0l>s)EWIa8e Dnf/+ ˆb-v((3auqeqAA嘄?-j|_߬fse0ŭ /Of76DaHOkFgr0 IDATwMe<~Y.b5G8} Vi|#.;ȯ\C<"i#0ib!v\W?(&FX.|MKe0~Xͯ-I1ngҊ27 W=t- PL*a4Wgr9Yw௮&$Х$7BERXalڶy5 ;N&eU.붸pyj>cq^Ġ,JUԡS0{wVdҭ(@| ZuP<|Q6_-4<Iz%\SGqq;t""vT|Uy?YJJmLEyM8h2!Pnl2[< m/$`d]5$#.ƴ "Rn{a=zQD~")4M?_=dls?M[YodTV׬8rެ0^!wg~`͏\:A_ۈnk4U54GFr.Q\7("" 1&IG*}2cv5 f8̷{iνa}U?+4M,4 Lè4itܣ%+Woߗ|σݻǞS$EH֫y~ݴߊw3#\68l+i"HNAw2ik( ]5OU3}u$C¼0 ,}90v6_/%y~I%AGa4`6JrtPi(Z51ylw\5O[ifqJ5l)zwYƭ6Y r<778eDAݳά;' gr+Q.}f[3 iHmQ#AG_4p\p=0u\iP:Q-;\f}qU&iRZgfI6HӅR݁{94'+6gfI$%lfV$F Kֿ`64G$kȊh_3w֝Z격%fXuT|{q&ϪاF-;\n!AeՒj (?8?/;8?")g!F֝R|(Y\KKXUвI{( _r1Na2gY&1nTLzL cǎ4K$n2~Gʦ.qF_/ͥUݴ: _߬y0{d㎦knPIAoot+~*~DDjMC|%ٸx|L?ޙ. dgf-N 4sV0ゼiGS4G$Eض͜9s(n`XL9?&_U'iT"yxMg"uOY Q.Eatph$+bЪI2Xe횁zlmÐj:<>?+IW*DRë/]`71I&瑑\u#$.ɜ4d-݋LZZX)/rUHR <:- =f[PI7;dלHP 4Q[$ K^N?H`v^t]gwOdgUJ.anw,if.ys%q 29Azy5 L?"Im6lM]DDyGfrUx.e#w[1\3Gg2v_Rޚd 7VC ɮ P(BIJҴ/$0hh1DDDviByú-#Ywt+ y3KKxnJ u>g"wc;`.F$)G"$âtl^VDDEraj:{ 1zPKO eKR8{Ş%>]a$̬<**$MR3 .=6=fdה$SY`!I2۶H}kHuMZY|СU;䮉,Zq5;xjcI 8w߭fڗ}I2ݻ/s!""KF$NHYqv6߭p9R w,IRwM p# !i@H9!|5068%ɡsgg6v6&YkSB_qx7F7ƶ5GjGMzi:tދw _lЪFIr&U'Sxgf''mc GdXhHG!.8n.,NziAF 0 0M0|x?*[ ?"IhMd9NED$9dXhaIF s0%.ϋ[)##tooԻUlfqVaxam, =mq-Ǝ;.afe p]x4hĦ-xyx:?yč<:}0]O$ m\>M5+SDDӜE1r2 &$F9&pk6;|8OcY&:dg+;#'@i>販5=eii8 hϊ 22슫Dv$Tc;U.JEDDe- 5FRKQ3X5&U6NpƝ84/dm J&wg8݋BaIȿe;TKS#d<5F\pR$""qyWw]ң}@W,X0o2 `h`]h5=;Z\~lvz:Iɣ]~My!D䗍8h_n=-Nƺ%""嚧%C.R;,\iro)pi]3}z8lFզ>bZ2sLJ!f8tP6#$΂K<81oQDO# @$[&drU*$nbѺ!2(tY.o|算*( кIF&DBdKL]l ⶋmCQGEazOr=/?_k_|A 9blG~GA6tnU.~%)AH23 TH߹" UNep$""P<7%F!?"1AWiU0zc.?PWP >K H^䲫lHaDzu&v&'+)rmpgmp936Lks}yKn-$?JEReX^i8f,6_wkxLf;};:̗{:pPY%dz랩Uޖ_+_vk7ѶR&x{F`jo XKS#b̙CV?""Rgw*Ka*,bw0q\"ziN 8,aj1t=y|fJ Lf-S`@_IzLl'}ߊHr( hVG$fpZևxwPBAEaA& bɱv͆bmf/r(a]أIYj}yf nlOkOu}/WH TǵVԎ9?$X"-Kڜ821t(:eYᇵ6߭jWmѺE2Qi sh"i- Q+VmtT,)66 OӾ$%.btB$̥[EDd׬h.&Azqiq-(`XA݊@1%kmsdѺiUKlr*ÄahXfͨLH #l A,G܆x#n׌zÂ6+a1b@scN^rIAb}UEDDRН/gd cHW'l3קWYG Gfs=ʫ:a5exfYx@eC2kF٘M f87rR>;4 \/N^$k׎M+""7K+e9dɔI/MK[e+Kvij}4I~zIAP}g&R㈈H 1Mh$ \F :"R;/KϏdfXL80 \O$?+&s1js9Lkz.XDd|,Gj~GWiڗIa_q3g4>˴@.C*(("b>6 -Z\tt/1MU#O#DRis(^T; sg1i("q^W?uZןŋ0+.4s=c&Iqξo#}^ms $""m`TޝN""O;p4GR4pٕa޼/|d﹉^{%""IA7oBɎ Ř:!0ro##%%4p揤ED~ׯ?W^vfcϩDD$٭q^Ý!"?q2/ƻXC0gOgGVT+ _p71j eY^´n-Eً=#i2- W-,OH >|~ ^z1n`F8bݙx*Z7h\~D.Kzɷ˪9kt~JYi81D~]q-pïѬǹavI fK/~ǐzjf#&?"222Bv"""a;*DʼY;1C}IP#")##t+7e ㈈n2 KЊԑ]}G/;p"IG})ʥ>wù,>qND$%m*IDjR Ei؎w ;;rյwj*>n|}.[BAzdT$?HvD" >_*~I[U1E7߭}=Bwh[SX KMD$M89q,H*)svEY1A]Bn6FGDvٞ=1طM-ZYV|-4tË f35YI!*Dd:0|;>B""qlVoE:r: ry~G:ԇ!֕S'0bH2Ӿt"O}ioهVc#`dTqm/8tҩ~Gm*DV|6c}^3\\+;ܑւ""{*Oу~GI)̷rvYj"E:V\{s4o8"B又ظq#-[d޼/˘0"w,]\; 5Ldgj+IW~\pݫqDj3?x%/r@ IF̌8\wї1}㑝ל;ԁX2>xN9l:S:Ӣu'6n6""j>!¡W2SXc ;ԣW'nmyoA %RoTHXnt.Ww%PRg q{ͣEN'%yYW hZcl]9`h iTH`?`E5TD$]2='_'xm/g_RZqa Ii Tr=OhMitU&"o.T ʴ1Dǝ/FYa`;ԓM%.3&$lg^#%te&"j0 ҠBt($'SSh$yL1 g;ԃJً,XǶ:wy&T4KDjݗ>WC+ ӗH|A)_ytӈj KL{%l~_GΨÎgϞ~IZ#"Dw8"iM又$-[p'rő#;,,Vn0g:t`=RRRBqq1ӧ}ĒEG9$31awUQ qi_xͲu ڵ2j`ÇD& ‚ mg$&]8«iӦDҞI:UUU\|?(")ij̣yQ?>Aa홞x>~LZ7Pe_]ʽG KV߭H\}Zw,i@bQ94kQDatso:wL׮]'`3O`}Qt7YXჯ37ڛ3'^I֭wk[xɚkc@-T;jALXcWy1℃$ IJ6K9,h"-:3lđ<4h*D$\|qw*2~'I K%bQx۷V~yy9~:&DudڕO~r=8gLvfxvr5ʼnC=ہ>Mrk>̖䷠gZlw<A又$D"EK"^`lJ6DFzX?G}ć^q`9I꘱ S\F 3+ǿ~0 \4j~A( P `aއ_Jm9w\ޝ4JOs^2접ID~I*SLa9f.*Ev`&lhQ;޽po=?opؒ&ܱ [=Àw6ac``дQMx8㸸^[SeejJ@0  BAP0ߢ!0j>40-e`0uFͯ-X5&dthiѹMr)/NsvEGhH#0=LFC9+""A又$Ʊ7دnJôjcZ XCfFô00uۏ̝9oICUI_QVerݩ|vB4U1Dcqh#n H5x.ض؎$3m0qܚ xe* j{uH-{75~fGǘ#;?"*++h^~Ii1lwCXŢ"4YgܾZ%Y\2Ӯ^tч=ܓ-[b$|oNO^aՏ6O5f*\ޙcu;tn };[OtNjQ+?SoIg\ʰFGDv]"³^9'>c,EЂז1d6"a{BhIff͛7' 1w|={ ' `7-§Y oCLv.M7ipwwA-+k;3bX*d*F 䐁k&7TKycf\zM:I%*DgM8GHfa\0*׃9ٰ 60gq?u>8qg˃h4Icܢ:7r_m۶~M݊9t.D ?Z ;\yV8Xǘ}3?H)D˫\_peK䜱3Lߧ ,ڟn(" T&Q\7~aۼ9 ?QČ3Xp.[1Vuv{%O]e"ǚ1,fH$2 d 2#Y&J]~X'2h(@Vp@T=^îfq~G]GD|QRR55Ktޙa8G@>}1׮]=7.xN+R|u>~!>N^tH=6D8Ie{%9VzTTyVTD=*]529lP9*$ڵ;"%"3r@b@w3w2g$Ӧz{mr)W2qL=ldcԹ٤G!-} ~2ʕ'ӱeHI);$;4шWI-9aѨ1Dd7zY8V@e)[-Ҽy _r{CiPh]0AhŸֲЅnC0 .~уW" Ҫ ]v;Hzy8q?Ö_~Ygg[xODs:Ҋq|_aZٝ6{ 35"ctjHOw"-xBN/!-8045t{]D{ҹ>ǏRf z.Βnu97$$~?}N"])e=`v  !N(=Zev 9EZ#"?~-~z/} [NAfǐ.Pn{("rKNW]sQD/t.w45 >N {ؔ`׍\0lxjFmvNs x^k,lЪ "2۷/X뉎ߜzCH pM,"*,@FP'x}PlP`pJUcM}a`ݞtay̎!"@]EYmWE b `Xp@l<10zՠ >?4| ʡ-Ce$ƅQt7@\+!v/Qn!v 16J+Oj`qbw^ekD_x yPnFī) s晬."@ QQaDE24m {_Kg;2c~?ɩp2;/ov0}t6s}w34#CG1  0 Gz~7{ }dYٖ 1p=a0 8*'ð'!?Ӊnp8zrqb TUUz#55QFuҧ3rq%{GuR~ivSKE5X,i|C͖\s='=笢bx< 6DKRG)ؙcvnk(n(9zn_ģ?9/x), Pp̍a̝!NLٳ5SxH]e5=>œ^ic\\]C}т;f~~>kV`'a1Hn=$}%!1c.t[|D K~bv*DT />kΪ`x| 0&>1'+Hwyޛ̱zwrưW̎bB[Ŭo_ln3)QPFYz/83NMkk+oʋ<3؈/}d!18q">"vI[0$ƶj0{ kZp{SQQAMM .ٴi׭bLΌ5LPT5:XvQDS-Yt3&6{lueo=g^Ȝ9Y8nn*&̥ﮗ ٯ>}Qb^.&:40tpMvؽ#L9ƨ뵟?x.]v>'??*f*.˲0gbǕgEVxGm_IDATg^c^óQS \V~BmK 0~)*PXXhiVpNW^7?0;t?"b˗ih1؜$9g5Bf͚ev<rsxfG6꣸m2/(RSS~nx[.s'z s95ʦ6o甏g`ha`~|T8~M}:‡3uX[*Y?dN'555q0+Wn0cAtbSRJcDMtl2؟&&ػJH=?_>BxxQDS-!Y.sC >}ّMV,xa!2܊6ECGAZ$δr ֭yGѡ͜ǔ={`Eu;mUF2d8R1``F;vfQZZJEY)y9)->C=̟nq;,d^{XDիWcۙ7o޿ ٵk-Mܟ};n"SN烓}~_ K2vR㭄j5O{_fРAfSGDLJhh1۷ܜ*Kٱ}+TSZz8"0:yScwKv|Uoq̌Y {졸0_Y<ĨKyfe 1,Xt5gcJ{kVsOs8# J@y^^\b1<ijl=3h7oUpk TsƘ]BlvyQD.QWWGtt4555|ܵ[GO>Gim vB8}Əov^-;;\}w+8'ϡ0]:ٳIHAL6Zj%|yhmĠH/ Err1 Gyy9o {vod\&}8-$DY蟬ҳyE9}"N k a4?.g)h"bR?i&C"))}&--Çs1, YYYfYG/6s&[ YrR)P#""+**rZohHY5CD0aP3CډThh vp >ʊ n M^vo+o$S;8/̛ӧ6 ',:s[Ȝ,sfHZx^ٲy|'5$FNBx}TIx+C&E?("rTHVRRB}}=*46h%%CXV!<ƮPj-v|;qqzXe5)4573n3oorSso#&&O@NEyy9ݱty};}!Y^@\\\[K^^r(-Mu..&)&j&kF I[ϖ,VsqD=zǏs ׿{9s6nͶy^Vm1xzg,Y zݻwngn{[8/e7w9ٿ?7?YM{w[ku_I?{}ev$ *DD+++k9g>]q=XuͶ۵~yzO[j-Аjº0ư9rّDzN|<kvP.^}cjH'x˿E6fhբcHC< 0;t?"""o]\9GN~-Zr-]qmGTVVr-a==Ewo2xe6qf4;t2?"""ry$Qҳrف^r|O>WMLX 5 Na^#| J1(ŎCz=^>҅ǞD,\xّD^?x Jn#5Fx()QrrF`=ܻB&6igGA4CFgqwwE|yyyTTTziii`A13'Ob#.J['߱RIkoeƌGD5jkkٸSK{k=Ghlj&#cbNֹTVɇoGSk:EW>SGxO>| W#rJKK<D#Ӝx%<g98"b?""""CƑ bl(޷/#L jvLSWWǁ؟Gme ;FzBxwSG[i@(GakqbX '2,ƶ2~N rrav1PSSCQQ---AttٱD̾}(,8 k))-Xea"X-vp⣬Lfjh 3WԴ1t,#ǞA\\mmmcٰ455QX5+?(fnv/ KfvSMc&kႄ\ɝ<щW&"=9)SUUž=TMvc@̜ILL w?ڍ,ut6|G_HHCDd !.7 Iۛ q:(,ȣ s>{8v2;(?VIێ") >ZDIxiD0q&?&THjhh JoO^aљ$Vm0s_3&%%C{A@kS-3x222;vlODog /eXk;j3.fUכGD[*DDDDD[{8+e/t53=f_THW__‘Lc.+xkk,O=RjHGDDDDDzRm?&/7 Mgob׮ jkw[)iz.=ȉR#"""""=ֆ (%c0B:x=JY}4gde~i*DDDDD$(_cܱ8nuVA, ^AjE8C4=n*OɬY̾dGDDDDDaΝ>\D˅v墱 @DDhi0|~?q 455Fbb"iՉ|?*DDDDDDDD""""""""yT1?""""""""AL又HS#"""""""T1?""""""""AL又HS#"""""""T1?""""""""AL又HS#"""""""T1?""""""""AL又HS#"""""""T1?""""""""AL又HS#"""""""T1?""""""""A"egPIENDB`Fiona-1.10.1/docs/index.rst000066400000000000000000000032011467206072700154260ustar00rootroot00000000000000=============================================== Fiona: access to simple geospatial feature data =============================================== Fiona streams simple feature data to and from GIS formats like GeoPackage and Shapefile. Simple features are record, or row-like, and have a single geometry attribute. Fiona can read and write real-world simple feature data using multi-layered GIS formats, zipped and in-memory virtual file systems, from files on your hard drive or in cloud storage. This project includes Python modules and a command line interface (CLI). Here's an example of streaming and filtering features from a zipped dataset on the web and saving them to a new layer in a new Geopackage file. .. code-block:: python import fiona with fiona.open( "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip" ) as src: profile = src.profile profile["driver"] = "GPKG" with fiona.open("example.gpkg", "w", layer="selection", **profile) as dst: dst.writerecords(feat in src.filter(bbox=(-107.0, 37.0, -105.0, 39.0))) The same result can be achieved on the command line using a combination of fio-cat and fio-load. .. code-block:: console fio cat zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip --bbox "-107.0,37.0,-105.0,39.0" \ | fio load -f GPKG --layer selection example.gpkg .. toctree:: :maxdepth: 2 Project Information Installation User Manual API Documentation CLI Documentation Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` Fiona-1.10.1/docs/install.rst000066400000000000000000000054141467206072700157750ustar00rootroot00000000000000============ Installation ============ Installation of the Fiona package is complicated by its dependency on libgdal and other C libraries. There are easy installations paths and an advanced installation path. Easy installation ================= Fiona has several `extension modules `__ which link against libgdal. This complicates installation. Binary distributions (wheels) containing libgdal and its own dependencies are available from the Python Package Index and can be installed using pip. .. code-block:: console pip install fiona These wheels are mainly intended to make installation easy for simple applications, not so much for production. They are not tested for compatibility with all other binary wheels, conda packages, or QGIS, and omit many of GDAL's optional format drivers. If you need, for example, GML support you will need to build and install Fiona from a source distribution. Many users find Anaconda and conda-forge a good way to install Fiona and get access to more optional format drivers (like GML). Fiona 1.9 (coming soon) requires Python 3.7 or higher and GDAL 3.2 or higher. Advanced installation ===================== Once GDAL and its dependencies are installed on your computer (how to do this is documented at https://gdal.org) Fiona can be built and installed using setuptools or pip. If your GDAL installation provides the ``gdal-config`` program, the process is simpler. Without pip: .. code-block:: console GDAL_CONFIG=/path/to/gdal-config python setup.py install With pip (version >= 22.3 is required): .. code-block:: console python -m pip install --user -U pip GDAL_CONFIG=/path/to/gdal-config python -m pip install --user . These are pretty much equivalent. Pip will use setuptools as the build backend. If the gdal-config program is on your executable path, then you don't need to set the environment variable. Without gdal-config you will need to configure header and library locations for the build in another way. One way to do this is to create a setup.cfg file in the source directory with content like this: .. code-block:: ini [build_ext] include_dirs = C:/vcpkg/installed/x64-windows/include libraries = gdal library_dirs = C:/vcpkg/installed/x64-windows/lib This is the approach taken by Fiona's `wheel-building workflow `__. With this file in place you can run either ``python setup.py install`` or ``python -m pip install --user .``. You can also pass those three values on the command line following the `setuptools documentation `__. However, the setup.cfg approach is easier. Fiona-1.10.1/docs/manual.rst000066400000000000000000001341651467206072700156120ustar00rootroot00000000000000===================== The Fiona User Manual ===================== :Author: Sean Gillies, :Version: |release| :Date: |today| :Copyright: This work, with the exception of code examples, is licensed under a `Creative Commons Attribution 3.0 United States License`__. The code examples are licensed under the BSD 3-clause license (see LICENSE.txt in the repository root). .. __: https://creativecommons.org/licenses/by/3.0/us/ :Abstract: Fiona is OGR's neat, nimble, no-nonsense API. This document explains how to use the Fiona package for reading and writing geospatial data files. Python 3 is used in examples. See the `README `__ for installation and quick start instructions. .. sectnum:: Introduction ============ :dfn:`Geographic information systems` (GIS) help us plan, react to, and understand changes in our physical, political, economic, and cultural landscapes. A generation ago, GIS was something done only by major institutions like nations and cities, but it's become ubiquitous today thanks to accurate and inexpensive global positioning systems, commoditization of satellite imagery, and open source software. The kinds of data in GIS are roughly divided into :dfn:`rasters` representing continuous scalar fields (land surface temperature or elevation, for example) and :dfn:`vectors` representing discrete entities like roads and administrative boundaries. Fiona is concerned exclusively with the latter. It is a Python wrapper for vector data access functions from the `GDAL/OGR `_ library. A very simple wrapper for minimalists. It reads data records from files as GeoJSON-like mappings and writes the same kind of mappings as records back to files. That's it. There are no layers, no cursors, no geometric operations, no transformations between coordinate systems, no remote method calls; all these concerns are left to other Python packages such as :py:mod:`Shapely ` and :py:mod:`pyproj ` and Python language protocols. Why? To eliminate unnecessary complication. Fiona aims to be simple to understand and use, with no gotchas. Please understand this: Fiona is designed to excel in a certain range of tasks and is less optimal in others. Fiona trades memory and speed for simplicity and reliability. Where OGR's Python bindings (for example) use C pointers, Fiona copies vector data from the data source to Python objects. These are simpler and safer to use, but more memory intensive. Fiona's performance is relatively more slow if you only need access to a single record field – and of course if you just want to reproject or filter data files, nothing beats the :command:`ogr2ogr` program – but Fiona's performance is much better than OGR's Python bindings if you want *all* fields and coordinates of a record. The copying is a constraint, but it simplifies programs. With Fiona, you don't have to track references to C objects to avoid crashes, and you can work with vector data using familiar Python mapping accessors. Less worry, less time spent reading API documentation. Rules of Thumb -------------- In what cases would you benefit from using Fiona? * If the features of interest are from or destined for a file in a non-text format like ESRI Shapefiles, Mapinfo TAB files, etc. * If you're more interested in the values of many feature properties than in a single property's value. * If you're more interested in all the coordinate values of a feature's geometry than in a single value. * If your processing system is distributed or not contained to a single process. In what cases would you not benefit from using Fiona? * If your data is in or destined for a JSON document you should use Python's :py:mod:`json` or :py:mod:`simplejson` modules. * If your data is in a RDBMS like PostGIS, use a Python DB package or ORM like :py:mod:`SQLAlchemy` or :py:mod:`GeoAlchemy`. Maybe you're using :py:mod:`GeoDjango` already. If so, carry on. * If your data is served via HTTP from CouchDB or CartoDB, etc, use an HTTP package (:py:mod:`httplib2`, :py:mod:`Requests`, etc) or the provider's Python API. * If you can use :command:`ogr2ogr`, do so. Example ------- The first example of using Fiona is this: copying features (another word for record) from one file to another, adding two attributes and making sure that all polygons are facing "up". Orientation of polygons is significant in some applications, extruded polygons in Google Earth for one. No other library (like :py:mod:`Shapely`) is needed here, which keeps it uncomplicated. There's a :file:`coutwildrnp.zip` file in the Fiona repository for use in this and other examples. .. code-block:: python import datetime import fiona from fiona import Geometry, Feature, Properties def signed_area(coords): """Return the signed area enclosed by a ring using the linear time algorithm at http://www.cgafaq.info/wiki/Polygon_Area. A value >= 0 indicates a counter-clockwise oriented ring. """ xs, ys = map(list, zip(*coords)) xs.append(xs[1]) ys.append(ys[1]) return sum(xs[i] * (ys[i + 1] - ys[i - 1]) for i in range(1, len(coords))) / 2.0 with fiona.open( "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip" ) as src: # Copy the source schema and add two new properties. dst_schema = src.schema dst_schema["properties"]["signed_area"] = "float" dst_schema["properties"]["timestamp"] = "datetime" # Create a sink for processed features with the same format and # coordinate reference system as the source. with fiona.open( "example.gpkg", mode="w", layer="oriented-ccw", crs=src.crs, driver="GPKG", schema=dst_schema, ) as dst: for feat in src: # If any feature's polygon is facing "down" (has rings # wound clockwise), its rings will be reordered to flip # it "up". geom = feat.geometry assert geom.type == "Polygon" rings = geom.coordinates sa = sum(signed_area(ring) for ring in rings) if sa < 0.0: rings = [r[::-1] for r in rings] geom = Geometry(type=geom.type, coordinates=rings) # Add the signed area of the polygon and a timestamp # to the feature properties map. props = Properties.from_dict( **feat.properties, signed_area=sa, timestamp=datetime.datetime.now().isoformat() ) dst.write(Feature(geometry=geom, properties=props)) Data Model ========== Discrete geographic features are usually represented in geographic information systems by :dfn:`records`. The characteristics of records and their semantic implications are well known [Kent1978]_. Among those most significant for geographic data: records have a single type, all records of that type have the same fields, and a record's fields concern a single geographic feature. Different systems model records in different ways, but the various models have enough in common that programmers have been able to create useful abstract data models. The `OGR model `__ is one. Its primary entities are :dfn:`Data Sources`, :dfn:`Layers`, and :dfn:`Features`. Features have not fields, but attributes and a :dfn:`Geometry`. An OGR Layer contains Features of a single type ("roads" or "wells", for example). The GeoJSON model is a bit more simple, keeping Features and substituting :dfn:`Feature Collections` for OGR Data Sources and Layers. The term "Feature" is thus overloaded in GIS modeling, denoting entities in both our conceptual and data models. Various formats for record files exist. The :dfn:`ESRI Shapefile` [ESRI1998]_ has been, at least in the United States, the most significant of these up to about 2005 and remains popular today. It is a binary format. The shape fields are stored in one .shp file and the other fields in another .dbf file. The GeoJSON [GeoJSON]_ format, from 2008, proposed a human readable text format in which geometry and other attribute fields are encoded together using :dfn:`Javascript Object Notation` [JSON]_. In GeoJSON, there's a uniformity of data access. Attributes of features are accessed in the same manner as attributes of a feature collection. Coordinates of a geometry are accessed in the same manner as features of a collection. The GeoJSON format turns out to be a good model for a Python API. JSON objects and Python dictionaries are semantically and syntactically similar. Replacing object-oriented Layer and Feature APIs with interfaces based on Python mappings provides a uniformity of access to data and reduces the amount of time spent reading documentation. A Python programmer knows how to use a mapping, so why not treat features as dictionaries? Use of existing Python idioms is one of Fiona's major design principles. .. admonition:: TL;DR Fiona subscribes to the conventional record model of data, but provides GeoJSON-like access to the data via Python file-like and mapping protocols. Reading Vector Data =================== Reading a GIS vector file begins by opening it in mode ``'r'`` using Fiona's :py:func:`~fiona.open` function. It returns an opened :py:class:`~fiona.collection.Collection` object. .. code-block:: pycon >>> import fiona >>> colxn = fiona.open("zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip", "r") >>> colxn >>> collection.closed False Mode ``'r'`` is the default and will be omitted in following examples. Fiona's :py:class:`~fiona.collection.Collection` is like a Python :py:class:`file`, but is iterable for records rather than lines. .. code-block:: pycon >>> next(iter(colxn)) {'geometry': {'type': 'Polygon', 'coordinates': ... >>> len(list(colxn)) 67 Note that :py:func:`list` iterates over the entire collection, effectively emptying it as with a Python :py:class:`file`. .. code-block:: pycon >>> next(iter(colxn)) Traceback (most recent call last): ... StopIteration >>> len(list(colxn)) 0 Seeking the beginning of the file is not supported. You must reopen the collection to get back to the beginning. .. code-block:: pycon >>> colxn = fiona.open("zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip") >>> len(list(colxn)) 67 .. admonition:: File Encoding The format drivers will attempt to detect the encoding of your data, but may fail. In this case, the proper encoding can be specified explicitly by using the ``encoding`` keyword parameter of :py:func:`fiona.open`, for example: ``encoding='Windows-1252'``. New in version 0.9.1. Collection indexing ------------------- Features of a collection may also be accessed by index. .. code-block:: pycon >>> with fiona.open("zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip") as colxn: ... print(colxn[1]) ... Note that these indices are controlled by GDAL, and do not always follow Python conventions. They can start from 0, 1 (e.g. geopackages), or even other values, and have no guarantee of contiguity. Negative indices will only function correctly if indices start from 0 and are contiguous. New in version 1.1.6 Closing Files ------------- A :py:class:`~fiona.collection.Collection` involves external resources. There's no guarantee that these will be released unless you explicitly :py:meth:`~fiona.collection.Collection.close` the object or use a :keyword:`with` statement. When a :py:class:`~fiona.collection.Collection` is a context guard, it is closed no matter what happens within the block. .. code-block:: pycon >>> try: ... with fiona.open("zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip") as colxn: ... print(len(list(colxn))) ... assert True is False ... except Exception: ... print(colxn.closed) ... raise ... 67 True Traceback (most recent call last): ... AssertionError An exception is raised in the :keyword:`with` block above, but as you can see from the print statement in the :keyword:`except` clause :py:meth:`colxn.__exit__` (and thereby :py:meth:`colxn.close`) has been called. .. important:: Always call :py:meth:`~fiona.collection.Collection.close` or use :keyword:`with` and you'll never stumble over tied-up external resources, locked files, etc. Format Drivers, CRS, Bounds, and Schema ======================================= In addition to attributes like those of :py:class:`file` (:py:attr:`~file.name`, :py:attr:`~file.mode`, :py:attr:`~file.closed`), a :py:class:`~fiona.collection.Collection` has a read-only :py:attr:`~fiona.collection.Collection.driver` attribute which names the :program:`OGR` :dfn:`format driver` used to open the vector file. .. code-block:: pycon >>> colxn = fiona.open("zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip") >>> colxn.driver 'ESRI Shapefile' The :dfn:`coordinate reference system` (CRS) of the collection's vector data is accessed via a read-only :py:attr:`~fiona.collection.Collection.crs` attribute. .. code-block:: pycon >>> colxn.crs CRS.from_epsg(4326) The :py:mod:`fiona.crs` module provides 3 functions to assist with these mappings. :py:func:`~fiona.crs.to_string` converts mappings to PROJ.4 strings: .. code-block:: pycon >>> from fiona.crs import to_string >>> to_string(colxn.crs) 'EPSG:4326' :py:func:`~fiona.crs.from_string` does the inverse. .. code-block:: pycon >>> from fiona.crs import from_string >>> from_string("+datum=WGS84 +ellps=WGS84 +no_defs +proj=longlat") CRS.from_epsg(4326) :py:func:`~fiona.crs.from_epsg` is a shortcut to CRS mappings from EPSG codes. .. code-block:: pycon >>> from fiona.crs import from_epsg >>> from_epsg(3857) CRS.from_epsg(3857) .. admonition:: No Validation Both :py:func:`~fiona.crs.from_epsg` and :py:func:`~fiona.crs.from_string` simply restructure data, they do not ensure that the resulting mapping is a pre-defined or otherwise valid CRS in any way. The number of records in the collection's file can be obtained via Python's built in :py:func:`len` function. .. code-block:: pycon >>> len(colxn) 67 The :dfn:`minimum bounding rectangle` (MBR) or :dfn:`bounds` of the collection's records is obtained via a read-only :py:attr:`~fiona.collection.Collection.bounds` attribute. .. code-block:: pycon >>> colxn.bounds (-113.56424713134766, 37.0689811706543, -104.97087097167969, 41.99627685546875) Finally, the schema of its record type (a vector file has a single type of record, remember) is accessed via a read-only :py:attr:`~fiona.collection.Collection.schema` attribute. It has 'geometry' and 'properties' items. The former is a string and the latter is a dict with items having the same order as the fields in the data file. .. code-block:: pycon >>> import pprint >>> pprint.pprint(colxn.schema) {'geometry': 'Polygon', 'properties': {'AGBUR': 'str:80', 'AREA': 'float:24.15', 'FEATURE1': 'str:80', 'FEATURE2': 'str:80', 'NAME': 'str:80', 'PERIMETER': 'float:24.15', 'STATE': 'str:80', 'STATE_FIPS': 'str:80', 'URL': 'str:101', 'WILDRNP020': 'int:10'}} Keeping Schemas Simple ---------------------- Fiona takes a less is more approach to record types and schemas. Data about record types is structured as closely to data about records as can be done. Modulo a record's 'id' key, the keys of a schema mapping are the same as the keys of the collection's record mappings. .. code-block:: pycon >>> feat = next(iter(colxn)) >>> set(feat.keys()) - set(colxn.schema.keys()) {'id'} >>> set(feat['properties'].keys()) == set(colxn.schema['properties'].keys()) True The values of the schema mapping are either additional mappings or field type names like 'Polygon', 'float', and 'str'. The corresponding Python types can be found in a dictionary named :py:attr:`fiona.FIELD_TYPES_MAP`. .. code-block:: pycon >>> pprint.pprint(fiona.FIELD_TYPES_MAP) {'List[str]': typing.List[str], 'bytes': , 'date': , 'datetime': , 'float': , 'int': , 'int32': , 'int64': , 'str': , 'time': } Field Types ----------- In a nutshell, the types and their names are as near to what you'd expect in Python (or Javascript) as possible. Since Python 3, the 'str' field type may contain Unicode characters. .. code-block:: pycon >>> type(feat.properties['NAME']) >>> colxn.schema['properties']['NAME'] 'str' >>> fiona.FIELD_TYPES_MAP[colxn.schema['properties']['NAME']] String type fields may also indicate their maximum width. A value of 'str:25' indicates that all values will be no longer than 25 characters. If this value is used in the schema of a file opened for writing, values of that property will be truncated at 25 characters. The default width is 80 chars, which means 'str' and 'str:80' are more or less equivalent. Fiona provides a function to get the width of a property. .. code-block:: pycon >>> from fiona import prop_width >>> prop_width('str:25') 25 >>> prop_width('str') 80 Another function gets the proper Python type of a property. .. code-block:: pycon >>> from fiona import prop_type >>> prop_type('int') >>> prop_type('float') >>> prop_type('str:25') Geometry Types -------------- Fiona supports the geometry types in GeoJSON and their 3D variants. This means that the value of a schema's geometry item will be one of the following: - Point - LineString - Polygon - MultiPoint - MultiLineString - MultiPolygon - GeometryCollection - 3D Point - 3D LineString - 3D Polygon - 3D MultiPoint - 3D MultiLineString - 3D MultiPolygon - 3D GeometryCollection The last seven of these, the 3D types, apply only to collection schema. The geometry types of features are always one of the first seven. A '3D Point' collection, for example, always has features with geometry type 'Point'. The coordinates of those geometries will be (x, y, z) tuples. Note that one of the most common vector data formats, Esri's Shapefile, has no 'MultiLineString' or 'MultiPolygon' schema geometries. However, a Shapefile that indicates 'Polygon' in its schema may yield either 'Polygon' or 'MultiPolygon' features. Features ======== A record you get from a collection is structured like a GeoJSON Feature. Fiona records are self-describing; the names of its fields are contained within the data structure and the values in the fields are typed properly for the type of record. Numeric field values are instances of type :py:class:`int` and :py:class:`float`, for example, not strings. The record data has no references to the :py:class:`~fiona.collection.Collection` from which it originates or to any other external resource. It's entirely independent and safe to use in any way. Closing the collection does not affect the record at all. .. admonition:: Features are mappings, not dicts In Fiona versions before 1.9.0 features were Python dicts, mutable and JSON serializable. Since 1.9.0 features are mappings and not immediately JSON serializable. Instances of Feature can be converted to dicts with :py:func:`fiona.model.to_dict` or serialized using the json module and :py:class:`fiona.model.ObjectEncoder`. Feature Id ---------- A feature has an ``id`` attribute. As in the GeoJSON specification, its corresponding value is a string unique within the data file. .. code-block:: pycon >>> colxn = fiona.open("zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip") >>> feat = next(iter(colxn)) >>> feat.id '0' .. admonition:: OGR Details In the :program:`OGR` model, feature ids are long integers. Fiona record ids are therefore usually string representations of integer record indexes. Feature Properties ------------------ A feature has a ``properties`` attribute. Its value is a mapping. The keys of the properties mapping are the same as the keys of the properties mapping in the schema of the collection the record comes from (see above). .. code-block:: pycon >>> for k, v in feat.properties.items(): ... print(k, v) ... PERIMETER 1.22107 FEATURE2 None NAME Mount Naomi Wilderness FEATURE1 Wilderness URL http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Naomi AGBUR FS AREA 0.0179264 STATE_FIPS 49 WILDRNP020 332 STATE UT Feature Geometry ---------------- A feature has a ``geometry`` attribute. Its value is a mapping with ``type`` and ``coordinates`` keys. .. code-block:: pycon >>> feat.geometry["type"] 'Polygon' >>> feat.geometry["coordinates"] [[(-111.73527526855469, 41.995094299316406), ..., (-111.73527526855469, 41.995094299316406)]] Since the coordinates are just tuples, or lists of tuples, or lists of lists of tuples, the ``type`` tells you how to interpret them. +-------------------+---------------------------------------------------+ | Type | Coordinates | +===================+===================================================+ | Point | A single (x, y) tuple | +-------------------+---------------------------------------------------+ | LineString | A list of (x, y) tuple vertices | +-------------------+---------------------------------------------------+ | Polygon | A list of rings (each a list of (x, y) tuples) | +-------------------+---------------------------------------------------+ | MultiPoint | A list of points (each a single (x, y) tuple) | +-------------------+---------------------------------------------------+ | MultiLineString | A list of lines (each a list of (x, y) tuples) | +-------------------+---------------------------------------------------+ | MultiPolygon | A list of polygons (see above) | +-------------------+---------------------------------------------------+ Fiona, like the GeoJSON format, has both Northern Hemisphere "North is up" and Cartesian "X-Y" biases. The values within a tuple that denoted as ``(x, y)`` above are either (longitude E of the prime meridian, latitude N of the equator) or, for other projected coordinate systems, (easting, northing). .. admonition:: Long-Lat, not Lat-Long Even though most of us say "lat, long" out loud, Fiona's ``x,y`` is always easting, northing, which means ``(long, lat)``. Longitude first and latitude second, consistent with the GeoJSON format specification. Point Set Theory and Simple Features ------------------------------------ In a proper, well-scrubbed vector data file the geometry mappings explained above are representations of geometric objects made up of :dfn:`point sets`. The following .. code-block:: python {"type": "LineString", "coordinates": [(0.0, 0.0), (0.0, 1.0)]} represents not just two points, but the set of infinitely many points along the line of length 1.0 from ``(0.0, 0.0)`` to ``(0.0, 1.0)``. In the application of point set theory commonly called :dfn:`Simple Features Access` [SFA]_ two geometric objects are equal if their point sets are equal whether they are equal in the Python sense or not. If you have Shapely (which implements Simple Features Access) installed, you can see this in by verifying the following. .. code-block:: pycon >>> from shapely.geometry import shape >>> l1 = shape( ... {'type': 'LineString', 'coordinates': [(0, 0), (2, 2)]}) >>> l2 = shape( ... {'type': 'LineString', 'coordinates': [(0, 0), (1, 1), (2, 2)]}) >>> l1 == l2 False >>> l1.equals(l2) True .. admonition:: Dirty data Some files may contain vectors that are :dfn:`invalid` from a simple features standpoint due to accident (inadequate quality control on the producer's end), intention ("dirty" vectors saved to a file for special treatment) or discrepancies of the numeric precision models (Fiona can't handle fixed precision models yet). Fiona doesn't sniff for or attempt to clean dirty data, so make sure you're getting yours from a clean source. Writing Vector Data =================== A vector file can be opened for writing in mode ``'a'`` (append) or mode ``'w'`` (write). .. admonition:: Note The in situ "update" mode of :program:`OGR` is quite format dependent and is therefore not supported by Fiona. Appending Data to Existing Files -------------------------------- Let's start with the simplest if not most common use case, adding new records to an existing file. .. code-block:: console $ wget https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip $ unzip coutwildrnp.zip The coordinate reference system. format, and schema of the file are already defined, so it's opened with just two arguments as for reading, but in ``'a'`` mode. The new record is written to the end of the file using the :py:meth:`~fiona.collection.Collection.write` method. Accordingly, the length of the file grows from 67 to 68. .. code-block:: pycon >>> with fiona.open("coutwildrnp.shp", "a") as dst: ... print(len(dst)) ... with fiona.open("zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip") as src: ... feat = src[0] ... print(feat.id, feat.properties["NAME"]) ... dst.write(feat) ... print(len(c)) ... 67 ('0', 'Mount Naomi Wilderness') 68 The feature you write must match the file's schema (because a file contains one type of record, remember). You'll get a :py:class:`ValueError` if it doesn't. .. code-block:: pycon >>> with fiona.open("coutwildrnp.shp", "a") as dst: ... dst.write({'properties': {'foo': 'bar'}}) ... Traceback (most recent call last): ... ValueError: Record data not match collection schema Now, what about record ids? The id of a record written to a file is ignored and replaced by the next value appropriate for the file. If you read the file just appended to above, .. code-block:: pycon >>> with fiona.open("coutwildrnp.shp") as colxn: ... feat = colxn[-1] ... >>> feat.id '67' >>> feat.properties["NAME"] 'Mount Naomi Wilderness' You'll see that the id of ``'0'`` which the record had when written is replaced by ``'67'``. The :py:meth:`~fiona.collection.Collection.write` method writes a single record to the collection's file. Its sibling :py:meth:`~fiona.collection.Collection.writerecords` writes a sequence (or iterator) of records. .. code-block:: pycon >>> with fiona.open("coutwildrnp.shp", "a") as colxn: ... colxn.writerecords([feat, feat, feat]) ... print(len(colxn)) ... 71 .. admonition:: Duplication Fiona's collections do not guard against duplication. The code above will write 3 duplicate records to the file, and they will be given unique sequential ids. .. admonition:: Transactions Fiona uses transactions during write operations to ensure data integrity. :py:meth:`writerecords` will start and commit one transaction. If there are lots of records, intermediate commits will be performed at reasonable intervals. Depending on the driver, a transaction can be a very costly operation. Since :py:meth:`write` is just a thin convenience wrapper that calls :py:meth:`writerecords` with a single record, you may experience significant performance issue if you write lots of features one by one using this method. Consider preparing your data first and then writing it in a single call to :py:meth:`writerecords`. .. admonition:: Buffering Fiona's output is buffered. The records passed to :py:meth:`write` and :py:meth:`writerecords` are flushed to disk when the collection is closed. You may also call :py:meth:`flush` periodically to write the buffer contents to disk. .. admonition:: Format requirements Format drivers may have specific requirements about what they store. For example, the Shapefile driver may "fix" topologically invalid features. Creating files of the same structure ------------------------------------ Writing a new file is more complex than appending to an existing file because the file CRS, format, and schema have not yet been defined and must be done so by the programmer. Still, it's not very complicated. A schema is just a mapping, as described above. The possible formats are enumerated in the :py:attr:`fiona.supported_drivers` dictionary. Review the parameters of our demo file. .. code-block:: pycon >>> with fiona.open( ... "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip" ) as src: ... driver = src.driver ... crs = src.crs ... schema = src.schema ... feat = src[1] ... >>> driver 'ESRI Shapefile' >>> crs CRS.from_epsg(4326) >>> pprint.pprint(schema) {'geometry': 'Polygon', 'properties': {'AGBUR': 'str:80', 'AREA': 'float:24.15', 'FEATURE1': 'str:80', 'FEATURE2': 'str:80', 'NAME': 'str:80', 'PERIMETER': 'float:24.15', 'STATE': 'str:80', 'STATE_FIPS': 'str:80', 'URL': 'str:101', 'WILDRNP020': 'int:10'}} We can create a new file using them. .. code-block:: pycon >>> with fiona.open("example.shp", "w", driver=driver, crs=crs, schema=schema) as dst: ... print(len(dst)) ... dst.write(feat) ... print(len(dst)) ... 0 1 >>> dst.closed True >>> len(dst) 1 Because the properties of the source schema are ordered and are passed in the same order to the write-mode collection, the written file's fields have the same order as those of the source file. The :py:attr:`~fiona.collection.Collection.profile` attribute makes duplication of a file's meta properties even easier. .. code-block:: pycon >>> src = fiona.open("zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip") >>> dst = fiona.open("example.shp", "w", **src.profile) Writing new files from scratch ------------------------------- To write a new file from scratch we have to define our own specific driver, crs and schema. .. code-block:: pycon Consider the following record, structured in accordance to the `Python geo protocol `__, representing the Eiffel Tower using a point geometry with UTM coordinates in zone 31N. .. code-block:: pycon >>> eiffel_tower = { ... 'geometry': { ... 'type': 'Point', ... 'coordinates': (448252, 5411935) ... }, ... 'properties': dict([ ... ('name', 'Eiffel Tower'), ... ('height', 300.01), ... ('view', 'scenic'), ... ('year', 1889) ... ]) ... } A corresponding scheme could be: .. code-block:: pycon >>> landmarks_schema = { ... 'geometry': 'Point', ... 'properties': dict([ ... ('name', 'str'), ... ('height', 'float'), ... ('view', 'str'), ... ('year', 'int') ... ]) ... } The coordinate reference system of these landmark coordinates is ETRS89 / UTM zone 31N which is referenced in the EPSG database as EPSG:25831. .. code-block:: pycon >>> from fiona.crs import CRS >>> landmarks_crs = CRS.from_epsg(25831) An appropriate driver could be: .. code-block:: pycon >>> driver = "GeoJSON" Having specified schema, crs and driver, we are ready to open a file for writing our record: .. code-block:: pycon >>> with fiona.open( ... "landmarks.geojson", ... "w", ... driver="GeoJSON", ... crs=CRS.from_epsg(25831), ... schema=landmarks_schema ... ) as colxn: ... colxn.write(eiffel_tower) ... Ordering Record Fields ...................... The 'properties' item of :py:func:`fiona.open`'s 'schema' keyword argument is used to specify the ordering with written files. This usually a :py:func:`dict` built-in .. code-block:: python fiona.open( "/tmp/file.shp", "w", schema={"properties": {"bar": "int", "foo": "str"}}, **kwargs ) The order may alternatively be supplied as a list of property items .. code-block:: python fiona.open( "/tmp/file.shp", "w", schema={"properties": [("bar", "int"), ("foo", "str")]}, **kwargs ) 3D Coordinates and Geometry Types --------------------------------- If you write 3D coordinates, ones having (x, y, z) tuples, to a 2D file ('Point' schema geometry, for example) the z values will be lost. .. code-block:: python >>> feat = {"geometry": {"type": "Point", "coordinates": (-1, 1, 5)}} >>> with fiona.open( ... "example.shp", ... "w", ... driver="Shapefile", ... schema={"geometry": "Point", "properties": {}} ... ) as dst: ... dst.write(feat) ... >>> with fiona.open("example.shp") as src: ... print(src[0].geometry.coordinates) ... (-1.0, 1.0) If you write 2D coordinates, ones having only (x, y) tuples, to a 3D file ('3D Point' schema geometry, for example) a default z value of 0 will be provided. .. code-block:: python >>> feat = {"geometry": {"type": "Point", "coordinates": (-1, 1)}} >>> with fiona.open( ... "example.shp", ... "w", ... driver="Shapefile", ... schema={"geometry": "3D Point", "properties": {}} ... ) as dst: ... dst.write(feat) ... >>> with fiona.open("example.shp") as src: ... print(src[0].geometry.coordinates) ... (-1.0, 1.0, 0.0) Advanced Topics =============== OGR configuration options ------------------------- GDAL/OGR has a large number of features that are controlled by global or thread-local `configuration options. `_ Fiona allows you to configure these options using a context manager, ``fiona.Env``. This class's constructor takes GDAL/OGR configuration options as keyword arguments. To see debugging information from GDAL/OGR, for example, you may do the following. .. code-block:: python import logging import fiona logging.basicConfig(level=logging.DEBUG) with fiona.Env(CPL_DEBUG=True): fiona.open( "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip" ) The following extra messages will appear in the Python logger's output. .. code-block:: DEBUG:fiona._env:CPLE_None in GNM: GNMRegisterAllInternal DEBUG:fiona._env:CPLE_None in GNM: RegisterGNMFile DEBUG:fiona._env:CPLE_None in GNM: RegisterGNMdatabase DEBUG:fiona._env:CPLE_None in GNM: GNMRegisterAllInternal DEBUG:fiona._env:CPLE_None in GNM: RegisterGNMFile DEBUG:fiona._env:CPLE_None in GNM: RegisterGNMdatabase DEBUG:fiona._env:CPLE_None in GDAL: GDALOpen(tests/data/coutwildrnp.shp, this=0x1683930) succeeds as ESRI Shapefile. If you call ``fiona.open()`` with no surrounding ``Env`` environment, one will be created for you. When your program exits the environment's ``with`` block the configuration reverts to its previous state. Driver configuration options ---------------------------- Drivers can have dataset open, dataset creation, respectively layer creation options. These options can be found on the drivers page on `GDAL's homepage. `_ or using the ``fiona.meta`` module: .. code-block:: pycon >>> import fiona.meta >>> fiona.meta.print_driver_options("GeoJSON") These options can be passed to ``fiona.open``: .. code-block:: python import fiona fiona.open('tests/data/coutwildrnp.json', ARRAY_AS_STRING="YES") Cloud storage credentials ------------------------- One of the most important uses of ``fiona.Env`` is to set credentials for accessing data stored in AWS S3 or another cloud storage system. .. code-block:: python import fiona from fiona.session import AWSSession with fiona.Env( session=AWSSession( aws_access_key_id="key", aws_secret_access_key="secret" ) ): fiona.open("zip+s3://example-bucket/example.zip") The AWSSession class is currently the only credential session manager in Fiona. The source code has an example of how classes for other cloud storage providers may be implemented. AWSSession relies upon boto3 and botocore, which will be installed as extra dependencies of Fiona if you run ``pip install fiona[s3]``. If you call ``fiona.open()`` with no surrounding ``Env`` and pass a path to an S3 object, a session will be created for you using code equivalent to the following code. .. code-block:: python import boto3 from fiona.session import AWSSession import fiona with fiona.Env(session=AWSSession(boto3.Session())): fiona.open("zip+s3://fiona-testing/coutwildrnp.zip") Slicing and masking iterators ----------------------------- With some vector data formats a spatial index accompanies the data file, allowing efficient bounding box searches. A collection's :py:meth:`~fiona.collection.Collection.items` method returns an iterator over pairs of FIDs and records that intersect a given ``(minx, miny, maxx, maxy)`` bounding box or geometry object. Spatial filtering may be inaccurate and returning all features overlapping the envelope of the geometry. The collection's own coordinate reference system (see below) is used to interpret the box's values. If you want a list of the iterator's items, pass it to Python's builtin :py:func:`list` as shown below. .. code-block:: pycon >>> colxn = fiona.open( ... "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip" ... ) >>> hits = list(colxn.items(bbox=(-110.0, 36.0, -108.0, 38.0))) >>> len(hits) 5 The iterator method takes the same ``stop`` or ``start, stop[, step]`` slicing arguments as :py:func:`itertools.islice`. To get just the first two items from that iterator, pass a stop index. .. code-block:: pycon >>> hits = colxn.items(2, bbox=(-110.0, 36.0, -108.0, 38.0))) >>> len(list(hits)) 2 To get the third through fifth items from that iterator, pass start and stop indexes. .. code-block:: pycon >>> hits = colxn.items(2, 5, bbox=(-110.0, 36.0, -108.0, 38.0))) >>> len(list(hits)) 3 To filter features by property values, use Python's builtin :py:func:`filter` and :keyword:`lambda` or your own filter function that takes a single feature record and returns ``True`` or ``False``. .. code-block:: pycon >>> def pass_positive_area(rec): ... return rec['properties'].get('AREA', 0.0) > 0.0 ... >>> colxn = fiona.open( ... "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip" ... ) >>> hits = filter(pass_positive_area, colxn) >>> len(list(hits)) 67 Reading Multilayer data ----------------------- Up to this point, only simple datasets with one thematic layer or feature type per file have been shown and the venerable Esri Shapefile has been the primary example. Other GIS data formats can encode multiple layers or feature types within a single file or directory. GeoPackage is one example of such a format. A more useful example, for the purpose of this manual, is a directory or zipfile comprising multiple shapefiles. The GitHub-hosted zipfile we've been using in these examples is, in fact, such a multilayer dataset. The layers of a dataset can be listed using :py:func:`fiona.listlayers`. In the shapefile format case, layer names match base names of the files. .. code-block:: pycon >>> fiona.listlayers( ... "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip" ... ) ['coutwildrnp'] Unlike OGR, Fiona has no classes representing layers or data sources. To access the features of a layer, open a collection using the path to the data source and specify the layer by name using the `layer` keyword. .. code-block:: pycon dataset_path = "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip" >>> for name in fiona.listlayers(dataset_path): ... with fiona.open(dataset_path, layer=name) as colxn: ... pprint.pprint(colxn.schema) ... {'geometry': 'Polygon', 'properties': {'AGBUR': 'str:80', 'AREA': 'float:24.15', 'FEATURE1': 'str:80', 'FEATURE2': 'str:80', 'NAME': 'str:80', 'PERIMETER': 'float:24.15', 'STATE': 'str:80', 'STATE_FIPS': 'str:80', 'URL': 'str:101', 'WILDRNP020': 'int:10'}} Layers may also be specified by their numerical index. .. code-block:: pycon >>> for index, name in enumerate(fiona.listlayers(dataset_path)): ... with fiona.open(dataset_path, layer=index) as colxn: ... print(len(colxn)) ... 67 If no layer is specified, :py:func:`fiona.open` returns an open collection using the first layer. .. code-block:: pycon >>> with fiona.open(dataset_path) as colxn: ... colxn.name == fiona.listlayers(datasset_path)[0] ... True We've been relying on this implicit behavior throughout the manual. The most general way to open a shapefile for reading, using all of the parameters of :py:func:`fiona.open`, is to treat it as a data source with a named layer. .. code-block:: pycon >>> fiona.open( ... "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip", ... mode="r", ... layer="coutwildrnp" ... ) In practice, it is fine to rely on the implicit first layer and default ``'r'`` mode and open a shapefile like this: .. code-block:: pycon >>> fiona.open( ... "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip", ... ) Writing Multilayer data ----------------------- To write an entirely new layer to a multilayer data source, simply provide a unique name to the `layer` keyword argument. .. code-block:: pycon >>> with fiona.open( ... "zip+https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip", ... ) as src: ... with fiona.open("example.gpkg", "w", layer="example one", **src.profile) as dst: ... dst.writerecords(src) ... >>> fiona.listlayers("example.gpkg") ['example one'] In ``'w'`` mode, existing layers will be overwritten if specified, just as normal files are overwritten by Python's :py:func:`open` function. Unsupported drivers ------------------- Fiona maintains a list of OGR drivers in :py:attr:`fiona.supported_drivers` that are tested and known to work together with Fiona. Opening a dataset using an unsupported driver or access mode results in an :py:attr: `DriverError` exception. By passing `allow_unsupported_drivers=True` to :py:attr:`fiona.open` no compatibility checks are performed and unsupported OGR drivers can be used. However, there are no guarantees that Fiona will be able to access or write data correctly using an unsupported driver. .. code-block:: python import fiona with fiona.open("file.kmz", allow_unsupported_drivers=True) as collection: ... Not all OGR drivers are necessarily enabled in every GDAL distribution. The following code snippet lists the drivers included in the GDAL installation used by Fiona: .. code-block:: python from fiona.env import Env with Env() as gdalenv: print(gdalenv.drivers().keys()) MemoryFile and ZipMemoryFile ---------------------------- :py:class:`fiona.io.MemoryFile` and :py:class:`fiona.io.ZipMemoryFile` allow formatted feature collections, even zipped feature collections, to be read or written in memory, with no filesystem access required. For example, you may have a zipped shapefile in a stream of bytes coming from a web upload or download. .. code-block:: pycon >>> data = open('tests/data/coutwildrnp.zip', 'rb').read() >>> len(data) 154006 >>> data[:20] b'PK\x03\x04\x14\x00\x00\x00\x00\x00\xaa~VM\xech\xae\x1e\xec\xab' The feature collection in this stream of bytes can be accessed by wrapping it in an instance of ZipMemoryFile. .. code-block:: pycon >>> from fiona.io import ZipMemoryFile >>> with ZipMemoryFile(data) as zip: ... with zip.open('coutwildrnp.shp') as collection: ... print(len(collection)) ... print(collection.schema) ... 67 {'properties': {'PERIMETER': 'float:24.15', 'FEATURE2': 'str:80', 'NAME': 'str:80', 'FEATURE1': 'str:80', 'URL': 'str:101', 'AGBUR': 'str:80', 'AREA': 'float:24.15', 'STATE_FIPS': 'str:80', 'WILDRNP020': 'int:10', 'STATE': 'str:80'}, 'geometry': 'Polygon'} *New in 1.8.0* Fiona command line interface ============================ Fiona comes with a command line interface called "fio". See the `CLI Documentation `__ for detailed usage instructions. Final Notes =========== This manual is a work in progress and will grow and improve with Fiona. Questions and suggestions are very welcome. Please feel free to use the `issue tracker `__ or email the author directly. Do see the `README `__ for installation instructions and information about supported versions of Python and other software dependencies. Fiona would not be possible without the `contributions of other developers `__, especially Frank Warmerdam and Even Rouault, the developers of GDAL/OGR; and Mike Weisman, who saved Fiona from neglect and obscurity. References ========== .. [Kent1978] William Kent, Data and Reality, North Holland, 1978. .. [ESRI1998] ESRI Shapefile Technical Description. July 1998. https://www.esri.com/library/whitepapers/pdfs/shapefile.pdf .. [GeoJSON] https://geojson.org .. [JSON] https://www.ietf.org/rfc/rfc4627 .. [SFA] https://en.wikipedia.org/wiki/Simple_feature_access Fiona-1.10.1/docs/modules.rst000066400000000000000000000000441467206072700157710ustar00rootroot00000000000000fiona ===== .. toctree:: fiona Fiona-1.10.1/environment.yml000066400000000000000000000002511467206072700157260ustar00rootroot00000000000000name: _fiona channels: - conda-forge - defaults dependencies: - pip - python=3.9.* - libgdal=3.4.* - cython=3 - sphinx-click - sphinx-rtd-theme - pip: - jinja2==3.0.3 Fiona-1.10.1/examples/000077500000000000000000000000001467206072700144575ustar00rootroot00000000000000Fiona-1.10.1/examples/open.py000066400000000000000000000051101467206072700157670ustar00rootroot00000000000000import fiona # This module contains examples of opening files to get feature collections in # different ways. # # It is meant to be run from the distribution root, the directory containing # setup.py. # # A ``path`` is always the ``open()`` function's first argument. It can be # absolute or relative to the working directory. It is the only positional # argument, though it is conventional to use the mode as a 2nd positional # argument. # 1. Opening a file with a single data layer (shapefiles, etc). # # args: path, mode # kwds: none # # The relative path to a file on the filesystem is given and its single layer # is selected implicitly (a shapefile has a single layer). The file is opened # for reading (mode 'r'), but since this is the default, we'll omit it in # following examples. with fiona.open('docs/data/test_uk.shp', 'r') as c: assert len(c) == 48 # 2. Opening a file with explicit layer selection (FileGDB, etc). # # args: path # kwds: layer # # Same as above but layer specified explicitly by name.. with fiona.open('docs/data/test_uk.shp', layer='test_uk') as c: assert len(c) == 48 # 3. Opening a directory for access to a single file. # # args: path # kwds: layer # # Same as above but using the path to the directory containing the shapefile, # specified explicitly by name. with fiona.open('docs/data', layer='test_uk') as c: assert len(c) == 48 # 4. Opening a single file within a zip archive. # # args: path # kwds: vfs # # Open a file given its absolute path within a virtual filesystem. The VFS # is given an Apache Commons VFS identifier. It may contain either an absolute # path or a path relative to the working directory. # # Example archive: # # $ unzip -l docs/data/test_uk.zip # Archive: docs/data/test_uk.zip # Length Date Time Name # -------- ---- ---- ---- # 10129 04-08-13 20:49 test_uk.dbf # 143 04-08-13 20:49 test_uk.prj # 65156 04-08-13 20:49 test_uk.shp # 484 04-08-13 20:49 test_uk.shx # -------- ------- # 75912 4 files with fiona.open('/test_uk.shp', vfs='zip://docs/data/test_uk.zip') as c: assert len(c) == 48 # 5. Opening a directory within a zip archive to select a layer. # # args: path # kwds: layer, vfs # # The most complicated case. As above, but specifying the root directory within # the virtual filesystem as the path and the layer by name (combination of # 4 and 3). It ought to be possible to open a file geodatabase within a zip # file like this. with fiona.open('/', layer='test_uk', vfs='zip://docs/data/test_uk.zip') as c: assert len(c) == 48 Fiona-1.10.1/examples/orient-ccw.py000066400000000000000000000040441467206072700171050ustar00rootroot00000000000000# An example of flipping feature polygons right side up. import datetime import logging import sys import fiona logging.basicConfig(stream=sys.stderr, level=logging.INFO) def signed_area(coords): """Return the signed area enclosed by a ring using the linear time algorithm at http://www.cgafaq.info/wiki/Polygon_Area. A value >= 0 indicates a counter-clockwise oriented ring. """ xs, ys = map(list, zip(*coords)) xs.append(xs[1]) ys.append(ys[1]) return sum(xs[i]*(ys[i+1]-ys[i-1]) for i in range(1, len(coords)))/2.0 with fiona.open('docs/data/test_uk.shp', 'r') as source: # Copy the source schema and add two new properties. schema = source.schema.copy() schema['properties']['s_area'] = 'float' schema['properties']['timestamp'] = 'str' # Create a sink for processed features with the same format and # coordinate reference system as the source. with fiona.open( 'oriented-ccw.shp', 'w', driver=source.driver, schema=schema, crs=source.crs ) as sink: for f in source: try: # If any feature's polygon is facing "down" (has rings # wound clockwise), its rings will be reordered to flip # it "up". g = f['geometry'] assert g['type'] == 'Polygon' rings = g['coordinates'] sa = sum(signed_area(r) for r in rings) if sa < 0.0: rings = [r[::-1] for r in rings] g['coordinates'] = rings f['geometry'] = g # Add the signed area of the polygon and a timestamp # to the feature properties map. f['properties'].update( s_area=sa, timestamp=datetime.datetime.now().isoformat() ) sink.write(f) except Exception as e: logging.exception("Error processing feature %s:", f['id']) Fiona-1.10.1/examples/with-descartes-functional.py000066400000000000000000000011571467206072700221230ustar00rootroot00000000000000# Making maps with reduce() from matplotlib import pyplot from descartes import PolygonPatch import fiona BLUE = '#6699cc' def render(fig, rec): """Given matplotlib axes and a record, adds the record as a patch and returns the axes so that reduce() can accumulate more patches.""" fig.gca().add_patch( PolygonPatch(rec['geometry'], fc=BLUE, ec=BLUE, alpha=0.5, zorder=2)) return fig with fiona.open('docs/data/test_uk.shp', 'r') as source: fig = reduce(render, source, pyplot.figure(figsize=(8, 8))) fig.gca().autoscale(tight=False) fig.savefig('with-descartes-functional.png') Fiona-1.10.1/examples/with-descartes.py000066400000000000000000000013321467206072700177560ustar00rootroot00000000000000import subprocess from matplotlib import pyplot from descartes import PolygonPatch import fiona # Set up the figure and axes. BLUE = '#6699cc' fig = pyplot.figure(1, figsize=(6, 6), dpi=90) ax = fig.add_subplot(111) with fiona.drivers(): # For each feature in the collection, add a patch to the axes. with fiona.open('docs/data/test_uk.shp', 'r') as input: for f in input: ax.add_patch( PolygonPatch( f['geometry'], fc=BLUE, ec=BLUE, alpha=0.5, zorder=2 )) # Should be able to get extents from the collection in a future version # of Fiona. ax.set_xlim(-9.25, 2.75) ax.set_ylim(49.5, 61.5) fig.savefig('test_uk.png') subprocess.call(['open', 'test_uk.png']) Fiona-1.10.1/examples/with-pyproj.py000066400000000000000000000022121467206072700173220ustar00rootroot00000000000000import logging import sys from pyproj import Proj, transform import fiona from fiona.crs import from_epsg logging.basicConfig(stream=sys.stderr, level=logging.INFO) with fiona.open('docs/data/test_uk.shp', 'r') as source: sink_schema = source.schema.copy() p_in = Proj(source.crs) with fiona.open( 'with-pyproj.shp', 'w', crs=from_epsg(27700), driver=source.driver, schema=sink_schema, ) as sink: p_out = Proj(sink.crs) for f in source: try: assert f['geometry']['type'] == "Polygon" new_coords = [] for ring in f['geometry']['coordinates']: x2, y2 = transform(p_in, p_out, *zip(*ring)) new_coords.append(zip(x2, y2)) f['geometry']['coordinates'] = new_coords sink.write(f) except Exception as e: # Writing uncleanable features to a different shapefile # is another option. logging.exception("Error transforming feature %s:", f['id']) Fiona-1.10.1/examples/with-shapely.py000066400000000000000000000020341467206072700174460ustar00rootroot00000000000000import logging import sys from shapely.geometry import mapping, shape import fiona logging.basicConfig(stream=sys.stderr, level=logging.INFO) with fiona.open('docs/data/test_uk.shp', 'r') as source: # **source.meta is a shortcut to get the crs, driver, and schema # keyword arguments from the source Collection. with fiona.open( 'with-shapely.shp', 'w', **source.meta) as sink: for f in source: try: geom = shape(f['geometry']) if not geom.is_valid: clean = geom.buffer(0.0) assert clean.is_valid assert clean.geom_type == 'Polygon' geom = clean f['geometry'] = mapping(geom) sink.write(f) except Exception as e: # Writing uncleanable features to a different shapefile # is another option. logging.exception("Error cleaning feature %s:", f['id']) Fiona-1.10.1/fiona/000077500000000000000000000000001467206072700137355ustar00rootroot00000000000000Fiona-1.10.1/fiona/__init__.py000066400000000000000000000540701467206072700160540ustar00rootroot00000000000000""" Fiona is OGR's neat, nimble API. Fiona provides a minimal, uncomplicated Python interface to the open source GIS community's most trusted geodata access library and integrates readily with other Python GIS packages such as pyproj, Rtree and Shapely. A Fiona feature is a Python mapping inspired by the GeoJSON format. It has ``id``, ``geometry``, and ``properties`` attributes. The value of ``id`` is a string identifier unique within the feature's parent collection. The ``geometry`` is another mapping with ``type`` and ``coordinates`` keys. The ``properties`` of a feature is another mapping corresponding to its attribute table. Features are read and written using the ``Collection`` class. These ``Collection`` objects are a lot like Python ``file`` objects. A ``Collection`` opened in reading mode serves as an iterator over features. One opened in a writing mode provides a ``write`` method. """ from contextlib import ExitStack import glob import logging import os from pathlib import Path import platform import warnings if platform.system() == "Windows": _whl_dir = os.path.join(os.path.dirname(__file__), ".libs") if os.path.exists(_whl_dir): os.add_dll_directory(_whl_dir) else: if "PATH" in os.environ: for p in os.environ["PATH"].split(os.pathsep): if glob.glob(os.path.join(p, "gdal*.dll")): os.add_dll_directory(os.path.abspath(p)) from fiona._env import ( calc_gdal_version_num, get_gdal_release_name, get_gdal_version_num, get_gdal_version_tuple, ) from fiona._env import driver_count from fiona._path import _ParsedPath, _UnparsedPath, _parse_path, _vsi_path from fiona._show_versions import show_versions from fiona._vsiopener import _opener_registration from fiona.collection import BytesCollection, Collection from fiona.drvsupport import supported_drivers from fiona.env import ensure_env_with_credentials, Env from fiona.errors import FionaDeprecationWarning from fiona.io import MemoryFile from fiona.model import Feature, Geometry, Properties from fiona.ogrext import _bounds, _listdir, _listlayers, _remove, _remove_layer from fiona.schema import FIELD_TYPES_MAP, NAMED_FIELD_TYPES from fiona.vfs import parse_paths as vfs_parse_paths # These modules are imported by fiona.ogrext, but are also import here to # help tools like cx_Freeze find them automatically from fiona import _geometry, _err, rfc3339 import uuid __all__ = [ "Feature", "Geometry", "Properties", "bounds", "listlayers", "listdir", "open", "prop_type", "prop_width", "remove", ] __version__ = "1.10.1" __gdal_version__ = get_gdal_release_name() gdal_version = get_gdal_version_tuple() log = logging.getLogger(__name__) log.addHandler(logging.NullHandler()) @ensure_env_with_credentials def open( fp, mode="r", driver=None, schema=None, crs=None, encoding=None, layer=None, vfs=None, enabled_drivers=None, crs_wkt=None, ignore_fields=None, ignore_geometry=False, include_fields=None, wkt_version=None, allow_unsupported_drivers=False, opener=None, **kwargs ): """Open a collection for read, append, or write In write mode, a driver name such as "ESRI Shapefile" or "GPX" (see OGR docs or ``ogr2ogr --help`` on the command line) and a schema mapping such as: {'geometry': 'Point', 'properties': [('class', 'int'), ('label', 'str'), ('value', 'float')]} must be provided. If a particular ordering of properties ("fields" in GIS parlance) in the written file is desired, a list of (key, value) pairs as above or an ordered dict is required. If no ordering is needed, a standard dict will suffice. A coordinate reference system for collections in write mode can be defined by the ``crs`` parameter. It takes Proj4 style mappings like {'proj': 'longlat', 'ellps': 'WGS84', 'datum': 'WGS84', 'no_defs': True} short hand strings like EPSG:4326 or WKT representations of coordinate reference systems. The drivers used by Fiona will try to detect the encoding of data files. If they fail, you may provide the proper ``encoding``, such as 'Windows-1252' for the original Natural Earth datasets. When the provided path is to a file containing multiple named layers of data, a layer can be singled out by ``layer``. The drivers enabled for opening datasets may be restricted to those listed in the ``enabled_drivers`` parameter. This and the ``driver`` parameter afford much control over opening of files. # Trying only the GeoJSON driver when opening to read, the # following raises ``DataIOError``: fiona.open('example.shp', driver='GeoJSON') # Trying first the GeoJSON driver, then the Shapefile driver, # the following succeeds: fiona.open( 'example.shp', enabled_drivers=['GeoJSON', 'ESRI Shapefile']) Some format drivers permit low-level filtering of fields. Specific fields can be omitted by using the ``ignore_fields`` parameter. Specific fields can be selected, excluding all others, by using the ``include_fields`` parameter. Parameters ---------- fp : URI (str or pathlib.Path), or file-like object A dataset resource identifier or file object. mode : str One of 'r', to read (the default); 'a', to append; or 'w', to write. driver : str In 'w' mode a format driver name is required. In 'r' or 'a' mode this parameter has no effect. schema : dict Required in 'w' mode, has no effect in 'r' or 'a' mode. crs : str or dict Required in 'w' mode, has no effect in 'r' or 'a' mode. encoding : str Name of the encoding used to encode or decode the dataset. layer : int or str The integer index or name of a layer in a multi-layer dataset. vfs : str This is a deprecated parameter. A URI scheme such as "zip://" should be used instead. enabled_drivers : list An optional list of driver names to used when opening a collection. crs_wkt : str An optional WKT representation of a coordinate reference system. ignore_fields : list[str], optional List of field names to ignore on load. include_fields : list[str], optional List of a subset of field names to include on load. ignore_geometry : bool Ignore the geometry on load. wkt_version : fiona.enums.WktVersion or str, optional Version to use to for the CRS WKT. Defaults to GDAL's default (WKT1_GDAL for GDAL 3). allow_unsupported_drivers : bool If set to true do not limit GDAL drivers to set set of known working. opener : callable or obj, optional A custom dataset opener which can serve GDAL's virtual filesystem machinery via Python file-like objects. The underlying file-like object is obtained by calling *opener* with (*fp*, *mode*) or (*fp*, *mode* + "b") depending on the format driver's native mode. *opener* must return a Python file-like object that provides read, seek, tell, and close methods. Note: only one opener at a time per fp, mode pair is allowed. Alternatively, opener may be a filesystem object from a package like fsspec that provides the following methods: isdir(), isfile(), ls(), mtime(), open(), and size(). The exact interface is defined in the fiona._vsiopener._AbstractOpener class. kwargs : mapping Other driver-specific parameters that will be interpreted by the OGR library as layer creation or opening options. Returns ------- Collection Raises ------ DriverError When the selected format driver cannot provide requested capabilities such as ignoring fields. """ if mode == "r" and hasattr(fp, "read"): memfile = MemoryFile(fp.read()) colxn = memfile.open( driver=driver, crs=crs, schema=schema, layer=layer, encoding=encoding, ignore_fields=ignore_fields, include_fields=include_fields, ignore_geometry=ignore_geometry, wkt_version=wkt_version, enabled_drivers=enabled_drivers, allow_unsupported_drivers=allow_unsupported_drivers, **kwargs ) colxn._env.enter_context(memfile) return colxn elif mode == "w" and hasattr(fp, "write"): memfile = MemoryFile() colxn = memfile.open( driver=driver, crs=crs, schema=schema, layer=layer, encoding=encoding, ignore_fields=ignore_fields, include_fields=include_fields, ignore_geometry=ignore_geometry, wkt_version=wkt_version, enabled_drivers=enabled_drivers, allow_unsupported_drivers=allow_unsupported_drivers, crs_wkt=crs_wkt, **kwargs ) colxn._env.enter_context(memfile) # For the writing case we push an extra callback onto the # ExitStack. It ensures that the MemoryFile's contents are # copied to the open file object. def func(*args, **kwds): memfile.seek(0) fp.write(memfile.read()) colxn._env.callback(func) return colxn elif mode == "a" and hasattr(fp, "write"): raise OSError( "Append mode is not supported for datasets in a Python file object." ) # TODO: test for a shared base class or abstract type. elif isinstance(fp, MemoryFile): if mode.startswith("r"): colxn = fp.open( driver=driver, allow_unsupported_drivers=allow_unsupported_drivers, **kwargs ) # Note: FilePath does not support writing and an exception will # result from this. elif mode.startswith("w"): colxn = fp.open( driver=driver, crs=crs, schema=schema, layer=layer, encoding=encoding, ignore_fields=ignore_fields, include_fields=include_fields, ignore_geometry=ignore_geometry, wkt_version=wkt_version, enabled_drivers=enabled_drivers, allow_unsupported_drivers=allow_unsupported_drivers, crs_wkt=crs_wkt, **kwargs ) return colxn # At this point, the fp argument is a string or path-like object # which can be converted to a string. else: stack = ExitStack() if hasattr(fp, "path") and hasattr(fp, "fs"): log.debug("Detected fp is an OpenFile: fp=%r", fp) raw_dataset_path = fp.path opener = fp.fs.open else: raw_dataset_path = os.fspath(fp) try: if opener: log.debug("Registering opener: raw_dataset_path=%r, opener=%r", raw_dataset_path, opener) vsi_path_ctx = _opener_registration(raw_dataset_path, opener) registered_vsi_path = stack.enter_context(vsi_path_ctx) log.debug("Registered vsi path: registered_vsi_path=%r", registered_vsi_path) path = _UnparsedPath(registered_vsi_path) else: if vfs: warnings.warn( "The vfs keyword argument is deprecated and will be removed in version 2.0.0. Instead, pass a URL that uses a zip or tar (for example) scheme.", FionaDeprecationWarning, stacklevel=2, ) path, scheme, archive = vfs_parse_paths(fp, vfs=vfs) path = _ParsedPath(path, archive, scheme) else: path = _parse_path(fp) if mode in ("a", "r"): colxn = Collection( path, mode, driver=driver, encoding=encoding, layer=layer, ignore_fields=ignore_fields, include_fields=include_fields, ignore_geometry=ignore_geometry, wkt_version=wkt_version, enabled_drivers=enabled_drivers, allow_unsupported_drivers=allow_unsupported_drivers, **kwargs ) elif mode == "w": colxn = Collection( path, mode, crs=crs, driver=driver, schema=schema, encoding=encoding, layer=layer, ignore_fields=ignore_fields, include_fields=include_fields, ignore_geometry=ignore_geometry, wkt_version=wkt_version, enabled_drivers=enabled_drivers, crs_wkt=crs_wkt, allow_unsupported_drivers=allow_unsupported_drivers, **kwargs ) else: raise ValueError("mode string must be one of {'r', 'w', 'a'}") except Exception: stack.close() raise colxn._env = stack return colxn collection = open @ensure_env_with_credentials def remove(path_or_collection, driver=None, layer=None, opener=None): """Delete an OGR data source or one of its layers. If no layer is specified, the entire dataset and all of its layers and associated sidecar files will be deleted. Parameters ---------- path_or_collection : str, pathlib.Path, or Collection The target Collection or its path. opener : callable or obj, optional A custom dataset opener which can serve GDAL's virtual filesystem machinery via Python file-like objects. The underlying file-like object is obtained by calling *opener* with (*fp*, *mode*) or (*fp*, *mode* + "b") depending on the format driver's native mode. *opener* must return a Python file-like object that provides read, seek, tell, and close methods. Note: only one opener at a time per fp, mode pair is allowed. Alternatively, opener may be a filesystem object from a package like fsspec that provides the following methods: isdir(), isfile(), ls(), mtime(), open(), and size(). The exact interface is defined in the fiona._vsiopener._AbstractOpener class. driver : str, optional The name of a driver to be used for deletion, optional. Can usually be detected. layer : str or int, optional The name or index of a specific layer. Returns ------- None Raises ------ DatasetDeleteError If the data source cannot be deleted. """ if isinstance(path_or_collection, Collection): collection = path_or_collection raw_dataset_path = collection.path driver = collection.driver collection.close() else: fp = path_or_collection if hasattr(fp, "path") and hasattr(fp, "fs"): log.debug("Detected fp is an OpenFile: fp=%r", fp) raw_dataset_path = fp.path opener = fp.fs.open else: raw_dataset_path = os.fspath(fp) if opener: log.debug("Registering opener: raw_dataset_path=%r, opener=%r", raw_dataset_path, opener) with _opener_registration(raw_dataset_path, opener) as registered_vsi_path: log.debug("Registered vsi path: registered_vsi_path=%r", registered_vsi_path) if layer is None: _remove(registered_vsi_path, driver) else: _remove_layer(registered_vsi_path, layer, driver) else: pobj = _parse_path(raw_dataset_path) if layer is None: _remove(_vsi_path(pobj), driver) else: _remove_layer(_vsi_path(pobj), layer, driver) @ensure_env_with_credentials def listdir(fp, opener=None): """Lists the datasets in a directory or archive file. Archive files must be prefixed like "zip://" or "tar://". Parameters ---------- fp : str or pathlib.Path Directory or archive path. opener : callable or obj, optional A custom dataset opener which can serve GDAL's virtual filesystem machinery via Python file-like objects. The underlying file-like object is obtained by calling *opener* with (*fp*, *mode*) or (*fp*, *mode* + "b") depending on the format driver's native mode. *opener* must return a Python file-like object that provides read, seek, tell, and close methods. Note: only one opener at a time per fp, mode pair is allowed. Alternatively, opener may be a filesystem object from a package like fsspec that provides the following methods: isdir(), isfile(), ls(), mtime(), open(), and size(). The exact interface is defined in the fiona._vsiopener._AbstractOpener class. Returns ------- list of str A list of datasets. Raises ------ TypeError If the input is not a str or Path. """ if hasattr(fp, "path") and hasattr(fp, "fs"): log.debug("Detected fp is an OpenFile: fp=%r", fp) raw_dataset_path = fp.path opener = fp.fs.open else: raw_dataset_path = os.fspath(fp) if opener: log.debug("Registering opener: raw_dataset_path=%r, opener=%r", raw_dataset_path, opener) with _opener_registration(raw_dataset_path, opener) as registered_vsi_path: log.debug("Registered vsi path: registered_vsi_path=%r", registered_vsi_path) return _listdir(registered_vsi_path) else: pobj = _parse_path(raw_dataset_path) return _listdir(_vsi_path(pobj)) @ensure_env_with_credentials def listlayers(fp, opener=None, vfs=None, **kwargs): """Lists the layers (collections) in a dataset. Archive files must be prefixed like "zip://" or "tar://". Parameters ---------- fp : str, pathlib.Path, or file-like object A dataset identifier or file object containing a dataset. opener : callable or obj, optional A custom dataset opener which can serve GDAL's virtual filesystem machinery via Python file-like objects. The underlying file-like object is obtained by calling *opener* with (*fp*, *mode*) or (*fp*, *mode* + "b") depending on the format driver's native mode. *opener* must return a Python file-like object that provides read, seek, tell, and close methods. Note: only one opener at a time per fp, mode pair is allowed. Alternatively, opener may be a filesystem object from a package like fsspec that provides the following methods: isdir(), isfile(), ls(), mtime(), open(), and size(). The exact interface is defined in the fiona._vsiopener._AbstractOpener class. vfs : str This is a deprecated parameter. A URI scheme such as "zip://" should be used instead. kwargs : dict Dataset opening options and other keyword args. Returns ------- list of str A list of layer name strings. Raises ------ TypeError If the input is not a str, Path, or file object. """ if vfs and not isinstance(vfs, str): raise TypeError(f"invalid vfs: {vfs!r}") if hasattr(fp, 'read'): with MemoryFile(fp.read()) as memfile: return _listlayers(memfile.name, **kwargs) if hasattr(fp, "path") and hasattr(fp, "fs"): log.debug("Detected fp is an OpenFile: fp=%r", fp) raw_dataset_path = fp.path opener = fp.fs.open else: raw_dataset_path = os.fspath(fp) if opener: log.debug("Registering opener: raw_dataset_path=%r, opener=%r", raw_dataset_path, opener) with _opener_registration(raw_dataset_path, opener) as registered_vsi_path: log.debug("Registered vsi path: registered_vsi_path=%r", registered_vsi_path) return _listlayers(registered_vsi_path, **kwargs) else: if vfs: warnings.warn( "The vfs keyword argument is deprecated and will be removed in 2.0. " "Instead, pass a URL that uses a zip or tar (for example) scheme.", FionaDeprecationWarning, stacklevel=2, ) pobj_vfs = _parse_path(vfs) pobj_path = _parse_path(raw_dataset_path) pobj = _ParsedPath(pobj_path.path, pobj_vfs.path, pobj_vfs.scheme) else: pobj = _parse_path(raw_dataset_path) return _listlayers(_vsi_path(pobj), **kwargs) def prop_width(val): """Returns the width of a str type property. Undefined for non-str properties. Parameters ---------- val : str A type:width string from a collection schema. Returns ------- int or None Examples -------- >>> prop_width('str:25') 25 >>> prop_width('str') 80 """ if val.startswith('str'): return int((val.split(":")[1:] or ["80"])[0]) return None def prop_type(text): """Returns a schema property's proper Python type. Parameters ---------- text : str A type name, with or without width. Returns ------- obj A Python class. Examples -------- >>> prop_type('int') >>> prop_type('str:25') """ key = text.split(':')[0] return NAMED_FIELD_TYPES[key].type def drivers(*args, **kwargs): """Returns a context manager with registered drivers. DEPRECATED """ warnings.warn("Use fiona.Env() instead.", FionaDeprecationWarning, stacklevel=2) if driver_count == 0: log.debug("Creating a chief GDALEnv in drivers()") return Env(**kwargs) else: log.debug("Creating a not-responsible GDALEnv in drivers()") return Env(**kwargs) def bounds(ob): """Returns a (minx, miny, maxx, maxy) bounding box. The ``ob`` may be a feature record or geometry.""" geom = ob.get('geometry') or ob return _bounds(geom) Fiona-1.10.1/fiona/_cpl.pxd000066400000000000000000000013351467206072700153710ustar00rootroot00000000000000# Cross-platform API functions. cdef extern from "cpl_conv.h": void * CPLMalloc (size_t) void CPLFree (void *ptr) void CPLSetThreadLocalConfigOption (char *key, char *val) const char *CPLGetConfigOption (char *, char *) cdef extern from "cpl_vsi.h": ctypedef struct VSILFILE: pass int VSIFCloseL (VSILFILE *) VSILFILE * VSIFileFromMemBuffer (const char * filename, unsigned char * data, int data_len, int take_ownership) int VSIUnlink (const char * pathname) ctypedef int OGRErr ctypedef struct OGREnvelope: double MinX double MaxX double MinY double MaxY Fiona-1.10.1/fiona/_crs.pyx000066400000000000000000000044551467206072700154350ustar00rootroot00000000000000"""Extension module supporting crs.py. Calls methods from GDAL's OSR module. """ import logging from fiona cimport _cpl from fiona._err cimport exc_wrap_pointer from fiona._err import CPLE_BaseError from fiona._shim cimport osr_get_name, osr_set_traditional_axis_mapping_strategy from fiona.compat import DICT_TYPES from fiona.errors import CRSError logger = logging.getLogger(__name__) cdef int OAMS_TRADITIONAL_GIS_ORDER = 0 # Export a WKT string from input crs. def crs_to_wkt(crs): """Convert a Fiona CRS object to WKT format""" cdef OGRSpatialReferenceH cogr_srs = NULL cdef char *proj_c = NULL try: cogr_srs = exc_wrap_pointer(OSRNewSpatialReference(NULL)) except CPLE_BaseError as exc: raise CRSError(str(exc)) # check for other CRS classes if hasattr(crs, "to_wkt") and callable(crs.to_wkt): crs = crs.to_wkt() # First, check for CRS strings like "EPSG:3857". if isinstance(crs, str): proj_b = crs.encode('utf-8') proj_c = proj_b OSRSetFromUserInput(cogr_srs, proj_c) elif isinstance(crs, DICT_TYPES): # EPSG is a special case. init = crs.get('init') if init: logger.debug("Init: %s", init) auth, val = init.split(':') if auth.upper() == 'EPSG': logger.debug("Setting EPSG: %s", val) OSRImportFromEPSG(cogr_srs, int(val)) else: params = [] crs['wktext'] = True for k, v in crs.items(): if v is True or (k in ('no_defs', 'wktext') and v): params.append("+%s" % k) else: params.append("+%s=%s" % (k, v)) proj = " ".join(params) logger.debug("PROJ.4 to be imported: %r", proj) proj_b = proj.encode('utf-8') proj_c = proj_b OSRImportFromProj4(cogr_srs, proj_c) else: raise CRSError(f"Invalid input to create CRS: {crs}") osr_set_traditional_axis_mapping_strategy(cogr_srs) OSRExportToWkt(cogr_srs, &proj_c) if proj_c == NULL: raise CRSError(f"Invalid input to create CRS: {crs}") proj_b = proj_c _cpl.CPLFree(proj_c) if not proj_b: raise CRSError(f"Invalid input to create CRS: {crs}") return proj_b.decode('utf-8') Fiona-1.10.1/fiona/_csl.pxd000066400000000000000000000003451467206072700153740ustar00rootroot00000000000000# String API functions. cdef extern from "cpl_string.h": char ** CSLAddNameValue (char **list, char *name, char *value) char ** CSLSetNameValue (char **list, char *name, char *value) void CSLDestroy (char **list) Fiona-1.10.1/fiona/_env.pxd000066400000000000000000000003261467206072700154020ustar00rootroot00000000000000include "gdal.pxi" cdef class ConfigEnv(object): cdef public object options cdef class GDALEnv(ConfigEnv): cdef public object _have_registered_drivers cdef _safe_osr_release(OGRSpatialReferenceH srs) Fiona-1.10.1/fiona/_env.pyx000066400000000000000000000357771467206072700154510ustar00rootroot00000000000000# cython: c_string_type=unicode, c_string_encoding=utf8 """GDAL and OGR driver and configuration management The main thread always utilizes CPLSetConfigOption. Child threads utilize CPLSetThreadLocalConfigOption instead. All threads use CPLGetConfigOption and not CPLGetThreadLocalConfigOption, thus child threads will inherit config options from the main thread unless the option is set to a new value inside the thread. """ from collections import namedtuple import logging import os import os.path import sys import threading from fiona._err cimport exc_wrap_int, exc_wrap_ogrerr from fiona._err import CPLE_BaseError from fiona.errors import EnvError level_map = { 0: 0, 1: logging.DEBUG, 2: logging.WARNING, 3: logging.ERROR, 4: logging.CRITICAL } code_map = { 0: 'CPLE_None', 1: 'CPLE_AppDefined', 2: 'CPLE_OutOfMemory', 3: 'CPLE_FileIO', 4: 'CPLE_OpenFailed', 5: 'CPLE_IllegalArg', 6: 'CPLE_NotSupported', 7: 'CPLE_AssertionFailed', 8: 'CPLE_NoWriteAccess', 9: 'CPLE_UserInterrupt', 10: 'ObjectNull', # error numbers 11-16 are introduced in GDAL 2.1. See # https://github.com/OSGeo/gdal/pull/98. 11: 'CPLE_HttpResponse', 12: 'CPLE_AWSBucketNotFound', 13: 'CPLE_AWSObjectNotFound', 14: 'CPLE_AWSAccessDenied', 15: 'CPLE_AWSInvalidCredentials', 16: 'CPLE_AWSSignatureDoesNotMatch'} log = logging.getLogger(__name__) try: import certifi ca_bundle = certifi.where() os.environ.setdefault("GDAL_CURL_CA_BUNDLE", ca_bundle) os.environ.setdefault("PROJ_CURL_CA_BUNDLE", ca_bundle) except ImportError: pass cdef VSIFilesystemPluginCallbacksStruct* pyopener_plugin = NULL cdef bint is_64bit = sys.maxsize > 2 ** 32 cdef void set_proj_search_path(object path): cdef char **paths = NULL cdef const char *path_c = NULL path_b = path.encode("utf-8") path_c = path_b paths = CSLAddString(paths, path_c) OSRSetPROJSearchPaths(paths) CSLDestroy(paths) cdef _safe_osr_release(OGRSpatialReferenceH srs): """Wrapper to handle OSR release when NULL.""" if srs != NULL: OSRRelease(srs) srs = NULL def calc_gdal_version_num(maj, min, rev): """Calculates the internal gdal version number based on major, minor and revision GDAL Version Information macro changed with GDAL version 1.10.0 (April 2013) """ if (maj, min, rev) >= (1, 10, 0): return int(maj * 1000000 + min * 10000 + rev * 100) else: return int(maj * 1000 + min * 100 + rev * 10) def get_gdal_version_num(): """Return current internal version number of gdal""" return int(GDALVersionInfo("VERSION_NUM")) def get_gdal_release_name(): """Return release name of gdal""" cdef const char *name_c = NULL name_c = GDALVersionInfo("RELEASE_NAME") name = name_c return name GDALVersion = namedtuple("GDALVersion", ["major", "minor", "revision"]) def get_gdal_version_tuple(): """ Calculates gdal version tuple from gdal's internal version number. GDAL Version Information macro changed with GDAL version 1.10.0 (April 2013) """ gdal_version_num = get_gdal_version_num() if gdal_version_num >= calc_gdal_version_num(1, 10, 0): major = gdal_version_num // 1000000 minor = (gdal_version_num - (major * 1000000)) // 10000 revision = (gdal_version_num - (major * 1000000) - (minor * 10000)) // 100 return GDALVersion(major, minor, revision) else: major = gdal_version_num // 1000 minor = (gdal_version_num - (major * 1000)) // 100 revision = (gdal_version_num - (major * 1000) - (minor * 100)) // 10 return GDALVersion(major, minor, revision) def get_proj_version_tuple(): """ Returns proj version tuple """ cdef int major = 0 cdef int minor = 0 cdef int patch = 0 OSRGetPROJVersion(&major, &minor, &patch) return (major, minor, patch) cdef void log_error(CPLErr err_class, int err_no, const char* msg) with gil: """Send CPL debug messages and warnings to Python's logger.""" log = logging.getLogger(__name__) if err_no in code_map: log.log(level_map[err_class], "%s", msg) else: log.info("Unknown error number %r.", err_no) # Definition of GDAL callback functions, one for Windows and one for # other platforms. Each calls log_error(). IF UNAME_SYSNAME == "Windows": cdef void __stdcall logging_error_handler(CPLErr err_class, int err_no, const char* msg) with gil: log_error(err_class, err_no, msg) ELSE: cdef void logging_error_handler(CPLErr err_class, int err_no, const char* msg) with gil: log_error(err_class, err_no, msg) def driver_count(): """Return the count of all drivers""" return GDALGetDriverCount() + OGRGetDriverCount() cpdef get_gdal_config(key, normalize=True): """Get the value of a GDAL configuration option. When requesting ``GDAL_CACHEMAX`` the value is returned unaltered. Parameters ---------- key : str Name of config option. normalize : bool, optional Convert values of ``"ON"'`` and ``"OFF"`` to ``True`` and ``False``. """ key = key.encode('utf-8') # GDAL_CACHEMAX is a special case if key.lower() == b'gdal_cachemax': if is_64bit: return GDALGetCacheMax64() else: return GDALGetCacheMax() else: val = CPLGetConfigOption(key, NULL) if not val: return None elif not normalize: return val elif val.isdigit(): return int(val) else: if val == 'ON': return True elif val == 'OFF': return False else: return val cpdef set_gdal_config(key, val, normalize=True): """Set a GDAL configuration option's value. Parameters ---------- key : str Name of config option. normalize : bool, optional Convert ``True`` to `"ON"` and ``False`` to `"OFF"``. """ key = key.encode('utf-8') # GDAL_CACHEMAX is a special case if key.lower() == b'gdal_cachemax': if is_64bit: GDALSetCacheMax64(val) else: GDALSetCacheMax(val) return elif normalize and isinstance(val, bool): val = ('ON' if val and val else 'OFF').encode('utf-8') else: # Value could be an int val = str(val).encode('utf-8') if isinstance(threading.current_thread(), threading._MainThread): CPLSetConfigOption(key, val) else: CPLSetThreadLocalConfigOption(key, val) cpdef del_gdal_config(key): """Delete a GDAL configuration option. Parameters ---------- key : str Name of config option. """ key = key.encode('utf-8') if isinstance(threading.current_thread(), threading._MainThread): CPLSetConfigOption(key, NULL) else: CPLSetThreadLocalConfigOption(key, NULL) cdef class ConfigEnv(object): """Configuration option management""" def __init__(self, **options): self.options = options.copy() self.update_config_options(**self.options) def update_config_options(self, **kwargs): """Update GDAL config options.""" for key, val in kwargs.items(): set_gdal_config(key, val) self.options[key] = val def clear_config_options(self): """Clear GDAL config options.""" while self.options: key, val = self.options.popitem() del_gdal_config(key) def get_config_options(self): return {k: get_gdal_config(k) for k in self.options} class GDALDataFinder(object): """Finds GDAL data files Note: this is not part of the 1.8.x public API. """ def find_file(self, basename): """Returns path of a GDAL data file or None Parameters ---------- basename : str Basename of a data file such as "header.dxf" Returns ------- str (on success) or None (on failure) """ cdef const char *path_c = NULL basename_b = basename.encode('utf-8') path_c = CPLFindFile("gdal", basename_b) if path_c == NULL: return None else: path = path_c return path def search(self, prefix=None): """Returns GDAL data directory Note well that os.environ is not consulted. Returns ------- str or None """ path = self.search_wheel(prefix or __file__) if not path: path = self.search_prefix(prefix or sys.prefix) if not path: path = self.search_debian(prefix or sys.prefix) return path def search_wheel(self, prefix=None): """Check wheel location""" if prefix is None: prefix = __file__ datadir = os.path.abspath(os.path.join(os.path.dirname(prefix), "gdal_data")) return datadir if os.path.exists(os.path.join(datadir, 'header.dxf')) else None def search_prefix(self, prefix=sys.prefix): """Check sys.prefix location""" datadir = os.path.join(prefix, 'share', 'gdal') return datadir if os.path.exists(os.path.join(datadir, 'header.dxf')) else None def search_debian(self, prefix=sys.prefix): """Check Debian locations""" gdal_release_name = GDALVersionInfo("RELEASE_NAME") datadir = os.path.join(prefix, 'share', 'gdal', '{}.{}'.format(*gdal_release_name.split('.')[:2])) return datadir if os.path.exists(os.path.join(datadir, 'header.dxf')) else None class PROJDataFinder(object): """Finds PROJ data files Note: this is not part of the public 1.8.x API. """ def has_data(self): """Returns True if PROJ's data files can be found Returns ------- bool """ cdef OGRSpatialReferenceH osr = OSRNewSpatialReference(NULL) try: exc_wrap_ogrerr(exc_wrap_int(OSRImportFromEPSG(osr, 4326))) except CPLE_BaseError: return False else: return True finally: _safe_osr_release(osr) def search(self, prefix=None): """Returns PROJ data directory Note well that os.environ is not consulted. Returns ------- str or None """ path = self.search_wheel(prefix or __file__) if not path: path = self.search_prefix(prefix or sys.prefix) return path def search_wheel(self, prefix=None): """Check wheel location""" if prefix is None: prefix = __file__ datadir = os.path.abspath(os.path.join(os.path.dirname(prefix), "proj_data")) return datadir if os.path.exists(datadir) else None def search_prefix(self, prefix=sys.prefix): """Check sys.prefix location""" datadir = os.path.join(prefix, 'share', 'proj') return datadir if os.path.exists(datadir) else None cdef class GDALEnv(ConfigEnv): """Configuration and driver management""" def __init__(self, **options): super().__init__(**options) self._have_registered_drivers = False def start(self): CPLPushErrorHandler(logging_error_handler) # The outer if statement prevents each thread from acquiring a # lock when the environment starts, and the inner avoids a # potential race condition. if not self._have_registered_drivers: with threading.Lock(): if not self._have_registered_drivers: GDALAllRegister() OGRRegisterAll() if 'GDAL_DATA' in os.environ: log.debug("GDAL_DATA found in environment.") self.update_config_options(GDAL_DATA=os.environ['GDAL_DATA']) else: path = GDALDataFinder().search_wheel() if path: log.debug("GDAL data found in package: path=%r.", path) self.update_config_options(GDAL_DATA=path) # See https://github.com/mapbox/rasterio/issues/1631. elif GDALDataFinder().find_file("header.dxf"): log.debug("GDAL data files are available at built-in paths.") else: path = GDALDataFinder().search() if path: log.debug("GDAL data found in other locations: path=%r.", path) self.update_config_options(GDAL_DATA=path) if 'PROJ_DATA' in os.environ: # PROJ 9.1+ log.debug("PROJ_DATA found in environment.") path = os.environ["PROJ_DATA"] set_proj_data_search_path(path) elif 'PROJ_LIB' in os.environ: # PROJ < 9.1 log.debug("PROJ_LIB found in environment.") path = os.environ["PROJ_LIB"] set_proj_data_search_path(path) else: path = PROJDataFinder().search_wheel() if path: log.debug("PROJ data found in package: path=%r.", path) set_proj_data_search_path(path) elif PROJDataFinder().has_data(): log.debug("PROJ data files are available at built-in paths.") else: path = PROJDataFinder().search() if path: log.debug("PROJ data found in other locations: path=%r.", path) set_proj_data_search_path(path) if driver_count() == 0: CPLPopErrorHandler() raise ValueError("Drivers not registered.") # Flag the drivers as registered, otherwise every thread # will acquire a threadlock every time a new environment # is started rather than just whenever the first thread # actually makes it this far. self._have_registered_drivers = True def stop(self): # NB: do not restore the CPL error handler to its default # state here. If you do, log messages will be written to stderr # by GDAL instead of being sent to Python's logging module. CPLPopErrorHandler() def drivers(self): cdef OGRSFDriverH driver = NULL cdef int i result = {} for i in range(OGRGetDriverCount()): drv = OGRGetDriver(i) key = OGR_Dr_GetName(drv) val = OGR_Dr_GetName(drv) result[key] = val return result def set_proj_data_search_path(path): """Set PROJ data search path""" set_proj_search_path(path) Fiona-1.10.1/fiona/_err.pxd000066400000000000000000000007001467206072700153760ustar00rootroot00000000000000include "gdal.pxi" from libc.stdio cimport * cdef get_last_error_msg() cdef int exc_wrap_int(int retval) except -1 cdef OGRErr exc_wrap_ogrerr(OGRErr retval) except -1 cdef void *exc_wrap_pointer(void *ptr) except NULL cdef VSILFILE *exc_wrap_vsilfile(VSILFILE *f) except NULL cdef class StackChecker: cdef object error_stack cdef int exc_wrap_int(self, int retval) except -1 cdef void *exc_wrap_pointer(self, void *ptr) except NULL Fiona-1.10.1/fiona/_err.pyx000066400000000000000000000273301467206072700154330ustar00rootroot00000000000000"""fiona._err Transformation of GDAL C API errors to Python exceptions using Python's ``with`` statement and an error-handling context manager class. The ``cpl_errs`` error-handling context manager is intended for use in Rasterio's Cython code. When entering the body of a ``with`` statement, the context manager clears GDAL's error stack. On exit, the context manager pops the last error off the stack and raises an appropriate Python exception. It's otherwise pretty difficult to do this kind of thing. I couldn't make it work with a CPL error handler, Cython's C code swallows exceptions raised from C callbacks. When used to wrap a call to open a PNG in update mode with cpl_errs: cdef void *hds = GDALOpen('file.png', 1) if hds == NULL: raise ValueError("NULL dataset") the ValueError of last resort never gets raised because the context manager raises a more useful and informative error: Traceback (most recent call last): File "/Users/sean/code/rasterio/scripts/rio_insp", line 65, in with rasterio.open(args.src, args.mode) as src: File "/Users/sean/code/rasterio/rasterio/__init__.py", line 111, in open s.start() ValueError: The PNG driver does not support update access to existing datasets. """ import contextlib from contextvars import ContextVar from enum import IntEnum from itertools import zip_longest import logging log = logging.getLogger(__name__) _ERROR_STACK = ContextVar("error_stack") _ERROR_STACK.set([]) # Python exceptions expressing the CPL error numbers. class CPLE_BaseError(Exception): """Base CPL error class Exceptions deriving from this class are intended for use only in Rasterio's Cython code. Let's not expose API users to them. """ def __init__(self, error, errno, errmsg): self.error = error self.errno = errno self.errmsg = errmsg def __str__(self): return str(self.errmsg) @property def args(self): return self.error, self.errno, self.errmsg class CPLE_AppDefinedError(CPLE_BaseError): pass class CPLE_OutOfMemoryError(CPLE_BaseError): pass class CPLE_FileIOError(CPLE_BaseError): pass class CPLE_OpenFailedError(CPLE_BaseError): pass class CPLE_IllegalArgError(CPLE_BaseError): pass class CPLE_NotSupportedError(CPLE_BaseError): pass class CPLE_AssertionFailedError(CPLE_BaseError): pass class CPLE_NoWriteAccessError(CPLE_BaseError): pass class CPLE_UserInterruptError(CPLE_BaseError): pass class ObjectNullError(CPLE_BaseError): pass class CPLE_HttpResponseError(CPLE_BaseError): pass class CPLE_AWSBucketNotFoundError(CPLE_BaseError): pass class CPLE_AWSObjectNotFoundError(CPLE_BaseError): pass class CPLE_AWSAccessDeniedError(CPLE_BaseError): pass class CPLE_AWSInvalidCredentialsError(CPLE_BaseError): pass class CPLE_AWSSignatureDoesNotMatchError(CPLE_BaseError): pass class CPLE_AWSError(CPLE_BaseError): pass class FionaNullPointerError(CPLE_BaseError): """ Returned from exc_wrap_pointer when a NULL pointer is passed, but no GDAL error was raised. """ pass class FionaCPLError(CPLE_BaseError): """ Returned from exc_wrap_int when a error code is returned, but no GDAL error was set. """ pass cdef dict _LEVEL_MAP = { 0: 0, 1: logging.DEBUG, 2: logging.WARNING, 3: logging.ERROR, 4: logging.CRITICAL } # Map of GDAL error numbers to the Python exceptions. exception_map = { 1: CPLE_AppDefinedError, 2: CPLE_OutOfMemoryError, 3: CPLE_FileIOError, 4: CPLE_OpenFailedError, 5: CPLE_IllegalArgError, 6: CPLE_NotSupportedError, 7: CPLE_AssertionFailedError, 8: CPLE_NoWriteAccessError, 9: CPLE_UserInterruptError, 10: ObjectNullError, # error numbers 11-16 are introduced in GDAL 2.1. See # https://github.com/OSGeo/gdal/pull/98. 11: CPLE_HttpResponseError, 12: CPLE_AWSBucketNotFoundError, 13: CPLE_AWSObjectNotFoundError, 14: CPLE_AWSAccessDeniedError, 15: CPLE_AWSInvalidCredentialsError, 16: CPLE_AWSSignatureDoesNotMatchError, 17: CPLE_AWSError } cdef dict _CODE_MAP = { 0: 'CPLE_None', 1: 'CPLE_AppDefined', 2: 'CPLE_OutOfMemory', 3: 'CPLE_FileIO', 4: 'CPLE_OpenFailed', 5: 'CPLE_IllegalArg', 6: 'CPLE_NotSupported', 7: 'CPLE_AssertionFailed', 8: 'CPLE_NoWriteAccess', 9: 'CPLE_UserInterrupt', 10: 'ObjectNull', 11: 'CPLE_HttpResponse', 12: 'CPLE_AWSBucketNotFound', 13: 'CPLE_AWSObjectNotFound', 14: 'CPLE_AWSAccessDenied', 15: 'CPLE_AWSInvalidCredentials', 16: 'CPLE_AWSSignatureDoesNotMatch', 17: 'CPLE_AWSError' } # CPL Error types as an enum. class GDALError(IntEnum): none = CE_None debug = CE_Debug warning = CE_Warning failure = CE_Failure fatal = CE_Fatal cdef class GDALErrCtxManager: """A manager for GDAL error handling contexts.""" def __enter__(self): CPLErrorReset() return self def __exit__(self, exc_type=None, exc_val=None, exc_tb=None): cdef int err_type = CPLGetLastErrorType() cdef int err_no = CPLGetLastErrorNo() cdef const char *msg = CPLGetLastErrorMsg() # TODO: warn for err_type 2? if err_type >= 2: raise exception_map[err_no](err_type, err_no, msg) cdef inline object exc_check(): """Checks GDAL error stack for fatal or non-fatal errors Returns ------- An Exception, SystemExit, or None """ cdef const char *msg_c = NULL err_type = CPLGetLastErrorType() err_no = CPLGetLastErrorNo() err_msg = CPLGetLastErrorMsg() if err_msg == NULL: msg = "No error message." else: # Reformat messages. msg_b = err_msg msg = msg_b.decode('utf-8') msg = msg.replace("`", "'") msg = msg.replace("\n", " ") if err_type == 3: CPLErrorReset() return exception_map.get( err_no, CPLE_BaseError)(err_type, err_no, msg) if err_type == 4: return SystemExit(f"Fatal error: {(err_type, err_no, msg)}") else: return cdef get_last_error_msg(): """Checks GDAL error stack for the latest error message Returns ------- An error message or empty string """ err_msg = CPLGetLastErrorMsg() if err_msg != NULL: # Reformat messages. msg_b = err_msg msg = msg_b.decode('utf-8') msg = msg.replace("`", "'") msg = msg.replace("\n", " ") else: msg = "" return msg cdef int exc_wrap_int(int err) except -1: """Wrap a GDAL/OGR function that returns CPLErr or OGRErr (int) Raises a Rasterio exception if a non-fatal error has be set. """ if err: exc = exc_check() if exc: raise exc else: raise FionaCPLError(-1, -1, "The wrapped function returned an error code, but no error message was set.") return err cdef OGRErr exc_wrap_ogrerr(OGRErr err) except -1: """Wrap a function that returns OGRErr but does not use the CPL error stack. """ if err == 0: return err exc = exc_check() if exc: raise exc raise CPLE_BaseError(3, err, f"OGR Error code {err}") cdef void *exc_wrap_pointer(void *ptr) except NULL: """Wrap a GDAL/OGR function that returns GDALDatasetH etc (void *) Raises a Rasterio exception if a non-fatal error has be set. """ if ptr == NULL: exc = exc_check() if exc: raise exc else: # null pointer was passed, but no error message from GDAL raise FionaNullPointerError(-1, -1, "NULL pointer error") return ptr cdef VSILFILE *exc_wrap_vsilfile(VSILFILE *f) except NULL: """Wrap a GDAL/OGR function that returns GDALDatasetH etc (void *) Raises a Rasterio exception if a non-fatal error has be set. """ if f == NULL: exc = exc_check() if exc: raise exc return f cpl_errs = GDALErrCtxManager() cdef class StackChecker: def __init__(self, error_stack=None): self.error_stack = error_stack or {} cdef int exc_wrap_int(self, int err) except -1: """Wrap a GDAL/OGR function that returns CPLErr (int). Raises a Rasterio exception if a non-fatal error has be set. """ if err: stack = self.error_stack.get() for error, cause in zip_longest(stack[::-1], stack[::-1][1:]): if error is not None and cause is not None: error.__cause__ = cause if stack: last = stack.pop() if last is not None: raise last return err cdef void *exc_wrap_pointer(self, void *ptr) except NULL: """Wrap a GDAL/OGR function that returns a pointer. Raises a Rasterio exception if a non-fatal error has be set. """ if ptr == NULL: stack = self.error_stack.get() for error, cause in zip_longest(stack[::-1], stack[::-1][1:]): if error is not None and cause is not None: error.__cause__ = cause if stack: last = stack.pop() if last is not None: raise last return ptr cdef void log_error( CPLErr err_class, int err_no, const char* msg, ) noexcept with gil: """Send CPL errors to Python's logger. Because this function is called by GDAL with no Python context, we can't propagate exceptions that we might raise here. They'll be ignored. """ if err_no in _CODE_MAP: # We've observed that some GDAL functions may emit multiple # ERROR level messages and yet succeed. We want to see those # messages in our log file, but not at the ERROR level. We # turn the level down to INFO. if err_class == 3: log.info( "GDAL signalled an error: err_no=%r, msg=%r", err_no, msg.decode("utf-8") ) elif err_no == 0: log.log(_LEVEL_MAP[err_class], "%s", msg.decode("utf-8")) else: log.log(_LEVEL_MAP[err_class], "%s:%s", _CODE_MAP[err_no], msg.decode("utf-8")) else: log.info("Unknown error number %r", err_no) IF UNAME_SYSNAME == "Windows": cdef void __stdcall chaining_error_handler( CPLErr err_class, int err_no, const char* msg ) noexcept with gil: global _ERROR_STACK log_error(err_class, err_no, msg) if err_class == 3: stack = _ERROR_STACK.get() stack.append( exception_map.get(err_no, CPLE_BaseError)(err_class, err_no, msg.decode("utf-8")), ) _ERROR_STACK.set(stack) ELSE: cdef void chaining_error_handler( CPLErr err_class, int err_no, const char* msg ) noexcept with gil: global _ERROR_STACK log_error(err_class, err_no, msg) if err_class == 3: stack = _ERROR_STACK.get() stack.append( exception_map.get(err_no, CPLE_BaseError)(err_class, err_no, msg.decode("utf-8")), ) _ERROR_STACK.set(stack) @contextlib.contextmanager def stack_errors(): # TODO: better name? # Note: this manager produces one chain of errors and thus assumes # that no more than one GDAL function is called. CPLErrorReset() global _ERROR_STACK _ERROR_STACK.set([]) # chaining_error_handler (better name a TODO) records GDAL errors # in the order they occur and converts to exceptions. CPLPushErrorHandlerEx(chaining_error_handler, NULL) # Run code in the `with` block. yield StackChecker(_ERROR_STACK) CPLPopErrorHandler() _ERROR_STACK.set([]) CPLErrorReset() Fiona-1.10.1/fiona/_geometry.pxd000066400000000000000000000110731467206072700164460ustar00rootroot00000000000000# Geometry API functions. ctypedef int OGRErr cdef extern from "ogr_core.h": ctypedef enum OGRwkbGeometryType: wkbUnknown wkbPoint wkbLineString wkbPolygon wkbMultiPoint wkbMultiLineString wkbMultiPolygon wkbGeometryCollection wkbCircularString wkbCompoundCurve wkbCurvePolygon wkbMultiCurve wkbMultiSurface wkbCurve wkbSurface wkbPolyhedralSurface wkbTIN wkbTriangle wkbNone wkbLinearRing wkbCircularStringZ wkbCompoundCurveZ wkbCurvePolygonZ wkbMultiCurveZ wkbMultiSurfaceZ wkbCurveZ wkbSurfaceZ wkbPolyhedralSurfaceZ wkbTINZ wkbTriangleZ wkbPointM wkbLineStringM wkbPolygonM wkbMultiPointM wkbMultiLineStringM wkbMultiPolygonM wkbGeometryCollectionM wkbCircularStringM wkbCompoundCurveM wkbCurvePolygonM wkbMultiCurveM wkbMultiSurfaceM wkbCurveM wkbSurfaceM wkbPolyhedralSurfaceM wkbTINM wkbTriangleM wkbPointZM wkbLineStringZM wkbPolygonZM wkbMultiPointZM wkbMultiLineStringZM wkbMultiPolygonZM wkbGeometryCollectionZM wkbCircularStringZM wkbCompoundCurveZM wkbCurvePolygonZM wkbMultiCurveZM wkbMultiSurfaceZM wkbCurveZM wkbSurfaceZM wkbPolyhedralSurfaceZM wkbTINZM wkbTriangleZM wkbPoint25D wkbLineString25D wkbPolygon25D wkbMultiPoint25D wkbMultiLineString25D wkbMultiPolygon25D wkbGeometryCollection25D ctypedef struct OGREnvelope: double MinX double MaxX double MinY double MaxY cdef extern from "ogr_api.h": OGRErr OGR_G_AddGeometryDirectly (void *geometry, void *part) void OGR_G_AddPoint (void *geometry, double x, double y, double z) void OGR_G_AddPoint_2D (void *geometry, double x, double y) void OGR_G_CloseRings (void *geometry) void * OGR_G_CreateGeometry (OGRwkbGeometryType wkbtypecode) void OGR_G_DestroyGeometry (void *geometry) unsigned char * OGR_G_ExportToJson (void *geometry) void OGR_G_ExportToWkb (void *geometry, int endianness, char *buffer) int OGR_G_GetCoordinateDimension (void *geometry) int OGR_G_GetGeometryCount (void *geometry) unsigned char * OGR_G_GetGeometryName (void *geometry) int OGR_G_GetGeometryType (void *geometry) void * OGR_G_GetGeometryRef (void *geometry, int n) int OGR_G_GetPointCount (void *geometry) double OGR_G_GetX (void *geometry, int n) double OGR_G_GetY (void *geometry, int n) double OGR_G_GetZ (void *geometry, int n) OGRErr OGR_G_ImportFromWkb (void *geometry, unsigned char *bytes, int nbytes) int OGR_G_WkbSize (void *geometry) cdef class GeomBuilder: cdef object ndims cdef list _buildCoords(self, void *geom) cdef dict _buildPoint(self, void *geom) cdef dict _buildLineString(self, void *geom) cdef dict _buildLinearRing(self, void *geom) cdef list _buildParts(self, void *geom) cdef dict _buildPolygon(self, void *geom) cdef dict _buildMultiPoint(self, void *geom) cdef dict _buildMultiLineString(self, void *geom) cdef dict _buildMultiPolygon(self, void *geom) cdef dict _buildGeometryCollection(self, void *geom) cdef object build_from_feature(self, void *feature) cdef object build(self, void *geom) cpdef build_wkb(self, object wkb) cdef class OGRGeomBuilder: cdef void * _createOgrGeometry(self, int geom_type) except NULL cdef _addPointToGeometry(self, void *cogr_geometry, object coordinate) cdef void * _buildPoint(self, object coordinates) except NULL cdef void * _buildLineString(self, object coordinates) except NULL cdef void * _buildLinearRing(self, object coordinates) except NULL cdef void * _buildPolygon(self, object coordinates) except NULL cdef void * _buildMultiPoint(self, object coordinates) except NULL cdef void * _buildMultiLineString(self, object coordinates) except NULL cdef void * _buildMultiPolygon(self, object coordinates) except NULL cdef void * _buildGeometryCollection(self, object coordinates) except NULL cdef void * build(self, object geom) except NULL cdef unsigned int geometry_type_code(object name) except? 9999 cdef object normalize_geometry_type_code(unsigned int code) cdef unsigned int base_geometry_type_code(unsigned int code) Fiona-1.10.1/fiona/_geometry.pyx000066400000000000000000000340261467206072700164760ustar00rootroot00000000000000# Coordinate and geometry transformations. include "gdal.pxi" import logging from fiona.errors import UnsupportedGeometryTypeError from fiona.model import decode_object, GEOMETRY_TYPES, Geometry, OGRGeometryType from fiona._err cimport exc_wrap_int class NullHandler(logging.Handler): def emit(self, record): pass log = logging.getLogger(__name__) log.addHandler(NullHandler()) # mapping of GeoJSON type names to OGR integer geometry types GEOJSON2OGR_GEOMETRY_TYPES = dict((v, k) for k, v in GEOMETRY_TYPES.items()) cdef set LINEAR_GEOM_TYPES = { OGRGeometryType.CircularString.value, OGRGeometryType.CompoundCurve.value, OGRGeometryType.CurvePolygon.value, OGRGeometryType.MultiCurve.value, OGRGeometryType.MultiSurface.value, # OGRGeometryType.Curve.value, # Abstract type # OGRGeometryType.Surface.value, # Abstract type } cdef set PS_TIN_Tri_TYPES = { OGRGeometryType.PolyhedralSurface.value, OGRGeometryType.TIN.value, OGRGeometryType.Triangle.value } cdef int ogr_get_geometry_type(void *geometry): # OGR_G_GetGeometryType with NULL geometry support if geometry == NULL: return 0 # unknown return OGR_G_GetGeometryType(geometry) cdef unsigned int geometry_type_code(name) except? 9999: """Map OGC geometry type names to integer codes.""" offset = 0 if name.endswith('ZM'): offset = 3000 elif name.endswith('M'): offset = 2000 elif name.endswith('Z'): offset = 1000 normalized_name = name.rstrip('ZM') if normalized_name not in GEOJSON2OGR_GEOMETRY_TYPES: raise UnsupportedGeometryTypeError(name) return offset + GEOJSON2OGR_GEOMETRY_TYPES[normalized_name] cdef object normalize_geometry_type_code(unsigned int code): """Normalize M geometry type codes.""" # Normalize 'M' types to 2D types. if 2000 <= code < 3000: code = code % 1000 elif code == 3000: code = 0 # Normalize 'ZM' types to 3D types. elif 3000 < code < 4000: code = (code % 1000) | 0x80000000 # Normalize to a linear type. code = OGR_GT_GetLinear(code) if code not in GEOMETRY_TYPES: raise UnsupportedGeometryTypeError(code) return code cdef inline unsigned int base_geometry_type_code(unsigned int code): """ Returns base geometry code without Z, M and ZM types """ # Remove 2.5D flag. code = code & (~0x80000000) # Normalize Z, M, and ZM types. Fiona 1.x does not support M # and doesn't treat OGC 'Z' variants as special types of their # own. return code % 1000 # Geometry related functions and classes follow. cdef void * _createOgrGeomFromWKB(object wkb) except NULL: """Make an OGR geometry from a WKB string""" wkbtype = bytearray(wkb)[1] cdef unsigned char *buffer = wkb cdef void *cogr_geometry = OGR_G_CreateGeometry(wkbtype) if cogr_geometry is not NULL: exc_wrap_int(OGR_G_ImportFromWkb(cogr_geometry, buffer, len(wkb))) return cogr_geometry cdef _deleteOgrGeom(void *cogr_geometry): """Delete an OGR geometry""" if cogr_geometry is not NULL: OGR_G_DestroyGeometry(cogr_geometry) cogr_geometry = NULL cdef class GeomBuilder: """Builds Fiona (GeoJSON) geometries from an OGR geometry handle. """ # Note: The geometry passed to OGR_G_ForceToPolygon and # OGR_G_ForceToMultiPolygon must be removed from the container / # feature beforehand and the returned geometry needs to be cleaned up # afterwards. # OGR_G_GetLinearGeometry returns a copy of the geometry that needs # to be cleaned up afterwards. cdef list _buildCoords(self, void *geom): # Build a coordinate sequence cdef int i if geom == NULL: raise ValueError("Null geom") npoints = OGR_G_GetPointCount(geom) coords = [] for i in range(npoints): values = [OGR_G_GetX(geom, i), OGR_G_GetY(geom, i)] if self.ndims > 2: values.append(OGR_G_GetZ(geom, i)) coords.append(tuple(values)) return coords cdef dict _buildPoint(self, void *geom): return {'type': 'Point', 'coordinates': self._buildCoords(geom)[0]} cdef dict _buildLineString(self, void *geom): return {'type': 'LineString', 'coordinates': self._buildCoords(geom)} cdef dict _buildLinearRing(self, void *geom): return {'type': 'LinearRing', 'coordinates': self._buildCoords(geom)} cdef list _buildParts(self, void *geom): cdef int j cdef int code cdef int count cdef void *part if geom == NULL: raise ValueError("Null geom") parts = [] j = 0 count = OGR_G_GetGeometryCount(geom) while j < count: part = OGR_G_GetGeometryRef(geom, j) code = base_geometry_type_code(OGR_G_GetGeometryType(part)) if code in PS_TIN_Tri_TYPES: OGR_G_RemoveGeometry(geom, j, False) # Removing a geometry will cause the geometry count to drop by one, # and all “higher” geometries will shuffle down one in index. count -= 1 parts.append(GeomBuilder().build(part)) else: parts.append(GeomBuilder().build(part)) j += 1 return parts cdef dict _buildPolygon(self, void *geom): coordinates = [p['coordinates'] for p in self._buildParts(geom)] return {'type': 'Polygon', 'coordinates': coordinates} cdef dict _buildMultiPoint(self, void *geom): coordinates = [p['coordinates'] for p in self._buildParts(geom)] return {'type': 'MultiPoint', 'coordinates': coordinates} cdef dict _buildMultiLineString(self, void *geom): coordinates = [p['coordinates'] for p in self._buildParts(geom)] return {'type': 'MultiLineString', 'coordinates': coordinates} cdef dict _buildMultiPolygon(self, void *geom): coordinates = [p['coordinates'] for p in self._buildParts(geom)] return {'type': 'MultiPolygon', 'coordinates': coordinates} cdef dict _buildGeometryCollection(self, void *geom): parts = self._buildParts(geom) return {'type': 'GeometryCollection', 'geometries': parts} cdef object build_from_feature(self, void *feature): # Build Geometry from *OGRFeatureH cdef void *cogr_geometry = NULL cdef int code cogr_geometry = OGR_F_GetGeometryRef(feature) code = base_geometry_type_code(ogr_get_geometry_type(cogr_geometry)) # We need to take ownership of the geometry before we can call # OGR_G_ForceToPolygon or OGR_G_ForceToMultiPolygon if code in PS_TIN_Tri_TYPES: cogr_geometry = OGR_F_StealGeometry(feature) return self.build(cogr_geometry) cdef object build(self, void *geom): # Build Geometry from *OGRGeometryH cdef void *geometry_to_dealloc = NULL if geom == NULL: return None code = base_geometry_type_code(ogr_get_geometry_type(geom)) # We convert special geometries (Curves, TIN, Triangle, ...) # to GeoJSON compatible geometries (LineStrings, Polygons, MultiPolygon, ...) if code in LINEAR_GEOM_TYPES: geometry_to_dealloc = OGR_G_GetLinearGeometry(geom, 0.0, NULL) code = base_geometry_type_code(ogr_get_geometry_type(geometry_to_dealloc)) geom = geometry_to_dealloc elif code in PS_TIN_Tri_TYPES: if code == OGRGeometryType.Triangle.value: geometry_to_dealloc = OGR_G_ForceToPolygon(geom) else: geometry_to_dealloc = OGR_G_ForceToMultiPolygon(geom) code = base_geometry_type_code(OGR_G_GetGeometryType(geometry_to_dealloc)) geom = geometry_to_dealloc self.ndims = OGR_G_GetCoordinateDimension(geom) if code not in GEOMETRY_TYPES: raise UnsupportedGeometryTypeError(code) geomtypename = GEOMETRY_TYPES[code] if geomtypename == "Point": built = self._buildPoint(geom) elif geomtypename == "LineString": built = self._buildLineString(geom) elif geomtypename == "LinearRing": built = self._buildLinearRing(geom) elif geomtypename == "Polygon": built = self._buildPolygon(geom) elif geomtypename == "MultiPoint": built = self._buildMultiPoint(geom) elif geomtypename == "MultiLineString": built = self._buildMultiLineString(geom) elif geomtypename == "MultiPolygon": built = self._buildMultiPolygon(geom) elif geomtypename == "GeometryCollection": built = self._buildGeometryCollection(geom) else: raise UnsupportedGeometryTypeError(code) # Cleanup geometries we have ownership over if geometry_to_dealloc is not NULL: OGR_G_DestroyGeometry(geometry_to_dealloc) return Geometry.from_dict(built) cpdef build_wkb(self, object wkb): # Build geometry from wkb cdef object data = wkb cdef void *cogr_geometry = _createOgrGeomFromWKB(data) result = self.build(cogr_geometry) _deleteOgrGeom(cogr_geometry) return result cdef class OGRGeomBuilder: """Builds OGR geometries from Fiona geometries. """ cdef void * _createOgrGeometry(self, int geom_type) except NULL: cdef void *cogr_geometry = OGR_G_CreateGeometry(geom_type) if cogr_geometry == NULL: raise Exception("Could not create OGR Geometry of type: %i" % geom_type) return cogr_geometry cdef _addPointToGeometry(self, void *cogr_geometry, object coordinate): if len(coordinate) == 2: x, y = coordinate OGR_G_AddPoint_2D(cogr_geometry, x, y) else: x, y, z = coordinate[:3] OGR_G_AddPoint(cogr_geometry, x, y, z) cdef void * _buildPoint(self, object coordinates) except NULL: cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['Point']) self._addPointToGeometry(cogr_geometry, coordinates) return cogr_geometry cdef void * _buildLineString(self, object coordinates) except NULL: cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['LineString']) for coordinate in coordinates: self._addPointToGeometry(cogr_geometry, coordinate) return cogr_geometry cdef void * _buildLinearRing(self, object coordinates) except NULL: cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['LinearRing']) for coordinate in coordinates: self._addPointToGeometry(cogr_geometry, coordinate) OGR_G_CloseRings(cogr_geometry) return cogr_geometry cdef void * _buildPolygon(self, object coordinates) except NULL: cdef void *cogr_ring cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['Polygon']) for ring in coordinates: cogr_ring = self._buildLinearRing(ring) exc_wrap_int(OGR_G_AddGeometryDirectly(cogr_geometry, cogr_ring)) return cogr_geometry cdef void * _buildMultiPoint(self, object coordinates) except NULL: cdef void *cogr_part cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['MultiPoint']) for coordinate in coordinates: cogr_part = self._buildPoint(coordinate) exc_wrap_int(OGR_G_AddGeometryDirectly(cogr_geometry, cogr_part)) return cogr_geometry cdef void * _buildMultiLineString(self, object coordinates) except NULL: cdef void *cogr_part cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['MultiLineString']) for line in coordinates: cogr_part = self._buildLineString(line) exc_wrap_int(OGR_G_AddGeometryDirectly(cogr_geometry, cogr_part)) return cogr_geometry cdef void * _buildMultiPolygon(self, object coordinates) except NULL: cdef void *cogr_part cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['MultiPolygon']) for part in coordinates: cogr_part = self._buildPolygon(part) exc_wrap_int(OGR_G_AddGeometryDirectly(cogr_geometry, cogr_part)) return cogr_geometry cdef void * _buildGeometryCollection(self, object geometries) except NULL: cdef void *cogr_part cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['GeometryCollection']) for part in geometries: cogr_part = OGRGeomBuilder().build(part) exc_wrap_int(OGR_G_AddGeometryDirectly(cogr_geometry, cogr_part)) return cogr_geometry cdef void * build(self, object geometry) except NULL: cdef object typename = geometry.type cdef object coordinates = geometry.coordinates cdef object geometries = geometry.geometries if typename == 'Point': return self._buildPoint(coordinates) elif typename == 'LineString': return self._buildLineString(coordinates) elif typename == 'LinearRing': return self._buildLinearRing(coordinates) elif typename == 'Polygon': return self._buildPolygon(coordinates) elif typename == 'MultiPoint': return self._buildMultiPoint(coordinates) elif typename == 'MultiLineString': return self._buildMultiLineString(coordinates) elif typename == 'MultiPolygon': return self._buildMultiPolygon(coordinates) elif typename == 'GeometryCollection': return self._buildGeometryCollection(geometries) else: raise UnsupportedGeometryTypeError("Unsupported geometry type %s" % typename) def geometryRT(geom): # For testing purposes only, leaks the JSON data geometry = decode_object(geom) cdef void *cogr_geometry = OGRGeomBuilder().build(geometry) result = GeomBuilder().build(cogr_geometry) _deleteOgrGeom(cogr_geometry) return result Fiona-1.10.1/fiona/_path.py000066400000000000000000000125571467206072700154140ustar00rootroot00000000000000"""Dataset paths, identifiers, and filenames Note: this module is not part of Rasterio's API. It is for internal use only. """ import os import pathlib import re import sys from urllib.parse import urlparse import attr from fiona.errors import PathError # Supported URI schemes and their mapping to GDAL's VSI suffix. # TODO: extend for other cloud platforms. SCHEMES = { 'ftp': 'curl', 'gzip': 'gzip', 'http': 'curl', 'https': 'curl', 's3': 's3', 'tar': 'tar', 'zip': 'zip', 'file': 'file', 'oss': 'oss', 'gs': 'gs', 'az': 'az', } ARCHIVESCHEMES = set CURLSCHEMES = set([k for k, v in SCHEMES.items() if v == 'curl']) # TODO: extend for other cloud platforms. REMOTESCHEMES = set([k for k, v in SCHEMES.items() if v in ('curl', 's3', 'oss', 'gs', 'az',)]) class _Path: """Base class for dataset paths""" def as_vsi(self): return _vsi_path(self) @attr.s(slots=True) class _ParsedPath(_Path): """Result of parsing a dataset URI/Path Attributes ---------- path : str Parsed path. Includes the hostname and query string in the case of a URI. archive : str Parsed archive path. scheme : str URI scheme such as "https" or "zip+s3". """ path = attr.ib() archive = attr.ib() scheme = attr.ib() @classmethod def from_uri(cls, uri): parts = urlparse(uri) if sys.platform == "win32" and re.match(r"^[a-zA-Z]\:", parts.netloc): parsed_path = f"{parts.netloc}{parts.path}" parsed_netloc = None else: parsed_path = parts.path parsed_netloc = parts.netloc path = parsed_path scheme = parts.scheme or None if parts.query: path += "?" + parts.query if scheme and scheme.startswith(("gzip", "tar", "zip")): path_parts = path.split('!') path = path_parts.pop() if path_parts else None archive = path_parts.pop() if path_parts else None else: archive = None if scheme and parsed_netloc: if archive: archive = parsed_netloc + archive else: path = parsed_netloc + path return _ParsedPath(path, archive, scheme) @property def name(self): """The parsed path's original URI""" if not self.scheme: return self.path elif self.archive: return "{}://{}!{}".format(self.scheme, self.archive, self.path) else: return "{}://{}".format(self.scheme, self.path) @property def is_remote(self): """Test if the path is a remote, network URI""" return bool(self.scheme) and self.scheme.split("+")[-1] in REMOTESCHEMES @property def is_local(self): """Test if the path is a local URI""" return not self.scheme or (self.scheme and self.scheme.split('+')[-1] not in REMOTESCHEMES) @attr.s(slots=True) class _UnparsedPath(_Path): """Encapsulates legacy GDAL filenames Attributes ---------- path : str The legacy GDAL filename. """ path = attr.ib() @property def name(self): """The unparsed path's original path""" return self.path def _parse_path(path): """Parse a dataset's identifier or path into its parts Parameters ---------- path : str or path-like object The path to be parsed. Returns ------- ParsedPath or UnparsedPath Notes ----- When legacy GDAL filenames are encountered, they will be returned in a UnparsedPath. """ if isinstance(path, _Path): return path elif isinstance(path, pathlib.PurePath): return _ParsedPath(os.fspath(path), None, None) elif isinstance(path, str): if sys.platform == "win32" and re.match(r"^[a-zA-Z]\:", path): return _ParsedPath(path, None, None) elif path.startswith('/vsi'): return _UnparsedPath(path) else: parts = urlparse(path) else: raise PathError("invalid path '{!r}'".format(path)) # if the scheme is not one of Rasterio's supported schemes, we # return an UnparsedPath. if parts.scheme: if all(p in SCHEMES for p in parts.scheme.split('+')): return _ParsedPath.from_uri(path) return _UnparsedPath(path) def _vsi_path(path): """Convert a parsed path to a GDAL VSI path Parameters ---------- path : Path A ParsedPath or UnparsedPath object. Returns ------- str """ if isinstance(path, _UnparsedPath): return path.path elif isinstance(path, _ParsedPath): if not path.scheme: return path.path else: if path.scheme.split('+')[-1] in CURLSCHEMES: suffix = '{}://'.format(path.scheme.split('+')[-1]) else: suffix = '' prefix = '/'.join('vsi{0}'.format(SCHEMES[p]) for p in path.scheme.split('+') if p != 'file') if prefix: if path.archive: result = '/{}/{}{}/{}'.format(prefix, suffix, path.archive, path.path.lstrip('/')) else: result = '/{}/{}{}'.format(prefix, suffix, path.path) else: result = path.path return result else: raise ValueError("path must be a ParsedPath or UnparsedPath object") Fiona-1.10.1/fiona/_show_versions.py000066400000000000000000000010241467206072700173530ustar00rootroot00000000000000import platform import sys import fiona from fiona._env import get_gdal_release_name, get_proj_version_tuple def show_versions(): """ Prints information useful for bug reports """ print("Fiona version:", fiona.__version__) print("GDAL version:", get_gdal_release_name()) print("PROJ version:", ".".join(map(str, get_proj_version_tuple()))) print() print("OS:", platform.system(), platform.release()) print("Python:", platform.python_version()) print("Python executable:", sys.executable) Fiona-1.10.1/fiona/_transform.pyx000066400000000000000000000122211467206072700166470ustar00rootroot00000000000000# distutils: language = c++ # # Coordinate and geometry transformations. include "gdal.pxi" import logging import warnings from collections import UserDict from fiona cimport _cpl, _csl, _geometry from fiona.crs cimport OGRSpatialReferenceH, osr_set_traditional_axis_mapping_strategy from fiona.compat import DICT_TYPES from fiona.crs import CRS from fiona.errors import TransformError from fiona.model import Geometry cdef extern from "ogr_geometry.h" nogil: cdef cppclass OGRGeometry: pass cdef cppclass OGRGeometryFactory: void * transformWithOptions(void *geom, void *ct, char **options) cdef extern from "ogr_spatialref.h": cdef cppclass OGRCoordinateTransformation: pass log = logging.getLogger(__name__) class NullHandler(logging.Handler): def emit(self, record): pass log.addHandler(NullHandler()) cdef void *_crs_from_crs(object crs): cdef char *wkt_c = NULL cdef OGRSpatialReferenceH osr = NULL osr = OSRNewSpatialReference(NULL) if osr == NULL: raise ValueError("NULL spatial reference") params = [] wkt = CRS.from_user_input(crs).to_wkt() wkt_b = wkt.encode('utf-8') wkt_c = wkt_b OSRSetFromUserInput(osr, wkt_c) osr_set_traditional_axis_mapping_strategy(osr) return osr def _transform(src_crs, dst_crs, xs, ys): cdef double *x cdef double *y cdef char *proj_c = NULL cdef OGRSpatialReferenceH src = NULL cdef OGRSpatialReferenceH dst = NULL cdef void *transform = NULL cdef int i assert len(xs) == len(ys) src = _crs_from_crs(src_crs) dst = _crs_from_crs(dst_crs) n = len(xs) x = _cpl.CPLMalloc(n*sizeof(double)) y = _cpl.CPLMalloc(n*sizeof(double)) for i in range(n): x[i] = xs[i] y[i] = ys[i] transform = OCTNewCoordinateTransformation(src, dst) res = OCTTransform(transform, n, x, y, NULL) res_xs = [0]*n res_ys = [0]*n for i in range(n): res_xs[i] = x[i] res_ys[i] = y[i] _cpl.CPLFree(x) _cpl.CPLFree(y) OCTDestroyCoordinateTransformation(transform) OSRRelease(src) OSRRelease(dst) return res_xs, res_ys cdef object _transform_single_geom( object single_geom, OGRGeometryFactory *factory, void *transform, char **options, ): """Transform a single geometry.""" cdef void *src_ogr_geom = NULL cdef void *dst_ogr_geom = NULL src_ogr_geom = _geometry.OGRGeomBuilder().build(single_geom) dst_ogr_geom = factory.transformWithOptions( src_ogr_geom, transform, options) if dst_ogr_geom == NULL and CPLGetConfigOption("OGR_ENABLE_PARTIAL_REPROJECTION", "OFF") != b"ON": raise TransformError( "Full reprojection failed. To enable partial reprojection set OGR_ENABLE_PARTIAL_REPROJECTION=True" ) else: out_geom = _geometry.GeomBuilder().build(dst_ogr_geom) _geometry.OGR_G_DestroyGeometry(dst_ogr_geom) if src_ogr_geom != NULL: _geometry.OGR_G_DestroyGeometry(src_ogr_geom) return out_geom def _transform_geom(src_crs, dst_crs, geom, antimeridian_cutting, antimeridian_offset, precision): """Return transformed geometries. """ cdef char *proj_c = NULL cdef char *key_c = NULL cdef char *val_c = NULL cdef char **options = NULL cdef OGRSpatialReferenceH src = NULL cdef OGRSpatialReferenceH dst = NULL cdef void *transform = NULL cdef OGRGeometryFactory *factory = NULL if not all([src_crs, dst_crs]): raise RuntimeError("Must provide a source and destination CRS.") src = _crs_from_crs(src_crs) dst = _crs_from_crs(dst_crs) transform = OCTNewCoordinateTransformation(src, dst) # Transform options. options = _csl.CSLSetNameValue( options, "DATELINEOFFSET", str(antimeridian_offset).encode('utf-8') ) if antimeridian_cutting: options = _csl.CSLSetNameValue(options, "WRAPDATELINE", "YES") factory = new OGRGeometryFactory() if isinstance(geom, Geometry): out_geom = recursive_round( _transform_single_geom(geom, factory, transform, options), precision) else: out_geom = [ recursive_round( _transform_single_geom(single_geom, factory, transform, options), precision, ) for single_geom in geom ] OCTDestroyCoordinateTransformation(transform) if options != NULL: _csl.CSLDestroy(options) OSRRelease(src) OSRRelease(dst) return out_geom def recursive_round(obj, precision): """Recursively round coordinates.""" if precision < 0: return obj if getattr(obj, 'geometries', None): return Geometry(geometries=[recursive_round(part, precision) for part in obj.geometries]) elif getattr(obj, 'coordinates', None): return Geometry(coordinates=[recursive_round(part, precision) for part in obj.coordinates]) if isinstance(obj, (int, float)): return round(obj, precision) else: return [recursive_round(part, precision) for part in obj] Fiona-1.10.1/fiona/_vendor/000077500000000000000000000000001467206072700153715ustar00rootroot00000000000000Fiona-1.10.1/fiona/_vendor/munch/000077500000000000000000000000001467206072700165035ustar00rootroot00000000000000Fiona-1.10.1/fiona/_vendor/munch/LICENSE.txt000066400000000000000000000020671467206072700203330ustar00rootroot00000000000000Copyright (c) 2010 David Schoonover 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Fiona-1.10.1/fiona/_vendor/munch/__init__.py000066400000000000000000000417441467206072700206260ustar00rootroot00000000000000""" Munch is a subclass of dict with attribute-style access. >>> b = Munch() >>> b.hello = 'world' >>> b.hello 'world' >>> b['hello'] += "!" >>> b.hello 'world!' >>> b.foo = Munch(lol=True) >>> b.foo.lol True >>> b.foo is b['foo'] True It is safe to import * from this module: __all__ = ('Munch', 'munchify','unmunchify') un/munchify provide dictionary conversion; Munches can also be converted via Munch.to/fromDict(). """ from collections.abc import Mapping __version__ = "2.5.0" VERSION = tuple(map(int, __version__.split('.')[:3])) __all__ = ('Munch', 'munchify', 'DefaultMunch', 'DefaultFactoryMunch', 'unmunchify') class Munch(dict): """ A dictionary that provides attribute-style access. >>> b = Munch() >>> b.hello = 'world' >>> b.hello 'world' >>> b['hello'] += "!" >>> b.hello 'world!' >>> b.foo = Munch(lol=True) >>> b.foo.lol True >>> b.foo is b['foo'] True A Munch is a subclass of dict; it supports all the methods a dict does... >>> sorted(b.keys()) ['foo', 'hello'] Including update()... >>> b.update({ 'ponies': 'are pretty!' }, hello=42) >>> print (repr(b)) Munch({'ponies': 'are pretty!', 'foo': Munch({'lol': True}), 'hello': 42}) As well as iteration... >>> sorted([ (k,b[k]) for k in b ]) [('foo', Munch({'lol': True})), ('hello', 42), ('ponies', 'are pretty!')] And "splats". >>> "The {knights} who say {ni}!".format(**Munch(knights='lolcats', ni='can haz')) 'The lolcats who say can haz!' See unmunchify/Munch.toDict, munchify/Munch.fromDict for notes about conversion. """ def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called self.update(*args, **kwargs) # only called if k not found in normal places def __getattr__(self, k): """ Gets key if it exists, otherwise throws AttributeError. nb. __getattr__ is only called if key is not found in normal places. >>> b = Munch(bar='baz', lol={}) >>> b.foo Traceback (most recent call last): ... AttributeError: foo >>> b.bar 'baz' >>> getattr(b, 'bar') 'baz' >>> b['bar'] 'baz' >>> b.lol is b['lol'] True >>> b.lol is getattr(b, 'lol') True """ try: # Throws exception if not in prototype chain return object.__getattribute__(self, k) except AttributeError: try: return self[k] except KeyError: raise AttributeError(k) def __setattr__(self, k, v): """ Sets attribute k if it exists, otherwise sets key k. A KeyError raised by set-item (only likely if you subclass Munch) will propagate as an AttributeError instead. >>> b = Munch(foo='bar', this_is='useful when subclassing') >>> hasattr(b.values, '__call__') True >>> b.values = 'uh oh' >>> b.values 'uh oh' >>> b['values'] Traceback (most recent call last): ... KeyError: 'values' """ try: # Throws exception if not in prototype chain object.__getattribute__(self, k) except AttributeError: try: self[k] = v except: raise AttributeError(k) else: object.__setattr__(self, k, v) def __delattr__(self, k): """ Deletes attribute k if it exists, otherwise deletes key k. A KeyError raised by deleting the key--such as when the key is missing--will propagate as an AttributeError instead. >>> b = Munch(lol=42) >>> del b.lol >>> b.lol Traceback (most recent call last): ... AttributeError: lol """ try: # Throws exception if not in prototype chain object.__getattribute__(self, k) except AttributeError: try: del self[k] except KeyError: raise AttributeError(k) else: object.__delattr__(self, k) def toDict(self): """ Recursively converts a munch back into a dictionary. >>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!') >>> sorted(b.toDict().items()) [('foo', {'lol': True}), ('hello', 42), ('ponies', 'are pretty!')] See unmunchify for more info. """ return unmunchify(self) @property def __dict__(self): return self.toDict() def __repr__(self): """ Invertible* string-form of a Munch. >>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!') >>> print (repr(b)) Munch({'ponies': 'are pretty!', 'foo': Munch({'lol': True}), 'hello': 42}) >>> eval(repr(b)) Munch({'ponies': 'are pretty!', 'foo': Munch({'lol': True}), 'hello': 42}) >>> with_spaces = Munch({1: 2, 'a b': 9, 'c': Munch({'simple': 5})}) >>> print (repr(with_spaces)) Munch({'a b': 9, 1: 2, 'c': Munch({'simple': 5})}) >>> eval(repr(with_spaces)) Munch({'a b': 9, 1: 2, 'c': Munch({'simple': 5})}) (*) Invertible so long as collection contents are each repr-invertible. """ return '{0}({1})'.format(self.__class__.__name__, dict.__repr__(self)) def __dir__(self): return list(self.keys()) def __getstate__(self): """ Implement a serializable interface used for pickling. See https://docs.python.org/3.6/library/pickle.html. """ return {k: v for k, v in self.items()} def __setstate__(self, state): """ Implement a serializable interface used for pickling. See https://docs.python.org/3.6/library/pickle.html. """ self.clear() self.update(state) __members__ = __dir__ # for python2.x compatibility @classmethod def fromDict(cls, d): """ Recursively transforms a dictionary into a Munch via copy. >>> b = Munch.fromDict({'urmom': {'sez': {'what': 'what'}}}) >>> b.urmom.sez.what 'what' See munchify for more info. """ return munchify(d, cls) def copy(self): return type(self).fromDict(self) def update(self, *args, **kwargs): """ Override built-in method to call custom __setitem__ method that may be defined in subclasses. """ for k, v in dict(*args, **kwargs).items(): self[k] = v def get(self, k, d=None): """ D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None. """ if k not in self: return d return self[k] def setdefault(self, k, d=None): """ D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D """ if k not in self: self[k] = d return self[k] class AutoMunch(Munch): def __setattr__(self, k, v): """ Works the same as Munch.__setattr__ but if you supply a dictionary as value it will convert it to another Munch. """ if isinstance(v, Mapping) and not isinstance(v, (AutoMunch, Munch)): v = munchify(v, AutoMunch) super(AutoMunch, self).__setattr__(k, v) class DefaultMunch(Munch): """ A Munch that returns a user-specified value for missing keys. """ def __init__(self, *args, **kwargs): """ Construct a new DefaultMunch. Like collections.defaultdict, the first argument is the default value; subsequent arguments are the same as those for dict. """ # Mimic collections.defaultdict constructor if args: default = args[0] args = args[1:] else: default = None super(DefaultMunch, self).__init__(*args, **kwargs) self.__default__ = default def __getattr__(self, k): """ Gets key if it exists, otherwise returns the default value.""" try: return super(DefaultMunch, self).__getattr__(k) except AttributeError: return self.__default__ def __setattr__(self, k, v): if k == '__default__': object.__setattr__(self, k, v) else: super(DefaultMunch, self).__setattr__(k, v) def __getitem__(self, k): """ Gets key if it exists, otherwise returns the default value.""" try: return super(DefaultMunch, self).__getitem__(k) except KeyError: return self.__default__ def __getstate__(self): """ Implement a serializable interface used for pickling. See https://docs.python.org/3.6/library/pickle.html. """ return (self.__default__, {k: v for k, v in self.items()}) def __setstate__(self, state): """ Implement a serializable interface used for pickling. See https://docs.python.org/3.6/library/pickle.html. """ self.clear() default, state_dict = state self.update(state_dict) self.__default__ = default @classmethod def fromDict(cls, d, default=None): # pylint: disable=arguments-differ return munchify(d, factory=lambda d_: cls(default, d_)) def copy(self): return type(self).fromDict(self, default=self.__default__) def __repr__(self): return '{0}({1!r}, {2})'.format( type(self).__name__, self.__undefined__, dict.__repr__(self)) class DefaultFactoryMunch(Munch): """ A Munch that calls a user-specified function to generate values for missing keys like collections.defaultdict. >>> b = DefaultFactoryMunch(list, {'hello': 'world!'}) >>> b.hello 'world!' >>> b.foo [] >>> b.bar.append('hello') >>> b.bar ['hello'] """ def __init__(self, default_factory, *args, **kwargs): super(DefaultFactoryMunch, self).__init__(*args, **kwargs) self.default_factory = default_factory @classmethod def fromDict(cls, d, default_factory): # pylint: disable=arguments-differ return munchify(d, factory=lambda d_: cls(default_factory, d_)) def copy(self): return type(self).fromDict(self, default_factory=self.default_factory) def __repr__(self): factory = self.default_factory.__name__ return '{0}({1}, {2})'.format( type(self).__name__, factory, dict.__repr__(self)) def __setattr__(self, k, v): if k == 'default_factory': object.__setattr__(self, k, v) else: super(DefaultFactoryMunch, self).__setattr__(k, v) def __missing__(self, k): self[k] = self.default_factory() return self[k] # While we could convert abstract types like Mapping or Iterable, I think # munchify is more likely to "do what you mean" if it is conservative about # casting (ex: isinstance(str,Iterable) == True ). # # Should you disagree, it is not difficult to duplicate this function with # more aggressive coercion to suit your own purposes. def munchify(x, factory=Munch): """ Recursively transforms a dictionary into a Munch via copy. >>> b = munchify({'urmom': {'sez': {'what': 'what'}}}) >>> b.urmom.sez.what 'what' munchify can handle intermediary dicts, lists and tuples (as well as their subclasses), but ymmv on custom datatypes. >>> b = munchify({ 'lol': ('cats', {'hah':'i win again'}), ... 'hello': [{'french':'salut', 'german':'hallo'}] }) >>> b.hello[0].french 'salut' >>> b.lol[1].hah 'i win again' nb. As dicts are not hashable, they cannot be nested in sets/frozensets. """ # Munchify x, using `seen` to track object cycles seen = dict() def munchify_cycles(obj): # If we've already begun munchifying obj, just return the already-created munchified obj try: return seen[id(obj)] except KeyError: pass # Otherwise, first partly munchify obj (but without descending into any lists or dicts) and save that seen[id(obj)] = partial = pre_munchify(obj) # Then finish munchifying lists and dicts inside obj (reusing munchified obj if cycles are encountered) return post_munchify(partial, obj) def pre_munchify(obj): # Here we return a skeleton of munchified obj, which is enough to save for later (in case # we need to break cycles) but it needs to filled out in post_munchify if isinstance(obj, Mapping): return factory({}) elif isinstance(obj, list): return type(obj)() elif isinstance(obj, tuple): type_factory = getattr(obj, "_make", type(obj)) return type_factory(munchify_cycles(item) for item in obj) else: return obj def post_munchify(partial, obj): # Here we finish munchifying the parts of obj that were deferred by pre_munchify because they # might be involved in a cycle if isinstance(obj, Mapping): partial.update((k, munchify_cycles(obj[k])) for k in obj.keys()) elif isinstance(obj, list): partial.extend(munchify_cycles(item) for item in obj) elif isinstance(obj, tuple): for (item_partial, item) in zip(partial, obj): post_munchify(item_partial, item) return partial return munchify_cycles(x) def unmunchify(x): """ Recursively converts a Munch into a dictionary. >>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!') >>> sorted(unmunchify(b).items()) [('foo', {'lol': True}), ('hello', 42), ('ponies', 'are pretty!')] unmunchify will handle intermediary dicts, lists and tuples (as well as their subclasses), but ymmv on custom datatypes. >>> b = Munch(foo=['bar', Munch(lol=True)], hello=42, ... ponies=('are pretty!', Munch(lies='are trouble!'))) >>> sorted(unmunchify(b).items()) #doctest: +NORMALIZE_WHITESPACE [('foo', ['bar', {'lol': True}]), ('hello', 42), ('ponies', ('are pretty!', {'lies': 'are trouble!'}))] nb. As dicts are not hashable, they cannot be nested in sets/frozensets. """ # Munchify x, using `seen` to track object cycles seen = dict() def unmunchify_cycles(obj): # If we've already begun unmunchifying obj, just return the already-created unmunchified obj try: return seen[id(obj)] except KeyError: pass # Otherwise, first partly unmunchify obj (but without descending into any lists or dicts) and save that seen[id(obj)] = partial = pre_unmunchify(obj) # Then finish unmunchifying lists and dicts inside obj (reusing unmunchified obj if cycles are encountered) return post_unmunchify(partial, obj) def pre_unmunchify(obj): # Here we return a skeleton of unmunchified obj, which is enough to save for later (in case # we need to break cycles) but it needs to filled out in post_unmunchify if isinstance(obj, Mapping): return dict() elif isinstance(obj, list): return type(obj)() elif isinstance(obj, tuple): type_factory = getattr(obj, "_make", type(obj)) return type_factory(unmunchify_cycles(item) for item in obj) else: return obj def post_unmunchify(partial, obj): # Here we finish unmunchifying the parts of obj that were deferred by pre_unmunchify because they # might be involved in a cycle if isinstance(obj, Mapping): partial.update((k, unmunchify_cycles(obj[k])) for k in obj.keys()) elif isinstance(obj, list): partial.extend(unmunchify_cycles(v) for v in obj) elif isinstance(obj, tuple): for (value_partial, value) in zip(partial, obj): post_unmunchify(value_partial, value) return partial return unmunchify_cycles(x) # Serialization try: try: import json except ImportError: import simplejson as json def toJSON(self, **options): """ Serializes this Munch to JSON. Accepts the same keyword options as `json.dumps()`. >>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!') >>> json.dumps(b) == b.toJSON() True """ return json.dumps(self, **options) def fromJSON(cls, stream, *args, **kwargs): """ Deserializes JSON to Munch or any of its subclasses. """ factory = lambda d: cls(*(args + (d,)), **kwargs) return munchify(json.loads(stream), factory=factory) Munch.toJSON = toJSON Munch.fromJSON = classmethod(fromJSON) except ImportError: pass Fiona-1.10.1/fiona/_vendor/snuggs.py000066400000000000000000000172151467206072700172570ustar00rootroot00000000000000"""Snuggs are s-expressions for Numpy.""" # This file is a modified version of snuggs 1.4.7. The numpy # requirement has been removed and support for keyword arguments in # expressions has been added. # # The original license follows. # # Copyright (c) 2014 Mapbox # # 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: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from collections import OrderedDict import functools import operator import re from typing import Mapping from pyparsing import ( # type: ignore Keyword, oneOf, Literal, QuotedString, ParseException, Forward, Group, OneOrMore, ParseResults, Regex, ZeroOrMore, alphanums, pyparsing_common, replace_with, ) __all__ = ["eval"] __version__ = "1.4.7" class Context(object): def __init__(self): self._data = OrderedDict() def add(self, name, val): self._data[name] = val def get(self, name): return self._data[name] def lookup(self, index, subindex=None): s = list(self._data.values())[int(index) - 1] if subindex: return s[int(subindex) - 1] else: return s def clear(self): self._data = OrderedDict() _ctx = Context() class ctx(object): def __init__(self, kwd_dict=None, **kwds): self.kwds = kwd_dict or kwds def __enter__(self): _ctx.clear() for k, v in self.kwds.items(): _ctx.add(k, v) return self def __exit__(self, exc_type=None, exc_val=None, exc_tb=None): self.kwds = None _ctx.clear() class ExpressionError(SyntaxError): """A Snuggs-specific syntax error.""" filename = "" lineno = 1 op_map = { "*": lambda *args: functools.reduce(lambda x, y: operator.mul(x, y), args), "+": lambda *args: functools.reduce(lambda x, y: operator.add(x, y), args), "/": lambda *args: functools.reduce(lambda x, y: operator.truediv(x, y), args), "-": lambda *args: functools.reduce(lambda x, y: operator.sub(x, y), args), "&": lambda *args: functools.reduce(lambda x, y: operator.and_(x, y), args), "|": lambda *args: functools.reduce(lambda x, y: operator.or_(x, y), args), "<": operator.lt, "<=": operator.le, "==": operator.eq, "!=": operator.ne, ">=": operator.ge, ">": operator.gt, "truth": operator.truth, "is": operator.is_, "not": operator.not_, } def compose(f, g): """Compose two functions. compose(f, g)(x) = f(g(x)). """ return lambda x, *args, **kwds: f(g(x)) func_map: Mapping = {} higher_func_map: Mapping = { "compose": compose, "map": map, "partial": functools.partial, "reduce": functools.reduce, "attrgetter": operator.attrgetter, "methodcaller": operator.methodcaller, "itemgetter": operator.itemgetter, } nil = Keyword("null").set_parse_action(replace_with(None)) true = Keyword("true").set_parse_action(replace_with(True)) false = Keyword("false").set_parse_action(replace_with(False)) def resolve_var(source, loc, toks): try: return _ctx.get(toks[0]) except KeyError: err = ExpressionError("name '{}' is not defined".format(toks[0])) err.text = source err.offset = loc + 1 raise err var = pyparsing_common.identifier.set_parse_action(resolve_var) string = QuotedString("'") | QuotedString('"') lparen = Literal("(").suppress() rparen = Literal(")").suppress() op = oneOf(" ".join(op_map.keys())).set_parse_action( lambda source, loc, toks: op_map[toks[0]] ) def resolve_func(source, loc, toks): try: return func_map[toks[0]] except (AttributeError, KeyError): err = ExpressionError("'{}' is not a function or operator".format(toks[0])) err.text = source err.offset = loc + 1 raise err # The look behind assertion is to disambiguate between functions and # variables. func = Regex(r"(?<=\()[{}]+".format(alphanums + "_")).set_parse_action(resolve_func) higher_func = oneOf(" ".join(higher_func_map.keys())).set_parse_action( lambda source, loc, toks: higher_func_map[toks[0]] ) func_expr = Forward() higher_func_expr = Forward() expr = higher_func_expr | func_expr class KeywordArg: def __init__(self, name): self.name = name kwarg = Regex(r":[{}]+".format(alphanums + "_")).set_parse_action( lambda source, loc, toks: KeywordArg(toks[0][1:]) ) operand = ( higher_func_expr | func_expr | true | false | nil | var | kwarg | pyparsing_common.sci_real | pyparsing_common.real | pyparsing_common.signed_integer | string ) func_expr << Group( lparen + (higher_func_expr | op | func) + OneOrMore(operand) + rparen ) higher_func_expr << Group( lparen + higher_func + (nil | higher_func_expr | op | func | OneOrMore(operand)) + ZeroOrMore(operand) + rparen ) def processArg(arg): if isinstance(arg, ParseResults): return processList(arg) else: return arg def processList(lst): items = [processArg(x) for x in lst[1:]] args = [] kwds = {} # An iterator is used instead of implicit iteration to allow # skipping ahead in the keyword argument case. itemitr = iter(items) for item in itemitr: if isinstance(item, KeywordArg): # The next item after the keyword arg marker is its value. # This advances the iterator in a way that is compatible # with the for loop. val = next(itemitr) key = item.name kwds[key] = val else: args.append(item) func = processArg(lst[0]) # list and tuple are two builtins that take a single argument, # whereas args is a list. On a KeyError, the call is retried # without arg unpacking. try: return func(*args, **kwds) except TypeError: return func(args, **kwds) def handleLine(line): try: result = expr.parseString(line) return processList(result[0]) except ParseException as exc: text = str(exc) m = re.search(r"(Expected .+) \(at char (\d+)\), \(line:(\d+)", text) msg = m.group(1) if "map|partial" in msg: msg = "expected a function or operator" err = ExpressionError(msg) err.text = line err.offset = int(m.group(2)) + 1 raise err def eval(source, kwd_dict=None, **kwds): """Evaluate a snuggs expression. Parameters ---------- source : str Expression source. kwd_dict : dict A dict of items that form the evaluation context. Deprecated. kwds : dict A dict of items that form the valuation context. Returns ------- object """ kwd_dict = kwd_dict or kwds with ctx(kwd_dict): return handleLine(source) Fiona-1.10.1/fiona/_vsiopener.pxd000066400000000000000000000000231467206072700166160ustar00rootroot00000000000000include "gdal.pxi" Fiona-1.10.1/fiona/_vsiopener.pyx000066400000000000000000000477751467206072700166740ustar00rootroot00000000000000"""Bridge between Python file openers and GDAL VSI. Based on _filepath.pyx. """ from abc import ABC, abstractmethod from collections.abc import Callable import contextlib from contextvars import ContextVar from functools import singledispatch import logging import os from pathlib import Path import stat from uuid import uuid4 from libc.string cimport memcpy from fiona._env import get_gdal_version_tuple from fiona.errors import OpenerRegistrationError log = logging.getLogger(__name__) cdef str VSI_NS_ROOT = "vsifiopener" # This is global state for the Python filesystem plugin. It currently only # contains path -> PyOpenerBase (or subclass) instances. This is used by # the plugin to determine what "files" exist on "disk". # Currently the only way to "create" a file in the filesystem is to add # an entry to this dictionary. GDAL will then Open the path later. _OPENER_REGISTRY = ContextVar("opener_registry") _OPENER_REGISTRY.set({}) _OPEN_FILE_EXIT_STACKS = ContextVar("open_file_exit_stacks") _OPEN_FILE_EXIT_STACKS.set({}) # When an opener is registered for a path, this structure captures the # path and unique registration instance. VSI stat, read_dir, and open # calls have access to the struct instance. cdef struct FSData: char *path char *uuid cdef int pyopener_stat( void *pUserData, const char *pszFilename, VSIStatBufL *pStatBuf, int nFlags ) with gil: """Provides POSIX stat data to GDAL from a Python filesystem.""" cdef FSData *fsdata = pUserData path = fsdata.path.decode("utf-8") uuid = fsdata.uuid.decode("utf-8") key = (Path(path), uuid) urlpath = pszFilename.decode("utf-8") registry = _OPENER_REGISTRY.get() log.debug( "Looking up opener in pyopener_stat: urlpath=%r, registry=%r, key=%r", urlpath, registry, key ) try: file_opener = registry[key] except KeyError as err: errmsg = f"Opener not found: {repr(err)}".encode("utf-8") CPLError(CE_Failure, 4, "%s", errmsg) return -1 try: if file_opener.isfile(urlpath): fmode = stat.S_IFREG elif file_opener.isdir(urlpath): fmode = stat.S_IFDIR else: # No such file or directory. return -1 size = file_opener.size(urlpath) mtime = file_opener.mtime(urlpath) except (FileNotFoundError, KeyError) as err: # No such file or directory. return -1 except Exception as err: errmsg = f"Opener failed to determine file info: {repr(err)}".encode("utf-8") CPLError(CE_Failure, 4, "%s", errmsg) return -1 pStatBuf.st_size = size pStatBuf.st_mode = fmode pStatBuf.st_mtime = mtime return 0 cdef int pyopener_unlink( void *pUserData, const char *pszFilename, ) with gil: """Unlink a file from a Python filesystem.""" cdef FSData *fsdata = pUserData path = fsdata.path.decode("utf-8") uuid = fsdata.uuid.decode("utf-8") key = (Path(path), uuid) urlpath = pszFilename.decode("utf-8") registry = _OPENER_REGISTRY.get() log.debug( "Looking up opener in pyopener_unlink: urlpath=%r, registry=%r, key=%r", urlpath, registry, key ) try: file_opener = registry[key] except KeyError as err: errmsg = f"Opener not found: {repr(err)}".encode("utf-8") CPLError(CE_Failure, 4, "%s", errmsg) return -1 try: file_opener.rm(urlpath) return 0 except (FileNotFoundError, KeyError) as err: # No such file or directory. return -1 except Exception as err: errmsg = f"Opener failed to determine file info: {repr(err)}".encode("utf-8") CPLError(CE_Failure, 4, "%s", errmsg) return -1 cdef char ** pyopener_read_dir( void *pUserData, const char *pszDirname, int nMaxFiles ) with gil: """Provides a directory listing to GDAL from a Python filesystem.""" cdef FSData *fsdata = pUserData path = fsdata.path.decode("utf-8") uuid = fsdata.uuid.decode("utf-8") key = (Path(path), uuid) urlpath = pszDirname.decode("utf-8") registry = _OPENER_REGISTRY.get() log.debug( "Looking up opener in pyopener_read_dir: urlpath=%r, registry=%r, key=%r", urlpath, registry, key ) try: file_opener = registry[key] except KeyError as err: errmsg = f"Opener not found: {repr(err)}".encode("utf-8") CPLError(CE_Failure, 4, "%s", errmsg) return NULL try: # GDAL wants relative file names. contents = [Path(item).name for item in file_opener.ls(urlpath)] except (FileNotFoundError, KeyError) as err: # No such file or directory. return NULL except Exception as err: errmsg = f"Opener failed to determine directory contents: {repr(err)}".encode("utf-8") CPLError(CE_Failure, 4, "%s", errmsg) return NULL cdef char **name_list = NULL for name in contents: fname = name.encode("utf-8") name_list = CSLAddString(name_list, fname) return name_list cdef void* pyopener_open( void *pUserData, const char *pszFilename, const char *pszAccess ) with gil: """Access files in the virtual filesystem. This function is mandatory in the GDAL Filesystem Plugin API. GDAL may call this function multiple times per filename and each result must be separately seekable. """ cdef FSData *fsdata = pUserData path = fsdata.path.decode("utf-8") uuid = fsdata.uuid.decode("utf-8") key = (Path(path), uuid) urlpath = pszFilename.decode("utf-8") mode = pszAccess.decode("utf-8") if not "b" in mode: mode += "b" registry = _OPENER_REGISTRY.get() log.debug( "Looking up opener in pyopener_open: urlpath=%r, registry=%r, key=%r", urlpath, registry, key ) try: file_opener = registry[key] except KeyError as err: errmsg = f"Opener not found: {repr(err)}".encode("utf-8") CPLError(CE_Failure, 4, "%s", errmsg) return NULL cdef object file_obj try: file_obj = file_opener.open(urlpath, mode) except ValueError as err: # ZipFile.open doesn't accept binary modes like "rb" and will # raise ValueError if given one. We strip the mode in this case. try: file_obj = file_opener.open(urlpath, mode.rstrip("b")) except Exception as err: return NULL except Exception as err: return NULL log.debug("Opened file object: file_obj=%r, mode=%r", file_obj, mode) # Before we return, we attempt to enter the file object's context # and store an exit callback stack for it. stack = contextlib.ExitStack() try: file_obj = stack.enter_context(file_obj) except (AttributeError, TypeError) as err: errmsg = f"Opener failed to open file with arguments ({repr(urlpath)}, {repr(mode)}): {repr(err)}".encode("utf-8") CPLError(CE_Failure, 4, "%s", errmsg) return NULL except FileNotFoundError as err: errmsg = "OpenFile didn't resolve".encode("utf-8") return NULL else: exit_stacks = _OPEN_FILE_EXIT_STACKS.get({}) exit_stacks[file_obj] = stack _OPEN_FILE_EXIT_STACKS.set(exit_stacks) return file_obj cdef int pyopener_eof(void *pFile) with gil: cdef object file_obj = pFile if file_obj.read(1): file_obj.seek(-1, 1) return 1 else: return 0 cdef vsi_l_offset pyopener_tell(void *pFile) with gil: cdef object file_obj = pFile return file_obj.tell() cdef int pyopener_seek(void *pFile, vsi_l_offset nOffset, int nWhence) with gil: cdef object file_obj = pFile # TODO: Add "seekable" check? file_obj.seek(nOffset, nWhence) return 0 cdef size_t pyopener_read(void *pFile, void *pBuffer, size_t nSize, size_t nCount) with gil: cdef object file_obj = pFile cdef bytes python_data = file_obj.read(nSize * nCount) cdef int num_bytes = len(python_data) # NOTE: We have to cast to char* first, otherwise Cython doesn't do the conversion properly memcpy(pBuffer, python_data, num_bytes) return (num_bytes / nSize) cdef size_t pyopener_write(void *pFile, void *pBuffer, size_t nSize, size_t nCount) with gil: if pBuffer == NULL: return -1 cdef object file_obj = pFile buffer_len = nSize * nCount cdef unsigned char [:] buff_view = pBuffer log.debug( "Writing data: file_obj=%r, buff_view=%r, buffer_len=%r", file_obj, buff_view, buffer_len ) try: num = file_obj.write(buff_view) except TypeError: num = file_obj.write(str(buff_view)) return (num // nSize) cdef int pyopener_flush(void *pFile) with gil: cdef object file_obj = pFile log.debug("Flushing: file_obj=%r", file_obj) try: file_obj.flush() return 0 except AttributeError: return 1 cdef int pyopener_truncate(void *pFile, vsi_l_offset size) with gil: cdef object file_obj = pFile log.debug("Truncating: file_obj=%r, size=%r", file_obj, size) try: file_obj.truncate(size) return 0 except AttributeError: return 1 cdef int pyopener_close(void *pFile) with gil: cdef object file_obj = pFile log.debug("Closing: file_obj=%r", file_obj) exit_stacks = _OPEN_FILE_EXIT_STACKS.get() stack = exit_stacks.pop(file_obj) stack.close() _OPEN_FILE_EXIT_STACKS.set(exit_stacks) return 0 cdef int pyopener_read_multi_range(void *pFile, int nRanges, void **ppData, vsi_l_offset *panOffsets, size_t *panSizes) except -1 with gil: cdef object file_obj = pFile if not hasattr(file_obj, "read_multi_range"): errmsg = "MultiRangeRead not implemented for Opener".encode("utf-8") CPLError(CE_Failure, 1, "%s", errmsg) return -1 # NOTE: Convert panOffsets and panSizes to Python lists cdef list offsets = [int(panOffsets[i]) for i in range(nRanges)] cdef list sizes = [int(panSizes[i]) for i in range(nRanges)] # NOTE: Call the Python method with the converted arguments cdef list python_data = file_obj.read_multi_range(nRanges, offsets, sizes) for i in range(nRanges): memcpy(ppData[i], python_data[i], len(python_data[i])) return 0 @contextlib.contextmanager def _opener_registration(urlpath, obj): cdef char **registered_prefixes = NULL cdef int prefix_index = 0 cdef VSIFilesystemPluginCallbacksStruct *callbacks_struct = NULL cdef FSData fsdata cdef char *path_c = NULL cdef char *uuid_c = NULL # To resolve issue 1406 we add the opener or filesystem id to the # registry key. kpath = Path(urlpath).parent kid = uuid4().hex key = (kpath, kid) path_b = kpath.as_posix().encode("utf-8") path_c = path_b uuid_b = kid.encode("utf-8") uuid_c = uuid_b fsdata = FSData(path_c, uuid_c) namespace = f"{VSI_NS_ROOT}_{kid}" cdef bytes prefix_bytes = f"/{namespace}/".encode("utf-8") # Might raise. opener = to_pyopener(obj) # Before returning we do a quick check that the opener will # plausibly function. try: _ = opener.size("test") except (AttributeError, TypeError, ValueError) as err: raise OpenerRegistrationError(f"Opener is invalid.") from err except Exception: # We expect the path to not resolve. pass registry = _OPENER_REGISTRY.get({}) if key in registry: if registry[key] != opener: raise OpenerRegistrationError(f"Opener already registered for urlpath.") else: try: yield f"/{namespace}/{urlpath}" finally: registry = _OPENER_REGISTRY.get() _ = registry.pop(key, None) _OPENER_REGISTRY.set(registry) else: # Install handler. registered_prefixes = VSIGetFileSystemsPrefixes() prefix_index = CSLFindString(registered_prefixes, prefix_bytes) CSLDestroy(registered_prefixes) if prefix_index < 0: log.debug("Installing Python opener handler plugin: prefix_bytes=%r", prefix_bytes) callbacks_struct = VSIAllocFilesystemPluginCallbacksStruct() callbacks_struct.open = pyopener_open callbacks_struct.eof = pyopener_eof callbacks_struct.tell = pyopener_tell callbacks_struct.seek = pyopener_seek callbacks_struct.read = pyopener_read callbacks_struct.write = pyopener_write callbacks_struct.flush = pyopener_flush callbacks_struct.truncate = pyopener_truncate callbacks_struct.close = pyopener_close callbacks_struct.read_dir = pyopener_read_dir callbacks_struct.stat = pyopener_stat callbacks_struct.unlink = pyopener_unlink if isinstance(opener, MultiByteRangeResource): callbacks_struct.read_multi_range = pyopener_read_multi_range callbacks_struct.pUserData = &fsdata retval = VSIInstallPluginHandler(prefix_bytes, callbacks_struct) VSIFreeFilesystemPluginCallbacksStruct(callbacks_struct) registered_prefixes = VSIGetFileSystemsPrefixes() prefix_index = CSLFindString(registered_prefixes, prefix_bytes) CSLDestroy(registered_prefixes) registry[key] = opener _OPENER_REGISTRY.set(registry) try: yield f"/{namespace}/{urlpath}" finally: registry = _OPENER_REGISTRY.get() _ = registry.pop(key, None) _OPENER_REGISTRY.set(registry) IF (CTE_GDAL_MAJOR_VERSION, CTE_GDAL_MINOR_VERSION) >= (3, 9): retval = VSIRemovePluginHandler(prefix_bytes) class FileContainer(ABC): """An object that can report on and open Python files.""" @abstractmethod def open(self, path: str, mode: str = "r", **kwds): """Get a Python file object for a resource. Parameters ---------- path : str The identifier/locator for a resource within a filesystem. mode : str Opening mode. kwds : dict Opener specific options. Encoding, etc. Returns ------- obj A Python 'file' object with methods read/write, seek, tell, etc. """ pass @abstractmethod def isfile(self, path: str) -> bool: """Test if the resource is a 'file', a sequence of bytes. Parameters ---------- path : str The identifier/locator for a resource within a filesystem. Returns ------- bool """ pass @abstractmethod def isdir(self, path: str) -> bool: """Test if the resource is a 'directory', a container. Parameters ---------- path : str The identifier/locator for a resource within a filesystem. Returns ------- bool """ pass @abstractmethod def ls(self, path: str) -> list[str]: """Get a 'directory' listing. Parameters ---------- path : str The identifier/locator for a directory within a filesystem. Returns ------- list of str List of 'path' paths relative to the directory. """ pass @abstractmethod def mtime(self, path: str) -> int: """Get the mtime of a resource.. Parameters ---------- path : str The identifier/locator for a directory within a filesystem. Returns ------- int Modification timestamp in seconds. """ pass @abstractmethod def rm(self, path: str) -> None: """Remove a resource. Parameters ---------- path : str The identifier/locator for a resource within a filesystem. Returns ------- None """ pass @abstractmethod def size(self, path: str) -> int: """Get the size, in bytes, of a resource.. Parameters ---------- path : str The identifier/locator for a resource within a filesystem. Returns ------- int """ pass class MultiByteRangeResource(ABC): """An object that provides VSIFilesystemPluginReadMultiRangeCallback.""" @abstractmethod def get_byte_ranges(self, offsets: list[int], sizes: list[int]) -> list[bytes]: """Get a sequence of bytes specified by a sequence of ranges.""" pass class MultiByteRangeResourceContainer(FileContainer): """An object that can open a MultiByteRangeResource.""" @abstractmethod def open(self, path: str, **kwds) -> MultiByteRangeResource: """Open the resource at the given path.""" pass class _FileContainer(FileContainer): """Adapts a Python file object to the opener interface.""" def __init__(self, obj): self._obj = obj def open(self, path, mode="r", **kwds): return self._obj(path, mode=mode, **kwds) def isfile(self, path): return True def isdir(self, path): return False def ls(self, path): return [] def mtime(self, path): return 0 def rm(self, path): pass def size(self, path): with self._obj(path) as f: f.seek(0, os.SEEK_END) return f.tell() class _FilesystemContainer(FileContainer): """Adapts an fsspec filesystem object to the opener interface.""" def __init__(self, obj): self._obj = obj def open(self, path, mode="r", **kwds): return self._obj.open(path, mode=mode, **kwds) def isfile(self, path): return self._obj.isfile(path) def isdir(self, path): return self._obj.isdir(path) def ls(self, path): # return value of ls() varies between file and zip fsspec filesystems. return [item if isinstance(item, str) else item["filename"] for item in self._obj.ls(path)] def mtime(self, path): try: mtime = int(self._obj.modified(path).timestamp()) except NotImplementedError: mtime = 0 return mtime def rm(self, path): return self._obj.rm(path) def size(self, path): return self._obj.size(path) class _AltFilesystemContainer(_FilesystemContainer): """Adapts a tiledb virtual filesystem object to the opener interface.""" def isfile(self, path): return self._obj.is_file(path) def isdir(self, path): return self._obj.is_dir(path) def mtime(self, path): return 0 def rm(self, path): self._obj.remove_file(path) def size(self, path): return self._obj.file_size(path) @singledispatch def to_pyopener(obj): """Adapt an object to the Pyopener interface.""" if hasattr(obj, "file_size"): return _AltFilesystemContainer(obj) else: return _FilesystemContainer(obj) @to_pyopener.register(FileContainer) def _(obj): return obj @to_pyopener.register(Callable) def _(obj): return _FileContainer(obj) Fiona-1.10.1/fiona/abc.py000066400000000000000000000001521467206072700150320ustar00rootroot00000000000000"""Abstract base classes.""" from fiona._vsiopener import FileContainer, MultiByteRangeResourceContainer Fiona-1.10.1/fiona/collection.py000066400000000000000000000656561467206072700164640ustar00rootroot00000000000000"""Collections provide file-like access to feature data.""" from contextlib import ExitStack import logging from pathlib import Path import warnings from fiona import compat, vfs from fiona.ogrext import Iterator, ItemsIterator, KeysIterator from fiona.ogrext import Session, WritingSession from fiona.ogrext import buffer_to_virtual_file, remove_virtual_file, GEOMETRY_TYPES from fiona.errors import ( DriverError, DriverSupportError, GDALVersionError, SchemaError, UnsupportedGeometryTypeError, UnsupportedOperation, ) from fiona.logutils import FieldSkipLogFilter from fiona.crs import CRS from fiona._env import get_gdal_release_name, get_gdal_version_tuple from fiona.env import env_ctx_if_needed from fiona.errors import FionaDeprecationWarning from fiona.drvsupport import ( driver_from_extension, supported_drivers, driver_mode_mingdal, _driver_converts_field_type_silently_to_str, _driver_supports_field, ) from fiona._path import _Path, _vsi_path, _parse_path _GDAL_VERSION_TUPLE = get_gdal_version_tuple() _GDAL_RELEASE_NAME = get_gdal_release_name() log = logging.getLogger(__name__) class Collection: """A file-like interface to features of a vector dataset Python text file objects are iterators over lines of a file. Fiona Collections are similar iterators (not lists!) over features represented as GeoJSON-like mappings. """ def __init__( self, path, mode="r", driver=None, schema=None, crs=None, encoding=None, layer=None, vsi=None, archive=None, enabled_drivers=None, crs_wkt=None, ignore_fields=None, ignore_geometry=False, include_fields=None, wkt_version=None, allow_unsupported_drivers=False, **kwargs ): """The required ``path`` is the absolute or relative path to a file, such as '/data/test_uk.shp'. In ``mode`` 'r', data can be read only. In ``mode`` 'a', data can be appended to a file. In ``mode`` 'w', data overwrites the existing contents of a file. In ``mode`` 'w', an OGR ``driver`` name and a ``schema`` are required. A Proj4 ``crs`` string is recommended. If both ``crs`` and ``crs_wkt`` keyword arguments are passed, the latter will trump the former. In 'w' mode, kwargs will be mapped to OGR layer creation options. """ self._closed = True if not isinstance(path, (str, _Path)): raise TypeError(f"invalid path: {path!r}") if not isinstance(mode, str) or mode not in ("r", "w", "a"): raise TypeError(f"invalid mode: {mode!r}") if driver and not isinstance(driver, str): raise TypeError(f"invalid driver: {driver!r}") if schema and not hasattr(schema, "get"): raise TypeError("invalid schema: %r" % schema) # Rasterio's CRS is compatible with Fiona. This class # constructor only requires that the crs value have a to_wkt() # method. if ( crs and not isinstance(crs, compat.DICT_TYPES + (str, CRS)) and not (hasattr(crs, "to_wkt") and callable(crs.to_wkt)) ): raise TypeError("invalid crs: %r" % crs) if crs_wkt and not isinstance(crs_wkt, str): raise TypeError(f"invalid crs_wkt: {crs_wkt!r}") if encoding and not isinstance(encoding, str): raise TypeError(f"invalid encoding: {encoding!r}") if layer and not isinstance(layer, (str, int)): raise TypeError(f"invalid name: {layer!r}") if vsi: if not isinstance(vsi, str) or not vfs.valid_vsi(vsi): raise TypeError(f"invalid vsi: {vsi!r}") if archive and not isinstance(archive, str): raise TypeError(f"invalid archive: {archive!r}") if ignore_fields is not None and include_fields is not None: raise ValueError("Cannot specify both 'ignore_fields' and 'include_fields'") if mode == "w" and driver is None: driver = driver_from_extension(path) # Check GDAL version against drivers if ( driver in driver_mode_mingdal[mode] and get_gdal_version_tuple() < driver_mode_mingdal[mode][driver] ): min_gdal_version = ".".join( list(map(str, driver_mode_mingdal[mode][driver])) ) raise DriverError( f"{driver} driver requires at least GDAL {min_gdal_version} " f"for mode '{mode}', " f"Fiona was compiled against: {get_gdal_release_name()}" ) self.session = None self.iterator = None self._len = 0 self._bounds = None self._driver = None self._schema = None self._crs = None self._crs_wkt = None self.enabled_drivers = enabled_drivers self.include_fields = include_fields self.ignore_fields = ignore_fields self.ignore_geometry = bool(ignore_geometry) self._allow_unsupported_drivers = allow_unsupported_drivers self._closed = True # Check GDAL version against drivers if ( driver in driver_mode_mingdal[mode] and get_gdal_version_tuple() < driver_mode_mingdal[mode][driver] ): min_gdal_version = ".".join( list(map(str, driver_mode_mingdal[mode][driver])) ) raise DriverError( f"{driver} driver requires at least GDAL {min_gdal_version} " f"for mode '{mode}', " f"Fiona was compiled against: {get_gdal_release_name()}" ) if vsi: self.path = vfs.vsi_path(path, vsi, archive) path = _parse_path(self.path) else: path = _parse_path(path) self.path = _vsi_path(path) self.layer = layer or 0 if mode == "w": if layer and not isinstance(layer, str): raise ValueError("in 'w' mode, layer names must be strings") self.name = layer or Path(self.path).stem else: self.name = 0 if layer is None else layer or Path(self.path).stem self.mode = mode if self.mode == "w": if driver == "Shapefile": driver = "ESRI Shapefile" if not driver: raise DriverError("no driver") if not allow_unsupported_drivers: if driver not in supported_drivers: raise DriverError(f"unsupported driver: {driver!r}") if self.mode not in supported_drivers[driver]: raise DriverError(f"unsupported mode: {self.mode!r}") self._driver = driver if not schema: raise SchemaError("no schema") if "properties" in schema: # Make properties as a dict built-in this_schema = schema.copy() this_schema["properties"] = dict(schema["properties"]) schema = this_schema else: schema["properties"] = {} if "geometry" not in schema: schema["geometry"] = None self._schema = schema self._check_schema_driver_support() if crs_wkt or crs: self._crs_wkt = CRS.from_user_input(crs_wkt or crs).to_wkt( version=wkt_version ) self._driver = driver kwargs.update(encoding=encoding) self.encoding = encoding try: if self.mode == "r": self.session = Session() self.session.start(self, **kwargs) elif self.mode in ("a", "w"): self.session = WritingSession() self.session.start(self, **kwargs) except OSError: self.session = None raise if self.session is not None: self.guard_driver_mode() if self.mode in ("a", "w"): self._valid_geom_types = _get_valid_geom_types(self.schema, self.driver) self.field_skip_log_filter = FieldSkipLogFilter() self._env = ExitStack() self._closed = False def __repr__(self): return "<{} Collection '{}', mode '{}' at {}>".format( self.closed and "closed" or "open", self.path + ":" + str(self.name), self.mode, hex(id(self)), ) def guard_driver_mode(self): if not self._allow_unsupported_drivers: driver = self.session.get_driver() if driver not in supported_drivers: raise DriverError(f"unsupported driver: {driver!r}") if self.mode not in supported_drivers[driver]: raise DriverError(f"unsupported mode: {self.mode!r}") @property def driver(self): """Returns the name of the proper OGR driver.""" if not self._driver and self.mode in ("a", "r") and self.session: self._driver = self.session.get_driver() return self._driver @property def schema(self): """Returns a mapping describing the data schema. The mapping has 'geometry' and 'properties' items. The former is a string such as 'Point' and the latter is an ordered mapping that follows the order of fields in the data file. """ if not self._schema and self.mode in ("a", "r") and self.session: self._schema = self.session.get_schema() return self._schema @property def crs(self): """The coordinate reference system (CRS) of the Collection.""" if self._crs is None and self.session: self._crs = self.session.get_crs() return self._crs @property def crs_wkt(self): """Returns a WKT string.""" if self._crs_wkt is None and self.session: self._crs_wkt = self.session.get_crs_wkt() return self._crs_wkt def tags(self, ns=None): """Returns a dict containing copies of the dataset or layers's tags. Tags are pairs of key and value strings. Tags belong to namespaces. The standard namespaces are: default (None) and 'IMAGE_STRUCTURE'. Applications can create their own additional namespaces. Parameters ---------- ns: str, optional Can be used to select a namespace other than the default. Returns ------- dict """ if _GDAL_VERSION_TUPLE.major < 2: raise GDALVersionError( "tags requires GDAL 2+, fiona was compiled " f"against: {_GDAL_RELEASE_NAME}" ) if self.session: return self.session.tags(ns=ns) return None def get_tag_item(self, key, ns=None): """Returns tag item value Parameters ---------- key: str The key for the metadata item to fetch. ns: str, optional Used to select a namespace other than the default. Returns ------- str """ if _GDAL_VERSION_TUPLE.major < 2: raise GDALVersionError( "get_tag_item requires GDAL 2+, fiona was compiled " f"against: {_GDAL_RELEASE_NAME}" ) if self.session: return self.session.get_tag_item(key=key, ns=ns) return None def update_tags(self, tags, ns=None): """Writes a dict containing the dataset or layers's tags. Tags are pairs of key and value strings. Tags belong to namespaces. The standard namespaces are: default (None) and 'IMAGE_STRUCTURE'. Applications can create their own additional namespaces. Parameters ---------- tags: dict The dict of metadata items to set. ns: str, optional Used to select a namespace other than the default. Returns ------- int """ if _GDAL_VERSION_TUPLE.major < 2: raise GDALVersionError( "update_tags requires GDAL 2+, fiona was compiled " f"against: {_GDAL_RELEASE_NAME}" ) if not isinstance(self.session, WritingSession): raise UnsupportedOperation("Unable to update tags as not in writing mode.") return self.session.update_tags(tags, ns=ns) def update_tag_item(self, key, tag, ns=None): """Updates the tag item value Parameters ---------- key: str The key for the metadata item to set. tag: str The value of the metadata item to set. ns: str, optional Used to select a namespace other than the default. Returns ------- int """ if _GDAL_VERSION_TUPLE.major < 2: raise GDALVersionError( "update_tag_item requires GDAL 2+, fiona was compiled " f"against: {_GDAL_RELEASE_NAME}" ) if not isinstance(self.session, WritingSession): raise UnsupportedOperation("Unable to update tag as not in writing mode.") return self.session.update_tag_item(key=key, tag=tag, ns=ns) @property def meta(self): """Returns a mapping with the driver, schema, crs, and additional properties.""" return { "driver": self.driver, "schema": self.schema, "crs": self.crs, "crs_wkt": self.crs_wkt, } profile = meta def filter(self, *args, **kwds): """Returns an iterator over records, but filtered by a test for spatial intersection with the provided ``bbox``, a (minx, miny, maxx, maxy) tuple or a geometry ``mask``. An attribute filter can be set using an SQL ``where`` clause, which uses the `OGR SQL dialect `__. Positional arguments ``stop`` or ``start, stop[, step]`` allows iteration to skip over items or stop at a specific item. Note: spatial filtering using ``mask`` may be inaccurate and returning all features overlapping the envelope of ``mask``. """ if self.closed: raise ValueError("I/O operation on closed collection") elif self.mode != "r": raise OSError("collection not open for reading") if args: s = slice(*args) start = s.start stop = s.stop step = s.step else: start = stop = step = None bbox = kwds.get("bbox") mask = kwds.get("mask") if bbox and mask: raise ValueError("mask and bbox can not be set together") where = kwds.get("where") self.iterator = Iterator(self, start, stop, step, bbox, mask, where) return self.iterator def items(self, *args, **kwds): """Returns an iterator over FID, record pairs, optionally filtered by a test for spatial intersection with the provided ``bbox``, a (minx, miny, maxx, maxy) tuple or a geometry ``mask``. An attribute filter can be set using an SQL ``where`` clause, which uses the `OGR SQL dialect `__. Positional arguments ``stop`` or ``start, stop[, step]`` allows iteration to skip over items or stop at a specific item. Note: spatial filtering using ``mask`` may be inaccurate and returning all features overlapping the envelope of ``mask``. """ if self.closed: raise ValueError("I/O operation on closed collection") elif self.mode != "r": raise OSError("collection not open for reading") if args: s = slice(*args) start = s.start stop = s.stop step = s.step else: start = stop = step = None bbox = kwds.get("bbox") mask = kwds.get("mask") if bbox and mask: raise ValueError("mask and bbox can not be set together") where = kwds.get("where") self.iterator = ItemsIterator(self, start, stop, step, bbox, mask, where) return self.iterator def keys(self, *args, **kwds): """Returns an iterator over FIDs, optionally filtered by a test for spatial intersection with the provided ``bbox``, a (minx, miny, maxx, maxy) tuple or a geometry ``mask``. An attribute filter can be set using an SQL ``where`` clause, which uses the `OGR SQL dialect `__. Positional arguments ``stop`` or ``start, stop[, step]`` allows iteration to skip over items or stop at a specific item. Note: spatial filtering using ``mask`` may be inaccurate and returning all features overlapping the envelope of ``mask``. """ if self.closed: raise ValueError("I/O operation on closed collection") elif self.mode != "r": raise OSError("collection not open for reading") if args: s = slice(*args) start = s.start stop = s.stop step = s.step else: start = stop = step = None bbox = kwds.get("bbox") mask = kwds.get("mask") if bbox and mask: raise ValueError("mask and bbox can not be set together") where = kwds.get("where") self.iterator = KeysIterator(self, start, stop, step, bbox, mask, where) return self.iterator def __contains__(self, fid): return self.session.has_feature(fid) values = filter def __iter__(self): """Returns an iterator over records.""" return self.filter() def __next__(self): """Returns next record from iterator.""" warnings.warn( "Collection.__next__() is buggy and will be removed in " "Fiona 2.0. Switch to `next(iter(collection))`.", FionaDeprecationWarning, stacklevel=2, ) if not self.iterator: iter(self) return next(self.iterator) next = __next__ def __getitem__(self, item): return self.session.__getitem__(item) def get(self, item): return self.session.get(item) def writerecords(self, records): """Stages multiple records for writing to disk.""" if self.closed: raise ValueError("I/O operation on closed collection") if self.mode not in ("a", "w"): raise OSError("collection not open for writing") self.session.writerecs(records, self) self._len = self.session.get_length() self._bounds = None def write(self, record): """Stages a record for writing to disk. Note: Each call of this method will start and commit a unique transaction with the data source. """ self.writerecords([record]) def validate_record(self, record): """Compares the record to the collection's schema. Returns ``True`` if the record matches, else ``False``. """ # Currently we only compare keys of properties, not the types of # values. return set(record["properties"].keys()) == set( self.schema["properties"].keys() ) and self.validate_record_geometry(record) def validate_record_geometry(self, record): """Compares the record's geometry to the collection's schema. Returns ``True`` if the record matches, else ``False``. """ # Shapefiles welcome mixes of line/multis and polygon/multis. # OGR reports these mixed files as type "Polygon" or "LineString" # but will return either these or their multi counterparts when # reading features. if ( self.driver == "ESRI Shapefile" and "Point" not in record["geometry"]["type"] ): return record["geometry"]["type"].lstrip("Multi") == self.schema[ "geometry" ].lstrip("3D ").lstrip("Multi") else: return record["geometry"]["type"] == self.schema["geometry"].lstrip("3D ") def __len__(self): if self._len <= 0 and self.session is not None: self._len = self.session.get_length() if self._len < 0: # Raise TypeError when we don't know the length so that Python # will treat Collection as a generator raise TypeError("Layer does not support counting") return self._len @property def bounds(self): """Returns (minx, miny, maxx, maxy).""" if self._bounds is None and self.session is not None: self._bounds = self.session.get_extent() return self._bounds def _check_schema_driver_support(self): """Check support for the schema against the driver See GH#572 for discussion. """ gdal_version_major = _GDAL_VERSION_TUPLE.major for field in self._schema["properties"].values(): field_type = field.split(":")[0] if not _driver_supports_field(self.driver, field_type): if ( self.driver == "GPKG" and gdal_version_major < 2 and field_type == "datetime" ): raise DriverSupportError( "GDAL 1.x GPKG driver does not support datetime fields" ) else: raise DriverSupportError( f"{self.driver} does not support {field_type} fields" ) elif ( field_type in { "time", "datetime", "date", } and _driver_converts_field_type_silently_to_str(self.driver, field_type) ): if ( self._driver == "GeoJSON" and gdal_version_major < 2 and field_type in {"datetime", "date"} ): warnings.warn( "GeoJSON driver in GDAL 1.x silently converts " f"{field_type} to string in non-standard format" ) else: warnings.warn( f"{self.driver} driver silently converts {field_type} " "to string" ) def flush(self): """Flush the buffer.""" if self.session is not None: self.session.sync(self) new_len = self.session.get_length() self._len = new_len > self._len and new_len or self._len self._bounds = None def close(self): """In append or write mode, flushes data to disk, then ends access.""" if not self._closed: if self.session is not None and self.session.isactive(): if self.mode in ("a", "w"): self.flush() log.debug("Flushed buffer") self.session.stop() log.debug("Stopped session") self.session = None self.iterator = None if self._env: self._env.close() self._env = None self._closed = True @property def closed(self): """``False`` if data can be accessed, otherwise ``True``.""" return self._closed def __enter__(self): self._env.enter_context(env_ctx_if_needed()) logging.getLogger("fiona.ogrext").addFilter(self.field_skip_log_filter) return self def __exit__(self, type, value, traceback): logging.getLogger("fiona.ogrext").removeFilter(self.field_skip_log_filter) self.close() def __del__(self): # Note: you can't count on this being called. Call close() explicitly # or use the context manager protocol ("with"). if not self._closed: self.close() ALL_GEOMETRY_TYPES = { geom_type for geom_type in GEOMETRY_TYPES.values() if "3D " not in geom_type and geom_type != "None" } ALL_GEOMETRY_TYPES.add("None") def _get_valid_geom_types(schema, driver): """Returns a set of geometry types the schema will accept""" schema_geom_type = schema["geometry"] if isinstance(schema_geom_type, str) or schema_geom_type is None: schema_geom_type = (schema_geom_type,) valid_types = set() for geom_type in schema_geom_type: geom_type = str(geom_type).lstrip("3D ") if geom_type == "Unknown" or geom_type == "Any": valid_types.update(ALL_GEOMETRY_TYPES) else: if geom_type not in ALL_GEOMETRY_TYPES: raise UnsupportedGeometryTypeError(geom_type) valid_types.add(geom_type) # shapefiles don't differentiate between single/multi geometries, except points if driver == "ESRI Shapefile" and "Point" not in valid_types: for geom_type in list(valid_types): if not geom_type.startswith("Multi"): valid_types.add("Multi" + geom_type) return valid_types def get_filetype(bytesbuf): """Detect compression type of bytesbuf. ZIP only. TODO: add others relevant to GDAL/OGR.""" if bytesbuf[:4].startswith(b"PK\x03\x04"): return "zip" else: return "" class BytesCollection(Collection): """BytesCollection takes a buffer of bytes and maps that to a virtual file that can then be opened by fiona. """ def __init__(self, bytesbuf, **kwds): """Takes buffer of bytes whose contents is something we'd like to open with Fiona and maps it to a virtual file. """ self._closed = True if not isinstance(bytesbuf, bytes): raise ValueError("input buffer must be bytes") # Hold a reference to the buffer, as bad things will happen if # it is garbage collected while in use. self.bytesbuf = bytesbuf # Map the buffer to a file. If the buffer contains a zipfile # we take extra steps in naming the buffer and in opening # it. If the requested driver is for GeoJSON, we append an an # appropriate extension to ensure the driver reads it. filetype = get_filetype(self.bytesbuf) ext = "" if filetype == "zip": ext = ".zip" elif kwds.get("driver") == "GeoJSON": ext = ".json" self.virtual_file = buffer_to_virtual_file(self.bytesbuf, ext=ext) # Instantiate the parent class. super().__init__(self.virtual_file, vsi=filetype, **kwds) self._closed = False def close(self): """Removes the virtual file associated with the class.""" super().close() if self.virtual_file: remove_virtual_file(self.virtual_file) self.virtual_file = None self.bytesbuf = None def __repr__(self): return "<{} BytesCollection '{}', mode '{}' at {}>".format( self.closed and "closed" or "open", self.path + ":" + str(self.name), self.mode, hex(id(self)), ) Fiona-1.10.1/fiona/compat.py000066400000000000000000000004261467206072700155740ustar00rootroot00000000000000from collections import UserDict from collections.abc import Mapping DICT_TYPES = (dict, Mapping, UserDict) def strencode(instr, encoding="utf-8"): try: instr = instr.encode(encoding) except (UnicodeDecodeError, AttributeError): pass return instr Fiona-1.10.1/fiona/crs.pxd000066400000000000000000000003321467206072700152370ustar00rootroot00000000000000include "gdal.pxi" cdef class CRS: cdef OGRSpatialReferenceH _osr cdef object _data cdef object _epsg cdef object _wkt cdef void osr_set_traditional_axis_mapping_strategy(OGRSpatialReferenceH hSrs) Fiona-1.10.1/fiona/crs.pyx000066400000000000000000001057121467206072700152740ustar00rootroot00000000000000# cython: boundscheck=False, embedsignature=True """Coordinate reference systems, the CRS class and supporting functions. A coordinate reference system (CRS) defines how a dataset's pixels map to locations on, for example, a globe or the Earth. A CRS may be local or global. The GIS field shares a number of authority files that define CRS. "EPSG:32618" is the name of a regional CRS from the European Petroleum Survey Group authority file. "OGC:CRS84" is the name of a global CRS from the Open Geospatial Consortium authority. Custom CRS can be described in text using several formats. Rasterio's CRS class is our abstraction for coordinate reference systems. A fiona.Collection's crs property is an instance of CRS. CRS are also used to define transformations between coordinate reference systems. These transformations are performed by the PROJ library. Rasterio does not call PROJ functions directly, but invokes them via calls to GDAL's "OSR*" functions. """ from collections import defaultdict import json import logging import pickle import typing import warnings import re import fiona._env from fiona._err import CPLE_BaseError, CPLE_NotSupportedError from fiona.compat import DICT_TYPES from fiona.errors import CRSError, FionaDeprecationWarning from fiona.enums import WktVersion from fiona._env cimport _safe_osr_release from fiona._err cimport exc_wrap_ogrerr, exc_wrap_int, exc_wrap_pointer log = logging.getLogger(__name__) _RE_PROJ_PARAM = re.compile(r""" \+ # parameter starts with '+' character (?P\w+) # capture parameter name \=? # match both key only and key-value parameters (?P\S+)? # capture all characters up to next space (None if no value) \s*? # consume remaining whitespace, if any """, re.X) cdef void osr_set_traditional_axis_mapping_strategy(OGRSpatialReferenceH hSrs): OSRSetAxisMappingStrategy(hSrs, OAMS_TRADITIONAL_GIS_ORDER) cdef class CRS: """A geographic or projected coordinate reference system. .. versionadded:: 1.9.0 CRS objects may be created by passing PROJ parameters as keyword arguments to the standard constructor or by passing EPSG codes, PROJ mappings, PROJ strings, or WKT strings to the from_epsg, from_dict, from_string, or from_wkt static methods. Examples -------- The from_dict method takes PROJ parameters as keyword arguments. >>> crs = CRS.from_dict(proj="aea") EPSG codes may be used with the from_epsg method. >>> crs = CRS.from_epsg(3005) The from_string method takes a variety of input. >>> crs = CRS.from_string("EPSG:3005") """ def __init__(self, initialdata=None, **kwargs): """Make a CRS from a PROJ dict or mapping. Parameters ---------- initialdata : mapping, optional A dictionary or other mapping kwargs : mapping, optional Another mapping. Will be overlaid on the initialdata. Returns ------- CRS """ cdef CRS tmp self._data = {} self._epsg = None self._wkt = None if initialdata or kwargs: tmp = CRS.from_dict(initialdata=initialdata, **kwargs) self._osr = OSRClone(tmp._osr) self._wkt = tmp._wkt self._data = tmp.data self._epsg = tmp._epsg @property def data(self): """A PROJ4 dict representation of the CRS. """ if not self._data: self._data = self.to_dict() return self._data @property def is_valid(self): """Test that the CRS is a geographic or projected CRS. Returns ------- bool """ return self.is_geographic or self.is_projected @property def is_epsg_code(self): """Test if the CRS is defined by an EPSG code. Returns ------- bool """ try: return bool(self.to_epsg()) except CRSError: return False @property def wkt(self): """An OGC WKT representation of the CRS Returns ------- str """ if not self._wkt: self._wkt = self.to_wkt() return self._wkt @property def is_geographic(self): """Test if the CRS is a geographic coordinate reference system. Returns ------- bool Raises ------ CRSError """ try: return bool(OSRIsGeographic(self._osr) == 1) except CPLE_BaseError as exc: raise CRSError(str(exc)) @property def is_projected(self): """Test if the CRS is a projected coordinate reference system. Returns ------- bool Raises ------ CRSError """ try: return bool(OSRIsProjected(self._osr) == 1) except CPLE_BaseError as exc: raise CRSError(str(exc)) @property def linear_units(self): """Get a short name for the linear units of the CRS. Returns ------- units : str "m", "ft", etc. Raises ------ CRSError """ try: return self.linear_units_factor[0] except CRSError: return "unknown" @property def linear_units_factor(self): """Get linear units and the conversion factor to meters of the CRS. Returns ------- units : str "m", "ft", etc. factor : float Ratio of one unit to one meter. Raises ------ CRSError """ cdef char *units_c = NULL cdef double to_meters try: if self.is_projected: to_meters = OSRGetLinearUnits(self._osr, &units_c) else: raise CRSError("Linear units factor is not defined for non projected CRS") except CPLE_BaseError as exc: raise CRSError(str(exc)) else: units_b = units_c return (units_b.decode('utf-8'), to_meters) @property def units_factor(self): """Get units and the conversion factor of the CRS. Returns ------- units : str "m", "ft", etc. factor : float Ratio of one unit to one radian if the CRS is geographic otherwise, it is to one meter. Raises ------ CRSError """ cdef char *units_c = NULL cdef double factor try: if self.is_geographic: factor = OSRGetAngularUnits(self._osr, &units_c) else: factor = OSRGetLinearUnits(self._osr, &units_c) except CPLE_BaseError as exc: raise CRSError(exc) else: units_b = units_c return (units_b.decode('utf-8'), factor) def to_dict(self, projjson=False): """Convert CRS to a PROJ dict. .. note:: If there is a corresponding EPSG code, it will be used when returning PROJ parameter dict. .. versionadded:: 1.9.0 Parameters ---------- projjson: bool, default=False If True, will convert to PROJ JSON dict (Requites GDAL 3.1+ and PROJ 6.2+). If False, will convert to PROJ parameter dict. Returns ------- dict """ cdef OGRSpatialReferenceH osr = NULL cdef char *proj_c = NULL if projjson: text = self._projjson() return json.loads(text) if text else {} epsg_code = self.to_epsg() if epsg_code: return {"init": f"epsg:{epsg_code}"} else: try: osr = exc_wrap_pointer(OSRClone(self._osr)) exc_wrap_ogrerr(OSRExportToProj4(osr, &proj_c)) except CPLE_BaseError as exc: return {} # raise CRSError(f"The WKT could not be parsed. {exc}" else: proj_b = proj_c proj = proj_b.decode('utf-8') finally: CPLFree(proj_c) _safe_osr_release(osr) def parse(v): try: return int(v) except ValueError: pass try: return float(v) except ValueError: return v rv = {} for param in _RE_PROJ_PARAM.finditer(proj): key, value = param.groups() if key not in all_proj_keys: continue if value is None or value.lower() == "true": rv[key] = True elif value.lower() == "false": continue else: rv[key] = parse(value) return rv def to_proj4(self): """Convert to a PROJ4 representation. Returns ------- str """ return " ".join([f"+{key}={val}" for key, val in self.data.items()]) def to_wkt(self, morph_to_esri_dialect=False, version=None): """Convert to a OGC WKT representation. .. versionadded:: 1.9.0 Parameters ---------- morph_to_esri_dialect : bool, optional Whether or not to morph to the Esri dialect of WKT Only applies to GDAL versions < 3. This parameter will be removed in a future version of fiona (2.0.0). version : WktVersion or str, optional The version of the WKT output. Defaults to GDAL's default (WKT1_GDAL for GDAL 3). Returns ------- str Raises ------ CRSError """ cdef char *conv_wkt = NULL cdef const char* options_wkt[2] options_wkt[0] = NULL options_wkt[1] = NULL try: if OSRGetName(self._osr) != NULL: if morph_to_esri_dialect: warnings.warn( "'morph_to_esri_dialect' ignored with GDAL 3+. " "Use 'version=WktVersion.WKT1_ESRI' instead." ) if version: version = WktVersion(version).value wkt_format = f"FORMAT={version}".encode("utf-8") options_wkt[0] = wkt_format exc_wrap_ogrerr(OSRExportToWktEx(self._osr, &conv_wkt, options_wkt)) except (CPLE_BaseError, ValueError) as exc: raise CRSError(f"Cannot convert to WKT. {exc}") from exc else: if conv_wkt != NULL: return conv_wkt.decode('utf-8') else: return '' finally: CPLFree(conv_wkt) def to_epsg(self, confidence_threshold=70): """Convert to the best match EPSG code. For a CRS created using an EPSG code, that same value is returned. For other CRS, including custom CRS, an attempt is made to match it to definitions in the EPSG authority file. Matches with a confidence below the threshold are discarded. Parameters ---------- confidence_threshold : int Percent match confidence threshold (0-100). Returns ------- int or None Raises ------ CRSError """ if self._epsg is not None: return self._epsg else: matches = self._matches(confidence_threshold=confidence_threshold) if "EPSG" in matches: self._epsg = int(matches["EPSG"][0]) return self._epsg else: return None def to_authority(self, confidence_threshold=70): """Convert to the best match authority name and code. For a CRS created using an EPSG code, that same value is returned. For other CRS, including custom CRS, an attempt is made to match it to definitions in authority files. Matches with a confidence below the threshold are discarded. Parameters ---------- confidence_threshold : int Percent match confidence threshold (0-100). Returns ------- name : str Authority name. code : str Code from the authority file. or None """ matches = self._matches(confidence_threshold=confidence_threshold) # Note: before version 1.2.7 this function only paid attention # to EPSG as an authority, which is why it takes priority over # others even if they were a better match. if "EPSG" in matches: return "EPSG", matches["EPSG"][0] elif "OGC" in matches: return "OGC", matches["OGC"][0] elif "ESRI" in matches: return "ESRI", matches["ESRI"][0] else: return None def _matches(self, confidence_threshold=70): """Find matches in authority files. Returns ------- dict : {name: [codes]} A dictionary in which capitalized authority names are the keys and lists of codes ordered by match confidence, descending, are the values. """ cdef OGRSpatialReferenceH osr = NULL cdef OGRSpatialReferenceH *matches = NULL cdef int *confidences = NULL cdef int num_matches = 0 cdef int i = 0 cdef char *c_code = NULL cdef char *c_name = NULL results = defaultdict(list) try: osr = exc_wrap_pointer(OSRClone(self._osr)) matches = OSRFindMatches(osr, NULL, &num_matches, &confidences) for i in range(num_matches): confidence = confidences[i] c_code = OSRGetAuthorityCode(matches[i], NULL) c_name = OSRGetAuthorityName(matches[i], NULL) if c_code != NULL and c_name != NULL and confidence >= confidence_threshold: code = c_code.decode('utf-8') name = c_name.decode('utf-8') results[name].append(code) return results finally: _safe_osr_release(osr) OSRFreeSRSArray(matches) CPLFree(confidences) def to_string(self): """Convert to a PROJ4 or WKT string. The output will be reduced as much as possible by attempting a match to CRS defined in authority files. Notes ----- Mapping keys are tested against the ``all_proj_keys`` list. Values of ``True`` are omitted, leaving the key bare: {'no_defs': True} -> "+no_defs" and items where the value is otherwise not a str, int, or float are omitted. Returns ------- str Raises ------ CRSError """ auth = self.to_authority() if auth: return ":".join(auth) else: return self.to_wkt() or self.to_proj4() @staticmethod def from_epsg(code): """Make a CRS from an EPSG code. Parameters ---------- code : int or str An EPSG code. Strings will be converted to integers. Notes ----- The input code is not validated against an EPSG database. Returns ------- CRS Raises ------ CRSError """ cdef CRS obj = CRS.__new__(CRS) try: code = int(code) except OverflowError as err: raise CRSError(f"Not in the range of valid EPSG codes: {code}") from err except TypeError as err: raise CRSError(f"Not a valid EPSG codes: {code}") from err if code <= 0: raise CRSError("EPSG codes are positive integers") try: exc_wrap_ogrerr(exc_wrap_int(OSRImportFromEPSG(obj._osr, code))) except OverflowError as err: raise CRSError(f"Not in the range of valid EPSG codes: {code}") from err except CPLE_BaseError as exc: raise CRSError(f"The EPSG code is unknown. {exc}") else: osr_set_traditional_axis_mapping_strategy(obj._osr) obj._epsg = code return obj @staticmethod def from_proj4(proj): """Make a CRS from a PROJ4 string. Parameters ---------- proj : str A PROJ4 string like "+proj=longlat ..." Returns ------- CRS Raises ------ CRSError """ cdef CRS obj = CRS.__new__(CRS) # Filter out nonsensical items that might have crept in. items_filtered = [] for param in _RE_PROJ_PARAM.finditer(proj): value = param.group('value') if value is None: items_filtered.append(param.group()) elif value.lower() == "false": continue else: items_filtered.append(param.group()) proj = ' '.join(items_filtered) proj_b = proj.encode('utf-8') try: exc_wrap_ogrerr(exc_wrap_int(OSRImportFromProj4(obj._osr, proj_b))) except CPLE_BaseError as exc: raise CRSError(f"The PROJ4 dict could not be understood. {exc}") else: osr_set_traditional_axis_mapping_strategy(obj._osr) return obj @staticmethod def from_dict(initialdata=None, **kwargs): """Make a CRS from a dict of PROJ parameters or PROJ JSON. Parameters ---------- initialdata : mapping, optional A dictionary or other mapping kwargs : mapping, optional Another mapping. Will be overlaid on the initialdata. Returns ------- CRS Raises ------ CRSError """ if initialdata is not None: data = dict(initialdata.items()) else: data = {} data.update(**kwargs) if not ("init" in data or "proj" in data): # We've been given a PROJ JSON-encoded text. return CRS.from_user_input(json.dumps(data)) # "+init=epsg:xxxx" is deprecated in GDAL. If we find this, we will # extract the epsg code and dispatch to from_epsg. if 'init' in data and data['init'].lower().startswith('epsg:'): epsg_code = int(data['init'].split(':')[1]) return CRS.from_epsg(epsg_code) # Continue with the general case. pjargs = [] for key in data.keys() & all_proj_keys: val = data[key] if val is None or val is True: pjargs.append(f"+{key}") elif val is False: pass else: pjargs.append(f"+{key}={val}") proj = ' '.join(pjargs) b_proj = proj.encode('utf-8') cdef CRS obj = CRS.__new__(CRS) try: exc_wrap_ogrerr(OSRImportFromProj4(obj._osr, b_proj)) except CPLE_BaseError as exc: raise CRSError(f"The PROJ4 dict could not be understood. {exc}") else: osr_set_traditional_axis_mapping_strategy(obj._osr) return obj @staticmethod def from_wkt(wkt, morph_from_esri_dialect=False): """Make a CRS from a WKT string. Parameters ---------- wkt : str A WKT string. morph_from_esri_dialect : bool, optional If True, items in the input using Esri's dialect of WKT will be replaced by OGC standard equivalents. Returns ------- CRS Raises ------ CRSError """ cdef char *wkt_c = NULL if not isinstance(wkt, str): raise ValueError("A string is expected") wkt_b= wkt.encode('utf-8') wkt_c = wkt_b cdef CRS obj = CRS.__new__(CRS) try: errcode = exc_wrap_ogrerr(OSRImportFromWkt(obj._osr, &wkt_c)) except CPLE_BaseError as exc: raise CRSError(f"The WKT could not be parsed. {exc}") else: osr_set_traditional_axis_mapping_strategy(obj._osr) return obj @staticmethod def from_user_input(value, morph_from_esri_dialect=False): """Make a CRS from a variety of inputs. Parameters ---------- value : object User input of many different kinds. morph_from_esri_dialect : bool, optional If True, items in the input using Esri's dialect of WKT will be replaced by OGC standard equivalents. Returns ------- CRS Raises ------ CRSError """ cdef const char *text_c = NULL cdef CRS obj if isinstance(value, CRS): return value elif hasattr(value, "to_wkt") and callable(value.to_wkt): return CRS.from_wkt(value.to_wkt(), morph_from_esri_dialect=morph_from_esri_dialect) elif isinstance(value, int): return CRS.from_epsg(value) elif isinstance(value, DICT_TYPES): return CRS(**value) elif isinstance(value, str): text_b = value.encode('utf-8') text_c = text_b obj = CRS.__new__(CRS) try: errcode = exc_wrap_ogrerr(OSRSetFromUserInput(obj._osr, text_c)) except CPLE_BaseError as exc: raise CRSError(f"The WKT could not be parsed. {exc}") else: osr_set_traditional_axis_mapping_strategy(obj._osr) return obj else: raise CRSError(f"CRS is invalid: {value!r}") @staticmethod def from_authority(auth_name, code): """Make a CRS from an authority name and code. .. versionadded:: 1.9.0 Parameters ---------- auth_name: str The name of the authority. code : int or str The code used by the authority. Returns ------- CRS Raises ------ CRSError """ return CRS.from_string(f"{auth_name}:{code}") @staticmethod def from_string(value, morph_from_esri_dialect=False): """Make a CRS from an EPSG, PROJ, or WKT string Parameters ---------- value : str An EPSG, PROJ, or WKT string. morph_from_esri_dialect : bool, optional If True, items in the input using Esri's dialect of WKT will be replaced by OGC standard equivalents. Returns ------- CRS Raises ------ CRSError """ try: value = value.strip() except AttributeError: pass if not value: raise CRSError(f"CRS is empty or invalid: {value!r}") elif value.upper().startswith('EPSG:') and "+" not in value: auth, val = value.split(':') if not val: raise CRSError(f"Invalid CRS: {value!r}") return CRS.from_epsg(val) elif value.startswith('{') or value.startswith('['): # may be json, try to decode it try: val = json.loads(value, strict=False) except ValueError: raise CRSError('CRS appears to be JSON but is not valid') if not val: raise CRSError("CRS is empty JSON") else: return CRS.from_dict(**val) elif value.endswith("]"): return CRS.from_wkt(value, morph_from_esri_dialect=morph_from_esri_dialect) elif "=" in value: return CRS.from_proj4(value) else: return CRS.from_user_input(value, morph_from_esri_dialect=morph_from_esri_dialect) def __cinit__(self): self._osr = OSRNewSpatialReference(NULL) def __dealloc__(self): _safe_osr_release(self._osr) def __hash__(self): return hash(self.wkt) def __getitem__(self, item): return self.data[item] def __iter__(self): return iter(self.data) def __len__(self): return len(self.data) def get(self, item): return self.data.get(item) def items(self): return self.data.items() def keys(self): return self.data.keys() def values(self): return self.data.values() def __bool__(self): return bool(self.wkt) __nonzero__ = __bool__ def __getstate__(self): return self.to_wkt() def __setstate__(self, state): cdef CRS tmp tmp = CRS.from_wkt(state) self._osr = OSRClone(tmp._osr) self._wkt = tmp._wkt self._data = tmp.data self._epsg = tmp._epsg def __copy__(self): return pickle.loads(pickle.dumps(self)) def __str__(self): return self.to_string() def __repr__(self): epsg_code = self.to_epsg() if epsg_code: return f"CRS.from_epsg({epsg_code})" else: return f"CRS.from_wkt('{self.wkt}')" def __eq__(self, other): cdef OGRSpatialReferenceH osr_s = NULL cdef OGRSpatialReferenceH osr_o = NULL cdef CRS crs_o try: crs_o = CRS.from_user_input(other) except CRSError: return False epsg_s = self.to_epsg() epsg_o = crs_o.to_epsg() if epsg_s is not None and epsg_o is not None and epsg_s == epsg_o: return True else: try: osr_s = exc_wrap_pointer(OSRClone(self._osr)) osr_o = exc_wrap_pointer(OSRClone(crs_o._osr)) return bool(OSRIsSame(osr_s, osr_o) == 1) finally: _safe_osr_release(osr_s) _safe_osr_release(osr_o) def _matches(self, confidence_threshold=70): """Find matches in authority files. Parameters ---------- confidence_threshold : int Percent match confidence threshold (0-100). Returns ------- dict : {name: [codes]} A dictionary in which capitalized authority names are the keys and lists of codes ordered by match confidence, descending, are the values. """ cdef OGRSpatialReferenceH osr = NULL cdef OGRSpatialReferenceH *matches = NULL cdef int *confidences = NULL cdef int num_matches = 0 cdef int i = 0 cdef char *c_code = NULL cdef char *c_name = NULL results = defaultdict(list) try: osr = exc_wrap_pointer(OSRClone(self._osr)) matches = OSRFindMatches(osr, NULL, &num_matches, &confidences) for i in range(num_matches): confidence = confidences[i] c_code = OSRGetAuthorityCode(matches[i], NULL) c_name = OSRGetAuthorityName(matches[i], NULL) if c_code != NULL and c_name != NULL and confidence >= confidence_threshold: code = c_code.decode('utf-8') name = c_name.decode('utf-8') results[name].append(code) return results finally: _safe_osr_release(osr) OSRFreeSRSArray(matches) CPLFree(confidences) def _projjson(self): """Get a PROJ JSON representation. For internal use only. .. versionadded:: 1.9.0 .. note:: Requires GDAL 3.1+ and PROJ 6.2+ Returns ------- projjson : str PROJ JSON-encoded text. Raises ------ CRSError """ cdef char *conv_json = NULL cdef const char* options[2] try: if OSRGetName(self._osr) != NULL: options[0] = b"MULTILINE=NO" options[1] = NULL exc_wrap_ogrerr(OSRExportToPROJJSON(self._osr, &conv_json, options)) except CPLE_BaseError as exc: raise CRSError(f"Cannot convert to PROJ JSON. {exc}") else: if conv_json != NULL: return conv_json.decode('utf-8') else: return '' finally: CPLFree(conv_json) def epsg_treats_as_latlong(input_crs): """Test if the CRS is in latlon order .. versionadded:: 1.9.0 From GDAL docs: > This method returns TRUE if EPSG feels this geographic coordinate system should be treated as having lat/long coordinate ordering. > Currently this returns TRUE for all geographic coordinate systems with an EPSG code set, and axes set defining it as lat, long. > FALSE will be returned for all coordinate systems that are not geographic, or that do not have an EPSG code set. > **Note** > Important change of behavior since GDAL 3.0. In previous versions, geographic CRS imported with importFromEPSG() would cause this method to return FALSE on them, whereas now it returns TRUE, since importFromEPSG() is now equivalent to importFromEPSGA(). Parameters ---------- input_crs : CRS Coordinate reference system, as a fiona CRS object Example: CRS({'init': 'EPSG:4326'}) Returns ------- bool """ cdef CRS crs if not isinstance(input_crs, CRS): crs = CRS.from_user_input(input_crs) else: crs = input_crs try: return bool(OSREPSGTreatsAsLatLong(crs._osr) == 1) except CPLE_BaseError as exc: raise CRSError(str(exc)) def epsg_treats_as_northingeasting(input_crs): """Test if the CRS should be treated as having northing/easting coordinate ordering .. versionadded:: 1.9.0 From GDAL docs: > This method returns TRUE if EPSG feels this projected coordinate system should be treated as having northing/easting coordinate ordering. > Currently this returns TRUE for all projected coordinate systems with an EPSG code set, and axes set defining it as northing, easting. > FALSE will be returned for all coordinate systems that are not projected, or that do not have an EPSG code set. > **Note** > Important change of behavior since GDAL 3.0. In previous versions, projected CRS with northing, easting axis order imported with importFromEPSG() would cause this method to return FALSE on them, whereas now it returns TRUE, since importFromEPSG() is now equivalent to importFromEPSGA(). Parameters ---------- input_crs : CRS Coordinate reference system, as a fiona CRS object Example: CRS({'init': 'EPSG:4326'}) Returns ------- bool """ cdef CRS crs if not isinstance(input_crs, CRS): crs = CRS.from_user_input(input_crs) else: crs = input_crs try: return bool(OSREPSGTreatsAsNorthingEasting(crs._osr) == 1) except CPLE_BaseError as exc: raise CRSError(str(exc)) # Below is the big list of PROJ4 parameters from # http://trac.osgeo.org/proj/wiki/GenParms. # It is parsed into a list of parameter keys ``all_proj_keys``. _param_data = """ +a Semimajor radius of the ellipsoid axis +alpha ? Used with Oblique Mercator and possibly a few others +axis Axis orientation (new in 4.8.0) +b Semiminor radius of the ellipsoid axis +datum Datum name (see `proj -ld`) +ellps Ellipsoid name (see `proj -le`) +init Initialize from a named CRS +k Scaling factor (old name) +k_0 Scaling factor (new name) +lat_0 Latitude of origin +lat_1 Latitude of first standard parallel +lat_2 Latitude of second standard parallel +lat_ts Latitude of true scale +lon_0 Central meridian +lonc ? Longitude used with Oblique Mercator and possibly a few others +lon_wrap Center longitude to use for wrapping (see below) +nadgrids Filename of NTv2 grid file to use for datum transforms (see below) +no_defs Don't use the /usr/share/proj/proj_def.dat defaults file +over Allow longitude output outside -180 to 180 range, disables wrapping (see below) +pm Alternate prime meridian (typically a city name, see below) +proj Projection name (see `proj -l`) +south Denotes southern hemisphere UTM zone +to_meter Multiplier to convert map units to 1.0m +towgs84 3 or 7 term datum transform parameters (see below) +units meters, US survey feet, etc. +vto_meter vertical conversion to meters. +vunits vertical units. +x_0 False easting +y_0 False northing +zone UTM zone +a Semimajor radius of the ellipsoid axis +alpha ? Used with Oblique Mercator and possibly a few others +azi +b Semiminor radius of the ellipsoid axis +belgium +beta +czech +e Eccentricity of the ellipsoid = sqrt(1 - b^2/a^2) = sqrt( f*(2-f) ) +ellps Ellipsoid name (see `proj -le`) +es Eccentricity of the ellipsoid squared +f Flattening of the ellipsoid (often presented as an inverse, e.g. 1/298) +gamma +geoc +guam +h +k Scaling factor (old name) +K +k_0 Scaling factor (new name) +lat_0 Latitude of origin +lat_1 Latitude of first standard parallel +lat_2 Latitude of second standard parallel +lat_b +lat_t +lat_ts Latitude of true scale +lon_0 Central meridian +lon_1 +lon_2 +lonc ? Longitude used with Oblique Mercator and possibly a few others +lsat +m +M +n +no_cut +no_off +no_rot +ns +o_alpha +o_lat_1 +o_lat_2 +o_lat_c +o_lat_p +o_lon_1 +o_lon_2 +o_lon_c +o_lon_p +o_proj +over +p +path +proj Projection name (see `proj -l`) +q +R +R_a +R_A Compute radius such that the area of the sphere is the same as the area of the ellipsoid +rf Reciprocal of the ellipsoid flattening term (e.g. 298) +R_g +R_h +R_lat_a +R_lat_g +rot +R_V +s +south Denotes southern hemisphere UTM zone +sym +t +theta +tilt +to_meter Multiplier to convert map units to 1.0m +units meters, US survey feet, etc. +vopt +W +westo +wktext +x_0 False easting +y_0 False northing +zone UTM zone """ all_proj_keys = set(line.split(' ', 1)[0][1:] for line in filter(None, _param_data.splitlines())) all_proj_keys.add('no_mayo') def from_epsg(val): """Given an integer code, returns an EPSG-like mapping. .. deprecated:: 1.9.0 This function will be removed in version 2.0. Please use CRS.from_epsg() instead. """ warnings.warn( "This function will be removed in version 2.0. Please use CRS.from_epsg() instead.", FionaDeprecationWarning, stacklevel=2, ) return CRS.from_epsg(val) def from_string(val): """Turn a PROJ.4 string into a mapping of parameters. .. deprecated:: 1.9.0 This function will be removed in version 2.0. Please use CRS.from_string() instead. """ warnings.warn( "This function will be removed in version 2.0. Please use CRS.from_string() instead.", FionaDeprecationWarning, stacklevel=2, ) return CRS.from_string(val) def to_string(val): """Turn a parameter mapping into a more conventional PROJ.4 string. .. deprecated:: 1.9.0 This function will be removed in version 2.0. Please use CRS.to_string() instead. """ warnings.warn( "This function will be removed in version 2.0. Please use CRS.to_string() instead.", FionaDeprecationWarning, stacklevel=2, ) return CRS.from_user_input(val).to_string() Fiona-1.10.1/fiona/drvsupport.py000066400000000000000000000333671467206072700165530ustar00rootroot00000000000000import os from fiona.env import Env from fiona._env import get_gdal_version_tuple _GDAL_VERSION = get_gdal_version_tuple() # Here is the list of available drivers as (name, modes) tuples. Currently, # we only expose the defaults (excepting FileGDB). We also don't expose # the CSV or GeoJSON drivers. Use Python's csv and json modules instead. # Might still exclude a few more of these after making a pass through the # entries for each at https://gdal.org/drivers/vector/index.html to screen # out the multi-layer formats. supported_drivers = dict( [ # OGR Vector Formats # Format Name Code Creation Georeferencing Compiled by default # Aeronav FAA files AeronavFAA No Yes Yes ("AeronavFAA", "r"), # ESRI ArcObjects ArcObjects No Yes No, needs ESRI ArcObjects # Arc/Info Binary Coverage AVCBin No Yes Yes # multi-layer # ("AVCBin", "r"), # Arc/Info .E00 (ASCII) Coverage AVCE00 No Yes Yes # multi-layer # ("AVCE00", "r"), # Arc/Info Generate ARCGEN No No Yes ("ARCGEN", "r"), # Atlas BNA BNA Yes No Yes ("BNA", "rw"), # AutoCAD DWG DWG No No No # AutoCAD DXF DXF Yes No Yes ("DXF", "rw"), # Comma Separated Value (.csv) CSV Yes No Yes ("CSV", "raw"), # CouchDB / GeoCouch CouchDB Yes Yes No, needs libcurl # DODS/OPeNDAP DODS No Yes No, needs libdap # EDIGEO EDIGEO No Yes Yes # multi-layer? Hard to tell from the OGR docs # ("EDIGEO", "r"), # ElasticSearch ElasticSearch Yes (write-only) - No, needs libcurl # ESRI FileGDB FileGDB Yes Yes No, needs FileGDB API library # multi-layer ("FileGDB", "raw"), ("OpenFileGDB", "raw"), # ESRI Personal GeoDatabase PGeo No Yes No, needs ODBC library # ESRI ArcSDE SDE No Yes No, needs ESRI SDE # ESRIJSON ESRIJSON No Yes Yes ("ESRIJSON", "r"), # ESRI Shapefile ESRI Shapefile Yes Yes Yes ("ESRI Shapefile", "raw"), # FMEObjects Gateway FMEObjects Gateway No Yes No, needs FME ("FlatGeobuf", "raw"), # GeoJSON GeoJSON Yes Yes Yes ("GeoJSON", "raw"), # GeoJSONSeq GeoJSON sequences Yes Yes Yes ("GeoJSONSeq", "raw"), # Géoconcept Export Geoconcept Yes Yes Yes # multi-layers # ("Geoconcept", "raw"), # Geomedia .mdb Geomedia No No No, needs ODBC library # GeoPackage GPKG Yes Yes No, needs libsqlite3 ("GPKG", "raw"), # GeoRSS GeoRSS Yes Yes Yes (read support needs libexpat) # Google Fusion Tables GFT Yes Yes No, needs libcurl # GML GML Yes Yes Yes (read support needs Xerces or libexpat) ("GML", "rw"), # GMT GMT Yes Yes Yes ("GMT", "rw"), # GMT renamed to OGR_GMT for GDAL 2.x ("OGR_GMT", "rw"), # GPSBabel GPSBabel Yes Yes Yes (needs GPSBabel and GPX driver) # GPX GPX Yes Yes Yes (read support needs libexpat) ("GPX", "rw"), # GRASS GRASS No Yes No, needs libgrass # GPSTrackMaker (.gtm, .gtz) GPSTrackMaker Yes Yes Yes # ("GPSTrackMaker", "rw"), # Hydrographic Transfer Format HTF No Yes Yes # TODO: Fiona is not ready for multi-layer formats: ("HTF", "r"), # Idrisi Vector (.VCT) Idrisi No Yes Yes ("Idrisi", "r"), # Informix DataBlade IDB Yes Yes No, needs Informix DataBlade # INTERLIS "Interlis 1" and "Interlis 2" Yes Yes No, needs Xerces (INTERLIS model reading needs ili2c.jar) # INGRES INGRES Yes No No, needs INGRESS # KML KML Yes Yes Yes (read support needs libexpat) # LIBKML LIBKML Yes Yes No, needs libkml # Mapinfo File MapInfo File Yes Yes Yes ("MapInfo File", "raw"), # Microstation DGN DGN Yes No Yes ("DGN", "raw"), # Access MDB (PGeo and Geomedia capable) MDB No Yes No, needs JDK/JRE # Memory Memory Yes Yes Yes # MySQL MySQL No Yes No, needs MySQL library # NAS - ALKIS NAS No Yes No, needs Xerces # Oracle Spatial OCI Yes Yes No, needs OCI library # ODBC ODBC No Yes No, needs ODBC library # MS SQL Spatial MSSQLSpatial Yes Yes No, needs ODBC library # Open Document Spreadsheet ODS Yes No No, needs libexpat # OGDI Vectors (VPF, VMAP, DCW) OGDI No Yes No, needs OGDI library # OpenAir OpenAir No Yes Yes # multi-layer # ("OpenAir", "r"), # (Geo)Parquet ("Parquet", "rw"), # PCI Geomatics Database File PCIDSK No No Yes, using internal PCIDSK SDK (from GDAL 1.7.0) ("PCIDSK", "raw"), # PDS PDS No Yes Yes ("PDS", "r"), # PDS renamed to OGR_PDS for GDAL 2.x ("OGR_PDS", "r"), # PGDump PostgreSQL SQL dump Yes Yes Yes # PostgreSQL/PostGIS PostgreSQL/PostGIS Yes Yes No, needs PostgreSQL client library (libpq) # EPIInfo .REC REC No No Yes # S-57 (ENC) S57 No Yes Yes # multi-layer ("S57", "r"), # SDTS SDTS No Yes Yes # multi-layer # ("SDTS", "r"), # SEG-P1 / UKOOA P1/90 SEGUKOOA No Yes Yes # multi-layers # ("SEGUKOOA", "r"), # SEG-Y SEGY No No Yes ("SEGY", "r"), # Norwegian SOSI Standard SOSI No Yes No, needs FYBA library # SQLite/SpatiaLite SQLite Yes Yes No, needs libsqlite3 or libspatialite ("SQLite", "raw"), # SUA SUA No Yes Yes ("SUA", "r"), # SVG SVG No Yes No, needs libexpat ("TileDB", "raw"), # TopoJSON TopoJSON No Yes Yes ("TopoJSON", "r"), # UK .NTF UK. NTF No Yes Yes # multi-layer # ("UK. NTF", "r"), # U.S. Census TIGER/Line TIGER No Yes Yes # multi-layer # ("TIGER", "r"), # VFK data VFK No Yes Yes # multi-layer # ("VFK", "r"), # VRT - Virtual Datasource VRT No Yes Yes # multi-layer # ("VRT", "r"), # OGC WFS (Web Feature Service) WFS Yes Yes No, needs libcurl # MS Excel format XLS No No No, needs libfreexl # Office Open XML spreadsheet XLSX Yes No No, needs libexpat # X-Plane/Flighgear aeronautical data XPLANE No Yes Yes # multi-layer # ("XPLANE", "r") ] ) # Minimal gdal version for different modes driver_mode_mingdal = { "r": {"GPKG": (1, 11, 0), "GeoJSONSeq": (2, 4, 0), "FlatGeobuf": (3, 1, 0)}, "w": { "GPKG": (1, 11, 0), "PCIDSK": (2, 0, 0), "GeoJSONSeq": (2, 4, 0), "FlatGeobuf": (3, 1, 3), "OpenFileGDB": (3, 6, 0), }, "a": { "GPKG": (1, 11, 0), "PCIDSK": (2, 0, 0), "GeoJSON": (2, 1, 0), "GeoJSONSeq": (3, 6, 0), "MapInfo File": (2, 0, 0), "FlatGeobuf": (3, 5, 1), "OpenFileGDB": (3, 6, 0), }, } def _driver_supports_mode(driver, mode): """ Returns True if driver supports mode, False otherwise Note: this function is not part of Fiona's public API. """ if driver not in supported_drivers: return False if mode not in supported_drivers[driver]: return False if driver in driver_mode_mingdal[mode]: if _GDAL_VERSION < driver_mode_mingdal[mode][driver]: return False return True # Removes drivers in the supported_drivers dictionary that the # machine's installation of OGR due to how it is compiled. # OGR may not have optional libraries compiled or installed. def _filter_supported_drivers(): global supported_drivers with Env() as gdalenv: ogrdrv_names = gdalenv.drivers().keys() supported_drivers_copy = supported_drivers.copy() for drv in supported_drivers.keys(): if drv not in ogrdrv_names: del supported_drivers_copy[drv] supported_drivers = supported_drivers_copy _filter_supported_drivers() def vector_driver_extensions(): """ Returns ------- dict: Map of extensions to the driver. """ from fiona.meta import extensions # prevent circular import extension_to_driver = {} for drv, modes in supported_drivers.items(): # update extensions based on driver support for extension in extensions(drv) or (): if "w" in modes: extension_to_driver[extension] = extension_to_driver.get(extension, drv) return extension_to_driver def driver_from_extension(path): """ Attempt to auto-detect driver based on the extension. Parameters ---------- path: str or pathlike object The path to the dataset to write with. Returns ------- str: The name of the driver for the extension. """ try: # in case the path is a file handle # or a partsed path path = path.name except AttributeError: pass driver_extensions = vector_driver_extensions() try: return driver_extensions[os.path.splitext(path)[-1].lstrip(".").lower()] except KeyError: raise ValueError("Unable to detect driver. Please specify driver.") # driver_converts_to_str contains field type, driver combinations that # are silently converted to string None: field type is always converted # to str (2, 0, 0): starting from gdal 2.0 field type is not converted # to string _driver_converts_to_str = { 'time': { 'CSV': None, 'PCIDSK': None, 'GeoJSON': (2, 0, 0), 'GPKG': None, 'GMT': None, 'OGR_GMT': None }, 'datetime': { 'CSV': None, 'PCIDSK': None, 'GeoJSON': (2, 0, 0), 'GML': (3, 1, 0), }, 'date': { 'CSV': None, 'PCIDSK': None, 'GeoJSON': (2, 0, 0), 'GMT': None, 'OGR_GMT': None, 'GML': (3, 1, 0), } } def _driver_converts_field_type_silently_to_str(driver, field_type): """ Returns True if the driver converts the field_type silently to str, False otherwise Note: this function is not part of Fiona's public API. """ if field_type in _driver_converts_to_str and driver in _driver_converts_to_str[field_type]: if _driver_converts_to_str[field_type][driver] is None: return True elif _GDAL_VERSION < _driver_converts_to_str[field_type][driver]: return True return False # None: field type is never supported, (2, 0, 0) field type is supported starting with gdal 2.0 _driver_field_type_unsupported = { "time": { "ESRI Shapefile": None, "GPKG": (2, 0, 0), "GPX": None, "GPSTrackMaker": None, "GML": (3, 1, 0), "DGN": None, "BNA": None, "DXF": None, "PCIDSK": (2, 1, 0), "FileGDB": (3, 5, 0), "FlatGeobuf": None, "OpenFileGDB": None, }, 'datetime': { 'ESRI Shapefile': None, 'GPKG': (2, 0, 0), 'DGN': None, 'BNA': None, 'DXF': None, 'PCIDSK': (2, 1, 0) }, "date": { "GPX": None, "GPSTrackMaker": None, "DGN": None, "BNA": None, "DXF": None, "PCIDSK": (2, 1, 0), "FileGDB": (3, 5, 0), "FlatGeobuf": None, "OpenFileGDB": None, }, } def _driver_supports_field(driver, field_type): """ Returns True if the driver supports the field_type, False otherwise Note: this function is not part of Fiona's public API. """ if field_type in _driver_field_type_unsupported and driver in _driver_field_type_unsupported[field_type]: if _driver_field_type_unsupported[field_type][driver] is None: return False elif _GDAL_VERSION < _driver_field_type_unsupported[field_type][driver]: return False return True # None: field type never supports timezones, (2, 0, 0): field type supports timezones with GDAL 2.0.0 _drivers_not_supporting_timezones = { 'datetime': { 'MapInfo File': None, 'GPKG': (3, 1, 0), 'GPSTrackMaker': (3, 1, 1), 'FileGDB': None, 'SQLite': (2, 4, 0) }, "time": { "MapInfo File": None, "GPKG": None, "GPSTrackMaker": None, "GeoJSON": None, "GeoJSONSeq": None, "GML": None, "CSV": None, "GMT": None, "OGR_GMT": None, "SQLite": None, }, } def _driver_supports_timezones(driver, field_type): """ Returns True if the driver supports timezones for field_type, False otherwise Note: this function is not part of Fiona's public API. """ if field_type in _drivers_not_supporting_timezones and driver in _drivers_not_supporting_timezones[field_type]: if _drivers_not_supporting_timezones[field_type][driver] is None: return False elif _GDAL_VERSION < _drivers_not_supporting_timezones[field_type][driver]: return False return True # None: driver never supports timezones, (2, 0, 0): driver supports timezones with GDAL 2.0.0 _drivers_not_supporting_milliseconds = { "GPSTrackMaker": None, "FileGDB": None, "OpenFileGDB": None, } def _driver_supports_milliseconds(driver): """ Returns True if the driver supports milliseconds, False otherwise Note: this function is not part of Fiona's public API. """ # GDAL 2.0 introduced support for milliseconds if _GDAL_VERSION.major < 2: return False if driver in _drivers_not_supporting_milliseconds: if _drivers_not_supporting_milliseconds[driver] is None: return False elif _drivers_not_supporting_milliseconds[driver] < _GDAL_VERSION: return False return True Fiona-1.10.1/fiona/enums.py000066400000000000000000000014131467206072700154350ustar00rootroot00000000000000"""Enumerations.""" from enum import Enum class WktVersion(Enum): """ .. versionadded:: 1.9.0 Supported CRS WKT string versions. """ #: WKT Version 2 from 2015 WKT2_2015 = "WKT2_2015" #: Alias for latest WKT Version 2 WKT2 = "WKT2" #: WKT Version 2 from 2019 WKT2_2019 = "WKT2_2018" #: WKT Version 1 GDAL Style WKT1_GDAL = "WKT1_GDAL" #: Alias for WKT Version 1 GDAL Style WKT1 = "WKT1" #: WKT Version 1 ESRI Style WKT1_ESRI = "WKT1_ESRI" @classmethod def _missing_(cls, value): if value == "WKT2_2019": # WKT2_2019 alias added in GDAL 3.2, use WKT2_2018 for compatibility return WktVersion.WKT2_2019 raise ValueError(f"Invalid value for WktVersion: {value}") Fiona-1.10.1/fiona/env.py000066400000000000000000000507271467206072700151120ustar00rootroot00000000000000"""Fiona's GDAL/AWS environment""" from functools import wraps, total_ordering from inspect import getfullargspec import logging import os import re import threading import warnings import attr from fiona._env import ( GDALDataFinder, GDALEnv, PROJDataFinder, calc_gdal_version_num, get_gdal_config, get_gdal_release_name, get_gdal_version_num, set_gdal_config, set_proj_data_search_path, ) from fiona.errors import EnvError, FionaDeprecationWarning, GDALVersionError from fiona.session import Session, DummySession class ThreadEnv(threading.local): def __init__(self): self._env = None # Initialises in each thread # When the outermost 'fiona.Env()' executes '__enter__' it # probes the GDAL environment to see if any of the supplied # config options already exist, the assumption being that they # were set with 'osgeo.gdal.SetConfigOption()' or possibly # 'fiona.env.set_gdal_config()'. The discovered options are # reinstated when the outermost Fiona environment exits. # Without this check any environment options that are present in # the GDAL environment and are also passed to 'fiona.Env()' # will be unset when 'fiona.Env()' tears down, regardless of # their value. For example: # # from osgeo import gdal import fiona # # gdal.SetConfigOption('key', 'value') # with fiona.Env(key='something'): # pass # # The config option 'key' would be unset when 'Env()' exits. # A more comprehensive solution would also leverage # https://trac.osgeo.org/gdal/changeset/37273 but this gets # Fiona + older versions of GDAL halfway there. One major # assumption is that environment variables are not set directly # with 'osgeo.gdal.SetConfigOption()' OR # 'fiona.env.set_gdal_config()' inside of a 'fiona.Env()'. self._discovered_options = None local = ThreadEnv() log = logging.getLogger(__name__) class Env: """Abstraction for GDAL and AWS configuration The GDAL library is stateful: it has a registry of format drivers, an error stack, and dozens of configuration options. Fiona's approach to working with GDAL is to wrap all the state up using a Python context manager (see PEP 343, https://www.python.org/dev/peps/pep-0343/). When the context is entered GDAL drivers are registered, error handlers are configured, and configuration options are set. When the context is exited, drivers are removed from the registry and other configurations are removed. Example: with fiona.Env(GDAL_CACHEMAX=512) as env: # All drivers are registered, GDAL's raster block cache # size is set to 512MB. # Commence processing... ... # End of processing. # At this point, configuration options are set to their # previous (possible unset) values. A boto3 session or boto3 session constructor arguments `aws_access_key_id`, `aws_secret_access_key`, `aws_session_token` may be passed to Env's constructor. In the latter case, a session will be created as soon as needed. AWS credentials are configured for GDAL as needed. """ @classmethod def default_options(cls): """Default configuration options Parameters ---------- None Returns ------- dict """ return { "CHECK_WITH_INVERT_PROJ": True, "GTIFF_IMPLICIT_JPEG_OVR": False, "FIONA_ENV": True, } def __init__( self, session=None, aws_unsigned=False, profile_name=None, session_class=Session.aws_or_dummy, **options ): """Create a new GDAL/AWS environment. Note: this class is a context manager. GDAL isn't configured until the context is entered via `with fiona.Env():` Parameters ---------- session : optional A Session object. aws_unsigned : bool, optional Do not sign cloud requests. profile_name : str, optional A shared credentials profile name, as per boto3. session_class : Session, optional A sub-class of Session. **options : optional A mapping of GDAL configuration options, e.g., `CPL_DEBUG=True, CHECK_WITH_INVERT_PROJ=False`. Returns ------- Env Notes ----- We raise EnvError if the GDAL config options AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY are given. AWS credentials are handled exclusively by boto3. Examples -------- >>> with Env(CPL_DEBUG=True, CPL_CURL_VERBOSE=True): ... with fiona.open("zip+https://example.com/a.zip") as col: ... print(col.profile) For access to secured cloud resources, a Fiona Session or a foreign session object may be passed to the constructor. >>> import boto3 >>> from fiona.session import AWSSession >>> boto3_session = boto3.Session(...) >>> with Env(AWSSession(boto3_session)): ... with fiona.open("zip+s3://example/a.zip") as col: ... print(col.profile """ aws_access_key_id = options.pop("aws_access_key_id", None) # Warn deprecation in 1.9, remove in 2.0. if aws_access_key_id: warnings.warn( "Passing abstract session keyword arguments is deprecated. " "Pass a Fiona AWSSession object instead.", FionaDeprecationWarning, ) aws_secret_access_key = options.pop("aws_secret_access_key", None) aws_session_token = options.pop("aws_session_token", None) region_name = options.pop("region_name", None) if not {"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"}.isdisjoint(options): raise EnvError( "GDAL's AWS config options can not be directly set. " "AWS credentials are handled exclusively by boto3." ) if session: # Passing a session via keyword argument is the canonical # way to configure access to secured cloud resources. # Warn deprecation in 1.9, remove in 2.0. if not isinstance(session, Session): warnings.warn( "Passing a boto3 session is deprecated. Pass a Fiona AWSSession object instead.", FionaDeprecationWarning, ) session = Session.aws_or_dummy(session=session) self.session = session elif aws_access_key_id or profile_name or aws_unsigned: self.session = Session.aws_or_dummy( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, aws_session_token=aws_session_token, region_name=region_name, profile_name=profile_name, aws_unsigned=aws_unsigned, ) elif {"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"}.issubset(os.environ.keys()): self.session = Session.from_environ() else: self.session = DummySession() self.options = options.copy() self.context_options = {} @classmethod def from_defaults(cls, *args, **kwargs): """Create an environment with default config options Parameters ---------- args : optional Positional arguments for Env() kwargs : optional Keyword arguments for Env() Returns ------- Env Notes ----- The items in kwargs will be overlaid on the default values. """ options = Env.default_options() options.update(**kwargs) return Env(*args, **options) def credentialize(self): """Get credentials and configure GDAL Note well: this method is a no-op if the GDAL environment already has credentials, unless session is not None. Returns ------- None """ cred_opts = self.session.get_credential_options() self.options.update(**cred_opts) setenv(**cred_opts) def drivers(self): """Return a mapping of registered drivers.""" return local._env.drivers() def _dump_open_datasets(self): """Writes descriptions of open datasets to stderr For debugging and testing purposes. """ return local._env._dump_open_datasets() def __enter__(self): if local._env is None: self._has_parent_env = False # See note directly above where _discovered_options is globally # defined. This MUST happen before calling 'defenv()'. local._discovered_options = {} # Don't want to reinstate the "RASTERIO_ENV" option. probe_env = {k for k in self.options.keys() if k != "RASTERIO_ENV"} for key in probe_env: val = get_gdal_config(key, normalize=False) if val is not None: local._discovered_options[key] = val defenv(**self.options) self.context_options = {} else: self._has_parent_env = True self.context_options = getenv() setenv(**self.options) self.credentialize() return self def __exit__(self, exc_type=None, exc_val=None, exc_tb=None): delenv() if self._has_parent_env: defenv() setenv(**self.context_options) else: # See note directly above where _discovered_options is globally # defined. while local._discovered_options: key, val = local._discovered_options.popitem() set_gdal_config(key, val, normalize=False) local._discovered_options = None def defenv(**options): """Create a default environment if necessary.""" if not local._env: local._env = GDALEnv() local._env.update_config_options(**options) local._env.start() def getenv(): """Get a mapping of current options.""" if not local._env: raise EnvError("No GDAL environment exists") else: return local._env.options.copy() def hasenv(): return bool(local._env) def setenv(**options): """Set options in the existing environment.""" if not local._env: raise EnvError("No GDAL environment exists") else: local._env.update_config_options(**options) def hascreds(): warnings.warn("Please use Env.session.hascreds() instead", FionaDeprecationWarning) return local._env is not None and all( key in local._env.get_config_options() for key in ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"] ) def delenv(): """Delete options in the existing environment.""" if not local._env: raise EnvError("No GDAL environment exists") else: local._env.clear_config_options() local._env.stop() local._env = None class NullContextManager: def __init__(self): pass def __enter__(self): return self def __exit__(self, *args): pass def env_ctx_if_needed(): """Return an Env if one does not exist Returns ------- Env or a do-nothing context manager """ if local._env: return NullContextManager() else: return Env.from_defaults() def ensure_env(f): """A decorator that ensures an env exists before a function calls any GDAL C functions. Parameters ---------- f : function A function. Returns ------- A function wrapper. Notes ----- If there is already an existing environment, the wrapper does nothing and immediately calls f with the given arguments. """ @wraps(f) def wrapper(*args, **kwargs): if local._env: return f(*args, **kwargs) else: with Env.from_defaults(): return f(*args, **kwargs) return wrapper def ensure_env_with_credentials(f): """Ensures a config environment exists and has credentials. Parameters ---------- f : function A function. Returns ------- A function wrapper. Notes ----- The function wrapper checks the first argument of f and credentializes the environment if the first argument is a URI with scheme "s3". If there is already an existing environment, the wrapper does nothing and immediately calls f with the given arguments. """ @wraps(f) def wrapper(*args, **kwds): if local._env: env_ctor = Env else: env_ctor = Env.from_defaults fp_arg = kwds.get("fp", None) or args[0] if isinstance(fp_arg, str): session_cls = Session.cls_from_path(fp_arg) if local._env and session_cls.hascreds(getenv()): session_cls = DummySession session = session_cls() else: session = DummySession() with env_ctor(session=session): return f(*args, **kwds) return wrapper @attr.s(slots=True) @total_ordering class GDALVersion: """Convenience class for obtaining GDAL major and minor version components and comparing between versions. This is highly simplistic and assumes a very normal numbering scheme for versions and ignores everything except the major and minor components. """ major = attr.ib(default=0, validator=attr.validators.instance_of(int)) minor = attr.ib(default=0, validator=attr.validators.instance_of(int)) def __eq__(self, other): return (self.major, self.minor) == tuple(other.major, other.minor) def __lt__(self, other): return (self.major, self.minor) < tuple(other.major, other.minor) def __repr__(self): return f"GDALVersion(major={self.major}, minor={self.minor})" def __str__(self): return f"{self.major}.{self.minor}" @classmethod def parse(cls, input): """ Parses input tuple or string to GDALVersion. If input is a GDALVersion instance, it is returned. Parameters ---------- input: tuple of (major, minor), string, or instance of GDALVersion Returns ------- GDALVersion instance """ if isinstance(input, cls): return input if isinstance(input, tuple): return cls(*input) elif isinstance(input, str): # Extract major and minor version components. # alpha, beta, rc suffixes ignored match = re.search(r"^\d+\.\d+", input) if not match: raise ValueError( "value does not appear to be a valid GDAL version " f"number: {input}" ) major, minor = (int(c) for c in match.group().split(".")) return cls(major=major, minor=minor) raise TypeError("GDALVersion can only be parsed from a string or tuple") @classmethod def runtime(cls): """Return GDALVersion of current GDAL runtime""" return cls.parse(get_gdal_release_name()) def at_least(self, other): other = self.__class__.parse(other) return self >= other def require_gdal_version( version, param=None, values=None, is_max_version=False, reason="" ): """A decorator that ensures the called function or parameters are supported by the runtime version of GDAL. Raises GDALVersionError if conditions are not met. Examples: \b @require_gdal_version('2.2') def some_func(): calling `some_func` with a runtime version of GDAL that is < 2.2 raises a GDALVersionError. \b @require_gdal_version('2.2', param='foo') def some_func(foo='bar'): calling `some_func` with parameter `foo` of any value on GDAL < 2.2 raises a GDALVersionError. \b @require_gdal_version('2.2', param='foo', values=('bar',)) def some_func(foo=None): calling `some_func` with parameter `foo` and value `bar` on GDAL < 2.2 raises a GDALVersionError. Parameters ------------ version: tuple, string, or GDALVersion param: string (optional, default: None) If `values` are absent, then all use of this parameter with a value other than default value requires at least GDAL `version`. values: tuple, list, or set (optional, default: None) contains values that require at least GDAL `version`. `param` is required for `values`. is_max_version: bool (optional, default: False) if `True` indicates that the version provided is the maximum version allowed, instead of requiring at least that version. reason: string (optional: default: '') custom error message presented to user in addition to message about GDAL version. Use this to provide an explanation of what changed if necessary context to the user. Returns --------- wrapped function """ if values is not None: if param is None: raise ValueError("require_gdal_version: param must be provided with values") if not isinstance(values, (tuple, list, set)): raise ValueError( "require_gdal_version: values must be a tuple, list, or set" ) version = GDALVersion.parse(version) runtime = GDALVersion.runtime() inequality = ">=" if runtime < version else "<=" reason = f"\n{reason}" if reason else reason def decorator(f): @wraps(f) def wrapper(*args, **kwds): if (runtime < version and not is_max_version) or ( is_max_version and runtime > version ): if param is None: raise GDALVersionError( f"GDAL version must be {inequality} {version}{reason}" ) # normalize args and kwds to dict argspec = getfullargspec(f) full_kwds = kwds.copy() if argspec.args: full_kwds.update(dict(zip(argspec.args[: len(args)], args))) if argspec.defaults: defaults = dict( zip(reversed(argspec.args), reversed(argspec.defaults)) ) else: defaults = {} if param in full_kwds: if values is None: if param not in defaults or ( full_kwds[param] != defaults[param] ): raise GDALVersionError( f'usage of parameter "{param}" requires ' f"GDAL {inequality} {version}{reason}" ) elif full_kwds[param] in values: raise GDALVersionError( f'parameter "{param}={full_kwds[param]}" requires ' f"GDAL {inequality} {version}{reason}" ) return f(*args, **kwds) return wrapper return decorator # Patch the environment if needed, such as in the installed wheel case. if "GDAL_DATA" not in os.environ: path = GDALDataFinder().search_wheel() if path: log.debug("GDAL data found in package: path=%r.", path) set_gdal_config("GDAL_DATA", path) # See https://github.com/mapbox/rasterio/issues/1631. elif GDALDataFinder().find_file("header.dxf"): log.debug("GDAL data files are available at built-in paths.") else: path = GDALDataFinder().search() if path: set_gdal_config("GDAL_DATA", path) log.debug("GDAL data found in other locations: path=%r.", path) if 'PROJ_DATA' in os.environ: # PROJ 9.1+ path = os.environ["PROJ_DATA"] set_proj_data_search_path(path) elif "PROJ_LIB" in os.environ: # PROJ < 9.1 path = os.environ["PROJ_LIB"] set_proj_data_search_path(path) elif PROJDataFinder().search_wheel(): path = PROJDataFinder().search_wheel() log.debug("PROJ data found in package: path=%r.", path) set_proj_data_search_path(path) # See https://github.com/mapbox/rasterio/issues/1631. elif PROJDataFinder().has_data(): log.debug("PROJ data files are available at built-in paths.") else: path = PROJDataFinder().search() if path: log.debug("PROJ data found in other locations: path=%r.", path) set_proj_data_search_path(path) Fiona-1.10.1/fiona/errors.py000066400000000000000000000042101467206072700156200ustar00rootroot00000000000000# Errors. class FionaError(Exception): """Base Fiona error""" class FionaValueError(FionaError, ValueError): """Fiona-specific value errors""" class AttributeFilterError(FionaValueError): """Error processing SQL WHERE clause with the dataset.""" class DriverError(FionaValueError): """Encapsulates unsupported driver and driver mode errors.""" class SchemaError(FionaValueError): """When a schema mapping has no properties or no geometry.""" class CRSError(FionaValueError): """When a crs mapping has neither init or proj items.""" class UnsupportedOperation(FionaError): """Raised when reading from a file opened in 'w' mode""" class DataIOError(OSError): """IO errors involving driver registration or availability.""" class DriverIOError(OSError): """A format specific driver error.""" class DriverSupportError(DriverIOError): """Driver does not support schema""" class DatasetDeleteError(OSError): """Failure to delete a dataset""" class FieldNameEncodeError(UnicodeEncodeError): """Failure to encode a field name.""" class UnsupportedGeometryTypeError(KeyError): """When a OGR geometry type isn't supported by Fiona.""" class GeometryTypeValidationError(FionaValueError): """Tried to write a geometry type not specified in the schema""" class TransactionError(RuntimeError): """Failure relating to GDAL transactions""" class EnvError(FionaError): """Environment Errors""" class GDALVersionError(FionaError): """Raised if the runtime version of GDAL does not meet the required version of GDAL. """ class TransformError(FionaError): """Raised if a coordinate transformation fails.""" class OpenerRegistrationError(FionaError): """Raised when a Python file opener can not be registered.""" class PathError(FionaError): """Raised when a dataset path is malformed or invalid""" class FionaDeprecationWarning(DeprecationWarning): """A warning about deprecation of Fiona features""" class FeatureWarning(UserWarning): """A warning about serialization of a feature""" class ReduceError(FionaError): """"Raised when reduce operation fails.""" Fiona-1.10.1/fiona/features.py000066400000000000000000000205331467206072700161300ustar00rootroot00000000000000"""Operations on GeoJSON feature and geometry objects.""" from collections import UserDict from functools import wraps import itertools from typing import Generator, Iterable, Mapping, Union from fiona.transform import transform_geom # type: ignore import shapely # type: ignore import shapely.ops # type: ignore from shapely.geometry import mapping, shape # type: ignore from shapely.geometry.base import BaseGeometry, BaseMultipartGeometry # type: ignore from .errors import ReduceError from ._vendor import snuggs # Patch snuggs's func_map, extending it with Python builtins, geometry # methods and attributes, and functions exported in the shapely module # (such as set_precision). class FuncMapper(UserDict, Mapping): """Resolves functions from names in pipeline expressions.""" def __getitem__(self, key): """Get a function by its name.""" if key in self.data: return self.data[key] elif key in __builtins__ and not key.startswith("__"): return __builtins__[key] elif key in dir(shapely): return lambda g, *args, **kwargs: getattr(shapely, key)(g, *args, **kwargs) elif key in dir(shapely.ops): return lambda g, *args, **kwargs: getattr(shapely.ops, key)( g, *args, **kwargs ) else: return ( lambda g, *args, **kwargs: getattr(g, key)(*args, **kwargs) if callable(getattr(g, key)) else getattr(g, key) ) def collect(geoms: Iterable) -> object: """Turn a sequence of geometries into a single GeometryCollection. Parameters ---------- geoms : Iterable A sequence of geometry objects. Returns ------- Geometry """ return shapely.GeometryCollection(list(geoms)) def dump(geom: Union[BaseGeometry, BaseMultipartGeometry]) -> Generator: """Get the individual parts of a geometry object. If the given geometry object has a single part, e.g., is an instance of LineString, Point, or Polygon, this function yields a single result, the geometry itself. Parameters ---------- geom : a shapely geometry object. Yields ------ A shapely geometry object. """ if hasattr(geom, "geoms"): parts = geom.geoms else: parts = [geom] for part in parts: yield part def identity(obj: object) -> object: """Get back the given argument. To help in making expression lists, where the first item must be a callable object. Parameters ---------- obj : objeect Returns ------- obj """ return obj def vertex_count(obj: object) -> int: """Count the vertices of a GeoJSON-like geometry object. Parameters ---------- obj: object A GeoJSON-like mapping or an object that provides __geo_interface__. Returns ------- int """ shp = shape(obj) if hasattr(shp, "geoms"): return sum(vertex_count(part) for part in shp.geoms) elif hasattr(shp, "exterior"): return vertex_count(shp.exterior) + sum( vertex_count(ring) for ring in shp.interiors ) else: return len(shp.coords) def binary_projectable_property_wrapper(func): """Project func's geometry args before computing a property. Parameters ---------- func : callable Signature is func(geom1, geom2, *args, **kwargs) Returns ------- callable Signature is func(geom1, geom2, projected=True, *args, **kwargs) """ @wraps(func) def wrapper(geom1, geom2, *args, projected=True, **kwargs): if projected: geom1 = shape(transform_geom("OGC:CRS84", "EPSG:6933", mapping(geom1))) geom2 = shape(transform_geom("OGC:CRS84", "EPSG:6933", mapping(geom2))) return func(geom1, geom2, *args, **kwargs) return wrapper def unary_projectable_property_wrapper(func): """Project func's geometry arg before computing a property. Parameters ---------- func : callable Signature is func(geom1, *args, **kwargs) Returns ------- callable Signature is func(geom1, projected=True, *args, **kwargs) """ @wraps(func) def wrapper(geom, *args, projected=True, **kwargs): if projected: geom = shape(transform_geom("OGC:CRS84", "EPSG:6933", mapping(geom))) return func(geom, *args, **kwargs) return wrapper def unary_projectable_constructive_wrapper(func): """Project func's geometry arg before constructing a new geometry. Parameters ---------- func : callable Signature is func(geom1, *args, **kwargs) Returns ------- callable Signature is func(geom1, projected=True, *args, **kwargs) """ @wraps(func) def wrapper(geom, *args, projected=True, **kwargs): if projected: geom = shape(transform_geom("OGC:CRS84", "EPSG:6933", mapping(geom))) product = func(geom, *args, **kwargs) return shape(transform_geom("EPSG:6933", "OGC:CRS84", mapping(product))) else: return func(geom, *args, **kwargs) return wrapper area = unary_projectable_property_wrapper(shapely.area) buffer = unary_projectable_constructive_wrapper(shapely.buffer) distance = binary_projectable_property_wrapper(shapely.distance) set_precision = unary_projectable_constructive_wrapper(shapely.set_precision) simplify = unary_projectable_constructive_wrapper(shapely.simplify) length = unary_projectable_property_wrapper(shapely.length) snuggs.func_map = FuncMapper( area=area, buffer=buffer, collect=collect, distance=distance, dump=dump, identity=identity, length=length, simplify=simplify, set_precision=set_precision, vertex_count=vertex_count, **{ k: getattr(itertools, k) for k in dir(itertools) if not k.startswith("_") and callable(getattr(itertools, k)) }, ) def map_feature( expression: str, feature: Mapping, dump_parts: bool = False ) -> Generator: """Map a pipeline expression to a feature. Yields one or more values. Parameters ---------- expression : str A snuggs expression. The outermost parentheses are optional. feature : dict A Fiona feature object. dump_parts : bool, optional (default: False) If True, the parts of the feature's geometry are turned into new features. Yields ------ object """ if not (expression.startswith("(") and expression.endswith(")")): expression = f"({expression})" try: geom = shape(feature.get("geometry", None)) if dump_parts and hasattr(geom, "geoms"): parts = geom.geoms else: parts = [geom] except (AttributeError, KeyError): parts = [None] for part in parts: result = snuggs.eval(expression, g=part, f=feature) if isinstance(result, (str, float, int, Mapping)): yield result elif isinstance(result, (BaseGeometry, BaseMultipartGeometry)): yield mapping(result) else: try: for item in result: if isinstance(item, (BaseGeometry, BaseMultipartGeometry)): item = mapping(item) yield item except TypeError: yield result def reduce_features(expression: str, features: Iterable[Mapping]) -> Generator: """Reduce a collection of features to a single value. The pipeline is a string that, when evaluated by snuggs, produces a new value. The name of the input feature collection in the context of the pipeline is "c". Parameters ---------- pipeline : str Geometry operation pipeline such as "(unary_union c)". features : iterable A sequence of Fiona feature objects. Yields ------ object Raises ------ ReduceError """ if not (expression.startswith("(") and expression.endswith(")")): expression = f"({expression})" collection = (shape(feat["geometry"]) for feat in features) result = snuggs.eval(expression, c=collection) if isinstance(result, (str, float, int, Mapping)): yield result elif isinstance(result, (BaseGeometry, BaseMultipartGeometry)): yield mapping(result) else: raise ReduceError("Expression failed to reduce to a single value.") Fiona-1.10.1/fiona/fio/000077500000000000000000000000001467206072700145125ustar00rootroot00000000000000Fiona-1.10.1/fiona/fio/__init__.py000066400000000000000000000007761467206072700166350ustar00rootroot00000000000000"""Fiona's command line interface""" from functools import wraps def with_context_env(f): """Pops the Fiona Env from the passed context and executes the wrapped func in the context of that obj. Click's pass_context decorator must precede this decorator, or else there will be no context in the wrapper args. """ @wraps(f) def wrapper(*args, **kwds): ctx = args[0] env = ctx.obj.pop('env') with env: return f(*args, **kwds) return wrapper Fiona-1.10.1/fiona/fio/bounds.py000066400000000000000000000054711467206072700163650ustar00rootroot00000000000000"""$ fio bounds""" import json import click from cligj import precision_opt, use_rs_opt import fiona from fiona.fio.helpers import obj_gen from fiona.fio import with_context_env from fiona.model import ObjectEncoder @click.command(short_help="Print the extent of GeoJSON objects") @precision_opt @click.option('--explode/--no-explode', default=False, help="Explode collections into features (default: no).") @click.option('--with-id/--without-id', default=False, help="Print GeoJSON ids and bounding boxes together " "(default: without).") @click.option('--with-obj/--without-obj', default=False, help="Print GeoJSON objects and bounding boxes together " "(default: without).") @use_rs_opt @click.pass_context @with_context_env def bounds(ctx, precision, explode, with_id, with_obj, use_rs): """Print the bounding boxes of GeoJSON objects read from stdin. Optionally explode collections and print the bounds of their features. To print identifiers for input objects along with their bounds as a {id: identifier, bbox: bounds} JSON object, use --with-id. To print the input objects themselves along with their bounds as GeoJSON object, use --with-obj. This has the effect of updating input objects with {id: identifier, bbox: bounds}. """ stdin = click.get_text_stream('stdin') source = obj_gen(stdin) for i, obj in enumerate(source): obj_id = obj.get("id", "collection:" + str(i)) xs = [] ys = [] features = obj.get("features") or [obj] for j, feat in enumerate(features): feat_id = feat.get("id", "feature:" + str(i)) w, s, e, n = fiona.bounds(feat) if precision > 0: w, s, e, n = (round(v, precision) for v in (w, s, e, n)) if explode: if with_id: rec = {"parent": obj_id, "id": feat_id, "bbox": (w, s, e, n)} elif with_obj: feat.update(parent=obj_id, bbox=(w, s, e, n)) rec = feat else: rec = (w, s, e, n) if use_rs: click.echo('\x1e', nl=False) click.echo(json.dumps(rec, cls=ObjectEncoder)) else: xs.extend([w, e]) ys.extend([s, n]) if not explode: w, s, e, n = (min(xs), min(ys), max(xs), max(ys)) if with_id: rec = {"id": obj_id, "bbox": (w, s, e, n)} elif with_obj: obj.update(id=obj_id, bbox=(w, s, e, n)) rec = obj else: rec = (w, s, e, n) if use_rs: click.echo("\x1e", nl=False) click.echo(json.dumps(rec, cls=ObjectEncoder)) Fiona-1.10.1/fiona/fio/calc.py000066400000000000000000000040201467206072700157620ustar00rootroot00000000000000import json import click from cligj import use_rs_opt from .helpers import obj_gen, eval_feature_expression from fiona.fio import with_context_env from fiona.model import ObjectEncoder @click.command(short_help="Calculate GeoJSON property by Python expression") @click.argument('property_name') @click.argument('expression') @click.option('--overwrite', is_flag=True, default=False, help="Overwrite properties, default: False") @use_rs_opt @click.pass_context @with_context_env def calc(ctx, property_name, expression, overwrite, use_rs): """ Create a new property on GeoJSON features using the specified expression. \b The expression is evaluated in a restricted namespace containing: - sum, pow, min, max and the imported math module - shape (optional, imported from shapely.geometry if available) - bool, int, str, len, float type conversions - f (the feature to be evaluated, allows item access via javascript-style dot notation using munch) The expression will be evaluated for each feature and its return value will be added to the properties as the specified property_name. Existing properties will not be overwritten by default (an Exception is raised). Example \b $ fio cat data.shp | fio calc sumAB "f.properties.A + f.properties.B" """ stdin = click.get_text_stream('stdin') source = obj_gen(stdin) for i, obj in enumerate(source): features = obj.get("features") or [obj] for j, feat in enumerate(features): if not overwrite and property_name in feat["properties"]: raise click.UsageError( f"{property_name} already exists in properties; " "rename or use --overwrite" ) feat["properties"][property_name] = eval_feature_expression( feat, expression ) if use_rs: click.echo("\x1e", nl=False) click.echo(json.dumps(feat, cls=ObjectEncoder)) Fiona-1.10.1/fiona/fio/cat.py000066400000000000000000000075261467206072700156450ustar00rootroot00000000000000"""fio-cat""" import json import warnings import click import cligj import fiona from fiona.transform import transform_geom from fiona.model import Feature, ObjectEncoder from fiona.fio import options, with_context_env from fiona.fio.helpers import recursive_round from fiona.errors import AttributeFilterError warnings.simplefilter("default") # Cat command @click.command(short_help="Concatenate and print the features of datasets") @click.argument("files", nargs=-1, required=True, metavar="INPUTS...") @click.option( "--layer", default=None, multiple=True, callback=options.cb_multilayer, help="Input layer(s), specified as 'fileindex:layer` " "For example, '1:foo,2:bar' will concatenate layer foo " "from file 1 and layer bar from file 2", ) @cligj.precision_opt @cligj.indent_opt @cligj.compact_opt @click.option( "--ignore-errors/--no-ignore-errors", default=False, help="log errors but do not stop serialization.", ) @options.dst_crs_opt @cligj.use_rs_opt @click.option( "--bbox", default=None, metavar="w,s,e,n", help="filter for features intersecting a bounding box", ) @click.option( "--where", default=None, help="attribute filter using SQL where clause", ) @click.option( "--cut-at-antimeridian", is_flag=True, default=False, help="Optionally cut geometries at the anti-meridian. To be used only for a geographic destination CRS.", ) @click.option('--where', default=None, help="attribute filter using SQL where clause") @options.open_opt @click.pass_context @with_context_env def cat( ctx, files, precision, indent, compact, ignore_errors, dst_crs, use_rs, bbox, where, cut_at_antimeridian, layer, open_options, ): """ Concatenate and print the features of input datasets as a sequence of GeoJSON features. When working with a multi-layer dataset the first layer is used by default. Use the '--layer' option to select a different layer. """ dump_kwds = {"sort_keys": True} if indent: dump_kwds["indent"] = indent if compact: dump_kwds["separators"] = (",", ":") # Validate file idexes provided in --layer option # (can't pass the files to option callback) if layer: options.validate_multilayer_file_index(files, layer) # first layer is the default for i in range(1, len(files) + 1): if str(i) not in layer.keys(): layer[str(i)] = [0] try: if bbox: try: bbox = tuple(map(float, bbox.split(","))) except ValueError: bbox = json.loads(bbox) for i, path in enumerate(files, 1): for lyr in layer[str(i)]: with fiona.open(path, layer=lyr, **open_options) as src: for i, feat in src.items(bbox=bbox, where=where): geom = feat.geometry if dst_crs: geom = transform_geom( src.crs, dst_crs, geom, antimeridian_cutting=cut_at_antimeridian, ) if precision >= 0: geom = recursive_round(geom, precision) feat = Feature( id=feat.id, properties=feat.properties, geometry=geom, bbox=fiona.bounds(geom), ) if use_rs: click.echo("\x1e", nl=False) click.echo(json.dumps(feat, cls=ObjectEncoder, **dump_kwds)) except AttributeFilterError as e: raise click.BadParameter("'where' clause is invalid: " + str(e)) Fiona-1.10.1/fiona/fio/collect.py000066400000000000000000000170611467206072700165160ustar00rootroot00000000000000"""fio-collect""" from functools import partial import json import logging import click import cligj from fiona.fio import helpers, options, with_context_env from fiona.model import Geometry, ObjectEncoder from fiona.transform import transform_geom @click.command(short_help="Collect a sequence of features.") @cligj.precision_opt @cligj.indent_opt @cligj.compact_opt @click.option( "--record-buffered/--no-record-buffered", default=False, help="Economical buffering of writes at record, not collection " "(default), level.", ) @click.option( "--ignore-errors/--no-ignore-errors", default=False, help="log errors but do not stop serialization.", ) @options.src_crs_opt @click.option( "--with-ld-context/--without-ld-context", default=False, help="add a JSON-LD context to JSON output.", ) @click.option( "--add-ld-context-item", multiple=True, help="map a term to a URI and add it to the output's JSON LD " "context.", ) @click.option( "--parse/--no-parse", default=True, help="load and dump the geojson feature (default is True)", ) @click.pass_context @with_context_env def collect( ctx, precision, indent, compact, record_buffered, ignore_errors, src_crs, with_ld_context, add_ld_context_item, parse, ): """Make a GeoJSON feature collection from a sequence of GeoJSON features and print it.""" logger = logging.getLogger(__name__) stdin = click.get_text_stream("stdin") sink = click.get_text_stream("stdout") dump_kwds = {"sort_keys": True} if indent: dump_kwds["indent"] = indent if compact: dump_kwds["separators"] = (",", ":") item_sep = compact and "," or ", " if src_crs: if not parse: raise click.UsageError("Can't specify --src-crs with --no-parse") transformer = partial( transform_geom, src_crs, "EPSG:4326", antimeridian_cutting=True, precision=precision, ) else: def transformer(x): return x first_line = next(stdin) # If parsing geojson if parse: # If input is RS-delimited JSON sequence. if first_line.startswith("\x1e"): def feature_text_gen(): buffer = first_line.strip("\x1e") for line in stdin: if line.startswith("\x1e"): if buffer: feat = json.loads(buffer) feat["geometry"] = transformer( Geometry.from_dict(**feat["geometry"]) ) yield json.dumps(feat, cls=ObjectEncoder, **dump_kwds) buffer = line.strip("\x1e") else: buffer += line else: feat = json.loads(buffer) feat["geometry"] = transformer( Geometry.from_dict(**feat["geometry"]) ) yield json.dumps(feat, cls=ObjectEncoder, **dump_kwds) else: def feature_text_gen(): feat = json.loads(first_line) feat["geometry"] = transformer(Geometry.from_dict(**feat["geometry"])) yield json.dumps(feat, cls=ObjectEncoder, **dump_kwds) for line in stdin: feat = json.loads(line) feat["geometry"] = transformer( Geometry.from_dict(**feat["geometry"]) ) yield json.dumps(feat, cls=ObjectEncoder, **dump_kwds) # If *not* parsing geojson else: # If input is RS-delimited JSON sequence. if first_line.startswith("\x1e"): def feature_text_gen(): buffer = first_line.strip("\x1e") for line in stdin: if line.startswith("\x1e"): if buffer: yield buffer buffer = line.strip("\x1e") else: buffer += line else: yield buffer else: def feature_text_gen(): yield first_line yield from stdin source = feature_text_gen() if record_buffered: # Buffer GeoJSON data at the feature level for smaller # memory footprint. indented = bool(indent) rec_indent = "\n" + " " * (2 * (indent or 0)) collection = {"type": "FeatureCollection", "features": []} if with_ld_context: collection["@context"] = helpers.make_ld_context(add_ld_context_item) head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split("[]") sink.write(head) sink.write("[") # Try the first record. try: i, first = 0, next(source) if with_ld_context: first = helpers.id_record(first) if indented: sink.write(rec_indent) sink.write(first.replace("\n", rec_indent)) except StopIteration: pass except Exception as exc: # Ignoring errors is *not* the default. if ignore_errors: logger.error( "failed to serialize file record %d (%s), " "continuing", i, exc ) else: # Log error and close up the GeoJSON, leaving it # more or less valid no matter what happens above. logger.critical( "failed to serialize file record %d (%s), " "quitting", i, exc ) sink.write("]") sink.write(tail) if indented: sink.write("\n") raise # Because trailing commas aren't valid in JSON arrays # we'll write the item separator before each of the # remaining features. for i, rec in enumerate(source, 1): try: if with_ld_context: rec = helpers.id_record(rec) if indented: sink.write(rec_indent) sink.write(item_sep) sink.write(rec.replace("\n", rec_indent)) except Exception as exc: if ignore_errors: logger.error( "failed to serialize file record %d (%s), " "continuing", i, exc, ) else: logger.critical( "failed to serialize file record %d (%s), " "quitting", i, exc, ) sink.write("]") sink.write(tail) if indented: sink.write("\n") raise # Close up the GeoJSON after writing all features. sink.write("]") sink.write(tail) if indented: sink.write("\n") else: # Buffer GeoJSON data at the collection level. The default. collection = {"type": "FeatureCollection", "features": []} if with_ld_context: collection["@context"] = helpers.make_ld_context(add_ld_context_item) head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split("[]") sink.write(head) sink.write("[") sink.write(",".join(source)) sink.write("]") sink.write(tail) sink.write("\n") Fiona-1.10.1/fiona/fio/distrib.py000066400000000000000000000016551467206072700165330ustar00rootroot00000000000000"""$ fio distrib""" import json import click import cligj from fiona.fio import helpers, with_context_env from fiona.model import ObjectEncoder @click.command() @cligj.use_rs_opt @click.pass_context @with_context_env def distrib(ctx, use_rs): """Distribute features from a collection. Print the features of GeoJSON objects read from stdin. """ stdin = click.get_text_stream('stdin') source = helpers.obj_gen(stdin) for i, obj in enumerate(source): obj_id = obj.get("id", "collection:" + str(i)) features = obj.get("features") or [obj] for j, feat in enumerate(features): if obj.get("type") == "FeatureCollection": feat["parent"] = obj_id feat_id = feat.get("id", "feature:" + str(i)) feat["id"] = feat_id if use_rs: click.echo("\x1e", nl=False) click.echo(json.dumps(feat, cls=ObjectEncoder)) Fiona-1.10.1/fiona/fio/dump.py000066400000000000000000000153751467206072700160440ustar00rootroot00000000000000"""fio-dump""" from functools import partial import json import logging import click import cligj import fiona from fiona.fio import helpers, options, with_context_env from fiona.model import Feature, ObjectEncoder from fiona.transform import transform_geom @click.command(short_help="Dump a dataset to GeoJSON.") @click.argument('input', required=True) @click.option('--layer', metavar="INDEX|NAME", callback=options.cb_layer, help="Print information about a specific layer. The first " "layer is used by default. Layers use zero-based " "numbering when accessed by index.") @click.option('--encoding', help="Specify encoding of the input file.") @cligj.precision_opt @cligj.indent_opt @cligj.compact_opt @click.option('--record-buffered/--no-record-buffered', default=False, help="Economical buffering of writes at record, not collection " "(default), level.") @click.option('--ignore-errors/--no-ignore-errors', default=False, help="log errors but do not stop serialization.") @click.option('--with-ld-context/--without-ld-context', default=False, help="add a JSON-LD context to JSON output.") @click.option('--add-ld-context-item', multiple=True, help="map a term to a URI and add it to the output's JSON LD " "context.") @options.open_opt @click.pass_context @with_context_env def dump( ctx, input, encoding, precision, indent, compact, record_buffered, ignore_errors, with_ld_context, add_ld_context_item, layer, open_options, ): """Dump a dataset either as a GeoJSON feature collection (the default) or a sequence of GeoJSON features.""" logger = logging.getLogger(__name__) sink = click.get_text_stream('stdout') dump_kwds = {'sort_keys': True} if indent: dump_kwds['indent'] = indent if compact: dump_kwds['separators'] = (',', ':') item_sep = compact and ',' or ', ' if encoding: open_options["encoding"] = encoding if layer: open_options["layer"] = layer def transformer(crs, feat): tg = partial( transform_geom, crs, "EPSG:4326", antimeridian_cutting=True, precision=precision, ) return Feature( id=feat.id, properties=feat.properties, geometry=tg(feat.geometry) ) with fiona.open(input, **open_options) as source: meta = source.meta meta["fields"] = dict(source.schema["properties"].items()) if record_buffered: # Buffer GeoJSON data at the feature level for smaller # memory footprint. indented = bool(indent) rec_indent = "\n" + " " * (2 * (indent or 0)) collection = { "type": "FeatureCollection", "fiona:schema": meta["schema"], "fiona:crs": meta["crs"], "features": [], } if with_ld_context: collection["@context"] = helpers.make_ld_context(add_ld_context_item) head, tail = json.dumps(collection, **dump_kwds).split("[]") sink.write(head) sink.write("[") itr = iter(source) # Try the first record. try: i, first = 0, next(itr) first = transformer(first) if with_ld_context: first = helpers.id_record(first) if indented: sink.write(rec_indent) sink.write( json.dumps(first, cls=ObjectEncoder, **dump_kwds).replace( "\n", rec_indent ) ) except StopIteration: pass except Exception as exc: # Ignoring errors is *not* the default. if ignore_errors: logger.error( "failed to serialize file record %d (%s), " "continuing", i, exc ) else: # Log error and close up the GeoJSON, leaving it # more or less valid no matter what happens above. logger.critical( "failed to serialize file record %d (%s), " "quitting", i, exc ) sink.write("]") sink.write(tail) if indented: sink.write("\n") raise # Because trailing commas aren't valid in JSON arrays # we'll write the item separator before each of the # remaining features. for i, rec in enumerate(itr, 1): rec = transformer(rec) try: if with_ld_context: rec = helpers.id_record(rec) if indented: sink.write(rec_indent) sink.write(item_sep) sink.write( json.dumps(rec, cls=ObjectEncoder, **dump_kwds).replace( "\n", rec_indent ) ) except Exception as exc: if ignore_errors: logger.error( "failed to serialize file record %d (%s), " "continuing", i, exc) else: logger.critical( "failed to serialize file record %d (%s), " "quitting", i, exc) sink.write("]") sink.write(tail) if indented: sink.write("\n") raise # Close up the GeoJSON after writing all features. sink.write("]") sink.write(tail) if indented: sink.write("\n") else: # Buffer GeoJSON data at the collection level. The default. collection = { "type": "FeatureCollection", "fiona:schema": meta["schema"], "fiona:crs": meta["crs"].to_string(), } if with_ld_context: collection["@context"] = helpers.make_ld_context(add_ld_context_item) collection["features"] = [ helpers.id_record(transformer(rec)) for rec in source ] else: collection["features"] = [ transformer(source.crs, rec) for rec in source ] json.dump(collection, sink, cls=ObjectEncoder, **dump_kwds) Fiona-1.10.1/fiona/fio/env.py000066400000000000000000000027051467206072700156600ustar00rootroot00000000000000"""$ fio env""" import json import os import click import fiona from fiona._env import GDALDataFinder, PROJDataFinder @click.command(short_help="Print information about the fio environment.") @click.option('--formats', 'key', flag_value='formats', default=True, help="Enumerate the available formats.") @click.option('--credentials', 'key', flag_value='credentials', default=False, help="Print credentials.") @click.option('--gdal-data', 'key', flag_value='gdal_data', default=False, help="Print GDAL data path.") @click.option('--proj-data', 'key', flag_value='proj_data', default=False, help="Print PROJ data path.") @click.pass_context def env(ctx, key): """Print information about the Fiona environment: available formats, etc. """ stdout = click.get_text_stream('stdout') with ctx.obj['env'] as env: if key == 'formats': for k, v in sorted(fiona.supported_drivers.items()): modes = ', '.join("'" + m + "'" for m in v) stdout.write(f"{k} (modes {modes})\n") stdout.write('\n') elif key == 'credentials': click.echo(json.dumps(env.session.credentials)) elif key == 'gdal_data': click.echo(os.environ.get('GDAL_DATA') or GDALDataFinder().search()) elif key == 'proj_data': click.echo(os.environ.get('PROJ_DATA', os.environ.get('PROJ_LIB')) or PROJDataFinder().search()) Fiona-1.10.1/fiona/fio/features.py000066400000000000000000000215041467206072700167040ustar00rootroot00000000000000"""Fiona CLI commands.""" from collections import defaultdict from copy import copy import itertools import json import logging import warnings import click from cligj import use_rs_opt # type: ignore from fiona.features import map_feature, reduce_features from fiona.fio import with_context_env from fiona.fio.helpers import obj_gen, eval_feature_expression # type: ignore log = logging.getLogger(__name__) @click.command( "map", short_help="Map a pipeline expression over GeoJSON features.", ) @click.argument("pipeline") @click.option( "--raw", "-r", is_flag=True, default=False, help="Print raw result, do not wrap in a GeoJSON Feature.", ) @click.option( "--no-input", "-n", is_flag=True, default=False, help="Do not read input from stream.", ) @click.option( "--dump-parts", is_flag=True, default=False, help="Dump parts of geometries to create new inputs before evaluating pipeline.", ) @use_rs_opt def map_cmd(pipeline, raw, no_input, dump_parts, use_rs): """Map a pipeline expression over GeoJSON features. Given a sequence of GeoJSON features (RS-delimited or not) on stdin this prints copies with geometries that are transformed using a provided transformation pipeline. In "raw" output mode, this command prints pipeline results without wrapping them in a feature object. The pipeline is a string that, when evaluated by fio-map, produces a new geometry object. The pipeline consists of expressions in the form of parenthesized lists that may contain other expressions. The first item in a list is the name of a function or method, or an expression that evaluates to a function. The second item is the function's first argument or the object to which the method is bound. The remaining list items are the positional and keyword arguments for the named function or method. The names of the input feature and its geometry in the context of these expressions are "f" and "g". For example, this pipeline expression '(simplify (buffer g 100.0) 5.0)' buffers input geometries and then simplifies them so that no vertices are closer than 5 units. Keyword arguments for the shapely methods are supported. A keyword argument is preceded by ':' and followed immediately by its value. For example: '(simplify g 5.0 :preserve_topology true)' and '(buffer g 100.0 :resolution 8 :join_style 1)' Numerical and string arguments may be replaced by expressions. The buffer distance could be a function of a geometry's area. '(buffer g (/ (area g) 100.0))' """ if no_input: features = [None] else: stdin = click.get_text_stream("stdin") features = obj_gen(stdin) for feat in features: for i, value in enumerate(map_feature(pipeline, feat, dump_parts=dump_parts)): if use_rs: click.echo("\x1e", nl=False) if raw: click.echo(json.dumps(value)) else: new_feat = copy(feat) new_feat["id"] = f"{feat.get('id', '0')}:{i}" new_feat["geometry"] = value click.echo(json.dumps(new_feat)) @click.command( "filter", short_help="Evaluate pipeline expressions to filter GeoJSON features.", ) @click.argument("pipeline") @use_rs_opt @click.option( "--snuggs-only", "-s", is_flag=True, default=False, help="Strictly require snuggs style expressions and skip check for type of expression.", ) @click.pass_context @with_context_env def filter_cmd(ctx, pipeline, use_rs, snuggs_only): """Evaluate pipeline expressions to filter GeoJSON features. The pipeline is a string that, when evaluated, gives a new value for each input feature. If the value evaluates to True, the feature passes through the filter. Otherwise the feature does not pass. The pipeline consists of expressions in the form of parenthesized lists that may contain other expressions. The first item in a list is the name of a function or method, or an expression that evaluates to a function. The second item is the function's first argument or the object to which the method is bound. The remaining list items are the positional and keyword arguments for the named function or method. The names of the input feature and its geometry in the context of these expressions are "f" and "g". For example, this pipeline expression '(< (distance g (Point 4 43)) 1)' lets through all features that are less than one unit from the given point and filters out all other features. *New in version 1.10*: these parenthesized list expressions. The older style Python expressions like 'f.properties.area > 1000.0' are deprecated and will not be supported in version 2.0. """ stdin = click.get_text_stream("stdin") features = obj_gen(stdin) if not snuggs_only: try: from pyparsing.exceptions import ParseException from fiona._vendor.snuggs import ExpressionError, expr if not pipeline.startswith("("): test_string = f"({pipeline})" expr.parseString(test_string) except ExpressionError: # It's a snuggs expression. log.info("Detected a snuggs expression.") pass except ParseException: # It's likely an old-style Python expression. log.info("Detected a legacy Python expression.") warnings.warn( "This style of filter expression is deprecated. " "Version 2.0 will only support the new parenthesized list expressions.", FutureWarning, ) for i, obj in enumerate(features): feats = obj.get("features") or [obj] for j, feat in enumerate(feats): if not eval_feature_expression(feat, pipeline): continue if use_rs: click.echo("\x1e", nl=False) click.echo(json.dumps(feat)) return for feat in features: for value in map_feature(pipeline, feat): if value: if use_rs: click.echo("\x1e", nl=False) click.echo(json.dumps(feat)) @click.command("reduce", short_help="Reduce a stream of GeoJSON features to one value.") @click.argument("pipeline") @click.option( "--raw", "-r", is_flag=True, default=False, help="Print raw result, do not wrap in a GeoJSON Feature.", ) @use_rs_opt @click.option( "--zip-properties", is_flag=True, default=False, help="Zip the items of input feature properties together for output.", ) def reduce_cmd(pipeline, raw, use_rs, zip_properties): """Reduce a stream of GeoJSON features to one value. Given a sequence of GeoJSON features (RS-delimited or not) on stdin this prints a single value using a provided transformation pipeline. The pipeline is a string that, when evaluated, produces a new geometry object. The pipeline consists of expressions in the form of parenthesized lists that may contain other expressions. The first item in a list is the name of a function or method, or an expression that evaluates to a function. The second item is the function's first argument or the object to which the method is bound. The remaining list items are the positional and keyword arguments for the named function or method. The set of geometries of the input features in the context of these expressions is named "c". For example, the pipeline expression '(unary_union c)' dissolves the geometries of input features. To keep the properties of input features while reducing them to a single feature, use the --zip-properties flag. The properties of the input features will surface in the output feature as lists containing the input values. """ stdin = click.get_text_stream("stdin") features = (feat for feat in obj_gen(stdin)) if zip_properties: prop_features, geom_features = itertools.tee(features) properties = defaultdict(list) for feat in prop_features: for key, val in feat["properties"].items(): properties[key].append(val) else: geom_features = features properties = {} for result in reduce_features(pipeline, geom_features): if use_rs: click.echo("\x1e", nl=False) if raw: click.echo(json.dumps(result)) else: click.echo( json.dumps( { "type": "Feature", "properties": properties, "geometry": result, "id": "0", } ) ) Fiona-1.10.1/fiona/fio/helpers.py000066400000000000000000000077621467206072700165420ustar00rootroot00000000000000"""Helper objects needed by multiple CLI commands. """ from functools import partial import json import math import warnings from fiona.model import Geometry, to_dict from fiona._vendor.munch import munchify warnings.simplefilter("default") def obj_gen(lines, object_hook=None): """Return a generator of JSON objects loaded from ``lines``.""" first_line = next(lines) if first_line.startswith("\x1e"): def gen(): buffer = first_line.strip("\x1e") for line in lines: if line.startswith("\x1e"): if buffer: yield json.loads(buffer, object_hook=object_hook) buffer = line.strip("\x1e") else: buffer += line else: yield json.loads(buffer, object_hook=object_hook) else: def gen(): yield json.loads(first_line, object_hook=object_hook) for line in lines: yield json.loads(line, object_hook=object_hook) return gen() def nullable(val, cast): if val is None: return None else: return cast(val) def eval_feature_expression(feature, expression): safe_dict = {"f": munchify(to_dict(feature))} safe_dict.update( { "sum": sum, "pow": pow, "min": min, "max": max, "math": math, "bool": bool, "int": partial(nullable, int), "str": partial(nullable, str), "float": partial(nullable, float), "len": partial(nullable, len), } ) try: from shapely.geometry import shape safe_dict["shape"] = shape except ImportError: pass return eval(expression, {"__builtins__": None}, safe_dict) def make_ld_context(context_items): """Returns a JSON-LD Context object. See https://json-ld.org/spec/latest/json-ld/.""" ctx = { "@context": { "geojson": "http://ld.geojson.org/vocab#", "Feature": "geojson:Feature", "FeatureCollection": "geojson:FeatureCollection", "GeometryCollection": "geojson:GeometryCollection", "LineString": "geojson:LineString", "MultiLineString": "geojson:MultiLineString", "MultiPoint": "geojson:MultiPoint", "MultiPolygon": "geojson:MultiPolygon", "Point": "geojson:Point", "Polygon": "geojson:Polygon", "bbox": {"@container": "@list", "@id": "geojson:bbox"}, "coordinates": "geojson:coordinates", "datetime": "http://www.w3.org/2006/time#inXSDDateTime", "description": "http://purl.org/dc/terms/description", "features": {"@container": "@set", "@id": "geojson:features"}, "geometry": "geojson:geometry", "id": "@id", "properties": "geojson:properties", "start": "http://www.w3.org/2006/time#hasBeginning", "stop": "http://www.w3.org/2006/time#hasEnding", "title": "http://purl.org/dc/terms/title", "type": "@type", "when": "geojson:when", } } for item in context_items or []: t, uri = item.split("=") ctx[t.strip()] = uri.strip() return ctx def id_record(rec): """Converts a record's id to a blank node id and returns the record.""" rec["id"] = f"_:f{rec['id']}" return rec def recursive_round(obj, precision): """Recursively round coordinates.""" if precision < 0: return obj if getattr(obj, "geometries", None): return Geometry( geometries=[recursive_round(part, precision) for part in obj.geometries] ) elif getattr(obj, "coordinates", None): return Geometry( coordinates=[recursive_round(part, precision) for part in obj.coordinates] ) if isinstance(obj, (int, float)): return round(obj, precision) else: return [recursive_round(part, precision) for part in obj] Fiona-1.10.1/fiona/fio/info.py000066400000000000000000000047771467206072700160360ustar00rootroot00000000000000"""$ fio info""" import logging import json import click from cligj import indent_opt import fiona import fiona.crs from fiona.errors import DriverError from fiona.fio import options, with_context_env logger = logging.getLogger(__name__) @click.command() # One or more files. @click.argument('input', required=True) @click.option('--layer', metavar="INDEX|NAME", callback=options.cb_layer, help="Print information about a specific layer. The first " "layer is used by default. Layers use zero-based " "numbering when accessed by index.") @indent_opt # Options to pick out a single metadata item and print it as # a string. @click.option('--count', 'meta_member', flag_value='count', help="Print the count of features.") @click.option('-f', '--format', '--driver', 'meta_member', flag_value='driver', help="Print the format driver.") @click.option('--crs', 'meta_member', flag_value='crs', help="Print the CRS as a PROJ.4 string.") @click.option('--bounds', 'meta_member', flag_value='bounds', help="Print the boundary coordinates " "(left, bottom, right, top).") @click.option('--name', 'meta_member', flag_value='name', help="Print the datasource's name.") @options.open_opt @click.pass_context @with_context_env def info(ctx, input, indent, meta_member, layer, open_options): """ Print information about a dataset. When working with a multi-layer dataset the first layer is used by default. Use the '--layer' option to select a different layer. """ with fiona.open(input, layer=layer, **open_options) as src: info = src.meta info.update(name=src.name) try: info.update(bounds=src.bounds) except DriverError: info.update(bounds=None) logger.debug( "Setting 'bounds' to None - driver was not able to calculate bounds" ) try: info.update(count=len(src)) except TypeError: info.update(count=None) logger.debug( "Setting 'count' to None/null - layer does not support counting" ) info["crs"] = src.crs.to_string() if meta_member: if isinstance(info[meta_member], (list, tuple)): click.echo(" ".join(map(str, info[meta_member]))) else: click.echo(info[meta_member]) else: click.echo(json.dumps(info, indent=indent)) Fiona-1.10.1/fiona/fio/insp.py000066400000000000000000000023451467206072700160410ustar00rootroot00000000000000"""$ fio insp""" import code import sys import click import fiona from fiona.fio import options, with_context_env @click.command(short_help="Open a dataset and start an interpreter.") @click.argument("src_path", required=True) @click.option( "--ipython", "interpreter", flag_value="ipython", help="Use IPython as interpreter." ) @options.open_opt @click.pass_context @with_context_env def insp(ctx, src_path, interpreter, open_options): """Open a collection within an interactive interpreter.""" banner = ( "Fiona %s Interactive Inspector (Python %s)\n" 'Type "src.schema", "next(src)", or "help(src)" ' "for more information." % (fiona.__version__, ".".join(map(str, sys.version_info[:3]))) ) with fiona.open(src_path, **open_options) as src: scope = locals() if not interpreter: code.interact(banner, local=scope) elif interpreter == "ipython": import IPython IPython.InteractiveShell.banner1 = banner IPython.start_ipython(argv=[], user_ns=scope) else: raise click.ClickException( f"Interpreter {interpreter} is unsupported or missing " "dependencies" ) Fiona-1.10.1/fiona/fio/load.py000066400000000000000000000056651467206072700160170ustar00rootroot00000000000000"""$ fio load""" from functools import partial import click import cligj import fiona from fiona.fio import options, with_context_env from fiona.model import Feature, Geometry from fiona.transform import transform_geom @click.command(short_help="Load GeoJSON to a dataset in another format.") @click.argument("output", required=True) @click.option("-f", "--format", "--driver", "driver", help="Output format driver name.") @options.src_crs_opt @click.option( "--dst-crs", "--dst_crs", help="Destination CRS. Defaults to --src-crs when not given.", ) @cligj.features_in_arg @click.option( "--layer", metavar="INDEX|NAME", callback=options.cb_layer, help="Load features into specified layer. Layers use " "zero-based numbering when accessed by index.", ) @options.creation_opt @options.open_opt @click.option("--append", is_flag=True, help="Open destination layer in append mode.") @click.pass_context @with_context_env def load( ctx, output, driver, src_crs, dst_crs, features, layer, creation_options, open_options, append, ): """Load features from JSON to a file in another format. The input is a GeoJSON feature collection or optionally a sequence of GeoJSON feature objects. """ dst_crs = dst_crs or src_crs if src_crs and dst_crs and src_crs != dst_crs: transformer = partial( transform_geom, src_crs, dst_crs, antimeridian_cutting=True ) else: def transformer(x): return Geometry.from_dict(**x) def feature_gen(): """Convert stream of JSON to features. Yields ------ Feature """ try: for feat in features: feat["geometry"] = transformer(Geometry.from_dict(**feat["geometry"])) yield Feature.from_dict(**feat) except TypeError: raise click.ClickException("Invalid input.") source = feature_gen() # Use schema of first feature as a template. # TODO: schema specified on command line? try: first = next(source) except TypeError: raise click.ClickException("Invalid input.") # TODO: this inference of a property's type from its value needs some # work. It works reliably only for the basic JSON serializable types. # The fio-load command does require JSON input but that may change # someday. schema = {"geometry": first.geometry.type} schema["properties"] = { k: type(v if v is not None else "").__name__ for k, v in first.properties.items() } if append: opener = fiona.open(output, "a", layer=layer, **open_options) else: opener = fiona.open( output, "w", driver=driver, crs=dst_crs, schema=schema, layer=layer, **creation_options ) with opener as dst: dst.write(first) dst.writerecords(source) Fiona-1.10.1/fiona/fio/ls.py000066400000000000000000000007111467206072700155010ustar00rootroot00000000000000"""$ fiona ls""" import json import click from cligj import indent_opt import fiona from fiona.fio import options, with_context_env @click.command() @click.argument('input', required=True) @indent_opt @options.open_opt @click.pass_context @with_context_env def ls(ctx, input, indent, open_options): """ List layers in a datasource. """ result = fiona.listlayers(input, **open_options) click.echo(json.dumps(result, indent=indent)) Fiona-1.10.1/fiona/fio/main.py000066400000000000000000000057731467206072700160240ustar00rootroot00000000000000""" Main click group for the CLI. Needs to be isolated for entry-point loading. """ import itertools import logging import sys import click from click_plugins import with_plugins from cligj import verbose_opt, quiet_opt if sys.version_info < (3, 10): from importlib_metadata import entry_points else: from importlib.metadata import entry_points import fiona from fiona import __version__ as fio_version from fiona.session import AWSSession, DummySession from fiona.fio.bounds import bounds from fiona.fio.calc import calc from fiona.fio.cat import cat from fiona.fio.collect import collect from fiona.fio.distrib import distrib from fiona.fio.dump import dump from fiona.fio.env import env from fiona.fio.info import info from fiona.fio.insp import insp from fiona.fio.load import load from fiona.fio.ls import ls from fiona.fio.rm import rm # The "calc" extras require pyparsing and shapely. try: import pyparsing import shapely from fiona.fio.features import filter_cmd, map_cmd, reduce_cmd supports_calc = True except ImportError: supports_calc = False def configure_logging(verbosity): log_level = max(10, 30 - 10 * verbosity) logging.basicConfig(stream=sys.stderr, level=log_level) @with_plugins( itertools.chain( entry_points(group="fiona.fio_plugins"), ) ) @click.group() @verbose_opt @quiet_opt @click.option( "--aws-profile", help="Select a profile from the AWS credentials file") @click.option( "--aws-no-sign-requests", is_flag=True, help="Make requests anonymously") @click.option( "--aws-requester-pays", is_flag=True, help="Requester pays data transfer costs") @click.version_option(fio_version) @click.version_option(fiona.__gdal_version__, '--gdal-version', prog_name='GDAL') @click.version_option(sys.version, '--python-version', prog_name='Python') @click.pass_context def main_group( ctx, verbose, quiet, aws_profile, aws_no_sign_requests, aws_requester_pays): """Fiona command line interface. """ verbosity = verbose - quiet configure_logging(verbosity) ctx.obj = {} ctx.obj["verbosity"] = verbosity ctx.obj["aws_profile"] = aws_profile envopts = {"CPL_DEBUG": (verbosity > 2)} if aws_profile or aws_no_sign_requests: session = AWSSession( profile_name=aws_profile, aws_unsigned=aws_no_sign_requests, requester_pays=aws_requester_pays, ) else: session = DummySession() ctx.obj["env"] = fiona.Env(session=session, **envopts) main_group.add_command(bounds) main_group.add_command(calc) main_group.add_command(cat) main_group.add_command(collect) main_group.add_command(distrib) main_group.add_command(dump) main_group.add_command(env) main_group.add_command(info) main_group.add_command(insp) main_group.add_command(load) main_group.add_command(ls) main_group.add_command(rm) if supports_calc: main_group.add_command(map_cmd) main_group.add_command(filter_cmd) main_group.add_command(reduce_cmd) Fiona-1.10.1/fiona/fio/options.py000066400000000000000000000047731467206072700165720ustar00rootroot00000000000000"""Common commandline options for `fio`""" from collections import defaultdict import click src_crs_opt = click.option('--src-crs', '--src_crs', help="Source CRS.") dst_crs_opt = click.option('--dst-crs', '--dst_crs', help="Destination CRS.") def cb_layer(ctx, param, value): """Let --layer be a name or index.""" if value is None or not value.isdigit(): return value else: return int(value) def cb_multilayer(ctx, param, value): """ Transform layer options from strings ("1:a,1:b", "2:a,2:c,2:z") to { '1': ['a', 'b'], '2': ['a', 'c', 'z'] } """ out = defaultdict(list) for raw in value: for v in raw.split(','): ds, name = v.split(':') out[ds].append(name) return out def cb_key_val(ctx, param, value): """ click callback to validate `--opt KEY1=VAL1 --opt KEY2=VAL2` and collect in a dictionary like the one below, which is what the CLI function receives. If no value or `None` is received then an empty dictionary is returned. { 'KEY1': 'VAL1', 'KEY2': 'VAL2' } Note: `==VAL` breaks this as `str.split('=', 1)` is used. """ if not value: return {} else: out = {} for pair in value: if "=" not in pair: raise click.BadParameter( f"Invalid syntax for KEY=VAL arg: {pair}" ) else: k, v = pair.split("=", 1) k = k.lower() v = v.lower() out[k] = None if v.lower() in ["none", "null", "nil", "nada"] else v return out def validate_multilayer_file_index(files, layerdict): """ Ensure file indexes provided in the --layer option are valid """ for key in layerdict.keys(): if key not in [str(k) for k in range(1, len(files) + 1)]: layer = key + ":" + layerdict[key][0] raise click.BadParameter(f"Layer {layer} does not exist") creation_opt = click.option( "--co", "--profile", "creation_options", metavar="NAME=VALUE", multiple=True, callback=cb_key_val, help="Driver specific creation options. See the documentation for the selected output driver for more information.", ) open_opt = click.option( "--oo", "open_options", metavar="NAME=VALUE", multiple=True, callback=cb_key_val, help="Driver specific open options. See the documentation for the selected output driver for more information.", ) Fiona-1.10.1/fiona/fio/rm.py000066400000000000000000000014011467206072700154760ustar00rootroot00000000000000import click import logging import fiona from fiona.fio import with_context_env logger = logging.getLogger(__name__) @click.command(help="Remove a datasource or an individual layer.") @click.argument("input", required=True) @click.option("--layer", type=str, default=None, required=False, help="Name of layer to remove.") @click.option("--yes", is_flag=True) @click.pass_context @with_context_env def rm(ctx, input, layer, yes): if layer is None: kind = "datasource" else: kind = "layer" if not yes: click.confirm(f"The {kind} will be removed. Are you sure?", abort=True) try: fiona.remove(input, layer=layer) except Exception: logger.exception(f"Failed to remove {kind}.") raise click.Abort() Fiona-1.10.1/fiona/gdal.pxi000066400000000000000000001061711467206072700153740ustar00rootroot00000000000000# GDAL API definitions. from libc.stdio cimport FILE cdef extern from "gdal_version.h": int GDAL_COMPUTE_VERSION(int maj, int min, int rev) cdef extern from "cpl_conv.h": void * CPLMalloc (size_t) void CPLFree (void *ptr) void CPLSetThreadLocalConfigOption (char *key, char *val) const char *CPLGetConfigOption (char *, char *) void CPLSetConfigOption(const char* key, const char* val) int CPLCheckForFile(char *, char **) const char *CPLFindFile(const char *pszClass, const char *pszBasename) cdef extern from "cpl_port.h": ctypedef char **CSLConstList cdef extern from "cpl_string.h": char ** CSLAddNameValue(char **list, const char *name, const char *value) char ** CSLSetNameValue(char **list, const char *name, const char *value) void CSLDestroy(char **list) char ** CSLAddString(char **list, const char *string) int CSLCount(CSLConstList papszStrList) char **CSLDuplicate(CSLConstList papszStrList) int CSLFindName(CSLConstList papszStrList, const char *pszName) int CSLFindString(CSLConstList papszStrList, const char *pszString) int CSLFetchBoolean(CSLConstList papszStrList, const char *pszName, int default) const char *CSLFetchNameValue(CSLConstList papszStrList, const char *pszName) char **CSLMerge(char **first, CSLConstList second) cdef extern from "cpl_error.h" nogil: ctypedef enum CPLErr: CE_None CE_Debug CE_Warning CE_Failure CE_Fatal ctypedef int CPLErrorNum ctypedef void (*CPLErrorHandler)(CPLErr, int, const char*) void CPLError(CPLErr eErrClass, CPLErrorNum err_no, const char *template, ...) void CPLErrorReset() int CPLGetLastErrorNo() const char* CPLGetLastErrorMsg() CPLErr CPLGetLastErrorType() void CPLPushErrorHandler(CPLErrorHandler handler) void CPLPushErrorHandlerEx(CPLErrorHandler handler, void *userdata) void CPLPopErrorHandler() void CPLQuietErrorHandler(CPLErr eErrClass, CPLErrorNum nError, const char *pszErrorMsg) cdef extern from "cpl_vsi.h" nogil: ctypedef unsigned long long vsi_l_offset ctypedef FILE VSILFILE ctypedef struct VSIStatBufL: long st_size long st_mode int st_mtime ctypedef enum VSIRangeStatus: VSI_RANGE_STATUS_UNKNOWN, VSI_RANGE_STATUS_DATA, VSI_RANGE_STATUS_HOLE, # GDAL Plugin System (GDAL 3.0+) # Filesystem functions ctypedef int (*VSIFilesystemPluginStatCallback)(void*, const char*, VSIStatBufL*, int) # Optional ctypedef int (*VSIFilesystemPluginUnlinkCallback)(void*, const char*) # Optional ctypedef int (*VSIFilesystemPluginRenameCallback)(void*, const char*, const char*) # Optional ctypedef int (*VSIFilesystemPluginMkdirCallback)(void*, const char*, long) # Optional ctypedef int (*VSIFilesystemPluginRmdirCallback)(void*, const char*) # Optional ctypedef char** (*VSIFilesystemPluginReadDirCallback)(void*, const char*, int) # Optional ctypedef char** (*VSIFilesystemPluginSiblingFilesCallback)(void*, const char*) # Optional (GDAL 3.2+) ctypedef void* (*VSIFilesystemPluginOpenCallback)(void*, const char*, const char*) # File functions ctypedef vsi_l_offset (*VSIFilesystemPluginTellCallback)(void*) ctypedef int (*VSIFilesystemPluginSeekCallback)(void*, vsi_l_offset, int) ctypedef size_t (*VSIFilesystemPluginReadCallback)(void*, void*, size_t, size_t) ctypedef int (*VSIFilesystemPluginReadMultiRangeCallback)(void*, int, void**, const vsi_l_offset*, const size_t*) # Optional ctypedef VSIRangeStatus (*VSIFilesystemPluginGetRangeStatusCallback)(void*, vsi_l_offset, vsi_l_offset) # Optional ctypedef int (*VSIFilesystemPluginEofCallback)(void*) # Mandatory? ctypedef size_t (*VSIFilesystemPluginWriteCallback)(void*, const void*, size_t, size_t) ctypedef int (*VSIFilesystemPluginFlushCallback)(void*) # Optional ctypedef int (*VSIFilesystemPluginTruncateCallback)(void*, vsi_l_offset) ctypedef int (*VSIFilesystemPluginCloseCallback)(void*) # Optional # Plugin function container struct ctypedef struct VSIFilesystemPluginCallbacksStruct: void *pUserData VSIFilesystemPluginStatCallback stat VSIFilesystemPluginUnlinkCallback unlink VSIFilesystemPluginRenameCallback rename VSIFilesystemPluginMkdirCallback mkdir VSIFilesystemPluginRmdirCallback rmdir VSIFilesystemPluginReadDirCallback read_dir VSIFilesystemPluginOpenCallback open VSIFilesystemPluginTellCallback tell VSIFilesystemPluginSeekCallback seek VSIFilesystemPluginReadCallback read VSIFilesystemPluginReadMultiRangeCallback read_multi_range VSIFilesystemPluginGetRangeStatusCallback get_range_status VSIFilesystemPluginEofCallback eof VSIFilesystemPluginWriteCallback write VSIFilesystemPluginFlushCallback flush VSIFilesystemPluginTruncateCallback truncate VSIFilesystemPluginCloseCallback close size_t nBufferSize size_t nCacheSize VSIFilesystemPluginSiblingFilesCallback sibling_files int VSIInstallPluginHandler(const char*, const VSIFilesystemPluginCallbacksStruct*) VSIFilesystemPluginCallbacksStruct* VSIAllocFilesystemPluginCallbacksStruct() void VSIFreeFilesystemPluginCallbacksStruct(VSIFilesystemPluginCallbacksStruct*) char** VSIGetFileSystemsPrefixes() unsigned char *VSIGetMemFileBuffer(const char *path, vsi_l_offset *data_len, int take_ownership) VSILFILE *VSIFileFromMemBuffer(const char *path, void *data, vsi_l_offset data_len, int take_ownership) VSILFILE* VSIFOpenL(const char *path, const char *mode) int VSIFCloseL(VSILFILE *fp) int VSIUnlink(const char *path) int VSIMkdir(const char *path, long mode) char** VSIReadDir(const char *path) int VSIRmdir(const char *path) int VSIRmdirRecursive(const char *path) int VSIFFlushL(VSILFILE *fp) size_t VSIFReadL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp) int VSIFSeekL(VSILFILE *fp, vsi_l_offset nOffset, int nWhence) vsi_l_offset VSIFTellL(VSILFILE *fp) int VSIFTruncateL(VSILFILE *fp, vsi_l_offset nNewSize) size_t VSIFWriteL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp) int VSIStatL(const char *pszFilename, VSIStatBufL *psStatBuf) int VSIMkdir(const char *path, long mode) int VSIRmdir(const char *path) int VSIStatL(const char *pszFilename, VSIStatBufL *psStatBuf) int VSI_ISDIR(int mode) IF (CTE_GDAL_MAJOR_VERSION, CTE_GDAL_MINOR_VERSION) >= (3, 9): cdef extern from "cpl_vsi.h" nogil: int VSIRemovePluginHandler(const char*) cdef extern from "ogr_core.h" nogil: ctypedef int OGRErr char *OGRGeometryTypeToName(int type) ctypedef enum OGRwkbGeometryType: wkbUnknown wkbPoint wkbLineString wkbPolygon wkbMultiPoint wkbMultiLineString wkbMultiPolygon wkbGeometryCollection wkbCircularString wkbCompoundCurve wkbCurvePolygon wkbMultiCurve wkbMultiSurface wkbCurve wkbSurface wkbPolyhedralSurface wkbTIN wkbTriangle wkbNone wkbLinearRing wkbCircularStringZ wkbCompoundCurveZ wkbCurvePolygonZ wkbMultiCurveZ wkbMultiSurfaceZ wkbCurveZ wkbSurfaceZ wkbPolyhedralSurfaceZ wkbTINZ wkbTriangleZ wkbPointM wkbLineStringM wkbPolygonM wkbMultiPointM wkbMultiLineStringM wkbMultiPolygonM wkbGeometryCollectionM wkbCircularStringM wkbCompoundCurveM wkbCurvePolygonM wkbMultiCurveM wkbMultiSurfaceM wkbCurveM wkbSurfaceM wkbPolyhedralSurfaceM wkbTINM wkbTriangleM wkbPointZM wkbLineStringZM wkbPolygonZM wkbMultiPointZM wkbMultiLineStringZM wkbMultiPolygonZM wkbGeometryCollectionZM wkbCircularStringZM wkbCompoundCurveZM wkbCurvePolygonZM wkbMultiCurveZM wkbMultiSurfaceZM wkbCurveZM wkbSurfaceZM wkbPolyhedralSurfaceZM wkbTINZM wkbTriangleZM wkbPoint25D wkbLineString25D wkbPolygon25D wkbMultiPoint25D wkbMultiLineString25D wkbMultiPolygon25D wkbGeometryCollection25D ctypedef enum OGRFieldType: OFTInteger OFTIntegerList OFTReal OFTRealList OFTString OFTStringList OFTWideString OFTWideStringList OFTBinary OFTDate OFTTime OFTDateTime OFTInteger64 OFTInteger64List OFTMaxType ctypedef int OGRFieldSubType cdef int OFSTNone = 0 cdef int OFSTBoolean = 1 cdef int OFSTInt16 = 2 cdef int OFSTFloat32 = 3 cdef int OFSTJSON = 4 cdef int OFSTUUID = 5 cdef int OFSTMaxSubType = 5 ctypedef struct OGREnvelope: double MinX double MaxX double MinY double MaxY char * OGRGeometryTypeToName(int) char * ODsCCreateLayer = "CreateLayer" char * ODsCDeleteLayer = "DeleteLayer" char * ODsCTransactions = "Transactions" cdef extern from "ogr_srs_api.h" nogil: ctypedef void * OGRCoordinateTransformationH ctypedef void * OGRSpatialReferenceH OGRCoordinateTransformationH OCTNewCoordinateTransformation( OGRSpatialReferenceH source, OGRSpatialReferenceH dest) void OCTDestroyCoordinateTransformation( OGRCoordinateTransformationH source) int OCTTransform(OGRCoordinateTransformationH ct, int nCount, double *x, double *y, double *z) int OSRAutoIdentifyEPSG(OGRSpatialReferenceH srs) void OSRCleanup() OGRSpatialReferenceH OSRClone(OGRSpatialReferenceH srs) int OSRExportToProj4(OGRSpatialReferenceH srs, char **params) int OSRExportToWkt(OGRSpatialReferenceH srs, char **params) const char *OSRGetAuthorityName(OGRSpatialReferenceH srs, const char *key) const char *OSRGetAuthorityCode(OGRSpatialReferenceH srs, const char *key) int OSRImportFromEPSG(OGRSpatialReferenceH srs, int code) int OSRImportFromProj4(OGRSpatialReferenceH srs, const char *proj) int OSRImportFromWkt(OGRSpatialReferenceH srs, char **wkt) int OSRIsGeographic(OGRSpatialReferenceH srs) int OSRIsProjected(OGRSpatialReferenceH srs) int OSRIsSame(OGRSpatialReferenceH srs1, OGRSpatialReferenceH srs2) OGRSpatialReferenceH OSRNewSpatialReference(const char *wkt) void OSRRelease(OGRSpatialReferenceH srs) int OSRSetFromUserInput(OGRSpatialReferenceH srs, const char *input) double OSRGetLinearUnits(OGRSpatialReferenceH srs, char **ppszName) double OSRGetAngularUnits(OGRSpatialReferenceH srs, char **ppszName) int OSREPSGTreatsAsLatLong(OGRSpatialReferenceH srs) int OSREPSGTreatsAsNorthingEasting(OGRSpatialReferenceH srs) OGRSpatialReferenceH *OSRFindMatches(OGRSpatialReferenceH srs, char **options, int *entries, int **matchConfidence) void OSRFreeSRSArray(OGRSpatialReferenceH *srs) ctypedef enum OSRAxisMappingStrategy: OAMS_TRADITIONAL_GIS_ORDER const char* OSRGetName(OGRSpatialReferenceH hSRS) void OSRSetAxisMappingStrategy(OGRSpatialReferenceH hSRS, OSRAxisMappingStrategy) void OSRSetPROJSearchPaths(const char *const *papszPaths) char ** OSRGetPROJSearchPaths() OGRErr OSRExportToWktEx(OGRSpatialReferenceH, char ** ppszResult, const char* const* papszOptions) OGRErr OSRExportToPROJJSON(OGRSpatialReferenceH hSRS, char ** ppszReturn, const char* const* papszOptions) void OSRGetPROJVersion (int *pnMajor, int *pnMinor, int *pnPatch) cdef extern from "gdal.h" nogil: ctypedef void * GDALMajorObjectH ctypedef void * GDALDatasetH ctypedef void * GDALRasterBandH ctypedef void * GDALDriverH ctypedef void * GDALColorTableH ctypedef void * GDALRasterAttributeTableH ctypedef void * GDALAsyncReaderH ctypedef long long GSpacing ctypedef unsigned long long GIntBig ctypedef enum GDALDataType: GDT_Unknown GDT_Byte GDT_UInt16 GDT_Int16 GDT_UInt32 GDT_Int32 GDT_Float32 GDT_Float64 GDT_CInt16 GDT_CInt32 GDT_CFloat32 GDT_CFloat64 GDT_TypeCount ctypedef enum GDALAccess: GA_ReadOnly GA_Update ctypedef enum GDALRWFlag: GF_Read GF_Write ctypedef enum GDALRIOResampleAlg: GRIORA_NearestNeighbour GRIORA_Bilinear GRIORA_Cubic, GRIORA_CubicSpline GRIORA_Lanczos GRIORA_Average GRIORA_Mode GRIORA_Gauss ctypedef enum GDALColorInterp: GCI_Undefined GCI_GrayIndex GCI_PaletteIndex GCI_RedBand GCI_GreenBand GCI_BlueBand GCI_AlphaBand GCI_HueBand GCI_SaturationBand GCI_LightnessBand GCI_CyanBand GCI_YCbCr_YBand GCI_YCbCr_CbBand GCI_YCbCr_CrBand GCI_Max ctypedef struct GDALColorEntry: short c1 short c2 short c3 short c4 ctypedef struct GDAL_GCP: char *pszId char *pszInfo double dfGCPPixel double dfGCPLine double dfGCPX double dfGCPY double dfGCPZ void GDALAllRegister() void GDALDestroyDriverManager() int GDALGetDriverCount() GDALDriverH GDALGetDriver(int i) const char *GDALGetDriverShortName(GDALDriverH driver) const char *GDALGetDriverLongName(GDALDriverH driver) const char* GDALGetDescription(GDALMajorObjectH obj) void GDALSetDescription(GDALMajorObjectH obj, const char *text) GDALDriverH GDALGetDriverByName(const char *name) GDALDatasetH GDALOpen(const char *filename, GDALAccess access) # except -1 GDALDatasetH GDALOpenShared(const char *filename, GDALAccess access) # except -1 void GDALFlushCache(GDALDatasetH hds) void GDALClose(GDALDatasetH hds) GDALDriverH GDALGetDatasetDriver(GDALDatasetH hds) int GDALGetGeoTransform(GDALDatasetH hds, double *transform) const char *GDALGetProjectionRef(GDALDatasetH hds) int GDALGetRasterXSize(GDALDatasetH hds) int GDALGetRasterYSize(GDALDatasetH hds) int GDALGetRasterCount(GDALDatasetH hds) GDALRasterBandH GDALGetRasterBand(GDALDatasetH hds, int num) GDALRasterBandH GDALGetOverview(GDALRasterBandH hband, int num) int GDALGetRasterBandXSize(GDALRasterBandH hband) int GDALGetRasterBandYSize(GDALRasterBandH hband) const char *GDALGetRasterUnitType(GDALRasterBandH hband) CPLErr GDALSetRasterUnitType(GDALRasterBandH hband, const char *val) int GDALSetGeoTransform(GDALDatasetH hds, double *transform) int GDALSetProjection(GDALDatasetH hds, const char *wkt) void GDALGetBlockSize(GDALRasterBandH , int *xsize, int *ysize) int GDALGetRasterDataType(GDALRasterBandH band) double GDALGetRasterNoDataValue(GDALRasterBandH band, int *success) int GDALSetRasterNoDataValue(GDALRasterBandH band, double value) int GDALDatasetRasterIO(GDALRasterBandH band, int, int xoff, int yoff, int xsize, int ysize, void *buffer, int width, int height, int, int count, int *bmap, int poff, int loff, int boff) int GDALRasterIO(GDALRasterBandH band, int, int xoff, int yoff, int xsize, int ysize, void *buffer, int width, int height, int, int poff, int loff) int GDALFillRaster(GDALRasterBandH band, double rvalue, double ivalue) GDALDatasetH GDALCreate(GDALDriverH driver, const char *path, int width, int height, int nbands, GDALDataType dtype, const char **options) GDALDatasetH GDALCreateCopy(GDALDriverH driver, const char *path, GDALDatasetH hds, int strict, char **options, void *progress_func, void *progress_data) char** GDALGetMetadata(GDALMajorObjectH obj, const char *pszDomain) int GDALSetMetadata(GDALMajorObjectH obj, char **papszMD, const char *pszDomain) const char* GDALGetMetadataItem(GDALMajorObjectH obj, const char *pszName, const char *pszDomain) int GDALSetMetadataItem(GDALMajorObjectH obj, const char *pszName, const char *pszValue, const char *pszDomain) const GDALColorEntry *GDALGetColorEntry(GDALColorTableH table, int) void GDALSetColorEntry(GDALColorTableH table, int i, const GDALColorEntry *poEntry) int GDALSetRasterColorTable(GDALRasterBandH band, GDALColorTableH table) GDALColorTableH GDALGetRasterColorTable(GDALRasterBandH band) GDALColorTableH GDALCreateColorTable(int) void GDALDestroyColorTable(GDALColorTableH table) int GDALGetColorEntryCount(GDALColorTableH table) int GDALGetRasterColorInterpretation(GDALRasterBandH band) int GDALSetRasterColorInterpretation(GDALRasterBandH band, GDALColorInterp) int GDALGetMaskFlags(GDALRasterBandH band) int GDALCreateDatasetMaskBand(GDALDatasetH hds, int flags) void *GDALGetMaskBand(GDALRasterBandH band) int GDALCreateMaskBand(GDALDatasetH hds, int flags) int GDALGetOverviewCount(GDALRasterBandH band) int GDALBuildOverviews(GDALDatasetH hds, const char *resampling, int nOverviews, int *overviews, int nBands, int *bands, void *progress_func, void *progress_data) int GDALCheckVersion(int nVersionMajor, int nVersionMinor, const char *pszCallingComponentName) const char* GDALVersionInfo(const char *pszRequest) CPLErr GDALSetGCPs(GDALDatasetH hDS, int nGCPCount, const GDAL_GCP *pasGCPList, const char *pszGCPProjection) const GDAL_GCP *GDALGetGCPs(GDALDatasetH hDS) int GDALGetGCPCount(GDALDatasetH hDS) const char *GDALGetGCPProjection(GDALDatasetH hDS) int GDALGetCacheMax() void GDALSetCacheMax(int nBytes) GIntBig GDALGetCacheMax64() void GDALSetCacheMax64(GIntBig nBytes) CPLErr GDALDeleteDataset(GDALDriverH, const char *) char** GDALGetFileList(GDALDatasetH hDS) CPLErr GDALCopyDatasetFiles (GDALDriverH hDriver, const char * pszNewName, const char * pszOldName) void * GDALOpenEx(const char * pszFilename, unsigned int nOpenFlags, const char *const *papszAllowedDrivers, const char *const *papszOpenOptions, const char *const *papszSiblingFiles ) int GDAL_OF_UPDATE int GDAL_OF_READONLY int GDAL_OF_VECTOR int GDAL_OF_VERBOSE_ERROR int GDALDatasetGetLayerCount(void * hds) void * GDALDatasetGetLayer(void * hDS, int iLayer) void * GDALDatasetGetLayerByName(void * hDS, char * pszName) void GDALClose(void * hDS) void * GDALCreate(void * hDriver, const char * pszFilename, int nXSize, int nYSize, int nBands, GDALDataType eBandType, char ** papszOptions) void * GDALDatasetCreateLayer(void * hDS, const char * pszName, void * hSpatialRef, int eType, char ** papszOptions) int GDALDatasetDeleteLayer(void * hDS, int iLayer) void GDALFlushCache(void * hDS) char * GDALGetDriverShortName(void * hDriver) OGRErr GDALDatasetStartTransaction (void * hDataset, int bForce) OGRErr GDALDatasetCommitTransaction (void * hDataset) OGRErr GDALDatasetRollbackTransaction (void * hDataset) int GDALDatasetTestCapability (void * hDataset, char *) cdef extern from "ogr_api.h" nogil: ctypedef void * OGRLayerH ctypedef void * OGRDataSourceH ctypedef void * OGRSFDriverH ctypedef void * OGRFieldDefnH ctypedef void * OGRFeatureDefnH ctypedef void * OGRFeatureH ctypedef void * OGRGeometryH ctypedef struct OGREnvelope: double MinX double MaxX double MinY double MaxY void OGRRegisterAll() void OGRCleanupAll() int OGRGetDriverCount() char *OGR_Dr_GetName(OGRSFDriverH driver) OGRDataSourceH OGR_Dr_CreateDataSource(OGRSFDriverH driver, const char *path, char **options) int OGR_Dr_DeleteDataSource(OGRSFDriverH driver, const char *path) int OGR_DS_DeleteLayer(OGRDataSourceH datasource, int n) OGRLayerH OGR_DS_CreateLayer(OGRDataSourceH datasource, const char *name, OGRSpatialReferenceH crs, int geomType, char **options) OGRLayerH OGR_DS_ExecuteSQL(OGRDataSourceH, const char *name, OGRGeometryH filter, const char *dialext) void OGR_DS_Destroy(OGRDataSourceH datasource) OGRSFDriverH OGR_DS_GetDriver(OGRLayerH layer_defn) OGRLayerH OGR_DS_GetLayerByName(OGRDataSourceH datasource, const char *name) int OGR_DS_GetLayerCount(OGRDataSourceH datasource) OGRLayerH OGR_DS_GetLayer(OGRDataSourceH datasource, int n) void OGR_DS_ReleaseResultSet(OGRDataSourceH datasource, OGRLayerH results) int OGR_DS_SyncToDisk(OGRDataSourceH datasource) OGRFeatureH OGR_F_Create(OGRFeatureDefnH featuredefn) void OGR_F_Destroy(OGRFeatureH feature) long OGR_F_GetFID(OGRFeatureH feature) int OGR_F_IsFieldSet(OGRFeatureH feature, int n) int OGR_F_GetFieldAsDateTime(OGRFeatureH feature, int n, int *y, int *m, int *d, int *h, int *m, int *s, int *z) double OGR_F_GetFieldAsDouble(OGRFeatureH feature, int n) int OGR_F_GetFieldAsInteger(OGRFeatureH feature, int n) const char *OGR_F_GetFieldAsString(OGRFeatureH feature, int n) char **OGR_F_GetFieldAsStringList( OGRFeatureH feature, int n) int OGR_F_GetFieldCount(OGRFeatureH feature) OGRFieldDefnH OGR_F_GetFieldDefnRef(OGRFeatureH feature, int n) int OGR_F_GetFieldIndex(OGRFeatureH feature, const char *name) OGRGeometryH OGR_F_GetGeometryRef(OGRFeatureH feature) void OGR_F_SetFieldDateTime(OGRFeatureH feature, int n, int y, int m, int d, int hh, int mm, int ss, int tz) void OGR_F_SetFieldDouble(OGRFeatureH feature, int n, double value) void OGR_F_SetFieldInteger(OGRFeatureH feature, int n, int value) void OGR_F_SetFieldString(OGRFeatureH feature, int n, const char *value) void OGR_F_SetFieldStringList(OGRFeatureH feature, int n, const char **value) int OGR_F_SetGeometryDirectly(OGRFeatureH feature, OGRGeometryH geometry) OGRFeatureDefnH OGR_FD_Create(const char *name) int OGR_FD_GetFieldCount(OGRFeatureDefnH featuredefn) OGRFieldDefnH OGR_FD_GetFieldDefn(OGRFeatureDefnH featuredefn, int n) int OGR_FD_GetGeomType(OGRFeatureDefnH featuredefn) const char *OGR_FD_GetName(OGRFeatureDefnH featuredefn) OGRFieldDefnH OGR_Fld_Create(const char *name, int fieldtype) void OGR_Fld_Destroy(OGRFieldDefnH) char *OGR_Fld_GetNameRef(OGRFieldDefnH) int OGR_Fld_GetPrecision(OGRFieldDefnH) int OGR_Fld_GetType(OGRFieldDefnH) int OGR_Fld_GetWidth(OGRFieldDefnH) void OGR_Fld_Set(OGRFieldDefnH, const char *name, int fieldtype, int width, int precision, int justification) void OGR_Fld_SetPrecision(OGRFieldDefnH, int n) void OGR_Fld_SetWidth(OGRFieldDefnH, int n) OGRErr OGR_G_AddGeometryDirectly(OGRGeometryH geometry, OGRGeometryH part) OGRErr OGR_G_RemoveGeometry(OGRGeometryH geometry, int i, int delete) void OGR_G_AddPoint(OGRGeometryH geometry, double x, double y, double z) void OGR_G_AddPoint_2D(OGRGeometryH geometry, double x, double y) void OGR_G_CloseRings(OGRGeometryH geometry) OGRGeometryH OGR_G_CreateGeometry(int wkbtypecode) OGRGeometryH OGR_G_CreateGeometryFromJson(const char *json) void OGR_G_DestroyGeometry(OGRGeometryH geometry) char *OGR_G_ExportToJson(OGRGeometryH geometry) OGRErr OGR_G_ExportToWkb(OGRGeometryH geometry, int endianness, char *buffer) int OGR_G_GetCoordinateDimension(OGRGeometryH geometry) int OGR_G_GetGeometryCount(OGRGeometryH geometry) const char *OGR_G_GetGeometryName(OGRGeometryH geometry) int OGR_G_GetGeometryType(OGRGeometryH geometry) OGRGeometryH OGR_G_GetGeometryRef(OGRGeometryH geometry, int n) int OGR_G_GetPointCount(OGRGeometryH geometry) double OGR_G_GetX(OGRGeometryH geometry, int n) double OGR_G_GetY(OGRGeometryH geometry, int n) double OGR_G_GetZ(OGRGeometryH geometry, int n) OGRErr OGR_G_ImportFromWkb(OGRGeometryH geometry, unsigned char *bytes, int nbytes) int OGR_G_WkbSize(OGRGeometryH geometry) OGRErr OGR_L_CreateFeature(OGRLayerH layer, OGRFeatureH feature) int OGR_L_CreateField(OGRLayerH layer, OGRFieldDefnH, int flexible) OGRErr OGR_L_GetExtent(OGRLayerH layer, void *extent, int force) OGRFeatureH OGR_L_GetFeature(OGRLayerH layer, int n) int OGR_L_GetFeatureCount(OGRLayerH layer, int m) OGRFeatureDefnH OGR_L_GetLayerDefn(OGRLayerH layer) const char *OGR_L_GetName(OGRLayerH layer) OGRFeatureH OGR_L_GetNextFeature(OGRLayerH layer) OGRGeometryH OGR_L_GetSpatialFilter(OGRLayerH layer) OGRSpatialReferenceH OGR_L_GetSpatialRef(OGRLayerH layer) void OGR_L_ResetReading(OGRLayerH layer) void OGR_L_SetSpatialFilter(OGRLayerH layer, OGRGeometryH geometry) void OGR_L_SetSpatialFilterRect(OGRLayerH layer, double minx, double miny, double maxx, double maxy) int OGR_L_TestCapability(OGRLayerH layer, const char *name) OGRSFDriverH OGRGetDriverByName(const char *) OGRSFDriverH OGRGetDriver(int i) OGRDataSourceH OGROpen(const char *path, int mode, void *x) OGRDataSourceH OGROpenShared(const char *path, int mode, void *x) int OGRReleaseDataSource(OGRDataSourceH datasource) const char * OGR_Dr_GetName (void *driver) int OGR_Dr_TestCapability (void *driver, const char *) void * OGR_F_Create (void *featuredefn) void OGR_F_Destroy (void *feature) long OGR_F_GetFID (void *feature) int OGR_F_IsFieldSet (void *feature, int n) int OGR_F_GetFieldAsDateTimeEx (void *feature, int n, int *y, int *m, int *d, int *h, int *m, float *s, int *z) double OGR_F_GetFieldAsDouble (void *feature, int n) int OGR_F_GetFieldAsInteger (void *feature, int n) char * OGR_F_GetFieldAsString (void *feature, int n) unsigned char * OGR_F_GetFieldAsBinary(void *feature, int n, int *s) int OGR_F_GetFieldCount (void *feature) void * OGR_F_GetFieldDefnRef (void *feature, int n) int OGR_F_GetFieldIndex (void *feature, char *name) void * OGR_F_GetGeometryRef (void *feature) void * OGR_F_StealGeometry (void *feature) void OGR_F_SetFieldDateTimeEx (void *feature, int n, int y, int m, int d, int hh, int mm, float ss, int tz) void OGR_F_SetFieldDouble (void *feature, int n, double value) void OGR_F_SetFieldInteger (void *feature, int n, int value) void OGR_F_SetFieldString (void *feature, int n, char *value) void OGR_F_SetFieldBinary (void *feature, int n, int l, unsigned char *value) void OGR_F_SetFieldNull (void *feature, int n) # new in GDAL 2.2 int OGR_F_SetGeometryDirectly (void *feature, void *geometry) void * OGR_FD_Create (char *name) int OGR_FD_GetFieldCount (void *featuredefn) void * OGR_FD_GetFieldDefn (void *featuredefn, int n) int OGR_FD_GetGeomType (void *featuredefn) char * OGR_FD_GetName (void *featuredefn) OGRFieldSubType OGR_Fld_GetSubType(void *fielddefn) void OGR_Fld_SetSubType(void *fielddefn, OGRFieldSubType subtype) void * OGR_G_ForceToMultiPolygon (void *geometry) void * OGR_G_ForceToPolygon (void *geometry) void * OGR_G_Clone(void *geometry) void * OGR_G_GetLinearGeometry (void *hGeom, double dfMaxAngleStepSizeDegrees, char **papszOptions) OGRErr OGR_L_SetIgnoredFields (void *layer, const char **papszFields) OGRErr OGR_L_SetAttributeFilter(void *layer, const char*) OGRErr OGR_L_SetNextByIndex (void *layer, long nIndex) long long OGR_F_GetFieldAsInteger64 (void *feature, int n) void OGR_F_SetFieldInteger64 (void *feature, int n, long long value) int OGR_F_IsFieldNull(void *feature, int n) OGRwkbGeometryType OGR_GT_GetLinear(OGRwkbGeometryType eType) cdef extern from "gdalwarper.h" nogil: ctypedef enum GDALResampleAlg: GRA_NearestNeighbour GRA_Bilinear GRA_Cubic GRA_CubicSpline GRA_Lanczos GRA_Average GRA_Mode ctypedef int (*GDALMaskFunc)( void *pMaskFuncArg, int nBandCount, int eType, int nXOff, int nYOff, int nXSize, int nYSize, unsigned char **papabyImageData, int bMaskIsFloat, void *pMask) ctypedef int (*GDALTransformerFunc)( void *pTransformerArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess) ctypedef struct GDALWarpOptions: char **papszWarpOptions double dfWarpMemoryLimit GDALResampleAlg eResampleAlg GDALDataType eWorkingDataType GDALDatasetH hSrcDS GDALDatasetH hDstDS # 0 for all bands int nBandCount # List of source band indexes int *panSrcBands # List of destination band indexes int *panDstBands # The source band so use as an alpha (transparency) value, 0=disabled int nSrcAlphaBand # The dest. band so use as an alpha (transparency) value, 0=disabled int nDstAlphaBand # The "nodata" value real component for each input band, if NULL there isn't one */ double *padfSrcNoDataReal # The "nodata" value imaginary component - may be NULL even if real component is provided. */ double *padfSrcNoDataImag # The "nodata" value real component for each output band, if NULL there isn't one */ double *padfDstNoDataReal # The "nodata" value imaginary component - may be NULL even if real component is provided. */ double *padfDstNoDataImag # GDALProgressFunc() compatible progress reporting function, or NULL if there isn't one. */ void *pfnProgress # Callback argument to be passed to pfnProgress. */ void *pProgressArg # Type of spatial point transformer function */ GDALTransformerFunc pfnTransformer # Handle to image transformer setup structure */ void *pTransformerArg GDALMaskFunc *papfnSrcPerBandValidityMaskFunc void **papSrcPerBandValidityMaskFuncArg GDALMaskFunc pfnSrcValidityMaskFunc void *pSrcValidityMaskFuncArg GDALMaskFunc pfnSrcDensityMaskFunc void *pSrcDensityMaskFuncArg GDALMaskFunc pfnDstDensityMaskFunc void *pDstDensityMaskFuncArg GDALMaskFunc pfnDstValidityMaskFunc void *pDstValidityMaskFuncArg int (*pfnPreWarpChunkProcessor)(void *pKern, void *pArg) void *pPreWarpProcessorArg int (*pfnPostWarpChunkProcessor)(void *pKern, void *pArg) void *pPostWarpProcessorArg # Optional OGRPolygonH for a masking cutline. */ OGRGeometryH hCutline # Optional blending distance to apply across cutline in pixels, default is 0 double dfCutlineBlendDist GDALWarpOptions *GDALCreateWarpOptions() void GDALDestroyWarpOptions(GDALWarpOptions *options) GDALDatasetH GDALAutoCreateWarpedVRT( GDALDatasetH hSrcDS, const char *pszSrcWKT, const char *pszDstWKT, GDALResampleAlg eResampleAlg, double dfMaxError, const GDALWarpOptions *psOptionsIn) GDALDatasetH GDALCreateWarpedVRT( GDALDatasetH hSrcDS, int nPixels, int nLines, double *padfGeoTransform, const GDALWarpOptions *psOptionsIn) cdef extern from "gdal_alg.h" nogil: int GDALPolygonize(GDALRasterBandH band, GDALRasterBandH mask_band, OGRLayerH layer, int fidx, char **options, void *progress_func, void *progress_data) int GDALFPolygonize(GDALRasterBandH band, GDALRasterBandH mask_band, OGRLayerH layer, int fidx, char **options, void *progress_func, void *progress_data) int GDALSieveFilter(GDALRasterBandH src_band, GDALRasterBandH mask_band, GDALRasterBandH dst_band, int size, int connectivity, char **options, void *progress_func, void *progress_data) int GDALRasterizeGeometries(GDALDatasetH hds, int band_count, int *dst_bands, int geom_count, OGRGeometryH *geometries, GDALTransformerFunc transform_func, void *transform, double *pixel_values, char **options, void *progress_func, void *progress_data) void *GDALCreateGenImgProjTransformer(GDALDatasetH src_hds, const char *pszSrcWKT, GDALDatasetH dst_hds, const char *pszDstWKT, int bGCPUseOK, double dfGCPErrorThreshold, int nOrder) void *GDALCreateGenImgProjTransformer2(GDALDatasetH src_hds, GDALDatasetH dst_hds, char **options) void *GDALCreateGenImgProjTransformer3( const char *pszSrcWKT, const double *padfSrcGeoTransform, const char *pszDstWKT, const double *padfDstGeoTransform) void GDALSetGenImgProjTransformerDstGeoTransform(void *hTransformArg, double *padfGeoTransform) int GDALGenImgProjTransform(void *pTransformArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess) void GDALDestroyGenImgProjTransformer(void *) void *GDALCreateApproxTransformer(GDALTransformerFunc pfnRawTransformer, void *pRawTransformerArg, double dfMaxError) int GDALApproxTransform(void *pTransformArg, int bDstToSrc, int npoints, double *x, double *y, double *z, int *panSuccess) void GDALDestroyApproxTransformer(void *) void GDALApproxTransformerOwnsSubtransformer(void *, int) int GDALFillNodata(GDALRasterBandH dst_band, GDALRasterBandH mask_band, double max_search_distance, int deprecated, int smoothing_iterations, char **options, void *progress_func, void *progress_data) int GDALChecksumImage(GDALRasterBandH band, int xoff, int yoff, int width, int height) int GDALSuggestedWarpOutput2( GDALDatasetH hSrcDS, GDALTransformerFunc pfnRawTransformer, void * pTransformArg, double * padfGeoTransformOut, int * pnPixels, int * pnLines, double * padfExtent, int nOptions) Fiona-1.10.1/fiona/inspector.py000066400000000000000000000017261467206072700163230ustar00rootroot00000000000000import code import logging import sys import fiona logging.basicConfig(stream=sys.stderr, level=logging.INFO) logger = logging.getLogger('fiona.inspector') def main(srcfile): """Open a dataset in an interactive session.""" with fiona.drivers(): with fiona.open(srcfile) as src: code.interact( 'Fiona %s Interactive Inspector (Python %s)\n' 'Type "src.schema", "next(src)", or "help(src)" ' "for more information." % (fiona.__version__, ".".join(map(str, sys.version_info[:3]))), local=locals(), ) return 1 if __name__ == '__main__': import argparse parser = argparse.ArgumentParser( prog="python -m fiona.inspector", description="Open a data file and drop into an interactive interpreter", ) parser.add_argument("src", metavar="FILE", help="Input dataset file name") args = parser.parse_args() main(args.src) Fiona-1.10.1/fiona/io.py000066400000000000000000000141661467206072700147260ustar00rootroot00000000000000"""Classes capable of reading and writing collections """ import logging from fiona.ogrext import MemoryFileBase, _listdir, _listlayers from fiona.collection import Collection from fiona.meta import supports_vsi from fiona.errors import DriverError log = logging.getLogger(__name__) class MemoryFile(MemoryFileBase): """A BytesIO-like object, backed by an in-memory file. This allows formatted files to be read and written without I/O. A MemoryFile created with initial bytes becomes immutable. A MemoryFile created without initial bytes may be written to using either file-like or dataset interfaces. Parameters ---------- file_or_bytes : an open Python file, bytes, or None If not None, the MemoryFile becomes immutable and read-only. If None, it is write-only. filename : str An optional filename. The default is a UUID-based name. ext : str An optional file extension. Some format drivers require a specific value. """ def __init__(self, file_or_bytes=None, filename=None, ext=""): if ext and not ext.startswith("."): ext = "." + ext super().__init__( file_or_bytes=file_or_bytes, filename=filename, ext=ext) def open( self, mode=None, driver=None, schema=None, crs=None, encoding=None, layer=None, vfs=None, enabled_drivers=None, crs_wkt=None, allow_unsupported_drivers=False, **kwargs ): """Open the file and return a Fiona collection object. If data has already been written, the file is opened in 'r' mode. Otherwise, the file is opened in 'w' mode. Parameters ---------- Note well that there is no `path` parameter: a `MemoryFile` contains a single dataset and there is no need to specify a path. Other parameters are optional and have the same semantics as the parameters of `fiona.open()`. """ if self.closed: raise OSError("I/O operation on closed file.") if ( not allow_unsupported_drivers and driver is not None and not supports_vsi(driver) ): raise DriverError(f"Driver {driver} does not support virtual files.") if mode in ('r', 'a') and not self.exists(): raise OSError("MemoryFile does not exist.") if layer is None and mode == 'w' and self.exists(): raise OSError("MemoryFile already exists.") if not self.exists() or mode == 'w': if driver is not None: self._ensure_extension(driver) mode = 'w' elif mode is None: mode = 'r' return Collection( self.name, mode, crs=crs, driver=driver, schema=schema, encoding=encoding, layer=layer, enabled_drivers=enabled_drivers, allow_unsupported_drivers=allow_unsupported_drivers, crs_wkt=crs_wkt, **kwargs ) def listdir(self, path=None): """List files in a directory. Parameters ---------- path : URI (str or pathlib.Path) A dataset resource identifier. Returns ------- list A list of filename strings. """ if self.closed: raise OSError("I/O operation on closed file.") if path: vsi_path = f"{self.name}/{path.lstrip('/')}" else: vsi_path = f"{self.name}" return _listdir(vsi_path) def listlayers(self, path=None): """List layer names in their index order Parameters ---------- path : URI (str or pathlib.Path) A dataset resource identifier. Returns ------- list A list of layer name strings. """ if self.closed: raise OSError("I/O operation on closed file.") if path: vsi_path = f"{self.name}/{path.lstrip('/')}" else: vsi_path = f"{self.name}" return _listlayers(vsi_path) def __enter__(self): return self def __exit__(self, *args, **kwargs): self.close() class ZipMemoryFile(MemoryFile): """A read-only BytesIO-like object backed by an in-memory zip file. This allows a zip file containing formatted files to be read without I/O. Parameters ---------- file_or_bytes : an open Python file, bytes, or None If not None, the MemoryFile becomes immutable and read-only. If None, it is write-only. filename : str An optional filename. The default is a UUID-based name. ext : str An optional file extension. Some format drivers require a specific value. The default is ".zip". """ def __init__(self, file_or_bytes=None, filename=None, ext=".zip"): super().__init__(file_or_bytes, filename=filename, ext=ext) self.name = f"/vsizip{self.name}" def open( self, path=None, driver=None, encoding=None, layer=None, enabled_drivers=None, allow_unsupported_drivers=False, **kwargs ): """Open a dataset within the zipped stream. Parameters ---------- path : str Path to a dataset in the zip file, relative to the root of the archive. Returns ------- A Fiona collection object """ if path: vsi_path = f"/vsizip{self.name}/{path.lstrip('/')}" else: vsi_path = f"/vsizip{self.name}" if self.closed: raise OSError("I/O operation on closed file.") if path: vsi_path = f"{self.name}/{path.lstrip('/')}" else: vsi_path = f"{self.name}" return Collection( vsi_path, "r", driver=driver, encoding=encoding, layer=layer, enabled_drivers=enabled_drivers, allow_unsupported_drivers=allow_unsupported_drivers, **kwargs ) Fiona-1.10.1/fiona/logutils.py000066400000000000000000000015501467206072700161520ustar00rootroot00000000000000"""Logging helper classes.""" import logging class FieldSkipLogFilter(logging.Filter): """Filter field skip log messages. At most, one message per field skipped per loop will be passed. """ def __init__(self, name=''): super().__init__(name) self.seen_msgs = set() def filter(self, record): """Pass record if not seen.""" msg = record.getMessage() if msg.startswith("Skipping field"): retval = msg not in self.seen_msgs self.seen_msgs.add(msg) return retval else: return 1 class LogFiltering: def __init__(self, logger, filter): self.logger = logger self.filter = filter def __enter__(self): self.logger.addFilter(self.filter) def __exit__(self, *args, **kwargs): self.logger.removeFilter(self.filter) Fiona-1.10.1/fiona/meta.py000066400000000000000000000152271467206072700152440ustar00rootroot00000000000000import logging import xml.etree.ElementTree as ET from fiona.env import require_gdal_version from fiona.ogrext import _get_metadata_item log = logging.getLogger(__name__) class MetadataItem: # since GDAL 2.0 CREATION_FIELD_DATA_TYPES = "DMD_CREATIONFIELDDATATYPES" # since GDAL 2.3 CREATION_FIELD_DATA_SUB_TYPES = "DMD_CREATIONFIELDDATASUBTYPES" CREATION_OPTION_LIST = "DMD_CREATIONOPTIONLIST" LAYER_CREATION_OPTION_LIST = "DS_LAYER_CREATIONOPTIONLIST" # since GDAL 2.0 DATASET_OPEN_OPTIONS = "DMD_OPENOPTIONLIST" # since GDAL 2.0 EXTENSIONS = "DMD_EXTENSIONS" EXTENSION = "DMD_EXTENSION" VIRTUAL_IO = "DCAP_VIRTUALIO" # since GDAL 2.0 NOT_NULL_FIELDS = "DCAP_NOTNULL_FIELDS" # since gdal 2.3 NOT_NULL_GEOMETRY_FIELDS = "DCAP_NOTNULL_GEOMFIELDS" # since GDAL 3.2 UNIQUE_FIELDS = "DCAP_UNIQUE_FIELDS" # since GDAL 2.0 DEFAULT_FIELDS = "DCAP_DEFAULT_FIELDS" OPEN = "DCAP_OPEN" CREATE = "DCAP_CREATE" def _parse_options(xml): """Convert metadata xml to dict""" options = {} if len(xml) > 0: root = ET.fromstring(xml) for option in root.iter('Option'): option_name = option.attrib['name'] opt = {} opt.update((k, v) for k, v in option.attrib.items() if not k == 'name') values = [] for value in option.iter('Value'): values.append(value.text) if len(values) > 0: opt['values'] = values options[option_name] = opt return options @require_gdal_version('2.0') def dataset_creation_options(driver): """ Returns dataset creation options for driver Parameters ---------- driver : str Returns ------- dict Dataset creation options """ xml = _get_metadata_item(driver, MetadataItem.CREATION_OPTION_LIST) if xml is None: return {} if len(xml) == 0: return {} return _parse_options(xml) @require_gdal_version('2.0') def layer_creation_options(driver): """ Returns layer creation options for driver Parameters ---------- driver : str Returns ------- dict Layer creation options """ xml = _get_metadata_item(driver, MetadataItem.LAYER_CREATION_OPTION_LIST) if xml is None: return {} if len(xml) == 0: return {} return _parse_options(xml) @require_gdal_version('2.0') def dataset_open_options(driver): """ Returns dataset open options for driver Parameters ---------- driver : str Returns ------- dict Dataset open options """ xml = _get_metadata_item(driver, MetadataItem.DATASET_OPEN_OPTIONS) if xml is None: return {} if len(xml) == 0: return {} return _parse_options(xml) @require_gdal_version('2.0') def print_driver_options(driver): """ Print driver options for dataset open, dataset creation, and layer creation. Parameters ---------- driver : str """ for option_type, options in [("Dataset Open Options", dataset_open_options(driver)), ("Dataset Creation Options", dataset_creation_options(driver)), ("Layer Creation Options", layer_creation_options(driver))]: print(f"{option_type}:") if len(options) == 0: print("\tNo options available.") else: for option_name in options: print(f"\t{option_name}:") if 'description' in options[option_name]: print(f"\t\tDescription: {options[option_name]['description']}") if 'type' in options[option_name]: print(f"\t\tType: {options[option_name]['type']}") if 'values' in options[option_name] and len(options[option_name]['values']) > 0: print(f"\t\tAccepted values: {','.join(options[option_name]['values'])}") for attr_text, attribute in [('Default value', 'default'), ('Required', 'required'), ('Alias', 'aliasOf'), ('Min', 'min'), ('Max', 'max'), ('Max size', 'maxsize'), ('Scope', 'scope'), ('Alternative configuration option', 'alt_config_option')]: if attribute in options[option_name]: print(f"\t\t{attr_text}: {options[option_name][attribute]}") print("") @require_gdal_version('2.0') def extensions(driver): """ Returns file extensions supported by driver Parameters ---------- driver : str Returns ------- list List with file extensions or None if not specified by driver """ exts = _get_metadata_item(driver, MetadataItem.EXTENSIONS) if exts is None: return None return [ext for ext in exts.split(" ") if len(ext) > 0] def extension(driver): """ Returns file extension of driver Parameters ---------- driver : str Returns ------- str File extensions or None if not specified by driver """ return _get_metadata_item(driver, MetadataItem.EXTENSION) @require_gdal_version('2.0') def supports_vsi(driver): """ Returns True if driver supports GDAL's VSI*L API Parameters ---------- driver : str Returns ------- bool """ virtual_io = _get_metadata_item(driver, MetadataItem.VIRTUAL_IO) return virtual_io is not None and virtual_io.upper() == "YES" @require_gdal_version('2.0') def supported_field_types(driver): """ Returns supported field types Parameters ---------- driver : str Returns ------- list List with supported field types or None if not specified by driver """ field_types_str = _get_metadata_item(driver, MetadataItem.CREATION_FIELD_DATA_TYPES) if field_types_str is None: return None return [field_type for field_type in field_types_str.split(" ") if len(field_type) > 0] @require_gdal_version('2.3') def supported_sub_field_types(driver): """ Returns supported sub field types Parameters ---------- driver : str Returns ------- list List with supported field types or None if not specified by driver """ field_types_str = _get_metadata_item(driver, MetadataItem.CREATION_FIELD_DATA_SUB_TYPES) if field_types_str is None: return None return [field_type for field_type in field_types_str.split(" ") if len(field_type) > 0] Fiona-1.10.1/fiona/model.py000066400000000000000000000273621467206072700154210ustar00rootroot00000000000000"""Fiona data model""" from binascii import hexlify from collections.abc import MutableMapping from enum import Enum import itertools from json import JSONEncoder import reprlib from warnings import warn from fiona.errors import FionaDeprecationWarning _model_repr = reprlib.Repr() _model_repr.maxlist = 1 _model_repr.maxdict = 5 class OGRGeometryType(Enum): Unknown = 0 Point = 1 LineString = 2 Polygon = 3 MultiPoint = 4 MultiLineString = 5 MultiPolygon = 6 GeometryCollection = 7 CircularString = 8 CompoundCurve = 9 CurvePolygon = 10 MultiCurve = 11 MultiSurface = 12 Curve = 13 Surface = 14 PolyhedralSurface = 15 TIN = 16 Triangle = 17 NONE = 100 LinearRing = 101 CircularStringZ = 1008 CompoundCurveZ = 1009 CurvePolygonZ = 1010 MultiCurveZ = 1011 MultiSurfaceZ = 1012 CurveZ = 1013 SurfaceZ = 1014 PolyhedralSurfaceZ = 1015 TINZ = 1016 TriangleZ = 1017 PointM = 2001 LineStringM = 2002 PolygonM = 2003 MultiPointM = 2004 MultiLineStringM = 2005 MultiPolygonM = 2006 GeometryCollectionM = 2007 CircularStringM = 2008 CompoundCurveM = 2009 CurvePolygonM = 2010 MultiCurveM = 2011 MultiSurfaceM = 2012 CurveM = 2013 SurfaceM = 2014 PolyhedralSurfaceM = 2015 TINM = 2016 TriangleM = 2017 PointZM = 3001 LineStringZM = 3002 PolygonZM = 3003 MultiPointZM = 3004 MultiLineStringZM = 3005 MultiPolygonZM = 3006 GeometryCollectionZM = 3007 CircularStringZM = 3008 CompoundCurveZM = 3009 CurvePolygonZM = 3010 MultiCurveZM = 3011 MultiSurfaceZM = 3012 CurveZM = 3013 SurfaceZM = 3014 PolyhedralSurfaceZM = 3015 TINZM = 3016 TriangleZM = 3017 Point25D = 0x80000001 LineString25D = 0x80000002 Polygon25D = 0x80000003 MultiPoint25D = 0x80000004 MultiLineString25D = 0x80000005 MultiPolygon25D = 0x80000006 GeometryCollection25D = 0x80000007 # Mapping of OGR integer geometry types to GeoJSON type names. _GEO_TYPES = { OGRGeometryType.Unknown.value: "Unknown", OGRGeometryType.Point.value: "Point", OGRGeometryType.LineString.value: "LineString", OGRGeometryType.Polygon.value: "Polygon", OGRGeometryType.MultiPoint.value: "MultiPoint", OGRGeometryType.MultiLineString.value: "MultiLineString", OGRGeometryType.MultiPolygon.value: "MultiPolygon", OGRGeometryType.GeometryCollection.value: "GeometryCollection" } GEOMETRY_TYPES = { **_GEO_TYPES, OGRGeometryType.NONE.value: "None", OGRGeometryType.LinearRing.value: "LinearRing", OGRGeometryType.Point25D.value: "3D Point", OGRGeometryType.LineString25D.value: "3D LineString", OGRGeometryType.Polygon25D.value: "3D Polygon", OGRGeometryType.MultiPoint25D.value: "3D MultiPoint", OGRGeometryType.MultiLineString25D.value: "3D MultiLineString", OGRGeometryType.MultiPolygon25D.value: "3D MultiPolygon", OGRGeometryType.GeometryCollection25D.value: "3D GeometryCollection", } class Object(MutableMapping): """Base class for CRS, geometry, and feature objects In Fiona 2.0, the implementation of those objects will change. They will no longer be dicts or derive from dict, and will lose some features like mutability and default JSON serialization. Object will be used for these objects in Fiona 1.9. This class warns about future deprecation of features. """ _delegated_properties = [] def __init__(self, **kwds): self._data = dict(**kwds) def _props(self): return { k: getattr(self._delegate, k) for k in self._delegated_properties if k is not None # getattr(self._delegate, k) is not None } def __getitem__(self, item): if item in self._delegated_properties: return getattr(self._delegate, item) else: props = { k: (dict(v) if isinstance(v, Object) else v) for k, v in self._props().items() } props.update(**self._data) return props[item] def __iter__(self): props = self._props() return itertools.chain(iter(props), iter(self._data)) def __len__(self): props = self._props() return len(props) + len(self._data) def __repr__(self): kvs = [ f"{k}={v!r}" for k, v in itertools.chain(self._props().items(), self._data.items()) ] return "fiona.{}({})".format(self.__class__.__name__, ", ".join(kvs)) def __setitem__(self, key, value): warn( "instances of this class -- CRS, geometry, and feature objects -- will become immutable in fiona version 2.0", FionaDeprecationWarning, stacklevel=2, ) if key in self._delegated_properties: setattr(self._delegate, key, value) else: self._data[key] = value def __delitem__(self, key): warn( "instances of this class -- CRS, geometry, and feature objects -- will become immutable in fiona version 2.0", FionaDeprecationWarning, stacklevel=2, ) if key in self._delegated_properties: setattr(self._delegate, key, None) else: del self._data[key] def __eq__(self, other): return dict(**self) == dict(**other) class _Geometry: def __init__(self, coordinates=None, type=None, geometries=None): self.coordinates = coordinates self.type = type self.geometries = geometries class Geometry(Object): """A GeoJSON-like geometry Notes ----- Delegates coordinates and type properties to an instance of _Geometry, which will become an extension class in Fiona 2.0. """ _delegated_properties = ["coordinates", "type", "geometries"] def __init__(self, coordinates=None, type=None, geometries=None, **data): self._delegate = _Geometry( coordinates=coordinates, type=type, geometries=geometries ) super().__init__(**data) def __repr__(self): kvs = [f"{k}={_model_repr.repr(v)}" for k, v in self.items() if v is not None] return "fiona.Geometry({})".format(", ".join(kvs)) @classmethod def from_dict(cls, ob=None, **kwargs): if ob is not None: data = dict(getattr(ob, "__geo_interface__", ob)) data.update(kwargs) else: data = kwargs if "geometries" in data and data["type"] == "GeometryCollection": _ = data.pop("coordinates", None) _ = data.pop("type", None) return Geometry( type="GeometryCollection", geometries=[ Geometry.from_dict(part) for part in data.pop("geometries") ], **data ) else: _ = data.pop("geometries", None) return Geometry( type=data.pop("type", None), coordinates=data.pop("coordinates", []), **data ) @property def coordinates(self): """The geometry's coordinates Returns ------- Sequence """ return self._delegate.coordinates @property def type(self): """The geometry's type Returns ------- str """ return self._delegate.type @property def geometries(self): """A collection's geometries. Returns ------- list """ return self._delegate.geometries @property def __geo_interface__(self): return ObjectEncoder().default(self) class _Feature: def __init__(self, geometry=None, id=None, properties=None): self.geometry = geometry self.id = id self.properties = properties class Feature(Object): """A GeoJSON-like feature Notes ----- Delegates geometry and properties to an instance of _Feature, which will become an extension class in Fiona 2.0. """ _delegated_properties = ["geometry", "id", "properties"] def __init__(self, geometry=None, id=None, properties=None, **data): if properties is None: properties = Properties() self._delegate = _Feature(geometry=geometry, id=id, properties=properties) super().__init__(**data) @classmethod def from_dict(cls, ob=None, **kwargs): if ob is not None: data = dict(getattr(ob, "__geo_interface__", ob)) data.update(kwargs) else: data = kwargs geom_data = data.pop("geometry", None) if isinstance(geom_data, Geometry): geom = geom_data else: geom = Geometry.from_dict(geom_data) if geom_data is not None else None props_data = data.pop("properties", None) if isinstance(props_data, Properties): props = props_data else: props = Properties(**props_data) if props_data is not None else None fid = data.pop("id", None) return Feature(geometry=geom, id=fid, properties=props, **data) def __eq__(self, other): return ( self.geometry == other.geometry and self.id == other.id and self.properties == other.properties ) @property def geometry(self): """The feature's geometry object Returns ------- Geometry """ return self._delegate.geometry @property def id(self): """The feature's id Returns ------ object """ return self._delegate.id @property def properties(self): """The feature's properties Returns ------- object """ return self._delegate.properties @property def type(self): """The Feature's type Returns ------- str """ return "Feature" @property def __geo_interface__(self): return ObjectEncoder().default(self) class Properties(Object): """A GeoJSON-like feature's properties""" def __init__(self, **kwds): super().__init__(**kwds) @classmethod def from_dict(cls, mapping=None, **kwargs): if mapping: return Properties(**mapping, **kwargs) return Properties(**kwargs) class ObjectEncoder(JSONEncoder): """Encodes Geometry, Feature, and Properties.""" def default(self, o): if isinstance(o, Object): o_dict = { k: self.default(v) for k, v in itertools.chain(o._props().items(), o._data.items()) } if isinstance(o, Geometry): if o.type == "GeometryCollection": _ = o_dict.pop("coordinates", None) else: _ = o_dict.pop("geometries", None) elif isinstance(o, Feature): o_dict["type"] = "Feature" return o_dict elif isinstance(o, bytes): return hexlify(o) else: return o def decode_object(obj): """A json.loads object_hook Parameters ---------- obj : dict A decoded dict. Returns ------- Feature, Geometry, or dict """ if isinstance(obj, Object): return obj else: obj = obj.get("__geo_interface__", obj) _type = obj.get("type", None) if (_type == "Feature") or "geometry" in obj: return Feature.from_dict(obj) elif _type in _GEO_TYPES.values(): return Geometry.from_dict(obj) else: return obj def to_dict(val): """Converts an object to a dict""" try: obj = ObjectEncoder().default(val) except TypeError: return val else: return obj Fiona-1.10.1/fiona/ogrext.pyx000066400000000000000000002442041467206072700160150ustar00rootroot00000000000000"""Extension classes and functions using the OGR C API.""" include "gdal.pxi" import datetime import json import locale import logging import os import warnings import math from collections import namedtuple from typing import List from uuid import uuid4 from fiona.crs cimport CRS, osr_set_traditional_axis_mapping_strategy from fiona._geometry cimport ( GeomBuilder, OGRGeomBuilder, geometry_type_code, normalize_geometry_type_code, base_geometry_type_code) from fiona._err cimport exc_wrap_int, exc_wrap_pointer, exc_wrap_vsilfile, get_last_error_msg from fiona._err cimport StackChecker import fiona from fiona._env import get_gdal_version_num, calc_gdal_version_num, get_gdal_version_tuple from fiona._err import ( cpl_errs, stack_errors, FionaNullPointerError, CPLE_BaseError, CPLE_AppDefinedError, CPLE_OpenFailedError) from fiona._geometry import GEOMETRY_TYPES from fiona import compat from fiona.compat import strencode from fiona.env import Env from fiona.errors import ( DriverError, DriverIOError, SchemaError, CRSError, FionaValueError, TransactionError, GeometryTypeValidationError, DatasetDeleteError, AttributeFilterError, FeatureWarning, FionaDeprecationWarning, UnsupportedGeometryTypeError) from fiona.model import decode_object, Feature, Geometry, Properties from fiona._path import _vsi_path from fiona.rfc3339 import parse_date, parse_datetime, parse_time from fiona.schema import FIELD_TYPES_MAP2, normalize_field_type, NAMED_FIELD_TYPES from libc.stdlib cimport malloc, free from libc.string cimport strcmp from cpython cimport PyBytes_FromStringAndSize, PyBytes_AsString from fiona.drvsupport import _driver_supports_timezones log = logging.getLogger(__name__) DEFAULT_TRANSACTION_SIZE = 20000 # OGR Driver capability cdef const char * ODrCCreateDataSource = "CreateDataSource" cdef const char * ODrCDeleteDataSource = "DeleteDataSource" # OGR Layer capability cdef const char * OLC_RANDOMREAD = "RandomRead" cdef const char * OLC_SEQUENTIALWRITE = "SequentialWrite" cdef const char * OLC_RANDOMWRITE = "RandomWrite" cdef const char * OLC_FASTSPATIALFILTER = "FastSpatialFilter" cdef const char * OLC_FASTFEATURECOUNT = "FastFeatureCount" cdef const char * OLC_FASTGETEXTENT = "FastGetExtent" cdef const char * OLC_FASTSETNEXTBYINDEX = "FastSetNextByIndex" cdef const char * OLC_CREATEFIELD = "CreateField" cdef const char * OLC_CREATEGEOMFIELD = "CreateGeomField" cdef const char * OLC_DELETEFIELD = "DeleteField" cdef const char * OLC_REORDERFIELDS = "ReorderFields" cdef const char * OLC_ALTERFIELDDEFN = "AlterFieldDefn" cdef const char * OLC_DELETEFEATURE = "DeleteFeature" cdef const char * OLC_STRINGSASUTF8 = "StringsAsUTF8" cdef const char * OLC_TRANSACTIONS = "Transactions" cdef const char * OLC_IGNOREFIELDS = "IgnoreFields" # OGR integer error types. OGRERR_NONE = 0 OGRERR_NOT_ENOUGH_DATA = 1 # not enough data to deserialize */ OGRERR_NOT_ENOUGH_MEMORY = 2 OGRERR_UNSUPPORTED_GEOMETRY_TYPE = 3 OGRERR_UNSUPPORTED_OPERATION = 4 OGRERR_CORRUPT_DATA = 5 OGRERR_FAILURE = 6 OGRERR_UNSUPPORTED_SRS = 7 OGRERR_INVALID_HANDLE = 8 cdef void gdal_flush_cache(void *cogr_ds): with cpl_errs: GDALFlushCache(cogr_ds) cdef void* gdal_open_vector(const char* path_c, int mode, drivers, options) except NULL: cdef void* cogr_ds = NULL cdef char **drvs = NULL cdef void* drv = NULL cdef char **open_opts = NULL cdef char **registered_prefixes = NULL cdef int prefix_index = 0 cdef VSIFilesystemPluginCallbacksStruct *callbacks_struct = NULL cdef StackChecker checker flags = GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR if mode == 1: flags |= GDAL_OF_UPDATE else: flags |= GDAL_OF_READONLY if drivers: for name in drivers: name_b = name.encode() name_c = name_b drv = GDALGetDriverByName(name_c) if drv != NULL: drvs = CSLAddString(drvs, name_c) for k, v in options.items(): if v is not None: kb = k.upper().encode('utf-8') if isinstance(v, bool): vb = ('ON' if v else 'OFF').encode('utf-8') else: vb = str(v).encode('utf-8') open_opts = CSLAddNameValue(open_opts, kb, vb) open_opts = CSLAddNameValue(open_opts, "VALIDATE_OPEN_OPTIONS", "NO") try: with stack_errors() as checker: cogr_ds = GDALOpenEx( path_c, flags, drvs, open_opts, NULL ) return checker.exc_wrap_pointer(cogr_ds) except CPLE_BaseError as exc: raise DriverError(f"Failed to open dataset (flags={flags}): {path_c.decode('utf-8')}") from exc finally: CSLDestroy(drvs) CSLDestroy(open_opts) cdef void* gdal_create(void* cogr_driver, const char *path_c, options) except NULL: cdef char **creation_opts = NULL cdef void *cogr_ds = NULL db = GDALGetDriverShortName(cogr_driver) # To avoid a circular import. from fiona import meta option_keys = set(key.upper() for key in options.keys()) creation_option_keys = option_keys & set(meta.dataset_creation_options(db.decode("utf-8"))) for k, v in options.items(): if k.upper() in creation_option_keys: kb = k.upper().encode('utf-8') if isinstance(v, bool): vb = ('ON' if v else 'OFF').encode('utf-8') else: vb = str(v).encode('utf-8') creation_opts = CSLAddNameValue(creation_opts, kb, vb) try: return exc_wrap_pointer(GDALCreate(cogr_driver, path_c, 0, 0, 0, GDT_Unknown, creation_opts)) except FionaNullPointerError: raise DriverError(f"Failed to create dataset: {path_c.decode('utf-8')}") except CPLE_BaseError as exc: raise DriverError(str(exc)) finally: CSLDestroy(creation_opts) def _explode(coords): """Explode a GeoJSON geometry's coordinates object and yield coordinate tuples. As long as the input is conforming, the type of the geometry doesn't matter.""" for e in coords: if isinstance(e, (float, int)): yield coords break else: for f in _explode(e): yield f def _bounds(geometry): """Bounding box of a GeoJSON geometry""" try: xyz = tuple(zip(*list(_explode(geometry['coordinates'])))) return min(xyz[0]), min(xyz[1]), max(xyz[0]), max(xyz[1]) except (KeyError, TypeError): return None cdef int GDAL_VERSION_NUM = get_gdal_version_num() class TZ(datetime.tzinfo): def __init__(self, minutes): self.minutes = minutes def utcoffset(self, dt): return datetime.timedelta(minutes=self.minutes) cdef class AbstractField: cdef object driver cdef object supports_tz def __init__(self, driver=None): self.driver = driver cdef object name(self, OGRFieldDefnH fdefn): """Get the short Fiona field name corresponding to the OGR field definition.""" raise NotImplementedError cdef object get(self, OGRFeatureH feature, int i, object kwds): """Get the value of a feature's field.""" raise NotImplementedError cdef set(self, OGRFeatureH feature, int i, object value, object kwds): """Set the value of a feature's field.""" raise NotImplementedError cdef class IntegerField(AbstractField): cdef object name(self, OGRFieldDefnH fdefn): cdef int width = OGR_Fld_GetWidth(fdefn) fmt = "" if width: fmt = f":{width:d}" return f"int32{fmt}" cdef object get(self, OGRFeatureH feature, int i, object kwds): return OGR_F_GetFieldAsInteger(feature, i) cdef set(self, OGRFeatureH feature, int i, object value, object kwds): OGR_F_SetFieldInteger(feature, i, int(value)) cdef class Int16Field(AbstractField): cdef object name(self, OGRFieldDefnH fdefn): cdef int width = OGR_Fld_GetWidth(fdefn) fmt = "" if width: fmt = f":{width:d}" return f"int16{fmt}" cdef object get(self, OGRFeatureH feature, int i, object kwds): return OGR_F_GetFieldAsInteger(feature, i) cdef set(self, OGRFeatureH feature, int i, object value, object kwds): OGR_F_SetFieldInteger(feature, i, int(value)) cdef class BooleanField(AbstractField): cdef object name(self, OGRFieldDefnH fdefn): return "bool" cdef object get(self, OGRFeatureH feature, int i, object kwds): return bool(OGR_F_GetFieldAsInteger(feature, i)) cdef set(self, OGRFeatureH feature, int i, object value, object kwds): OGR_F_SetFieldInteger(feature, i, int(value)) cdef class Integer64Field(AbstractField): cdef object name(self, OGRFieldDefnH fdefn): cdef int width = OGR_Fld_GetWidth(fdefn) fmt = "" if width: fmt = f":{width:d}" return f"int{fmt}" cdef object get(self, OGRFeatureH feature, int i, object kwds): return OGR_F_GetFieldAsInteger64(feature, i) cdef set(self, OGRFeatureH feature, int i, object value, object kwds): OGR_F_SetFieldInteger64(feature, i, int(value)) cdef class RealField(AbstractField): cdef object name(self, OGRFieldDefnH fdefn): cdef int width = OGR_Fld_GetWidth(fdefn) cdef int precision = OGR_Fld_GetPrecision(fdefn) fmt = "" if width: fmt = f":{width:d}" if precision: fmt += f".{precision:d}" return f"float{fmt}" cdef object get(self, OGRFeatureH feature, int i, object kwds): return OGR_F_GetFieldAsDouble(feature, i) cdef set(self, OGRFeatureH feature, int i, object value, object kwds): OGR_F_SetFieldDouble(feature, i, float(value)) cdef class StringField(AbstractField): cdef object name(self, OGRFieldDefnH fdefn): cdef int width = OGR_Fld_GetWidth(fdefn) fmt = "" if width: fmt = f":{width:d}" return f"str{fmt}" cdef object get(self, OGRFeatureH feature, int i, object kwds): encoding = kwds["encoding"] val = OGR_F_GetFieldAsString(feature, i) try: val = val.decode(encoding) except UnicodeDecodeError: log.warning( "Failed to decode %s using %s codec", val, encoding) else: return val cdef set(self, OGRFeatureH feature, int i, object value, object kwds): encoding = kwds["encoding"] cdef object value_b = str(value).encode(encoding) OGR_F_SetFieldString(feature, i, value_b) cdef class BinaryField(AbstractField): cdef object name(self, OGRFieldDefnH fdefn): return "bytes" cdef object get(self, OGRFeatureH feature, int i, object kwds): cdef unsigned char *data = NULL cdef int l data = OGR_F_GetFieldAsBinary(feature, i, &l) return data[:l] cdef set(self, OGRFeatureH feature, int i, object value, object kwds): OGR_F_SetFieldBinary(feature, i, len(value), value) cdef class StringListField(AbstractField): cdef object name(self, OGRFieldDefnH fdefn): return "List[str]" cdef object get(self, OGRFeatureH feature, int i, object kwds): cdef char **string_list = NULL encoding = kwds["encoding"] string_list = OGR_F_GetFieldAsStringList(feature, i) string_list_index = 0 vals = [] if string_list != NULL: while string_list[string_list_index] != NULL: val = string_list[string_list_index] try: val = val.decode(encoding) except UnicodeDecodeError: log.warning( "Failed to decode %s using %s codec", val, encoding ) vals.append(val) string_list_index += 1 return vals cdef set(self, OGRFeatureH feature, int i, object value, object kwds): cdef char **string_list = NULL encoding = kwds["encoding"] for item in value: item_b = item.encode(encoding) string_list = CSLAddString(string_list, item_b) OGR_F_SetFieldStringList(feature, i, string_list) cdef class JSONField(AbstractField): cdef object name(self, OGRFieldDefnH fdefn): return "json" cdef object get(self, OGRFeatureH feature, int i, object kwds): val = OGR_F_GetFieldAsString(feature, i) return json.loads(val) cdef set(self, OGRFeatureH feature, int i, object value, object kwds): value_b = json.dumps(value).encode("utf-8") OGR_F_SetFieldString(feature, i, value_b) cdef class DateField(AbstractField): """Dates without time.""" cdef object name(self, OGRFieldDefnH fdefn): return "date" cdef object get(self, OGRFeatureH feature, int i, object kwds): cdef int retval cdef int y = 0 cdef int m = 0 cdef int d = 0 cdef int hh = 0 cdef int mm = 0 cdef float fss = 0.0 cdef int tz = 0 retval = OGR_F_GetFieldAsDateTimeEx(feature, i, &y, &m, &d, &hh, &mm, &fss, &tz) return datetime.date(y, m, d).isoformat() cdef set(self, OGRFeatureH feature, int i, object value, object kwds): if isinstance(value, str): y, m, d, hh, mm, ss, ms, tz = parse_date(value) elif isinstance(value, datetime.date): y, m, d = value.year, value.month, value.day hh = mm = ss = ms = 0 else: raise ValueError() tzinfo = 0 OGR_F_SetFieldDateTimeEx(feature, i, y, m, d, hh, mm, ss, tzinfo) cdef class TimeField(AbstractField): """Times without dates.""" def __init__(self, driver=None): self.driver = driver self.supports_tz = _driver_supports_timezones(self.driver, "time") cdef object name(self, OGRFieldDefnH fdefn): return "time" cdef object get(self, OGRFeatureH feature, int i, object kwds): cdef int retval cdef int y = 0 cdef int m = 0 cdef int d = 0 cdef int hh = 0 cdef int mm = 0 cdef float fss = 0.0 cdef int tz = 0 retval = OGR_F_GetFieldAsDateTimeEx(feature, i, &y, &m, &d, &hh, &mm, &fss, &tz) ms, ss = math.modf(fss) ss = int(ss) ms = int(round(ms * 10**6)) # OGR_F_GetFieldAsDateTimeEx: (0=unknown, 1=localtime, 100=GMT, see data model for details) # CPLParseRFC822DateTime: (0=unknown, 100=GMT, 101=GMT+15minute, 99=GMT-15minute), or NULL tzinfo = None if tz > 1: tz_minutes = (tz - 100) * 15 tzinfo = TZ(tz_minutes) return datetime.time(hh, mm, ss, ms, tzinfo).isoformat() cdef set(self, OGRFeatureH feature, int i, object value, object kwds): if isinstance(value, str): y, m, d, hh, mm, ss, ms, tz = parse_time(value) elif isinstance(value, datetime.time): y = m = d = 0 hh, mm, ss, ms = value.hour, value.minute, value.second, value.microsecond if value.utcoffset() is None: tz = None else: tz = value.utcoffset().total_seconds() / 60 else: raise ValueError() if tz is not None and not self.supports_tz: d_tz = datetime.datetime(1900, 1, 1, hh, mm, ss, int(ms), TZ(tz)) d_utc = d_tz - d_tz.utcoffset() y = m = d = 0 hh, mm, ss, ms = d_utc.hour, d_utc.minute, d_utc.second, d_utc.microsecond tz = 0 # tzinfo: (0=unknown, 100=GMT, 101=GMT+15minute, 99=GMT-15minute), or NULL if tz is not None: tzinfo = int(tz / 15.0 + 100) else: tzinfo = 0 ss += ms / 10**6 OGR_F_SetFieldDateTimeEx(feature, i, y, m, d, hh, mm, ss, tzinfo) cdef class DateTimeField(AbstractField): """Dates and times.""" def __init__(self, driver=None): self.driver = driver self.supports_tz = _driver_supports_timezones(self.driver, "datetime") cdef object name(self, OGRFieldDefnH fdefn): return "datetime" cdef object get(self, OGRFeatureH feature, int i, object kwds): cdef int retval cdef int y = 0 cdef int m = 0 cdef int d = 0 cdef int hh = 0 cdef int mm = 0 cdef float fss = 0.0 cdef int tz = 0 retval = OGR_F_GetFieldAsDateTimeEx(feature, i, &y, &m, &d, &hh, &mm, &fss, &tz) ms, ss = math.modf(fss) ss = int(ss) ms = int(round(ms * 10**6)) # OGR_F_GetFieldAsDateTimeEx: (0=unknown, 1=localtime, 100=GMT, see data model for details) # CPLParseRFC822DateTime: (0=unknown, 100=GMT, 101=GMT+15minute, 99=GMT-15minute), or NULL tzinfo = None if tz > 1: tz_minutes = (tz - 100) * 15 tzinfo = TZ(tz_minutes) return datetime.datetime(y, m, d, hh, mm, ss, ms, tzinfo).isoformat() cdef set(self, OGRFeatureH feature, int i, object value, object kwds): if isinstance(value, str): y, m, d, hh, mm, ss, ms, tz = parse_datetime(value) elif isinstance(value, datetime.datetime): y, m, d = value.year, value.month, value.day hh, mm, ss, ms = value.hour, value.minute, value.second, value.microsecond if value.utcoffset() is None: tz = None else: tz = value.utcoffset().total_seconds() / 60 else: raise ValueError() if tz is not None and not self.supports_tz: d_tz = datetime.datetime(y, m, d, hh, mm, ss, int(ms), TZ(tz)) d_utc = d_tz - d_tz.utcoffset() y, m, d = d_utc.year, d_utc.month, d_utc.day hh, mm, ss, ms = d_utc.hour, d_utc.minute, d_utc.second, d_utc.microsecond tz = 0 # tzinfo: (0=unknown, 100=GMT, 101=GMT+15minute, 99=GMT-15minute), or NULL if tz is not None: tzinfo = int(tz / 15.0 + 100) else: tzinfo = 0 ss += ms / 10**6 OGR_F_SetFieldDateTimeEx(feature, i, y, m, d, hh, mm, ss, tzinfo) cdef bint is_field_null(OGRFeatureH feature, int i): return OGR_F_IsFieldNull(feature, i) or not OGR_F_IsFieldSet(feature, i) cdef class FeatureBuilder: """Build Fiona features from OGR feature pointers. No OGR objects are allocated by this function and the feature argument is not destroyed. """ cdef object driver cdef object property_getter_cache OGRPropertyGetter = { (OFTInteger, OFSTNone): IntegerField, (OFTInteger, OFSTBoolean): BooleanField, (OFTInteger, OFSTInt16): Int16Field, (OFTInteger64, OFSTNone): Integer64Field, (OFTReal, OFSTNone): RealField, (OFTString, OFSTNone): StringField, (OFTDate, OFSTNone): DateField, (OFTTime, OFSTNone): TimeField, (OFTDateTime, OFSTNone): DateTimeField, (OFTBinary, OFSTNone): BinaryField, (OFTStringList, OFSTNone): StringListField, (OFTString, OFSTJSON): JSONField, } def __init__(self, driver=None): self.driver = driver self.property_getter_cache = {} cdef build( self, OGRFeatureH feature, encoding='utf-8', bbox=False, driver=None, ignore_fields=None, ignore_geometry=False ): """Build a Fiona feature object from an OGR feature Parameters ---------- feature : void * The OGR feature # TODO: use a real typedef encoding : str The encoding of OGR feature attributes bbox : bool Not used driver : str OGR format driver name like 'GeoJSON' ignore_fields : sequence A sequence of field names that will be ignored and omitted in the Fiona feature properties ignore_geometry : bool Flag for whether the OGR geometry field is to be ignored Returns ------- dict """ cdef OGRFieldDefnH fdefn cdef int i cdef int fieldtype cdef int fieldsubtype cdef const char *key_c cdef AbstractField getter # Skeleton of the feature to be returned. fid = OGR_F_GetFID(feature) props = {} ignore_fields = set(ignore_fields or []) for i in range(OGR_F_GetFieldCount(feature)): fdefn = OGR_F_GetFieldDefnRef(feature, i) if fdefn == NULL: raise ValueError(f"NULL field definition at index {i}") key_c = OGR_Fld_GetNameRef(fdefn) if key_c == NULL: raise ValueError(f"NULL field name reference at index {i}") key_b = key_c key = key_b.decode(encoding) # Some field names are empty strings, apparently. # We warn in this case. if not key: warnings.warn(f"Empty field name at index {i}") if key in ignore_fields: continue fieldtype = OGR_Fld_GetType(fdefn) fieldsubtype = OGR_Fld_GetSubType(fdefn) fieldkey = (fieldtype, fieldsubtype) if is_field_null(feature, i): props[key] = None else: if fieldkey in self.property_getter_cache: getter = self.property_getter_cache[fieldkey] else: try: getter = self.OGRPropertyGetter[fieldkey](driver=driver or self.driver) self.property_getter_cache[fieldkey] = getter except KeyError: log.warning( "Skipping field %s: invalid type %s", key, fieldkey ) continue props[key] = getter.get(feature, i, {"encoding": encoding}) cdef void *cogr_geometry = NULL geom = None if not ignore_geometry: cogr_geometry = OGR_F_GetGeometryRef(feature) geom = GeomBuilder().build_from_feature(feature) return Feature(id=str(fid), properties=Properties(**props), geometry=geom) cdef class OGRFeatureBuilder: """Builds an OGR Feature from a Fiona feature mapping. Allocates one OGR Feature which should be destroyed by the caller. Borrows a layer definition from the collection. """ cdef object driver cdef object property_setter_cache OGRPropertySetter = { (OFTInteger, OFSTNone, "int"): IntegerField, (OFTInteger, OFSTNone, "int32"): IntegerField, (OFTInteger, OFSTNone, "float"): RealField, (OFTInteger, OFSTNone, "str"): StringField, (OFTInteger, OFSTBoolean, "bool"): BooleanField, (OFTInteger, OFSTBoolean, "int"): BooleanField, (OFTInteger, OFSTInt16, "int"): Int16Field, (OFTInteger, OFSTInt16, "str"): StringField, (OFTInteger64, OFSTNone, "int"): Integer64Field, (OFTInteger64, OFSTNone, "int64"): Integer64Field, (OFTInteger64, OFSTNone, "float"): RealField, (OFTInteger64, OFSTNone, "str"): StringField, (OFTReal, OFSTNone, "float"): RealField, (OFTReal, OFSTNone, "str"): StringField, (OFTReal, OFSTFloat32, "float"): RealField, (OFTReal, OFSTFloat32, "float32"): RealField, (OFTReal, OFSTFloat32, "str"): StringField, (OFTString, OFSTNone, "str"): StringField, (OFTString, OFSTNone, "dict"): StringField, (OFTDate, OFSTNone, "date"): DateField, (OFTDate, OFSTNone, "str"): DateField, (OFTTime, OFSTNone, "time"): TimeField, (OFTTime, OFSTNone, "str"): TimeField, (OFTDateTime, OFSTNone, "datetime"): DateTimeField, (OFTDateTime, OFSTNone, "str"): DateTimeField, (OFTBinary, OFSTNone, "bytes"): BinaryField, (OFTBinary, OFSTNone, "bytearray"): BinaryField, (OFTBinary, OFSTNone, "memoryview"): BinaryField, (OFTStringList, OFSTNone, "list"): StringListField, (OFTString, OFSTJSON, "dict"): JSONField, (OFTString, OFSTJSON, "list"): JSONField, } def __init__(self, driver=None): self.driver = driver self.property_setter_cache = {} cdef OGRFeatureH build(self, feature, collection) except NULL: cdef void *cogr_geometry = NULL cdef const char *string_c = NULL cdef WritingSession session = collection.session cdef void *cogr_layer = session.cogr_layer cdef void *cogr_featuredefn = OGR_L_GetLayerDefn(cogr_layer) cdef void *cogr_feature = OGR_F_Create(cogr_featuredefn) cdef AbstractField setter if cogr_layer == NULL: raise ValueError("Null layer") if cogr_featuredefn == NULL: raise ValueError("Null feature definition") if cogr_feature == NULL: raise ValueError("Null feature") if feature.geometry is not None: cogr_geometry = OGRGeomBuilder().build(feature.geometry) exc_wrap_int(OGR_F_SetGeometryDirectly(cogr_feature, cogr_geometry)) encoding = session._get_internal_encoding() for key, value in feature.properties.items(): i = session._schema_mapping_index[key] if i < 0: continue if value is None: OGR_F_SetFieldNull(cogr_feature, i) else: schema_type = session._schema_normalized_field_types[key] val_type = type(value) if val_type in self.property_setter_cache: setter = self.property_setter_cache[val_type] else: for cls in val_type.mro(): fieldkey = (*FIELD_TYPES_MAP2[NAMED_FIELD_TYPES[schema_type]], cls.__name__) try: setter = self.OGRPropertySetter[fieldkey](driver=self.driver) except KeyError: continue else: self.property_setter_cache[val_type] = setter break else: log.warning("Skipping field because of invalid value: key=%r, value=%r", key, value) continue # Special case: serialize dicts to assist OGR. if isinstance(value, dict): value = json.dumps(value) log.debug("Setting feature property: key=%r, value=%r, i=%r, setter=%r", key, value, i, setter) setter.set(cogr_feature, i, value, {"encoding": encoding}) return cogr_feature cdef _deleteOgrFeature(void *cogr_feature): """Delete an OGR feature""" if cogr_feature is not NULL: OGR_F_Destroy(cogr_feature) cogr_feature = NULL def featureRT(feat, collection): # For testing purposes only, leaks the JSON data feature = decode_object(feat) cdef void *cogr_feature = OGRFeatureBuilder().build(feature, collection) cdef void *cogr_geometry = OGR_F_GetGeometryRef(cogr_feature) if cogr_geometry == NULL: raise ValueError("Null geometry") result = FeatureBuilder().build( cogr_feature, encoding='utf-8', bbox=False, driver=collection.driver ) _deleteOgrFeature(cogr_feature) return result cdef class Session: cdef void *cogr_ds cdef void *cogr_layer cdef object _fileencoding cdef object _encoding cdef object collection cdef bint cursor_interrupted OGRFieldGetter = { (OFTInteger, OFSTNone): IntegerField, (OFTInteger, OFSTBoolean): BooleanField, (OFTInteger, OFSTInt16): Int16Field, (OFTInteger64, OFSTNone): Integer64Field, (OFTReal, OFSTNone): RealField, (OFTString, OFSTNone): StringField, (OFTDate, OFSTNone): DateField, (OFTTime, OFSTNone): TimeField, (OFTDateTime, OFSTNone): DateTimeField, (OFTBinary, OFSTNone): BinaryField, (OFTStringList, OFSTNone): StringListField, (OFTString, OFSTJSON): JSONField, } def __init__(self): self.cogr_ds = NULL self.cogr_layer = NULL self._fileencoding = None self._encoding = None self.cursor_interrupted = False def __dealloc__(self): self.stop() def start(self, collection, **kwargs): cdef const char *path_c = NULL cdef const char *name_c = NULL cdef void *drv = NULL cdef void *ds = NULL cdef char **ignore_fields = NULL path_b = collection.path.encode('utf-8') path_c = path_b self._fileencoding = kwargs.get('encoding') or collection.encoding # We have two ways of specifying drivers to try. Resolve the # values into a single set of driver short names. if collection._driver: drivers = set([collection._driver]) elif collection.enabled_drivers: drivers = set(collection.enabled_drivers) else: drivers = None encoding = kwargs.pop('encoding', None) if encoding: kwargs['encoding'] = encoding.upper() self.cogr_ds = gdal_open_vector(path_c, 0, drivers, kwargs) layername = collection.name if isinstance(layername, str): layername_b = layername.encode('utf-8') self.cogr_layer = GDALDatasetGetLayerByName(self.cogr_ds, layername_b) elif isinstance(layername, int): self.cogr_layer = GDALDatasetGetLayer(self.cogr_ds, layername) if self.cogr_layer == NULL: raise ValueError(f"Null layer: {collection} {layername}") else: name_c = OGR_L_GetName(self.cogr_layer) name_b = name_c collection.name = name_b.decode('utf-8') encoding = self._get_internal_encoding() if collection.ignore_fields or collection.include_fields is not None: if not OGR_L_TestCapability(self.cogr_layer, OLC_IGNOREFIELDS): raise DriverError("Driver does not support ignore_fields") self.collection = collection if self.collection.include_fields is not None: self.collection.ignore_fields = list( set(self.get_schema()["properties"]) - set(collection.include_fields) ) if self.collection.ignore_fields: try: for name in self.collection.ignore_fields: try: name_b = name.encode(encoding) except AttributeError: raise TypeError( f'Ignored field "{name}" has type ' f'"{name.__class__.__name__}", expected string' ) else: ignore_fields = CSLAddString(ignore_fields, name_b) OGR_L_SetIgnoredFields(self.cogr_layer, ignore_fields) finally: CSLDestroy(ignore_fields) cpdef stop(self): self.cogr_layer = NULL if self.cogr_ds != NULL: try: with cpl_errs: GDALClose(self.cogr_ds) except CPLE_BaseError as exc: raise DriverError(str(exc)) finally: self.cogr_ds = NULL def get_fileencoding(self): """DEPRECATED""" warnings.warn("get_fileencoding is deprecated and will be removed in a future version.", FionaDeprecationWarning) return self._fileencoding def _get_fallback_encoding(self): """Determine a format-specific fallback encoding to use when using OGR_F functions Parameters ---------- None Returns ------- str """ if "Shapefile" in self.get_driver(): return 'iso-8859-1' else: return locale.getpreferredencoding() def _get_internal_encoding(self): """Determine the encoding to use when use OGR_F functions Parameters ---------- None Returns ------- str Notes ----- If the layer implements RFC 23 support for UTF-8, the return value will be 'utf-8' and callers can be certain that this is correct. If the layer does not have the OLC_STRINGSASUTF8 capability marker, it is not possible to know exactly what the internal encoding is and this method returns best guesses. That means ISO-8859-1 for shapefiles and the locale's preferred encoding for other formats such as CSV files. """ if OGR_L_TestCapability(self.cogr_layer, OLC_STRINGSASUTF8): return 'utf-8' else: return self._fileencoding or self._get_fallback_encoding() def get_length(self): if self.cogr_layer == NULL: raise ValueError("Null layer") return self._get_feature_count(0) def get_driver(self): cdef void *cogr_driver = GDALGetDatasetDriver(self.cogr_ds) if cogr_driver == NULL: raise ValueError("Null driver") cdef const char *name = OGR_Dr_GetName(cogr_driver) driver_name = name return driver_name.decode() def get_schema(self): """Get a dictionary representation of a collection's schema. The schema dict contains "geometry" and "properties" items. Returns ------- dict Warnings -------- Fiona 1.9 does not support multiple fields with the name name. When encountered, a warning message is logged and the field is skipped. """ cdef int i cdef int num_fields cdef OGRFeatureDefnH featuredefn = NULL cdef OGRFieldDefnH fielddefn = NULL cdef const char *key_c cdef AbstractField getter props = {} if self.cogr_layer == NULL: raise ValueError("Null layer") if self.collection.ignore_fields: ignore_fields = self.collection.ignore_fields else: ignore_fields = set() featuredefn = OGR_L_GetLayerDefn(self.cogr_layer) if featuredefn == NULL: raise ValueError("Null feature definition") encoding = self._get_internal_encoding() num_fields = OGR_FD_GetFieldCount(featuredefn) for i from 0 <= i < num_fields: fielddefn = OGR_FD_GetFieldDefn(featuredefn, i) if fielddefn == NULL: raise ValueError(f"NULL field definition at index {i}") key_c = OGR_Fld_GetNameRef(fielddefn) if key_c == NULL: raise ValueError(f"NULL field name reference at index {i}") key_b = key_c key = key_b.decode(encoding) if not key: warnings.warn(f"Empty field name at index {i}", FeatureWarning) if key in ignore_fields: continue # See gh-1178 for an example of a pathological collection # with multiple identically name fields. if key in props: log.warning( "Field name collision detected, field is skipped: i=%r, key=%r", i, key ) continue fieldtype = OGR_Fld_GetType(fielddefn) fieldsubtype = OGR_Fld_GetSubType(fielddefn) fieldkey = (fieldtype, fieldsubtype) try: getter = self.OGRFieldGetter[fieldkey](driver=self.collection.driver) props[key] = getter.name(fielddefn) except KeyError: log.warning( "Skipping field %s: invalid type %s", key, fieldkey ) continue ret = {"properties": props} if not self.collection.ignore_geometry: code = normalize_geometry_type_code(OGR_FD_GetGeomType(featuredefn)) ret["geometry"] = GEOMETRY_TYPES[code] return ret def get_crs(self): """Get the layer's CRS Returns ------- CRS """ wkt = self.get_crs_wkt() if not wkt: return CRS() else: return CRS.from_user_input(wkt) def get_crs_wkt(self): cdef char *proj_c = NULL cdef void *cogr_crs = NULL if self.cogr_layer == NULL: raise ValueError("Null layer") try: cogr_crs = exc_wrap_pointer(OGR_L_GetSpatialRef(self.cogr_layer)) # TODO: we don't intend to use try/except for flow control # this is a work around for a GDAL issue. except FionaNullPointerError: log.debug("Layer has no coordinate system") except fiona._err.CPLE_OpenFailedError as exc: log.debug("A support file wasn't opened. See the preceding ERROR level message.") cogr_crs = OGR_L_GetSpatialRef(self.cogr_layer) log.debug("Called OGR_L_GetSpatialRef() again without error checking.") if cogr_crs == NULL: raise exc if cogr_crs is not NULL: log.debug("Got coordinate system") try: OSRExportToWkt(cogr_crs, &proj_c) if proj_c == NULL: raise ValueError("Null projection") proj_b = proj_c crs_wkt = proj_b.decode('utf-8') finally: CPLFree(proj_c) return crs_wkt else: log.debug("Projection not found (cogr_crs was NULL)") return "" def get_extent(self): cdef OGREnvelope extent if self.cogr_layer == NULL: raise ValueError("Null layer") result = OGR_L_GetExtent(self.cogr_layer, &extent, 1) self.cursor_interrupted = True if result != OGRERR_NONE: raise DriverError("Driver was not able to calculate bounds") return (extent.MinX, extent.MinY, extent.MaxX, extent.MaxY) cdef int _get_feature_count(self, force=0): if self.cogr_layer == NULL: raise ValueError("Null layer") self.cursor_interrupted = True return OGR_L_GetFeatureCount(self.cogr_layer, force) def has_feature(self, fid): """Provides access to feature data by FID. Supports Collection.__contains__(). """ cdef void * cogr_feature fid = int(fid) cogr_feature = OGR_L_GetFeature(self.cogr_layer, fid) if cogr_feature != NULL: _deleteOgrFeature(cogr_feature) return True else: return False def get_feature(self, fid): """Provides access to feature data by FID. Supports Collection.__contains__(). """ cdef void * cogr_feature fid = int(fid) cogr_feature = OGR_L_GetFeature(self.cogr_layer, fid) if cogr_feature != NULL: feature = FeatureBuilder(driver=self.collection.driver).build( cogr_feature, encoding=self._get_internal_encoding(), bbox=False, driver=self.collection.driver, ignore_fields=self.collection.ignore_fields, ignore_geometry=self.collection.ignore_geometry, ) _deleteOgrFeature(cogr_feature) return feature else: raise KeyError(f"There is no feature with fid {fid!r}") get = get_feature # TODO: Make this an alias for get_feature in a future version. def __getitem__(self, item): cdef void * cogr_feature if isinstance(item, slice): warnings.warn("Collection slicing is deprecated and will be disabled in a future version.", FionaDeprecationWarning) itr = Iterator(self.collection, item.start, item.stop, item.step) return list(itr) elif isinstance(item, int): index = item # from the back if index < 0: ftcount = self._get_feature_count(0) if ftcount == -1: raise IndexError( "collection's dataset does not support negative indexes") index += ftcount cogr_feature = OGR_L_GetFeature(self.cogr_layer, index) if cogr_feature == NULL: return None feature = FeatureBuilder(driver=self.collection.driver).build( cogr_feature, encoding=self._get_internal_encoding(), bbox=False, driver=self.collection.driver, ignore_fields=self.collection.ignore_fields, ignore_geometry=self.collection.ignore_geometry, ) _deleteOgrFeature(cogr_feature) return feature def isactive(self): if self.cogr_layer != NULL and self.cogr_ds != NULL: return 1 else: return 0 def tags(self, ns=None): """Returns a dict containing copies of the dataset or layers's tags. Tags are pairs of key and value strings. Tags belong to namespaces. The standard namespaces are: default (None) and 'IMAGE_STRUCTURE'. Applications can create their own additional namespaces. Parameters ---------- ns: str, optional Can be used to select a namespace other than the default. Returns ------- dict """ cdef GDALMajorObjectH obj = NULL if self.cogr_layer != NULL: obj = self.cogr_layer else: obj = self.cogr_ds cdef const char *domain = NULL if ns: ns = ns.encode('utf-8') domain = ns cdef char **metadata = NULL metadata = GDALGetMetadata(obj, domain) num_items = CSLCount(metadata) return dict(metadata[i].decode('utf-8').split('=', 1) for i in range(num_items)) def get_tag_item(self, key, ns=None): """Returns tag item value Parameters ---------- key: str The key for the metadata item to fetch. ns: str, optional Used to select a namespace other than the default. Returns ------- str """ key = key.encode('utf-8') cdef const char *name = key cdef const char *domain = NULL if ns: ns = ns.encode('utf-8') domain = ns cdef GDALMajorObjectH obj = NULL if self.cogr_layer != NULL: obj = self.cogr_layer else: obj = self.cogr_ds cdef const char *value = NULL value = GDALGetMetadataItem(obj, name, domain) if value == NULL: return None return value.decode("utf-8") cdef class WritingSession(Session): cdef object _schema_mapping cdef object _schema_mapping_index cdef object _schema_normalized_field_types def start(self, collection, **kwargs): cdef OGRSpatialReferenceH cogr_srs = NULL cdef char **options = NULL cdef char *path_c = NULL cdef const char *driver_c = NULL cdef const char *name_c = NULL cdef const char *proj_c = NULL cdef const char *fileencoding_c = NULL cdef OGRFieldSubType field_subtype cdef int ret path = collection.path self.collection = collection userencoding = kwargs.get('encoding') if collection.mode == 'a': path_b = strencode(path) path_c = path_b if not CPLCheckForFile(path_c, NULL): raise OSError("No such file or directory %s" % path) try: self.cogr_ds = gdal_open_vector(path_c, 1, None, kwargs) if isinstance(collection.name, str): name_b = collection.name.encode('utf-8') name_c = name_b self.cogr_layer = exc_wrap_pointer(GDALDatasetGetLayerByName(self.cogr_ds, name_c)) elif isinstance(collection.name, int): self.cogr_layer = exc_wrap_pointer(GDALDatasetGetLayer(self.cogr_ds, collection.name)) except CPLE_BaseError as exc: GDALClose(self.cogr_ds) self.cogr_ds = NULL self.cogr_layer = NULL raise DriverError(str(exc)) else: self._fileencoding = userencoding or self._get_fallback_encoding() before_fields = self.get_schema()['properties'] elif collection.mode == 'w': path_b = strencode(path) path_c = path_b driver_b = collection.driver.encode() driver_c = driver_b cogr_driver = exc_wrap_pointer(GDALGetDriverByName(driver_c)) cogr_ds = NULL if not CPLCheckForFile(path_c, NULL): log.debug("File doesn't exist. Creating a new one...") with Env(GDAL_VALIDATE_CREATION_OPTIONS="NO"): cogr_ds = gdal_create(cogr_driver, path_c, kwargs) else: if collection.driver == "GeoJSON": # We must manually remove geojson files as GDAL doesn't do this for us. log.debug("Removing GeoJSON file") if path.startswith("/vsi"): VSIUnlink(path_c) else: os.unlink(path) with Env(GDAL_VALIDATE_CREATION_OPTIONS="NO"): cogr_ds = gdal_create(cogr_driver, path_c, kwargs) else: try: # Attempt to open existing dataset in write mode, # letting GDAL/OGR handle the overwriting. cogr_ds = gdal_open_vector(path_c, 1, None, kwargs) except DriverError: # log.exception("Caught DriverError") # failed, attempt to create it with Env(GDAL_VALIDATE_CREATION_OPTIONS="NO"): cogr_ds = gdal_create(cogr_driver, path_c, kwargs) else: # check capability of creating a new layer in the existing dataset capability = GDALDatasetTestCapability(cogr_ds, ODsCCreateLayer) if not capability or collection.name is None: # unable to use existing dataset, recreate it log.debug("Unable to use existing dataset: capability=%r, name=%r", capability, collection.name) GDALClose(cogr_ds) cogr_ds = NULL with Env(GDAL_VALIDATE_CREATION_OPTIONS="NO"): cogr_ds = gdal_create(cogr_driver, path_c, kwargs) self.cogr_ds = cogr_ds # Set the spatial reference system from the crs given to the # collection constructor. We by-pass the crs_wkt # properties because they aren't accessible until the layer # is constructed (later). try: col_crs = collection._crs_wkt if col_crs: cogr_srs = exc_wrap_pointer(OSRNewSpatialReference(NULL)) proj_b = col_crs.encode('utf-8') proj_c = proj_b OSRSetFromUserInput(cogr_srs, proj_c) osr_set_traditional_axis_mapping_strategy(cogr_srs) except CPLE_BaseError as exc: GDALClose(self.cogr_ds) self.cogr_ds = NULL self.cogr_layer = NULL raise CRSError(str(exc)) # Determine which encoding to use. The encoding parameter given to # the collection constructor takes highest precedence, then # 'iso-8859-1' (for shapefiles), then the system's default encoding # as last resort. sysencoding = locale.getpreferredencoding() self._fileencoding = userencoding or ("Shapefile" in collection.driver and 'iso-8859-1') or sysencoding if "Shapefile" in collection.driver: if self._fileencoding: fileencoding_b = self._fileencoding.upper().encode('utf-8') fileencoding_c = fileencoding_b options = CSLSetNameValue(options, "ENCODING", fileencoding_c) # Does the layer exist already? If so, we delete it. layer_count = GDALDatasetGetLayerCount(self.cogr_ds) layer_names = [] for i in range(layer_count): cogr_layer = GDALDatasetGetLayer(self.cogr_ds, i) name_c = OGR_L_GetName(cogr_layer) name_b = name_c layer_names.append(name_b.decode('utf-8')) idx = -1 if isinstance(collection.name, str): if collection.name in layer_names: idx = layer_names.index(collection.name) elif isinstance(collection.name, int): if collection.name >= 0 and collection.name < layer_count: idx = collection.name if idx >= 0: log.debug("Deleted pre-existing layer at %s", collection.name) GDALDatasetDeleteLayer(self.cogr_ds, idx) # Create the named layer in the datasource. layername = collection.name layername_b = layername.encode('utf-8') # To avoid circular import. from fiona import meta kwarg_keys = set(key.upper() for key in kwargs.keys()) lyr_creation_option_keys = kwarg_keys & set(meta.layer_creation_options(collection.driver)) for k, v in kwargs.items(): if v is not None and k.upper() in lyr_creation_option_keys: kb = k.upper().encode('utf-8') if isinstance(v, bool): vb = ('ON' if v else 'OFF').encode('utf-8') else: vb = str(v).encode('utf-8') options = CSLAddNameValue(options, kb, vb) geometry_type = collection.schema.get("geometry", "Unknown") if not isinstance(geometry_type, str) and geometry_type is not None: geometry_types = set(geometry_type) if len(geometry_types) > 1: geometry_type = "Unknown" else: geometry_type = geometry_types.pop() if geometry_type == "Any" or geometry_type is None: geometry_type = "Unknown" geometry_code = geometry_type_code(geometry_type) try: # In GDAL versions > 3.6.0 the following directive may # suffice and we might be able to eliminate the import # of fiona.meta in a future version of Fiona. with Env(GDAL_VALIDATE_CREATION_OPTIONS="NO"): self.cogr_layer = exc_wrap_pointer( GDALDatasetCreateLayer( self.cogr_ds, layername_b, cogr_srs, geometry_code, options)) except Exception as exc: GDALClose(self.cogr_ds) self.cogr_ds = NULL raise DriverIOError(str(exc)) finally: if options != NULL: CSLDestroy(options) # Shapefile layers make a copy of the passed srs. GPKG # layers, on the other hand, increment its reference # count. OSRRelease() is the safe way to release # OGRSpatialReferenceH. if cogr_srs != NULL: OSRRelease(cogr_srs) log.debug("Created layer %s", collection.name) # Next, make a layer definition from the given schema properties, # which are a dict built-in since Fiona 2.0 # Test if default fields are included in provided schema schema_fields = collection.schema['properties'] default_fields = self.get_schema()['properties'] for key, value in default_fields.items(): if key in schema_fields and not schema_fields[key] == value: raise SchemaError( f"Property '{key}' must have type '{value}' " f"for driver '{self.collection.driver}'" ) new_fields = {k: v for k, v in schema_fields.items() if k not in default_fields} before_fields = default_fields.copy() before_fields.update(new_fields) encoding = self._get_internal_encoding() for key, value in new_fields.items(): # Is there a field width/precision? width = precision = None if ':' in value: value, fmt = value.split(':') if '.' in fmt: width, precision = map(int, fmt.split('.')) else: width = int(fmt) # Type inference based on field width is something # we should reconsider down the road. if value == 'int': if width == 0 or width >= 10: value = 'int64' else: value = 'int32' value = normalize_field_type(value) ftype = NAMED_FIELD_TYPES[value] ogrfieldtype, ogrfieldsubtype = FIELD_TYPES_MAP2[ftype] try: key_b = key.encode(encoding) cogr_fielddefn = exc_wrap_pointer( OGR_Fld_Create(key_b, ogrfieldtype) ) if width: OGR_Fld_SetWidth(cogr_fielddefn, width) if precision: OGR_Fld_SetPrecision(cogr_fielddefn, precision) if ogrfieldsubtype != OFSTNone: OGR_Fld_SetSubType(cogr_fielddefn, ogrfieldsubtype) exc_wrap_int(OGR_L_CreateField(self.cogr_layer, cogr_fielddefn, 1)) except (UnicodeEncodeError, CPLE_BaseError) as exc: GDALClose(self.cogr_ds) self.cogr_ds = NULL self.cogr_layer = NULL raise SchemaError(str(exc)) else: OGR_Fld_Destroy(cogr_fielddefn) # Mapping of the Python collection schema to the munged # OGR schema. after_fields = self.get_schema()['properties'] self._schema_mapping = dict(zip(before_fields.keys(), after_fields.keys())) # Mapping of the Python collection schema to OGR field indices. # We assume that get_schema()['properties'].keys() is in the exact OGR field order assert len(before_fields) == len(after_fields) self._schema_mapping_index = dict(zip(before_fields.keys(), range(len(after_fields.keys())))) # Mapping of the Python collection schema to normalized field types self._schema_normalized_field_types = {k: normalize_field_type(v) for (k, v) in self.collection.schema['properties'].items()} log.debug("Writing started") def writerecs(self, records, collection): """Writes records to collection storage. Parameters ---------- records : Iterable A stream of feature records. collection : Collection The collection in which feature records are stored. Returns ------- None """ cdef OGRSFDriverH cogr_driver cdef OGRFeatureH cogr_feature cdef int features_in_transaction = 0 cdef OGRLayerH cogr_layer = self.cogr_layer if cogr_layer == NULL: raise ValueError("Null layer") cdef OGRFeatureBuilder feat_builder = OGRFeatureBuilder(driver=collection.driver) valid_geom_types = collection._valid_geom_types def validate_geometry_type(record): if record["geometry"] is None: return True return record["geometry"]["type"].lstrip("3D ") in valid_geom_types transactions_supported = GDALDatasetTestCapability(self.cogr_ds, ODsCTransactions) log.debug("Transaction supported: %s", transactions_supported) if transactions_supported: log.debug("Starting transaction (initial)") result = GDALDatasetStartTransaction(self.cogr_ds, 0) if result == OGRERR_FAILURE: raise TransactionError("Failed to start transaction") schema_props_keys = set(collection.schema['properties'].keys()) for _rec in records: record = decode_object(_rec) # Validate against collection's schema. if set(record.properties.keys()) != schema_props_keys: raise ValueError( "Record does not match collection schema: %r != %r" % ( list(record.properties.keys()), list(schema_props_keys) )) if not validate_geometry_type(record): raise GeometryTypeValidationError( "Record's geometry type does not match " "collection schema's geometry type: %r != %r" % ( record.geometry.type, collection.schema['geometry'] )) cogr_feature = feat_builder.build(record, collection) result = OGR_L_CreateFeature(cogr_layer, cogr_feature) if result != OGRERR_NONE: msg = get_last_error_msg() raise RuntimeError( f"GDAL Error: {msg}. Failed to write record: {record}" ) _deleteOgrFeature(cogr_feature) if transactions_supported: features_in_transaction += 1 if features_in_transaction == DEFAULT_TRANSACTION_SIZE: log.debug("Committing transaction (intermediate)") result = GDALDatasetCommitTransaction(self.cogr_ds) if result == OGRERR_FAILURE: raise TransactionError("Failed to commit transaction") log.debug("Starting transaction (intermediate)") result = GDALDatasetStartTransaction(self.cogr_ds, 0) if result == OGRERR_FAILURE: raise TransactionError("Failed to start transaction") features_in_transaction = 0 if transactions_supported: log.debug("Committing transaction (final)") result = GDALDatasetCommitTransaction(self.cogr_ds) if result == OGRERR_FAILURE: raise TransactionError("Failed to commit transaction") def sync(self, collection): """Syncs OGR to disk.""" cdef void *cogr_ds = self.cogr_ds cdef void *cogr_layer = self.cogr_layer if cogr_ds == NULL: raise ValueError("Null data source") gdal_flush_cache(cogr_ds) log.debug("Flushed data source cache") def update_tags(self, tags, ns=None): """Writes a dict containing the dataset or layers's tags. Tags are pairs of key and value strings. Tags belong to namespaces. The standard namespaces are: default (None) and 'IMAGE_STRUCTURE'. Applications can create their own additional namespaces. Parameters ---------- tags: dict The dict of metadata items to set. ns: str, optional Used to select a namespace other than the default. Returns ------- int """ cdef GDALMajorObjectH obj = NULL if self.cogr_layer != NULL: obj = self.cogr_layer else: obj = self.cogr_ds cdef const char *domain = NULL if ns: ns = ns.encode('utf-8') domain = ns cdef char **metadata = NULL try: for key, value in tags.items(): key = key.encode("utf-8") value = value.encode("utf-8") metadata = CSLAddNameValue(metadata, key, value) return GDALSetMetadata(obj, metadata, domain) finally: CSLDestroy(metadata) def update_tag_item(self, key, tag, ns=None): """Updates the tag item value Parameters ---------- key: str The key for the metadata item to set. tag: str The value of the metadata item to set. ns: str Used to select a namespace other than the default. Returns ------- int """ key = key.encode('utf-8') cdef const char *name = key tag = tag.encode("utf-8") cdef char *value = tag cdef const char *domain = NULL if ns: ns = ns.encode('utf-8') domain = ns cdef GDALMajorObjectH obj = NULL if self.cogr_layer != NULL: obj = self.cogr_layer else: obj = self.cogr_ds return GDALSetMetadataItem(obj, name, value, domain) cdef class Iterator: """Provides iterated access to feature data. """ cdef collection cdef encoding cdef int next_index cdef stop cdef start cdef step cdef fastindex cdef fastcount cdef ftcount cdef stepsign cdef FeatureBuilder feat_builder def __cinit__(self, collection, start=None, stop=None, step=None, bbox=None, mask=None, where=None): if collection.session is None: raise ValueError("I/O operation on closed collection") self.collection = collection cdef Session session cdef void *cogr_geometry session = self.collection.session cdef void *cogr_layer = session.cogr_layer if cogr_layer == NULL: raise ValueError("Null layer") OGR_L_ResetReading(cogr_layer) if bbox and mask: raise ValueError("mask and bbox can not be set together") if bbox: OGR_L_SetSpatialFilterRect( cogr_layer, bbox[0], bbox[1], bbox[2], bbox[3]) elif mask: mask_geom = decode_object(mask) cogr_geometry = OGRGeomBuilder().build(mask_geom) OGR_L_SetSpatialFilter(cogr_layer, cogr_geometry) OGR_G_DestroyGeometry(cogr_geometry) else: OGR_L_SetSpatialFilter(cogr_layer, NULL) if where: where_b = where.encode('utf-8') where_c = where_b try: exc_wrap_int( OGR_L_SetAttributeFilter(cogr_layer, where_c)) except CPLE_AppDefinedError as e: raise AttributeFilterError(e) from None else: OGR_L_SetAttributeFilter(cogr_layer, NULL) self.encoding = session._get_internal_encoding() self.fastindex = OGR_L_TestCapability( session.cogr_layer, OLC_FASTSETNEXTBYINDEX) log.debug("OLC_FASTSETNEXTBYINDEX: %s", self.fastindex) self.fastcount = OGR_L_TestCapability( session.cogr_layer, OLC_FASTFEATURECOUNT) log.debug("OLC_FASTFEATURECOUNT: %s", self.fastcount) # In some cases we need to force count of all features # We need to check if start is not greater ftcount: (start is not None and start > 0) # If start is a negative index: (start is not None and start < 0) # If stop is a negative index: (stop is not None and stop < 0) if ((start is not None and not start == 0) or (stop is not None and stop < 0)): if not self.fastcount: warnings.warn("Layer does not support" \ " OLC_FASTFEATURECOUNT, negative slices or start values other than zero" \ " may be slow.", RuntimeWarning) self.ftcount = session._get_feature_count(1) else: self.ftcount = session._get_feature_count(0) if self.ftcount == -1 and ((start is not None and start < 0) or (stop is not None and stop < 0)): raise IndexError( "collection's dataset does not support negative slice indexes") if stop is not None and stop < 0: stop += self.ftcount if start is None: start = 0 if start is not None and start < 0: start += self.ftcount # step size if step is None: step = 1 if step == 0: raise ValueError("slice step cannot be zero") if step < 0 and not self.fastindex: warnings.warn("Layer does not support" \ " OLCFastSetNextByIndex, negative step size may" \ " be slow.", RuntimeWarning) # Check if we are outside of the range: if not self.ftcount == -1: if start > self.ftcount and step > 0: start = -1 if start > self.ftcount and step < 0: start = self.ftcount - 1 elif self.ftcount == -1 and not start == 0: warnings.warn("Layer is unable to check if slice is within range of data.", RuntimeWarning) self.stepsign = int(math.copysign(1, step)) self.stop = stop self.start = start self.step = step self.next_index = start log.debug("Next index: %d", self.next_index) # Set OGR_L_SetNextByIndex only if within range if start >= 0 and (self.ftcount == -1 or self.start < self.ftcount): exc_wrap_int(OGR_L_SetNextByIndex(session.cogr_layer, self.next_index)) session.cursor_interrupted = False self.feat_builder = FeatureBuilder(driver=collection.driver) def __iter__(self): return self def _next(self): """Internal method to set read cursor to next item""" cdef Session session = self.collection.session # Check if next_index is valid if self.next_index < 0: raise StopIteration # GeoJSON driver with gdal 2.1 - 2.2 returns last feature # if index greater than number of features if self.ftcount >= 0 and self.next_index >= self.ftcount: raise StopIteration if self.stepsign == 1: if self.next_index < self.start or (self.stop is not None and self.next_index >= self.stop): raise StopIteration else: if self.next_index > self.start or (self.stop is not None and self.next_index <= self.stop): raise StopIteration # Set read cursor to next_item position if session.cursor_interrupted: if not self.fastindex and not self.next_index == 0: warnings.warn( "Sequential read of iterator was interrupted. Resetting iterator. " "This can negatively impact the performance.", RuntimeWarning ) exc_wrap_int(OGR_L_SetNextByIndex(session.cogr_layer, self.next_index)) session.cursor_interrupted = False else: if self.step > 1 and self.fastindex: exc_wrap_int(OGR_L_SetNextByIndex(session.cogr_layer, self.next_index)) elif self.step > 1 and not self.fastindex and not self.next_index == self.start: # OGR's default implementation of SetNextByIndex is # calling ResetReading() and then calling GetNextFeature # n times. We can shortcut that if we know the previous # index. OGR_L_GetNextFeature increments cursor by 1, # therefore self.step - 1 as one increment was performed # when feature is read. for _ in range(self.step - 1): try: cogr_feature = OGR_L_GetNextFeature(session.cogr_layer) if cogr_feature == NULL: raise StopIteration finally: _deleteOgrFeature(cogr_feature) elif self.step > 1 and not self.fastindex and self.next_index == self.start: exc_wrap_int(OGR_L_SetNextByIndex(session.cogr_layer, self.next_index)) elif self.step == 0: # OGR_L_GetNextFeature increments read cursor by one pass elif self.step < 0: exc_wrap_int(OGR_L_SetNextByIndex(session.cogr_layer, self.next_index)) # set the next index self.next_index += self.step log.debug("Next index: %d", self.next_index) def __next__(self): cdef OGRFeatureH cogr_feature = NULL cdef Session session = self.collection.session if not session or not session.isactive: raise FionaValueError("Session is inactive, dataset is closed or layer is unavailable.") # Update read cursor self._next() # Get the next feature. cogr_feature = OGR_L_GetNextFeature(session.cogr_layer) if cogr_feature == NULL: raise StopIteration try: return self.feat_builder.build( cogr_feature, encoding=self.collection.session._get_internal_encoding(), bbox=False, driver=self.collection.driver, ignore_fields=self.collection.ignore_fields, ignore_geometry=self.collection.ignore_geometry, ) finally: _deleteOgrFeature(cogr_feature) cdef class ItemsIterator(Iterator): def __next__(self): cdef long fid cdef OGRFeatureH cogr_feature = NULL cdef Session session = self.collection.session if not session or not session.isactive: raise FionaValueError("Session is inactive, dataset is closed or layer is unavailable.") # Update read cursor self._next() # Get the next feature. cogr_feature = OGR_L_GetNextFeature(session.cogr_layer) if cogr_feature == NULL: raise StopIteration try: fid = OGR_F_GetFID(cogr_feature) feature = self.feat_builder.build( cogr_feature, encoding=self.collection.session._get_internal_encoding(), bbox=False, driver=self.collection.driver, ignore_fields=self.collection.ignore_fields, ignore_geometry=self.collection.ignore_geometry, ) else: return fid, feature finally: _deleteOgrFeature(cogr_feature) cdef class KeysIterator(Iterator): def __next__(self): cdef long fid cdef OGRFeatureH cogr_feature = NULL cdef Session session = self.collection.session if not session or not session.isactive: raise FionaValueError("Session is inactive, dataset is closed or layer is unavailable.") # Update read cursor self._next() # Get the next feature. cogr_feature = OGR_L_GetNextFeature(session.cogr_layer) if cogr_feature == NULL: raise StopIteration fid = OGR_F_GetFID(cogr_feature) _deleteOgrFeature(cogr_feature) return fid def _remove(path, driver=None): """Deletes an OGR data source """ cdef void *cogr_driver cdef void *cogr_ds cdef int result cdef char *driver_c if driver is None: # attempt to identify the driver by opening the dataset try: cogr_ds = gdal_open_vector(path.encode("utf-8"), 0, None, {}) except (DriverError, FionaNullPointerError): raise DatasetDeleteError(f"Failed to remove data source {path}") cogr_driver = GDALGetDatasetDriver(cogr_ds) GDALClose(cogr_ds) else: cogr_driver = GDALGetDriverByName(driver.encode("utf-8")) if cogr_driver == NULL: raise DatasetDeleteError(f"Null driver when attempting to delete {path}") if not OGR_Dr_TestCapability(cogr_driver, ODrCDeleteDataSource): raise DatasetDeleteError("Driver does not support dataset removal operation") result = GDALDeleteDataset(cogr_driver, path.encode('utf-8')) if result != OGRERR_NONE: raise DatasetDeleteError(f"Failed to remove data source {path}") def _remove_layer(path, layer, driver=None): cdef void *cogr_ds cdef int layer_index if isinstance(layer, int): layer_index = layer layer_str = str(layer_index) else: layer_names = _listlayers(path) try: layer_index = layer_names.index(layer) except ValueError: raise ValueError(f'Layer "{layer}" does not exist in datasource: {path}') layer_str = f'"{layer}"' if layer_index < 0: layer_names = _listlayers(path) layer_index = len(layer_names) + layer_index try: cogr_ds = gdal_open_vector(path.encode("utf-8"), 1, None, {}) except (DriverError, FionaNullPointerError): raise DatasetDeleteError(f"Failed to remove data source {path}") result = GDALDatasetDeleteLayer(cogr_ds, layer_index) GDALClose(cogr_ds) if result == OGRERR_UNSUPPORTED_OPERATION: raise DatasetDeleteError(f"Removal of layer {layer_str} not supported by driver") elif result != OGRERR_NONE: raise DatasetDeleteError(f"Failed to remove layer {layer_str} from datasource: {path}") def _listlayers(path, **kwargs): """Provides a list of the layers in an OGR data source. """ cdef void *cogr_ds = NULL cdef void *cogr_layer = NULL cdef const char *path_c cdef const char *name_c # Open OGR data source. path_b = strencode(path) path_c = path_b cogr_ds = gdal_open_vector(path_c, 0, None, kwargs) # Loop over the layers to get their names. layer_count = GDALDatasetGetLayerCount(cogr_ds) layer_names = [] for i in range(layer_count): cogr_layer = GDALDatasetGetLayer(cogr_ds, i) name_c = OGR_L_GetName(cogr_layer) name_b = name_c layer_names.append(name_b.decode('utf-8')) # Close up data source. if cogr_ds != NULL: GDALClose(cogr_ds) cogr_ds = NULL return layer_names def _listdir(path): """List all files in path, if path points to a directory""" cdef const char *path_c cdef int n cdef char** papszFiles cdef VSIStatBufL st_buf try: path_b = path.encode('utf-8') except UnicodeDecodeError: path_b = path path_c = path_b if not VSIStatL(path_c, &st_buf) == 0: raise FionaValueError(f"Path '{path}' does not exist.") if not VSI_ISDIR(st_buf.st_mode): raise FionaValueError(f"Path '{path}' is not a directory.") papszFiles = VSIReadDir(path_c) n = CSLCount(papszFiles) files = [] for i in range(n): files.append(papszFiles[i].decode("utf-8")) CSLDestroy(papszFiles) return files def buffer_to_virtual_file(bytesbuf, ext=''): """Maps a bytes buffer to a virtual file. `ext` is empty or begins with a period and contains at most one period. """ vsi_filename = f"/vsimem/{uuid4().hex}{ext}" vsi_cfilename = vsi_filename if not isinstance(vsi_filename, str) else vsi_filename.encode('utf-8') vsi_handle = VSIFileFromMemBuffer(vsi_cfilename, bytesbuf, len(bytesbuf), 0) if vsi_handle == NULL: raise OSError('failed to map buffer to file') if VSIFCloseL(vsi_handle) != 0: raise OSError('failed to close mapped file handle') return vsi_filename def remove_virtual_file(vsi_filename): vsi_cfilename = vsi_filename if not isinstance(vsi_filename, str) else vsi_filename.encode('utf-8') return VSIUnlink(vsi_cfilename) cdef class MemoryFileBase: """Base for a BytesIO-like class backed by an in-memory file.""" cdef VSILFILE * _vsif def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext=''): """A file in an in-memory filesystem. Parameters ---------- file_or_bytes : file or bytes A file opened in binary mode or bytes filename : str A filename for the in-memory file under /vsimem ext : str A file extension for the in-memory file under /vsimem. Ignored if filename was provided. """ if file_or_bytes: if hasattr(file_or_bytes, 'read'): initial_bytes = file_or_bytes.read() elif isinstance(file_or_bytes, bytes): initial_bytes = file_or_bytes else: raise TypeError( "Constructor argument must be a file opened in binary " "mode or bytes.") else: initial_bytes = b'' # Make an in-memory directory specific to this dataset to help organize # auxiliary files. self._dirname = dirname or str(uuid4().hex) VSIMkdir(f"/vsimem/{self._dirname}".encode("utf-8"), 0666) if filename: # GDAL's SRTMHGT driver requires the filename to be "correct" (match # the bounds being written) self.name = f"/vsimem/{self._dirname}/{filename}" else: # GDAL 2.1 requires a .zip extension for zipped files. self.name = f"/vsimem/{self._dirname}/{self._dirname}{ext}" name_b = self.name.encode('utf-8') self._initial_bytes = initial_bytes cdef unsigned char *buffer = self._initial_bytes if self._initial_bytes: self._vsif = VSIFileFromMemBuffer( name_b, buffer, len(self._initial_bytes), 0) self.mode = "r" else: self._vsif = NULL self.mode = "r+" self.closed = False def _open(self): """Ensure that the instance has a valid VSI file handle.""" cdef VSILFILE *fp = NULL name_b = self.name.encode('utf-8') if not self.exists(): fp = VSIFOpenL(name_b, "w") if fp == NULL: raise OSError("VSIFOpenL failed") else: VSIFCloseL(fp) self._vsif = NULL if self._vsif == NULL: fp = VSIFOpenL(name_b, self.mode.encode("utf-8")) if fp == NULL: log.error("VSIFOpenL failed: name=%r, mode=%r", self.name, self.mode) raise OSError("VSIFOpenL failed") else: self._vsif = fp def _ensure_extension(self, drivername=None): """Ensure that the instance's name uses a file extension supported by the driver.""" # Avoid a crashing bug with GDAL versions < 2. if get_gdal_version_tuple() < (2, ): return recommended_extension = _get_metadata_item(drivername, "DMD_EXTENSION") if recommended_extension is not None: if not recommended_extension.startswith("."): recommended_extension = "." + recommended_extension root, ext = os.path.splitext(self.name) if not ext: log.info("Setting extension: root=%r, extension=%r", root, recommended_extension) self.name = root + recommended_extension def exists(self): """Test if the in-memory file exists. Returns ------- bool True if the in-memory file exists. """ cdef VSIStatBufL st_buf name_b = self.name.encode('utf-8') return VSIStatL(name_b, &st_buf) == 0 def __len__(self): """Length of the file's buffer in number of bytes. Returns ------- int """ if not self.getbuffer(): return 0 return self.getbuffer().size def getbuffer(self): """Return a view on bytes of the file, or None.""" cdef unsigned char *buffer = NULL cdef vsi_l_offset buffer_len = 0 cdef unsigned char [:] buff_view name_b = self.name.encode('utf-8') buffer = VSIGetMemFileBuffer(name_b, &buffer_len, 0) if buffer == NULL or buffer_len == 0: return None else: buff_view = buffer return buff_view def close(self): """Close and tear down VSI file and directory.""" if self._vsif != NULL: VSIFCloseL(self._vsif) self._vsif = NULL # As soon as support for GDAL < 3 is dropped, we can switch # to VSIRmdirRecursive. VSIUnlink(self.name.encode("utf-8")) VSIRmdir(self._dirname.encode("utf-8")) self.closed = True def seek(self, offset, whence=0): self._open() return VSIFSeekL(self._vsif, offset, whence) def tell(self): self._open() if self._vsif != NULL: return VSIFTellL(self._vsif) else: return 0 def read(self, size=-1): """Read size bytes from MemoryFile.""" cdef bytes result cdef unsigned char *buffer = NULL cdef vsi_l_offset buffer_len = 0 if size < 0: name_b = self.name.encode('utf-8') buffer = VSIGetMemFileBuffer(name_b, &buffer_len, 0) size = buffer_len buffer = CPLMalloc(size) self._open() try: objects_read = VSIFReadL(buffer, 1, size, self._vsif) result = buffer[:objects_read] return result finally: CPLFree(buffer) def write(self, data): """Write data bytes to MemoryFile""" cdef const unsigned char *view = data n = len(data) self._open() result = VSIFWriteL(view, 1, n, self._vsif) VSIFFlushL(self._vsif) return result def _get_metadata_item(driver, metadata_item): """Query metadata items Parameters ---------- driver : str Driver to query metadata_item : str or None Metadata item to query Returns ------- str or None Metadata item """ cdef const char* metadata_c = NULL cdef void *cogr_driver if get_gdal_version_tuple() < (2, ): return None if driver is None: return None driver_b = strencode(driver) cogr_driver = GDALGetDriverByName(driver_b) if cogr_driver == NULL: raise FionaValueError(f"Could not find driver '{driver}'") metadata_c = GDALGetMetadataItem(cogr_driver, metadata_item.encode('utf-8'), NULL) metadata = None if metadata_c != NULL: metadata = metadata_c metadata = metadata.decode('utf-8') if len(metadata) == 0: metadata = None return metadata Fiona-1.10.1/fiona/ogrext1.pxd000066400000000000000000000260401467206072700160450ustar00rootroot00000000000000# Copyright (c) 2007, Sean C. Gillies # All rights reserved. # See ../LICENSE.txt from libc.stdio cimport FILE cdef extern from "gdal.h": ctypedef void * GDALDriverH ctypedef void * GDALMajorObjectH const char* GDALGetMetadataItem(GDALMajorObjectH obj, const char *pszName, const char *pszDomain) char * GDALVersionInfo (char *pszRequest) cdef extern from "gdal_version.h": int GDAL_COMPUTE_VERSION(int maj, int min, int rev) cdef extern from "cpl_conv.h": void * CPLMalloc (size_t) void CPLFree (void *ptr) void CPLSetThreadLocalConfigOption (char *key, char *val) void CPLSetConfigOption (char *key, char *val) const char *CPLGetConfigOption (char *, char *) int CPLCheckForFile(char *, char **) cdef extern from "cpl_string.h": char ** CSLAddNameValue (char **list, char *name, char *value) char ** CSLSetNameValue (char **list, char *name, char *value) void CSLDestroy (char **list) char ** CSLAddString(char **list, const char *string) int CSLCount(char **papszStrList) cdef extern from "sys/stat.h" nogil: struct stat: int st_mode cdef extern from "cpl_vsi.h" nogil: ctypedef int vsi_l_offset ctypedef FILE VSILFILE ctypedef stat VSIStatBufL unsigned char *VSIGetMemFileBuffer(const char *path, vsi_l_offset *data_len, int take_ownership) VSILFILE *VSIFileFromMemBuffer(const char *path, void *data, vsi_l_offset data_len, int take_ownership) VSILFILE* VSIFOpenL(const char *path, const char *mode) int VSIFCloseL(VSILFILE *fp) int VSIUnlink(const char *path) int VSIMkdir(const char *path, long mode) int VSIRmdir(const char *path) int VSIFFlushL(VSILFILE *fp) size_t VSIFReadL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp) char** VSIReadDir(const char* pszPath) int VSIFSeekL(VSILFILE *fp, vsi_l_offset nOffset, int nWhence) vsi_l_offset VSIFTellL(VSILFILE *fp) int VSIFTruncateL(VSILFILE *fp, vsi_l_offset nNewSize) size_t VSIFWriteL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp) int VSIStatL(const char *pszFilename, VSIStatBufL *psStatBuf) int VSI_ISDIR(int mode) ctypedef int OGRErr ctypedef struct OGREnvelope: double MinX double MaxX double MinY double MaxY cdef extern from "ogr_core.h": ctypedef enum OGRwkbGeometryType: wkbUnknown wkbPoint wkbLineString wkbPolygon wkbMultiPoint wkbMultiLineString wkbMultiPolygon wkbGeometryCollection wkbCircularString wkbCompoundCurve wkbCurvePolygon wkbMultiCurve wkbMultiSurface wkbCurve wkbSurface wkbPolyhedralSurface wkbTIN wkbTriangle wkbNone wkbLinearRing wkbCircularStringZ wkbCompoundCurveZ wkbCurvePolygonZ wkbMultiCurveZ wkbMultiSurfaceZ wkbCurveZ wkbSurfaceZ wkbPolyhedralSurfaceZ wkbTINZ wkbTriangleZ wkbPointM wkbLineStringM wkbPolygonM wkbMultiPointM wkbMultiLineStringM wkbMultiPolygonM wkbGeometryCollectionM wkbCircularStringM wkbCompoundCurveM wkbCurvePolygonM wkbMultiCurveM wkbMultiSurfaceM wkbCurveM wkbSurfaceM wkbPolyhedralSurfaceM wkbTINM wkbTriangleM wkbPointZM wkbLineStringZM wkbPolygonZM wkbMultiPointZM wkbMultiLineStringZM wkbMultiPolygonZM wkbGeometryCollectionZM wkbCircularStringZM wkbCompoundCurveZM wkbCurvePolygonZM wkbMultiCurveZM wkbMultiSurfaceZM wkbCurveZM wkbSurfaceZM wkbPolyhedralSurfaceZM wkbTINZM wkbTriangleZM wkbPoint25D wkbLineString25D wkbPolygon25D wkbMultiPoint25D wkbMultiLineString25D wkbMultiPolygon25D wkbGeometryCollection25D ctypedef enum OGRFieldType: OFTInteger OFTIntegerList OFTReal OFTRealList OFTString OFTStringList OFTWideString OFTWideStringList OFTBinary OFTDate OFTTime OFTDateTime OFTMaxType char * OGRGeometryTypeToName(int) char * ODsCCreateLayer = "CreateLayer" char * ODsCDeleteLayer = "DeleteLayer" cdef extern from "ogr_srs_api.h": ctypedef void * OGRSpatialReferenceH void OSRCleanup () OGRSpatialReferenceH OSRClone (OGRSpatialReferenceH srs) int OSRFixup (OGRSpatialReferenceH srs) int OSRExportToProj4 (OGRSpatialReferenceH srs, char **params) int OSRExportToWkt (OGRSpatialReferenceH srs, char **params) int OSRImportFromEPSG (OGRSpatialReferenceH, int code) int OSRImportFromProj4 (OGRSpatialReferenceH srs, const char *proj) int OSRSetFromUserInput (OGRSpatialReferenceH srs, const char *input) int OSRAutoIdentifyEPSG (OGRSpatialReferenceH srs) const char * OSRGetAuthorityName (OGRSpatialReferenceH srs, const char *key) const char * OSRGetAuthorityCode (OGRSpatialReferenceH srs, const char *key) OGRSpatialReferenceH OSRNewSpatialReference (char *wkt) void OSRRelease (OGRSpatialReferenceH srs) void * OCTNewCoordinateTransformation (OGRSpatialReferenceH source, OGRSpatialReferenceH dest) void OCTDestroyCoordinateTransformation (void *source) int OCTTransform (void *ct, int nCount, double *x, double *y, double *z) cdef extern from "ogr_api.h": const char * OGR_Dr_GetName (void *driver) void * OGR_Dr_CreateDataSource (void *driver, const char *path, char **options) int OGR_Dr_DeleteDataSource (void *driver, char *) void * OGR_Dr_Open (void *driver, const char *path, int bupdate) int OGR_Dr_TestCapability (void *driver, const char *) int OGR_DS_DeleteLayer (void *datasource, int n) void * OGR_DS_CreateLayer (void *datasource, char *name, void *crs, int geomType, char **options) void * OGR_DS_ExecuteSQL (void *datasource, char *name, void *filter, char *dialext) void OGR_DS_Destroy (void *datasource) void * OGR_DS_GetDriver (void *layer_defn) void * OGR_DS_GetLayerByName (void *datasource, char *name) int OGR_DS_GetLayerCount (void *datasource) void * OGR_DS_GetLayer (void *datasource, int n) void OGR_DS_ReleaseResultSet (void *datasource, void *results) int OGR_DS_SyncToDisk (void *datasource) int OGR_DS_TestCapability(void *datasource, char *capability) void * OGR_F_Create (void *featuredefn) void OGR_F_Destroy (void *feature) long OGR_F_GetFID (void *feature) int OGR_F_IsFieldSet (void *feature, int n) int OGR_F_GetFieldAsDateTime (void *feature, int n, int *y, int *m, int *d, int *h, int *m, int *s, int *z) double OGR_F_GetFieldAsDouble (void *feature, int n) int OGR_F_GetFieldAsInteger (void *feature, int n) char * OGR_F_GetFieldAsString (void *feature, int n) unsigned char * OGR_F_GetFieldAsBinary(void *feature, int n, int *s) int OGR_F_GetFieldCount (void *feature) void * OGR_F_GetFieldDefnRef (void *feature, int n) int OGR_F_GetFieldIndex (void *feature, char *name) void * OGR_F_GetGeometryRef (void *feature) void * OGR_F_StealGeometry (void *feature) void OGR_F_SetFieldDateTime (void *feature, int n, int y, int m, int d, int hh, int mm, int ss, int tz) void OGR_F_SetFieldDouble (void *feature, int n, double value) void OGR_F_SetFieldInteger (void *feature, int n, int value) void OGR_F_SetFieldString (void *feature, int n, char *value) void OGR_F_SetFieldBinary (void *feature, int n, int l, unsigned char *value) int OGR_F_SetGeometryDirectly (void *feature, void *geometry) void * OGR_FD_Create (char *name) int OGR_FD_GetFieldCount (void *featuredefn) void * OGR_FD_GetFieldDefn (void *featuredefn, int n) int OGR_FD_GetGeomType (void *featuredefn) char * OGR_FD_GetName (void *featuredefn) void * OGR_Fld_Create (char *name, OGRFieldType fieldtype) void OGR_Fld_Destroy (void *fielddefn) char * OGR_Fld_GetNameRef (void *fielddefn) int OGR_Fld_GetPrecision (void *fielddefn) int OGR_Fld_GetType (void *fielddefn) int OGR_Fld_GetWidth (void *fielddefn) void OGR_Fld_Set (void *fielddefn, char *name, int fieldtype, int width, int precision, int justification) void OGR_Fld_SetPrecision (void *fielddefn, int n) void OGR_Fld_SetWidth (void *fielddefn, int n) OGRErr OGR_G_AddGeometryDirectly (void *geometry, void *part) void OGR_G_AddPoint (void *geometry, double x, double y, double z) void OGR_G_AddPoint_2D (void *geometry, double x, double y) void OGR_G_CloseRings (void *geometry) void * OGR_G_CreateGeometry (int wkbtypecode) void OGR_G_DestroyGeometry (void *geometry) unsigned char * OGR_G_ExportToJson (void *geometry) void OGR_G_ExportToWkb (void *geometry, int endianness, char *buffer) int OGR_G_GetCoordinateDimension (void *geometry) int OGR_G_GetGeometryCount (void *geometry) unsigned char * OGR_G_GetGeometryName (void *geometry) int OGR_G_GetGeometryType (void *geometry) void * OGR_G_GetGeometryRef (void *geometry, int n) int OGR_G_GetPointCount (void *geometry) double OGR_G_GetX (void *geometry, int n) double OGR_G_GetY (void *geometry, int n) double OGR_G_GetZ (void *geometry, int n) OGRErr OGR_G_ImportFromWkb (void *geometry, unsigned char *bytes, int nbytes) int OGR_G_WkbSize (void *geometry) void * OGR_G_ForceToMultiPolygon (void *geometry) void * OGR_G_ForceToPolygon (void *geometry) void * OGR_G_Clone(void *geometry) OGRErr OGR_L_CreateFeature (void *layer, void *feature) OGRErr OGR_L_CreateField (void *layer, void *fielddefn, int flexible) OGRErr OGR_L_GetExtent (void *layer, void *extent, int force) void * OGR_L_GetFeature (void *layer, int n) int OGR_L_GetFeatureCount (void *layer, int m) void * OGR_L_GetLayerDefn (void *layer) char * OGR_L_GetName (void *layer) void * OGR_L_GetNextFeature (void *layer) void * OGR_L_GetSpatialFilter (void *layer) void * OGR_L_GetSpatialRef (void *layer) void OGR_L_ResetReading (void *layer) void OGR_L_SetSpatialFilter (void *layer, void *geometry) void OGR_L_SetSpatialFilterRect ( void *layer, double minx, double miny, double maxx, double maxy ) int OGR_L_TestCapability (void *layer, char *name) void * OGRGetDriverByName (char *) void * OGROpen (char *path, int mode, void *x) void * OGROpenShared (char *path, int mode, void *x) int OGRReleaseDataSource (void *datasource) OGRErr OGR_L_SetIgnoredFields (void *layer, const char **papszFields) OGRErr OGR_L_SetAttributeFilter(void *layer, const char*) OGRErr OGR_L_SetNextByIndex (void *layer, long nIndex) Fiona-1.10.1/fiona/ogrext2.pxd000066400000000000000000000311131467206072700160430ustar00rootroot00000000000000# Copyright (c) 2007, Sean C. Gillies # All rights reserved. # See ../LICENSE.txt from libc.stdio cimport FILE cdef extern from "ogr_core.h": ctypedef int OGRErr ctypedef enum OGRwkbGeometryType: wkbUnknown wkbPoint wkbLineString wkbPolygon wkbMultiPoint wkbMultiLineString wkbMultiPolygon wkbGeometryCollection wkbCircularString wkbCompoundCurve wkbCurvePolygon wkbMultiCurve wkbMultiSurface wkbCurve wkbSurface wkbPolyhedralSurface wkbTIN wkbTriangle wkbNone wkbLinearRing wkbCircularStringZ wkbCompoundCurveZ wkbCurvePolygonZ wkbMultiCurveZ wkbMultiSurfaceZ wkbCurveZ wkbSurfaceZ wkbPolyhedralSurfaceZ wkbTINZ wkbTriangleZ wkbPointM wkbLineStringM wkbPolygonM wkbMultiPointM wkbMultiLineStringM wkbMultiPolygonM wkbGeometryCollectionM wkbCircularStringM wkbCompoundCurveM wkbCurvePolygonM wkbMultiCurveM wkbMultiSurfaceM wkbCurveM wkbSurfaceM wkbPolyhedralSurfaceM wkbTINM wkbTriangleM wkbPointZM wkbLineStringZM wkbPolygonZM wkbMultiPointZM wkbMultiLineStringZM wkbMultiPolygonZM wkbGeometryCollectionZM wkbCircularStringZM wkbCompoundCurveZM wkbCurvePolygonZM wkbMultiCurveZM wkbMultiSurfaceZM wkbCurveZM wkbSurfaceZM wkbPolyhedralSurfaceZM wkbTINZM wkbTriangleZM wkbPoint25D wkbLineString25D wkbPolygon25D wkbMultiPoint25D wkbMultiLineString25D wkbMultiPolygon25D wkbGeometryCollection25D ctypedef enum OGRFieldType: OFTInteger OFTIntegerList OFTReal OFTRealList OFTString OFTStringList OFTWideString OFTWideStringList OFTBinary OFTDate OFTTime OFTDateTime OFTInteger64 OFTInteger64List OFTMaxType ctypedef int OGRFieldSubType cdef int OFSTNone = 0 cdef int OFSTBoolean = 1 cdef int OFSTInt16 = 2 cdef int OFSTFloat32 = 3 cdef int OFSTMaxSubType = 3 ctypedef struct OGREnvelope: double MinX double MaxX double MinY double MaxY char * OGRGeometryTypeToName(int) char * ODsCCreateLayer = "CreateLayer" char * ODsCDeleteLayer = "DeleteLayer" char * ODsCTransactions = "Transactions" cdef extern from "gdal.h": ctypedef void * GDALDriverH ctypedef void * GDALMajorObjectH char * GDALVersionInfo (char *pszRequest) void * GDALGetDriverByName(const char * pszName) void * GDALOpenEx(const char * pszFilename, unsigned int nOpenFlags, const char *const *papszAllowedDrivers, const char *const *papszOpenOptions, const char *const *papszSiblingFiles ) int GDAL_OF_UPDATE int GDAL_OF_READONLY int GDAL_OF_VECTOR int GDAL_OF_VERBOSE_ERROR int GDALDatasetGetLayerCount(void * hds) void * GDALDatasetGetLayer(void * hDS, int iLayer) void * GDALDatasetGetLayerByName(void * hDS, char * pszName) void GDALClose(void * hDS) void * GDALCreate(void * hDriver, const char * pszFilename, int nXSize, int nYSize, int nBands, GDALDataType eBandType, char ** papszOptions) void * GDALDatasetCreateLayer(void * hDS, const char * pszName, void * hSpatialRef, int eType, char ** papszOptions) int GDALDatasetDeleteLayer(void * hDS, int iLayer) void GDALFlushCache(void * hDS) char * GDALGetDriverShortName(void * hDriver) char * GDALGetDatasetDriver (void * hDataset) int GDALDeleteDataset(void * hDriver, const char * pszFilename) OGRErr GDALDatasetStartTransaction (void * hDataset, int bForce) OGRErr GDALDatasetCommitTransaction (void * hDataset) OGRErr GDALDatasetRollbackTransaction (void * hDataset) int GDALDatasetTestCapability (void * hDataset, char *) const char* GDALGetMetadataItem(GDALMajorObjectH obj, const char *pszName, const char *pszDomain) ctypedef enum GDALDataType: GDT_Unknown GDT_Byte GDT_UInt16 GDT_Int16 GDT_UInt32 GDT_Int32 GDT_Float32 GDT_Float64 GDT_CInt16 GDT_CInt32 GDT_CFloat32 GDT_CFloat64 GDT_TypeCount cdef extern from "gdal_version.h": int GDAL_COMPUTE_VERSION(int maj, int min, int rev) cdef extern from "cpl_conv.h": void * CPLMalloc (size_t) void CPLFree (void *ptr) void CPLSetThreadLocalConfigOption (char *key, char *val) const char *CPLGetConfigOption (char *, char *) int CPLCheckForFile(char *, char **) cdef extern from "cpl_string.h": char ** CSLAddNameValue (char **list, const char *name, const char *value) char ** CSLSetNameValue (char **list, const char *name, const char *value) void CSLDestroy (char **list) char ** CSLAddString(char **list, const char *string) int CSLCount(char **papszStrList) cdef extern from "sys/stat.h" nogil: struct stat: int st_mode cdef extern from "cpl_vsi.h" nogil: ctypedef int vsi_l_offset ctypedef FILE VSILFILE ctypedef stat VSIStatBufL unsigned char *VSIGetMemFileBuffer(const char *path, vsi_l_offset *data_len, int take_ownership) VSILFILE *VSIFileFromMemBuffer(const char *path, void *data, vsi_l_offset data_len, int take_ownership) VSILFILE* VSIFOpenL(const char *path, const char *mode) int VSIFCloseL(VSILFILE *fp) int VSIUnlink(const char *path) int VSIMkdir(const char *path, long mode) int VSIRmdir(const char *path) int VSIFFlushL(VSILFILE *fp) size_t VSIFReadL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp) char** VSIReadDir(const char* pszPath) int VSIFSeekL(VSILFILE *fp, vsi_l_offset nOffset, int nWhence) vsi_l_offset VSIFTellL(VSILFILE *fp) int VSIFTruncateL(VSILFILE *fp, vsi_l_offset nNewSize) size_t VSIFWriteL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp) int VSIStatL(const char *pszFilename, VSIStatBufL *psStatBuf) int VSI_ISDIR(int mode) cdef extern from "ogr_srs_api.h": ctypedef void * OGRSpatialReferenceH void OSRCleanup () OGRSpatialReferenceH OSRClone (OGRSpatialReferenceH srs) int OSRFixup (OGRSpatialReferenceH srs) int OSRExportToProj4 (OGRSpatialReferenceH srs, char **params) int OSRExportToWkt (OGRSpatialReferenceH srs, char **params) int OSRImportFromEPSG (OGRSpatialReferenceH, int code) int OSRImportFromProj4 (OGRSpatialReferenceH srs, const char *proj) int OSRSetFromUserInput (OGRSpatialReferenceH srs, const char *input) int OSRAutoIdentifyEPSG (OGRSpatialReferenceH srs) const char * OSRGetAuthorityName (OGRSpatialReferenceH srs, const char *key) const char * OSRGetAuthorityCode (OGRSpatialReferenceH srs, const char *key) OGRSpatialReferenceH OSRNewSpatialReference (char *wkt) void OSRRelease (OGRSpatialReferenceH srs) void * OCTNewCoordinateTransformation (OGRSpatialReferenceH source, OGRSpatialReferenceH dest) void OCTDestroyCoordinateTransformation (void *source) int OCTTransform (void *ct, int nCount, double *x, double *y, double *z) cdef extern from "ogr_api.h": const char * OGR_Dr_GetName (void *driver) int OGR_Dr_TestCapability (void *driver, const char *) void * OGR_F_Create (void *featuredefn) void OGR_F_Destroy (void *feature) long OGR_F_GetFID (void *feature) int OGR_F_IsFieldSet (void *feature, int n) int OGR_F_GetFieldAsDateTimeEx (void *feature, int n, int *y, int *m, int *d, int *h, int *m, float *s, int *z) double OGR_F_GetFieldAsDouble (void *feature, int n) int OGR_F_GetFieldAsInteger (void *feature, int n) char * OGR_F_GetFieldAsString (void *feature, int n) unsigned char * OGR_F_GetFieldAsBinary(void *feature, int n, int *s) int OGR_F_GetFieldCount (void *feature) void * OGR_F_GetFieldDefnRef (void *feature, int n) int OGR_F_GetFieldIndex (void *feature, char *name) void * OGR_F_GetGeometryRef (void *feature) void * OGR_F_StealGeometry (void *feature) void OGR_F_SetFieldDateTimeEx (void *feature, int n, int y, int m, int d, int hh, int mm, float ss, int tz) void OGR_F_SetFieldDouble (void *feature, int n, double value) void OGR_F_SetFieldInteger (void *feature, int n, int value) void OGR_F_SetFieldString (void *feature, int n, char *value) void OGR_F_SetFieldBinary (void *feature, int n, int l, unsigned char *value) void OGR_F_SetFieldNull (void *feature, int n) # new in GDAL 2.2 int OGR_F_SetGeometryDirectly (void *feature, void *geometry) void * OGR_FD_Create (char *name) int OGR_FD_GetFieldCount (void *featuredefn) void * OGR_FD_GetFieldDefn (void *featuredefn, int n) int OGR_FD_GetGeomType (void *featuredefn) char * OGR_FD_GetName (void *featuredefn) void * OGR_Fld_Create (char *name, OGRFieldType fieldtype) void OGR_Fld_Destroy (void *fielddefn) char * OGR_Fld_GetNameRef (void *fielddefn) int OGR_Fld_GetPrecision (void *fielddefn) int OGR_Fld_GetType (void *fielddefn) int OGR_Fld_GetWidth (void *fielddefn) void OGR_Fld_Set (void *fielddefn, char *name, int fieldtype, int width, int precision, int justification) void OGR_Fld_SetPrecision (void *fielddefn, int n) void OGR_Fld_SetWidth (void *fielddefn, int n) OGRFieldSubType OGR_Fld_GetSubType(void *fielddefn) void OGR_Fld_SetSubType(void *fielddefn, OGRFieldSubType subtype) OGRErr OGR_G_AddGeometryDirectly (void *geometry, void *part) void OGR_G_AddPoint (void *geometry, double x, double y, double z) void OGR_G_AddPoint_2D (void *geometry, double x, double y) void OGR_G_CloseRings (void *geometry) void * OGR_G_CreateGeometry (int wkbtypecode) void OGR_G_DestroyGeometry (void *geometry) unsigned char * OGR_G_ExportToJson (void *geometry) OGRErr OGR_G_ExportToWkb (void *geometry, int endianness, char *buffer) int OGR_G_GetCoordinateDimension (void *geometry) int OGR_G_GetGeometryCount (void *geometry) unsigned char * OGR_G_GetGeometryName (void *geometry) int OGR_G_GetGeometryType (void *geometry) void * OGR_G_GetGeometryRef (void *geometry, int n) int OGR_G_GetPointCount (void *geometry) double OGR_G_GetX (void *geometry, int n) double OGR_G_GetY (void *geometry, int n) double OGR_G_GetZ (void *geometry, int n) OGRErr OGR_G_ImportFromWkb (void *geometry, unsigned char *bytes, int nbytes) int OGR_G_WkbSize (void *geometry) void * OGR_G_ForceToMultiPolygon (void *geometry) void * OGR_G_ForceToPolygon (void *geometry) void * OGR_G_Clone(void *geometry) OGRErr OGR_L_CreateFeature (void *layer, void *feature) OGRErr OGR_L_CreateField (void *layer, void *fielddefn, int flexible) OGRErr OGR_L_GetExtent (void *layer, void *extent, int force) void * OGR_L_GetFeature (void *layer, int n) int OGR_L_GetFeatureCount (void *layer, int m) void * OGR_G_GetLinearGeometry (void *hGeom, double dfMaxAngleStepSizeDegrees, char **papszOptions) void * OGR_L_GetLayerDefn (void *layer) char * OGR_L_GetName (void *layer) void * OGR_L_GetNextFeature (void *layer) void * OGR_L_GetSpatialFilter (void *layer) void * OGR_L_GetSpatialRef (void *layer) void OGR_L_ResetReading (void *layer) void OGR_L_SetSpatialFilter (void *layer, void *geometry) void OGR_L_SetSpatialFilterRect ( void *layer, double minx, double miny, double maxx, double maxy ) int OGR_L_TestCapability (void *layer, char *name) OGRErr OGR_L_SetIgnoredFields (void *layer, const char **papszFields) OGRErr OGR_L_SetAttributeFilter(void *layer, const char*) OGRErr OGR_L_SetNextByIndex (void *layer, long nIndex) long long OGR_F_GetFieldAsInteger64 (void *feature, int n) void OGR_F_SetFieldInteger64 (void *feature, int n, long long value) Fiona-1.10.1/fiona/ogrext3.pxd000066400000000000000000000310531467206072700160470ustar00rootroot00000000000000# Copyright (c) 2007, Sean C. Gillies # All rights reserved. # See ../LICENSE.txt from libc.stdio cimport FILE cdef extern from "ogr_core.h": ctypedef int OGRErr ctypedef enum OGRwkbGeometryType: wkbUnknown wkbPoint wkbLineString wkbPolygon wkbMultiPoint wkbMultiLineString wkbMultiPolygon wkbGeometryCollection wkbCircularString wkbCompoundCurve wkbCurvePolygon wkbMultiCurve wkbMultiSurface wkbCurve wkbSurface wkbPolyhedralSurface wkbTIN wkbTriangle wkbNone wkbLinearRing wkbCircularStringZ wkbCompoundCurveZ wkbCurvePolygonZ wkbMultiCurveZ wkbMultiSurfaceZ wkbCurveZ wkbSurfaceZ wkbPolyhedralSurfaceZ wkbTINZ wkbTriangleZ wkbPointM wkbLineStringM wkbPolygonM wkbMultiPointM wkbMultiLineStringM wkbMultiPolygonM wkbGeometryCollectionM wkbCircularStringM wkbCompoundCurveM wkbCurvePolygonM wkbMultiCurveM wkbMultiSurfaceM wkbCurveM wkbSurfaceM wkbPolyhedralSurfaceM wkbTINM wkbTriangleM wkbPointZM wkbLineStringZM wkbPolygonZM wkbMultiPointZM wkbMultiLineStringZM wkbMultiPolygonZM wkbGeometryCollectionZM wkbCircularStringZM wkbCompoundCurveZM wkbCurvePolygonZM wkbMultiCurveZM wkbMultiSurfaceZM wkbCurveZM wkbSurfaceZM wkbPolyhedralSurfaceZM wkbTINZM wkbTriangleZM wkbPoint25D wkbLineString25D wkbPolygon25D wkbMultiPoint25D wkbMultiLineString25D wkbMultiPolygon25D wkbGeometryCollection25D ctypedef enum OGRFieldType: OFTInteger OFTIntegerList OFTReal OFTRealList OFTString OFTStringList OFTWideString OFTWideStringList OFTBinary OFTDate OFTTime OFTDateTime OFTInteger64 OFTInteger64List OFTMaxType ctypedef int OGRFieldSubType cdef int OFSTNone = 0 cdef int OFSTBoolean = 1 cdef int OFSTInt16 = 2 cdef int OFSTFloat32 = 3 cdef int OFSTMaxSubType = 3 ctypedef struct OGREnvelope: double MinX double MaxX double MinY double MaxY char * OGRGeometryTypeToName(int) char * ODsCCreateLayer = "CreateLayer" char * ODsCDeleteLayer = "DeleteLayer" char * ODsCTransactions = "Transactions" cdef extern from "gdal.h": ctypedef void * GDALDriverH ctypedef void * GDALMajorObjectH char * GDALVersionInfo (char *pszRequest) void * GDALGetDriverByName(const char * pszName) void * GDALOpenEx(const char * pszFilename, unsigned int nOpenFlags, const char *const *papszAllowedDrivers, const char *const *papszOpenOptions, const char *const *papszSiblingFiles ) int GDAL_OF_UPDATE int GDAL_OF_READONLY int GDAL_OF_VECTOR int GDAL_OF_VERBOSE_ERROR int GDALDatasetGetLayerCount(void * hds) void * GDALDatasetGetLayer(void * hDS, int iLayer) void * GDALDatasetGetLayerByName(void * hDS, char * pszName) void GDALClose(void * hDS) void * GDALCreate(void * hDriver, const char * pszFilename, int nXSize, int nYSize, int nBands, GDALDataType eBandType, char ** papszOptions) void * GDALDatasetCreateLayer(void * hDS, const char * pszName, void * hSpatialRef, int eType, char ** papszOptions) int GDALDatasetDeleteLayer(void * hDS, int iLayer) void GDALFlushCache(void * hDS) char * GDALGetDriverShortName(void * hDriver) char * GDALGetDatasetDriver (void * hDataset) int GDALDeleteDataset(void * hDriver, const char * pszFilename) OGRErr GDALDatasetStartTransaction (void * hDataset, int bForce) OGRErr GDALDatasetCommitTransaction (void * hDataset) OGRErr GDALDatasetRollbackTransaction (void * hDataset) int GDALDatasetTestCapability (void * hDataset, char *) const char* GDALGetMetadataItem(GDALMajorObjectH obj, const char *pszName, const char *pszDomain) ctypedef enum GDALDataType: GDT_Unknown GDT_Byte GDT_UInt16 GDT_Int16 GDT_UInt32 GDT_Int32 GDT_Float32 GDT_Float64 GDT_CInt16 GDT_CInt32 GDT_CFloat32 GDT_CFloat64 GDT_TypeCount cdef extern from "gdal_version.h": int GDAL_COMPUTE_VERSION(int maj, int min, int rev) cdef extern from "cpl_conv.h": void * CPLMalloc (size_t) void CPLFree (void *ptr) void CPLSetThreadLocalConfigOption (char *key, char *val) const char *CPLGetConfigOption (char *, char *) int CPLCheckForFile(char *, char **) cdef extern from "cpl_string.h": char ** CSLAddNameValue (char **list, const char *name, const char *value) char ** CSLSetNameValue (char **list, const char *name, const char *value) void CSLDestroy (char **list) char ** CSLAddString(char **list, const char *string) int CSLCount(char **papszStrList) cdef extern from "sys/stat.h" nogil: struct stat: int st_mode cdef extern from "cpl_vsi.h" nogil: ctypedef int vsi_l_offset ctypedef FILE VSILFILE ctypedef stat VSIStatBufL unsigned char *VSIGetMemFileBuffer(const char *path, vsi_l_offset *data_len, int take_ownership) VSILFILE *VSIFileFromMemBuffer(const char *path, void *data, vsi_l_offset data_len, int take_ownership) VSILFILE* VSIFOpenL(const char *path, const char *mode) int VSIFCloseL(VSILFILE *fp) int VSIUnlink(const char *path) int VSIMkdir(const char *path, long mode) int VSIRmdir(const char *path) int VSIFFlushL(VSILFILE *fp) size_t VSIFReadL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp) char** VSIReadDir(const char* pszPath) int VSIFSeekL(VSILFILE *fp, vsi_l_offset nOffset, int nWhence) vsi_l_offset VSIFTellL(VSILFILE *fp) int VSIFTruncateL(VSILFILE *fp, vsi_l_offset nNewSize) size_t VSIFWriteL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp) int VSIStatL(const char *pszFilename, VSIStatBufL *psStatBuf) int VSI_ISDIR(int mode) cdef extern from "ogr_srs_api.h": ctypedef void * OGRSpatialReferenceH void OSRCleanup () OGRSpatialReferenceH OSRClone (OGRSpatialReferenceH srs) int OSRExportToProj4 (OGRSpatialReferenceH srs, char **params) int OSRExportToWkt (OGRSpatialReferenceH srs, char **params) int OSRImportFromEPSG (OGRSpatialReferenceH, int code) int OSRImportFromProj4 (OGRSpatialReferenceH srs, const char *proj) int OSRSetFromUserInput (OGRSpatialReferenceH srs, const char *input) int OSRAutoIdentifyEPSG (OGRSpatialReferenceH srs) const char * OSRGetAuthorityName (OGRSpatialReferenceH srs, const char *key) const char * OSRGetAuthorityCode (OGRSpatialReferenceH srs, const char *key) OGRSpatialReferenceH OSRNewSpatialReference (char *wkt) void OSRRelease (OGRSpatialReferenceH srs) void * OCTNewCoordinateTransformation (OGRSpatialReferenceH source, OGRSpatialReferenceH dest) void OCTDestroyCoordinateTransformation (void *source) int OCTTransform (void *ct, int nCount, double *x, double *y, double *z) void OSRGetPROJVersion (int *pnMajor, int *pnMinor, int *pnPatch) cdef extern from "ogr_api.h": const char * OGR_Dr_GetName (void *driver) int OGR_Dr_TestCapability (void *driver, const char *) void * OGR_F_Create (void *featuredefn) void OGR_F_Destroy (void *feature) long OGR_F_GetFID (void *feature) int OGR_F_IsFieldSet (void *feature, int n) int OGR_F_GetFieldAsDateTimeEx (void *feature, int n, int *y, int *m, int *d, int *h, int *m, float *s, int *z) double OGR_F_GetFieldAsDouble (void *feature, int n) int OGR_F_GetFieldAsInteger (void *feature, int n) char * OGR_F_GetFieldAsString (void *feature, int n) unsigned char * OGR_F_GetFieldAsBinary(void *feature, int n, int *s) int OGR_F_GetFieldCount (void *feature) void * OGR_F_GetFieldDefnRef (void *feature, int n) int OGR_F_GetFieldIndex (void *feature, char *name) void * OGR_F_GetGeometryRef (void *feature) void * OGR_F_StealGeometry (void *feature) void OGR_F_SetFieldDateTimeEx (void *feature, int n, int y, int m, int d, int hh, int mm, float ss, int tz) void OGR_F_SetFieldDouble (void *feature, int n, double value) void OGR_F_SetFieldInteger (void *feature, int n, int value) void OGR_F_SetFieldString (void *feature, int n, char *value) void OGR_F_SetFieldBinary (void *feature, int n, int l, unsigned char *value) void OGR_F_SetFieldNull (void *feature, int n) # new in GDAL 2.2 int OGR_F_SetGeometryDirectly (void *feature, void *geometry) void * OGR_FD_Create (char *name) int OGR_FD_GetFieldCount (void *featuredefn) void * OGR_FD_GetFieldDefn (void *featuredefn, int n) int OGR_FD_GetGeomType (void *featuredefn) char * OGR_FD_GetName (void *featuredefn) void * OGR_Fld_Create (char *name, OGRFieldType fieldtype) void OGR_Fld_Destroy (void *fielddefn) char * OGR_Fld_GetNameRef (void *fielddefn) int OGR_Fld_GetPrecision (void *fielddefn) int OGR_Fld_GetType (void *fielddefn) int OGR_Fld_GetWidth (void *fielddefn) void OGR_Fld_Set (void *fielddefn, char *name, int fieldtype, int width, int precision, int justification) void OGR_Fld_SetPrecision (void *fielddefn, int n) void OGR_Fld_SetWidth (void *fielddefn, int n) OGRFieldSubType OGR_Fld_GetSubType(void *fielddefn) void OGR_Fld_SetSubType(void *fielddefn, OGRFieldSubType subtype) OGRErr OGR_G_AddGeometryDirectly (void *geometry, void *part) void OGR_G_AddPoint (void *geometry, double x, double y, double z) void OGR_G_AddPoint_2D (void *geometry, double x, double y) void OGR_G_CloseRings (void *geometry) void * OGR_G_CreateGeometry (int wkbtypecode) void OGR_G_DestroyGeometry (void *geometry) unsigned char * OGR_G_ExportToJson (void *geometry) OGRErr OGR_G_ExportToWkb (void *geometry, int endianness, char *buffer) int OGR_G_GetGeometryCount (void *geometry) unsigned char * OGR_G_GetGeometryName (void *geometry) int OGR_G_GetGeometryType (void *geometry) void * OGR_G_GetGeometryRef (void *geometry, int n) int OGR_G_GetPointCount (void *geometry) double OGR_G_GetX (void *geometry, int n) double OGR_G_GetY (void *geometry, int n) double OGR_G_GetZ (void *geometry, int n) OGRErr OGR_G_ImportFromWkb (void *geometry, unsigned char *bytes, int nbytes) int OGR_G_WkbSize (void *geometry) void * OGR_G_ForceToMultiPolygon (void *geometry) void * OGR_G_ForceToPolygon (void *geometry) void * OGR_G_Clone(void *geometry) OGRErr OGR_L_CreateFeature (void *layer, void *feature) OGRErr OGR_L_CreateField (void *layer, void *fielddefn, int flexible) OGRErr OGR_L_GetExtent (void *layer, void *extent, int force) void * OGR_L_GetFeature (void *layer, int n) int OGR_L_GetFeatureCount (void *layer, int m) void * OGR_G_GetLinearGeometry (void *hGeom, double dfMaxAngleStepSizeDegrees, char **papszOptions) void * OGR_L_GetLayerDefn (void *layer) char * OGR_L_GetName (void *layer) void * OGR_L_GetNextFeature (void *layer) void * OGR_L_GetSpatialFilter (void *layer) void * OGR_L_GetSpatialRef (void *layer) void OGR_L_ResetReading (void *layer) void OGR_L_SetSpatialFilter (void *layer, void *geometry) void OGR_L_SetSpatialFilterRect ( void *layer, double minx, double miny, double maxx, double maxy ) int OGR_L_TestCapability (void *layer, char *name) OGRErr OGR_L_SetIgnoredFields (void *layer, const char **papszFields) OGRErr OGR_L_SetAttributeFilter(void *layer, const char*) OGRErr OGR_L_SetNextByIndex (void *layer, long nIndex) long long OGR_F_GetFieldAsInteger64 (void *feature, int n) void OGR_F_SetFieldInteger64 (void *feature, int n, long long value) Fiona-1.10.1/fiona/path.py000066400000000000000000000007601467206072700152460ustar00rootroot00000000000000"""Dataset paths, identifiers, and filenames Note well: this module is deprecated in 1.3.0 and will be removed in a future version. """ import warnings from fiona._path import _ParsedPath as ParsedPath from fiona._path import _UnparsedPath as UnparsedPath from fiona._path import _parse_path as parse_path from fiona._path import _vsi_path as vsi_path from fiona.errors import FionaDeprecationWarning warnings.warn( "fiona.path will be removed in version 2.0.", FionaDeprecationWarning ) Fiona-1.10.1/fiona/rfc3339.py000066400000000000000000000064511467206072700154110ustar00rootroot00000000000000# Fiona's date and time is founded on RFC 3339. # # OGR knows 3 time "zones": GMT, "local time", amd "unknown". Fiona, when # writing will convert times with a timezone offset to GMT (Z) and otherwise # will write times with the unknown zone. import logging import re log = logging.getLogger("Fiona") pattern_date = re.compile(r"(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)") pattern_time = re.compile( r"(\d\d)(:)?(\d\d)(:)?(\d\d)?(\.\d+)?(Z|([+-])?(\d\d)?(:)?(\d\d))?") pattern_datetime = re.compile( r"(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T)?(\d\d)(:)?(\d\d)(:)?(\d\d)?(\.\d+)?(Z|([+-])?(\d\d)?(:)?(\d\d))?") class group_accessor: def __init__(self, m): self.match = m def group(self, i): try: return self.match.group(i) or 0 except IndexError: return 0 def parse_time(text): """ Given a time, returns a datetime tuple Parameters ---------- text: string to be parsed Returns ------- (int, int , int, int, int, int, int, int): datetime tuple: (year, month, day, hour, minute, second, microsecond, utcoffset in minutes or None) """ match = re.search(pattern_time, text) if match is None: raise ValueError(f"Time data '{text}' does not match pattern") g = group_accessor(match) log.debug("Match groups: %s", match.groups()) if g.group(8) == '-': tz = -1.0 * (int(g.group(9)) * 60 + int(g.group(11))) elif g.group(8) == '+': tz = int(g.group(9)) * 60 + int(g.group(11)) else: tz = None return (0, 0, 0, int(g.group(1)), int(g.group(3)), int(g.group(5)), int(1000000.0 * float(g.group(6))), tz ) def parse_date(text): """Given a date, returns a datetime tuple Parameters ---------- text: string to be parsed Returns ------- (int, int , int, int, int, int, int, int): datetime tuple: (year, month, day, hour, minute, second, microsecond, utcoffset in minutes or None) """ match = re.search(pattern_date, text) if match is None: raise ValueError(f"Time data '{text}' does not match pattern") g = group_accessor(match) log.debug("Match groups: %s", match.groups()) return ( int(g.group(1)), int(g.group(3)), int(g.group(5)), 0, 0, 0, 0, None) def parse_datetime(text): """Given a datetime, returns a datetime tuple Parameters ---------- text: string to be parsed Returns ------- (int, int , int, int, int, int, int, int): datetime tuple: (year, month, day, hour, minute, second, microsecond, utcoffset in minutes or None) """ match = re.search(pattern_datetime, text) if match is None: raise ValueError(f"Time data '{text}' does not match pattern") g = group_accessor(match) log.debug("Match groups: %s", match.groups()) if g.group(14) == '-': tz = -1.0 * (int(g.group(15)) * 60 + int(g.group(17))) elif g.group(14) == '+': tz = int(g.group(15)) * 60 + int(g.group(17)) else: tz = None return ( int(g.group(1)), int(g.group(3)), int(g.group(5)), int(g.group(7)), int(g.group(9)), int(g.group(11)), int(1000000.0 * float(g.group(12))), tz) Fiona-1.10.1/fiona/schema.pyx000066400000000000000000000076761467206072700157570ustar00rootroot00000000000000"""Fiona schema module.""" include "gdal.pxi" import itertools from typing import List from fiona.errors import SchemaError cdef class FionaIntegerType: names = ["int32"] type = int cdef class FionaInt16Type: names = ["int16"] type = int cdef class FionaBooleanType: names = ["bool"] type = bool cdef class FionaInteger64Type: names = ["int", "int64"] type = int cdef class FionaRealType: names = ["float", "float64"] type = float cdef class FionaStringType: names = ["str"] type = str cdef class FionaBinaryType: names = ["bytes"] type = bytes cdef class FionaStringListType: names = ["List[str]", "list[str]"] type = List[str] cdef class FionaJSONType: names = ["json"] type = str cdef class FionaDateType: """Dates without time.""" names = ["date"] type = str cdef class FionaTimeType: """Times without dates.""" names = ["time"] type = str cdef class FionaDateTimeType: """Dates and times.""" names = ["datetime"] type = str FIELD_TYPES_MAP2_REV = { (OFTInteger, OFSTNone): FionaIntegerType, (OFTInteger, OFSTBoolean): FionaBooleanType, (OFTInteger, OFSTInt16): FionaInt16Type, (OFTInteger64, OFSTNone): FionaInteger64Type, (OFTReal, OFSTNone): FionaRealType, (OFTString, OFSTNone): FionaStringType, (OFTDate, OFSTNone): FionaDateType, (OFTTime, OFSTNone): FionaTimeType, (OFTDateTime, OFSTNone): FionaDateTimeType, (OFTBinary, OFSTNone): FionaBinaryType, (OFTStringList, OFSTNone): FionaStringListType, (OFTString, OFSTJSON): FionaJSONType, } FIELD_TYPES_MAP2 = {v: k for k, v in FIELD_TYPES_MAP2_REV.items()} FIELD_TYPES_NAMES = list(itertools.chain.from_iterable((k.names for k in FIELD_TYPES_MAP2))) NAMED_FIELD_TYPES = {n: k for k in FIELD_TYPES_MAP2 for n in k.names} def normalize_field_type(ftype): """Normalize free form field types to an element of FIELD_TYPES Parameters ---------- ftype : str A type:width format like 'int:9' or 'str:255' Returns ------- str An element from FIELD_TYPES """ if ftype in FIELD_TYPES_NAMES: return ftype elif ftype.startswith('int'): width = int((ftype.split(':')[1:] or ['0'])[0]) if width == 0 or width >= 10: return 'int64' else: return 'int32' elif ftype.startswith('str'): return 'str' elif ftype.startswith('float'): return 'float' else: raise SchemaError(f"Unknown field type: {ftype}") # Fiona field type names indexed by their major OGR integer field type. # This data is deprecated, no longer used by the project and is left # only for other projects that import it. FIELD_TYPES = [ 'int32', # OFTInteger, Simple 32bit integer None, # OFTIntegerList, List of 32bit integers 'float', # OFTReal, Double Precision floating point None, # OFTRealList, List of doubles 'str', # OFTString, String of UTF-8 chars 'List[str]', # OFTStringList, Array of strings None, # OFTWideString, deprecated None, # OFTWideStringList, deprecated 'bytes', # OFTBinary, Raw Binary data 'date', # OFTDate, Date 'time', # OFTTime, Time 'datetime', # OFTDateTime, Date and Time 'int64', # OFTInteger64, Single 64bit integer None # OFTInteger64List, List of 64bit integers ] # Mapping of Fiona field type names to Python types. # This data is deprecated, no longer used by the project and is left # only for other projects that import it. FIELD_TYPES_MAP = { 'int32': int, 'float': float, 'str': str, 'date': FionaDateType, 'time': FionaTimeType, 'datetime': FionaDateTimeType, 'bytes': bytes, 'int64': int, 'int': int, 'List[str]': List[str], } FIELD_TYPES_MAP_REV = dict([(v, k) for k, v in FIELD_TYPES_MAP.items()]) FIELD_TYPES_MAP_REV[int] = 'int' Fiona-1.10.1/fiona/session.py000066400000000000000000000431461467206072700160020ustar00rootroot00000000000000"""Abstraction for sessions in various clouds.""" import logging import os import warnings from fiona._path import _parse_path, _UnparsedPath log = logging.getLogger(__name__) try: with warnings.catch_warnings(): warnings.simplefilter("ignore") import boto3 except ImportError: log.debug("Could not import boto3, continuing with reduced functionality.") boto3 = None class Session: """Base for classes that configure access to secured resources. Attributes ---------- credentials : dict Keys and values for session credentials. Notes ----- This class is not intended to be instantiated. """ @classmethod def hascreds(cls, config): """Determine if the given configuration has proper credentials Parameters ---------- cls : class A Session class. config : dict GDAL configuration as a dict. Returns ------- bool """ return NotImplemented def get_credential_options(self): """Get credentials as GDAL configuration options Returns ------- dict """ return NotImplemented @staticmethod def from_foreign_session(session, cls=None): """Create a session object matching the foreign `session`. Parameters ---------- session : obj A foreign session object. cls : Session class, optional The class to return. Returns ------- Session """ if not cls: return DummySession() else: return cls(session) @staticmethod def cls_from_path(path): """Find the session class suited to the data at `path`. Parameters ---------- path : str A dataset path or identifier. Returns ------- class """ if not path: return DummySession path = _parse_path(path) if isinstance(path, _UnparsedPath) or path.is_local: return DummySession elif ( path.scheme == "s3" or "amazonaws.com" in path.path ) and "X-Amz-Signature" not in path.path: if boto3 is not None: return AWSSession else: log.info("boto3 not available, falling back to a DummySession.") return DummySession elif path.scheme == "oss" or "aliyuncs.com" in path.path: return OSSSession elif path.path.startswith("/vsiswift/"): return SwiftSession elif path.scheme == "az": return AzureSession # This factory can be extended to other cloud providers here. # elif path.scheme == "cumulonimbus": # for example. # return CumulonimbusSession(*args, **kwargs) else: return DummySession @staticmethod def from_path(path, *args, **kwargs): """Create a session object suited to the data at `path`. Parameters ---------- path : str A dataset path or identifier. args : sequence Positional arguments for the foreign session constructor. kwargs : dict Keyword arguments for the foreign session constructor. Returns ------- Session """ return Session.cls_from_path(path)(*args, **kwargs) @staticmethod def aws_or_dummy(*args, **kwargs): """Create an AWSSession if boto3 is available, else DummySession Parameters ---------- path : str A dataset path or identifier. args : sequence Positional arguments for the foreign session constructor. kwargs : dict Keyword arguments for the foreign session constructor. Returns ------- Session """ if boto3 is not None: return AWSSession(*args, **kwargs) else: return DummySession(*args, **kwargs) @staticmethod def from_environ(*args, **kwargs): """Create a session object suited to the environment. Parameters ---------- path : str A dataset path or identifier. args : sequence Positional arguments for the foreign session constructor. kwargs : dict Keyword arguments for the foreign session constructor. Returns ------- Session """ try: session = Session.aws_or_dummy(*args, **kwargs) session.credentials except RuntimeError: log.warning( "Credentials in environment have expired. Creating a DummySession." ) session = DummySession(*args, **kwargs) return session class DummySession(Session): """A dummy session. Attributes ---------- credentials : dict The session credentials. """ def __init__(self, *args, **kwargs): self._session = None self.credentials = {} @classmethod def hascreds(cls, config): """Determine if the given configuration has proper credentials Parameters ---------- cls : class A Session class. config : dict GDAL configuration as a dict. Returns ------- bool """ return True def get_credential_options(self): """Get credentials as GDAL configuration options Returns ------- dict """ return {} class AWSSession(Session): """Configures access to secured resources stored in AWS S3. """ def __init__( self, session=None, aws_unsigned=False, aws_access_key_id=None, aws_secret_access_key=None, aws_session_token=None, region_name=None, profile_name=None, endpoint_url=None, requester_pays=False, ): """Create a new AWS session Parameters ---------- session : optional A boto3 session object. aws_unsigned : bool, optional (default: False) If True, requests will be unsigned. aws_access_key_id : str, optional An access key id, as per boto3. aws_secret_access_key : str, optional A secret access key, as per boto3. aws_session_token : str, optional A session token, as per boto3. region_name : str, optional A region name, as per boto3. profile_name : str, optional A shared credentials profile name, as per boto3. endpoint_url: str, optional An endpoint_url, as per GDAL's AWS_S3_ENDPOINT requester_pays : bool, optional True if the requester agrees to pay transfer costs (default: False) """ if session: self._session = session else: self._session = boto3.Session( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, aws_session_token=aws_session_token, region_name=region_name, profile_name=profile_name) self.requester_pays = requester_pays self.unsigned = bool(os.getenv("AWS_NO_SIGN_REQUEST", aws_unsigned)) self.endpoint_url = endpoint_url self._creds = ( self._session.get_credentials() if not self.unsigned and self._session else None ) @classmethod def hascreds(cls, config): """Determine if the given configuration has proper credentials Parameters ---------- cls : class A Session class. config : dict GDAL configuration as a dict. Returns ------- bool """ return {"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"}.issubset(config.keys()) @property def credentials(self): """The session credentials as a dict""" res = {} if self._creds: # pragma: no branch frozen_creds = self._creds.get_frozen_credentials() if frozen_creds.access_key: # pragma: no branch res["aws_access_key_id"] = frozen_creds.access_key if frozen_creds.secret_key: # pragma: no branch res["aws_secret_access_key"] = frozen_creds.secret_key if frozen_creds.token: res["aws_session_token"] = frozen_creds.token if self._session.region_name: res["aws_region"] = self._session.region_name if self.requester_pays: res["aws_request_payer"] = "requester" if self.endpoint_url: res["aws_s3_endpoint"] = self.endpoint_url return res def get_credential_options(self): """Get credentials as GDAL configuration options Returns ------- dict """ if self.unsigned: opts = {"AWS_NO_SIGN_REQUEST": "YES"} if "aws_region" in self.credentials: opts["AWS_REGION"] = self.credentials["aws_region"] return opts else: return {k.upper(): v for k, v in self.credentials.items()} class GSSession(Session): """Configures access to secured resources stored in Google Cloud Storage """ def __init__(self, google_application_credentials=None): """Create new Google Cloud Storage session Parameters ---------- google_application_credentials: string Path to the google application credentials JSON file. """ self._creds = {} if google_application_credentials is not None: self._creds['google_application_credentials'] = google_application_credentials @classmethod def hascreds(cls, config): """Determine if the given configuration has proper credentials Parameters ---------- cls : class A Session class. config : dict GDAL configuration as a dict. Returns ------- bool """ return 'GOOGLE_APPLICATION_CREDENTIALS' in config @property def credentials(self): """The session credentials as a dict""" return self._creds def get_credential_options(self): """Get credentials as GDAL configuration options Returns ------- dict """ return {k.upper(): v for k, v in self.credentials.items()} class OSSSession(Session): """Configures access to secured resources stored in Alibaba Cloud OSS.""" def __init__( self, oss_access_key_id=None, oss_secret_access_key=None, oss_endpoint=None ): """Create new Alibaba Cloud OSS session Parameters ---------- oss_access_key_id: string, optional (default: None) An access key id oss_secret_access_key: string, optional (default: None) An secret access key oss_endpoint: string, optional (default: None) the region attached to the bucket """ self._creds = { "oss_access_key_id": oss_access_key_id, "oss_secret_access_key": oss_secret_access_key, "oss_endpoint": oss_endpoint, } @classmethod def hascreds(cls, config): """Determine if the given configuration has proper credentials Parameters ---------- cls : class A Session class. config : dict GDAL configuration as a dict. Returns ------- bool """ return {"OSS_ACCESS_KEY_ID", "OSS_SECRET_ACCESS_KEY"}.issubset(config.keys()) @property def credentials(self): """The session credentials as a dict""" return self._creds def get_credential_options(self): """Get credentials as GDAL configuration options Returns ------- dict """ return {k.upper(): v for k, v in self.credentials.items()} class SwiftSession(Session): """Configures access to secured resources stored in OpenStack Swift Object Storage.""" def __init__( self, session=None, swift_storage_url=None, swift_auth_token=None, swift_auth_v1_url=None, swift_user=None, swift_key=None, ): """Create new OpenStack Swift Object Storage Session. Three methods are possible: 1. Create session by the swiftclient library. 2. The SWIFT_STORAGE_URL and SWIFT_AUTH_TOKEN (this method is recommended by GDAL docs). 3. The SWIFT_AUTH_V1_URL, SWIFT_USER and SWIFT_KEY (This depends on the swiftclient library). Parameters ---------- session: optional A swiftclient connection object swift_storage_url: the storage URL swift_auth_token: the value of the x-auth-token authorization token swift_storage_url: string, optional authentication URL swift_user: string, optional user name to authenticate as swift_key: string, optional key/password to authenticate with Examples -------- >>> import rasterio >>> from rasterio.session import SwiftSession >>> fp = '/vsiswift/bucket/key.tif' >>> conn = Connection( ... authurl='http://127.0.0.1:7777/auth/v1.0', ... user='test:tester', ... key='testing' ... ) >>> session = SwiftSession(conn) >>> with rasterio.Env(session): >>> with rasterio.open(fp) as src: >>> print(src.profile) """ if swift_storage_url and swift_auth_token: self._creds = { "swift_storage_url": swift_storage_url, "swift_auth_token": swift_auth_token, } else: from swiftclient.client import Connection if session: self._session = session else: self._session = Connection( authurl=swift_auth_v1_url, user=swift_user, key=swift_key ) self._creds = { "swift_storage_url": self._session.get_auth()[0], "swift_auth_token": self._session.get_auth()[1], } @classmethod def hascreds(cls, config): """Determine if the given configuration has proper credentials Parameters ---------- cls : class A Session class. config : dict GDAL configuration as a dict. Returns ------- bool """ return {"SWIFT_STORAGE_URL", "SWIFT_AUTH_TOKEN"}.issubset(config.keys()) @property def credentials(self): """The session credentials as a dict""" return self._creds def get_credential_options(self): """Get credentials as GDAL configuration options Returns ------- dict """ return {k.upper(): v for k, v in self.credentials.items()} class AzureSession(Session): """Configures access to secured resources stored in Microsoft Azure Blob Storage.""" def __init__( self, azure_storage_connection_string=None, azure_storage_account=None, azure_storage_access_key=None, azure_unsigned=False, ): """Create new Microsoft Azure Blob Storage session Parameters ---------- azure_storage_connection_string: string A connection string contains both an account name and a secret key. azure_storage_account: string An account name azure_storage_access_key: string A secret key azure_unsigned : bool, optional (default: False) If True, requests will be unsigned. """ self.unsigned = bool(os.getenv("AZURE_NO_SIGN_REQUEST", azure_unsigned)) self.storage_account = os.getenv("AZURE_STORAGE_ACCOUNT", azure_storage_account) if azure_storage_connection_string: self._creds = { "azure_storage_connection_string": azure_storage_connection_string } elif not self.unsigned: self._creds = { "azure_storage_account": self.storage_account, "azure_storage_access_key": azure_storage_access_key, } else: self._creds = {"azure_storage_account": self.storage_account} @classmethod def hascreds(cls, config): """Determine if the given configuration has proper credentials Parameters ---------- cls : class A Session class. config : dict GDAL configuration as a dict. Returns ------- bool """ return ( "AZURE_STORAGE_CONNECTION_STRING" in config or {"AZURE_STORAGE_ACCOUNT", "AZURE_STORAGE_ACCESS_KEY"}.issubset( config.keys() ) or {"AZURE_STORAGE_ACCOUNT", "AZURE_NO_SIGN_REQUEST"}.issubset( config.keys() ) ) @property def credentials(self): """The session credentials as a dict""" return self._creds def get_credential_options(self): """Get credentials as GDAL configuration options Returns ------- dict """ if self.unsigned: return { "AZURE_NO_SIGN_REQUEST": "YES", "AZURE_STORAGE_ACCOUNT": self.storage_account, } else: return {k.upper(): v for k, v in self.credentials.items()} Fiona-1.10.1/fiona/transform.py000066400000000000000000000102301467206072700163160ustar00rootroot00000000000000"""Coordinate and geometry warping and reprojection""" from warnings import warn from fiona._transform import _transform, _transform_geom from fiona.compat import DICT_TYPES from fiona.errors import FionaDeprecationWarning from fiona.model import decode_object, Geometry def transform(src_crs, dst_crs, xs, ys): """Transform coordinates from one reference system to another. Parameters ---------- src_crs: str or dict A string like 'EPSG:4326' or a dict of proj4 parameters like {'proj': 'lcc', 'lat_0': 18.0, 'lat_1': 18.0, 'lon_0': -77.0} representing the coordinate reference system on the "source" or "from" side of the transformation. dst_crs: str or dict A string or dict representing the coordinate reference system on the "destination" or "to" side of the transformation. xs: sequence of float A list or tuple of x coordinate values. Must have the same length as the ``ys`` parameter. ys: sequence of float A list or tuple of y coordinate values. Must have the same length as the ``xs`` parameter. Returns ------- xp, yp: list of float A pair of transformed coordinate sequences. The elements of ``xp`` and ``yp`` correspond exactly to the elements of the ``xs`` and ``ys`` input parameters. Examples -------- >>> transform('EPSG:4326', 'EPSG:26953', [-105.0], [40.0]) ([957097.0952383667], [378940.8419189212]) """ # Function is implemented in the _transform C extension module. return _transform(src_crs, dst_crs, xs, ys) def transform_geom( src_crs, dst_crs, geom, antimeridian_cutting=False, antimeridian_offset=10.0, precision=-1, ): """Transform a geometry obj from one reference system to another. Parameters ---------- src_crs: str or dict A string like 'EPSG:4326' or a dict of proj4 parameters like {'proj': 'lcc', 'lat_0': 18.0, 'lat_1': 18.0, 'lon_0': -77.0} representing the coordinate reference system on the "source" or "from" side of the transformation. dst_crs: str or dict A string or dict representing the coordinate reference system on the "destination" or "to" side of the transformation. geom: obj A GeoJSON-like geometry object with 'type' and 'coordinates' members or an iterable of GeoJSON-like geometry objects. antimeridian_cutting: bool, optional ``True`` to cut output geometries in two at the antimeridian, the default is ``False`. antimeridian_offset: float, optional A distance in decimal degrees from the antimeridian, outside of which geometries will not be cut. precision: int, optional Round geometry coordinates to this number of decimal places. This parameter is deprecated and will be removed in 2.0. Returns ------- obj A new GeoJSON-like geometry (or a list of GeoJSON-like geometries if an iterable was given as input) with transformed coordinates. Note that if the output is at the antimeridian, it may be cut and of a different geometry ``type`` than the input, e.g., a polygon input may result in multi-polygon output. Examples -------- >>> transform_geom( ... 'EPSG:4326', 'EPSG:26953', ... {'type': 'Point', 'coordinates': [-105.0, 40.0]}) {'type': 'Point', 'coordinates': (957097.0952383667, 378940.8419189212)} """ if precision >= 0: warn( "The precision keyword argument is deprecated and will be removed in 2.0", FionaDeprecationWarning, ) # Function is implemented in the _transform C extension module. if isinstance(geom, (Geometry,) + DICT_TYPES): return _transform_geom( src_crs, dst_crs, decode_object(geom), antimeridian_cutting, antimeridian_offset, precision, ) else: return _transform_geom( src_crs, dst_crs, (decode_object(g) for g in geom), antimeridian_cutting, antimeridian_offset, precision, ) Fiona-1.10.1/fiona/vfs.py000066400000000000000000000047071467206072700151150ustar00rootroot00000000000000"""Implementation of Apache VFS schemes and URLs.""" import sys import re from urllib.parse import urlparse # Supported URI schemes and their mapping to GDAL's VSI suffix. # TODO: extend for other cloud platforms. SCHEMES = { 'ftp': 'curl', 'gzip': 'gzip', 'http': 'curl', 'https': 'curl', 's3': 's3', 'tar': 'tar', 'zip': 'zip', 'gs': 'gs', } CURLSCHEMES = {k for k, v in SCHEMES.items() if v == 'curl'} # TODO: extend for other cloud platforms. REMOTESCHEMES = {k for k, v in SCHEMES.items() if v in ('curl', 's3', 'gs')} def valid_vsi(vsi): """Ensures all parts of our vsi path are valid schemes.""" return all(p in SCHEMES for p in vsi.split('+')) def is_remote(scheme): if scheme is None: return False return any(p in REMOTESCHEMES for p in scheme.split('+')) def vsi_path(path, vsi=None, archive=None): # If a VSI and archive file are specified, we convert the path to # an OGR VSI path (see cpl_vsi.h). if vsi: prefix = '/'.join(f'vsi{SCHEMES[p]}' for p in vsi.split('+')) if archive: result = f'/{prefix}/{archive}{path}' else: result = f'/{prefix}/{path}' else: result = path return result def parse_paths(uri, vfs=None): """Parse a URI or Apache VFS URL into its parts Returns: tuple (path, scheme, archive) """ archive = scheme = None path = uri # Windows drive letters (e.g. "C:\") confuse `urlparse` as they look like # URL schemes if sys.platform == "win32" and re.match("^[a-zA-Z]\\:", path): return path, None, None if vfs: parts = urlparse(vfs) scheme = parts.scheme archive = parts.path if parts.netloc and parts.netloc != 'localhost': archive = parts.netloc + archive else: parts = urlparse(path) scheme = parts.scheme path = parts.path if parts.netloc and parts.netloc != 'localhost': if scheme.split("+")[-1] in CURLSCHEMES: # We need to deal with cases such as zip+https://server.com/data.zip path = f"{scheme.split('+')[-1]}://{parts.netloc}{path}" else: path = parts.netloc + path if scheme in SCHEMES: parts = path.split('!') path = parts.pop() if parts else None archive = parts.pop() if parts else None scheme = None if not scheme else scheme return path, scheme, archive Fiona-1.10.1/pyproject.toml000066400000000000000000000033051467206072700155560ustar00rootroot00000000000000[build-system] requires = ["setuptools>=67.8", "cython~=3.0.2"] build-backend = "setuptools.build_meta" [project] name = "fiona" dynamic = ["readme", "version"] authors = [ {name = "Sean Gillies"}, ] maintainers = [ {name = "Fiona contributors"}, ] description = "Fiona reads and writes spatial data files" keywords = ["gis", "vector", "feature", "data"] license = {text = "BSD 3-Clause"} classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: GIS", ] requires-python = ">=3.8" dependencies = [ "attrs>=19.2.0", "certifi", "click~=8.0", "click-plugins>=1.0", "cligj>=0.5", 'importlib-metadata;python_version<"3.10"', ] [project.optional-dependencies] all = ["fiona[calc,s3,test]"] calc = ["pyparsing", "shapely"] s3 = ["boto3>=1.3.1"] test = [ "aiohttp", "fsspec", "fiona[s3]", "pytest>=7", "pytest-cov", "pytz", ] [project.scripts] fio = "fiona.fio.main:main_group" [project.urls] Documentation = "https://fiona.readthedocs.io/" Repository = "https://github.com/Toblerity/Fiona" [tool.setuptools] include-package-data = false [tool.setuptools.dynamic] version = {attr = "fiona.__version__"} readme = {file = ["README.rst", "CHANGES.txt", "CREDITS.txt"]} [tool.setuptools.packages.find] include = ["fiona", "fiona.*"] Fiona-1.10.1/pytest.ini000066400000000000000000000013601467206072700146720ustar00rootroot00000000000000[pytest] filterwarnings = error ignore:.*Sequential read of iterator was interrupted*:RuntimeWarning ignore:.*negative slices or start values other than zero may be slow*:RuntimeWarning ignore:.*negative step size may be slow*:RuntimeWarning ignore:.*is buggy and will be removed in Fiona 2.0.* ignore:.*unclosed =1.3.1 coverage~=6.5 cython>=3 fsspec pyparsing pytest~=7.2 pytest-cov~=4.0 pytz==2022.6 requests setuptools shapely wheel Fiona-1.10.1/requirements.txt000066400000000000000000000001451467206072700161250ustar00rootroot00000000000000attrs>=19.2.0 click~=8.0 click-plugins cligj>=0.5.0 importlib-metadata;python_version<"3.10" certifi Fiona-1.10.1/scripts/000077500000000000000000000000001467206072700143305ustar00rootroot00000000000000Fiona-1.10.1/scripts/check_deprecated.py000066400000000000000000000031041467206072700201350ustar00rootroot00000000000000import glob import os from collections import defaultdict import re ignored_files = {'_shim.pyx', '_shim1.pyx', '_shim1.pxd', 'ogrext1.pxd'} # List of deprecated methods from https://gdal.org/doxygen/deprecated.html#_deprecated000028 deprecated = { 'CPL_LSBINT16PTR', 'CPL_LSBINT32PTR(x)', 'OGR_Dr_CopyDataSource', 'OGR_Dr_CreateDataSource', 'OGR_Dr_DeleteDataSource', 'OGR_Dr_Open', 'OGR_Dr_TestCapability', 'OGR_DS_CopyLayer', 'OGR_DS_CreateLayer', 'OGR_DS_DeleteLayer', 'OGR_DS_Destroy', 'OGR_DS_ExecuteSQL', 'OGR_DS_GetDriver', 'OGR_DS_GetLayer', 'OGR_DS_GetLayerByName', 'OGR_DS_GetLayerCount', 'OGR_DS_GetName', 'OGR_DS_ReleaseResultSet', 'OGR_DS_TestCapability', 'OGR_G_GetCoordinateDimension', 'OGR_G_SetCoordinateDimension', 'OGRGetDriver', 'OGRGetDriverByName', 'OGRGetDriverCount', 'OGROpen', 'OGROpenShared', 'OGRRegisterAll', 'OGRReleaseDataSource', } found_lines = defaultdict(list) files = glob.glob('fiona/*.pyx') + glob.glob('fiona/*.pxd') for path in files: if os.path.basename(path) in ignored_files: continue with open(path) as f: for i, line in enumerate(f): for deprecated_method in deprecated: match = re.search(fr'{deprecated_method}\s*\(', line) if match: found_lines[path].append((i+1, line.strip(), deprecated_method)) for path in sorted(found_lines): print(path) for line_nr, line, method in found_lines[path]: print(f"\t{line_nr}\t{line}") print("") Fiona-1.10.1/scripts/check_urls.py000066400000000000000000000017471467206072700170350ustar00rootroot00000000000000import requests import glob import re def test_urls(files): headers = {'User-Agent': 'Mozilla/5.0 (compatible; MSIE 6.0; Fiona CI check)'} for fpath in files: print(f"Processing: {fpath}") with open(fpath) as f: text = f.read() urls = re.findall('(https?:\\/\\/[^\\s`>\'"()]+)', text) for url in urls: http_code = None try: r = requests.get(url, headers=headers) http_code = r.status_code warn = '' if not http_code == 200: warn = ' <--- !!!' except Exception as e: warn = str(e) if len(warn) > 0: print(f"\t {url} HTTP code: {http_code} {warn}") print("Test URLs in documentation") test_urls(glob.glob('**/*.rst', recursive=True)) print('') print('Test URLs in code') test_urls(glob.glob('fiona/**/*.py', recursive=True)) Fiona-1.10.1/setup.py000066400000000000000000000164171467206072700143640ustar00rootroot00000000000000# Fiona build script. import logging import os import shutil import subprocess import sys from setuptools import setup from setuptools.extension import Extension # Ensure minimum version of Python is running if sys.version_info[0:2] < (3, 6): raise RuntimeError('Fiona requires Python>=3.6') # Use Cython if available. try: from Cython.Build import cythonize except ImportError: cythonize = None def check_output(cmd): return subprocess.check_output(cmd).decode('utf') def copy_data_tree(datadir, destdir): try: shutil.rmtree(destdir) except OSError: pass shutil.copytree(datadir, destdir) # Building Fiona requires options that can be obtained from GDAL's gdal-config # program or can be specified using setup arguments. The latter override the # former. # # A GDAL API version is strictly required. Without this the setup script # cannot know whether to use the GDAL version 1 or 2 source files. The GDAL # API version can be specified in 2 ways. # # 1. By the gdal-config program, optionally pointed to by GDAL_CONFIG # 2. By a GDAL_VERSION environment variable. This overrides number 1. include_dirs = [] library_dirs = [] libraries = [] extra_link_args = [] gdal_output = [None for i in range(4)] gdalversion = None language = None gdal_major_version = 0 gdal_minor_version = 0 if 'clean' not in sys.argv: try: gdal_config = os.environ.get('GDAL_CONFIG', 'gdal-config') for i, flag in enumerate( ["--cflags", "--libs", "--datadir", "--version"]): gdal_output[i] = check_output([gdal_config, flag]).strip() for item in gdal_output[0].split(): if item.startswith("-I"): include_dirs.extend(item[2:].split(":")) for item in gdal_output[1].split(): if item.startswith("-L"): library_dirs.extend(item[2:].split(":")) elif item.startswith("-l"): libraries.append(item[2:]) else: # e.g. -framework GDAL extra_link_args.append(item) gdalversion = gdal_output[3] if gdalversion: logging.info("GDAL API version obtained from gdal-config: %s", gdalversion) except Exception as e: if os.name == "nt": logging.info("Building on Windows requires extra options to" " setup.py to locate needed GDAL files.\nMore" " information is available in the README.") else: logging.warning("Failed to get options via gdal-config: %s", str(e)) # Get GDAL API version from environment variable. if 'GDAL_VERSION' in os.environ: gdalversion = os.environ['GDAL_VERSION'] logging.info("GDAL API version obtained from environment: %s", gdalversion) # Get GDAL API version from the command line if specified there. if '--gdalversion' in sys.argv: index = sys.argv.index('--gdalversion') sys.argv.pop(index) gdalversion = sys.argv.pop(index) logging.info("GDAL API version obtained from command line option: %s", gdalversion) if not gdalversion: logging.fatal("A GDAL API version must be specified. Provide a path " "to gdal-config using a GDAL_CONFIG environment " "variable or use a GDAL_VERSION environment variable.") sys.exit(1) if os.environ.get('PACKAGE_DATA'): destdir = 'fiona/gdal_data' if gdal_output[2]: logging.info(f"Copying gdal data from {gdal_output[2]}") copy_data_tree(gdal_output[2], destdir) else: # check to see if GDAL_DATA is defined gdal_data = os.environ.get('GDAL_DATA', None) if gdal_data: logging.info(f"Copying gdal data from {gdal_data}") copy_data_tree(gdal_data, destdir) # Conditionally copy PROJ DATA. projdatadir = os.environ.get('PROJ_DATA', os.environ.get('PROJ_LIB', '/usr/local/share/proj')) if os.path.exists(projdatadir): logging.info(f"Copying proj data from {projdatadir}") copy_data_tree(projdatadir, 'fiona/proj_data') if "--cython-language" in sys.argv: index = sys.argv.index("--cython-language") sys.argv.pop(index) language = sys.argv.pop(index).lower() gdal_version_parts = gdalversion.split('.') gdal_major_version = int(gdal_version_parts[0]) gdal_minor_version = int(gdal_version_parts[1]) if (gdal_major_version, gdal_minor_version) < (3, 1): raise SystemExit( "ERROR: GDAL >= 3.1 is required for fiona. " "Please upgrade GDAL." ) logging.info("GDAL version major=%r minor=%r", gdal_major_version, gdal_minor_version) compile_time_env = { "CTE_GDAL_MAJOR_VERSION": gdal_major_version, "CTE_GDAL_MINOR_VERSION": gdal_minor_version, } ext_options = dict( include_dirs=include_dirs, library_dirs=library_dirs, libraries=libraries, extra_link_args=extra_link_args, cython_compile_time_env=compile_time_env, ) # Enable coverage for cython pyx files. if os.environ.get('CYTHON_COVERAGE'): from Cython.Compiler.Options import get_directive_defaults directive_defaults = get_directive_defaults() directive_defaults['linetrace'] = True directive_defaults['binding'] = True ext_options.update(dict( define_macros=[("CYTHON_TRACE_NOGIL", "1")])) # GDAL 2.3+ requires C++11 if language == "c++": ext_options["language"] = "c++" if sys.platform != "win32": ext_options["extra_compile_args"] = ["-std=c++11"] ext_options_cpp = ext_options.copy() if sys.platform != "win32": ext_options_cpp["extra_compile_args"] = ["-std=c++11"] # Define the extension modules. ext_modules = [] if "clean" not in sys.argv: # When building from a repo, Cython is required. logging.info("MANIFEST.in found, presume a repo, cythonizing...") if not cythonize: raise SystemExit( "Cython.Build.cythonize not found. " "Cython is required to build fiona." ) ext_modules = cythonize( [ Extension("fiona._geometry", ["fiona/_geometry.pyx"], **ext_options), Extension("fiona._vsiopener", ["fiona/_vsiopener.pyx"], **ext_options), Extension("fiona.schema", ["fiona/schema.pyx"], **ext_options), Extension("fiona._transform", ["fiona/_transform.pyx"], **ext_options_cpp), Extension("fiona.crs", ["fiona/crs.pyx"], **ext_options), Extension("fiona._env", ["fiona/_env.pyx"], **ext_options), Extension("fiona._err", ["fiona/_err.pyx"], **ext_options), Extension("fiona.ogrext", ["fiona/ogrext.pyx"], **ext_options), ], compiler_directives={"language_level": "3"}, compile_time_env=compile_time_env, ) # Include these files for binary wheels fiona_package_data = ['gdal.pxi', '*.pxd'] if os.environ.get('PACKAGE_DATA'): fiona_package_data.extend([ 'gdal_data/*', 'proj_data/*', '.libs/*', '.libs/licenses/*', ]) # See pyproject.toml for project metadata setup( name="Fiona", # need by GitHub dependency graph package_data={"fiona": fiona_package_data}, ext_modules=ext_modules, ) Fiona-1.10.1/tests/000077500000000000000000000000001467206072700140035ustar00rootroot00000000000000Fiona-1.10.1/tests/__init__.py000066400000000000000000000000541467206072700161130ustar00rootroot00000000000000"""Fiona's test package. Do not delete!""" Fiona-1.10.1/tests/conftest.py000066400000000000000000000461441467206072700162130ustar00rootroot00000000000000"""pytest fixtures and automatic test data generation.""" import copy import json import os.path import shutil import tarfile import zipfile from click.testing import CliRunner import pytest import fiona from fiona.crs import CRS from fiona.env import GDALVersion from fiona.meta import extensions from fiona.model import Feature, ObjectEncoder, to_dict def pytest_collection_modifyitems(config, items): # Fiona contains some tests that depend only on GDALs behavior. # E.g. some test the driver specific access modes maintained in # fiona/drvsupport.py for different GDAL versions. # These tests can fail on exotic architectures (e.g. not returning # the exact same value) # We explicitly enable these tests on Fiona CI using pytest -m gdal # and hide these tests otherwise. markers_options = config.getoption("-m", "") if "gdal" not in markers_options: skip_gdal = pytest.mark.skip(reason="use '-m gdal' to run GDAL related tests.") for item in items: gdal_marker = item.get_closest_marker("gdal") if gdal_marker is not None and gdal_marker.name == "gdal": item.add_marker(skip_gdal) def pytest_report_header(config): headers = [] # gdal version number gdal_release_name = fiona.get_gdal_release_name() headers.append( f"GDAL: {gdal_release_name} ({fiona.get_gdal_version_num()})" ) supported_drivers = ", ".join( sorted(list(fiona.drvsupport.supported_drivers.keys())) ) # supported drivers headers.append(f"Supported drivers: {supported_drivers}") return "\n".join(headers) def get_temp_filename(driver): """Create a temporary file name with driver extension if required.""" basename = "foo" exts = extensions(driver) if exts is None or len(exts) == 0: ext = "" else: ext = f".{exts[0]}" return f"{basename}{ext}" _COUTWILDRNP_FILES = [ "coutwildrnp.shp", "coutwildrnp.shx", "coutwildrnp.dbf", "coutwildrnp.prj", ] def _read_file(name): with open(os.path.join(os.path.dirname(__file__), name)) as f: return f.read() has_gpkg = "GPKG" in fiona.drvsupport.supported_drivers.keys() has_gpkg_reason = "Requires geopackage driver" requires_gpkg = pytest.mark.skipif(not has_gpkg, reason=has_gpkg_reason) @pytest.fixture(scope="function") def gdalenv(request): import fiona.env def fin(): if fiona.env.local._env: fiona.env.delenv() fiona.env.local._env = None request.addfinalizer(fin) @pytest.fixture(scope="session") def data_dir(): """Absolute file path to the directory containing test datasets.""" return os.path.abspath(os.path.join(os.path.dirname(__file__), "data")) @pytest.fixture(scope="function") def data(tmpdir, data_dir): """A temporary directory containing a copy of the files in data.""" for filename in _COUTWILDRNP_FILES: shutil.copy(os.path.join(data_dir, filename), str(tmpdir)) return tmpdir @pytest.fixture(scope="session") def path_curves_line_csv(data_dir): """Path to ```curves_line.csv``""" return os.path.join(data_dir, "curves_line.csv") @pytest.fixture(scope="session") def path_multicurve_gml(data_dir): """Path to ```multicurve.gml``""" return os.path.join(data_dir, "multicurve.gml") @pytest.fixture(scope="session") def path_test_tin_shp(data_dir): """Path to ```test_tin.shp``""" return os.path.join(data_dir, "test_tin.shp") @pytest.fixture(scope="session") def path_test_tin_csv(data_dir): """Path to ```test_tin.csv``""" return os.path.join(data_dir, "test_tin.csv") @pytest.fixture(scope="session") def path_coutwildrnp_shp(data_dir): """Path to ```coutwildrnp.shp``""" return os.path.join(data_dir, "coutwildrnp.shp") @pytest.fixture(scope="session") def path_coutwildrnp_zip(data_dir): """Creates ``coutwildrnp.zip`` if it does not exist and returns the absolute file path.""" path = os.path.join(data_dir, "coutwildrnp.zip") if not os.path.exists(path): with zipfile.ZipFile(path, "w") as zip: for filename in _COUTWILDRNP_FILES: zip.write(os.path.join(data_dir, filename), filename) return path @pytest.fixture(scope="session") def path_grenada_geojson(data_dir): """Path to ```grenada.geojson```""" return os.path.join(data_dir, "grenada.geojson") @pytest.fixture(scope="session") def bytes_coutwildrnp_zip(path_coutwildrnp_zip): """The zip file's bytes""" with open(path_coutwildrnp_zip, "rb") as src: return src.read() @pytest.fixture(scope="session") def path_coutwildrnp_tar(data_dir): """Creates ``coutwildrnp.tar`` if it does not exist and returns the absolute file path.""" path = os.path.join(data_dir, "coutwildrnp.tar") if not os.path.exists(path): with tarfile.open(path, "w") as tar: for filename in _COUTWILDRNP_FILES: tar.add( os.path.join(data_dir, filename), arcname=os.path.join("testing", filename), ) return path @pytest.fixture(scope="session") def path_coutwildrnp_json(data_dir): """Creates ``coutwildrnp.json`` if it does not exist and returns the absolute file path.""" path = os.path.join(data_dir, "coutwildrnp.json") if not os.path.exists(path): name = _COUTWILDRNP_FILES[0] with fiona.open(os.path.join(data_dir, name), "r") as source: features = [feat for feat in source] my_layer = {"type": "FeatureCollection", "features": features} with open(path, "w") as f: f.write(json.dumps(my_layer, cls=ObjectEncoder)) return path @pytest.fixture(scope="session") def bytes_grenada_geojson(path_grenada_geojson): """The geojson as bytes.""" with open(path_grenada_geojson, "rb") as src: return src.read() @pytest.fixture(scope="session") def path_testopenfilegdb_zip(data_dir): """Creates .gdb.zip file.""" return os.path.join(data_dir, "testopenfilegdb.gdb.zip") @pytest.fixture(scope="session") def bytes_testopenfilegdb_zip(path_testopenfilegdb_zip): """.gdb.zip bytes.""" with open(path_testopenfilegdb_zip, "rb") as f: return f.read() @pytest.fixture(scope="session") def path_coutwildrnp_gpkg(data_dir): """Creates ``coutwildrnp.gpkg`` if it does not exist and returns the absolute file path.""" if not has_gpkg: raise RuntimeError("GDAL has not been compiled with GPKG support") path = os.path.join(data_dir, "coutwildrnp.gpkg") if not os.path.exists(path): filename_shp = _COUTWILDRNP_FILES[0] path_shp = os.path.join(data_dir, filename_shp) with fiona.open(path_shp, "r") as src: meta = copy.deepcopy(src.meta) meta["driver"] = "GPKG" with fiona.open(path, "w", **meta) as dst: dst.writerecords(src) return path @pytest.fixture(scope="session") def path_gpx(data_dir): return os.path.join(data_dir, "test_gpx.gpx") @pytest.fixture(scope="session") def feature_collection(): """GeoJSON feature collection on a single line.""" return _read_file(os.path.join("data", "collection.txt")) @pytest.fixture(scope="session") def feature_collection_pp(): """Same as above but with pretty-print styling applied.""" return _read_file(os.path.join("data", "collection-pp.txt")) @pytest.fixture(scope="session") def feature_seq(): """One feature per line.""" return _read_file(os.path.join("data", "sequence.txt")) @pytest.fixture(scope="session") def feature_seq_pp_rs(): """Same as above but each feature has pretty-print styling""" return _read_file(os.path.join("data", "sequence-pp.txt")) @pytest.fixture(scope="session") def runner(): """Returns a ```click.testing.CliRunner()`` instance.""" return CliRunner() @pytest.fixture(scope="class") def uttc_path_coutwildrnp_zip(path_coutwildrnp_zip, request): """Make the ``path_coutwildrnp_zip`` fixture work with a ``unittest.TestCase()``. ``uttc`` stands for unittest test case.""" request.cls.path_coutwildrnp_zip = path_coutwildrnp_zip @pytest.fixture(scope="class") def uttc_path_coutwildrnp_tar(path_coutwildrnp_tar, request): """Make the ``path_coutwildrnp_tar`` fixture work with a ``unittest.TestCase()``. ``uttc`` stands for unittest test case.""" request.cls.path_coutwildrnp_tar = path_coutwildrnp_tar @pytest.fixture(scope="class") def uttc_path_coutwildrnp_json(path_coutwildrnp_json, request): """Make the ``path_coutwildrnp_json`` fixture work with a ``unittest.TestCase()``. ``uttc`` stands for unittest test case.""" request.cls.path_coutwildrnp_json = path_coutwildrnp_json @pytest.fixture(scope="class") def uttc_data_dir(data_dir, request): """Make the ``data_dir`` fixture work with a ``unittest.TestCase()``. ``uttc`` stands for unittest test case.""" request.cls.data_dir = data_dir @pytest.fixture(scope="class") def uttc_path_gpx(path_gpx, request): """Make the ``path_gpx`` fixture work with a ``unittest.TestCase()``. ``uttc`` stands for unittest test case.""" request.cls.path_gpx = path_gpx # GDAL 2.3.x silently converts ESRI WKT to OGC WKT # The regular expression below will match against either WGS84PATTERN = ( r'GEOGCS\["(?:GCS_WGS_1984|WGS 84)",DATUM\["WGS_1984",SPHEROID\["WGS[_ ]84"' ) # Define helpers to skip tests based on GDAL version gdal_version = GDALVersion.runtime() requires_only_gdal1 = pytest.mark.skipif( gdal_version.major != 1, reason="Only relevant for GDAL 1.x" ) requires_gdal2 = pytest.mark.skipif( not gdal_version.major >= 2, reason="Requires at least GDAL 2.x" ) requires_gdal21 = pytest.mark.skipif( not gdal_version.at_least("2.1"), reason="Requires at least GDAL 2.1.x" ) requires_gdal22 = pytest.mark.skipif( not gdal_version.at_least("2.2"), reason="Requires at least GDAL 2.2.x" ) requires_gdal23 = pytest.mark.skipif( not gdal_version.at_least("2.3"), reason="Requires at least GDAL 2.3.x" ) requires_gdal24 = pytest.mark.skipif( not gdal_version.at_least("2.4"), reason="Requires at least GDAL 2.4.x" ) requires_gdal_lt_3 = pytest.mark.skipif( not gdal_version.major < 3, reason="Requires at least GDAL < 3" ) requires_gdal3 = pytest.mark.skipif( not gdal_version.major >= 3, reason="Requires at least GDAL 3.x" ) requires_gdal31 = pytest.mark.skipif( not gdal_version.at_least("3.1"), reason="Requires at least GDAL 3.1.x" ) requires_gdal33 = pytest.mark.skipif( not gdal_version.at_least("3.3"), reason="Requires at least GDAL 3.3.0" ) travis_only = pytest.mark.skipif( not os.getenv("TRAVIS", "false") == "true", reason="Requires travis CI environment" ) travis_only = pytest.mark.skipif( not os.getenv("TRAVIS", "false") == "true", reason="Requires travis CI environment" ) @pytest.fixture(scope="class") def unittest_data_dir(data_dir, request): """Makes data_dir available to unittest tests""" request.cls.data_dir = data_dir @pytest.fixture(scope="class") def unittest_path_coutwildrnp_shp(path_coutwildrnp_shp, request): """Makes shapefile path available to unittest tests""" request.cls.path_coutwildrnp_shp = path_coutwildrnp_shp @pytest.fixture() def testdata_generator(): """Helper function to create test data sets for ideally all supported drivers""" def get_schema(driver): special_schemas = { "CSV": {"geometry": None, "properties": {"position": "int"}}, "BNA": {"geometry": "Point", "properties": {}}, "DXF": { "properties": { "Layer": "str", "SubClasses": "str", "Linetype": "str", "EntityHandle": "str", "Text": "str", }, "geometry": "Point", }, "GPX": { "geometry": "Point", "properties": {"ele": "float", "time": "datetime"}, }, "GPSTrackMaker": {"properties": {}, "geometry": "Point"}, "DGN": {"properties": {}, "geometry": "LineString"}, "MapInfo File": { "geometry": "Point", "properties": {"position": "str"}, }, } return special_schemas.get( driver, {"geometry": "Point", "properties": {"position": "int"}}, ) def get_crs(driver): special_crs = {"MapInfo File": CRS.from_epsg(4326)} return special_crs.get(driver, None) def get_records(driver, range): special_records1 = { "CSV": [ Feature.from_dict(**{"geometry": None, "properties": {"position": i}}) for i in range ], "BNA": [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, float(i))}, "properties": {}, } ) for i in range ], "DXF": [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, float(i))}, "properties": { "Layer": "0", "SubClasses": "AcDbEntity:AcDbPoint", "Linetype": None, "EntityHandle": str(i + 20000), "Text": None, }, } ) for i in range ], "GPX": [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, float(i))}, "properties": {"ele": 0.0, "time": "2020-03-24T16:08:40+00:00"}, } ) for i in range ], "GPSTrackMaker": [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, float(i))}, "properties": {}, } ) for i in range ], "DGN": [ Feature.from_dict( **{ "geometry": { "type": "LineString", "coordinates": [(float(i), 0.0), (0.0, 0.0)], }, "properties": {}, } ) for i in range ], "MapInfo File": [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, float(i))}, "properties": {"position": str(i)}, } ) for i in range ], "PCIDSK": [ Feature.from_dict( **{ "geometry": { "type": "Point", "coordinates": (0.0, float(i), 0.0), }, "properties": {"position": i}, } ) for i in range ], } return special_records1.get( driver, [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, float(i))}, "properties": {"position": i}, } ) for i in range ], ) def get_records2(driver, range): special_records2 = { "DGN": [ Feature.from_dict( **{ "geometry": { "type": "LineString", "coordinates": [(float(i), 0.0), (0.0, 0.0)], }, # TODO py39: use PEP 584 to union two dict objs "properties": dict( [ ("Type", 4), ("Level", 0), ("GraphicGroup", 0), ("ColorIndex", 0), ("Weight", 0), ("Style", 0), ("EntityNum", None), ("MSLink", None), ("Text", None), ] + ( [("ULink", None)] if gdal_version.at_least("3.3") else [] ) ), } ) for i in range ], } return special_records2.get(driver, get_records(driver, range)) def get_create_kwargs(driver): kwargs = {"FlatGeobuf": {"SPATIAL_INDEX": False}} return kwargs.get(driver, {}) def test_equal(driver, val_in, val_out): assert val_in["geometry"] == to_dict(val_out["geometry"]) for key in val_in["properties"]: if key in val_out["properties"]: if driver == "FileGDB" and isinstance(val_in["properties"][key], int): assert str(val_in["properties"][key]) == str( int(val_out["properties"][key]) ) else: assert str(val_in["properties"][key]) == str( val_out["properties"][key] ) def _testdata_generator(driver, range1, range2): """Generate test data and helper methods for a specific driver. Each set of generated set of records contains the position specified with range. These positions are either encoded as field or in the geometry of the record, depending of the driver characteristics. Parameters ---------- driver : str Name of drive to generate tests for range1 : list of integer Range of positions for first set of records range2 : list of integer Range of positions for second set of records Returns ------- schema A schema for the records crs A crs for the records records1 A set of records containing the positions of range1 records2 A set of records containing the positions of range2 test_equal A function that returns True if the geometry is equal between the generated records and a record and if the properties of the generated records can be found in a record. """ return ( get_schema(driver), get_crs(driver), get_records(driver, range1), get_records2(driver, range2), test_equal, ) return _testdata_generator @pytest.fixture(scope="session") def path_test_tz_geojson(data_dir): """Path to ```test_tz.geojson``""" return os.path.join(data_dir, "test_tz.geojson") Fiona-1.10.1/tests/data/000077500000000000000000000000001467206072700147145ustar00rootroot00000000000000Fiona-1.10.1/tests/data/!test.geojson000066400000000000000000000311721467206072700173260ustar00rootroot00000000000000{"features":[{"geometry":{"coordinates":[[[[-61.173214300000005,12.516654800000001],[-61.3827217,12.5301363],[-61.665747100000004,12.5966532],[-61.6661847,12.596],[-61.66814250000001,12.593],[-61.6700247,12.59],[-61.6718337,12.587],[-61.673571700000004,12.584],[-61.6752407,12.581],[-61.6768427,12.578],[-61.678379400000004,12.575000000000001],[-61.6803295,12.571],[-61.6830501,12.565000000000001],[-61.68553430000001,12.559000000000001],[-61.687063699999996,12.555000000000001],[-61.6884946,12.551],[-61.6898391,12.546999999999999],[-61.69209600000001,12.540999999999999],[-61.69413360000001,12.535],[-61.69595870000001,12.529],[-61.697577200000005,12.523],[-61.69899410000001,12.517],[-61.700213700000006,12.511],[-61.7012395,12.505],[-61.7020744,12.499],[-61.702626200000005,12.494],[-61.7033841,12.493],[-61.706211800000005,12.491],[-61.7089415,12.489],[-61.7141311,12.485000000000001],[-61.718995500000005,12.481],[-61.72356890000001,12.477],[-61.727879200000004,12.473],[-61.7319495,12.469000000000001],[-61.73579920000001,12.465000000000002],[-61.74032590000001,12.46],[-61.74373590000001,12.456000000000001],[-61.746971,12.452000000000002],[-61.7500412,12.447999999999999],[-61.75295580000001,12.443999999999999],[-61.753784499999995,12.443],[-61.756858300000005,12.44],[-61.7598054,12.437],[-61.762633400000006,12.434],[-61.76534870000001,12.431],[-61.767957200000005,12.427999999999999],[-61.7704641,12.425],[-61.7728741,12.422],[-61.775191500000005,12.419],[-61.7774201,12.416],[-61.7802595,12.412],[-61.782954800000006,12.408],[-61.78551270000001,12.404],[-61.7873446,12.401],[-61.789675900000006,12.397],[-61.7918847,12.393],[-61.79397550000001,12.389000000000001],[-61.794998400000004,12.388],[-61.79830060000001,12.386000000000001],[-61.8030062,12.383000000000001],[-61.8059936,12.381],[-61.810272399999995,12.378],[-61.8130009,12.376000000000001],[-61.815637599999995,12.374],[-61.8181882,12.372000000000002],[-61.82186339999999,12.369000000000002],[-61.8265048,12.365000000000002],[-61.830876599999996,12.361],[-61.8329692,12.359000000000002],[-61.835999,12.356000000000002],[-61.8413082,12.351],[-61.845319800000006,12.347],[-61.8464439,12.346],[-61.8501187,12.343],[-61.853625699999995,12.34],[-61.85697739999999,12.337],[-61.86122339999999,12.333],[-61.864252900000004,12.33],[-61.8671584,12.327],[-61.8699469,12.324],[-61.872645999999996,12.321],[-61.8754727,12.318],[-61.87906749999999,12.314],[-61.8833,12.309000000000001],[-61.88726319999999,12.304],[-61.88952,12.301],[-61.891690399999995,12.297999999999998],[-61.8937778,12.295],[-61.895785200000006,12.292],[-61.89771530000001,12.289],[-61.899570800000006,12.286],[-61.90251490000001,12.280999999999999],[-61.904753,12.277],[-61.9068719,12.273],[-61.908875900000005,12.269],[-61.911674299999994,12.263],[-61.9134062,12.259],[-61.9150578,12.255],[-61.9179797,12.248999999999999],[-61.920656900000004,12.242999999999999],[-61.92290190000001,12.238999999999999],[-61.925082,12.235],[-61.92666,12.232],[-61.9286637,12.227999999999998],[-61.930556100000004,12.223999999999998],[-61.9332651,12.217999999999998],[-61.936145100000005,12.212],[-61.938782200000006,12.206],[-61.943587599999994,12.193999999999999],[-61.94511500000001,12.19],[-61.9465439,12.186],[-61.9485074,12.18],[-61.95028749999999,12.174],[-61.95186999999999,12.168],[-61.9532519,12.162],[-61.95443739999999,12.156],[-61.954975999999995,12.154],[-61.9570107,12.147999999999998],[-61.9594482,12.139999999999999],[-61.961132600000006,12.133999999999999],[-61.962614,12.127999999999998],[-61.96295200000001,12.126999999999999],[-61.9668105,12.122],[-61.9704259,12.116999999999999],[-61.9738135,12.112],[-61.9769866,12.107],[-61.9799566,12.102],[-61.9827336,12.097],[-61.9853262,12.092],[-61.9882048,12.086],[-61.990875800000005,12.08],[-61.99252880000001,12.076],[-61.994819,12.07],[-61.996888999999996,12.064],[-61.99874590000001,12.058],[-62.000395600000004,12.052000000000001],[-62.0018433,12.046],[-62.0030933,12.04],[-62.003818700000004,12.036],[-62.0047472,12.03],[-62.0052609,12.026],[-62.005875200000006,12.02],[-62.0061812,12.016],[-62.0064861,12.01],[-62.0065868,12.006],[-62.006584499999995,12],[-62.006398100000006,11.994],[-62.0061714,11.99],[-62.0056768,11.984],[-62.0052436,11.98],[-62.004436999999996,11.974],[-62.003794,11.97],[-62.0026693,11.964],[-62.001811399999994,11.96],[-62.0003595,11.954],[-61.999279800000004,11.950000000000001],[-61.9974886,11.943999999999999],[-61.9961776,11.94],[-61.9940313,11.934],[-61.9924772,11.93],[-61.9908218,11.926],[-61.989062399999995,11.922],[-61.9871961,11.918],[-61.984707699999994,11.913],[-61.9825882,11.909],[-61.9803498,11.905000000000001],[-61.9773776,11.9],[-61.9748543,11.896],[-61.972195400000004,11.892000000000001],[-61.9693945,11.888],[-61.9664442,11.884],[-61.9641286,11.881],[-61.9617206,11.878],[-61.959215900000004,11.875000000000002],[-61.9557177,11.871],[-61.9520267,11.867],[-61.9496952,11.864],[-61.94728729999999,11.861],[-61.9430571,11.856000000000002],[-61.93853550000001,11.851],[-61.934690599999996,11.847],[-61.9306255,11.843],[-61.9274208,11.84],[-61.922921800000005,11.836],[-61.9193636,11.833],[-61.9156332,11.83],[-61.911715,11.827],[-61.9075906,11.824],[-61.903238,11.821],[-61.89863020000001,11.818],[-61.8937341,11.815000000000001],[-61.888507499999996,11.812000000000001],[-61.88481339999999,11.81],[-61.8789067,11.807],[-61.87468659999999,11.805000000000001],[-61.870200499999996,11.803],[-61.86540230000001,11.801],[-61.8602301,11.799],[-61.854597299999995,11.796999999999999],[-61.848375600000004,11.795],[-61.84498479999999,11.793999999999999],[-61.8413608,11.793],[-61.8374527,11.792],[-61.8331873,11.790999999999999],[-61.828452500000004,11.79],[-61.8230605,11.789],[-61.81664609999999,11.787999999999998],[-61.808274399999995,11.786999999999999],[-61.790283900000006,11.786],[-61.7840631,11.786],[-61.76607270000001,11.786999999999999],[-61.7573236,11.787999999999998],[-61.73933300000001,11.789],[-61.730961300000004,11.79],[-61.72079310000001,11.790999999999999],[-61.70280230000001,11.792],[-61.6944305,11.793],[-61.688016000000005,11.793999999999999],[-61.6826238,11.795],[-61.6732043,11.796999999999999],[-61.667812100000006,11.797999999999998],[-61.663077200000004,11.799],[-61.6588117,11.8],[-61.654903499999996,11.801],[-61.6512793,11.802000000000001],[-61.64788839999999,11.803],[-61.644693499999995,11.804],[-61.63878580000001,11.806000000000001],[-61.636033600000005,11.807],[-61.6308613,11.809000000000001],[-61.62841970000001,11.81],[-61.623784,11.812000000000001],[-61.621576700000006,11.813],[-61.6173564,11.815000000000001],[-61.6133668,11.817],[-61.60957990000001,11.819],[-61.6042318,11.822000000000001],[-61.6008621,11.824],[-61.5976324,11.826],[-61.5930244,11.829],[-61.590096,11.831],[-61.5872727,11.833],[-61.585739600000004,11.834],[-61.5816382,11.836],[-61.5758831,11.839],[-61.5705345,11.842],[-61.565532900000015,11.845],[-61.56375870000001,11.846],[-61.55785109999999,11.849],[-61.552374300000004,11.852000000000002],[-61.5472238,11.855000000000002],[-61.543925300000005,11.857000000000001],[-61.5407605,11.859000000000002],[-61.5362404,11.862000000000002],[-61.533365200000006,11.864],[-61.530591300000005,11.866000000000001],[-61.52791200000001,11.868],[-61.5240577,11.871],[-61.5215904,11.873000000000001],[-61.5180317,11.876000000000001],[-61.515748300000006,11.878],[-61.5124482,11.881],[-61.5103269,11.883000000000001],[-61.5072563,11.886000000000001],[-61.49835930000001,11.895000000000001],[-61.494617399999996,11.899000000000001],[-61.4902146,11.904],[-61.48533390000001,11.909],[-61.4816423,11.913],[-61.47729749999999,11.918],[-61.4732301,11.923],[-61.4694197,11.927999999999999],[-61.464353900000006,11.935],[-61.4615887,11.939],[-61.458328800000004,11.943999999999999],[-61.4552762,11.949],[-61.452420399999994,11.954],[-61.450271099999995,11.958],[-61.4482377,11.962000000000002],[-61.4454269,11.967],[-61.4438039,11.97],[-61.4412339,11.975000000000001],[-61.4388714,11.979000000000001],[-61.436091399999995,11.984],[-61.43451230000001,11.987],[-61.4329978,11.99],[-61.4310762,11.994],[-61.428396400000004,12],[-61.42595080000001,12.006],[-61.423730500000005,12.012],[-61.42313910000001,12.013],[-61.4211047,12.016],[-61.41851320000001,12.02],[-61.4166569,12.023],[-61.41487299999999,12.026],[-61.4131591,12.029],[-61.4109797,12.033],[-61.409422,12.036],[-61.40744449999999,12.04],[-61.405577300000004,12.043999999999999],[-61.4038171,12.047999999999998],[-61.402161,12.052000000000001],[-61.399865999999996,12.058],[-61.39845880000001,12.062000000000001],[-61.3971473,12.066],[-61.3959295,12.07],[-61.394275,12.076],[-61.393596300000006,12.078],[-61.3910564,12.081],[-61.38782199999999,12.085],[-61.3855047,12.088],[-61.382552100000005,12.092],[-61.3804362,12.095],[-61.37774039999999,12.099],[-61.3758089,12.102],[-61.3733493,12.106],[-61.37158839999999,12.109],[-61.369348200000005,12.113],[-61.367746499999996,12.116],[-61.36571180000001,12.12],[-61.3637893,12.123999999999999],[-61.3619754,12.127999999999998],[-61.360267099999994,12.132],[-61.35866139999999,12.136],[-61.35643999999999,12.142],[-61.35508039999999,12.145999999999999],[-61.3538123,12.15],[-61.3520543,12.156],[-61.3509963,12.16],[-61.35002769999999,12.164],[-61.3487397,12.17],[-61.34798939999999,12.174],[-61.34732449999999,12.177999999999999],[-61.3464859,12.184],[-61.346031399999994,12.187999999999999],[-61.345660099999996,12.192],[-61.3452579,12.197999999999999],[-61.3450925,12.202],[-61.3450091,12.206],[-61.345007599999995,12.209999999999999],[-61.345087899999996,12.213999999999999],[-61.3452502,12.217999999999998],[-61.345494599999995,12.222],[-61.34582149999999,12.225999999999999],[-61.3462313,12.229999999999999],[-61.347002499999995,12.235999999999999],[-61.34762189999999,12.239999999999998],[-61.3483264,12.243999999999998],[-61.3495448,12.25],[-61.345779300000004,12.253],[-61.3421596,12.256],[-61.339838,12.258],[-61.3364839,12.261],[-61.333273999999996,12.264],[-61.330199,12.267],[-61.3272505,12.27],[-61.324421099999995,12.273],[-61.3217043,12.276],[-61.31824699999999,12.28],[-61.3157713,12.283],[-61.3126179,12.286999999999999],[-61.30962449999999,12.290999999999999],[-61.3067826,12.295],[-61.30408469999999,12.299],[-61.301524099999995,12.303],[-61.300435,12.304],[-61.297963700000004,12.306000000000001],[-61.29439910000001,12.309000000000001],[-61.29211200000002,12.311],[-61.2888064,12.314],[-61.286681699999995,12.316],[-61.283606,12.319],[-61.280656900000004,12.322000000000001],[-61.277826999999995,12.325000000000001],[-61.27510960000001,12.328],[-61.272499,12.331],[-61.26999000000001,12.334],[-61.267577800000005,12.337],[-61.26525820000001,12.34],[-61.262302800000015,12.344],[-61.260184900000006,12.347],[-61.25748639999999,12.351],[-61.256502700000006,12.352000000000002],[-61.25251010000001,12.355000000000002],[-61.248711400000005,12.358],[-61.245090100000006,12.361],[-61.2416324,12.364],[-61.23832620000001,12.367],[-61.235161000000005,12.370000000000001],[-61.23212780000001,12.373000000000001],[-61.22827520000001,12.377],[-61.22552040000001,12.38],[-61.22287430000001,12.383000000000001],[-61.220331400000006,12.386000000000001],[-61.2170932,12.39],[-61.2147732,12.393],[-61.2118172,12.397],[-61.209698800000005,12.4],[-61.206999800000006,12.404],[-61.205066,12.407],[-61.20258810000001,12.411],[-61.2008015,12.414],[-61.199085100000005,12.417],[-61.19743690000001,12.42],[-61.19585520000001,12.423],[-61.19433810000001,12.426],[-61.19241330000001,12.43],[-61.1897291,12.436],[-61.1872793,12.442],[-61.18505530000001,12.447999999999999],[-61.1836941,12.452000000000002],[-61.1824277,12.456000000000001],[-61.18125439999999,12.46],[-61.180172500000005,12.464],[-61.1791805,12.468],[-61.178277,12.472000000000001],[-61.1770853,12.478],[-61.17603230000001,12.484],[-61.175387900000004,12.488],[-61.1745797,12.494],[-61.1741456,12.498],[-61.1737945,12.502],[-61.173526,12.506],[-61.17333980000001,12.51],[-61.17323580000001,12.514],[-61.173214300000005,12.516654800000001]]]],"type":"MultiPolygon"},"id":550727,"osm_type":"relation","type":"Feature","name":"Grenada","properties":{"flag":"http://upload.wikimedia.org/wikipedia/commons/b/bc/Flag_of_Grenada.svg","name":"Grenada","name:cs":"Grenada","name:de":"Grenada","name:en":"Grenada","name:eo":"Grenado","name:fr":"Grenade","name:fy":"Grenada","name:hr":"Grenada","name:nl":"Grenada","name:ru":"Гренада","name:sl":"Grenada","name:ta":"கிரெனடா","name:uk":"Гренада","boundary":"administrative","name:tzl":"Grenada","timezone":"America/Grenada","wikidata":"Q769","ISO3166-1":"GD","wikipedia":"en:Grenada","admin_level":"2","is_in:continent":"North America","ISO3166-1:alpha2":"GD","ISO3166-1:alpha3":"GRD","ISO3166-1:numeric":"308"}}],"type":"FeatureCollection","geocoding":{"creation_date":"2016-10-12","generator":{"author":{"name":"Mapzen"},"package":"fences-builder","version":"0.1.2"},"license":"ODbL (see http://www.openstreetmap.org/copyright)"}} Fiona-1.10.1/tests/data/LICENSE.txt000066400000000000000000000003231467206072700165350ustar00rootroot00000000000000The coutwildrnp shapefile and all .txt files are extracts from the US National Map's 1:2M scale Wilderness Area boundaries [1] and are in the public domain. [1] http://nationalmap.gov/small_scale/atlasftp.html Fiona-1.10.1/tests/data/collection-pp.txt000066400000000000000000000355011467206072700202310ustar00rootroot00000000000000{ "type": "FeatureCollection", "features": [ { "geometry": { "type": "Polygon", "coordinates": [ [ [ -111.73527526855469, 41.995094299316406 ], [ -111.65931701660156, 41.99627685546875 ], [ -111.6587142944336, 41.9921875 ], [ -111.65888977050781, 41.95676803588867 ], [ -111.67082977294922, 41.91230010986328 ], [ -111.67332458496094, 41.905494689941406 ], [ -111.67088317871094, 41.90049362182617 ], [ -111.66474914550781, 41.893211364746094 ], [ -111.6506576538086, 41.875465393066406 ], [ -111.64759826660156, 41.87091827392578 ], [ -111.64640808105469, 41.86273956298828 ], [ -111.64334869384766, 41.858192443847656 ], [ -111.63720703125, 41.85499572753906 ], [ -111.633544921875, 41.847267150878906 ], [ -111.63053894042969, 41.83409118652344 ], [ -111.6330337524414, 41.82728576660156 ], [ -111.63983154296875, 41.8227653503418 ], [ -111.6484603881836, 41.82188034057617 ], [ -111.66077423095703, 41.82327651977539 ], [ -111.6712417602539, 41.82330322265625 ], [ -111.67618560791016, 41.82013702392578 ], [ -111.68803405761719, 41.78792953491211 ], [ -111.69361114501953, 41.77931594848633 ], [ -111.70162200927734, 41.77797317504883 ], [ -111.70901489257812, 41.77663040161133 ], [ -111.71395111083984, 41.772098541259766 ], [ -111.71891784667969, 41.763031005859375 ], [ -111.72816467285156, 41.75851058959961 ], [ -111.74726104736328, 41.75537109375 ], [ -111.75650024414062, 41.752662658691406 ], [ -111.77067565917969, 41.7445182800293 ], [ -111.77064514160156, 41.75495910644531 ], [ -111.75585174560547, 41.76219940185547 ], [ -111.7330551147461, 41.766693115234375 ], [ -111.72749328613281, 41.77212905883789 ], [ -111.71883392333984, 41.7834587097168 ], [ -111.71080780029297, 41.78889083862305 ], [ -111.70340728759766, 41.79250717163086 ], [ -111.70030212402344, 41.798404693603516 ], [ -111.70210266113281, 41.8088493347168 ], [ -111.70760345458984, 41.819759368896484 ], [ -111.71312713623047, 41.82340621948242 ], [ -111.71929168701172, 41.82341766357422 ], [ -111.72545623779297, 41.8225212097168 ], [ -111.7341537475586, 41.803016662597656 ], [ -111.740966796875, 41.79213333129883 ], [ -111.74531555175781, 41.78215408325195 ], [ -111.77122497558594, 41.7658576965332 ], [ -111.77056884765625, 41.77811813354492 ], [ -111.7662582397461, 41.778106689453125 ], [ -111.76746368408203, 41.78628158569336 ], [ -111.76253509521484, 41.78627395629883 ], [ -111.76241302490234, 41.82259750366211 ], [ -111.77104187011719, 41.8221549987793 ], [ -111.77161407470703, 41.83351135253906 ], [ -111.7333755493164, 41.84524154663086 ], [ -111.73274993896484, 41.847511291503906 ], [ -111.7376708984375, 41.84979248046875 ], [ -111.77157592773438, 41.845767974853516 ], [ -111.77215576171875, 41.85802459716797 ], [ -111.75243377685547, 41.85844802856445 ], [ -111.72467803955078, 41.86384201049805 ], [ -111.71109771728516, 41.868804931640625 ], [ -111.70182037353516, 41.87604904174805 ], [ -111.69624328613281, 41.88193893432617 ], [ -111.69497680664062, 41.88874816894531 ], [ -111.70053100585938, 41.89057540893555 ], [ -111.70793151855469, 41.88923263549805 ], [ -111.72091674804688, 41.87972640991211 ], [ -111.73388671875, 41.87384796142578 ], [ -111.75301361083984, 41.86888885498047 ], [ -111.75350952148438, 41.90249252319336 ], [ -111.74364471435547, 41.90247344970703 ], [ -111.74463653564453, 41.967864990234375 ], [ -111.7119369506836, 41.96416473388672 ], [ -111.69283294677734, 41.95912551879883 ], [ -111.68911743164062, 41.96047592163086 ], [ -111.6891098022461, 41.96320343017578 ], [ -111.69341278076172, 41.96684646606445 ], [ -111.70449829101562, 41.972320556640625 ], [ -111.7341079711914, 41.97828674316406 ], [ -111.73527526855469, 41.995094299316406 ] ] ] }, "type": "Feature", "id": "0", "properties": { "PERIMETER": 1.22107, "FEATURE2": null, "NAME": "Mount Naomi Wilderness", "FEATURE1": "Wilderness", "URL": "http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Naomi", "AGBUR": "FS", "AREA": 0.0179264, "STATE_FIPS": "49", "WILDRNP020": 332, "STATE": "UT" } }, { "geometry": { "type": "Polygon", "coordinates": [ [ [ -112.00384521484375, 41.552703857421875 ], [ -112.00446319580078, 41.56586456298828 ], [ -112.0112075805664, 41.56586456298828 ], [ -112.01121520996094, 41.57902526855469 ], [ -112.01734924316406, 41.57902526855469 ], [ -112.0173568725586, 41.594459533691406 ], [ -112.02779388427734, 41.5940055847168 ], [ -112.02779388427734, 41.60171890258789 ], [ -112.03945922851562, 41.60126495361328 ], [ -112.04007720947266, 41.608524322509766 ], [ -112.04744720458984, 41.608524322509766 ], [ -112.0474624633789, 41.62804412841797 ], [ -112.05974578857422, 41.62758255004883 ], [ -112.05975341796875, 41.640296936035156 ], [ -112.050537109375, 41.64030075073242 ], [ -112.05054473876953, 41.64983367919922 ], [ -112.04132843017578, 41.64983367919922 ], [ -112.04195404052734, 41.66299819946289 ], [ -112.05793762207031, 41.662540435791016 ], [ -112.0579605102539, 41.692047119140625 ], [ -112.07394409179688, 41.692039489746094 ], [ -112.07459259033203, 41.72381591796875 ], [ -112.06167602539062, 41.72382354736328 ], [ -112.0616683959961, 41.71383285522461 ], [ -112.05490112304688, 41.713836669921875 ], [ -112.04137420654297, 41.71384048461914 ], [ -112.04138946533203, 41.7379035949707 ], [ -112.0376968383789, 41.74108123779297 ], [ -112.03339385986328, 41.741085052490234 ], [ -112.02908325195312, 41.729736328125 ], [ -112.02599334716797, 41.71657180786133 ], [ -112.0241470336914, 41.71157455444336 ], [ -112.0272216796875, 41.704769134521484 ], [ -112.02413940429688, 41.70068359375 ], [ -112.01676177978516, 41.69977951049805 ], [ -112.01615142822266, 41.7070426940918 ], [ -112.00508117675781, 41.707496643066406 ], [ -112.00508117675781, 41.66618347167969 ], [ -111.9792709350586, 41.6666374206543 ], [ -111.9786605834961, 41.653926849365234 ], [ -111.96821594238281, 41.65346908569336 ], [ -111.96760559082031, 41.6407585144043 ], [ -111.96146392822266, 41.6407585144043 ], [ -111.96025085449219, 41.61125183105469 ], [ -111.95042419433594, 41.61124801635742 ], [ -111.94796752929688, 41.60988235473633 ], [ -111.94735717773438, 41.60761260986328 ], [ -111.9522705078125, 41.60443878173828 ], [ -111.96455383300781, 41.60262680053711 ], [ -111.9682388305664, 41.60398864746094 ], [ -111.9725341796875, 41.60807418823242 ], [ -111.97560119628906, 41.60943603515625 ], [ -111.97928619384766, 41.61034393310547 ], [ -111.98542785644531, 41.609439849853516 ], [ -111.98481750488281, 41.58356475830078 ], [ -111.97868347167969, 41.58356857299805 ], [ -111.97745513916016, 41.570404052734375 ], [ -111.97132110595703, 41.57085418701172 ], [ -111.97132110595703, 41.56450271606445 ], [ -111.98297882080078, 41.564048767089844 ], [ -111.98175811767578, 41.54090118408203 ], [ -111.98176574707031, 41.53545379638672 ], [ -112.00323486328125, 41.53545379638672 ], [ -112.00384521484375, 41.552703857421875 ] ] ] }, "type": "Feature", "id": "1", "properties": { "PERIMETER": 0.755827, "FEATURE2": null, "NAME": "Wellsville Mountain Wilderness", "FEATURE1": "Wilderness", "URL": "http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Wellsville%20Mountain", "AGBUR": "FS", "AREA": 0.0104441, "STATE_FIPS": "49", "WILDRNP020": 336, "STATE": "UT" } } ] } Fiona-1.10.1/tests/data/collection.txt000066400000000000000000000154051467206072700176150ustar00rootroot00000000000000{"type": "FeatureCollection", "features": [{"geometry": {"type": "Polygon", "coordinates": [[[-111.73527526855469, 41.995094299316406], [-111.65931701660156, 41.99627685546875], [-111.6587142944336, 41.9921875], [-111.65888977050781, 41.95676803588867], [-111.67082977294922, 41.91230010986328], [-111.67332458496094, 41.905494689941406], [-111.67088317871094, 41.90049362182617], [-111.66474914550781, 41.893211364746094], [-111.6506576538086, 41.875465393066406], [-111.64759826660156, 41.87091827392578], [-111.64640808105469, 41.86273956298828], [-111.64334869384766, 41.858192443847656], [-111.63720703125, 41.85499572753906], [-111.633544921875, 41.847267150878906], [-111.63053894042969, 41.83409118652344], [-111.6330337524414, 41.82728576660156], [-111.63983154296875, 41.8227653503418], [-111.6484603881836, 41.82188034057617], [-111.66077423095703, 41.82327651977539], [-111.6712417602539, 41.82330322265625], [-111.67618560791016, 41.82013702392578], [-111.68803405761719, 41.78792953491211], [-111.69361114501953, 41.77931594848633], [-111.70162200927734, 41.77797317504883], [-111.70901489257812, 41.77663040161133], [-111.71395111083984, 41.772098541259766], [-111.71891784667969, 41.763031005859375], [-111.72816467285156, 41.75851058959961], [-111.74726104736328, 41.75537109375], [-111.75650024414062, 41.752662658691406], [-111.77067565917969, 41.7445182800293], [-111.77064514160156, 41.75495910644531], [-111.75585174560547, 41.76219940185547], [-111.7330551147461, 41.766693115234375], [-111.72749328613281, 41.77212905883789], [-111.71883392333984, 41.7834587097168], [-111.71080780029297, 41.78889083862305], [-111.70340728759766, 41.79250717163086], [-111.70030212402344, 41.798404693603516], [-111.70210266113281, 41.8088493347168], [-111.70760345458984, 41.819759368896484], [-111.71312713623047, 41.82340621948242], [-111.71929168701172, 41.82341766357422], [-111.72545623779297, 41.8225212097168], [-111.7341537475586, 41.803016662597656], [-111.740966796875, 41.79213333129883], [-111.74531555175781, 41.78215408325195], [-111.77122497558594, 41.7658576965332], [-111.77056884765625, 41.77811813354492], [-111.7662582397461, 41.778106689453125], [-111.76746368408203, 41.78628158569336], [-111.76253509521484, 41.78627395629883], [-111.76241302490234, 41.82259750366211], [-111.77104187011719, 41.8221549987793], [-111.77161407470703, 41.83351135253906], [-111.7333755493164, 41.84524154663086], [-111.73274993896484, 41.847511291503906], [-111.7376708984375, 41.84979248046875], [-111.77157592773438, 41.845767974853516], [-111.77215576171875, 41.85802459716797], [-111.75243377685547, 41.85844802856445], [-111.72467803955078, 41.86384201049805], [-111.71109771728516, 41.868804931640625], [-111.70182037353516, 41.87604904174805], [-111.69624328613281, 41.88193893432617], [-111.69497680664062, 41.88874816894531], [-111.70053100585938, 41.89057540893555], [-111.70793151855469, 41.88923263549805], [-111.72091674804688, 41.87972640991211], [-111.73388671875, 41.87384796142578], [-111.75301361083984, 41.86888885498047], [-111.75350952148438, 41.90249252319336], [-111.74364471435547, 41.90247344970703], [-111.74463653564453, 41.967864990234375], [-111.7119369506836, 41.96416473388672], [-111.69283294677734, 41.95912551879883], [-111.68911743164062, 41.96047592163086], [-111.6891098022461, 41.96320343017578], [-111.69341278076172, 41.96684646606445], [-111.70449829101562, 41.972320556640625], [-111.7341079711914, 41.97828674316406], [-111.73527526855469, 41.995094299316406]]]}, "type": "Feature", "id": "0", "properties": {"PERIMETER": 1.22107, "FEATURE2": null, "NAME": "Mount Naomi Wilderness", "FEATURE1": "Wilderness", "URL": "http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Naomi", "AGBUR": "FS", "AREA": 0.0179264, "STATE_FIPS": "49", "WILDRNP020": 332, "STATE": "UT"}}, {"geometry": {"type": "Polygon", "coordinates": [[[-112.00384521484375, 41.552703857421875], [-112.00446319580078, 41.56586456298828], [-112.0112075805664, 41.56586456298828], [-112.01121520996094, 41.57902526855469], [-112.01734924316406, 41.57902526855469], [-112.0173568725586, 41.594459533691406], [-112.02779388427734, 41.5940055847168], [-112.02779388427734, 41.60171890258789], [-112.03945922851562, 41.60126495361328], [-112.04007720947266, 41.608524322509766], [-112.04744720458984, 41.608524322509766], [-112.0474624633789, 41.62804412841797], [-112.05974578857422, 41.62758255004883], [-112.05975341796875, 41.640296936035156], [-112.050537109375, 41.64030075073242], [-112.05054473876953, 41.64983367919922], [-112.04132843017578, 41.64983367919922], [-112.04195404052734, 41.66299819946289], [-112.05793762207031, 41.662540435791016], [-112.0579605102539, 41.692047119140625], [-112.07394409179688, 41.692039489746094], [-112.07459259033203, 41.72381591796875], [-112.06167602539062, 41.72382354736328], [-112.0616683959961, 41.71383285522461], [-112.05490112304688, 41.713836669921875], [-112.04137420654297, 41.71384048461914], [-112.04138946533203, 41.7379035949707], [-112.0376968383789, 41.74108123779297], [-112.03339385986328, 41.741085052490234], [-112.02908325195312, 41.729736328125], [-112.02599334716797, 41.71657180786133], [-112.0241470336914, 41.71157455444336], [-112.0272216796875, 41.704769134521484], [-112.02413940429688, 41.70068359375], [-112.01676177978516, 41.69977951049805], [-112.01615142822266, 41.7070426940918], [-112.00508117675781, 41.707496643066406], [-112.00508117675781, 41.66618347167969], [-111.9792709350586, 41.6666374206543], [-111.9786605834961, 41.653926849365234], [-111.96821594238281, 41.65346908569336], [-111.96760559082031, 41.6407585144043], [-111.96146392822266, 41.6407585144043], [-111.96025085449219, 41.61125183105469], [-111.95042419433594, 41.61124801635742], [-111.94796752929688, 41.60988235473633], [-111.94735717773438, 41.60761260986328], [-111.9522705078125, 41.60443878173828], [-111.96455383300781, 41.60262680053711], [-111.9682388305664, 41.60398864746094], [-111.9725341796875, 41.60807418823242], [-111.97560119628906, 41.60943603515625], [-111.97928619384766, 41.61034393310547], [-111.98542785644531, 41.609439849853516], [-111.98481750488281, 41.58356475830078], [-111.97868347167969, 41.58356857299805], [-111.97745513916016, 41.570404052734375], [-111.97132110595703, 41.57085418701172], [-111.97132110595703, 41.56450271606445], [-111.98297882080078, 41.564048767089844], [-111.98175811767578, 41.54090118408203], [-111.98176574707031, 41.53545379638672], [-112.00323486328125, 41.53545379638672], [-112.00384521484375, 41.552703857421875]]]}, "type": "Feature", "id": "1", "properties": {"PERIMETER": 0.755827, "FEATURE2": null, "NAME": "Wellsville Mountain Wilderness", "FEATURE1": "Wilderness", "URL": "http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Wellsville%20Mountain", "AGBUR": "FS", "AREA": 0.0104441, "STATE_FIPS": "49", "WILDRNP020": 336, "STATE": "UT"}}]}Fiona-1.10.1/tests/data/coutwildrnp.cpg000066400000000000000000000000131467206072700177530ustar00rootroot00000000000000ISO-8859-1 Fiona-1.10.1/tests/data/coutwildrnp.dbf000066400000000000000000001243411467206072700177500ustar00rootroot00000000000000_CaPERIMETERNFEATURE2CPNAMECPFEATURE1CPURLCeAGBURCPAREANSTATE_FIPSCPWILDRNP020N STATECP 1.221070000000000 Mount Naomi Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Naomi FS 0.01792640000000049 332UT 0.755827000000000 Wellsville Mountain Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Wellsville%20Mountain FS 0.01044410000000049 336UT 1.708510000000000 Mount Zirkel Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Zirkel FS 0.07149550000000008 357CO 2.232410000000000 High Uintas Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=High%20Uintas FS 0.18291900000000049 358UT 1.054580000000000 Rawah Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Rawah FS 0.03373710000000008 359CO 0.418340000000000 Mount Olympus Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Olympus FS 0.00633137000000049 364UT 1.760390000000000 Comanche Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Comanche%20Peak FS 0.03197700000000008 365CO 0.462863000000000 Cache La Poudre Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Cache%20La%20Poudre FS 0.00481977000000008 366CO 0.315219000000000 Twin Peaks Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Twin%20Peaks FS 0.00477962000000049 367UT 0.329520000000000 Neota Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Neota FS 0.00576742000000008 369CO 0.518395000000000 Lone Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Lone%20Peak FS 0.01251300000000049 371UT 0.477348000000000 Deseret Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Deseret%20Peak FS 0.01077180000000049 373UT 0.675146000000000 Never Summer Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Never%20Summer FS 0.00908863000000008 374CO 0.288683000000000 Mount Timpanogos Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Timpanogos FS 0.00442921000000049 375UT 0.768802000000000 Sarvis Creek Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sarvis%20Creek FS 0.01957160000000008 376CO 1.372940000000000 Indian Peaks Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Indian%20Peaks FS 0.03140190000000008 378CO 2.029470000000000 Flat Tops Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Flat%20Tops FS 0.10322100000000008 380CO 0.765491000000000 James Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=James%20Peak FS 0.00676706000000008 384CO 0.726088000000000 Mount Nebo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Nebo FS 0.01203290000000049 385UT 0.376165000000000 Byers Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Byers%20Peak FS 0.00345459000000008 386CO 0.528667000000000 Vasquez Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Vasquez%20Peak FS 0.00542539000000008 387CO 1.257970000000000 Eagles Nest Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Eagles%20Nest FS 0.05923840000000008 388CO 0.522076000000000 Ptarmigan Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Ptarmigan%20Peak FS 0.00574553000000008 389CO 1.078160000000000 Mount Evans Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Evans FS 0.03254480000000008 390CO 1.438710000000000 Holy Cross Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Holy%20Cross FS 0.05451810000000008 391CO 1.463510000000000 Lost Creek Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Lost%20Creek FS 0.04967140000000008 396CO 1.063300000000000 Hunter-Fryingpan Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Hunter%2DFryingpan FS 0.03224480000000008 398CO 1.458040000000000 Maroon Bells-Snowmass Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Maroon%20Bells%2DSnowmass FS 0.07808400000000008 399CO 0.738527000000000 Mount Massive Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Massive FS 0.01047520000000008 400CO 0.193332000000000 Mount Massive Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Massive FWS 0.00093778200000008 401CO 0.820306000000000 Buffalo Peaks Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Buffalo%20Peaks FS 0.01711430000000008 404CO 2.025460000000000 Collegiate Peaks Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Collegiate%20Peaks FS 0.07376710000000008 405CO 0.907013000000000 Raggeds Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Raggeds FS 0.02990640000000008 406CO 1.644000000000000 West Elk Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=West%20Elk FS 0.07623760000000008 415CO 0.538332000000000 Fossil Ridge Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Fossil%20Ridge FS 0.01317560000000008 419CO 0.826888000000000 Gunnison Gorge Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Gunnison%20Gorge BLM 0.00739996000000008 420CO 0.707377000000000 Black Canyon of the Gunnison Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Black%20Canyon%20of%20the%20GunnisonNPS 0.00663470000000008 425CO 0.735176000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.01336290000000008 427CO 0.393427000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00685532000000008 433CO 0.829067000000000 Powderhorn Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Powderhorn BLM 0.01925610000000008 438CO 0.446917000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00708528000000008 440CO 1.224290000000000 Uncompahgre Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Uncompahgre FS 0.04125950000000008 442CO 0.047141800000000 Uncompahgre Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Uncompahgre BLM 0.00013151100000008 444CO 0.349875000000000 Powderhorn Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Powderhorn FS 0.00614416000000008 446CO 0.733543000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00914042000000008 447CO 0.065853400000000 Uncompahgre Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Uncompahgre BLM 0.00026714500000008 449CO 1.616820000000000 La Garita Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=La%20Garita FS 0.05340450000000008 451CO 0.407763000000000 Mount Sneffels Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Sneffels FS 0.00702802000000008 452CO 0.196611000000000 Uncompahgre Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Uncompahgre BLM 0.00150059000000008 454CO 0.860610000000000 Box-Death Hollow Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Box%2DDeath%20Hollow FS 0.00997307000000049 455UT 0.779127000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo NPS 0.01755200000000008 458CO 0.561821000000000 Greenhorn Mountain Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Greenhorn%20Mountain FS 0.00975499000000008 461CO 0.171344000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00099257300000008 462CO 0.697435000000000 Lizard Head Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Lizard%20Head FS 0.01764510000000008 465CO 1.742490000000000 Dark Canyon Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Dark%20Canyon FS 0.02586860000000049 468UT 0.574187000000000 Great Sand Dunes Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Great%20Sand%20Dunes NPS 0.01359200000000008 470CO 0.133740000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00093922600000008 471CO 3.301370000000000 Weminuche Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Weminuche FS 0.19608500000000008 472CO 0.454338000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00750880000000008 479CO 0.275139000000000 Ashdown Gorge Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Ashdown%20Gorge FS 0.00342165000000049 480UT 0.336214000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00394863000000008 482CO 0.191092000000000 Weminuche Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Weminuche FS 0.00123684000000008 487CO 0.736247000000000 Pine Valley Mountain Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Pine%20Valley%20Mountain FS 0.02126900000000049 499UT 2.032130000000000 South San Juan Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=South%20San%20Juan FS 0.07043340000000008 504CO 0.263251000000000 Mesa Verde Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mesa%20Verde NPS 0.00218289000000008 509CO 0.119581000000000 Mesa Verde Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mesa%20Verde NPS 0.00053934000000008 510CO 0.120627000000000 Mesa Verde Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mesa%20Verde NPS 0.00081771100000008 511CO Fiona-1.10.1/tests/data/coutwildrnp.prj000066400000000000000000000002171467206072700200030ustar00rootroot00000000000000GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]Fiona-1.10.1/tests/data/coutwildrnp.shp000066400000000000000000003257541467206072700200220ustar00rootroot00000000000000' d\`ԈB@">ZD@k[`LD@Z[D@R[@_D@@2[D@`([D@@+[`wD@[@D@[@D@[`CD@@[TD@`[@D@@r[@zD@^[@nD@,[@D@[pD@[@sD@Z[D@[D@[`PD@`[`3D@ J[ aD@[bD@F[@D@[D@ d[D@`[D@`[hD@`[ D@[D@@[D@ [D@j[@WD@R[`LD@@R[D@_[D@`[#D@@[ D@`[`HD@}[`D@[pD@[ 2D@@[`D@`I[D@[`eD@[eD@m[`HD@`[@D@l[dD@@[D@[[D@Q[`D@` [D@ [D@`[D@`[JD@X[`[`D@@p[D@[D@@[ D@@[@D@[`D@[D@ *[D@ *[AD@ [2D@ [@Z{D@ ߵZ2|D@iZn|D@`Z`{D@ hZ|D@Z@}D@[GD@[`qD@n/[kmD@@[ nlD@[lkD@[fiD@[hD@ં[hD@@[?hD@@u[gD@`[gD@[gD@Y[@gD@R[gD@[gD@`W[@>gD@`[{gD@[$gD@v[reD@o[WbD@[`^D@![,\D@[@yZD@ [YD@[`4WD@n[}VD@ [TD@[`*SD@`[RD@@N[RD@࿕[@QD@@[ -PD@@[`wOD@8[ ND@`[@LD@@[ KD@[ND@ ל["RD@`[`QD@[PD@[QOD@)[@8ND@ [@LD@w[jLD@[LD@Š[LD@`[`LD@ [`LD@`)[ LD@ [2LD@ޡ[@KD@ף[KD@[KD@J[KD@J[LD@[LD@[!KD@][`LD@`[ gLD@[`TKD@`[LD@[ WLD@[KD@ȳ[ID@G[ GD@[GD@[ 'HD@S[`nMD@[ND@M[PD@w[8SD@ D[TD@[#TD@`¶[mTD@@3[@sVD@श[@YD@`[ZD@([\D@`8[]D@ȵ[`^D@`[``D@H[ dD@[eD@@[@TfD@[ egD@>[^gD@1[ gD@[`5hD@`Z[YD@~ZYD@@~Z:ZD@LZZD@`jZ [D@`tZc[D@`jZ`\D@`jZ@~\D@Z\D@@Z ]D@` Z]D@ ЀZ8_D@Z`D@`[Z`D@`tZ)aD@ yZAaD@ ZaD@Z bD@Z`cD@TZ (dD@ZdD@˂Z@`eD@`߂Z ?fD@@ZfD@9Z@gD@tZ@gD@Z VhD@ Z@hD@Z iD@`pZiD@ ZiD@ʅZ 1jD@Z@jD@ZjD@-ZjD@ ZjD@ZkD@Z KkD@ZlD@ Z@lD@`ZmD@ZmD@ ZlD@ZlD@ZqmD@ ZmD@`PZnD@ZnD@ŃZ@mD@`kZ@mD@@‚Z@`nD@6ZnD@`ƀZoD@VZoD@`!~ZoD@|ZoD@@|Z jD@@0|Z jD@`0|Z hD@{Z`fD@ .{Z`eD@SzZbdD@QyZDdD@`QyZ@KbD@yZ@D@]Z@=D@]ZJ=D@k]Z]Z@VD@ ]ZJWD@\Z@WD@\ZXD@9\Z}YD@[ZAYD@[ZWD@ [ZWD@[Z *WD@ `['ID@@[;PD@[6ND@n[ND@@V[,PD@ [;PD@J[@OD@ [ND@ "[@MD@>[ LD@[&LD@@[@KD@[@JD@[=JD@[pID@ ['ID@[8ID@ [@&JD@`[JD@[LD@[7ND@[6ND@  xZD@tZ`>D@3tZ >D@`tZD@ vZ>D@{vZ?D@vZ @D@ vZ`@D@@vZAD@wZ BD@@xZBD@ixZBD@ xZBD@ xZ@CD@@xZ;CD@xZ CD@xZCD@xZDD@ UxZED@wZFD@@wZGD@fwZVHD@HwZ HD@ wZID@ p@[v6D@w[FD@+@D[FD@[OFD@[`"FD@ [FD@G[FD@[FD@M[FD@@[FD@`[xFD@[ED@[`DD@[cCD@a[BD@`[@BD@[`BD@@j[BD@`[H=D@w[@;D@@[1;D@p[99D@[7D@@[7D@[@8D@ [8D@[7D@@[ 7D@ [@6D@[v6D@[ 7D@[7D@3[ 8D@(["9D@ 2[@:D@0[ =D@0[ \>D@@ [k>D@m[?D@[.?D@[@D@@[DD@@[DD@[7ED@@D[FD@ 0@+\1D@ $\`CD@#+\@D@'+\@D@+\BD@a)\MBD@@'\nBD@`K'\CD@ &\`CD@5&\`(BD@ &\ AD@&\`{?D@&\>D@&\a>D@&\@6>D@`%\@=D@ %\=D@ %\@;D@ %\)9D@%\`l7D@`%\4D@ $\`^3D@ $\2D@^%\@2D@%\22D@#&\ 2D@&\2D@`9'\2D@k(\v2D@)\1D@`y)\`4D@*\ 4D@*\@7D@@+\@7D@`+\ 9D@+\)9D@+\@D@ 0Zm'D@ wZ mZ0D@ mZD@@mZD@mZKD@ mZD@GmZD@lZD@lZD@lZ~D@mmZ mD@mZfD@NnZ F D@nZ@ D@@oZ D@oZ D@oZ D@`oZ D@oZ D@@oZ@ D@aoZ D@%oZ` D@oZ D@ %oZ D@}oZ D@oZD@oZD@UpZ`D@7qZD@ AqZD@rZD@rZD@UsZ D@qsZ`D@`sZD@rZ`<D@@rZ}D@`qZ~D@@aqZ@oD@"qZ@YD@ qZ8D@pZD@ pZ`)D@oZjD@soZ xD@oZ`D@nZD@`nZ@D@DnZ@D@ (nZpD@nZHD@ nZD@mZD@ mZ`D@mZ D@@,mZ@*D@0mZ`D@`mZ D@lZ@D@lZD@dlZ)D@kZD@jZKD@jZ`D@|jZD@iZwD@ [iZ{D@@$iZPD@@hZ@D@LhZ@D@gZD@NgZD@@*gZD@fZhD@fZ>D@fZDD@ eZ |D@eZD@ eZ@D@dZ .D@`dZD@dZD@@dZiD@@^dZ@D@@cZD@cZxD@5cZD@bZ9D@bZbD@@bZ`8D@bcZ D@cZD@`cZjD@cZD@@cZD@bZ ND@bZD@[cZD@@cZD@@dZD@4eZD@feZD@ feZhD@ReZ-D@ eZ@D@dZMD@`dZ@D@`dZ$D@dZ@ D@`dZ@ D@@dZ D@_dZw D@ cZ` D@cZ@z D@@cZ0 D@dZ D@kdZ`C D@eZ& D@WeZ` D@keZ@ D@leZ`D@eZ`.D@ eZD@fZoD@ciZD@FiZJD@ 3iZ`D@ hZD@ hZ D@ iZPD@yiZD@ iZ 7D@@iZ vD@@iZ:D@ziZ`D@ fZ D@`fZD@zdZ D@|dZ`D@dZC@ eZC@fZ`oC@VfZ`4C@fZ4C@fZsC@gZ@C@@igZ2C@gZ}C@@AhZC@@hZC@@hZ`C@ iZDC@iZC@ hZ`C@`hZ C@@hZhC@`9hZwC@!hZEC@gZ C@gZ@C@`VgZ`C@ CgZjC@gZC@ gZC@`gZC@fZC@`fZC@@fZ`xC@@fZ@gC@@fZ XC@fZ@&C@gZ C@`zgZC@@XgZ@OC@XgZ@C@XgZC@gZeC@gZ*C@2hZ@C@vhZC@hZC@hZC@iZ>C@ C@ IZC@GZC@dZC@ZC@ZC@@ZC@ZC@`Z@C@ ZqC@`fZ`RC@Z`C@\ZC@ZC@ZC@Z C@ ZC@@$Z`(C@3Z C@=Z@C@ dZ`C@mZ1C@ EZC@ZUC@`Z C@ nZ@C@ZC@@Z`#C@/Z@C@-Z FC@Z9C@@@ZC@ZC@ ZC@@ZuC@@Z C@&ZhC@MZ@JC@`ZC@@Z"C@`Z@C@;ZC@@XZC@Z`C@`Z`C@ZVC@KZC@ZyC@@`Z`C@ ZC@ Z&C@`>Z C@`Z`"C@Z C@hZ NC@@ZC@`ZpC@ ZvC@Z@C@ZC@Z@hC@jZ9C@`ZC@ZC@ZC@Z PC@`Z@C@[Z`Z`/C@Z`C@aZC@ Z7C@ Z(C@XZ@ C@tZC@ZTC@LZC@1Z C@EZ>C@ZC@ZC@Z C@ZqC@@ZC@ Z`C@Z`C@Z C@`Z`8C@@ZC@Z~C@@|ZC@`3ZKC@`ZPC@ZC@@Z9C@Z C@fZC@HZC@Z@4C@ZtC@/ZhC@Z`C@`Z@7C@Z@C@Z C@@HZC@Z C@Z`C@`OZC@ ZRC@\ZC@ #Z@qC@`8Z@C@yZtC@fZC@ZC@ZHD@Z 7D@MZsD@Z D@[ QC@ >[C@`=[@C@ F[C@[C@@@rZC@{ZC@E{Z`OC@'|ZC@S|ZC@ /|ZC@"|ZC@ |Z%C@|Z@C@@}Z`C@}Z`RC@}Z@!C@ }Z /C@}ZC@@*}Z@C@ }ZGC@@$}ZC@[}ZC@b}ZC@S}ZC@|ZC@.}Z`C@@@}ZC@ }Z`C@}ZC@Y~Z`4C@[~Z aC@}Z`C@@}Z`"C@ G~Z&C@`~ZyC@~Z`C@ \~Z@#C@]~Z AC@~Z#C@kZC@`Z C@`Z`[C@ZEC@Z`C@ Z C@\Z C@ZC@UZ`8C@`XZC@ ZC@ Z@4C@@rZ@C@Z@C@ ZC@ZC@IZ]C@`ZaC@`qZC@ Z`C@`7Z`WC@@Z C@`ZC@Z@C@ZC@`ԋZC@ZSC@^Z5C@TZC@@hZ HC@{Z}ZC@ |ZC@`|Z`C@}|ZwC@l|Z\C@`_|ZHC@ %|ZC@|ZC@&|Z`C@mZ$C@`]Z`C@`gZ/C@gZ $C@gZ@#C@gZDC@`VgZ 'C@tgZC@~gZ@fC@jgZC@`0gZC@fZhC@fZJC@fZ C@`mfZkC@`FfZ`\C@ fZ C@@eZ@C@eZ@:C@eZ@C@eZ'C@ceZ@C@OeZC@`YeZAC@@OeZ`C@`1eZ[C@dZ@C@@dZC@dZ +C@dZWC@ cZ4C@cZ`C@`bZ$C@[bZ C@faZ>C@5aZ`>C@``Z C@s`Z C@_ZBC@_ZC@`.aZ`uC@ aZ ;C@@aZ`C@bZ`C@cZC@`AcZfC@AcZC@_cZ =C@cZC@cZ C@`cZnC@ cZC@cZ bC@tcZ`5C@@0cZ4C@cZpC@bZ~C@@YbZC@aZC@4aZC@`Z C@`Z`C@`ZVC@_Z VC@/_Z UC@^ZC@`2^Z tC@R]ZC@ ]Z`C@`]Z`WC@6]ZC@@!^Z C@f^ZC@@^ZC@` _ZbC@@Q_ZC@_ZC@`_Z`C@9aZ gC@ aZC@bZ C@^bZ`9C@`bZ C@@rbZ`xC@`YbZC@bZ +C@@cZpC@dZC@IeZ`C@@eZC@`DfZ2C@ QfZ@C@@=eZ@C@zeZ`7C@ dZ,C@eZ$C@ gZIC@hZ C@^kZ`sC@zkZC@`kZ`C@`lZ`zC@@ZlZ`hC@lZC@mZC@}mZ`_C@@mZ C@`mZ@kC@mZC@mZ@C@ mZC@JmZC@mZ`MC@lZ C@mZ ;C@ImZC@pmZC@mZdC@mZ C@mZ`C@mZC@mZC@mZKC@mZC@ZmZC@dmZ&C@mZC@ mZC@mZ@C@mZC@mZ C@umZC@:mZC@@mZC@`lZC@ lZ@EC@ClZ@C@jZ@C@@jZ@oC@PjZ QC@jZ@$C@>iZ@C@hZC@]hZ`C@@hZ`C@`gZ/C@שZ¤C@~Z9C@uZ C@Z C@ZC@ ZC@tZC@`~Z`C@̚ZrC@ߚZ@EC@Z@+C@ޚZC@"ZiC@?Z C@>ZC@RZC@xZC@@ZUC@@xZ޿C@ZZwC@Z C@`Z`C@Z ˽C@`Z ڽC@ZC@ZǾC@Z kC@@Z`,C@UZC@`ZC@ZC@ :ZC@`Z C@@Z6C@~ZC@ZC@#ZԻC@Z`TC@Z ·C@Z` C@ZC@@Z@RC@(Z 4C@YZQC@Z@C@ZǴC@ZKC@ZC@8ZC@Z C@@˞ZC@Z@C@@"Z@ C@"Z@C@Z=C@ZC@qZdzC@qZ@C@Z_C@zZC@6ZC@ZUC@ZdC@`iZC@ZC@@לZC@XZC@ZdC@Z@γC@`TZ C@sZC@טZC@ZC@bZC@ ;Z >C@`'Z@ֲC@DZC@7ZC@`^Z@RC@{ZC@ {ZC@{ZFC@\Z C@@\Z?C@ZC@Z`C@Z9C@ZC@ZC@`ęZթC@@ZOC@aZ C@WZ@ΧC@ZtC@`™Z C@ ߙZ ZC@ԙZ`{C@`ޙZ?C@5ZC@@WZC@ ^Z¤C@ Z`ΤC@ZC@`iZ C@`ZC@Z C@ SZ@C@`zZ^C@`ZC@ZC@@Z)C@ZC@Z C@Z@bC@ ZC@`Z#C@ Z@@C@-ZC@ZC@ Z(C@@jZཫC@9ZC@Z@ܫC@ġZ@C@Z`ΫC@Z C@ Z C@`Z`nC@@Z@֭C@ ڡZ@C@bZC@`ZC@_ZsC@_Z༤C@`_Z@QC@_ZC@@_ZzC@`Z`C@V`ZC@ `Z@C@`ZC@`Z C@6aZ/C@aZ ԧC@PbZ`=C@`bZ@ĨC@cZC@dZݪC@ dZ@FC@(eZdC@eZC@LfZ@EC@@fZ C@gZC@SgZsC@gZ)C@gZ C@ hZC@iZIC@hjZ`*C@`rjZVC@ rjZC@jZ6C@ kZڮC@@4kZ`$C@lZC@`lZկC@tmZ 5C@`!nZC@`kZ"C@kZC@lZ C@@lZC@lZ`C@kZ;C@ {jZC@iZҸC@`hZC@fZC@dZ@C@:cZ C@aZ@^C@@|ZC@nZ1C@p{ZC@@ZwC@ZC@۟Z/C@ПZฝC@Z3C@nZC@`Z C@ZÚC@ZǙC@ZC@˟ZC@"ZC@\ZC@eZC@xZkC@٠Z }C@ Z@AC@`tZ@OC@ZC@6ZMC@`ZC@ Z`xC@ GZiC@ ZC@`Z@MC@ Z@C@௣Z3C@`Z`TC@ZC@ZC@Z`ϏC@@uZC@Z-C@`ZC@ZHC@eZ`C@ǧZ{C@ZC@`mZ@C@Z ϐC@ZːC@_ZC@ZC@mZ C@MZC@Z9C@TZC@@|Z/C@ /ZC@ZC@@Z@"C@@үZC@ZZC@+Z)C@@KZəC@@BZ]C@ͯZ|C@Z@C@Z kC@=ZC@ZÜC@ZC@DZǜC@Z &C@ =Z`C@࡫Z@sC@ Z`C@ZiC@3Z@MC@ Z@C@`Z@hC@Z@C@`RZFC@`Z@C@@ZൟC@ Z@C@ZrC@`ZOC@@ZC@Z@C@ZC@(Z{C@ Z@ӥC@Z+C@Z`C@`Z`C@@ZAC@Z@oC@:Z1C@OZ C@ZШC@ PZ`AC@ZC@)ZZ`C@ZuC@-ZC@ˆZ:C@ NZC@@ZC@ZڇC@xZC@wZyC@PZ@C@Z@C@Z`C@ZC@ Z@C@@PZC@ PZC@2Z C@ ZՀC@Z|C@Z@nC@ZǀC@@Z`C@Z+C@aZC@ZC@@eZނC@ ZC@ZC@@ZC@;Z ԀC@@̃Zl}C@`Z@h}C@DZ}C@Z m|C@ńZ{C@ Z{C@MZ"|C@Z |C@?Z|C@VZ|C@Z {C@`Z {C@ Z{C@ZzC@Z {C@DZ{C@இZzC@ ܇ZzC@@Z zC@ZH{C@@9Zw{C@ ZZ}{C@ZY{C@@ˆZ zC@`ZzC@jZyC@@߉ZyC@ RZzC@RZzC@@Z{C@Z|C@BZ`|C@@Z}C@ZIC@ Z`cC@Z@3C@ Z`C@ZوC@ZZ C@QZC@ZnC@ZmC@`ϑZ @mC@ZlC@`ZZkC@ҐZjC@ Z@jC@qZjC@]ZhC@ZphC@Z &hC@@ҒZ@nhC@vZOhC@ÓZgC@ZgC@3Z hC@Z [hC@ZhC@4Z]gC@@ؗZgC@VZ gC@ԘZ@igC@!Z hC@?ZhC@ љZiC@Z`kC@)ZkC@ĚZ@ lC@BZ`blC@ ԛZRlC@`xZ lC@@ٜZ@lC@Z@nlC@ƜZ mC@@#Z@nC@̛Z oC@`ÛZoC@@ZpC@KZqC@Z@pC@ܜZ`pC@ ZpC@ZqC@НZ@qC@'ZGrC@1ZrC@ OZasC@`ZsC@Z]tC@ԟZ uC@mZuC@@2Z `vC@BZvC@ӢZgwC@Z^yC@cZ`yC@8ZyyC@Z iyC@`Z@ zC@Z?{C@ LZ{C@ʨZ@|C@`Z@|C@fZ|C@ Z`6|C@DZ{C@êZB|C@ Z Z}C@@pZ`c~C@ Z C@ਭZ@,C@@uZC@@AZـC@Z #C@ZC@ZԁC@ fZ:C@ZC@Z9C@`Z uC@యZC@@دZ@C@Z C@ ZࢆC@Z6C@@ ZC@gC@ ZgC@`BZ `hC@@ZiC@`ZkC@ Z`kC@ZlC@Z` mC@=Z kmC@Z`mC@Z@mC@,Z lC@ZYlC@ ZlC@Z mC@@YZ mC@ZmC@`Z`2lC@`ZlC@@ZkC@ZwkC@PZLkC@ZjC@Z`pjC@@ZAjC@Z UjC@ZBkC@,ZFkC@`7ZiC@"Z iC@ ZfC@ZfC@ZeC@ZZ [eC@VZGeC@+ZeC@0Z@eC@OZ`aC@,Z^C@GZx]C@ )Z@]C@Z`]C@BZn]C@@mZc]C@Z\C@ Zj\C@Z[C@`wZ$[C@`2ZsZC@Z`YC@ZXC@@ZOXC@@ZWC@ 6Z`ZWC@`"Z@WC@`5Z`VC@Z VC@ZUC@Z TC@PZ@SC@ ZRC@QZ `QC@`PZPC@ZOC@ Z UOC@ZNC@`Z@NC@`GZNC@ZNC@Z NC@`7ZJNC@Z9NC@ Z MC@Z@MC@XZMC@&Z OC@Z7PC@Z`qPC@=Z QC@KZ"QC@`ZQC@ ZQC@QZ@(RC@ZpRC@` ZRC@kZ RC@Z lRC@Z`#QC@`Z`&OC@ ZsNC@Z@MC@Z5KC@`Z@,IC@ZGC@Z GC@ Z _GC@ZFC@Z@FC@`:ZdEC@#ZNC@ʤZaC@o Z$aC@Z`aC@ZaC@Z@aC@ |ZwaC@*ZCaC@ĦZ!aC@`$Z`C@Z`C@@ťZM_C@Zs^C@ Z]C@@Z)]C@Z\C@RZZC@ #ZYC@`+ZyYC@ PZ`XC@Z 2XC@ Z`WC@ZvWC@Z`%WC@}Z VC@`LZVC@ZVC@@ ZUC@ʤZTC@@ͤZUTC@5ZSC@Z7SC@ Z RC@@Z RC@@BZ@VSC@[Z@ TC@`ZTC@Z@TC@@ZSC@ZSC@ZZSC@#ZTSC@`ZSC@ZzSC@`ɨZPSC@ Z'SC@ZRC@@%ZRC@9ZqRC@zZRC@ZnRC@Z"RC@ZQC@@ZQC@ZQC@@ΩZQC@ߩZ@ RC@Z RC@)ZQC@EZ`QC@ZPC@ZVPC@1Z`OC@GZNC@ìZNC@Z NC@ Z NC@ԬZFOC@ڬZ@rOC@yZPC@ZhPC@Z`PC@ZPC@@vZZQC@ ZRC@Z SC@Z`SC@ZSC@ ZMTC@`ͬZ TC@@Z@OUC@LZUC@OZUC@@@ZVC@`Z@RVC@ZVC@ ZVC@Z`VC@@ZVC@`Z`VC@ ZWC@@Z$XC@Z}XC@ܬZXC@ ʬZ`mYC@ZZC@SZ ZC@7Z[C@@7Z\C@@RZ\C@SZ}]C@dZ`^C@@ZZn^C@@Z@^C@`Z^C@Z5_C@Z_C@ Z_C@}Z<`C@%Zg`C@ Z =`C@IZ[`C@ Z$aC@$ZHC@@Z`C@ZPC@@Z PC@Z@QC@ Z`RC@Z RC@@Z"QC@ZMC@ZJC@lZJC@kZJC@Z&JC@Z`IC@ LZ`IC@RZHC@@Z`HC@`ZHC@Z.IC@@ZBIC@"Z`IC@@>ZIC@mZjIC@@ZIC@Z JC@ lZ`&JC@@ZJC@@Z eKC@Z`hKC@ZKC@ Z`BLC@;ZLC@@FZgMC@@Z`MC@;Z`MC@AZNC@2Z!OC@ vZOC@@Z`OC@ 8Z PC@JZ@nPC@rZ`hPC@Z@PC@Z cQC@ OZ@QC@ ]Z@RC@`@Z@ASC@bZSC@kZTC@@*Z%UC@@/Z@|UC@Z`VC@ ZVC@@:Z`2WC@ 0Z@WC@ Z WC@ Z?XC@ZXC@ZsYC@ZYC@(ZYC@Z`YC@ZAZC@Z`"[C@ ~Z`[C@@MZ[C@XZ`$\C@`ZS]C@Z@{]C@ gZ]C@mZ]^C@7Z@^C@Z`C@Z``C@@Z`l_C@Z@6_C@Z^C@`PZ@O^C@Z]^C@Z@]C@?Z]C@@tZ?]C@Z T\C@Z \C@"Z`\C@vZ\C@Z\C@Z \C@@Z[C@@!Z[C@Z8[C@Z [C@ZZC@qZ ZC@@Z qZC@LZ`CZC@(Z@YC@ZYC@Z YC@@YZ /YC@ZXC@Z@YC@@ZYC@ZXC@ZXC@ZyXC@7ZtXC@WZ`-XC@VZWC@ Z`UWC@Z@VC@Z@{VC@Z=VC@Z@UC@ Z UC@ Z6UC@[Z>UC@^ZTC@Z TC@`Z>UC@nZUC@@ZHUC@Z`fTC@"Z@-TC@iZQSC@@rZSC@ >ZRTC@ IZTC@kZNTC@ZKTC@ZSC@ZZSC@`Z@ SC@Z@RC@@Z@RC@.ZRC@@EZRC@^Z@xRC@wZ^RC@ Z QRC@ZSRC@ZRC@`ZRC@`ZRC@Z SC@`#ZSC@@HC@Z@HC@Z HC@ZHC@Z@HC@Z HC@@ZIC@Z-IC@`ZXIC@%Z{IC@CZ wIC@jZIC@wZIC@`Z@JC@ZdJC@Z lJC@Z aJC@`Z#JC@`Z JC@@ZJC@1ZIC@@CZIC@aZ@IC@dZ}IC@sZ@kIC@Z(IC@Z`HC@Z HC@Z XHC@`HZ@NHC@bZ HC@ZHC@`ZMHC@ ZTHC@ZHC@ ZJHC@"Z HC@@*ZHC@@'Z GC@zZGC@@tZ@GC@ Z GC@Z HC@RZHC@ LZ`IC@Z`IC@Z&JC@kZJC@lZJC@ZJC@ZMC@@Z"QC@Z RC@ Z`RC@Z@QC@@Z PC@ZPC@& Z$C@@tZ Y:C@}Z3C@iZ 5C@@KZo5C@@RZ6C@ZH6C@Z |6C@Z6C@̀Z6C@Z 6C@`rZc6C@ுZ J6C@Z6C@ Z6C@ ؁ZZ7C@Z7C@ kZ 7C@ FZ 8C@&ZN8C@ Z8C@Z8C@fZ8C@MZ@K9C@ Z:C@ Z(:C@`Z Y:C@~ZL:C@~Z`9C@~Z\9C@~ZR9C@~Z8C@O~Z8C@ }Z8C@`}Z@68C@`~Zv7C@G}Z5C@}Z'6C@|Z a6C@|Z 6C@@|Z 7C@|Zc7C@ C|Z@U7C@{Z 7C@{Z 7C@{Z 6C@~{Z5C@zZ5C@zZk5C@zZJ5C@@zZ@5C@zZ4C@zZ@4C@@zZ@4C@ MzZ 4C@ HzZ3C@yZ 3C@yZ 53C@yZ2C@!yZ2C@xZ2C@\xZv2C@ /xZ2C@wZ2C@wZq2C@ wZ#2C@` wZ 2C@@wZ/1C@wZI0C@vZ/C@ vZ/C@wvZT/C@cvZ.C@vZ@.C@tvZ-C@vZ ,C@vZo,C@AvZ`a,C@uZ`&,C@uuZ`a+C@tZh*C@tZ[*C@tZ@A*C@@tZ )C@@1uZ B)C@BuZ(C@ uZY(C@uZ`'C@`uZ &C@uZ@%C@GvZ`W%C@vZ!%C@ vZ$C@xwZ %C@wZ%C@wZB&C@wZ?&C@`xZ@%C@ xZ&C@xZu'C@yZ@9)C@yZ)C@`zZ)C@ zZV*C@{Zf*C@{Z6+C@`{Z+C@R}Z.C@P}Z /C@,}Z`O0C@Q}Z0C@U}Zh1C@g}Z@1C@@~Z@|1C@@~Z`0C@~Z0C@Z1C@`BZN2C@Z2C@Z2C@Z2C@ Z:3C@Z ]3C@{Zd3C@ୀZ2C@Z2C@Z3C@'XvZ C@`lZ(C@HvZ@(%C@uZ%C@uZ^&C@ uZ&C@@FuZ'C@tZ=(C@tZ(C@tZ(C@jtZ'C@%tZ'C@ tZa'C@@sZ@'C@esZ'C@rZ'C@rZ |'C@rZ`I'C@rZ&C@@rZ`&C@5rZK'C@ rZ}'C@qZ'C@qZ`'C@ ]qZ 'C@4qZ'C@`pZ(C@`oZ 'C@oZ+'C@@loZ&C@@oZ`z&C@nZ@\&C@nZ6&C@ nZ`&C@nZ%C@dnZ%C@LnZ&C@"nZ`%C@mZ@I%C@{mZ$C@jmZ$C@\mZ$C@5mZ#C@lZ@#C@lZ T#C@`lZ "C@@-mZ!C@mZY!C@nZ.!C@NnZ`7!C@0oZ@6 C@@UoZC@oZ`kC@oZ`C@UpZ C@ppZC@pZ`BC@@qZ`C@qZ{C@`C@>jZC@ xjZhC@@jZeC@@jZ C@jZ@qC@`jZAC@ jZ@C@ jZC@`jZ.C@5kZ`yC@ kZ@C@`kZ@;C@TlZhC@ slZC@{lZ@C@`lZGC@ lZ`C@mZ`C@ mZC@XnZkC@`oZ C@`#oZ C@nZzC@!nZC@*ZB@ZC@S Z %C@`ZI C@Z C@=Z` C@@BZ C@?ZC@`2ZV C@Z C@Z` C@Z C@`Z C@@2Z C@`ZGC@Z'C@Z@C@Z@BC@ ZC@ZHC@ HZC@`ZC@`ZC@ZC@ZC@ZC@ZC@@ZC@ZC@Z@C@Z@oC@Z@C@@AZ`9C@@Z UC@=Z@ C@`\Z@ C@ Z C@ Z C@-h@+iZB@X^Z C@LcZB@`vcZrB@cZB@cZB@GdZB@ eZ_B@,eZ`B@5eZ@zB@5eZ JB@ZeZB@fZ B@"fZSB@ "fZ#C@4fZ C@ >fZ C@*fZ yC@ fZC@@eZWC@B@ZB@׶Z@B@ZB@`Z B@@Z@B@@ZpB@pZ`B@@ZJB@ZB@ZB@CZ`mB@ZrB@`[@B@ G[@MB@[B@[`B@[ B@'[GB@ [B@`[B@[ B@ [8B@[B@ [B@@;[B@0[`}B@ C[B@@&[`B@[+B@[B@_[B@9[ B@` [@RB@@[ B@y[B@@@[B@`-[_B@J[B@@K[B@`8[B@ [B@ [B@[@bB@[@B@H[B@ [%B@ [ B@ [B@[ BB@[B@$[RB@ I[B@[B@N[@/B@[@B@ [ B@3X@eZB@ _[Z B@hLcZB@ cZB@`bZ B@`bZ@B@ bZ@B@`raZ B@`Z&B@q`ZB@`ZB@_Z B@`K_ZB@`^ZB@ q^Z BB@p^Z>B@.^ZB@ _^Z@B@@i^ZfB@V^ZB@]ZB@`]ZIB@ \ZB@7\ZZB@/\ZAB@[Z B@ h[ZB@ _[ZB@[Z@B@`[Z`AB@@[ZB@@[ZB@ [ZfB@[ZB@ [Z`B@[ZB@@\ZB@@i\ZcB@`y\ZNB@ \Z B@\ZB@ \Z`uB@ \ZB@`\ZB@\ZB@`\Z@`B@\ZB@`\ZB@\Z`B@\ZB@@Q]Z8B@]ZB@@\Z:B@\ZjB@\Z}B@A]ZTB@`{]ZB@ (^ZB@ u^ZB@`^ZBB@a_ZB@(`Z@B@W`ZiB@`ZHB@`ZB@aZ4B@ aZ{B@aZ rB@y`Z B@ j`ZB@_Z kB@@_ZB@@_ZPB@_Z@qB@ dZ rB@dZB@dZDB@@eZGB@dZ@B@ ldZ=B@qdZB@dZ2B@cZcB@ cZ`B@cZB@@cZ&B@cZB@QcZB@0cZ'B@@+cZyB@cZB@ bZB@bZB@bZB@`bZ`!B@ bZrB@@bZ`B@bZ PB@bZ B@bZ B@bZ B@ cZB@1cZ'B@CcZB@8cZ`B@LcZB@4@FZ^B@">Z}B@EFZ`B@EZ`B@`GEZGB@@rEZB@6EZB@ DZ@B@@&DZ B@aCZ B@ CZ B@5CZ5B@pBZoB@BZB@@BZB@BZ3B@@C@Z}B@?Z@=B@|?Z@B@`>Z`B@>Z B@ 3>Z|B@O>ZB@ >Z@B@>ZIB@@>ZB@Q>Z`B@@&>ZB@>Z +B@ "?ZB@>Z,B@q>Z@B@">ZB@@)>ZB@`}>ZB@g>Z@QB@`>Z`zB@>Z@B@>ZnB@>ZB@>ZkB@>Z`B@ '>ZB@->ZBB@~>ZDB@`>ZB@>ZB@`?ZB@ ?ZB@ ?ZB@@?ZfB@ ?Z B@`b?ZBB@ g?ZB@@?Z@B@`?ZDB@?ZB@=@Z^B@AZB@ AZ@sB@AZNB@CZDB@BZB@BZB@*CZ?B@ CZ@B@xCZ` B@MDZB@EZB@ EZZB@FZ`B@5`^ZAB@ZZB@/\ZAB@7\ZZB@ \ZB@`]ZIB@]ZB@V^ZB@@i^ZfB@ _^Z@B@.^ZB@p^Z>B@ q^Z BB@`^ZB@^ZB@]Z@B@``]ZB@I]ZB@J]ZB@`d]ZB@\ZB@|[Z0B@ [ZlB@[ZB@ZZ@B@`4[ZB@[ZJB@/\ZAB@6[B@ ZB@3[ 7B@[B@ y[{B@@[nB@`[>B@{[B@@[B@[@FB@[}B@*[@B@"[B@~[@(B@>[OB@Z LB@`YZB@Z8B@ZB@`@Z B@ZB@LZ@jB@6ZB@ZB@`ZB@rZ B@@ZB@ZB@Z`B@ ZB@ZB@(Z ?B@$Z`B@ wZ`B@=Z^B@ZIB@AZB@@?ZB@ZB@[uB@@[B@ [ #B@ X[mB@`{[B@[@@B@[B@N[B@@[B@[?B@O[@B@[B@H[`B@[ 7B@70`t[B@q[@B@[B@`t[rB@a[B@`:[ B@ [B@[>B@[@B@[B@ [B@V[B@?[`B@[rB@~[ B@R~[B@t}[B@@|[6B@|[ B@|[B@6|[B@`|[@rB@`{[nB@m{[`B@` {[@B@@0z[PB@ x[B@$w[B@v[B@ nv[` B@@*t[`B@s[@B@Ds[@B@`r[B@@r[B@r[ dB@ s[B@`s[dB@u[@B@ v[ B@`w[B@`*w[ B@5w[3B@`1v[`JB@`u[`SB@s[~B@(s[>B@ r[lB@r[B@`zs[@^B@`hs[@B@`s[B@Qr[B@r[3B@q[B@q[KB@ms[\B@s[@B@t[@B@Yu[B@`u[B@Ov[B@e~[B@~[ B@~[@{B@[B@ F[ B@[cB@@ [ B@ [$B@v[B@s[B@[B@@[[B@Z[JB@s[eB@ [B@ـ[HB@π[B@[`B@[B@8@iZ`B@_ZB@%dZB@ dZ rB@_Z@qB@@_ZPB@s`ZOB@t`Z B@``Z@B@``ZB@t`Z`B@n`Z2B@`Z`PB@`ZB@`ZB@`ZB@ aZ@UB@aZaB@_aZB@bZB@bZ B@cZ`B@eZB@eZ B@gZB@gZbB@gZ dB@ gZB@~gZ@B@`>iZ@B@=iZB@iZB@`iZ!B@fZ1B@ PfZ @B@@MfZ`B@eZdB@eZB@dZB@9`\Z B@`ZZ)B@\ZB@`\Z@`B@\ZB@`\ZB@ \ZB@ \Z`uB@\ZB@ \Z B@`y\ZNB@@i\ZcB@@\ZB@[ZB@ [Z`B@[ZB@ [ZfB@@[ZB@@[ZB@ m[Z&B@[Z)B@ZZ`B@`ZZB@ZZ B@ ZZ6B@ [ZB@h[Z`B@ [Z B@)\ZB@\ZB@:hZ@1B@lZB@  ZB@BZB@Z@B@ZB@6ZB@Z'B@pZ`B@ZB@ ZJB@ZB@@9ZB@ZB@ZB@tZ B@ ZB@ZB@ {ZB@Z'B@KZB@ZPB@Z`B@Z;B@PZMB@Z oB@`Z@B@xZHB@`ZB@Z@eB@~Z B@hZB@WZB@@ZB@?ZB@ZYB@ QZB@@Z 6B@Z@gB@}ZB@`lZB@Z`B@Z@B@@Z uB@ZB@Z@B@ZB@0Z`6B@Z -B@!ZoB@ EZB@@Z?B@Z B@`ZB@ZB@Z B@ zZ B@ZB@ fZ@B@Z@B@Z@ B@ZB@vZ B@Z B@Z`=B@NZ`B@BZB@ ZJB@@^Z` B@ZB@`ZzB@ZB@Z5B@Z7B@mZeB@ZB@PZB@vZTB@ZB@ ZtB@Z@*B@ZB@LZ@kB@ Z@B@Z`B@Z B@`ZB@@ZB@ ?ZB@GZ7B@ZdB@Z`B@ZB@]ZB@ ZB@/Z gB@ZB@Z`tB@JZ`;B@ ZiB@ϾZ@B@:Z pB@Z B@ ,Z tB@οZB@`^ZB@Z@hB@ ZB@ ZB@ZGB@iZB@ZB@Z PB@6ZB@ֻZ@ B@Z`B@`]Z*B@ĸZB@Z]B@`YZ`aB@Z B@Z`B@_ZB@ZvB@PZB@`Z%B@=Z&B@_Z`B@@Z B@lZ` B@ଲZpB@@Z B@]ZB@ Z`B@=ZB@ Z B@7Z&B@bZB@`DZdB@`KZB@ZB@`ZſB@`2Z[B@@YZ`B@lZ@EB@`Z۾B@FZB@Z@B@ ZCB@ ~Z@B@ݺZļB@ZYB@CZKB@8ZB@ZZRB@ZZsB@ ZZB@ZZB@`ZZ`B@`ZZ`B@YZB@XZB@XZB@@XZjB@XZEB@ XZ 7B@@XZ B@XZ B@XZ]B@ XZB@XZB@XZ`B@XZ B@@=XZB@` XZB@XZB@WZ B@ WZB@WZ nB@`GXZB@XZB@XZB@`YZB@ AYZB@@zYZ@B@@ZZB@`qZZB@9[ZB@\Z B@;\Z`B@u\Z`B@\Z@B@5]Z` B@]Z@B@ ^ZB@`^ZB@S^ZB@&^Z mB@ ;^Z@ B@(^Z`B@^ZB@ ^ZB@]ZB@]ZB@ ]Z@B@+\ZB@@\Z@B@>@ZB@Z@B@vZ B@`5ZB@\Z@B@ Z`CB@Z B@ Z "B@Z@B@ZB@`QZB@ZB@Z SB@@Z`B@ZB@@EZdB@ZB@ bZ0B@@Z[B@vZ B@?d\ bB@T\6B@2U\ ֲB@yV\ B@`W\XB@3W\iB@`V\཯B@V\B@W\B@X\@ӪB@?Y\`XB@`Y\B@ .[\`B@)]\B@w^\ B@@Y_\ bB@_\}B@`\஦B@ a\B@a\B@ b\%B@ @b\`MB@b\`FB@c\@߬B@d\@B@c\B@b\̯B@`\B@_\@B@|]\ ¯B@`b]\B@]\)B@r]\B@ \\ ùB@[\`B@rZ\oB@Y\bB@`W\B@NV\6B@ U\B@U\B@@U\`AB@`T\`B@ 0T\@B@T\fB@?T\`¶B@T\ B@+U\ dB@lU\@B@MU\B@U\B@U\ ֲB@@/Z`ԈB@ZB@`ZB@Z@B@`ZB@5ZնB@Z_B@ZdB@EZ@hB@@kZ`,B@ZळB@ZB@ CZ@ƱB@hZ @B@ ZB@ ثZcB@`@Z tB@ ZuB@`yZࢯB@Z@B@pZ B@IZ`B@eZ@B@HZB@ Z`B@ QZ@ܭB@ZtB@@vZ ;B@*ZŬB@`)Z`?B@WZ QB@@DZ༪B@ Z B@Z>B@ZB@`Z@CB@aZ NB@@-Z`ܦB@ZB@eZĥB@`ZB@@-ZæB@ZҦB@nZB@ZລB@ZB@`סZB@`6Z5B@ৠZB@@,ZYB@֟ZZ@hB@ZB@Z@B@;Z@B@ tZ ˚B@@'ZB@@Z zB@@ZB@ƮZ`zB@ Z֘B@SZ@B@Z`B@ZPB@@ Z+B@:Z`iB@ :Z@B@pZB@@JZYB@(ZIB@ ;Z B@ྯZwB@ZhB@@ȰZ B@`Z@`B@ZࡎB@;ZaB@@Z@ԐB@mZKB@ ZB@вZ`̓B@ ȲZB@`ZYB@>Z@B@QZB@?Z]B@ZԙB@ϲZ B@AZB@ аZ 5B@ZB@`Z@B@Z NB@ ZNB@Z`B@ NZB@ZB@`lZB@`cZB@`ZB@`̮Z-B@QZ:B@ ZuB@Z`B@eZcB@ JZBB@ _ZӡB@ZB@ٯZ yB@|ZdB@ZB@Z@B@ͱZğB@@ZB@ ZB@Z B@Z@}B@kZB@kZ`fB@ZܥB@`[Z TB@ ZB@ZԧB@]Z ,B@ƲZB@ Z jB@|Z wB@`ڵZ+B@Z B@ZשB@ZMB@`ZӪB@Z@B@`Z B@öZB@ֶZ@B@@/Z,B@/Z B@ZϯB@Z B@ZVB@୵ZeB@ݳZ`lB@`B@ _#[4B@ [>B@ [>B@ [`B@ [ಢB@`![`B@_#[B@ _#[*B@_#[4B@Fiona-1.10.1/tests/data/coutwildrnp.shx000066400000000000000000000011741467206072700200150ustar00rootroot00000000000000' >d\`ԈB@">ZD@2 `X^h !#*p$0%0)*,3j:XARC@EZH NPUJ\^bejJlmqxx|x8VZXbPj@h@: ^ X&@jV 0>@~hǶȺ(˒>B>PՒ`Fiona-1.10.1/tests/data/coutwildrnp.zip000066400000000000000000004546261467206072700200330ustar00rootroot00000000000000PK~VMhcoutwildrnp.shp' d\`ԈB@">ZD@k[`LD@Z[D@R[@_D@@2[D@`([D@@+[`wD@[@D@[@D@[`CD@@[TD@`[@D@@r[@zD@^[@nD@,[@D@[pD@[@sD@Z[D@[D@[`PD@`[`3D@ J[ aD@[bD@F[@D@[D@ d[D@`[D@`[hD@`[ D@[D@@[D@ [D@j[@WD@R[`LD@@R[D@_[D@`[#D@@[ D@`[`HD@}[`D@[pD@[ 2D@@[`D@`I[D@[`eD@[eD@m[`HD@`[@D@l[dD@@[D@[[D@Q[`D@` [D@ [D@`[D@`[JD@X[`[`D@@p[D@[D@@[ D@@[@D@[`D@[D@ *[D@ *[AD@ [2D@ [@Z{D@ ߵZ2|D@iZn|D@`Z`{D@ hZ|D@Z@}D@[GD@[`qD@n/[kmD@@[ nlD@[lkD@[fiD@[hD@ં[hD@@[?hD@@u[gD@`[gD@[gD@Y[@gD@R[gD@[gD@`W[@>gD@`[{gD@[$gD@v[reD@o[WbD@[`^D@![,\D@[@yZD@ [YD@[`4WD@n[}VD@ [TD@[`*SD@`[RD@@N[RD@࿕[@QD@@[ -PD@@[`wOD@8[ ND@`[@LD@@[ KD@[ND@ ל["RD@`[`QD@[PD@[QOD@)[@8ND@ [@LD@w[jLD@[LD@Š[LD@`[`LD@ [`LD@`)[ LD@ [2LD@ޡ[@KD@ף[KD@[KD@J[KD@J[LD@[LD@[!KD@][`LD@`[ gLD@[`TKD@`[LD@[ WLD@[KD@ȳ[ID@G[ GD@[GD@[ 'HD@S[`nMD@[ND@M[PD@w[8SD@ D[TD@[#TD@`¶[mTD@@3[@sVD@श[@YD@`[ZD@([\D@`8[]D@ȵ[`^D@`[``D@H[ dD@[eD@@[@TfD@[ egD@>[^gD@1[ gD@[`5hD@`Z[YD@~ZYD@@~Z:ZD@LZZD@`jZ [D@`tZc[D@`jZ`\D@`jZ@~\D@Z\D@@Z ]D@` Z]D@ ЀZ8_D@Z`D@`[Z`D@`tZ)aD@ yZAaD@ ZaD@Z bD@Z`cD@TZ (dD@ZdD@˂Z@`eD@`߂Z ?fD@@ZfD@9Z@gD@tZ@gD@Z VhD@ Z@hD@Z iD@`pZiD@ ZiD@ʅZ 1jD@Z@jD@ZjD@-ZjD@ ZjD@ZkD@Z KkD@ZlD@ Z@lD@`ZmD@ZmD@ ZlD@ZlD@ZqmD@ ZmD@`PZnD@ZnD@ŃZ@mD@`kZ@mD@@‚Z@`nD@6ZnD@`ƀZoD@VZoD@`!~ZoD@|ZoD@@|Z jD@@0|Z jD@`0|Z hD@{Z`fD@ .{Z`eD@SzZbdD@QyZDdD@`QyZ@KbD@yZ@D@]Z@=D@]ZJ=D@k]Z]Z@VD@ ]ZJWD@\Z@WD@\ZXD@9\Z}YD@[ZAYD@[ZWD@ [ZWD@[Z *WD@ `['ID@@[;PD@[6ND@n[ND@@V[,PD@ [;PD@J[@OD@ [ND@ "[@MD@>[ LD@[&LD@@[@KD@[@JD@[=JD@[pID@ ['ID@[8ID@ [@&JD@`[JD@[LD@[7ND@[6ND@  xZD@tZ`>D@3tZ >D@`tZD@ vZ>D@{vZ?D@vZ @D@ vZ`@D@@vZAD@wZ BD@@xZBD@ixZBD@ xZBD@ xZ@CD@@xZ;CD@xZ CD@xZCD@xZDD@ UxZED@wZFD@@wZGD@fwZVHD@HwZ HD@ wZID@ p@[v6D@w[FD@+@D[FD@[OFD@[`"FD@ [FD@G[FD@[FD@M[FD@@[FD@`[xFD@[ED@[`DD@[cCD@a[BD@`[@BD@[`BD@@j[BD@`[H=D@w[@;D@@[1;D@p[99D@[7D@@[7D@[@8D@ [8D@[7D@@[ 7D@ [@6D@[v6D@[ 7D@[7D@3[ 8D@(["9D@ 2[@:D@0[ =D@0[ \>D@@ [k>D@m[?D@[.?D@[@D@@[DD@@[DD@[7ED@@D[FD@ 0@+\1D@ $\`CD@#+\@D@'+\@D@+\BD@a)\MBD@@'\nBD@`K'\CD@ &\`CD@5&\`(BD@ &\ AD@&\`{?D@&\>D@&\a>D@&\@6>D@`%\@=D@ %\=D@ %\@;D@ %\)9D@%\`l7D@`%\4D@ $\`^3D@ $\2D@^%\@2D@%\22D@#&\ 2D@&\2D@`9'\2D@k(\v2D@)\1D@`y)\`4D@*\ 4D@*\@7D@@+\@7D@`+\ 9D@+\)9D@+\@D@ 0Zm'D@ wZ mZ0D@ mZD@@mZD@mZKD@ mZD@GmZD@lZD@lZD@lZ~D@mmZ mD@mZfD@NnZ F D@nZ@ D@@oZ D@oZ D@oZ D@`oZ D@oZ D@@oZ@ D@aoZ D@%oZ` D@oZ D@ %oZ D@}oZ D@oZD@oZD@UpZ`D@7qZD@ AqZD@rZD@rZD@UsZ D@qsZ`D@`sZD@rZ`<D@@rZ}D@`qZ~D@@aqZ@oD@"qZ@YD@ qZ8D@pZD@ pZ`)D@oZjD@soZ xD@oZ`D@nZD@`nZ@D@DnZ@D@ (nZpD@nZHD@ nZD@mZD@ mZ`D@mZ D@@,mZ@*D@0mZ`D@`mZ D@lZ@D@lZD@dlZ)D@kZD@jZKD@jZ`D@|jZD@iZwD@ [iZ{D@@$iZPD@@hZ@D@LhZ@D@gZD@NgZD@@*gZD@fZhD@fZ>D@fZDD@ eZ |D@eZD@ eZ@D@dZ .D@`dZD@dZD@@dZiD@@^dZ@D@@cZD@cZxD@5cZD@bZ9D@bZbD@@bZ`8D@bcZ D@cZD@`cZjD@cZD@@cZD@bZ ND@bZD@[cZD@@cZD@@dZD@4eZD@feZD@ feZhD@ReZ-D@ eZ@D@dZMD@`dZ@D@`dZ$D@dZ@ D@`dZ@ D@@dZ D@_dZw D@ cZ` D@cZ@z D@@cZ0 D@dZ D@kdZ`C D@eZ& D@WeZ` D@keZ@ D@leZ`D@eZ`.D@ eZD@fZoD@ciZD@FiZJD@ 3iZ`D@ hZD@ hZ D@ iZPD@yiZD@ iZ 7D@@iZ vD@@iZ:D@ziZ`D@ fZ D@`fZD@zdZ D@|dZ`D@dZC@ eZC@fZ`oC@VfZ`4C@fZ4C@fZsC@gZ@C@@igZ2C@gZ}C@@AhZC@@hZC@@hZ`C@ iZDC@iZC@ hZ`C@`hZ C@@hZhC@`9hZwC@!hZEC@gZ C@gZ@C@`VgZ`C@ CgZjC@gZC@ gZC@`gZC@fZC@`fZC@@fZ`xC@@fZ@gC@@fZ XC@fZ@&C@gZ C@`zgZC@@XgZ@OC@XgZ@C@XgZC@gZeC@gZ*C@2hZ@C@vhZC@hZC@hZC@iZ>C@ C@ IZC@GZC@dZC@ZC@ZC@@ZC@ZC@`Z@C@ ZqC@`fZ`RC@Z`C@\ZC@ZC@ZC@Z C@ ZC@@$Z`(C@3Z C@=Z@C@ dZ`C@mZ1C@ EZC@ZUC@`Z C@ nZ@C@ZC@@Z`#C@/Z@C@-Z FC@Z9C@@@ZC@ZC@ ZC@@ZuC@@Z C@&ZhC@MZ@JC@`ZC@@Z"C@`Z@C@;ZC@@XZC@Z`C@`Z`C@ZVC@KZC@ZyC@@`Z`C@ ZC@ Z&C@`>Z C@`Z`"C@Z C@hZ NC@@ZC@`ZpC@ ZvC@Z@C@ZC@Z@hC@jZ9C@`ZC@ZC@ZC@Z PC@`Z@C@[Z`Z`/C@Z`C@aZC@ Z7C@ Z(C@XZ@ C@tZC@ZTC@LZC@1Z C@EZ>C@ZC@ZC@Z C@ZqC@@ZC@ Z`C@Z`C@Z C@`Z`8C@@ZC@Z~C@@|ZC@`3ZKC@`ZPC@ZC@@Z9C@Z C@fZC@HZC@Z@4C@ZtC@/ZhC@Z`C@`Z@7C@Z@C@Z C@@HZC@Z C@Z`C@`OZC@ ZRC@\ZC@ #Z@qC@`8Z@C@yZtC@fZC@ZC@ZHD@Z 7D@MZsD@Z D@[ QC@ >[C@`=[@C@ F[C@[C@@@rZC@{ZC@E{Z`OC@'|ZC@S|ZC@ /|ZC@"|ZC@ |Z%C@|Z@C@@}Z`C@}Z`RC@}Z@!C@ }Z /C@}ZC@@*}Z@C@ }ZGC@@$}ZC@[}ZC@b}ZC@S}ZC@|ZC@.}Z`C@@@}ZC@ }Z`C@}ZC@Y~Z`4C@[~Z aC@}Z`C@@}Z`"C@ G~Z&C@`~ZyC@~Z`C@ \~Z@#C@]~Z AC@~Z#C@kZC@`Z C@`Z`[C@ZEC@Z`C@ Z C@\Z C@ZC@UZ`8C@`XZC@ ZC@ Z@4C@@rZ@C@Z@C@ ZC@ZC@IZ]C@`ZaC@`qZC@ Z`C@`7Z`WC@@Z C@`ZC@Z@C@ZC@`ԋZC@ZSC@^Z5C@TZC@@hZ HC@{Z}ZC@ |ZC@`|Z`C@}|ZwC@l|Z\C@`_|ZHC@ %|ZC@|ZC@&|Z`C@mZ$C@`]Z`C@`gZ/C@gZ $C@gZ@#C@gZDC@`VgZ 'C@tgZC@~gZ@fC@jgZC@`0gZC@fZhC@fZJC@fZ C@`mfZkC@`FfZ`\C@ fZ C@@eZ@C@eZ@:C@eZ@C@eZ'C@ceZ@C@OeZC@`YeZAC@@OeZ`C@`1eZ[C@dZ@C@@dZC@dZ +C@dZWC@ cZ4C@cZ`C@`bZ$C@[bZ C@faZ>C@5aZ`>C@``Z C@s`Z C@_ZBC@_ZC@`.aZ`uC@ aZ ;C@@aZ`C@bZ`C@cZC@`AcZfC@AcZC@_cZ =C@cZC@cZ C@`cZnC@ cZC@cZ bC@tcZ`5C@@0cZ4C@cZpC@bZ~C@@YbZC@aZC@4aZC@`Z C@`Z`C@`ZVC@_Z VC@/_Z UC@^ZC@`2^Z tC@R]ZC@ ]Z`C@`]Z`WC@6]ZC@@!^Z C@f^ZC@@^ZC@` _ZbC@@Q_ZC@_ZC@`_Z`C@9aZ gC@ aZC@bZ C@^bZ`9C@`bZ C@@rbZ`xC@`YbZC@bZ +C@@cZpC@dZC@IeZ`C@@eZC@`DfZ2C@ QfZ@C@@=eZ@C@zeZ`7C@ dZ,C@eZ$C@ gZIC@hZ C@^kZ`sC@zkZC@`kZ`C@`lZ`zC@@ZlZ`hC@lZC@mZC@}mZ`_C@@mZ C@`mZ@kC@mZC@mZ@C@ mZC@JmZC@mZ`MC@lZ C@mZ ;C@ImZC@pmZC@mZdC@mZ C@mZ`C@mZC@mZC@mZKC@mZC@ZmZC@dmZ&C@mZC@ mZC@mZ@C@mZC@mZ C@umZC@:mZC@@mZC@`lZC@ lZ@EC@ClZ@C@jZ@C@@jZ@oC@PjZ QC@jZ@$C@>iZ@C@hZC@]hZ`C@@hZ`C@`gZ/C@שZ¤C@~Z9C@uZ C@Z C@ZC@ ZC@tZC@`~Z`C@̚ZrC@ߚZ@EC@Z@+C@ޚZC@"ZiC@?Z C@>ZC@RZC@xZC@@ZUC@@xZ޿C@ZZwC@Z C@`Z`C@Z ˽C@`Z ڽC@ZC@ZǾC@Z kC@@Z`,C@UZC@`ZC@ZC@ :ZC@`Z C@@Z6C@~ZC@ZC@#ZԻC@Z`TC@Z ·C@Z` C@ZC@@Z@RC@(Z 4C@YZQC@Z@C@ZǴC@ZKC@ZC@8ZC@Z C@@˞ZC@Z@C@@"Z@ C@"Z@C@Z=C@ZC@qZdzC@qZ@C@Z_C@zZC@6ZC@ZUC@ZdC@`iZC@ZC@@לZC@XZC@ZdC@Z@γC@`TZ C@sZC@טZC@ZC@bZC@ ;Z >C@`'Z@ֲC@DZC@7ZC@`^Z@RC@{ZC@ {ZC@{ZFC@\Z C@@\Z?C@ZC@Z`C@Z9C@ZC@ZC@`ęZթC@@ZOC@aZ C@WZ@ΧC@ZtC@`™Z C@ ߙZ ZC@ԙZ`{C@`ޙZ?C@5ZC@@WZC@ ^Z¤C@ Z`ΤC@ZC@`iZ C@`ZC@Z C@ SZ@C@`zZ^C@`ZC@ZC@@Z)C@ZC@Z C@Z@bC@ ZC@`Z#C@ Z@@C@-ZC@ZC@ Z(C@@jZཫC@9ZC@Z@ܫC@ġZ@C@Z`ΫC@Z C@ Z C@`Z`nC@@Z@֭C@ ڡZ@C@bZC@`ZC@_ZsC@_Z༤C@`_Z@QC@_ZC@@_ZzC@`Z`C@V`ZC@ `Z@C@`ZC@`Z C@6aZ/C@aZ ԧC@PbZ`=C@`bZ@ĨC@cZC@dZݪC@ dZ@FC@(eZdC@eZC@LfZ@EC@@fZ C@gZC@SgZsC@gZ)C@gZ C@ hZC@iZIC@hjZ`*C@`rjZVC@ rjZC@jZ6C@ kZڮC@@4kZ`$C@lZC@`lZկC@tmZ 5C@`!nZC@`kZ"C@kZC@lZ C@@lZC@lZ`C@kZ;C@ {jZC@iZҸC@`hZC@fZC@dZ@C@:cZ C@aZ@^C@@|ZC@nZ1C@p{ZC@@ZwC@ZC@۟Z/C@ПZฝC@Z3C@nZC@`Z C@ZÚC@ZǙC@ZC@˟ZC@"ZC@\ZC@eZC@xZkC@٠Z }C@ Z@AC@`tZ@OC@ZC@6ZMC@`ZC@ Z`xC@ GZiC@ ZC@`Z@MC@ Z@C@௣Z3C@`Z`TC@ZC@ZC@Z`ϏC@@uZC@Z-C@`ZC@ZHC@eZ`C@ǧZ{C@ZC@`mZ@C@Z ϐC@ZːC@_ZC@ZC@mZ C@MZC@Z9C@TZC@@|Z/C@ /ZC@ZC@@Z@"C@@үZC@ZZC@+Z)C@@KZəC@@BZ]C@ͯZ|C@Z@C@Z kC@=ZC@ZÜC@ZC@DZǜC@Z &C@ =Z`C@࡫Z@sC@ Z`C@ZiC@3Z@MC@ Z@C@`Z@hC@Z@C@`RZFC@`Z@C@@ZൟC@ Z@C@ZrC@`ZOC@@ZC@Z@C@ZC@(Z{C@ Z@ӥC@Z+C@Z`C@`Z`C@@ZAC@Z@oC@:Z1C@OZ C@ZШC@ PZ`AC@ZC@)ZZ`C@ZuC@-ZC@ˆZ:C@ NZC@@ZC@ZڇC@xZC@wZyC@PZ@C@Z@C@Z`C@ZC@ Z@C@@PZC@ PZC@2Z C@ ZՀC@Z|C@Z@nC@ZǀC@@Z`C@Z+C@aZC@ZC@@eZނC@ ZC@ZC@@ZC@;Z ԀC@@̃Zl}C@`Z@h}C@DZ}C@Z m|C@ńZ{C@ Z{C@MZ"|C@Z |C@?Z|C@VZ|C@Z {C@`Z {C@ Z{C@ZzC@Z {C@DZ{C@இZzC@ ܇ZzC@@Z zC@ZH{C@@9Zw{C@ ZZ}{C@ZY{C@@ˆZ zC@`ZzC@jZyC@@߉ZyC@ RZzC@RZzC@@Z{C@Z|C@BZ`|C@@Z}C@ZIC@ Z`cC@Z@3C@ Z`C@ZوC@ZZ C@QZC@ZnC@ZmC@`ϑZ @mC@ZlC@`ZZkC@ҐZjC@ Z@jC@qZjC@]ZhC@ZphC@Z &hC@@ҒZ@nhC@vZOhC@ÓZgC@ZgC@3Z hC@Z [hC@ZhC@4Z]gC@@ؗZgC@VZ gC@ԘZ@igC@!Z hC@?ZhC@ љZiC@Z`kC@)ZkC@ĚZ@ lC@BZ`blC@ ԛZRlC@`xZ lC@@ٜZ@lC@Z@nlC@ƜZ mC@@#Z@nC@̛Z oC@`ÛZoC@@ZpC@KZqC@Z@pC@ܜZ`pC@ ZpC@ZqC@НZ@qC@'ZGrC@1ZrC@ OZasC@`ZsC@Z]tC@ԟZ uC@mZuC@@2Z `vC@BZvC@ӢZgwC@Z^yC@cZ`yC@8ZyyC@Z iyC@`Z@ zC@Z?{C@ LZ{C@ʨZ@|C@`Z@|C@fZ|C@ Z`6|C@DZ{C@êZB|C@ Z Z}C@@pZ`c~C@ Z C@ਭZ@,C@@uZC@@AZـC@Z #C@ZC@ZԁC@ fZ:C@ZC@Z9C@`Z uC@యZC@@دZ@C@Z C@ ZࢆC@Z6C@@ ZC@gC@ ZgC@`BZ `hC@@ZiC@`ZkC@ Z`kC@ZlC@Z` mC@=Z kmC@Z`mC@Z@mC@,Z lC@ZYlC@ ZlC@Z mC@@YZ mC@ZmC@`Z`2lC@`ZlC@@ZkC@ZwkC@PZLkC@ZjC@Z`pjC@@ZAjC@Z UjC@ZBkC@,ZFkC@`7ZiC@"Z iC@ ZfC@ZfC@ZeC@ZZ [eC@VZGeC@+ZeC@0Z@eC@OZ`aC@,Z^C@GZx]C@ )Z@]C@Z`]C@BZn]C@@mZc]C@Z\C@ Zj\C@Z[C@`wZ$[C@`2ZsZC@Z`YC@ZXC@@ZOXC@@ZWC@ 6Z`ZWC@`"Z@WC@`5Z`VC@Z VC@ZUC@Z TC@PZ@SC@ ZRC@QZ `QC@`PZPC@ZOC@ Z UOC@ZNC@`Z@NC@`GZNC@ZNC@Z NC@`7ZJNC@Z9NC@ Z MC@Z@MC@XZMC@&Z OC@Z7PC@Z`qPC@=Z QC@KZ"QC@`ZQC@ ZQC@QZ@(RC@ZpRC@` ZRC@kZ RC@Z lRC@Z`#QC@`Z`&OC@ ZsNC@Z@MC@Z5KC@`Z@,IC@ZGC@Z GC@ Z _GC@ZFC@Z@FC@`:ZdEC@#ZNC@ʤZaC@o Z$aC@Z`aC@ZaC@Z@aC@ |ZwaC@*ZCaC@ĦZ!aC@`$Z`C@Z`C@@ťZM_C@Zs^C@ Z]C@@Z)]C@Z\C@RZZC@ #ZYC@`+ZyYC@ PZ`XC@Z 2XC@ Z`WC@ZvWC@Z`%WC@}Z VC@`LZVC@ZVC@@ ZUC@ʤZTC@@ͤZUTC@5ZSC@Z7SC@ Z RC@@Z RC@@BZ@VSC@[Z@ TC@`ZTC@Z@TC@@ZSC@ZSC@ZZSC@#ZTSC@`ZSC@ZzSC@`ɨZPSC@ Z'SC@ZRC@@%ZRC@9ZqRC@zZRC@ZnRC@Z"RC@ZQC@@ZQC@ZQC@@ΩZQC@ߩZ@ RC@Z RC@)ZQC@EZ`QC@ZPC@ZVPC@1Z`OC@GZNC@ìZNC@Z NC@ Z NC@ԬZFOC@ڬZ@rOC@yZPC@ZhPC@Z`PC@ZPC@@vZZQC@ ZRC@Z SC@Z`SC@ZSC@ ZMTC@`ͬZ TC@@Z@OUC@LZUC@OZUC@@@ZVC@`Z@RVC@ZVC@ ZVC@Z`VC@@ZVC@`Z`VC@ ZWC@@Z$XC@Z}XC@ܬZXC@ ʬZ`mYC@ZZC@SZ ZC@7Z[C@@7Z\C@@RZ\C@SZ}]C@dZ`^C@@ZZn^C@@Z@^C@`Z^C@Z5_C@Z_C@ Z_C@}Z<`C@%Zg`C@ Z =`C@IZ[`C@ Z$aC@$ZHC@@Z`C@ZPC@@Z PC@Z@QC@ Z`RC@Z RC@@Z"QC@ZMC@ZJC@lZJC@kZJC@Z&JC@Z`IC@ LZ`IC@RZHC@@Z`HC@`ZHC@Z.IC@@ZBIC@"Z`IC@@>ZIC@mZjIC@@ZIC@Z JC@ lZ`&JC@@ZJC@@Z eKC@Z`hKC@ZKC@ Z`BLC@;ZLC@@FZgMC@@Z`MC@;Z`MC@AZNC@2Z!OC@ vZOC@@Z`OC@ 8Z PC@JZ@nPC@rZ`hPC@Z@PC@Z cQC@ OZ@QC@ ]Z@RC@`@Z@ASC@bZSC@kZTC@@*Z%UC@@/Z@|UC@Z`VC@ ZVC@@:Z`2WC@ 0Z@WC@ Z WC@ Z?XC@ZXC@ZsYC@ZYC@(ZYC@Z`YC@ZAZC@Z`"[C@ ~Z`[C@@MZ[C@XZ`$\C@`ZS]C@Z@{]C@ gZ]C@mZ]^C@7Z@^C@Z`C@Z``C@@Z`l_C@Z@6_C@Z^C@`PZ@O^C@Z]^C@Z@]C@?Z]C@@tZ?]C@Z T\C@Z \C@"Z`\C@vZ\C@Z\C@Z \C@@Z[C@@!Z[C@Z8[C@Z [C@ZZC@qZ ZC@@Z qZC@LZ`CZC@(Z@YC@ZYC@Z YC@@YZ /YC@ZXC@Z@YC@@ZYC@ZXC@ZXC@ZyXC@7ZtXC@WZ`-XC@VZWC@ Z`UWC@Z@VC@Z@{VC@Z=VC@Z@UC@ Z UC@ Z6UC@[Z>UC@^ZTC@Z TC@`Z>UC@nZUC@@ZHUC@Z`fTC@"Z@-TC@iZQSC@@rZSC@ >ZRTC@ IZTC@kZNTC@ZKTC@ZSC@ZZSC@`Z@ SC@Z@RC@@Z@RC@.ZRC@@EZRC@^Z@xRC@wZ^RC@ Z QRC@ZSRC@ZRC@`ZRC@`ZRC@Z SC@`#ZSC@@HC@Z@HC@Z HC@ZHC@Z@HC@Z HC@@ZIC@Z-IC@`ZXIC@%Z{IC@CZ wIC@jZIC@wZIC@`Z@JC@ZdJC@Z lJC@Z aJC@`Z#JC@`Z JC@@ZJC@1ZIC@@CZIC@aZ@IC@dZ}IC@sZ@kIC@Z(IC@Z`HC@Z HC@Z XHC@`HZ@NHC@bZ HC@ZHC@`ZMHC@ ZTHC@ZHC@ ZJHC@"Z HC@@*ZHC@@'Z GC@zZGC@@tZ@GC@ Z GC@Z HC@RZHC@ LZ`IC@Z`IC@Z&JC@kZJC@lZJC@ZJC@ZMC@@Z"QC@Z RC@ Z`RC@Z@QC@@Z PC@ZPC@& Z$C@@tZ Y:C@}Z3C@iZ 5C@@KZo5C@@RZ6C@ZH6C@Z |6C@Z6C@̀Z6C@Z 6C@`rZc6C@ுZ J6C@Z6C@ Z6C@ ؁ZZ7C@Z7C@ kZ 7C@ FZ 8C@&ZN8C@ Z8C@Z8C@fZ8C@MZ@K9C@ Z:C@ Z(:C@`Z Y:C@~ZL:C@~Z`9C@~Z\9C@~ZR9C@~Z8C@O~Z8C@ }Z8C@`}Z@68C@`~Zv7C@G}Z5C@}Z'6C@|Z a6C@|Z 6C@@|Z 7C@|Zc7C@ C|Z@U7C@{Z 7C@{Z 7C@{Z 6C@~{Z5C@zZ5C@zZk5C@zZJ5C@@zZ@5C@zZ4C@zZ@4C@@zZ@4C@ MzZ 4C@ HzZ3C@yZ 3C@yZ 53C@yZ2C@!yZ2C@xZ2C@\xZv2C@ /xZ2C@wZ2C@wZq2C@ wZ#2C@` wZ 2C@@wZ/1C@wZI0C@vZ/C@ vZ/C@wvZT/C@cvZ.C@vZ@.C@tvZ-C@vZ ,C@vZo,C@AvZ`a,C@uZ`&,C@uuZ`a+C@tZh*C@tZ[*C@tZ@A*C@@tZ )C@@1uZ B)C@BuZ(C@ uZY(C@uZ`'C@`uZ &C@uZ@%C@GvZ`W%C@vZ!%C@ vZ$C@xwZ %C@wZ%C@wZB&C@wZ?&C@`xZ@%C@ xZ&C@xZu'C@yZ@9)C@yZ)C@`zZ)C@ zZV*C@{Zf*C@{Z6+C@`{Z+C@R}Z.C@P}Z /C@,}Z`O0C@Q}Z0C@U}Zh1C@g}Z@1C@@~Z@|1C@@~Z`0C@~Z0C@Z1C@`BZN2C@Z2C@Z2C@Z2C@ Z:3C@Z ]3C@{Zd3C@ୀZ2C@Z2C@Z3C@'XvZ C@`lZ(C@HvZ@(%C@uZ%C@uZ^&C@ uZ&C@@FuZ'C@tZ=(C@tZ(C@tZ(C@jtZ'C@%tZ'C@ tZa'C@@sZ@'C@esZ'C@rZ'C@rZ |'C@rZ`I'C@rZ&C@@rZ`&C@5rZK'C@ rZ}'C@qZ'C@qZ`'C@ ]qZ 'C@4qZ'C@`pZ(C@`oZ 'C@oZ+'C@@loZ&C@@oZ`z&C@nZ@\&C@nZ6&C@ nZ`&C@nZ%C@dnZ%C@LnZ&C@"nZ`%C@mZ@I%C@{mZ$C@jmZ$C@\mZ$C@5mZ#C@lZ@#C@lZ T#C@`lZ "C@@-mZ!C@mZY!C@nZ.!C@NnZ`7!C@0oZ@6 C@@UoZC@oZ`kC@oZ`C@UpZ C@ppZC@pZ`BC@@qZ`C@qZ{C@`C@>jZC@ xjZhC@@jZeC@@jZ C@jZ@qC@`jZAC@ jZ@C@ jZC@`jZ.C@5kZ`yC@ kZ@C@`kZ@;C@TlZhC@ slZC@{lZ@C@`lZGC@ lZ`C@mZ`C@ mZC@XnZkC@`oZ C@`#oZ C@nZzC@!nZC@*ZB@ZC@S Z %C@`ZI C@Z C@=Z` C@@BZ C@?ZC@`2ZV C@Z C@Z` C@Z C@`Z C@@2Z C@`ZGC@Z'C@Z@C@Z@BC@ ZC@ZHC@ HZC@`ZC@`ZC@ZC@ZC@ZC@ZC@@ZC@ZC@Z@C@Z@oC@Z@C@@AZ`9C@@Z UC@=Z@ C@`\Z@ C@ Z C@ Z C@-h@+iZB@X^Z C@LcZB@`vcZrB@cZB@cZB@GdZB@ eZ_B@,eZ`B@5eZ@zB@5eZ JB@ZeZB@fZ B@"fZSB@ "fZ#C@4fZ C@ >fZ C@*fZ yC@ fZC@@eZWC@B@ZB@׶Z@B@ZB@`Z B@@Z@B@@ZpB@pZ`B@@ZJB@ZB@ZB@CZ`mB@ZrB@`[@B@ G[@MB@[B@[`B@[ B@'[GB@ [B@`[B@[ B@ [8B@[B@ [B@@;[B@0[`}B@ C[B@@&[`B@[+B@[B@_[B@9[ B@` [@RB@@[ B@y[B@@@[B@`-[_B@J[B@@K[B@`8[B@ [B@ [B@[@bB@[@B@H[B@ [%B@ [ B@ [B@[ BB@[B@$[RB@ I[B@[B@N[@/B@[@B@ [ B@3X@eZB@ _[Z B@hLcZB@ cZB@`bZ B@`bZ@B@ bZ@B@`raZ B@`Z&B@q`ZB@`ZB@_Z B@`K_ZB@`^ZB@ q^Z BB@p^Z>B@.^ZB@ _^Z@B@@i^ZfB@V^ZB@]ZB@`]ZIB@ \ZB@7\ZZB@/\ZAB@[Z B@ h[ZB@ _[ZB@[Z@B@`[Z`AB@@[ZB@@[ZB@ [ZfB@[ZB@ [Z`B@[ZB@@\ZB@@i\ZcB@`y\ZNB@ \Z B@\ZB@ \Z`uB@ \ZB@`\ZB@\ZB@`\Z@`B@\ZB@`\ZB@\Z`B@\ZB@@Q]Z8B@]ZB@@\Z:B@\ZjB@\Z}B@A]ZTB@`{]ZB@ (^ZB@ u^ZB@`^ZBB@a_ZB@(`Z@B@W`ZiB@`ZHB@`ZB@aZ4B@ aZ{B@aZ rB@y`Z B@ j`ZB@_Z kB@@_ZB@@_ZPB@_Z@qB@ dZ rB@dZB@dZDB@@eZGB@dZ@B@ ldZ=B@qdZB@dZ2B@cZcB@ cZ`B@cZB@@cZ&B@cZB@QcZB@0cZ'B@@+cZyB@cZB@ bZB@bZB@bZB@`bZ`!B@ bZrB@@bZ`B@bZ PB@bZ B@bZ B@bZ B@ cZB@1cZ'B@CcZB@8cZ`B@LcZB@4@FZ^B@">Z}B@EFZ`B@EZ`B@`GEZGB@@rEZB@6EZB@ DZ@B@@&DZ B@aCZ B@ CZ B@5CZ5B@pBZoB@BZB@@BZB@BZ3B@@C@Z}B@?Z@=B@|?Z@B@`>Z`B@>Z B@ 3>Z|B@O>ZB@ >Z@B@>ZIB@@>ZB@Q>Z`B@@&>ZB@>Z +B@ "?ZB@>Z,B@q>Z@B@">ZB@@)>ZB@`}>ZB@g>Z@QB@`>Z`zB@>Z@B@>ZnB@>ZB@>ZkB@>Z`B@ '>ZB@->ZBB@~>ZDB@`>ZB@>ZB@`?ZB@ ?ZB@ ?ZB@@?ZfB@ ?Z B@`b?ZBB@ g?ZB@@?Z@B@`?ZDB@?ZB@=@Z^B@AZB@ AZ@sB@AZNB@CZDB@BZB@BZB@*CZ?B@ CZ@B@xCZ` B@MDZB@EZB@ EZZB@FZ`B@5`^ZAB@ZZB@/\ZAB@7\ZZB@ \ZB@`]ZIB@]ZB@V^ZB@@i^ZfB@ _^Z@B@.^ZB@p^Z>B@ q^Z BB@`^ZB@^ZB@]Z@B@``]ZB@I]ZB@J]ZB@`d]ZB@\ZB@|[Z0B@ [ZlB@[ZB@ZZ@B@`4[ZB@[ZJB@/\ZAB@6[B@ ZB@3[ 7B@[B@ y[{B@@[nB@`[>B@{[B@@[B@[@FB@[}B@*[@B@"[B@~[@(B@>[OB@Z LB@`YZB@Z8B@ZB@`@Z B@ZB@LZ@jB@6ZB@ZB@`ZB@rZ B@@ZB@ZB@Z`B@ ZB@ZB@(Z ?B@$Z`B@ wZ`B@=Z^B@ZIB@AZB@@?ZB@ZB@[uB@@[B@ [ #B@ X[mB@`{[B@[@@B@[B@N[B@@[B@[?B@O[@B@[B@H[`B@[ 7B@70`t[B@q[@B@[B@`t[rB@a[B@`:[ B@ [B@[>B@[@B@[B@ [B@V[B@?[`B@[rB@~[ B@R~[B@t}[B@@|[6B@|[ B@|[B@6|[B@`|[@rB@`{[nB@m{[`B@` {[@B@@0z[PB@ x[B@$w[B@v[B@ nv[` B@@*t[`B@s[@B@Ds[@B@`r[B@@r[B@r[ dB@ s[B@`s[dB@u[@B@ v[ B@`w[B@`*w[ B@5w[3B@`1v[`JB@`u[`SB@s[~B@(s[>B@ r[lB@r[B@`zs[@^B@`hs[@B@`s[B@Qr[B@r[3B@q[B@q[KB@ms[\B@s[@B@t[@B@Yu[B@`u[B@Ov[B@e~[B@~[ B@~[@{B@[B@ F[ B@[cB@@ [ B@ [$B@v[B@s[B@[B@@[[B@Z[JB@s[eB@ [B@ـ[HB@π[B@[`B@[B@8@iZ`B@_ZB@%dZB@ dZ rB@_Z@qB@@_ZPB@s`ZOB@t`Z B@``Z@B@``ZB@t`Z`B@n`Z2B@`Z`PB@`ZB@`ZB@`ZB@ aZ@UB@aZaB@_aZB@bZB@bZ B@cZ`B@eZB@eZ B@gZB@gZbB@gZ dB@ gZB@~gZ@B@`>iZ@B@=iZB@iZB@`iZ!B@fZ1B@ PfZ @B@@MfZ`B@eZdB@eZB@dZB@9`\Z B@`ZZ)B@\ZB@`\Z@`B@\ZB@`\ZB@ \ZB@ \Z`uB@\ZB@ \Z B@`y\ZNB@@i\ZcB@@\ZB@[ZB@ [Z`B@[ZB@ [ZfB@@[ZB@@[ZB@ m[Z&B@[Z)B@ZZ`B@`ZZB@ZZ B@ ZZ6B@ [ZB@h[Z`B@ [Z B@)\ZB@\ZB@:hZ@1B@lZB@  ZB@BZB@Z@B@ZB@6ZB@Z'B@pZ`B@ZB@ ZJB@ZB@@9ZB@ZB@ZB@tZ B@ ZB@ZB@ {ZB@Z'B@KZB@ZPB@Z`B@Z;B@PZMB@Z oB@`Z@B@xZHB@`ZB@Z@eB@~Z B@hZB@WZB@@ZB@?ZB@ZYB@ QZB@@Z 6B@Z@gB@}ZB@`lZB@Z`B@Z@B@@Z uB@ZB@Z@B@ZB@0Z`6B@Z -B@!ZoB@ EZB@@Z?B@Z B@`ZB@ZB@Z B@ zZ B@ZB@ fZ@B@Z@B@Z@ B@ZB@vZ B@Z B@Z`=B@NZ`B@BZB@ ZJB@@^Z` B@ZB@`ZzB@ZB@Z5B@Z7B@mZeB@ZB@PZB@vZTB@ZB@ ZtB@Z@*B@ZB@LZ@kB@ Z@B@Z`B@Z B@`ZB@@ZB@ ?ZB@GZ7B@ZdB@Z`B@ZB@]ZB@ ZB@/Z gB@ZB@Z`tB@JZ`;B@ ZiB@ϾZ@B@:Z pB@Z B@ ,Z tB@οZB@`^ZB@Z@hB@ ZB@ ZB@ZGB@iZB@ZB@Z PB@6ZB@ֻZ@ B@Z`B@`]Z*B@ĸZB@Z]B@`YZ`aB@Z B@Z`B@_ZB@ZvB@PZB@`Z%B@=Z&B@_Z`B@@Z B@lZ` B@ଲZpB@@Z B@]ZB@ Z`B@=ZB@ Z B@7Z&B@bZB@`DZdB@`KZB@ZB@`ZſB@`2Z[B@@YZ`B@lZ@EB@`Z۾B@FZB@Z@B@ ZCB@ ~Z@B@ݺZļB@ZYB@CZKB@8ZB@ZZRB@ZZsB@ ZZB@ZZB@`ZZ`B@`ZZ`B@YZB@XZB@XZB@@XZjB@XZEB@ XZ 7B@@XZ B@XZ B@XZ]B@ XZB@XZB@XZ`B@XZ B@@=XZB@` XZB@XZB@WZ B@ WZB@WZ nB@`GXZB@XZB@XZB@`YZB@ AYZB@@zYZ@B@@ZZB@`qZZB@9[ZB@\Z B@;\Z`B@u\Z`B@\Z@B@5]Z` B@]Z@B@ ^ZB@`^ZB@S^ZB@&^Z mB@ ;^Z@ B@(^Z`B@^ZB@ ^ZB@]ZB@]ZB@ ]Z@B@+\ZB@@\Z@B@>@ZB@Z@B@vZ B@`5ZB@\Z@B@ Z`CB@Z B@ Z "B@Z@B@ZB@`QZB@ZB@Z SB@@Z`B@ZB@@EZdB@ZB@ bZ0B@@Z[B@vZ B@?d\ bB@T\6B@2U\ ֲB@yV\ B@`W\XB@3W\iB@`V\཯B@V\B@W\B@X\@ӪB@?Y\`XB@`Y\B@ .[\`B@)]\B@w^\ B@@Y_\ bB@_\}B@`\஦B@ a\B@a\B@ b\%B@ @b\`MB@b\`FB@c\@߬B@d\@B@c\B@b\̯B@`\B@_\@B@|]\ ¯B@`b]\B@]\)B@r]\B@ \\ ùB@[\`B@rZ\oB@Y\bB@`W\B@NV\6B@ U\B@U\B@@U\`AB@`T\`B@ 0T\@B@T\fB@?T\`¶B@T\ B@+U\ dB@lU\@B@MU\B@U\B@U\ ֲB@@/Z`ԈB@ZB@`ZB@Z@B@`ZB@5ZնB@Z_B@ZdB@EZ@hB@@kZ`,B@ZळB@ZB@ CZ@ƱB@hZ @B@ ZB@ ثZcB@`@Z tB@ ZuB@`yZࢯB@Z@B@pZ B@IZ`B@eZ@B@HZB@ Z`B@ QZ@ܭB@ZtB@@vZ ;B@*ZŬB@`)Z`?B@WZ QB@@DZ༪B@ Z B@Z>B@ZB@`Z@CB@aZ NB@@-Z`ܦB@ZB@eZĥB@`ZB@@-ZæB@ZҦB@nZB@ZລB@ZB@`סZB@`6Z5B@ৠZB@@,ZYB@֟ZZ@hB@ZB@Z@B@;Z@B@ tZ ˚B@@'ZB@@Z zB@@ZB@ƮZ`zB@ Z֘B@SZ@B@Z`B@ZPB@@ Z+B@:Z`iB@ :Z@B@pZB@@JZYB@(ZIB@ ;Z B@ྯZwB@ZhB@@ȰZ B@`Z@`B@ZࡎB@;ZaB@@Z@ԐB@mZKB@ ZB@вZ`̓B@ ȲZB@`ZYB@>Z@B@QZB@?Z]B@ZԙB@ϲZ B@AZB@ аZ 5B@ZB@`Z@B@Z NB@ ZNB@Z`B@ NZB@ZB@`lZB@`cZB@`ZB@`̮Z-B@QZ:B@ ZuB@Z`B@eZcB@ JZBB@ _ZӡB@ZB@ٯZ yB@|ZdB@ZB@Z@B@ͱZğB@@ZB@ ZB@Z B@Z@}B@kZB@kZ`fB@ZܥB@`[Z TB@ ZB@ZԧB@]Z ,B@ƲZB@ Z jB@|Z wB@`ڵZ+B@Z B@ZשB@ZMB@`ZӪB@Z@B@`Z B@öZB@ֶZ@B@@/Z,B@/Z B@ZϯB@Z B@ZVB@୵ZeB@ݳZ`lB@`B@ _#[4B@ [>B@ [>B@ [`B@ [ಢB@`![`B@_#[B@ _#[*B@_#[4B@PK~VM5֯||coutwildrnp.shx' >d\`ԈB@">ZD@2 `X^h !#*p$0%0)*,3j:XARC@EZH NPUJ\^bejJlmqxx|x8VZXbPj@h@: ^ X&@jV 0>@~hǶȺ(˒>B>PՒ`PK~VMug8coutwildrnp.dbf_CaPERIMETERNFEATURE2CPNAMECPFEATURE1CPURLCeAGBURCPAREANSTATE_FIPSCPWILDRNP020N STATECP 1.221070000000000 Mount Naomi Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Naomi FS 0.01792640000000049 332UT 0.755827000000000 Wellsville Mountain Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Wellsville%20Mountain FS 0.01044410000000049 336UT 1.708510000000000 Mount Zirkel Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Zirkel FS 0.07149550000000008 357CO 2.232410000000000 High Uintas Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=High%20Uintas FS 0.18291900000000049 358UT 1.054580000000000 Rawah Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Rawah FS 0.03373710000000008 359CO 0.418340000000000 Mount Olympus Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Olympus FS 0.00633137000000049 364UT 1.760390000000000 Comanche Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Comanche%20Peak FS 0.03197700000000008 365CO 0.462863000000000 Cache La Poudre Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Cache%20La%20Poudre FS 0.00481977000000008 366CO 0.315219000000000 Twin Peaks Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Twin%20Peaks FS 0.00477962000000049 367UT 0.329520000000000 Neota Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Neota FS 0.00576742000000008 369CO 0.518395000000000 Lone Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Lone%20Peak FS 0.01251300000000049 371UT 0.477348000000000 Deseret Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Deseret%20Peak FS 0.01077180000000049 373UT 0.675146000000000 Never Summer Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Never%20Summer FS 0.00908863000000008 374CO 0.288683000000000 Mount Timpanogos Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Timpanogos FS 0.00442921000000049 375UT 0.768802000000000 Sarvis Creek Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sarvis%20Creek FS 0.01957160000000008 376CO 1.372940000000000 Indian Peaks Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Indian%20Peaks FS 0.03140190000000008 378CO 2.029470000000000 Flat Tops Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Flat%20Tops FS 0.10322100000000008 380CO 0.765491000000000 James Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=James%20Peak FS 0.00676706000000008 384CO 0.726088000000000 Mount Nebo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Nebo FS 0.01203290000000049 385UT 0.376165000000000 Byers Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Byers%20Peak FS 0.00345459000000008 386CO 0.528667000000000 Vasquez Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Vasquez%20Peak FS 0.00542539000000008 387CO 1.257970000000000 Eagles Nest Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Eagles%20Nest FS 0.05923840000000008 388CO 0.522076000000000 Ptarmigan Peak Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Ptarmigan%20Peak FS 0.00574553000000008 389CO 1.078160000000000 Mount Evans Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Evans FS 0.03254480000000008 390CO 1.438710000000000 Holy Cross Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Holy%20Cross FS 0.05451810000000008 391CO 1.463510000000000 Lost Creek Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Lost%20Creek FS 0.04967140000000008 396CO 1.063300000000000 Hunter-Fryingpan Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Hunter%2DFryingpan FS 0.03224480000000008 398CO 1.458040000000000 Maroon Bells-Snowmass Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Maroon%20Bells%2DSnowmass FS 0.07808400000000008 399CO 0.738527000000000 Mount Massive Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Massive FS 0.01047520000000008 400CO 0.193332000000000 Mount Massive Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Massive FWS 0.00093778200000008 401CO 0.820306000000000 Buffalo Peaks Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Buffalo%20Peaks FS 0.01711430000000008 404CO 2.025460000000000 Collegiate Peaks Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Collegiate%20Peaks FS 0.07376710000000008 405CO 0.907013000000000 Raggeds Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Raggeds FS 0.02990640000000008 406CO 1.644000000000000 West Elk Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=West%20Elk FS 0.07623760000000008 415CO 0.538332000000000 Fossil Ridge Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Fossil%20Ridge FS 0.01317560000000008 419CO 0.826888000000000 Gunnison Gorge Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Gunnison%20Gorge BLM 0.00739996000000008 420CO 0.707377000000000 Black Canyon of the Gunnison Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Black%20Canyon%20of%20the%20GunnisonNPS 0.00663470000000008 425CO 0.735176000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.01336290000000008 427CO 0.393427000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00685532000000008 433CO 0.829067000000000 Powderhorn Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Powderhorn BLM 0.01925610000000008 438CO 0.446917000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00708528000000008 440CO 1.224290000000000 Uncompahgre Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Uncompahgre FS 0.04125950000000008 442CO 0.047141800000000 Uncompahgre Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Uncompahgre BLM 0.00013151100000008 444CO 0.349875000000000 Powderhorn Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Powderhorn FS 0.00614416000000008 446CO 0.733543000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00914042000000008 447CO 0.065853400000000 Uncompahgre Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Uncompahgre BLM 0.00026714500000008 449CO 1.616820000000000 La Garita Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=La%20Garita FS 0.05340450000000008 451CO 0.407763000000000 Mount Sneffels Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Sneffels FS 0.00702802000000008 452CO 0.196611000000000 Uncompahgre Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Uncompahgre BLM 0.00150059000000008 454CO 0.860610000000000 Box-Death Hollow Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Box%2DDeath%20Hollow FS 0.00997307000000049 455UT 0.779127000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo NPS 0.01755200000000008 458CO 0.561821000000000 Greenhorn Mountain Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Greenhorn%20Mountain FS 0.00975499000000008 461CO 0.171344000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00099257300000008 462CO 0.697435000000000 Lizard Head Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Lizard%20Head FS 0.01764510000000008 465CO 1.742490000000000 Dark Canyon Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Dark%20Canyon FS 0.02586860000000049 468UT 0.574187000000000 Great Sand Dunes Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Great%20Sand%20Dunes NPS 0.01359200000000008 470CO 0.133740000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00093922600000008 471CO 3.301370000000000 Weminuche Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Weminuche FS 0.19608500000000008 472CO 0.454338000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00750880000000008 479CO 0.275139000000000 Ashdown Gorge Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Ashdown%20Gorge FS 0.00342165000000049 480UT 0.336214000000000 Sangre de Cristo Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Sangre%20de%20Cristo FS 0.00394863000000008 482CO 0.191092000000000 Weminuche Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Weminuche FS 0.00123684000000008 487CO 0.736247000000000 Pine Valley Mountain Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Pine%20Valley%20Mountain FS 0.02126900000000049 499UT 2.032130000000000 South San Juan Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=South%20San%20Juan FS 0.07043340000000008 504CO 0.263251000000000 Mesa Verde Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mesa%20Verde NPS 0.00218289000000008 509CO 0.119581000000000 Mesa Verde Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mesa%20Verde NPS 0.00053934000000008 510CO 0.120627000000000 Mesa Verde Wilderness Wilderness http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mesa%20Verde NPS 0.00081771100000008 511CO PK~VMQjcoutwildrnp.prjGEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]PK~VMhcoutwildrnp.shpPK~VM5֯||coutwildrnp.shxPK~VMug8®coutwildrnp.dbfPK~VMQjWcoutwildrnp.prjPKXFiona-1.10.1/tests/data/curves_line.csv000066400000000000000000000166071467206072700177610ustar00rootroot00000000000000WKT,SHAPE_Length "MULTILINESTRING ((-1.02439 48.4878,2.471545 48.45528))",3.49608621305261 "MULTICURVE (COMPOUNDCURVE ((-0.9105691 47.21951,1.414634 47.17073),CIRCULARSTRING (1.414634 47.17073,2.423818 47.48377,1.407531 46.72668),(1.407531 46.72668,-0.9243407 46.72668)))",8.39459167219456 "MULTICURVE (COMPOUNDCURVE (CIRCULARSTRING (-0.3902439 46.42109,0.2422325 45.78862,-0.3902439 45.15614,-1.02272 45.78862,-0.3902439 46.42109)))",3.97396663612273 "MULTILINESTRING ((2.404137 38.88428,2.475991 38.93491,2.54878 38.98351,2.622149 39.02986,2.69574 39.07372,2.769195 39.11488,2.842157 39.15314,2.914269 39.18832,2.98518 39.22023,3.054546 39.24874,3.122027 39.2737,3.187295 39.29498,3.250033 39.31248,3.309934 39.32613,3.366707 39.33584,3.420075 39.34158,3.469778 39.34331,3.515575 39.34104,3.557241 39.33476,3.594574 39.32451,3.627391 39.31034,3.655534 39.29232,3.678865 39.27053,3.697271 39.24509,3.710661 39.21612,3.71897 39.18375,3.722158 39.14814,3.72021 39.10948,3.713135 39.06794,3.700967 39.02373,3.683765 38.97707,3.661614 38.92818,3.634622 38.87729,3.602919 38.82467,3.566661 38.77056,3.526023 38.71523,3.481205 38.65895,3.432424 38.60199,3.379918 38.54462,3.323942 38.48714,3.26477 38.42982,3.20269 38.37293,3.138004 38.31676,3.071028 38.26158,3.002087 38.20766,2.931517 38.15525,2.859663 38.10463,2.786874 38.05602,2.713506 38.00968,2.639914 37.96582,2.566459 37.92466,2.493498 37.88639,2.421386 37.85122,2.350474 37.8193,2.281109 37.79079,2.213628 37.76584,2.148359 37.74456,2.085621 37.72705,2.02572 37.71341,1.968947 37.70369,1.915579 37.69795,1.865876 37.69622,1.82008 37.6985,1.778414 37.70477,1.741081 37.71502,1.708263 37.72919,1.68012 37.74721,1.656789 37.769,1.638384 37.79444,1.624994 37.82342,1.616684 37.85579,1.613496 37.89139,1.615444 37.93006,1.62252 37.97159,1.634688 38.0158,1.651889 38.06247,1.67404 38.11136,1.701033 38.16224,1.732735 38.21486,1.768994 38.26897,1.809631 38.3243,1.854449 38.38059,1.90323 38.43755,1.955737 38.49491,2.011712 38.55239,2.070884 38.60972,2.132964 38.6666,2.19765 38.72277,2.264627 38.77795,2.333568 38.83188,2.404137 38.88428))",5.67762431364471 "MULTILINESTRING ((-0.6666667 44.03252,-0.6056813 44.10943,-0.5428571 44.18257,-0.4782797 44.25197,-0.4120346 44.31763,-0.3442073 44.37959,-0.2748833 44.43785,-0.2041482 44.49243,-0.1320875 44.54336,-0.0587867 44.59064,0.0156686 44.63431,0.091193 44.67437,0.167701 44.71084,0.2451069 44.74375,0.3233253 44.77311,0.4022706 44.79894,0.4818574 44.82125,0.5620001 44.84007,0.6426132 44.85542,0.7236111 44.8673,0.8049083 44.87574,0.8864194 44.88076,0.9680587 44.88238,1.049741 44.88061,1.13138 44.87547,1.212891 44.86698,1.294188 44.85516,1.375186 44.84003,1.455799 44.8216,1.535942 44.79989,1.615529 44.77492,1.694474 44.74671,1.772693 44.71528,1.850099 44.68064,1.926607 44.64281,2.002131 44.60182,2.076586 44.55767,2.149887 44.51039,2.221948 44.46,2.292683 44.4065))",3.34511332340398 "MULTICURVE (COMPOUNDCURVE ((-1.300813 42.89431,0.3902439 43.31707),CIRCULARSTRING (0.3902439 43.31707,1.4163 43.74383,2.455285 43.34959),(2.455285 43.34959,2.455121 43.34941,2.454636 43.34885,2.453842 43.34794,2.452751 43.34666,2.451373 43.34503,2.44972 43.34305,2.447803 43.34073,2.445634 43.33807,2.443223 43.33507,2.440583 43.33175,2.437724 43.3281,2.434658 43.32413,2.431396 43.31985,2.42795 43.31526,2.42433 43.31037,2.420549 43.30517,2.416617 43.29968,2.412546 43.2939,2.408347 43.28784,2.404032 43.28149,2.399611 43.27487,2.395096 43.26799,2.390499 43.26083,2.385831 43.25342,2.381103 43.24575,2.376327 43.23783,2.371513 43.22966,2.366673 43.22126,2.361819 43.21262,2.356962 43.20374,2.352112 43.19465,2.347283 43.18533,2.342484 43.17579,2.337727 43.16605,2.333023 43.1561,2.328385 43.14594,2.323822 43.13559,2.319347 43.12505,2.314971 43.11433,2.310704 43.10342,2.30656 43.09233,2.302547 43.08108,2.29868 43.06965,2.294967 43.05807,2.291421 43.04633,2.288054 43.03443,2.284875 43.02239,2.281898 43.01021,2.279132 42.99789,2.27659 42.98543,2.274283 42.97285,2.272222 42.96015,2.270418 42.94733,2.268883 42.9344,2.267628 42.92136,2.266664 42.90821,2.266003 42.89497,2.265657 42.88164,2.265635 42.86821,2.265951 42.85471,2.266614 42.84112,2.267637 42.82747,2.26903 42.81374,2.270806 42.79995,2.272975 42.7861,2.275548 42.7722,2.278538 42.75825,2.281955 42.74426,2.28581 42.73022,2.290116 42.71616,2.294883 42.70206,2.300123 42.68794,2.305847 42.6738,2.312066 42.65965,2.318792 42.64548,2.326036 42.63132,2.333809 42.61715,2.342123 42.60299,2.350989 42.58883,2.360418 42.5747,2.370422 42.56058,2.381012 42.54649,2.392199 42.53243,2.403995 42.5184,2.416411 42.50442,2.429458 42.49047,2.443148 42.47658,2.457492 42.46274,2.472501 42.44896,2.488187 42.43525,2.504561 42.4216,2.521634 42.40803,2.539418 42.39454,2.557924 42.38113,2.577163 42.36781,2.597146 42.35459,2.617886 42.34146),(2.617886 42.34146,2.636783 42.32997,2.656209 42.31853,2.676146 42.30716,2.696577 42.29584,2.717483 42.28458,2.738844 42.27338,2.760644 42.26223,2.782863 42.25113,2.805484 42.24007,2.828486 42.22907,2.851854 42.21811,2.875567 42.2072,2.899607 42.19633,2.923957 42.18549,2.948598 42.1747,2.97351 42.16394,2.998677 42.15321,3.024079 42.14252,3.049698 42.13186,3.075516 42.12122,3.101515 42.11062,3.127675 42.10003,3.153979 42.08947,3.180407 42.07893,3.206943 42.06841,3.233567 42.0579,3.260261 42.04741,3.287006 42.03694,3.313785 42.02647,3.340578 42.01602,3.367367 42.00557,3.394135 41.99512,3.420862 41.98468,3.44753 41.97424,3.474121 41.96381,3.500616 41.95336,3.526997 41.94292,3.553246 41.93247,3.579344 41.92201,3.605273 41.91154,3.631014 41.90106,3.656549 41.89057,3.681859 41.88006,3.706927 41.86953,3.731733 41.85898,3.75626 41.84841,3.780489 41.83782,3.804401 41.82721,3.827979 41.81656,3.851203 41.80589,3.874056 41.79519,3.896518 41.78445,3.918573 41.77368,3.9402 41.76288,3.961382 41.75203,3.982101 41.74115,4.002337 41.73022,4.022073 41.71925,4.041291 41.70823,4.059971 41.69717,4.078095 41.68605,4.095646 41.67488,4.112604 41.66366,4.128951 41.65239,4.144669 41.64105,4.15974 41.62966,4.174144 41.6182,4.187864 41.60669,4.200882 41.5951,4.213178 41.58345,4.224734 41.57174,4.235533 41.55995,4.245555 41.54808,4.254783 41.53615,4.263197 41.52413,4.27078 41.51204,4.277512 41.49987,4.283376 41.48762,4.288354 41.47528,4.292426 41.46285,4.295575 41.45034,4.297782 41.43774,4.299028 41.42504,4.299296 41.41226,4.298567 41.39937,4.296822 41.38639,4.294043 41.37331,4.290211 41.36013,4.285309 41.34685,4.279318 41.33346,4.272219 41.31996,4.263995 41.30635,4.254626 41.29264,4.244094 41.27881,4.232381 41.26486,4.219468 41.2508,4.205338 41.23662,4.189971 41.22232,4.17335 41.2079,4.155455 41.19336,4.136269 41.17868,4.115773 41.16388,4.093948 41.14896,4.070777 41.13389,4.046241 41.1187,4.020321 41.10337,3.993 41.0879,3.964258 41.07229,3.934077 41.05654,3.902439 41.04065),CIRCULARSTRING (3.902439 41.04065,1.775383 40.65987,0.3414634 42.27642)))",12.2623236074563 "MULTILINESTRING ((-0.2762998 38.32375,-0.2637102 38.43947,-0.2447018 38.55117,-0.2193601 38.65833,-0.1877989 38.76047,-0.1501601 38.85714,-0.1066131 38.94789,-0.0573536 39.03233,-0.002603 39.11007,0.0573925 39.18076,0.1223631 39.24409,0.1920168 39.29976,0.2660403 39.34754,0.3441008 39.3872))",1.29261161044762 "MULTICURVE (COMPOUNDCURVE (CIRCULARSTRING (-1.389372 40.02584,-1.109435 40.65503,-0.4250745 40.73184),CIRCULARSTRING (-0.4250745 40.73184,-0.2233581 40.09231,0.4014657 40.33579)),COMPOUNDCURVE (CIRCULARSTRING (0.9008338 40.26691,1.138662 40.45594,1.434641 40.38745)))",3.57349361227513 "MULTILINESTRING ((1.383736 39.35035,1.012627 38.5647,0.5434618 37.97689,-0.0220862 37.58902))",2.3133339931156 Fiona-1.10.1/tests/data/example.topojson000066400000000000000000000014661467206072700201530ustar00rootroot00000000000000{ "type": "Topology", "objects": { "example": { "type": "GeometryCollection", "geometries": [ { "type": "Point", "properties": { "prop0": "value0" }, "coordinates": [102, 0.5] }, { "type": "LineString", "properties": { "prop0": "value0", "prop1": 0 }, "arcs": [0] }, { "type": "Polygon", "properties": { "prop0": "value0", "prop1": { "this": "that" } }, "arcs": [[-2]] } ] } }, "arcs": [ [[102, 0], [103, 1], [104, 0], [105, 1]], [[100, 0], [101, 0], [101, 1], [100, 1], [100, 0]] ] } Fiona-1.10.1/tests/data/gre.cpg000066400000000000000000000000061467206072700161600ustar00rootroot00000000000000UTF-8 Fiona-1.10.1/tests/data/gre.dbf000066400000000000000000000054221467206072700161510ustar00rootroot00000000000000_AflagCPnameCPname_csCPname_deCPname_enCPname_eoCPname_frCPname_fyCPname_hrCPname_nlCPname_ruCPname_slCPname_taCPname_ukCPboundaryCPname_tzlCPtimezoneCPwikidataCPISO3166-1CPwikipediaCPadmin_leveCPis_in_contCPISO3166-1_CPISO3166-_1CPISO3166-_2CP http://upload.wikimedia.org/wikipedia/commons/b/bc/Flag_of_Grenada.svg Grenada Grenada Grenada Grenada Grenado Grenade Grenada Grenada Grenada Гренада Grenada கிரெனடா Гренада administrative Grenada America/Grenada Q769 GD en:Grenada 2 North America GD GRD 308 Fiona-1.10.1/tests/data/gre.prj000066400000000000000000000002171467206072700162060ustar00rootroot00000000000000GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]Fiona-1.10.1/tests/data/gre.shp000066400000000000000000000154741467206072700162200ustar00rootroot00000000000000' JOOn'@+N:|1)@ hJOOn'@+N:|1)@+NZ)@7,NI +)@0 /NQ)@6NPn)@>N/$)@_,gJN"(@XNrh(@NWsN`"(@9N+(@ *N~j(@PіNl(@\bNV-(@yN!rh(@ X3NQ(@7vYNx(@INCl(@˹^䯗NK7A`(@[NM(@ INZd;(@bhN\((@#N'1(@4`Njt(@CœENףp= (@zܞ{N/$(@nݳN(@(hNOn(@I?NMb(@-~N+(@WiךN(@z]uN%C(@U/}NK7(@ɛNHz(@3Nʡ(@9"%Nkt(@ZJ;ݜN(\(@t>+8N/$(@ ]N"(@%rN> ףp(@cyNCl(@{N|?5^(@e _NFԸ(@:p՟NK(@r>@RN(\µ(@ՠNObX9(@yPNʡE(@4"MNl(@]#N㥛 (@yNGz(@]F?NC(@FN^I (@J?Nx(@hh6N-(@yՏNgffff(@rN/ݤ(@y3MNS(@KfNoʡ(@pNT㥛Ġ(@vbcNZd;(@f<ޮN|?5(@r#N&1(@qqtNS㥛(@X:zWNB`"(@bX?N +(@DNףp= (@)ŎơNE(@uRNl(@A1kN7A`(@iTQN(\(@ŽC-NZd;O(@PoNƋ(@)XN ףp=(@CNbX9(@M񸨪NI +(@Nʡ(@Njt(@˫NPn(@ BNK7(@Q>N⽬N(@*Nrh|(@~NzGz(@jNEx(@-NQN(\u(@w`CNʡEs(@*9Nlq(@(1NU-o(@f_+N rhm(@o5)NQk(@DB)Nxi(@%̴+NClg(@×.i1NK7A`e(@>NMb(@1JNK7A`(@7dYN|?5^(@L!uNA`"[(@d-ꊬN +Y(@(Nףp= W(@*ͬNS(@-^rNRQ(@EuNZd;O(@INL(@HFsNnJ(@<~oӟNbX9H(@4NʡE(@)z;N$C(@oz5UNK7A(@?եNrh|?(@saϮN= ףp=(@[;QNZd;(@CFN`"9(@Vl5N+7(@ɯNʡE6(@ۅNNbX94(@`0$YN 2(@u&"Nq= ף0(@BwNv/(@&z7XNV-(@U&NQ+(@r"Nx)(@)]aN-'(@PkwNx&(@_ѭNp= #(@ղNo!(@fN:v(@#d .NV-(@]zNv(@d[GN~jt(@NI +(@71$'NGz(@!gNOn(@hN7A`(@EeN+(@'NZd;O (@gUN (@x-בN ףp= (@XANx&1(@W l)N-(@)Nx&'@'acNq= ף'@^EFNv'@hN!rh'@ZsNy&1'@oN^I '@*x+N"~'@DNx'@q99N r'@׃7Nx'@s NS'@wWN/ݤ'@EfӶNS'@9͂NM'@ՈNzG'@R:GӏNK7A'@Ps'N:v'@ԅpNQ'@%N|?5'@iNw/'@N&1'@8 QNS㥛'@NB`"'@]Nv'@DN'1'@pSN'@\N +'@¯zN~jt'@g+NMb'@RN~jt'@mNl'@ @-\NOn'@(NOn'@uNl'@܇N~jt'@ ZN'@ D NGz'@xNE'@49k1N/$'@uNt'@@v(NI +'@\XNףp= '@_qcNMb'@uDN +'@ 1N'1'@>;bNB`"'@N]('@^Nw/'@ĐANQ'@`N:v'@heNzG'@W|PNA`Т'@@MN1Z'@+NS'@"NCl'@w3N)\'@ٴN"~'@1M"Ny&1'@ЊNGz'@mNV-'@vp Nl'@IQg!NʡE'@[aUNʡE'@Cҵ@NFԸ'@N|?5^'@NCl'@$TNˡE'@&,N'@qNK7'@hNPn'@V4qNS㥛'@ 6uN-'@,)pNcX9'@nNn'@덵N'@,|N(\'@7+Nv/(@W6Nl1(@?7oNNbX94(@Л NX96(@sNMbX9(@w6NCl;(@vKrNX9v>(@BN/$A(@U6NK7A(@KdNS㥛D(@`2NGzG(@6SNK(@;PNʡE(@IN,η(@~*ZNFԸ(@WN|G(@c2Nrh(@|dNY9v(@fNsh|(@-iNn(@lNK7(@d\*NPn(@;Nkt(@I̶.Nʡ(@/N-(@);NI +(@YTz\NK7(@>xN%C(@R5NZd;O(@ދN+(@7N7A`(@XrNm(@d`xNE(@Qy9NJ +(@uۉNMb(@PN(@']kLNA`"(@D:N&1(@-N|?5(@)MAN9v(@dNzG(@ݯ|NA`(@* `NS(@SYNK7A`(@辜NCl(@;2Nx(@pNQ(@%6.NGz(@jinN䥛 (@&G%+NV-(@NNbX9(@PNʡ)@jxNbX9)@75JN )@,N+)@^NR)@ NE)@D˥LNMb)@~ -0.9243407 46.27182575052962.70658958605966 47.6054714507864 -0.9243407 46.27182575052962.70658958605966 47.6054714507864 -0.9105691 47.21951 1.414634 47.170731.414634 47.17073 2.423818 47.48377 1.407531 46.726681.407531 46.72668 -0.9243407 46.72668 MULTICURVE (COMPOUNDCURVE ((-0.9105691 47.21951,1.414634 47.17073),CIRCULARSTRING (1.414634 47.17073,2.423818 47.48377,1.407531 46.72668),(1.407531 46.72668,-0.9243407 46.72668))) 8.39459167219456 Fiona-1.10.1/tests/data/multicurve.xsd000066400000000000000000000047021467206072700176360ustar00rootroot00000000000000 0 Fiona-1.10.1/tests/data/rmnp.geojson000066400000000000000000000311251467206072700172600ustar00rootroot00000000000000{"features": [{"geometry": {"coordinates": [[[[-105.82993236599998, 40.23429787900005], [-105.82532010999995, 40.234090120000076], [-105.80130186799994, 40.238022355000055], [-105.79971981686558, 40.23834806093726], [-105.80615125399999, 40.248891729000036], [-105.80604781499994, 40.25259529400006], [-105.80323751899999, 40.256223652000074], [-105.84344629799995, 40.256590533000065], [-105.85209887999997, 40.27187296100004], [-105.85215931999994, 40.27205192000008], [-105.86034979199997, 40.30162991100008], [-105.86041877199995, 40.30189618600008], [-105.86043595199999, 40.302194443000076], [-105.86044234999997, 40.30242113800006], [-105.85872471399995, 40.305752203000054], [-105.85865781799998, 40.30587968700007], [-105.85858570499994, 40.30600321500003], [-105.85779012199998, 40.306659301000025], [-105.87150570599994, 40.31460928800004], [-105.87096989399998, 40.34002330200008], [-105.87089412899996, 40.34472017200005], [-105.86851234235445, 40.36866639070081], [-105.87465775499999, 40.36700144500003], [-105.89323599499994, 40.37071941500005], [-105.91207991899995, 40.37242368300008], [-105.91272218299997, 40.37249360700008], [-105.91287933999996, 40.37253276000007], [-105.91360267899995, 40.372845693000045], [-105.91372432699995, 40.37293256000004], [-105.90505138299994, 40.39897570900007], [-105.90346006599998, 40.431925275000026], [-105.90344864899998, 40.43216639100007], [-105.89927318399998, 40.465591937000056], [-105.89922084599999, 40.465786036000054], [-105.89880951399999, 40.466564007000045], [-105.89822195199997, 40.46764514500006], [-105.89732067099999, 40.46917015400004], [-105.89188595999997, 40.476450914000054], [-105.89177666799998, 40.47657788200007], [-105.89159512399999, 40.47670540800004], [-105.88663187599997, 40.47736520700005], [-105.85499564999998, 40.486197454000035], [-105.85496571099998, 40.48620149300007], [-105.85489999899994, 40.486210355000026], [-105.85474552299996, 40.48623118900008], [-105.83766572599995, 40.48284897600007], [-105.83753977299995, 40.48282070100004], [-105.82538607593936, 40.477886708345466], [-105.81588885599996, 40.484865049000064], [-105.80526615399998, 40.49096921100005], [-105.80478208599999, 40.49119330100007], [-105.79614713399997, 40.49419001000007], [-105.76340551899995, 40.51327554900007], [-105.75138246899996, 40.52241727900008], [-105.75068548999997, 40.52287225300006], [-105.75041200099997, 40.52302718000004], [-105.74898221500828, 40.52333502440806], [-105.75294155799997, 40.53132139600007], [-105.75297840399998, 40.531415749000075], [-105.75298352099998, 40.531510308000065], [-105.75304362899999, 40.53500084500007], [-105.75046013199994, 40.538454529000035], [-105.75026447499994, 40.53870009700006], [-105.75022003999999, 40.538730858000065], [-105.73601753799994, 40.54698421300003], [-105.73543265699999, 40.54729423800006], [-105.73490789599998, 40.54750027600005], [-105.73417386699998, 40.54760404000007], [-105.72224790899998, 40.54923323300005], [-105.70060787699998, 40.553433977000054], [-105.69951366899994, 40.553625289000024], [-105.69846461499998, 40.55379379500005], [-105.69740136799999, 40.55376868700006], [-105.69590444199997, 40.553529971000046], [-105.68403573899997, 40.55086216500007], [-105.68354186699997, 40.55067590200008], [-105.68264478099996, 40.55025328600004], [-105.68224110499995, 40.550043963000064], [-105.68188181999994, 40.54981710000004], [-105.68171810199999, 40.549713723000025], [-105.68137426799996, 40.54947251200008], [-105.67910352499996, 40.54780573200003], [-105.67111994099997, 40.539478649000046], [-105.67089639699998, 40.53922769900004], [-105.67076231199997, 40.53906631800004], [-105.66685877099997, 40.533507919000044], [-105.66666479199995, 40.53322075400007], [-105.66460226864893, 40.52988730500347], [-105.64525353599998, 40.532981953000046], [-105.62241943799995, 40.53981883100005], [-105.62153453099995, 40.540057825000076], [-105.62042534799997, 40.54030702600005], [-105.62003581299996, 40.54036317100008], [-105.61955640099995, 40.54039727300005], [-105.61904733599994, 40.54033694000003], [-105.60211979999997, 40.53829128900003], [-105.60162589399994, 40.538230800000065], [-105.60016054299996, 40.53781500300005], [-105.59972731399995, 40.537565004000044], [-105.59929443299995, 40.53722941700005], [-105.59910215099995, 40.537014759000044], [-105.59906149099999, 40.53696936700004], [-105.59893700599997, 40.53683037400003], [-105.57756808599999, 40.51851144700004], [-105.57734419699995, 40.518386441000075], [-105.57649357899999, 40.51787717900004], [-105.57619528499998, 40.51763542600003], [-105.57028856699998, 40.513556933000075], [-105.57010850299997, 40.51345394200007], [-105.56840699299994, 40.51241728800005], [-105.53543969099997, 40.51327744200006], [-105.53504912299996, 40.513517988000046], [-105.53257093699995, 40.51499789300004], [-105.53209022499999, 40.51527037500006], [-105.53189886999996, 40.515325492000045], [-105.53156575899999, 40.515421436000054], [-105.52984182799997, 40.515825747000065], [-105.51756163999994, 40.51918768100006], [-105.51739464799999, 40.51920669100008], [-105.51700292299995, 40.51227282200006], [-105.51681120999996, 40.508877280000036], [-105.51661963499998, 40.50548173400006], [-105.51642772099996, 40.50208528600007], [-105.51623569999998, 40.498688833000074], [-105.51616764099998, 40.49747991800007], [-105.51615748699999, 40.497299560000044], [-105.51614708899996, 40.497114870000075], [-105.51604451099996, 40.49529272700005], [-105.51585310799999, 40.49189670900006], [-105.51566160099998, 40.488499699000045], [-105.51546987699999, 40.485102598000026], [-105.51527841899997, 40.48170648400003], [-105.51508686399995, 40.47831036700006], [-105.51489496899995, 40.47491325900006], [-105.51470297699996, 40.471516149000024], [-105.51451112999996, 40.46811993700004], [-105.51273101209253, 40.43773005864719], [-105.51268141899999, 40.43771853100003], [-105.49359377399998, 40.433632946000046], [-105.49384450399998, 40.42265419300003], [-105.49445713499995, 40.40834343900008], [-105.49462260299998, 40.404728686000055], [-105.51175716599994, 40.392979152000066], [-105.52357935999999, 40.38692618600004], [-105.53802058899998, 40.38253243100007], [-105.55202337099996, 40.38337468200007], [-105.56086616230968, 40.38911621698159], [-105.55199686599997, 40.37976664800004], [-105.54432240599999, 40.36429782000005], [-105.54390615499995, 40.36299049200005], [-105.54997263999996, 40.36027357100005], [-105.57119972699996, 40.348654846000045], [-105.58199381599997, 40.32891757900006], [-105.58199446199995, 40.32889092400006], [-105.58205034399998, 40.326759225000046], [-105.57230443299994, 40.32527691300004], [-105.56764467899995, 40.32528722600006], [-105.56268212499998, 40.325360086000046], [-105.55805262399997, 40.325314278000064], [-105.55330571199994, 40.32532461400007], [-105.54849582199995, 40.325334963000046], [-105.54383945699999, 40.318265127000075], [-105.53509543499996, 40.31407892900006], [-105.53403439699997, 40.30697408900005], [-105.53362917399994, 40.29996820400004], [-105.53743801499996, 40.30014405900005], [-105.53829550999995, 40.300192554000034], [-105.53857702999994, 40.300208473000055], [-105.54211996799995, 40.30046056600003], [-105.55233481799996, 40.29336605000003], [-105.55659629599995, 40.284294583000076], [-105.55658333899999, 40.28274042900006], [-105.55638339299998, 40.27564530300003], [-105.54206666299996, 40.26143052400005], [-105.54175131899996, 40.24686076900008], [-105.54177471199995, 40.24486010000004], [-105.53265796099998, 40.225078703000065], [-105.53268332499994, 40.21936352200004], [-105.53370137907686, 40.21890372610939], [-105.53264866599994, 40.216782204000026], [-105.53282174599997, 40.21053215400008], [-105.54226766999994, 40.19058178800003], [-105.57130617599995, 40.165553240000065], [-105.57154539499999, 40.16537187000006], [-105.57194869999995, 40.16510860400007], [-105.57850588199994, 40.16121546900007], [-105.57889369499998, 40.16107838800008], [-105.57917676799997, 40.16100940200005], [-105.57944527699999, 40.16095400200004], [-105.59509419199998, 40.15830693100003], [-105.59555592999999, 40.15823698500003], [-105.59665870999999, 40.158082652000076], [-105.59761194299995, 40.15807323100006], [-105.60285317299997, 40.15809561300006], [-105.60294910099998, 40.15811294400004], [-105.60370143199998, 40.158248859000025], [-105.62170529799994, 40.16156817700005], [-105.65465245599995, 40.16767968000005], [-105.66688537484622, 40.17000001598165], [-105.66686555399997, 40.16991096000004], [-105.67000857399995, 40.16730004600004], [-105.67016297799995, 40.167195606000064], [-105.67141738899994, 40.16646097700004], [-105.67154528999998, 40.16646576100004], [-105.69696639199998, 40.162049558000035], [-105.69735396799996, 40.16194812100008], [-105.69783084399995, 40.161823620000064], [-105.69809911899995, 40.16179047000003], [-105.69856090999997, 40.161769666000055], [-105.69885836099996, 40.161790394000036], [-105.70324941299998, 40.16243050500003], [-105.70347260999995, 40.16247419700005], [-105.70375553299999, 40.16254455300003], [-105.70390424299995, 40.16258869600006], [-105.70415694699994, 40.16271779600004], [-105.70531632599995, 40.16340446600003], [-105.71618835799995, 40.16834696700005], [-105.71659006199997, 40.16850665100003], [-105.71685758599995, 40.16864464100007], [-105.71702112799994, 40.16880579800005], [-105.71722881499994, 40.169011728000044], [-105.71743665699995, 40.169267209000054], [-105.71771866899996, 40.16963034200006], [-105.74348628399997, 40.168688426000074], [-105.74383073499996, 40.16800149800008], [-105.74411460099998, 40.167648307000036], [-105.74439820099997, 40.16737169700008], [-105.74468148799997, 40.16723473400003], [-105.75252233799995, 40.16418840700004], [-105.75953475599994, 40.16484084700005], [-105.75999619699996, 40.16488737000003], [-105.76080028999996, 40.16499020200007], [-105.76197617699995, 40.16516265800004], [-105.76516258499998, 40.16561463800008], [-105.76550512899996, 40.16569345800008], [-105.77107118899994, 40.167417862000036], [-105.79663688699998, 40.17037553900008], [-105.79802255699997, 40.16995606900008], [-105.79875292299994, 40.16973931200005], [-105.79921441199997, 40.169691080000064], [-105.81725341699996, 40.16740740300003], [-105.81999581999997, 40.16951501300008], [-105.82021443499997, 40.17101724500003], [-105.82022265399996, 40.171120795000036], [-105.82022362999999, 40.17120187300003], [-105.82600834399994, 40.19147235300005], [-105.83044446399998, 40.19612547200006], [-105.83056168099995, 40.19622373400006], [-105.83415290399995, 40.19890521700006], [-105.83590610799996, 40.20007279200007], [-105.83622886799998, 40.20031371300007], [-105.83724425399998, 40.201819940000064], [-105.83997704999996, 40.20730591200004], [-105.84082691599997, 40.20925216400008], [-105.84235351099994, 40.214368886000045], [-105.84235770799995, 40.21444093000008], [-105.84207465799994, 40.21575384300007], [-105.83545929499996, 40.234072627000046], [-105.83526359799998, 40.23451919900003], [-105.82993236599998, 40.23429787900005]], [[-105.58371488559169, 40.40142222697275], [-105.57356491679676, 40.39736138986373], [-105.57949503399999, 40.40121175500008], [-105.58371488559169, 40.40142222697275]]], [[[-105.52387338099999, 40.29587849500007], [-105.51833431299997, 40.289190561000055], [-105.51805444999997, 40.289083703000074], [-105.51783916499994, 40.28900808800006], [-105.51771470599999, 40.28891414100008], [-105.51770271699996, 40.28890509200005], [-105.51753073399999, 40.28877072200004], [-105.51610842599996, 40.28685359100007], [-105.51610838199997, 40.28685353000003], [-105.51606038099999, 40.28678602700006], [-105.51593599699999, 40.286611109000035], [-105.51585722899995, 40.286453797000036], [-105.51584008899994, 40.28641720100006], [-105.51577090499995, 40.28626949100004], [-105.51573753999998, 40.28616337100004], [-105.51568463099994, 40.285995092000064], [-105.51342582599995, 40.27889229300007], [-105.51204804099996, 40.27581721400003], [-105.51178041799994, 40.27498503600003], [-105.51168516799999, 40.27464401300006], [-105.51191604799999, 40.274650560000055], [-105.51389726599996, 40.27470672100003], [-105.51864548699996, 40.27484292500003], [-105.52323037599996, 40.26789955000004], [-105.52782832999998, 40.26816456300003], [-105.52793138299995, 40.27156944300003], [-105.52804855399995, 40.27511655600006], [-105.52817409, 40.27859085100005], [-105.52823393999995, 40.28210154200008], [-105.52831281699997, 40.28556538600003], [-105.53304071299999, 40.289422278000075], [-105.53352711399998, 40.297435663000044], [-105.53362917399994, 40.29996820400004], [-105.52898800099996, 40.29965667500005], [-105.52419156499997, 40.29937584700008], [-105.52417450299998, 40.298356116000036], [-105.52387338099999, 40.29587849500007]]]], "type": "MultiPolygon"}, "id": "0:0", "properties": {}, "type": "Feature"}], "type": "FeatureCollection"} Fiona-1.10.1/tests/data/sequence-pp.txt000066400000000000000000000311561467206072700177100ustar00rootroot00000000000000{ "geometry": { "type": "Polygon", "coordinates": [ [ [ -111.73527526855469, 41.995094299316406 ], [ -111.65931701660156, 41.99627685546875 ], [ -111.6587142944336, 41.9921875 ], [ -111.65888977050781, 41.95676803588867 ], [ -111.67082977294922, 41.91230010986328 ], [ -111.67332458496094, 41.905494689941406 ], [ -111.67088317871094, 41.90049362182617 ], [ -111.66474914550781, 41.893211364746094 ], [ -111.6506576538086, 41.875465393066406 ], [ -111.64759826660156, 41.87091827392578 ], [ -111.64640808105469, 41.86273956298828 ], [ -111.64334869384766, 41.858192443847656 ], [ -111.63720703125, 41.85499572753906 ], [ -111.633544921875, 41.847267150878906 ], [ -111.63053894042969, 41.83409118652344 ], [ -111.6330337524414, 41.82728576660156 ], [ -111.63983154296875, 41.8227653503418 ], [ -111.6484603881836, 41.82188034057617 ], [ -111.66077423095703, 41.82327651977539 ], [ -111.6712417602539, 41.82330322265625 ], [ -111.67618560791016, 41.82013702392578 ], [ -111.68803405761719, 41.78792953491211 ], [ -111.69361114501953, 41.77931594848633 ], [ -111.70162200927734, 41.77797317504883 ], [ -111.70901489257812, 41.77663040161133 ], [ -111.71395111083984, 41.772098541259766 ], [ -111.71891784667969, 41.763031005859375 ], [ -111.72816467285156, 41.75851058959961 ], [ -111.74726104736328, 41.75537109375 ], [ -111.75650024414062, 41.752662658691406 ], [ -111.77067565917969, 41.7445182800293 ], [ -111.77064514160156, 41.75495910644531 ], [ -111.75585174560547, 41.76219940185547 ], [ -111.7330551147461, 41.766693115234375 ], [ -111.72749328613281, 41.77212905883789 ], [ -111.71883392333984, 41.7834587097168 ], [ -111.71080780029297, 41.78889083862305 ], [ -111.70340728759766, 41.79250717163086 ], [ -111.70030212402344, 41.798404693603516 ], [ -111.70210266113281, 41.8088493347168 ], [ -111.70760345458984, 41.819759368896484 ], [ -111.71312713623047, 41.82340621948242 ], [ -111.71929168701172, 41.82341766357422 ], [ -111.72545623779297, 41.8225212097168 ], [ -111.7341537475586, 41.803016662597656 ], [ -111.740966796875, 41.79213333129883 ], [ -111.74531555175781, 41.78215408325195 ], [ -111.77122497558594, 41.7658576965332 ], [ -111.77056884765625, 41.77811813354492 ], [ -111.7662582397461, 41.778106689453125 ], [ -111.76746368408203, 41.78628158569336 ], [ -111.76253509521484, 41.78627395629883 ], [ -111.76241302490234, 41.82259750366211 ], [ -111.77104187011719, 41.8221549987793 ], [ -111.77161407470703, 41.83351135253906 ], [ -111.7333755493164, 41.84524154663086 ], [ -111.73274993896484, 41.847511291503906 ], [ -111.7376708984375, 41.84979248046875 ], [ -111.77157592773438, 41.845767974853516 ], [ -111.77215576171875, 41.85802459716797 ], [ -111.75243377685547, 41.85844802856445 ], [ -111.72467803955078, 41.86384201049805 ], [ -111.71109771728516, 41.868804931640625 ], [ -111.70182037353516, 41.87604904174805 ], [ -111.69624328613281, 41.88193893432617 ], [ -111.69497680664062, 41.88874816894531 ], [ -111.70053100585938, 41.89057540893555 ], [ -111.70793151855469, 41.88923263549805 ], [ -111.72091674804688, 41.87972640991211 ], [ -111.73388671875, 41.87384796142578 ], [ -111.75301361083984, 41.86888885498047 ], [ -111.75350952148438, 41.90249252319336 ], [ -111.74364471435547, 41.90247344970703 ], [ -111.74463653564453, 41.967864990234375 ], [ -111.7119369506836, 41.96416473388672 ], [ -111.69283294677734, 41.95912551879883 ], [ -111.68911743164062, 41.96047592163086 ], [ -111.6891098022461, 41.96320343017578 ], [ -111.69341278076172, 41.96684646606445 ], [ -111.70449829101562, 41.972320556640625 ], [ -111.7341079711914, 41.97828674316406 ], [ -111.73527526855469, 41.995094299316406 ] ] ] }, "type": "Feature", "id": "0", "properties": { "PERIMETER": 1.22107, "FEATURE2": null, "NAME": "Mount Naomi Wilderness", "FEATURE1": "Wilderness", "URL": "http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Naomi", "AGBUR": "FS", "AREA": 0.0179264, "STATE_FIPS": "49", "WILDRNP020": 332, "STATE": "UT" } } { "geometry": { "type": "Polygon", "coordinates": [ [ [ -112.00384521484375, 41.552703857421875 ], [ -112.00446319580078, 41.56586456298828 ], [ -112.0112075805664, 41.56586456298828 ], [ -112.01121520996094, 41.57902526855469 ], [ -112.01734924316406, 41.57902526855469 ], [ -112.0173568725586, 41.594459533691406 ], [ -112.02779388427734, 41.5940055847168 ], [ -112.02779388427734, 41.60171890258789 ], [ -112.03945922851562, 41.60126495361328 ], [ -112.04007720947266, 41.608524322509766 ], [ -112.04744720458984, 41.608524322509766 ], [ -112.0474624633789, 41.62804412841797 ], [ -112.05974578857422, 41.62758255004883 ], [ -112.05975341796875, 41.640296936035156 ], [ -112.050537109375, 41.64030075073242 ], [ -112.05054473876953, 41.64983367919922 ], [ -112.04132843017578, 41.64983367919922 ], [ -112.04195404052734, 41.66299819946289 ], [ -112.05793762207031, 41.662540435791016 ], [ -112.0579605102539, 41.692047119140625 ], [ -112.07394409179688, 41.692039489746094 ], [ -112.07459259033203, 41.72381591796875 ], [ -112.06167602539062, 41.72382354736328 ], [ -112.0616683959961, 41.71383285522461 ], [ -112.05490112304688, 41.713836669921875 ], [ -112.04137420654297, 41.71384048461914 ], [ -112.04138946533203, 41.7379035949707 ], [ -112.0376968383789, 41.74108123779297 ], [ -112.03339385986328, 41.741085052490234 ], [ -112.02908325195312, 41.729736328125 ], [ -112.02599334716797, 41.71657180786133 ], [ -112.0241470336914, 41.71157455444336 ], [ -112.0272216796875, 41.704769134521484 ], [ -112.02413940429688, 41.70068359375 ], [ -112.01676177978516, 41.69977951049805 ], [ -112.01615142822266, 41.7070426940918 ], [ -112.00508117675781, 41.707496643066406 ], [ -112.00508117675781, 41.66618347167969 ], [ -111.9792709350586, 41.6666374206543 ], [ -111.9786605834961, 41.653926849365234 ], [ -111.96821594238281, 41.65346908569336 ], [ -111.96760559082031, 41.6407585144043 ], [ -111.96146392822266, 41.6407585144043 ], [ -111.96025085449219, 41.61125183105469 ], [ -111.95042419433594, 41.61124801635742 ], [ -111.94796752929688, 41.60988235473633 ], [ -111.94735717773438, 41.60761260986328 ], [ -111.9522705078125, 41.60443878173828 ], [ -111.96455383300781, 41.60262680053711 ], [ -111.9682388305664, 41.60398864746094 ], [ -111.9725341796875, 41.60807418823242 ], [ -111.97560119628906, 41.60943603515625 ], [ -111.97928619384766, 41.61034393310547 ], [ -111.98542785644531, 41.609439849853516 ], [ -111.98481750488281, 41.58356475830078 ], [ -111.97868347167969, 41.58356857299805 ], [ -111.97745513916016, 41.570404052734375 ], [ -111.97132110595703, 41.57085418701172 ], [ -111.97132110595703, 41.56450271606445 ], [ -111.98297882080078, 41.564048767089844 ], [ -111.98175811767578, 41.54090118408203 ], [ -111.98176574707031, 41.53545379638672 ], [ -112.00323486328125, 41.53545379638672 ], [ -112.00384521484375, 41.552703857421875 ] ] ] }, "type": "Feature", "id": "1", "properties": { "PERIMETER": 0.755827, "FEATURE2": null, "NAME": "Wellsville Mountain Wilderness", "FEATURE1": "Wilderness", "URL": "http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Wellsville%20Mountain", "AGBUR": "FS", "AREA": 0.0104441, "STATE_FIPS": "49", "WILDRNP020": 336, "STATE": "UT" } } Fiona-1.10.1/tests/data/sequence.txt000066400000000000000000000153301467206072700172670ustar00rootroot00000000000000{"geometry": {"type": "Polygon", "coordinates": [[[-111.73527526855469, 41.995094299316406], [-111.65931701660156, 41.99627685546875], [-111.6587142944336, 41.9921875], [-111.65888977050781, 41.95676803588867], [-111.67082977294922, 41.91230010986328], [-111.67332458496094, 41.905494689941406], [-111.67088317871094, 41.90049362182617], [-111.66474914550781, 41.893211364746094], [-111.6506576538086, 41.875465393066406], [-111.64759826660156, 41.87091827392578], [-111.64640808105469, 41.86273956298828], [-111.64334869384766, 41.858192443847656], [-111.63720703125, 41.85499572753906], [-111.633544921875, 41.847267150878906], [-111.63053894042969, 41.83409118652344], [-111.6330337524414, 41.82728576660156], [-111.63983154296875, 41.8227653503418], [-111.6484603881836, 41.82188034057617], [-111.66077423095703, 41.82327651977539], [-111.6712417602539, 41.82330322265625], [-111.67618560791016, 41.82013702392578], [-111.68803405761719, 41.78792953491211], [-111.69361114501953, 41.77931594848633], [-111.70162200927734, 41.77797317504883], [-111.70901489257812, 41.77663040161133], [-111.71395111083984, 41.772098541259766], [-111.71891784667969, 41.763031005859375], [-111.72816467285156, 41.75851058959961], [-111.74726104736328, 41.75537109375], [-111.75650024414062, 41.752662658691406], [-111.77067565917969, 41.7445182800293], [-111.77064514160156, 41.75495910644531], [-111.75585174560547, 41.76219940185547], [-111.7330551147461, 41.766693115234375], [-111.72749328613281, 41.77212905883789], [-111.71883392333984, 41.7834587097168], [-111.71080780029297, 41.78889083862305], [-111.70340728759766, 41.79250717163086], [-111.70030212402344, 41.798404693603516], [-111.70210266113281, 41.8088493347168], [-111.70760345458984, 41.819759368896484], [-111.71312713623047, 41.82340621948242], [-111.71929168701172, 41.82341766357422], [-111.72545623779297, 41.8225212097168], [-111.7341537475586, 41.803016662597656], [-111.740966796875, 41.79213333129883], [-111.74531555175781, 41.78215408325195], [-111.77122497558594, 41.7658576965332], [-111.77056884765625, 41.77811813354492], [-111.7662582397461, 41.778106689453125], [-111.76746368408203, 41.78628158569336], [-111.76253509521484, 41.78627395629883], [-111.76241302490234, 41.82259750366211], [-111.77104187011719, 41.8221549987793], [-111.77161407470703, 41.83351135253906], [-111.7333755493164, 41.84524154663086], [-111.73274993896484, 41.847511291503906], [-111.7376708984375, 41.84979248046875], [-111.77157592773438, 41.845767974853516], [-111.77215576171875, 41.85802459716797], [-111.75243377685547, 41.85844802856445], [-111.72467803955078, 41.86384201049805], [-111.71109771728516, 41.868804931640625], [-111.70182037353516, 41.87604904174805], [-111.69624328613281, 41.88193893432617], [-111.69497680664062, 41.88874816894531], [-111.70053100585938, 41.89057540893555], [-111.70793151855469, 41.88923263549805], [-111.72091674804688, 41.87972640991211], [-111.73388671875, 41.87384796142578], [-111.75301361083984, 41.86888885498047], [-111.75350952148438, 41.90249252319336], [-111.74364471435547, 41.90247344970703], [-111.74463653564453, 41.967864990234375], [-111.7119369506836, 41.96416473388672], [-111.69283294677734, 41.95912551879883], [-111.68911743164062, 41.96047592163086], [-111.6891098022461, 41.96320343017578], [-111.69341278076172, 41.96684646606445], [-111.70449829101562, 41.972320556640625], [-111.7341079711914, 41.97828674316406], [-111.73527526855469, 41.995094299316406]]]}, "type": "Feature", "id": "0", "properties": {"PERIMETER": 1.22107, "FEATURE2": null, "NAME": "Mount Naomi Wilderness", "FEATURE1": "Wilderness", "URL": "http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Naomi", "AGBUR": "FS", "AREA": 0.0179264, "STATE_FIPS": "49", "WILDRNP020": 332, "STATE": "UT"}} {"geometry": {"type": "Polygon", "coordinates": [[[-112.00384521484375, 41.552703857421875], [-112.00446319580078, 41.56586456298828], [-112.0112075805664, 41.56586456298828], [-112.01121520996094, 41.57902526855469], [-112.01734924316406, 41.57902526855469], [-112.0173568725586, 41.594459533691406], [-112.02779388427734, 41.5940055847168], [-112.02779388427734, 41.60171890258789], [-112.03945922851562, 41.60126495361328], [-112.04007720947266, 41.608524322509766], [-112.04744720458984, 41.608524322509766], [-112.0474624633789, 41.62804412841797], [-112.05974578857422, 41.62758255004883], [-112.05975341796875, 41.640296936035156], [-112.050537109375, 41.64030075073242], [-112.05054473876953, 41.64983367919922], [-112.04132843017578, 41.64983367919922], [-112.04195404052734, 41.66299819946289], [-112.05793762207031, 41.662540435791016], [-112.0579605102539, 41.692047119140625], [-112.07394409179688, 41.692039489746094], [-112.07459259033203, 41.72381591796875], [-112.06167602539062, 41.72382354736328], [-112.0616683959961, 41.71383285522461], [-112.05490112304688, 41.713836669921875], [-112.04137420654297, 41.71384048461914], [-112.04138946533203, 41.7379035949707], [-112.0376968383789, 41.74108123779297], [-112.03339385986328, 41.741085052490234], [-112.02908325195312, 41.729736328125], [-112.02599334716797, 41.71657180786133], [-112.0241470336914, 41.71157455444336], [-112.0272216796875, 41.704769134521484], [-112.02413940429688, 41.70068359375], [-112.01676177978516, 41.69977951049805], [-112.01615142822266, 41.7070426940918], [-112.00508117675781, 41.707496643066406], [-112.00508117675781, 41.66618347167969], [-111.9792709350586, 41.6666374206543], [-111.9786605834961, 41.653926849365234], [-111.96821594238281, 41.65346908569336], [-111.96760559082031, 41.6407585144043], [-111.96146392822266, 41.6407585144043], [-111.96025085449219, 41.61125183105469], [-111.95042419433594, 41.61124801635742], [-111.94796752929688, 41.60988235473633], [-111.94735717773438, 41.60761260986328], [-111.9522705078125, 41.60443878173828], [-111.96455383300781, 41.60262680053711], [-111.9682388305664, 41.60398864746094], [-111.9725341796875, 41.60807418823242], [-111.97560119628906, 41.60943603515625], [-111.97928619384766, 41.61034393310547], [-111.98542785644531, 41.609439849853516], [-111.98481750488281, 41.58356475830078], [-111.97868347167969, 41.58356857299805], [-111.97745513916016, 41.570404052734375], [-111.97132110595703, 41.57085418701172], [-111.97132110595703, 41.56450271606445], [-111.98297882080078, 41.564048767089844], [-111.98175811767578, 41.54090118408203], [-111.98176574707031, 41.53545379638672], [-112.00323486328125, 41.53545379638672], [-112.00384521484375, 41.552703857421875]]]}, "type": "Feature", "id": "1", "properties": {"PERIMETER": 0.755827, "FEATURE2": null, "NAME": "Wellsville Mountain Wilderness", "FEATURE1": "Wilderness", "URL": "http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Wellsville%20Mountain", "AGBUR": "FS", "AREA": 0.0104441, "STATE_FIPS": "49", "WILDRNP020": 336, "STATE": "UT"}} Fiona-1.10.1/tests/data/test_gpx.gpx000066400000000000000000000046341467206072700173000ustar00rootroot00000000000000 11.0 10.9 10.7 10.5 10.4 10.2 10.0 10.0 10.0 10.2 10.4 10.5 10.5 10.1 9.6 9.1 8.3 7.2 6.6 Fiona-1.10.1/tests/data/test_tin.csv000066400000000000000000000003731467206072700172650ustar00rootroot00000000000000WKT,id "TIN (((0 0 0, 0 0 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 0 0 0)))",1 "TRIANGLE((0 0 0,0 1 0,1 1 0,0 0 0))",2 "GEOMETRYCOLLECTION (TIN (((0 0 0, 0 0 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 0 0 0))), TRIANGLE((0 0 0,0 1 0,1 1 0,0 0 0)))",3 Fiona-1.10.1/tests/data/test_tin.dbf000066400000000000000000000002231467206072700172170ustar00rootroot00000000000000v AQWidCP 1 Fiona-1.10.1/tests/data/test_tin.shp000066400000000000000000000004201467206072700172550ustar00rootroot00000000000000' ???R???????Fiona-1.10.1/tests/data/test_tin.shx000066400000000000000000000001541467206072700172710ustar00rootroot00000000000000' 6???2RFiona-1.10.1/tests/data/test_tz.geojson000066400000000000000000000004371467206072700200020ustar00rootroot00000000000000{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "test": "2015-04-22T00:00:00+07:00" }, "geometry": { "type": "Point", "coordinates": [ -79.4, 43.6 ] } } ] } Fiona-1.10.1/tests/data/testopenfilegdb.gdb.zip000066400000000000000000002470721467206072700213650ustar00rootroot00000000000000PK Ttestopenfilegdb.gdb/UT pЗbpЗbux PKTc.rB9.testopenfilegdb.gdb/a0000000a.idx_smallint.atxUT pЗbpЗbux ر Du/_DM-l#\IԴY#%RS0v۹`PKTX&testopenfilegdb.gdb/a00000017.gdbtableUT pЗboЗbux R=hSQ޻V^DJm~)&V!64iEJjFl3("NBAP"RyPptE;98;j!wsOPog}{ԞBm'tG~3DkC@ga %:FN dc?Pyu'VH]f,a]Kp'i w'Gə%{v{%Y\Gq0w6o 3gk$Mm+=0Up npMƽ#Zls\̥ߵ[ ~k Qx-/"18<(/2]Idm9CߥɀT"%WOFߛ5bm{БI_d TVJ͠=~O..ԂwVۭ EݡDe e~lIFT&V7n龶2Oμ|+|TSW6bg^8һ_Bbb[r[+Ւ]y-ٕޮ~PKTK.2 &testopenfilegdb.gdb/a00000014.gdbtablxUT pЗboЗbux  2))@>hT,wݧ==7kȭ T6PKT)!*(testopenfilegdb.gdb/a00000003.gdbindexesUT oЗboЗbux cd```b7xO 3I& PKT+ H!testopenfilegdb.gdb/a0000000e.spxUT pЗbpЗbux 1 DD!e5BJπJҥ!vW\y kH뙍r=9x_/PKT0#!testopenfilegdb.gdb/a0000001b.spxUT pЗbpЗbux ȱ @1~, cd(urPKT A9X&testopenfilegdb.gdb/a00000004.freelistUT pЗbpЗbux ܱKUQ}{dA6A$;\BnXPB7 "4KPns{ӋHċ~^|NѫAīMħ G31Z2el'EZz5_ûmeƿp) 9noe4J:d7s9e J [fߕZO^vRͰ~];`x5_EY2E9n??G|{2odD|a60^ac5l͙~(9`4.({$+}c;{:{ջ1ܘ1Jnqw9 _GOI?'/| _E_>/| c1~PKT>(testopenfilegdb.gdb/a00000006.gdbindexesUT oЗboЗbux m] AFؔ+%$uׯ3&vg3>4}`( rVdυ#^,`,Uqͤ][gT\=)tMR2əZj>MKW|{"q X%6MPKT O &testopenfilegdb.gdb/a00000006.gdbtableUT pЗbpЗbux u 4Yϼ0#MK!=cgXP(a0J+){V)CjhK$רI)QgιwϽH%M ih过%mx3HA4$Â8P8Aq3D 򆼿 PĕA C,9I%AђKd,@\HBcJǿ̋þ&Phh"EQutOk dA%v]ţ/6ro-'>NjĿI Ȏڌ*C”R =Jَaؿ#-{υMrd#{Sjң*;J(D(bD84xԋx'^eߗze12uXnzk TQ௫ 'WnE2'3T⦶(!v|A84}RO忋Bx%.?,ʰBOgrТׂmNKlK7EdQT:n$wASDE8C==1>3ε6IsBJF#"ZSgz$5Į 3(E thv I̧smx[]dT&"@"W;%Mo_4֔+RKgjҁPT||uyԗESjL 䔋蠊x>n¡ sr;tv=q ][Uȼb\eCo':]MFKP ;5GpMlfFw!-V3|t5"Vxlҽ>:D(Ce]=uc%U!;gId8Ė Gz,ʬUPz-k So҈ ɸK6!ہ?d %EF NOu1awCjnW̦l5 φ:ИJvVbO!Lۈ+g/+X*%[87PuMvxu18`?mmRTd}obP9GYl!|]1\.٢[)_$pc4AX$`]C_nK# `%ρ٠ {`dˠKowזNFPcG$!Wm@&'?Fމ>_VΗI&G=]%f>'+Qo﷫mz!Y|tqzС 8tS$@~L=OY!x[p>o S'F]9ckQT8ReRSWޙ9ܟd\\/z<7p5[(lӘ} ibU|@4hJ4Q#֔ji Kׂ+q!t""nKĕ -tQw3qge{flq}1 K]R.5bElصzA'bxZszbhSXG;FD#@K!%r'_42C䌐=-Rsx4&051@|9o SNA-rf悎]ݢs%s-?\6Eƣq|칱6 ɱz6 #$iAc3w5NH``M!ؔ ( K-ş9wbUuʒjXq͟"˞#DVRP@ζw._X'?{/O:U2{2moz#!߷ի3_h$呼K;++WKPKT=u`6+testopenfilegdb.gdb/a0000000a.idx_float.atxUT pЗbpЗbux  ƺlR-̀>vv`?"ICE-ꑧ;h?P/{?PKTBPT?testopenfilegdb.gdb/a00000007.CatRelTypesByOriginItemTypeID.atxUT oЗboЗbux 혹0 @+xTIIO-IRb0g ¾qm.~K?c)~{S*}s^g=VW[`pjS]+.аn]"S){8˞cޞ(q{*gp}lϊGf"^<*6,YkFќ0ABQc` #@N!s+ V> L5{,[SY=u a+GXr`x{RzZk XbYiͭDWZv=nt!L;(R<UG{,;8E@'b:Ag18#aj5STOn5G(nkN&Q\%?k-`wYQIF\זc7⦪[rhj_;;g+޾wDV Ozbǽ0. ] ;O-.=v끛 *X,Wkz[Vxo*cW[Xen =gdW=QLbOV' {rPKT¢ &testopenfilegdb.gdb/a00000002.gdbtablxUT oЗboЗbux ͽ PFObim-:.a #.vb'Ik#w ؟|poRb `mk0+_(+|` P (XBgh,F?PKT<=&testopenfilegdb.gdb/a00000022.gdbtableUT pЗbpЗbux cf```/@l gef8d33833D3(AY @6dd`00u02UP 4hj\O*ZĺYoR= ;`s'/`8 n6{kJnrUGNgaPKT=I&testopenfilegdb.gdb/a00000007.gdbtableUT oЗboЗbux  Lg (E>:d Qg[ hl,^IwC l%F@#f č7E66bPkl4i{yz {7XyoVNZ`A r/:Q6d;Q"5@m(. GcO_=@m -B4 6"z?)B(SGMvgޫqcP}?鳞N> JY ΄|J:V5&L,0>@Ku. C/G:829yd\XVgBsҺ76U*Y"'L,N -B)M<XeH"ާ0ʼn76%Z۾|'t[n8%,,8[@-v0w[:]N#JWu\b;NR v1Iriޘuw3ds+D5GMȩ|TJi`vǪ,tЙaʎG/"?v(dF%tY4U`QӤWÒ&%)8aU0R2H=yDeM֌6û16"$  e vw8%#ǶX+]&D_G`ZF9HV #ѕfƾ#՝喏^q[*,L2B[EZ¨5k)&THـ.{gx5K֗ueB=~xZ{7Vr_3q S[^R Ov-@ uӢᙄ&ٰ_jژ{.*@BNڒ.qp/|UFj\yP?vTz+(|lO/ *h$.qxߊYC<#ME^IJV}prLHOZ.a6~(= /PKTtoD*testopenfilegdb.gdb/a0000000a.idx_real.atxUT pЗbpЗbux c````eF fbf fA`Q@庾Vȁ=R`Q0 F(^#GPKTs$E7&testopenfilegdb.gdb/a0000001d.gdbtableUT pЗbpЗbux S;N@}8_1 @ " D !DKK Q7p.y2H6jywv=Y`+Mas"^voޅbt07qⳃ1DͱxZ2*ˑ09P|#aIx#ݎ\4mn~%:^Npc@O3qNװs -0O++z6Ao9qxÂdĆ?7w7|diyLPKTA ^Ah(testopenfilegdb.gdb/a00000009.gdbindexesUT pЗbpЗbux cb``b7x vbbpepfa10@1ـ8!D3001#PKTKVPg&testopenfilegdb.gdb/a00000012.gdbtableUT pЗboЗbux RMHTQB̙yY昆LhP:DAm V#"čh!p"sتs/{{5[Y4k_akz9nEWФwĝ0:p.#-cf È+d_PIuavDq^XV,⭴'yr'ȫ-d"gq]-d8q9cJ1)6")]oYGR#EWjYT:>1Q*(ء>ǯcn1v=ϊq؀^^K e9L>ˆ9YQ"0H:gMw@K=X}K$0I, }+Pbum@ras lg"ElEv{}9my}Ysrp*{ jy|u^ZGחE'7B_M #nV)DQ%pg9zQX仨0 N23C.na6l"f,/*: ֈRWza,nj5U >}'3Mk?G;M.2/k8\)4y'Axs(`ġ,%r@O3W&Kߓ 0KGEߛ=bm'cСI_`% TVJ͠|H.%S}[i׏*OO$_F5QF fTTó;Hŵx"n$1^Yߪą"ˊhmfT+5R3 PKT5 &testopenfilegdb.gdb/a00000018.gdbtablxUT pЗboЗbux  D3bR%fIyUx [#d4Ys=D9m PKTZ< &testopenfilegdb.gdb/a00000002.gdbtableUT oЗboЗbux Un05BRѡѩTIti2%4%x'^Gx NJQ{νk6y)>X-Kc6;٣epH(/hQ!BUV])e9:R[ѥ/| b{q!: ɓ7o0Qt俄yXz:f0[ة!3KmefYWc8 ~b5'Q޽ aNꚡl tsOekoPK[=~}ƊJsAe i\R̈hr'U,\"u\N.*cdHj,;E bݿ.TaPUG5y$BV`Fi|_UPLjޯ˞\ ǿA)XfЅ)OțB LXG܈D2z|(J\O PKTةE!testopenfilegdb.gdb/a00000018.spxUT pЗbpЗbux  0DщFO@KIM&? l$j[^noqzh5/{M+PKTpzBt(testopenfilegdb.gdb/a00000010.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKTnb- &testopenfilegdb.gdb/a00000003.gdbtablxUT pЗbpЗbux  X]yL*dFDld3uV95gګ\PKTpzBt(testopenfilegdb.gdb/a0000002d.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKT/N1 &testopenfilegdb.gdb/a0000001b.gdbtablxUT pЗbpЗbux  S C; ;0#"ZvϽ#7kvj/[PKT{+B(testopenfilegdb.gdb/a0000001e.gdbindexesUT pЗbpЗbux cd``b7x vbbpepfa10@1PKTz&testopenfilegdb.gdb/a0000001c.gdbtableUT pЗbpЗbux ̱@9B (Kqxm[=ye)΅% 5W8Ԏt `c,>Ǜ" ٵPKTpzBt(testopenfilegdb.gdb/a00000017.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKT0#!testopenfilegdb.gdb/a00000020.spxUT pЗbpЗbux ȱ @1~, cd(urPKTm:Q0testopenfilegdb.gdb/a00000004.CatItemsByType.atxUT pЗbpЗbux 7N0EQi9g/9qSh#,ґ\­-/X5:l&l6.>!1\%\5-=<# au%XS8?L2QtPZJ*#/x+^ꏯft9((J;7X\kqTjҗj <2eCRϮn]drTIѵLQ qq+'~oPKT& &testopenfilegdb.gdb/a00000021.gdbtablxUT pЗbpЗbux  Uoz6! L3۬V PKTof&testopenfilegdb.gdb/a0000002c.gdbtableUT pЗbpЗbux }Q=KBQ~WACnN "'3 -)~!}HCCKA5ԅZA44DsX9}ޏ>4>tlEMt]FY bA JX`$0(&aǏ$,K2TOS5GYo&&$d-,ka.`.qEuKvembؐA&E̓_P-c]{D8Ô\q(kj8gwle洯_WxΕ]`Ә$1v=_\-]h5ˣQ9oPyh՚N[5ۚz[PKT$W*/ &testopenfilegdb.gdb/a0000000c.gdbtablxUT pЗboЗbux ā XJq;pr="Zv֕{{n֞j/SPKTk| t& &testopenfilegdb.gdb/a00000028.gdbtablxUT pЗbpЗbux  SS;۬V PKT&<&)+&testopenfilegdb.gdb/a0000001f.gdbtableUT pЗbpЗbux cf``@ʀZɳ0P5!݇H:1i sK ]@=@`YZ7 * j2:;ʏ|8`tc ! @g30[)(YY?l 7o?R 0Å9@Āq¹'ilhxDPWh7\iƫjSVr*̍B5r*TSa6p*TrBtr**Ph$TFPڈݩPK TKptestopenfilegdb.gdb/gdbUT oЗboЗbux ޭPKTt:!testopenfilegdb.gdb/a00000023.spxUT pЗbpЗbux ر D XX:8jF5`p#\$h+g=,oij{v:?PKT{ &testopenfilegdb.gdb/a00000006.gdbtablxUT pЗbpЗbux  D5:8R)VH`B):/Q̗iD$Z_iw-j. 8TtPox(k@) BX:s9s9sF_PKTӰ 6(testopenfilegdb.gdb/a00000004.gdbindexesUT oЗboЗbux u @F`d!tcvAB7>}[.gYfNd8q On\8*&Ok([.SR|l7:Ysԛ#k ;zRF*16xb˨Ciɵv?OPKTo7)2 &testopenfilegdb.gdb/a0000000f.gdbtablxUT pЗboЗbux  2@hT,wݧ==7kȭ T6PKT`34testopenfilegdb.gdb/a00000005.CatItemTypesByUUID.atxUT oЗboЗbux XI+7 <ʎ$R$R$!&P ,UE~z  x|~~>>>>>~~uճ6) C6DU侴$}g5hU)qĻiIRir~=p}7&W6E|=GE{;w1kQ_#xڈ2|T(q\yZddt1*ZD&Ƣ J 8sSD_gեs8Zp]VD1VXD_uhIvYX`u!.as>riWUh/aO䵠6rh {2vč~x],N2jp n#+cM i/d)S+RC}Yƪ[8uu]wu]wϿſPKTtestopenfilegdb.gdb/timestampsUT oЗboЗbux  &PKT0)&testopenfilegdb.gdb/a00000010.gdbtableUT pЗboЗbux R=hSQ޻AJm~)&V!64iEJjFl(8ंE"O B@:b;9B[qA߹;N{ι߹)vk)0Ct # 7~Sh Q'@'"Lxa o VTQo?R8(aLQ/=* QkGjQDH{Bj'#Q`V'}!rv;wAaIUlNiFRщֺPUdu)Z>Yj- t"8Ԃl#RUXdY:)PلdM2go궨[GEe_q&!\e3LǞ}q&麗f.n؅ .\p… /~|?PKT /i,@.testopenfilegdb.gdb/a00000001.TablesByName.atxUT pЗbpЗbux yO@?A@ HjƏu5TǼa $;axHOMiHK?ɍJE G$`t kκ{e0L&d2L&Қ.o[&|zX<z< /e;T5).J~;w`+~hQ1v(" TE0ڋbwt,;xkEPG1ǘO#|d|yS?>׶֖1Mm/[)Sa{:eO1uO]#iid2Lh>]/+x7IxއB`>/=8KI_gɡi|I~O{&!lxׅeˏI)u1ʓ"{$*b3`-J w;P.&4ϹlR;d2x{>gex%w GzY?gI|L^ϹS3_,|_(]G—'Y<򿒿Οԩg~ӓǙw&d2^ $`U+PKT`Ql&testopenfilegdb.gdb/a00000014.gdbtableUT pЗboЗbux RMHTQ޻fDb6E…34`rpF"dF̉4qу6j+Ar%E @D%nE ;wPV{{ι;w'=3˶tРchW ;>t!K׏^t#0ѓ6V\L#zX T}d e$8yD(lyQ&spGIMF23N.2Ӹ,FqK17+6US &4KҖVES\bK\cb 'Ŏ,}$:qdVbk{J[W`T+@7% eYeC`o?Hc;!͒$Hg.G c%$+ ̦I,s~d ܃ E(44dsQ7wwmL{i#ce}e x󪳵X#gזKjF;o?&R?c!7nqw3,wmh;+O0 QmXц PKT KT4)testopenfilegdb.gdb/a0000000a.idx_int.atxUT pЗbpЗbux  C/yI N:8$ild3r'Зo{˿~PKTpzBt(testopenfilegdb.gdb/a0000000e.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKT5 &testopenfilegdb.gdb/a0000000e.gdbtablxUT pЗboЗbux  A#ťG9t-QE4#~GDgʼ=*"хs-8BbX,bX,dϿ>@mR%dS*Y#g̓?jh>\C(!@^?;+%V>GM; $Z'{6Azn&$9cd9B{{a+U ~ 'fQ~ꄨTڢ+wb.ZʶĵT,p݇+\t?Fpm864-%&m'.m#5C Ǘf<15Ɔ DN@"2؅i4 N+yGdE (ۘUztgҁjl>~'_….\jyE:J: 6V]:tFujPKTTPZ͌PA'/`8 ^6{kJ3gblrUGNnspXj 77hZ+&6}fPKTK=testopenfilegdb.gdb/a00000007.CatRelTypesByDestItemTypeID.atxUT oЗboЗbux jE! E}O]ο7} :B]@d@m۞ sڽqo{=s/'}ta[$uH9%HM 4Fj#b/J(X$T::RG[mSnJ(d5 6(2ή6| cqx=0)$4I18b| զXh3n[EASJ\jLM*怜 8XTH" 1L~9 W#`Vɦ--bX,/PKT*testopenfilegdb.gdb/a00000006.FDO_UUID.atxUT pЗbpЗbux XI-& Ŵ.q)`֨;iM^h4)xNiCr0c$k;e,ekY"^*I$[6 ,W.Еbb:3YN!m 7!Ye9=v}\򞴹&{6,+;5h oq_+f%q8$H<3b4̫V&l1햍f5m͖2nh1gƆuYS VN\WYآc TV:bz #hPpԦSj:*gE/"Z`\ )KVTEedžVNz6Ќb9cv̡cr4 =@qduT[{ , t_5:'hAצft;{uپRת|ҪOhEm8<po0ɹ1XmDCS} HNpޏP΃$,d5en^n+2! B=FZ]mG'Je_a,4ۙ'R5UjKDdCgZ3"+-.k7Ndq-vyy0I_K̜i2+ 8 tau`]En+X]Zȵko;0R,q}eMAWcSJ@WAEa{bCΘ3ϋG=Qzԣ|W_/PKTpzBt(testopenfilegdb.gdb/a0000001a.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKT9IF)testopenfilegdb.gdb/a0000000a.idx_str.atxUT pЗbpЗbux ػ =،X-b J;]ʭ-I`dn~JSTM_M}/ PKT`Ql&testopenfilegdb.gdb/a00000013.gdbtableUT pЗboЗbux RMHTQ޻fDb6E…34`rpF"dF̉4qу6j+Ar%E @D%nE ;wPV{{ι;w'=3˶tРchW ;>t!K׏^t#0ѓ6V\L#zX T}d e$8yD(lyQ&spGIMF23N.2Ӹ,FqK17+6US &4KҖVES\bK\cb 'Ŏ,}$:qdVbk{J[W`T+@7% eYeC`o?Hc;!͒$Hg.G c%$+ ̦I,s~d ܃ E(44dsQ7wwmL{i#ce}e x󪳵X#gזKjF;o?&R?c!7nqw3,wmh;+O0 QmXц PKT+ H!testopenfilegdb.gdb/a00000015.spxUT pЗbpЗbux 1 DD!e5BJπJҥ!vW\y kH뙍r=9x_/PKT\կ=&testopenfilegdb.gdb/a00000001.gdbtableUT pЗbpЗbux mKN0^!c%R>2`RK%.R8  +pf&*{j𻌪LvF3л!|ԋA@ ѿ5X$aPl CY8^ 'M(:Oqb$ ׂj-`Z2LQS:XSUcVMc$3ŝMzT4Okˬ2NSm˕I\QU6sBfuXCMbu=WQ\bjPdPNPGMyܺ:.e VvB91)3dN˙q6^PKT&(testopenfilegdb.gdb/a0000000a.gdbindexesUT pЗbpЗbux m `E^ꦠC"(+mqlO F,f&zQ;z+YJv,l+a_oA۹.\ @݂DQρTτy Sb#ܩᬦn%2o;?ִ߿V٦sboQVPKTRD&testopenfilegdb.gdb/a00000019.gdbtableUT oЗboЗbux R;/Ca~z.= bO0AJ/nKi-"!"--T\bZ$fD"f2AR09{st&cͶ5lR0F?,X !zB ̡0CO]DfDy2)2*AaNw(Vµ#F~e}he.>6Azn&$9cd9B{{a+U ~ 'fQ~ꄨTڢ+wb.ZʶĵT,p݇+\t?Fpm864-%&m'.m#5C Ǘf<15Ɔ DN@"2؅i4 N+yGdE (ۘUztgҁjl>~'_….\jyE:J: 6V]:tFujPKT+ H!testopenfilegdb.gdb/a0000000f.spxUT pЗbpЗbux 1 DD!e5BJπJҥ!vW\y kH뙍r=9x_/PKTpzBt(testopenfilegdb.gdb/a00000018.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKT gRd&testopenfilegdb.gdb/a0000002b.gdbtableUT pЗbpЗbux }Q=OP=mW&BE0IP1& cpp1nLQ7,,Nn&Q{}z_{8=W.5tgT.!bȀ#좈,*XLrp6H`i, Ny~YCd`T/RJe77 cq$H0׹TRӡv{&>-HT:J~V+9)ΰl1W1͚=yC5zL>zi_}g6-8^"Vx9K\/B{tXkyo O^ ߵΧ|}הHCj33~PKTYJΚ0*testopenfilegdb.gdb/a0000001c.idx_real.atxUT pЗbpЗbux Tu]$.y/ATDEPB BE@[llP0g /g=sΞD"HcQEё+#+ ((JR{ɜϩ8)OeS49iOgғ d(ٌq*$..j3Br wp 8yMle^Mv;]|>~[<0)My*Sԧ1iM{:ӕe CN<[KP2T 5CЂ6t E?1c͉4&s!Sk,ndXƭnVxl^-C> v?y)@JP Tuh@ZІt %s(HQJRT&uiHSZҖdҝg0ɜY\ĥ\ɵ\lrYmacyymۼG|ʗ|wįOM )JIRԤ. iJKґLӛ f8SU QRըE=ьVMn0! 1yLb.*q=s,as'kX˓3g/q]rA!QrTG#ъvt"aC͌3g"+f1c'{EQ(((NaoPKT1*3(testopenfilegdb.gdb/a0000000a.idx_id.atxUT pЗbpЗbux  C/KA  Hn{%w  PKTpzBt(testopenfilegdb.gdb/a00000016.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKT 4&testopenfilegdb.gdb/a0000000c.gdbtableUT pЗboЗbux RMHTQ= NH̦h!̼~gj098A2hDhrVE["# DTU x;w6̽ssϹ=6|,uQ)nF W^l}AdQ`&1v\J3zy2a$5/4:D{Hӱm܋z y9̄yCLR=in9)TPZ͌PA'/`8 ^6{kJ3gblrUGNnspXj 77hZ+&6}fPKT;>0 &testopenfilegdb.gdb/a0000001e.gdbtablxUT pЗbpЗbux ı !'\pw☁'I;8}Y[PKTOE#2 &testopenfilegdb.gdb/a00000012.gdbtablxUT pЗboЗbux  2 @NhT,wݧ==7kȭ T6PKT=3testopenfilegdb.gdb/a00000006.CatRelsByOriginID.atxUT pЗbpЗbux ط0Fa{}VHF+, j+YP@%QA5@-A=1#13̱+;q[YNAbu.~j6I^cc|d%+Y+%I$I]~PKT& &testopenfilegdb.gdb/a00000025.gdbtablxUT pЗbpЗbux  Uoz6! L3۬V PKT[׉&testopenfilegdb.gdb/a0000002d.gdbtableUT pЗbpЗbux NA],aYbaJ),lb+kQ!4xov9sOygrAO>C{.mSp)G FئNnXfV*Tp6RY,QVb%jy0OE)H=qc$kιefCt+ҜfvkXS-vSls.&MoY魾4<'gys_t2[ˁo//CPKTn(W5 &testopenfilegdb.gdb/a00000016.gdbtablxUT pЗboЗbux  AH!,2 -QE4#~GDgʼ=*d~ PKT0#!testopenfilegdb.gdb/a0000001d.spxUT pЗbpЗbux ȱ @1~, cd(urPKTpzBt(testopenfilegdb.gdb/a00000027.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKT3 )&testopenfilegdb.gdb/a00000005.gdbtableUT oЗboЗbux cf``P8 fe@lZɳ ß!!!!%!Rc `Hd(ad(a,~@\{ d]]Wݩ{m hų$5Wȷ|*UĵYe5礤i|0~ju#(8(9y4v=ژ^%$8DZߪ^fI9|Q\]/sHfP]+&ysh}0]cRqIQbrIHbRN1Plۢqn7G1]PPjNbIf~^qFfsNbqPgaq;N,;kwKM,)-JUz'x論q=TS@#螚ZRZR_ AeioUKxuqWs8 HH9 nI5zO_nf~B!Լpɀ(L_UWy{&C촭%S/ -'"2.(ˮ{ȮkwS'bļt`NBN۬Goa42_krOIMQK)iPKTee&testopenfilegdb.gdb/a00000028.gdbtableUT pЗbpЗbux cf```/@R g X<\ؕ݁,wg`h%(+!ц  &@P_J{M  TRM19g bF`y= m 3Bc 2u& umC*13d2$3dM6RTuo*C:T=B!`Ah 3 0?=fwv?+?^M g@`|:(-9  hg r381xMPp30[=yy|}ő7.!VlZB n>am0 PKT0#!testopenfilegdb.gdb/a00000004.spxUT oЗboЗbux ȱ @1~, cd(urPKTpzBt(testopenfilegdb.gdb/a0000002b.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKT9H2 &testopenfilegdb.gdb/a0000000b.gdbtablxUT pЗboЗbux ı AD)2w Ö"{D>+ܬ=#_hPKTX&testopenfilegdb.gdb/a00000016.gdbtableUT pЗboЗbux R=hSQ޻V^DJm~)&V!64iEJjFl3("NBAP"RyPptE;98;j!wsOPog}{ԞBm'tG~3DkC@ga %:FN dc?Pyu'VH]f,a]Kp'i w'Gə%{v{%Y\Gq0w6o 3gk$Mm+=0Up npMƽ#Zls\̥ߵ[ ~k Qx-/"18<(/2]Idm9CߥɀT"%WOFߛ5bm{БI_d TVJ͠=~O..ԂwVۭ EݡDe e~lIFT&V7n龶2Oμ|+|TSW6bg^8һ_Bbb[r[+Ւ]y-ٕޮ~PKTO;testopenfilegdb.gdb/a00000007.CatRelTypesByForwardLabel.atxUT oЗboЗbux Mn0B[~໴E. T.zqCe<"m)p ?&m~ϸ{:WD[ވx]"_j }0J!t&;,?*H 4-繬vD.he]OtEsIT ^Q`ayDEq9ClWnKa3Uaukp\H* JzEB}؉8:Y霃+5 _2_P[`Ւ1rW>GUX,zˆez%F+8>5tueYиjyhjiyW/drtX=B94;e\(ނl\wNF N8 yAJT0NpE"kqE)AIޓe;{5,u9Mcde8z8YuNX\7CN9]Ek46ڌ@qYsr9}89HfzŦQOWZS+fiwVtks&C"cҞG<x#k^PKTt:!testopenfilegdb.gdb/a0000002c.spxUT pЗbpЗbux ر D XX:8jF5`p#\$h+g=,oij{v:?PKTSX8testopenfilegdb.gdb/a00000006.CatRelsByDestinationID.atxUT pЗbpЗbux XGl7 s)RTǿKc/|(C* yzzzxxxx^__o_?oo=v+N)81gkpߝt&^.9YMmk|,=:k%m>HW_h|m< lrv\n9iCiLj8+XM~ayDp9Rks/yDӫjS „ykp,9Y霃V(o0-tԵXd`LUh`378$X݈QgZK2<$p(jɭ?Kկ#HzYhI$e27.%+T #BQġܮѡ k 8_p{1NG$7ptsEW!EfscƬWLbEcG=Qzԣ__PKTlL &testopenfilegdb.gdb/a0000001d.gdbtablxUT pЗbpЗbux ˱ 0 Qe6`** ,O%׈()7FwrI#/YA.fv$I$I+}PKTݦr((testopenfilegdb.gdb/a00000005.gdbindexesUT oЗboЗbux ca``b7x NbbHeHf(a10@1兀ؙ!/20T2Y N@V(BT21$ FK0!(Wea2&[!7r ؀]83{I!bQ5q)U؂8"?J2ڡ;a f` qqi?@aAUCKtBo xAXXXXXXXXXXXXc!PKTpzBt(testopenfilegdb.gdb/a00000029.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKT'2!testopenfilegdb.gdb/a00000011.spxUT pЗbpЗbux  DD,ʅ` {/l|Zիz;pڿ/PKTZ&testopenfilegdb.gdb/a00000018.gdbtableUT pЗboЗbux S;hSQ+76W)(6֦*Ć&hڴF[1 v'PP8^(AZtwνp3?7i5u7}~k$сN&FQChtAa$|-8;td }Kd_PIw'jMhf-$MX q y{'{ܽ5dpY\E,O`oጰ-;vvٗ ^rSѪ9D;;g]|ORqXB^xtUb0݀6 >x ? 8 Y4A 7r0WB q4E/Ï>Zi01+ƊQf%($aso7ֿ*{weo]Yn֖fTɧyS5G,=lIc Swb~y|fU cx9v+&|x88la{U_g$\Q)Kv{[W,%Rk|Ŀ=-lZyXAa5nil'zujKU^z]2^PKTY%<testopenfilegdb.gdb/a00000007.CatRelTypesByBackwardLabel.atxUT oЗboЗbux An0E?-Z|*.RCX(e.1'=䧱ƨCSwTz3BRCOp&BPHKò m_2Q"!dsΖ8Vc*e |hLڊm]RɊ\ WmO9W\y^٫$:h,yc`'Bz?[|w:_5җ˞`}^giߕ7 l8_~VT:ZӋdm0_hѢE NPKTJ'3testopenfilegdb.gdb/a00000007.CatRelTypesByName.atxUT oЗboЗbux [N@ Eoy7,!{e@ H;դAغ|v%tOD[Fh4DN1GGuj5xFD{'Wk9 x[%3=ϫ~mkBnB kMODi4gFg,Ԯō +Y|1M{f9"滛Kc|7_Mga:߷D|8\dn%y˜liV8b{5hw./{bŊޫAEPKTA&testopenfilegdb.gdb/a00000009.gdbtableUT oЗbpЗbux cf```;@ʀ3 g1x8\B<\f01e2YXJr32$2a&CP i`ML@>KC*X3P6h0H.F %l+spBV$@+8X YŎ}A~ +?{-BKv yPf"gև{B&aV}mag"3% 1Q8fB]Xe(PKTee&testopenfilegdb.gdb/a00000026.gdbtableUT pЗbpЗbux cf```/@R g X<\ؕ݁,wg`h%(+!ц  &@P_J{M  TRM19g bF`y= m 3Bc 2u& umC*13d2$3dM6RTuo*C:T=B!`Ah 3 0?=fwv?+?^M g@`|:(-9  hg r381xMPp30[=yy|}ő7.!VlZB n>am0 PKT)w&testopenfilegdb.gdb/a00000003.gdbtableUT pЗbpЗbux cf```?@ʀZɳ < L . l,Ll A ! @D1$2039XE"!"@V(CC&C P]UT$ _/n! @u E@y @X+ /.{k J@">=8DI1$7ZI(85%Z !fflnahlgcdigdjnddljf 4(55<39CI@ V'3hnj:P,dhnbjldidjhiib : L79Y? o.~ݟ/&Q0?=KУty9cxQj'# 3g3'] sK]CC#]GGgG]g7Gccg#Z}"ha#PKTK.2 &testopenfilegdb.gdb/a00000013.gdbtablxUT pЗboЗbux  2))@>hT,wݧ==7kȭ T6PKTQ/testopenfilegdb.gdb/a00000006.CatRelsByType.atxUT pЗbpЗbux ط0Fa{}DKfuRwHwR^E!( h衏a aXa a]-r]6:u؛wYJVwWJ$I$;=_/PKTхD&testopenfilegdb.gdb/a0000001b.gdbtableUT pЗbpЗbux R;/Ca~z.= bO0AJ/nKi-"!"--T\bZ$fD"f2AR09{st&cͶ5lR0F?,X !zB ̡0CO]DfDy2)2*AaNw(Vµ#F~e}he.>6Azn&$9cd9B{{a+U ~ 'fQ~ꄨTڢ+wb.ZʶĵT,p݇+\t?Fpm864-%&m'.m#5#ߝ Ǘf<15Ɔ DN@"2؅i4 N+yGdE (ۘUztgҁjl>~'_….\jyE:J: 6V]:tFujPKT& &testopenfilegdb.gdb/a00000029.gdbtablxUT pЗbpЗbux  Uoz6! L3۬V PKTpzBt(testopenfilegdb.gdb/a00000020.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKTL5&testopenfilegdb.gdb/a0000000a.gdbtableUT pЗbpЗbux RKK[A=ut!Y{7cL Z/o\ZQ&CKPKTpzBt(testopenfilegdb.gdb/a0000002a.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKTpzBt(testopenfilegdb.gdb/a0000001b.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKTlM!testopenfilegdb.gdb/a0000000c.spxUT pЗbpЗbux c```aF fbf fbV F(``LD@ GchQ0 F(`FhyPKTk| t& &testopenfilegdb.gdb/a00000024.gdbtablxUT pЗbpЗbux  SS;۬V PKT$*&testopenfilegdb.gdb/a00000015.gdbtableUT pЗboЗbux RMhA I!C)"Jm~)6V!6$iEJjS.AvDy2a2z*IA&{(w !fP |I9B\I5v2'#{LW;,$"븡;)]oN!-ue] iURWirO7wk#ƴ_?vƵhߪ}T-EI; .l/ܦa\1Q~q 4&-<4C" sbM yl2O(11KIN`,OSz' #+콠-~~.o}uy>X<&yp7jl;W(.WT3K=ޕ{WW}6"őΚ?-HT:J~V+9)ΰl1W1͚=yC5zL>zi_}g6-8^"Vx9K\/B{tXkyo O^ ߵΧ|}הHCj33~PKT2W*testopenfilegdb.gdb/a0000000a.idx_guid.atxUT pЗbpЗbux ع 05G+d+M!zg D0Oș#[D$%Ԗ=:Uݴ]?hYu5K_OqWw_PKT'E#i&testopenfilegdb.gdb/a00000004.gdbtableUT pЗbpЗbux  t՝'ۊ;l][ !-'‰lɶ^d'Jbm%zIv$B[@RH݅)= Ji嵴(!R HCÎc+~|q4sFcR O^[?ye*sU(ȫe zf2L0&hcڄٗT!D0K ;e܊^{b'K#:@$Q(3R_%۰#fab4BLqr 2Lۃ"&ZM" ieUu$Q"KL1_b"w).UT߁+T:0Y7G0.K'5w y4033`F2R,qj3:R|OFM1ibL k+/rj-SKk%"eV2 %K\:o1s+٧&m|.YYo#p,#KQj>:Z厱iw-O W[o4N 7\NO՟~˶e|ʐ|e[o^vםk˄6u7diUc(1viCb70p|y{lwsjK<5XCݫg 7Fl*G.0ekp ^QT/`._6wS|Y`2'vl,hߒJ6UvU^6TÝWZU-7V=UJ]fu[V7J[ ú\8%.W.X`hFP\`^+̓}`UGX]'M޶˂5{#lPm3 9áI Xy.^KWZ}=.="stV;?_ۻ DiQS'IZ/#?W aߘ|? e1q'jEiR9`KTbhFdyiW &Ѱ趘 UIņf'%<4N +6K݊Axk>$Ě  J1Tgۇbp?,W&,Zk}a\!mUCUeXksNhTujIS7j*UԤә'_m0vKUdgcrxwS3n7_K5l|$m]&[mɦznd->ǃR&ßx5 ^鏂/Yו)Clɉl&'͜[X6MF%"h8E?$Wu_&IQ<95ښ3ZbFniNeEsn22nmt&wN H8DT{"¬?>y0׏"tyP"](;N<yӟP1Bs[nK,ϑr<>Y WIAZƹP<BbipNhIA;|s&c(3O&ƣ4,[n6) {z}PsQ5KJw}Z[NXPuMFwmmIVr[I7zmTVիŚ:}VCu9̲_gobUj6MrH]j4:ljK8ՎЩȉ_ړړV 'Z![˻k[5I;X#۞ɷ\0Փuzrk}%ǫ]Q-^NRY;l2˓\!}UŖoa]17j\RUT8|HJc'S>MN(B̆HD 'z&fxi߈) љʿLG#TO*=-IGm@0U%Ԩu:FW+i$xapJY.+eHI1 {X,f19hZG)NO!+K+>g<=;+zJ[mQlToﷁҼTUh*!99999999999999999 9ɼ SSӸF#_n')*Z{pQjl7*=BA%%#*48pG'Yv|<Ԏ(,B}&n#fS,4>gU2iFYM^w':ZTnv <%06Jnv[-_OxJK Z/!<ttNCt t t*Yn`Ϯ;n|mEka;H3 #e.UV鰶7;쮹:ۻB2`=GI7R55&V1jV1j5Eiqɪ&LS7LG<~G.=WZK+GJtCP@iǧYSr[k[[[[[[[[[[[[[[[[[[[c~11jD0S0S<t )M桪OS|Gy<ޛ7;na^ v϶PCxuOƍ(xvx+ndWCwn7+=jo~ڹ-te =b5D;`i/>/㚨͡Z*UW_ *UW_:ZzUث^Ճ?P35-wG;^-nPp`xUۺnk;^[!9]*0^ax +W0^ax +W0^aMx?l|.^k襍>YjSaB'^D=UT{[չ4Y0Yad &+LV0Yad &+LV3YGŽŚpad+'G+n_S>;gKEʔQ6 AErn{jl3O%'cSBr0v 3?SϏX,6aOͷ}OO{ɥ\urڡiDJ!`q܊Qx*ΔT. #WU*&XU :jK``` ;_N8ٹWWWWϐ`~ZġPSeoCS}Ww,j4XBVI9*M͓դJj;@Vi d%YՂNUU'+Wu\y[>vgSюF_$\ ;'z S)nbc"OM)(dWވ|\j^^^^^^^^^^^^^^^^^^1x#eu'+v5m݇pF>zzBAMItXۛv8#sRH.X%+*+ȥZv+Vح[an v+Vح[an:Z' nkmw[2ٻ/-9/7SfL؁m ߵCPavX6%*6l-lXذaa† 6,lXذaa† 6,lXذa† 6~?E3^Cq꣟*v*{bn_Ez|+KUn+Vp[m n+Vp[m:ƙǸ hӷ>VZ,VJܟb{!ZWQJۮle0^Criz +WX^az +WX^aN]zx:WXlMZ/[.x-(g\c+|b>OznŌNⰚ̭UzbRqSF]1'՜GbX87H񡹶_(*[Ȗݏ O+{ 9_tǡOR%EUUjR2`U*&\UWWWWWWWWWWWWWWWWWWWp美W9#UUUUUT}16wY;“/L " U\½L2`kz*e,+u^^^^^^^^^^^^^^^^G,>v{{8?wmnZܴqņh?VKSⰚ="\\Z,eW\zZxTᩎe$p|LTp`rgLaNjB"4H-!$BDF"D B"j!);gKŕFH4fW6v`5yʬs>:ʬKʥW o-a)xxSj:HHm6)ejbbAO~ަo˟,qG-  ̖F؊= jd=@Wtts0Pu1Puන+WV p\ %?/~uǶ__e+ Ц@$kl%y%@}Y\?UA!\:U?7^7:Ϸ48kfGx;f[-vj7~c$MoI%&'U@t:=s5^:|x=c^Ց-nk)tI~H/]t.$cHz<35=Gt DQύM?7=,?6 ^&_Yƻ\Bp}ScrZ4>tlEMt]FY bA JX`$0(&aǏ$,K2TOS5GYo&&$d-,ka.`.qEuKvembؐA&E̓_P-c]{D8Ô\q(kj8gwle洯_WxΕ]`Ә$1v=_\-]h5ˣQ9oPyh՚N[5ۚz[PKTt:!testopenfilegdb.gdb/a00000025.spxUT pЗbpЗbux ر D XX:8jF5`p#\$h+g=,oij{v:?PKT(T 2 &testopenfilegdb.gdb/a00000011.gdbtablxUT pЗboЗbux  2 C hT,wݧy垞j~PKTt:!testopenfilegdb.gdb/a00000027.spxUT pЗbpЗbux ر D XX:8jF5`p#\$h+g=,oij{v:?PKTl|&testopenfilegdb.gdb/a00000020.gdbtableUT pЗbpЗbux cfFVaH,@D3x08202030T3811X0183I'] m`` d P^,kVk3PC-@AAa0^>YoR=ʏz\4Tp3C' l,LBi 9 ) y %`(ÐȐ$S$PcSpoPKTt:!testopenfilegdb.gdb/a0000002a.spxUT pЗbpЗbux ر D XX:8jF5`p#\$h+g=,oij{v:?PKTk| t& &testopenfilegdb.gdb/a0000002c.gdbtablxUT pЗbpЗbux  SS;۬V PKT/N1 &testopenfilegdb.gdb/a00000019.gdbtablxUT pЗboЗbux  S C; ;0#"ZvϽ#7kvj/[PKTpzBt(testopenfilegdb.gdb/a00000023.gdbindexesUT pЗbpЗbux eA 00Зw [oB v8yb'8(<`Mj+~ޙOCO-b4PKTDA &testopenfilegdb.gdb/a0000001f.gdbtablxUT pЗbpЗbux A PDn bX 1ELp̅[k6צښ'gr'Pْ#'~PKTߥc"-testopenfilegdb.gdb/a0000000a.idx_nullint.atxUT pЗbpЗbux  @A+Y<W9PKTlM!testopenfilegdb.gdb/a00000013.spxUT pЗbpЗbux c```aF fbf fbV F(``LD@ GchQ0 F(`FhyPKT\55 &testopenfilegdb.gdb/a00000010.gdbtablxUT pЗboЗbux  D3dAv Vfֈ(}WΨ9_yן~PKTA<testopenfilegdb.gdb/a00000005.CatItemTypesByParentTypeID.atxUT oЗboЗbux عj0aO}*-U)K/5̻$4!EH79`c3 47u؀M؂m؁]؃}8C8c8S8k[{xX. ֆksh1wRsjHU q $@ܚl!W}UDZRn4IF٩OWf̵$UعHגk3{ ^6bXB̜ɖIK]UNȳ(ri*6$~cZM*NH[ēHBYI[/z^o?/ PKTaw(704testopenfilegdb.gdb/a00000005.CatItemTypesByName.atxUT oЗboЗbux ZNP}Q4>4B,-iAoTL/Q+c976!L!"')_B uȧ<#O|f0 `0 ø=O@vFn)xg>oSU _C_F_Sr"PD +9CK+y M GO_E'OGkԓX65T4.V/1eeii +8V9.W/{t{Gr1=X49uzt>ײz[*֓HE_ō:;״)V@V3g/u'mig cs2 .wrr򮚺jq %in/vB{%b;ZEY$JPM[^=CI_e~Ug TYmel/ J=ڢM_(V5e0  ?8 PKT'2!testopenfilegdb.gdb/a0000000a.spxUT pЗbpЗbux  DD,ʅ` {/l|Zիz;pڿ/PK TAtestopenfilegdb.gdb/UTpЗbux PKTc.rB9.Ntestopenfilegdb.gdb/a0000000a.idx_smallint.atxUTpЗbux PKTX&testopenfilegdb.gdb/a00000017.gdbtableUTpЗbux PKTK.2 &^testopenfilegdb.gdb/a00000014.gdbtablxUTpЗbux PKT)!*(testopenfilegdb.gdb/a00000003.gdbindexesUToЗbux PKT+ H!stestopenfilegdb.gdb/a0000000e.spxUTpЗbux PKT0#!testopenfilegdb.gdb/a0000001b.spxUTpЗbux PKT A9X&testopenfilegdb.gdb/a00000004.freelistUTpЗbux PKT<-B!testopenfilegdb.gdb/a00000010.spxUTpЗbux PKTm)P2(& testopenfilegdb.gdb/a00000021.gdbtableUTpЗbux PKTӳ & testopenfilegdb.gdb/a00000004.gdbtablxUTpЗbux PKTpzBt( testopenfilegdb.gdb/a00000024.gdbindexesUTpЗbux PKTn(W5 &i testopenfilegdb.gdb/a00000017.gdbtablxUTpЗbux PKTKۅ! testopenfilegdb.gdb/a0000001f.spxUTpЗbux PKT>( testopenfilegdb.gdb/a00000006.gdbindexesUToЗbux PKT O & testopenfilegdb.gdb/a00000006.gdbtableUTpЗbux PKTlM!+testopenfilegdb.gdb/a00000012.spxUTpЗbux PKTXB /&testopenfilegdb.gdb/a0000000b.gdbtableUTpЗbux PKT8Gp(<testopenfilegdb.gdb/a0000001c.gdbindexesUTpЗbux PKTlM!testopenfilegdb.gdb/a0000000d.spxUTpЗbux PKT 4&testopenfilegdb.gdb/a0000000d.gdbtableUTpЗbux PKTI~2 &testopenfilegdb.gdb/a0000000a.gdbtablxUTpЗbux PKTt:!testopenfilegdb.gdb/a00000028.spxUTpЗbux PKT]&testopenfilegdb.gdb/a00000011.gdbtableUTpЗbux PKT=u`6+rtestopenfilegdb.gdb/a0000000a.idx_float.atxUTpЗbux PKTBPT? testopenfilegdb.gdb/a00000007.CatRelTypesByOriginItemTypeID.atxUToЗbux PKTpzBt( testopenfilegdb.gdb/a0000002c.gdbindexesUTpЗbux PKT &~!testopenfilegdb.gdb/a00000020.gdbtablxUTpЗbux PKTWA&!testopenfilegdb.gdb/a0000000e.gdbtableUTpЗbux PKT¢ &\$testopenfilegdb.gdb/a00000002.gdbtablxUToЗbux PKT<=&B%testopenfilegdb.gdb/a00000022.gdbtableUTpЗbux PKT=I&&testopenfilegdb.gdb/a00000007.gdbtableUToЗbux PKTtoD*+testopenfilegdb.gdb/a0000000a.idx_real.atxUTpЗbux PKTs$E7&,testopenfilegdb.gdb/a0000001d.gdbtableUTpЗbux PKTpzBt(6.testopenfilegdb.gdb/a00000022.gdbindexesUTpЗbux PKTˊD+.testopenfilegdb.gdb/a0000000a.idx_adate.atxUTpЗbux PKTD7qdd&/testopenfilegdb.gdb/a00000024.gdbtableUTpЗbux PKTA ^Ah(G1testopenfilegdb.gdb/a00000009.gdbindexesUTpЗbux PKTKVPg&1testopenfilegdb.gdb/a00000012.gdbtableUTpЗbux PKT/N1 &f4testopenfilegdb.gdb/a0000001a.gdbtablxUTpЗbux PKTpzBt(4testopenfilegdb.gdb/a00000026.gdbindexesUTpЗbux PKTpzBt(5testopenfilegdb.gdb/a0000000b.gdbindexesUTpЗbux PKT0#!?6testopenfilegdb.gdb/a00000019.spxUTpЗbux PKTl5 &6testopenfilegdb.gdb/a00000015.gdbtablxUTpЗbux PKTpzBt(R7testopenfilegdb.gdb/a00000015.gdbindexesUTpЗbux PKTT<2z&7testopenfilegdb.gdb/a0000000f.gdbtableUTpЗbux PKT5 &X:testopenfilegdb.gdb/a00000018.gdbtablxUTpЗbux PKTZ< &:testopenfilegdb.gdb/a00000002.gdbtableUToЗbux PKTةE!=testopenfilegdb.gdb/a00000018.spxUTpЗbux PKTpzBt(|>testopenfilegdb.gdb/a00000010.gdbindexesUTpЗbux PKTnb- & ?testopenfilegdb.gdb/a00000003.gdbtablxUTpЗbux PKTpzBt(?testopenfilegdb.gdb/a0000002d.gdbindexesUTpЗbux PKT/N1 &Q@testopenfilegdb.gdb/a0000001b.gdbtablxUTpЗbux PKT{+B(@testopenfilegdb.gdb/a0000001e.gdbindexesUTpЗbux PKTz&oAtestopenfilegdb.gdb/a0000001c.gdbtableUTpЗbux PKTpzBt(IBtestopenfilegdb.gdb/a00000017.gdbindexesUTpЗbux PKT0#!Btestopenfilegdb.gdb/a00000020.spxUTpЗbux PKTm:Q0kCtestopenfilegdb.gdb/a00000004.CatItemsByType.atxUTpЗbux PKT& &Dtestopenfilegdb.gdb/a00000021.gdbtablxUTpЗbux PKTof&0 &߅testopenfilegdb.gdb/a0000001e.gdbtablxUTpЗbux PKTOE#2 &otestopenfilegdb.gdb/a00000012.gdbtablxUTpЗbux PKT=3testopenfilegdb.gdb/a00000006.CatRelsByOriginID.atxUTpЗbux PKT& &testopenfilegdb.gdb/a00000025.gdbtablxUTpЗbux PKT[׉&testopenfilegdb.gdb/a0000002d.gdbtableUTpЗbux PKTn(W5 &testopenfilegdb.gdb/a00000016.gdbtablxUTpЗbux PKTt:!testopenfilegdb.gdb/a00000024.spxUTpЗbux PKTpzBt((testopenfilegdb.gdb/a0000001f.gdbindexesUTpЗbux PKTt:!̋testopenfilegdb.gdb/a00000029.spxUTpЗbux PKT=X!&atestopenfilegdb.gdb/a0000001d.freelistUTpЗbux PKT0#!testopenfilegdb.gdb/a0000001d.spxUTpЗbux PKTpzBt(|testopenfilegdb.gdb/a00000027.gdbindexesUTpЗbux PKT3 )& testopenfilegdb.gdb/a00000005.gdbtableUToЗbux PKTee&testopenfilegdb.gdb/a00000028.gdbtableUTpЗbux PKT0#!ntestopenfilegdb.gdb/a00000004.spxUToЗbux PKTpzBt(testopenfilegdb.gdb/a0000002b.gdbindexesUTpЗbux PKT9H2 &testopenfilegdb.gdb/a0000000b.gdbtablxUTpЗbux PKTX&"testopenfilegdb.gdb/a00000016.gdbtableUTpЗbux PKTO;testopenfilegdb.gdb/a00000007.CatRelTypesByForwardLabel.atxUToЗbux PKT& &testopenfilegdb.gdb/a0000002b.gdbtablxUTpЗbux PKTk| t& &testopenfilegdb.gdb/a00000026.gdbtablxUTpЗbux PKTJ*testopenfilegdb.gdb/a00000004.FDO_UUID.atxUTpЗbux PKTt:!9testopenfilegdb.gdb/a0000002c.spxUTpЗbux PKTSX8Οtestopenfilegdb.gdb/a00000006.CatRelsByDestinationID.atxUTpЗbux PKTlL &ݣtestopenfilegdb.gdb/a0000001d.gdbtablxUTpЗbux PKTݦr((testopenfilegdb.gdb/a00000005.gdbindexesUToЗbux PKT+ H!]testopenfilegdb.gdb/a00000017.spxUTpЗbux PKT9 &testopenfilegdb.gdb/a0000001c.gdbtablxUTpЗbux PKTpzBt(testopenfilegdb.gdb/a00000029.gdbindexesUTpЗbux PKT'2!testopenfilegdb.gdb/a00000011.spxUTpЗbux PKTZ&testopenfilegdb.gdb/a00000018.gdbtableUTpЗbux PKTY%<Ӭtestopenfilegdb.gdb/a00000007.CatRelTypesByBackwardLabel.atxUToЗbux PKTJ'3ntestopenfilegdb.gdb/a00000007.CatRelTypesByName.atxUToЗbux PKTA&testopenfilegdb.gdb/a00000009.gdbtableUToЗbux PKTee&ptestopenfilegdb.gdb/a00000026.gdbtableUTpЗbux PKT)w&5testopenfilegdb.gdb/a00000003.gdbtableUTpЗbux PKTK.2 &testopenfilegdb.gdb/a00000013.gdbtablxUTpЗbux PKTQ/testopenfilegdb.gdb/a00000006.CatRelsByType.atxUTpЗbux PKTхD&testopenfilegdb.gdb/a0000001b.gdbtableUTpЗbux PKT& &Ѹtestopenfilegdb.gdb/a00000029.gdbtablxUTpЗbux PKTpzBt(Wtestopenfilegdb.gdb/a00000020.gdbindexesUTpЗbux PKTL5&testopenfilegdb.gdb/a0000000a.gdbtableUTpЗbux PKTpzBt(Ltestopenfilegdb.gdb/a0000000c.gdbindexesUTpЗbux PKT"B| &testopenfilegdb.gdb/a00000005.gdbtablxUToЗbux PKT˖Pn(̽testopenfilegdb.gdb/a00000001.gdbindexesUToЗbux PKTw~ 6 &~testopenfilegdb.gdb/a00000009.gdbtablxUTpЗbux PKTlM!testopenfilegdb.gdb/a0000000b.spxUTpЗbux PKT+ H!testopenfilegdb.gdb/a00000016.spxUTpЗbux PKTk| t& &_testopenfilegdb.gdb/a00000022.gdbtablxUTpЗbux PKTpzBt(testopenfilegdb.gdb/a00000012.gdbindexesUTpЗbux PKT0#!testopenfilegdb.gdb/a0000002d.spxUTpЗbux PKTϩ(!testopenfilegdb.gdb/a00000022.spxUTpЗbux PKTt:!testopenfilegdb.gdb/a0000002b.spxUTpЗbux PKTpzBt(testopenfilegdb.gdb/a00000019.gdbindexesUTpЗbux PKT1*3(testopenfilegdb.gdb/a00000009.idx_id.atxUTpЗbux PKT$W*/ &Xtestopenfilegdb.gdb/a0000000d.gdbtablxUTpЗbux PKT& &testopenfilegdb.gdb/a00000027.gdbtablxUTpЗbux PKTpzBt(mtestopenfilegdb.gdb/a0000000f.gdbindexesUTpЗbux PKT#YL&testopenfilegdb.gdb/a00000023.gdbtableUTpЗbux PKTdZ(testopenfilegdb.gdb/a00000007.gdbindexesUToЗbux PKTpzBt(testopenfilegdb.gdb/a0000002a.gdbindexesUTpЗbux PKTpzBt(}testopenfilegdb.gdb/a0000001b.gdbindexesUTpЗbux PKTlM!!testopenfilegdb.gdb/a0000000c.spxUTpЗbux PKTk| t& &testopenfilegdb.gdb/a00000024.gdbtablxUTpЗbux PKT$*&Otestopenfilegdb.gdb/a00000015.gdbtableUTpЗbux PKT gRd&testopenfilegdb.gdb/a00000029.gdbtableUTpЗbux PKT2W*testopenfilegdb.gdb/a0000000a.idx_guid.atxUTpЗbux PKT'E#i&Ftestopenfilegdb.gdb/a00000004.gdbtableUTpЗbux PKTpzBt(@testopenfilegdb.gdb/a00000014.gdbindexesUTpЗbux PKTpzBt(testopenfilegdb.gdb/a00000013.gdbindexesUTpЗbux PKT& &testopenfilegdb.gdb/a00000023.gdbtablxUTpЗbux PKTi5c&testopenfilegdb.gdb/a0000001e.gdbtableUTpЗbux PKTof&testopenfilegdb.gdb/a0000002a.gdbtableUTpЗbux PKTt:!itestopenfilegdb.gdb/a00000025.spxUTpЗbux PKT(T 2 &testopenfilegdb.gdb/a00000011.gdbtablxUTpЗbux PKTt:!testopenfilegdb.gdb/a00000027.spxUTpЗbux PKTl|&%testopenfilegdb.gdb/a00000020.gdbtableUTpЗbux PKTt:!Vtestopenfilegdb.gdb/a0000002a.spxUTpЗbux PKTk| t& &testopenfilegdb.gdb/a0000002c.gdbtablxUTpЗbux PKT/N1 &qtestopenfilegdb.gdb/a00000019.gdbtablxUTpЗbux PKTpzBt(testopenfilegdb.gdb/a00000023.gdbindexesUTpЗbux PKTDA &testopenfilegdb.gdb/a0000001f.gdbtablxUTpЗbux PKTߥc"-Gtestopenfilegdb.gdb/a0000000a.idx_nullint.atxUTpЗbux PKTlM!testopenfilegdb.gdb/a00000013.spxUTpЗbux PKT\55 &xtestopenfilegdb.gdb/a00000010.gdbtablxUTpЗbux PKTA< testopenfilegdb.gdb/a00000005.CatItemTypesByParentTypeID.atxUToЗbux PKTaw(704testopenfilegdb.gdb/a00000005.CatItemTypesByName.atxUToЗbux PKT'2!testopenfilegdb.gdb/a0000000a.spxUTpЗbux PKTFiona-1.10.1/tests/data/trio.geojson000066400000000000000000000030231467206072700172550ustar00rootroot00000000000000{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "Le château d'eau" }, "geometry": { "type": "Point", "coordinates": [ 3.869011402130127, 43.611401128587104 ] } }, { "type": "Feature", "properties": { "aqueduct": "yes" }, "geometry": { "type": "LineString", "coordinates": [ [ 3.8645052909851074, 43.61172738574996 ], [ 3.868989944458008, 43.61140889663537 ] ] } }, { "type": "Feature", "properties": {"name": "promenade du Peyrou", "architect": "Giral"}, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3.8684856891632085, 43.61205364114294 ], [ 3.8683247566223145, 43.6108340583545 ], [ 3.8685393333435054, 43.610748608951816 ], [ 3.871554136276245, 43.610577709782206 ], [ 3.871725797653198, 43.61063208684338 ], [ 3.8719189167022705, 43.61183613774427 ], [ 3.8684856891632085, 43.61205364114294 ] ] ] } } ] } Fiona-1.10.1/tests/data/trio.seq000066400000000000000000000014451467206072700164070ustar00rootroot00000000000000{"geometry": {"coordinates": [3.869011402130127, 43.611401128587104], "type": "Point"}, "id": "0", "properties": {"name": "Le ch\u00e2teau d'eau"}, "type": "Feature"} {"geometry": {"coordinates": [[3.8645052909851074, 43.61172738574996], [3.868989944458008, 43.61140889663537]], "type": "LineString"}, "id": "1", "properties": {"aqueduct": "yes"}, "type": "Feature"} {"geometry": {"coordinates": [[[3.8684856891632085, 43.61205364114294], [3.8683247566223145, 43.6108340583545], [3.8685393333435054, 43.610748608951816], [3.871554136276245, 43.610577709782206], [3.871725797653198, 43.61063208684338], [3.8719189167022705, 43.61183613774427], [3.8684856891632085, 43.61205364114294]]], "type": "Polygon"}, "id": "2", "properties": {"architect": "Giral", "name": "promenade du Peyrou"}, "type": "Feature"} Fiona-1.10.1/tests/test__env.py000066400000000000000000000073241467206072700163510ustar00rootroot00000000000000"""Tests of _env util module""" from unittest import mock import pytest from fiona._env import GDALDataFinder, PROJDataFinder from .conftest import gdal_version @pytest.fixture def mock_wheel(tmpdir): """A fake rasterio wheel""" moduledir = tmpdir.mkdir("rasterio") moduledir.ensure("__init__.py") moduledir.ensure("_env.py") moduledir.ensure("gdal_data/header.dxf") moduledir.ensure("proj_data/epsg") return moduledir @pytest.fixture def mock_fhs(tmpdir): """A fake FHS system""" tmpdir.ensure("share/gdal/header.dxf") tmpdir.ensure("share/proj/epsg") return tmpdir @pytest.fixture def mock_debian(tmpdir): """A fake Debian multi-install system""" tmpdir.ensure(f"share/gdal/{gdal_version.major}.{gdal_version.minor}/header.dxf") tmpdir.ensure("share/proj/epsg") return tmpdir def test_search_wheel_gdal_data_failure(tmpdir): """Fail to find GDAL data in a non-wheel""" finder = GDALDataFinder() assert not finder.search_wheel(str(tmpdir)) def test_search_wheel_gdal_data(mock_wheel): """Find GDAL data in a wheel""" finder = GDALDataFinder() assert finder.search_wheel(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("gdal_data")) def test_search_prefix_gdal_data_failure(tmpdir): """Fail to find GDAL data in a bogus prefix""" finder = GDALDataFinder() assert not finder.search_prefix(str(tmpdir)) def test_search_prefix_gdal_data(mock_fhs): """Find GDAL data under prefix""" finder = GDALDataFinder() assert finder.search_prefix(str(mock_fhs)) == str(mock_fhs.join("share").join("gdal")) def test_search_debian_gdal_data_failure(tmpdir): """Fail to find GDAL data in a bogus Debian location""" finder = GDALDataFinder() assert not finder.search_debian(str(tmpdir)) def test_search_debian_gdal_data(mock_debian): """Find GDAL data under Debian locations""" finder = GDALDataFinder() assert finder.search_debian(str(mock_debian)) == str(mock_debian.join("share").join("gdal").join(f"{gdal_version.major}.{gdal_version.minor}")) def test_search_gdal_data_wheel(mock_wheel): finder = GDALDataFinder() assert finder.search(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("gdal_data")) def test_search_gdal_data_fhs(mock_fhs): finder = GDALDataFinder() assert finder.search(str(mock_fhs)) == str(mock_fhs.join("share").join("gdal")) def test_search_gdal_data_debian(mock_debian): """Find GDAL data under Debian locations""" finder = GDALDataFinder() assert finder.search(str(mock_debian)) == str(mock_debian.join("share").join("gdal").join(f"{gdal_version.major}.{gdal_version.minor}")) def test_search_wheel_proj_data_failure(tmpdir): """Fail to find GDAL data in a non-wheel""" finder = PROJDataFinder() assert not finder.search_wheel(str(tmpdir)) def test_search_wheel_proj_data(mock_wheel): """Find GDAL data in a wheel""" finder = PROJDataFinder() assert finder.search_wheel(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("proj_data")) def test_search_prefix_proj_data_failure(tmpdir): """Fail to find GDAL data in a bogus prefix""" finder = PROJDataFinder() assert not finder.search_prefix(str(tmpdir)) def test_search_prefix_proj_data(mock_fhs): """Find GDAL data under prefix""" finder = PROJDataFinder() assert finder.search_prefix(str(mock_fhs)) == str(mock_fhs.join("share").join("proj")) def test_search_proj_data_wheel(mock_wheel): finder = PROJDataFinder() assert finder.search(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("proj_data")) def test_search_proj_data_fhs(mock_fhs): finder = PROJDataFinder() assert finder.search(str(mock_fhs)) == str(mock_fhs.join("share").join("proj")) Fiona-1.10.1/tests/test__path.py000066400000000000000000000014751467206072700165160ustar00rootroot00000000000000"""_path tests.""" import sys from fiona._path import _parse_path, _vsi_path def test_parse_zip_windows(monkeypatch): """Parse a zip+ Windows path.""" monkeypatch.setattr(sys, "platform", "win32") path = _parse_path("zip://D:\\a\\Fiona\\Fiona\\tests\\data\\coutwildrnp.zip!coutwildrnp.shp") vsi_path = _vsi_path(path) assert vsi_path.startswith("/vsizip/D") assert vsi_path.endswith("coutwildrnp.zip/coutwildrnp.shp") def test_parse_zip_windows(monkeypatch): """Parse a tar+ Windows path.""" monkeypatch.setattr(sys, "platform", "win32") path = _parse_path("tar://D:\\a\\Fiona\\Fiona\\tests\\data\\coutwildrnp.tar!testing/coutwildrnp.shp") vsi_path = _vsi_path(path) assert vsi_path.startswith("/vsitar/D") assert vsi_path.endswith("coutwildrnp.tar/testing/coutwildrnp.shp") Fiona-1.10.1/tests/test_bigint.py000066400000000000000000000053301467206072700166710ustar00rootroot00000000000000"""OGR 64bit handling: https://trac.osgeo.org/gdal/wiki/rfc31_ogr_64 Shapefile: OFTInteger fields are created by default with a width of 9 characters, so to be unambiguously read as OFTInteger (and if specifying integer that require 10 or 11 characters. the field is dynamically extended like managed since a few versions). OFTInteger64 fields are created by default with a width of 18 digits, so to be unambiguously read as OFTInteger64, and extended to 19 or 20 if needed. Integer fields of width between 10 and 18 will be read as OFTInteger64. Above they will be treated as OFTReal. In previous GDAL versions, Integer fields were created with a default with of 10, and thus will be now read as OFTInteger64. An open option, DETECT_TYPE=YES, can be specified so as OGR does a full scan of the DBF file to see if integer fields of size 10 or 11 hold 32 bit or 64 bit values and adjust the type accordingly (and same for integer fields of size 19 or 20, in case of overflow of 64 bit integer, OFTReal is chosen) """ import pytest import fiona from fiona.env import calc_gdal_version_num, get_gdal_version_num from fiona.model import Feature def testCreateBigIntSchema(tmpdir): name = str(tmpdir.join("output1.shp")) a_bigint = 10 ** 18 - 1 fieldname = "abigint" kwargs = { "driver": "ESRI Shapefile", "crs": "EPSG:4326", "schema": {"geometry": "Point", "properties": [(fieldname, "int:10")]}, } with fiona.open(name, "w", **kwargs) as dst: rec = {} rec["geometry"] = {"type": "Point", "coordinates": (0, 0)} rec["properties"] = {fieldname: a_bigint} dst.write(Feature.from_dict(**rec)) with fiona.open(name) as src: if fiona.gdal_version >= (2, 0, 0): first = next(iter(src)) assert first["properties"][fieldname] == a_bigint @pytest.mark.parametrize("dtype", ["int", "int64"]) def test_issue691(tmpdir, dtype): """Type 'int' maps to 'int64'""" schema = {"geometry": "Any", "properties": {"foo": dtype}} with fiona.open( str(tmpdir.join("test.shp")), "w", driver="Shapefile", schema=schema, crs="epsg:4326", ) as dst: dst.write( Feature.from_dict( **{ "type": "Feature", "geometry": { "type": "Point", "coordinates": (-122.278015, 37.868995), }, "properties": {"foo": 3694063472}, } ) ) with fiona.open(str(tmpdir.join("test.shp"))) as src: assert src.schema["properties"]["foo"] == "int:18" first = next(iter(src)) assert first["properties"]["foo"] == 3694063472 Fiona-1.10.1/tests/test_binary_field.py000066400000000000000000000021731467206072700200460ustar00rootroot00000000000000"""Binary BLOB field testing.""" import struct import fiona from fiona.model import Feature from .conftest import requires_gpkg @requires_gpkg def test_binary_field(tmpdir): meta = { "driver": "GPKG", "schema": { "geometry": "Point", "properties": {"name": "str", "data": "bytes"}, }, } # create some binary data input_data = struct.pack("256B", *range(256)) # write the binary data to a BLOB field filename = str(tmpdir.join("binary_test.gpkg")) with fiona.open(filename, "w", **meta) as dst: feature = Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": ((0, 0))}, "properties": { "name": "test", "data": input_data, }, } ) dst.write(feature) # read the data back and check consistency with fiona.open(filename, "r") as src: feature = next(iter(src)) assert feature.properties["name"] == "test" output_data = feature.properties["data"] assert output_data == input_data Fiona-1.10.1/tests/test_bounds.py000066400000000000000000000052211467206072700167060ustar00rootroot00000000000000import pytest from fiona._env import get_gdal_version_tuple import fiona from fiona.drvsupport import supported_drivers, _driver_supports_mode from fiona.errors import DriverError from fiona.env import GDALVersion from tests.conftest import get_temp_filename def test_bounds_point(): g = {"type": "Point", "coordinates": [10, 10]} assert fiona.bounds(g) == (10, 10, 10, 10) def test_bounds_line(): g = {"type": "LineString", "coordinates": [[0, 0], [10, 10]]} assert fiona.bounds(g) == (0, 0, 10, 10) def test_bounds_polygon(): g = {"type": "Polygon", "coordinates": [[[0, 0], [10, 10], [10, 0]]]} assert fiona.bounds(g) == (0, 0, 10, 10) def test_bounds_z(): g = {"type": "Point", "coordinates": [10, 10, 10]} assert fiona.bounds(g) == (10, 10, 10, 10) # MapInfo File driver requires that the bounds (geographical extents) of a new file # be set before writing the first feature (https://gdal.org/drivers/vector/mitab.html) @pytest.mark.parametrize( "driver", [ driver for driver in supported_drivers if _driver_supports_mode(driver, "w") and not driver == "MapInfo File" ], ) def test_bounds(tmpdir, driver, testdata_generator): """Test if bounds are correctly calculated after writing.""" if driver == "BNA" and GDALVersion.runtime() < GDALVersion(2, 0): pytest.skip("BNA driver segfaults with gdal 1.11") if driver == "ESRI Shapefile" and get_gdal_version_tuple() < (3, 1): pytest.skip( "Bug in GDALs Shapefile driver: https://github.com/OSGeo/gdal/issues/2269" ) range1 = list(range(0, 5)) range2 = list(range(5, 10)) schema, crs, records1, records2, test_equal = testdata_generator( driver, range1, range2 ) if not schema["geometry"] == "Point": pytest.skip("Driver does not support point geometries") filename = get_temp_filename(driver) path = str(tmpdir.join(filename)) def calc_bounds(records): xs = [] ys = [] for r in records: xs.append(r.geometry["coordinates"][0]) ys.append(r.geometry["coordinates"][1]) return min(xs), max(xs), min(ys), max(ys) with fiona.open(path, "w", crs="OGC:CRS84", driver=driver, schema=schema) as c: c.writerecords(records1) try: bounds = c.bounds assert bounds == calc_bounds(records1) except Exception as e: assert isinstance(e, DriverError) c.writerecords(records2) try: bounds = c.bounds assert bounds == calc_bounds(records1 + records2) except Exception as e: assert isinstance(e, DriverError) Fiona-1.10.1/tests/test_bytescollection.py000066400000000000000000000160121467206072700206160ustar00rootroot00000000000000"""Tests for ``fiona.BytesCollection()``.""" import pytest import fiona from fiona.model import Geometry class TestReading: @pytest.fixture(autouse=True) def bytes_collection_object(self, path_coutwildrnp_json): with open(path_coutwildrnp_json) as src: bytesbuf = src.read().encode("utf-8") self.c = fiona.BytesCollection(bytesbuf, encoding="utf-8") yield self.c.close() def test_construct_with_str(self, path_coutwildrnp_json): with open(path_coutwildrnp_json) as src: strbuf = src.read() with pytest.raises(ValueError): fiona.BytesCollection(strbuf) def test_open_repr(self): # I'm skipping checking the name of the virtual file as it produced by uuid. print(repr(self.c)) assert repr(self.c).startswith(" 0 def test_mode(self): assert self.c.mode == "r" def test_collection(self): assert self.c.encoding == "utf-8" def test_iter(self): assert iter(self.c) def test_closed_no_iter(self): self.c.close() with pytest.raises(ValueError): iter(self.c) def test_len(self): assert len(self.c) == 67 def test_closed_len(self): # Len is lazy, it's never computed in this case. TODO? self.c.close() assert len(self.c) == 0 def test_len_closed_len(self): # Lazy len is computed in this case and sticks. len(self.c) self.c.close() assert len(self.c) == 67 def test_driver(self): assert self.c.driver == "GeoJSON" def test_closed_driver(self): self.c.close() assert self.c.driver is None def test_driver_closed_driver(self): self.c.driver self.c.close() assert self.c.driver == "GeoJSON" def test_schema(self): s = self.c.schema["properties"] assert s["PERIMETER"] == "float" assert s["NAME"] == "str" assert s["URL"] == "str" assert s["STATE_FIPS"] == "str" assert s["WILDRNP020"] == "int32" def test_closed_schema(self): # Schema is lazy too, never computed in this case. TODO? self.c.close() assert self.c.schema is None def test_schema_closed_schema(self): self.c.schema self.c.close() assert sorted(self.c.schema.keys()) == ["geometry", "properties"] def test_crs(self): assert self.c.crs["init"] == "epsg:4326" def test_crs_wkt(self): assert self.c.crs_wkt.startswith('GEOGCS["WGS 84"') def test_closed_crs(self): # Crs is lazy too, never computed in this case. TODO? self.c.close() assert self.c.crs is None def test_crs_closed_crs(self): self.c.crs self.c.close() assert sorted(self.c.crs.keys()) == ["init"] def test_meta(self): assert sorted(self.c.meta.keys()) == ["crs", "crs_wkt", "driver", "schema"] def test_bounds(self): assert self.c.bounds[0] == pytest.approx(-113.564247) assert self.c.bounds[1] == pytest.approx(37.068981) assert self.c.bounds[2] == pytest.approx(-104.970871) assert self.c.bounds[3] == pytest.approx(41.996277) def test_iter_one(self): itr = iter(self.c) f = next(itr) assert f["id"] == "0" assert f["properties"]["STATE"] == "UT" def test_iter_list(self): f = list(self.c)[0] assert f["id"] == "0" assert f["properties"]["STATE"] == "UT" def test_re_iter_list(self): f = list(self.c)[0] # Run through iterator f = list(self.c)[0] # Run through a new, reset iterator assert f["id"] == "0" assert f["properties"]["STATE"] == "UT" def test_getitem_one(self): f = self.c[0] assert f["id"] == "0" assert f["properties"]["STATE"] == "UT" def test_no_write(self): with pytest.raises(OSError): self.c.write({}) def test_iter_items_list(self): i, f = list(self.c.items())[0] assert i == 0 assert f["id"] == "0" assert f["properties"]["STATE"] == "UT" def test_iter_keys_list(self): i = list(self.c.keys())[0] assert i == 0 def test_in_keys(self): assert 0 in self.c.keys() assert 0 in self.c class TestFilterReading: @pytest.fixture(autouse=True) def bytes_collection_object(self, path_coutwildrnp_json): with open(path_coutwildrnp_json) as src: bytesbuf = src.read().encode("utf-8") self.c = fiona.BytesCollection(bytesbuf) yield self.c.close() def test_filter_1(self): results = list(self.c.filter(bbox=(-120.0, 30.0, -100.0, 50.0))) assert len(results) == 67 f = results[0] assert f["id"] == "0" assert f["properties"]["STATE"] == "UT" def test_filter_reset(self): results = list(self.c.filter(bbox=(-112.0, 38.0, -106.0, 40.0))) assert len(results) == 26 results = list(self.c.filter()) assert len(results) == 67 def test_filter_mask(self): mask = Geometry.from_dict( **{ "type": "Polygon", "coordinates": ( ((-112, 38), (-112, 40), (-106, 40), (-106, 38), (-112, 38)), ), } ) results = list(self.c.filter(mask=mask)) assert len(results) == 26 def test_zipped_bytes_collection(bytes_coutwildrnp_zip): """Open a zipped stream of bytes as a collection""" with fiona.BytesCollection(bytes_coutwildrnp_zip) as col: assert col.name == "coutwildrnp" assert len(col) == 67 @pytest.mark.skipif( fiona.gdal_version >= (2, 3, 0), reason="Changed behavior with gdal 2.3, possibly related to RFC 70:" "Guessing output format from output file name extension for utilities", ) def test_grenada_bytes_geojson(bytes_grenada_geojson): """Read grenada.geojson as BytesCollection. grenada.geojson is an example of geojson that GDAL's GeoJSON driver will fail to read successfully unless the file's extension reflects its json'ness. """ # We expect an exception if the GeoJSON driver isn't specified. with pytest.raises(fiona.errors.FionaValueError): with fiona.BytesCollection(bytes_grenada_geojson) as col: pass # If told what driver to use, we should be good. with fiona.BytesCollection(bytes_grenada_geojson, driver="GeoJSON") as col: assert len(col) == 1 Fiona-1.10.1/tests/test_collection.py000066400000000000000000001145101467206072700175510ustar00rootroot00000000000000"""Testing collections and workspaces.""" # coding=utf-8 import datetime import json import logging import os import random import re import sys import pytest import fiona from fiona.collection import Collection from fiona.drvsupport import supported_drivers from fiona.env import getenv from fiona.errors import ( AttributeFilterError, FionaValueError, DriverError, FionaDeprecationWarning, ) from fiona.model import Feature, Geometry from .conftest import WGS84PATTERN class TestSupportedDrivers: def test_shapefile(self): assert "ESRI Shapefile" in supported_drivers assert set(supported_drivers["ESRI Shapefile"]) == set("raw") def test_map(self): assert "MapInfo File" in supported_drivers assert set(supported_drivers["MapInfo File"]) == set("raw") class TestCollectionArgs: def test_path(self): with pytest.raises(TypeError): Collection(0) def test_mode(self): with pytest.raises(TypeError): Collection("foo", mode=0) def test_driver(self): with pytest.raises(TypeError): Collection("foo", mode="w", driver=1) def test_schema(self): with pytest.raises(TypeError): Collection("foo", mode="w", driver="ESRI Shapefile", schema=1) def test_crs(self): with pytest.raises(TypeError): Collection("foo", mode="w", driver="ESRI Shapefile", schema=0, crs=1) def test_encoding(self): with pytest.raises(TypeError): Collection("foo", mode="r", encoding=1) def test_layer(self): with pytest.raises(TypeError): Collection("foo", mode="r", layer=0.5) def test_vsi(self): with pytest.raises(TypeError): Collection("foo", mode="r", vsi="git") def test_archive(self): with pytest.raises(TypeError): Collection("foo", mode="r", archive=1) def test_write_numeric_layer(self): with pytest.raises(ValueError): Collection("foo", mode="w", layer=1) def test_write_geojson_layer(self): with pytest.raises(ValueError): Collection("foo", mode="w", driver="GeoJSON", layer="foo") def test_append_geojson(self): with pytest.raises(ValueError): Collection("foo", mode="w", driver="ARCGEN") class TestOpenException: def test_no_archive(self): with pytest.warns(FionaDeprecationWarning), pytest.raises(DriverError): fiona.open("/", mode="r", vfs="zip:///foo.zip") class TestReading: @pytest.fixture(autouse=True) def shapefile(self, path_coutwildrnp_shp): self.c = fiona.open(path_coutwildrnp_shp, "r") yield self.c.close() def test_open_repr(self, path_coutwildrnp_shp): assert repr(self.c) == ( f"" ) def test_closed_repr(self, path_coutwildrnp_shp): self.c.close() assert repr(self.c) == ( f"" ) def test_path(self, path_coutwildrnp_shp): assert self.c.path == path_coutwildrnp_shp def test_name(self): assert self.c.name == "coutwildrnp" def test_mode(self): assert self.c.mode == "r" def test_encoding(self): assert self.c.encoding is None def test_iter(self): assert iter(self.c) def test_closed_no_iter(self): self.c.close() with pytest.raises(ValueError): iter(self.c) def test_len(self): assert len(self.c) == 67 def test_closed_len(self): # Len is lazy, it's never computed in this case. TODO? self.c.close() assert len(self.c) == 0 def test_len_closed_len(self): # Lazy len is computed in this case and sticks. len(self.c) self.c.close() assert len(self.c) == 67 def test_driver(self): assert self.c.driver == "ESRI Shapefile" def test_closed_driver(self): self.c.close() assert self.c.driver is None def test_driver_closed_driver(self): self.c.driver self.c.close() assert self.c.driver == "ESRI Shapefile" def test_schema(self): s = self.c.schema["properties"] assert s["PERIMETER"] == "float:24.15" assert s["NAME"] == "str:80" assert s["URL"] == "str:101" assert s["STATE_FIPS"] == "str:80" assert s["WILDRNP020"] == "int:10" def test_closed_schema(self): # Schema is lazy too, never computed in this case. TODO? self.c.close() assert self.c.schema is None def test_schema_closed_schema(self): self.c.schema self.c.close() assert sorted(self.c.schema.keys()) == ["geometry", "properties"] def test_crs(self): crs = self.c.crs assert crs["init"] == "epsg:4326" def test_crs_wkt(self): crs = self.c.crs_wkt assert re.match(WGS84PATTERN, crs) def test_closed_crs(self): # Crs is lazy too, never computed in this case. TODO? self.c.close() assert self.c.crs is None def test_crs_closed_crs(self): self.c.crs self.c.close() assert sorted(self.c.crs.keys()) == ["init"] def test_meta(self): assert sorted(self.c.meta.keys()) == ["crs", "crs_wkt", "driver", "schema"] def test_profile(self): assert sorted(self.c.profile.keys()) == ["crs", "crs_wkt", "driver", "schema"] def test_bounds(self): assert self.c.bounds[0] == pytest.approx(-113.564247) assert self.c.bounds[1] == pytest.approx(37.068981) assert self.c.bounds[2] == pytest.approx(-104.970871) assert self.c.bounds[3] == pytest.approx(41.996277) def test_context(self, path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp, "r") as c: assert c.name == "coutwildrnp" assert len(c) == 67 assert c.crs assert c.closed def test_iter_one(self): itr = iter(self.c) f = next(itr) assert f.id == "0" assert f.properties["STATE"] == "UT" def test_iter_list(self): f = list(self.c)[0] assert f.id == "0" assert f.properties["STATE"] == "UT" def test_re_iter_list(self): f = list(self.c)[0] # Run through iterator f = list(self.c)[0] # Run through a new, reset iterator assert f.id == "0" assert f.properties["STATE"] == "UT" def test_getitem_one(self): f = self.c[0] assert f.id == "0" assert f.properties["STATE"] == "UT" def test_getitem_iter_combo(self): i = iter(self.c) f = next(i) f = next(i) assert f.id == "1" f = self.c[0] assert f.id == "0" f = next(i) assert f.id == "2" def test_no_write(self): with pytest.raises(OSError): self.c.write({}) def test_iter_items_list(self): i, f = list(self.c.items())[0] assert i == 0 assert f.id == "0" assert f.properties["STATE"] == "UT" def test_iter_keys_list(self): i = list(self.c.keys())[0] assert i == 0 def test_in_keys(self): assert 0 in self.c.keys() assert 0 in self.c class TestReadingPathTest: def test_open_path(self, path_coutwildrnp_shp): pathlib = pytest.importorskip("pathlib") with fiona.open(pathlib.Path(path_coutwildrnp_shp)) as collection: assert collection.name == "coutwildrnp" @pytest.mark.usefixtures("unittest_path_coutwildrnp_shp") class TestIgnoreFieldsAndGeometry: def test_without_ignore(self): with fiona.open(self.path_coutwildrnp_shp, "r") as collection: assert "AREA" in collection.schema["properties"].keys() assert "STATE" in collection.schema["properties"].keys() assert "NAME" in collection.schema["properties"].keys() assert "geometry" in collection.schema.keys() feature = next(iter(collection)) assert feature["properties"]["AREA"] is not None assert feature["properties"]["STATE"] is not None assert feature["properties"]["NAME"] is not None assert feature["geometry"] is not None def test_ignore_fields(self): with fiona.open( self.path_coutwildrnp_shp, "r", ignore_fields=["AREA", "STATE"] ) as collection: assert "AREA" not in collection.schema["properties"].keys() assert "STATE" not in collection.schema["properties"].keys() assert "NAME" in collection.schema["properties"].keys() assert "geometry" in collection.schema.keys() feature = next(iter(collection)) assert "AREA" not in feature["properties"].keys() assert "STATE" not in feature["properties"].keys() assert feature["properties"]["NAME"] is not None assert feature["geometry"] is not None def test_ignore_invalid_field_missing(self): with fiona.open( self.path_coutwildrnp_shp, "r", ignore_fields=["DOES_NOT_EXIST"] ): pass def test_ignore_invalid_field_not_string(self): with pytest.raises(TypeError): with fiona.open(self.path_coutwildrnp_shp, "r", ignore_fields=[42]): pass def test_include_fields(self): with fiona.open( self.path_coutwildrnp_shp, "r", include_fields=["AREA", "STATE"] ) as collection: assert sorted(collection.schema["properties"]) == ["AREA", "STATE"] assert "geometry" in collection.schema.keys() feature = next(iter(collection)) assert sorted(feature["properties"]) == ["AREA", "STATE"] assert feature["properties"]["AREA"] is not None assert feature["properties"]["STATE"] is not None assert feature["geometry"] is not None def test_include_fields__geom_only(self): with fiona.open( self.path_coutwildrnp_shp, "r", include_fields=() ) as collection: assert sorted(collection.schema["properties"]) == [] assert "geometry" in collection.schema.keys() feature = next(iter(collection)) assert sorted(feature["properties"]) == [] assert feature["geometry"] is not None def test_include_fields__ignore_fields_error(self): with pytest.raises(ValueError): with fiona.open( self.path_coutwildrnp_shp, "r", include_fields=["AREA"], ignore_fields=["STATE"], ): pass def test_ignore_geometry(self): with fiona.open( self.path_coutwildrnp_shp, "r", ignore_geometry=True ) as collection: assert "AREA" in collection.schema["properties"].keys() assert "STATE" in collection.schema["properties"].keys() assert "NAME" in collection.schema["properties"].keys() assert "geometry" not in collection.schema.keys() feature = next(iter(collection)) assert feature.properties["AREA"] is not None assert feature.properties["STATE"] is not None assert feature.properties["NAME"] is not None assert feature.geometry is None class TestFilterReading: @pytest.fixture(autouse=True) def shapefile(self, path_coutwildrnp_shp): self.c = fiona.open(path_coutwildrnp_shp, "r") yield self.c.close() def test_filter_1(self): results = list(self.c.filter(bbox=(-120.0, 30.0, -100.0, 50.0))) assert len(results) == 67 f = results[0] assert f.id == "0" assert f.properties["STATE"] == "UT" def test_filter_reset(self): results = list(self.c.filter(bbox=(-112.0, 38.0, -106.0, 40.0))) assert len(results) == 26 results = list(self.c.filter()) assert len(results) == 67 def test_filter_mask(self): mask = Geometry.from_dict( **{ "type": "Polygon", "coordinates": ( ((-112, 38), (-112, 40), (-106, 40), (-106, 38), (-112, 38)), ), } ) results = list(self.c.filter(mask=mask)) assert len(results) == 26 def test_filter_where(self): results = list(self.c.filter(where="NAME LIKE 'Mount%'")) assert len(results) == 9 assert all([x.properties["NAME"].startswith("Mount") for x in results]) results = list(self.c.filter(where="NAME LIKE '%foo%'")) assert len(results) == 0 results = list(self.c.filter()) assert len(results) == 67 def test_filter_where_error(self): for w in ["bad stuff", "NAME=3", "NNAME LIKE 'Mount%'"]: with pytest.raises(AttributeFilterError): self.c.filter(where=w) def test_filter_bbox_where(self): # combined filter criteria results = set( self.c.keys(bbox=(-120.0, 40.0, -100.0, 50.0), where="NAME LIKE 'Mount%'") ) assert results == {0, 2, 5, 13} results = set(self.c.keys()) assert len(results) == 67 class TestUnsupportedDriver: def test_immediate_fail_driver(self, tmpdir): schema = { "geometry": "Point", "properties": {"label": "str", "verit\xe9": "int"}, } with pytest.raises(DriverError): fiona.open(str(tmpdir.join("foo")), "w", "Bogus", schema=schema) @pytest.mark.iconv class TestGenericWritingTest: @pytest.fixture(autouse=True) def no_iter_shp(self, tmpdir): schema = { "geometry": "Point", "properties": [("label", "str"), ("verit\xe9", "int")], } self.c = fiona.open( str(tmpdir.join("test-no-iter.shp")), "w", driver="ESRI Shapefile", schema=schema, encoding="Windows-1252", ) yield self.c.close() def test_encoding(self): assert self.c.encoding == "Windows-1252" def test_no_iter(self): with pytest.raises(OSError): iter(self.c) def test_no_filter(self): with pytest.raises(OSError): self.c.filter() class TestPropertiesNumberFormatting: @pytest.fixture(autouse=True) def shapefile(self, tmpdir): self.filename = str(tmpdir.join("properties_number_formatting_test")) _records_with_float_property1 = [ { "geometry": {"type": "Point", "coordinates": (0.0, 0.1)}, "properties": {"property1": 12.22}, }, { "geometry": {"type": "Point", "coordinates": (0.0, 0.2)}, "properties": {"property1": 12.88}, }, ] _records_with_float_property1_as_string = [ { "geometry": {"type": "Point", "coordinates": (0.0, 0.1)}, "properties": {"property1": "12.22"}, }, { "geometry": {"type": "Point", "coordinates": (0.0, 0.2)}, "properties": {"property1": "12.88"}, }, ] _records_with_invalid_number_property1 = [ { "geometry": {"type": "Point", "coordinates": (0.0, 0.3)}, "properties": {"property1": "invalid number"}, } ] def _write_collection(self, records, schema, driver): with fiona.open( self.filename, "w", driver=driver, schema=schema, crs="epsg:4326", encoding="utf-8", ) as c: c.writerecords([Feature.from_dict(**rec) for rec in records]) def test_shape_driver_truncates_float_property_to_requested_int_format(self): driver = "ESRI Shapefile" self._write_collection( self._records_with_float_property1, {"geometry": "Point", "properties": [("property1", "int")]}, driver, ) with fiona.open(self.filename, driver=driver, encoding="utf-8") as c: assert 2 == len(c) rf1, rf2 = list(c) assert 12 == rf1.properties["property1"] assert 12 == rf2.properties["property1"] def test_shape_driver_rounds_float_property_to_requested_digits_number(self): driver = "ESRI Shapefile" self._write_collection( self._records_with_float_property1, {"geometry": "Point", "properties": [("property1", "float:15.1")]}, driver, ) with fiona.open(self.filename, driver=driver, encoding="utf-8") as c: assert 2 == len(c) rf1, rf2 = list(c) assert 12.2 == rf1.properties["property1"] assert 12.9 == rf2.properties["property1"] def test_string_is_converted_to_number_and_truncated_to_requested_int_by_shape_driver( self, ): driver = "ESRI Shapefile" self._write_collection( self._records_with_float_property1_as_string, {"geometry": "Point", "properties": [("property1", "int")]}, driver, ) with fiona.open(self.filename, driver=driver, encoding="utf-8") as c: assert 2 == len(c) rf1, rf2 = list(c) assert 12 == rf1.properties["property1"] assert 12 == rf2.properties["property1"] def test_string_is_converted_to_number_and_rounded_to_requested_digits_number_by_shape_driver( self, ): driver = "ESRI Shapefile" self._write_collection( self._records_with_float_property1_as_string, {"geometry": "Point", "properties": [("property1", "float:15.1")]}, driver, ) with fiona.open(self.filename, driver=driver, encoding="utf-8") as c: assert 2 == len(c) rf1, rf2 = list(c) assert 12.2 == rf1.properties["property1"] assert 12.9 == rf2.properties["property1"] def test_invalid_number_is_converted_to_0_and_written_by_shape_driver(self): driver = "ESRI Shapefile" self._write_collection( self._records_with_invalid_number_property1, # {'geometry': 'Point', 'properties': [('property1', 'int')]}, {"geometry": "Point", "properties": [("property1", "float:15.1")]}, driver, ) with fiona.open(self.filename, driver=driver, encoding="utf-8") as c: assert 1 == len(c) rf1 = c[0] assert 0 == rf1.properties["property1"] def test_geojson_driver_truncates_float_property_to_requested_int_format(self): driver = "GeoJSON" self._write_collection( self._records_with_float_property1, {"geometry": "Point", "properties": [("property1", "int")]}, driver, ) with fiona.open(self.filename, driver=driver, encoding="utf-8") as c: assert 2 == len(c) rf1, rf2 = list(c) assert 12 == rf1.properties["property1"] assert 12 == rf2.properties["property1"] def test_geojson_driver_does_not_round_float_property_to_requested_digits_number( self, ): driver = "GeoJSON" self._write_collection( self._records_with_float_property1, {"geometry": "Point", "properties": [("property1", "float:15.1")]}, driver, ) with fiona.open(self.filename, driver=driver, encoding="utf-8") as c: assert 2 == len(c) rf1, rf2 = list(c) # **************************************** # FLOAT FORMATTING IS NOT RESPECTED... assert 12.22 == rf1.properties["property1"] assert 12.88 == rf2.properties["property1"] def test_string_is_converted_to_number_and_truncated_to_requested_int_by_geojson_driver( self, ): driver = "GeoJSON" self._write_collection( self._records_with_float_property1_as_string, {"geometry": "Point", "properties": [("property1", "int")]}, driver, ) with fiona.open(self.filename, driver=driver, encoding="utf-8") as c: assert 2 == len(c) rf1, rf2 = list(c) assert 12 == rf1.properties["property1"] assert 12 == rf2.properties["property1"] def test_string_is_converted_to_number_but_not_rounded_to_requested_digits_number_by_geojson_driver( self, ): driver = "GeoJSON" self._write_collection( self._records_with_float_property1_as_string, {"geometry": "Point", "properties": [("property1", "float:15.1")]}, driver, ) with fiona.open(self.filename, driver=driver, encoding="utf-8") as c: assert 2 == len(c) rf1, rf2 = list(c) # **************************************** # FLOAT FORMATTING IS NOT RESPECTED... assert 12.22 == rf1.properties["property1"] assert 12.88 == rf2.properties["property1"] def test_invalid_number_is_converted_to_0_and_written_by_geojson_driver(self): driver = "GeoJSON" self._write_collection( self._records_with_invalid_number_property1, {"geometry": "Point", "properties": [("property1", "float:15.1")]}, driver, ) with fiona.open(self.filename, driver=driver, encoding="utf-8") as c: assert 1 == len(c) rf1 = c[0] assert 0 == rf1.properties["property1"] class TestPointWriting: @pytest.fixture(autouse=True) def shapefile(self, tmpdir): self.filename = str(tmpdir.join("point_writing_test.shp")) self.sink = fiona.open( self.filename, "w", driver="ESRI Shapefile", schema={ "geometry": "Point", "properties": [("title", "str"), ("date", "date")], }, crs="epsg:4326", encoding="utf-8", ) yield self.sink.close() def test_cpg(self, tmpdir): """Requires GDAL 1.9""" self.sink.close() encoding = tmpdir.join("point_writing_test.cpg").read() assert encoding == "UTF-8" def test_write_one(self): assert len(self.sink) == 0 assert self.sink.bounds == (0.0, 0.0, 0.0, 0.0) f = Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, 0.1)}, "properties": {"title": "point one", "date": "2012-01-29"}, } ) self.sink.writerecords([f]) assert len(self.sink) == 1 assert self.sink.bounds == (0.0, 0.1, 0.0, 0.1) self.sink.close() def test_write_two(self): assert len(self.sink) == 0 assert self.sink.bounds == (0.0, 0.0, 0.0, 0.0) f1 = Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, 0.1)}, "properties": {"title": "point one", "date": "2012-01-29"}, } ) f2 = Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, -0.1)}, "properties": {"title": "point two", "date": "2012-01-29"}, } ) self.sink.writerecords([f1, f2]) assert len(self.sink) == 2 assert self.sink.bounds == (0.0, -0.1, 0.0, 0.1) def test_write_one_null_geom(self): assert len(self.sink) == 0 assert self.sink.bounds == (0.0, 0.0, 0.0, 0.0) f = Feature.from_dict( **{ "geometry": None, "properties": {"title": "point one", "date": "2012-01-29"}, } ) self.sink.writerecords([f]) assert len(self.sink) == 1 assert self.sink.bounds == (0.0, 0.0, 0.0, 0.0) def test_validate_record(self): fvalid = { "geometry": {"type": "Point", "coordinates": (0.0, 0.1)}, "properties": {"title": "point one", "date": "2012-01-29"}, } finvalid = { "geometry": {"type": "Point", "coordinates": (0.0, -0.1)}, "properties": {"not-a-title": "point two", "date": "2012-01-29"}, } assert self.sink.validate_record(fvalid) assert not self.sink.validate_record(finvalid) class TestLineWriting: @pytest.fixture(autouse=True) def shapefile(self, tmpdir): self.sink = fiona.open( str(tmpdir.join("line_writing_test.shp")), "w", driver="ESRI Shapefile", schema={ "geometry": "LineString", "properties": [("title", "str"), ("date", "date")], }, crs={"init": "epsg:4326", "no_defs": True}, ) yield self.sink.close() def test_write_one(self): assert len(self.sink) == 0 assert self.sink.bounds == (0.0, 0.0, 0.0, 0.0) f = Feature.from_dict( **{ "geometry": { "type": "LineString", "coordinates": [(0.0, 0.1), (0.0, 0.2)], }, "properties": {"title": "line one", "date": "2012-01-29"}, } ) self.sink.writerecords([f]) assert len(self.sink) == 1 assert self.sink.bounds == (0.0, 0.1, 0.0, 0.2) def test_write_two(self): assert len(self.sink) == 0 assert self.sink.bounds == (0.0, 0.0, 0.0, 0.0) f1 = Feature.from_dict( **{ "geometry": { "type": "LineString", "coordinates": [(0.0, 0.1), (0.0, 0.2)], }, "properties": {"title": "line one", "date": "2012-01-29"}, } ) f2 = Feature.from_dict( **{ "geometry": { "type": "MultiLineString", "coordinates": [ [(0.0, 0.0), (0.0, -0.1)], [(0.0, -0.1), (0.0, -0.2)], ], }, "properties": {"title": "line two", "date": "2012-01-29"}, } ) self.sink.writerecords([f1, f2]) assert len(self.sink) == 2 assert self.sink.bounds == (0.0, -0.2, 0.0, 0.2) class TestPointAppend: @pytest.fixture(autouse=True) def shapefile(self, tmpdir, path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp, "r") as input: output_schema = input.schema output_schema["geometry"] = "3D Point" with fiona.open( str(tmpdir.join("test_append_point.shp")), "w", crs=None, driver="ESRI Shapefile", schema=output_schema, ) as output: for f in input: fnew = Feature( id=f.id, properties=f.properties, geometry=Geometry( type="Point", coordinates=f.geometry.coordinates[0][0] ), ) output.write(fnew) def test_append_point(self, tmpdir): with fiona.open(str(tmpdir.join("test_append_point.shp")), "a") as c: assert c.schema["geometry"] == "3D Point" c.write( Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, 45.0)}, "properties": { "PERIMETER": 1.0, "FEATURE2": None, "NAME": "Foo", "FEATURE1": None, "URL": "http://example.com", "AGBUR": "BAR", "AREA": 0.0, "STATE_FIPS": 1, "WILDRNP020": 1, "STATE": "XL", }, } ) ) assert len(c) == 68 class TestLineAppend: @pytest.fixture(autouse=True) def shapefile(self, tmpdir): with fiona.open( str(tmpdir.join("test_append_line.shp")), "w", driver="ESRI Shapefile", schema={ "geometry": "MultiLineString", "properties": {"title": "str", "date": "date"}, }, crs={"init": "epsg:4326", "no_defs": True}, ) as output: f = Feature.from_dict( **{ "geometry": { "type": "MultiLineString", "coordinates": [[(0.0, 0.1), (0.0, 0.2)]], }, "properties": {"title": "line one", "date": "2012-01-29"}, } ) output.writerecords([f]) def test_append_line(self, tmpdir): with fiona.open(str(tmpdir.join("test_append_line.shp")), "a") as c: assert c.schema["geometry"] == "LineString" f1 = Feature.from_dict( **{ "geometry": { "type": "LineString", "coordinates": [(0.0, 0.1), (0.0, 0.2)], }, "properties": {"title": "line one", "date": "2012-01-29"}, } ) f2 = Feature.from_dict( **{ "geometry": { "type": "MultiLineString", "coordinates": [ [(0.0, 0.0), (0.0, -0.1)], [(0.0, -0.1), (0.0, -0.2)], ], }, "properties": {"title": "line two", "date": "2012-01-29"}, } ) c.writerecords([f1, f2]) assert len(c) == 3 assert c.bounds == (0.0, -0.2, 0.0, 0.2) def test_shapefile_field_width(tmpdir): name = str(tmpdir.join("textfield.shp")) with fiona.open( name, "w", schema={"geometry": "Point", "properties": {"text": "str:254"}}, driver="ESRI Shapefile", ) as c: c.write( Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, 45.0)}, "properties": {"text": "a" * 254}, } ) ) c = fiona.open(name, "r") assert c.schema["properties"]["text"] == "str:254" f = next(iter(c)) assert f.properties["text"] == "a" * 254 c.close() class TestCollection: def test_invalid_mode(self, tmpdir): with pytest.raises(ValueError): fiona.open(str(tmpdir.join("bogus.shp")), "r+") def test_w_args(self, tmpdir): with pytest.raises(FionaValueError): fiona.open(str(tmpdir.join("test-no-iter.shp")), "w") with pytest.raises(FionaValueError): fiona.open(str(tmpdir.join("test-no-iter.shp")), "w", "Driver") def test_no_path(self): with pytest.raises(Exception): fiona.open("no-path.shp", "a") def test_no_read_conn_str(self): with pytest.raises(DriverError): fiona.open("PG:dbname=databasename", "r") @pytest.mark.skipif( sys.platform.startswith("win"), reason="test only for *nix based system" ) def test_no_read_directory(self): with pytest.raises(DriverError): fiona.open("/dev/null", "r") def test_date(tmpdir): name = str(tmpdir.join("date_test.shp")) sink = fiona.open( name, "w", driver="ESRI Shapefile", schema={"geometry": "Point", "properties": [("id", "int"), ("date", "date")]}, crs={"init": "epsg:4326", "no_defs": True}, ) recs = [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (7.0, 50.0)}, "properties": {"id": 1, "date": "2013-02-25"}, } ), Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (7.0, 50.2)}, "properties": {"id": 1, "date": datetime.date(2014, 2, 3)}, } ), ] sink.writerecords(recs) sink.close() assert len(sink) == 2 with fiona.open(name, "r") as c: assert len(c) == 2 rf1, rf2 = list(c) assert rf1.properties["date"] == "2013-02-25" assert rf2.properties["date"] == "2014-02-03" def test_open_kwargs(tmpdir, path_coutwildrnp_shp): dstfile = str(tmpdir.join("test.json")) with fiona.open(path_coutwildrnp_shp) as src: kwds = src.profile kwds["driver"] = "GeoJSON" kwds["coordinate_precision"] = 2 with fiona.open(dstfile, "w", **kwds) as dst: dst.writerecords(ftr for ftr in src) with open(dstfile) as f: assert '"coordinates": [ [ [ -111.74, 42.0 ], [ -111.66, 42.0 ]' in f.read(2000) @pytest.mark.network def test_collection_http(): ds = fiona.Collection( "https://raw.githubusercontent.com/Toblerity/Fiona/main/tests/data/coutwildrnp.shp", vsi="https", ) assert ( ds.path == "/vsicurl/https://raw.githubusercontent.com/Toblerity/Fiona/main/tests/data/coutwildrnp.shp" ) assert len(ds) == 67 @pytest.mark.network def test_collection_zip_http(): ds = fiona.Collection( "https://raw.githubusercontent.com/Toblerity/Fiona/main/tests/data/coutwildrnp.zip", vsi="zip+https", ) assert ds.path == "/vsizip/vsicurl/https://raw.githubusercontent.com/Toblerity/Fiona/main/tests/data/coutwildrnp.zip" assert len(ds) == 67 def test_encoding_option_warning(tmpdir, caplog): """There is no ENCODING creation option log warning for GeoJSON""" with caplog.at_level(logging.WARNING): fiona.Collection( str(tmpdir.join("test.geojson")), "w", driver="GeoJSON", crs="EPSG:4326", schema={"geometry": "Point", "properties": {"foo": "int"}}, encoding="bogus", ) assert not caplog.text def test_closed_session_next(gdalenv, path_coutwildrnp_shp): """Confirm fix for issue #687""" src = fiona.open(path_coutwildrnp_shp) itr = iter(src) list(itr) src.close() with pytest.raises(FionaValueError): next(itr) def test_collection_no_env(path_coutwildrnp_shp): """We have no GDAL env left over from open""" collection = fiona.open(path_coutwildrnp_shp) assert collection with pytest.raises(Exception): getenv() def test_collection_env(path_coutwildrnp_shp): """We have a GDAL env within collection context""" with fiona.open(path_coutwildrnp_shp): assert "FIONA_ENV" in getenv() @pytest.mark.parametrize( "driver,filename", [("ESRI Shapefile", "test.shp"), ("GeoJSON", "test.json"), ("GPKG", "test.gpkg")], ) def test_mask_polygon_triangle(tmpdir, driver, filename): """Test if mask works for non trivial geometries""" schema = { "geometry": "Polygon", "properties": {"position_i": "int", "position_j": "int"}, } records = [ Feature.from_dict( **{ "geometry": { "type": "Polygon", "coordinates": ( ( (float(i), float(j)), (float(i + 1), float(j)), (float(i + 1), float(j + 1)), (float(i), float(j + 1)), (float(i), float(j)), ), ), }, "properties": {"position_i": i, "position_j": j}, } ) for i in range(10) for j in range(10) ] random.shuffle(records) path = str(tmpdir.join(filename)) with fiona.open( path, "w", driver=driver, schema=schema, ) as c: c.writerecords(records) with fiona.open(path) as c: items = list( c.items( mask=Geometry.from_dict( **{ "type": "Polygon", "coordinates": ( ((2.0, 2.0), (4.0, 4.0), (4.0, 6.0), (2.0, 2.0)), ), } ) ) ) assert len(items) == 15 def test_collection__empty_column_name(tmpdir): """Based on pull #955""" tmpfile = str(tmpdir.join("test_empty.geojson")) with pytest.warns(UserWarning, match="Empty field name at index 0"): with fiona.open( tmpfile, "w", driver="GeoJSON", schema={"geometry": "Point", "properties": {"": "str", "name": "str"}}, ) as tmp: tmp.writerecords( [ { "geometry": {"type": "Point", "coordinates": [8, 49]}, "properties": {"": "", "name": "test"}, } ] ) with fiona.open(tmpfile) as tmp: with pytest.warns(UserWarning, match="Empty field name at index 0"): assert tmp.schema == { "geometry": "Point", "properties": {"": "str", "name": "str"}, } with pytest.warns(UserWarning, match="Empty field name at index 0"): next(tmp) @pytest.mark.parametrize( "extension, driver", [ ("shp", "ESRI Shapefile"), ("geojson", "GeoJSON"), ("json", "GeoJSON"), ("gpkg", "GPKG"), ("SHP", "ESRI Shapefile"), ], ) def test_driver_detection(tmpdir, extension, driver): with fiona.open( str(tmpdir.join(f"test.{extension}")), "w", schema={ "geometry": "MultiLineString", "properties": {"title": "str", "date": "date"}, }, crs="EPSG:4326", ) as output: assert output.driver == driver @pytest.mark.skipif( sys.platform == "win32", reason="Windows test runners don't have full unicode support", ) def test_collection_name(tmp_path): """A Collection name is plumbed all the way through.""" filename = os.fspath(tmp_path.joinpath("test.geojson")) with fiona.Collection( filename, "w", driver="GeoJSON", crs="EPSG:4326", schema={"geometry": "Point", "properties": {"foo": "int"}}, layer="Darwin Núñez", write_name=True, ) as colxn: assert colxn.name == "Darwin Núñez" with open(filename) as f: geojson = json.load(f) assert geojson["name"] == "Darwin Núñez" Fiona-1.10.1/tests/test_collection_crs.py000066400000000000000000000045701467206072700204240ustar00rootroot00000000000000import os import re import pytest import fiona import fiona.crs from fiona.errors import CRSError from .conftest import WGS84PATTERN def test_collection_crs_wkt(path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp) as src: assert re.match(WGS84PATTERN, src.crs_wkt) def test_collection_no_crs_wkt(tmpdir, path_coutwildrnp_shp): """crs members of a dataset with no crs can be accessed safely.""" filename = str(tmpdir.join("test.shp")) with fiona.open(path_coutwildrnp_shp) as src: profile = src.meta del profile['crs'] del profile['crs_wkt'] with fiona.open(filename, 'w', **profile) as dst: assert dst.crs_wkt == "" assert dst.crs == fiona.crs.CRS() def test_collection_create_crs_wkt(tmpdir): """A collection can be created using crs_wkt""" filename = str(tmpdir.join("test.geojson")) wkt = 'GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295],AUTHORITY["EPSG","4326"]]' with fiona.open(filename, 'w', schema={'geometry': 'Point', 'properties': {'foo': 'int'}}, crs_wkt=wkt, driver='GeoJSON') as dst: assert dst.crs_wkt.startswith('GEOGCS["WGS 84') or dst.crs_wkt.startswith('GEOGCS["GCS_WGS_1984') with fiona.open(filename) as col: assert col.crs_wkt.startswith('GEOGCS["WGS 84') or col.crs_wkt.startswith('GEOGCS["GCS_WGS_1984') def test_collection_urn_crs(tmpdir): filename = str(tmpdir.join("test.geojson")) crs = "urn:ogc:def:crs:OGC:1.3:CRS84" with fiona.open(filename, 'w', schema={'geometry': 'Point', 'properties': {'foo': 'int'}}, crs=crs, driver='GeoJSON') as dst: assert dst.crs_wkt.startswith('GEOGCS["WGS 84') with fiona.open(filename) as col: assert col.crs_wkt.startswith('GEOGCS["WGS 84') def test_collection_invalid_crs(tmpdir): filename = str(tmpdir.join("test.geojson")) with pytest.raises(CRSError): with fiona.open(filename, 'w', schema={'geometry': 'Point', 'properties': {'foo': 'int'}}, crs="12ab-invalid", driver='GeoJSON') as dst: pass def test_collection_invalid_crs_wkt(tmpdir): filename = str(tmpdir.join("test.geojson")) with pytest.raises(CRSError): with fiona.open(filename, 'w', schema={'geometry': 'Point', 'properties': {'foo': 'int'}}, crs_wkt="12ab-invalid", driver='GeoJSON') as dst: pass Fiona-1.10.1/tests/test_collection_legacy.py000066400000000000000000000117061467206072700211000ustar00rootroot00000000000000# Testing collections and workspaces import unittest import re import pytest import fiona from .conftest import WGS84PATTERN @pytest.mark.usefixtures("unittest_path_coutwildrnp_shp") class ReadingTest(unittest.TestCase): def setUp(self): self.c = fiona.open(self.path_coutwildrnp_shp, "r") def tearDown(self): self.c.close() def test_open_repr(self): assert repr(self.c) == ( f"" ) def test_closed_repr(self): self.c.close() assert repr(self.c) == ( f"" ) def test_path(self): assert self.c.path == self.path_coutwildrnp_shp def test_name(self): assert self.c.name == 'coutwildrnp' def test_mode(self): assert self.c.mode == 'r' def test_encoding(self): assert self.c.encoding is None def test_iter(self): assert iter(self.c) def test_closed_no_iter(self): self.c.close() with pytest.raises(ValueError): iter(self.c) def test_len(self): assert len(self.c) == 67 def test_closed_len(self): # Len is lazy, it's never computed in this case. TODO? self.c.close() assert len(self.c) == 0 def test_len_closed_len(self): # Lazy len is computed in this case and sticks. len(self.c) self.c.close() assert len(self.c) == 67 def test_driver(self): assert self.c.driver == "ESRI Shapefile" def test_closed_driver(self): self.c.close() assert self.c.driver is None def test_driver_closed_driver(self): self.c.driver self.c.close() assert self.c.driver == "ESRI Shapefile" def test_schema(self): s = self.c.schema['properties'] assert s['PERIMETER'] == "float:24.15" assert s['NAME'] == "str:80" assert s['URL'] == "str:101" assert s['STATE_FIPS'] == "str:80" assert s['WILDRNP020'] == "int:10" def test_closed_schema(self): # Schema is lazy too, never computed in this case. TODO? self.c.close() assert self.c.schema is None def test_schema_closed_schema(self): self.c.schema self.c.close() assert sorted(self.c.schema.keys()) == ['geometry', 'properties'] def test_crs(self): crs = self.c.crs assert crs['init'] == 'epsg:4326' def test_crs_wkt(self): crs = self.c.crs_wkt assert re.match(WGS84PATTERN, crs) def test_closed_crs(self): # Crs is lazy too, never computed in this case. TODO? self.c.close() assert self.c.crs is None def test_crs_closed_crs(self): self.c.crs self.c.close() assert sorted(self.c.crs.keys()) == ['init'] def test_meta(self): assert (sorted(self.c.meta.keys()) == ['crs', 'crs_wkt', 'driver', 'schema']) def test_profile(self): assert (sorted(self.c.profile.keys()) == ['crs', 'crs_wkt', 'driver', 'schema']) def test_bounds(self): assert self.c.bounds[0] == pytest.approx(-113.564247) assert self.c.bounds[1] == pytest.approx(37.068981) assert self.c.bounds[2] == pytest.approx(-104.970871) assert self.c.bounds[3] == pytest.approx(41.996277) def test_context(self): with fiona.open(self.path_coutwildrnp_shp, "r") as c: assert c.name == 'coutwildrnp' assert len(c) == 67 assert c.closed def test_iter_one(self): itr = iter(self.c) f = next(itr) assert f['id'] == "0" assert f['properties']['STATE'] == 'UT' def test_iter_list(self): f = list(self.c)[0] assert f['id'] == "0" assert f['properties']['STATE'] == 'UT' def test_re_iter_list(self): f = list(self.c)[0] # Run through iterator f = list(self.c)[0] # Run through a new, reset iterator assert f['id'] == "0" assert f['properties']['STATE'] == 'UT' def test_getitem_one(self): f = self.c[0] assert f['id'] == "0" assert f['properties']['STATE'] == 'UT' def test_getitem_iter_combo(self): i = iter(self.c) f = next(i) f = next(i) assert f['id'] == "1" f = self.c[0] assert f['id'] == "0" f = next(i) assert f['id'] == "2" def test_no_write(self): with pytest.raises(OSError): self.c.write({}) def test_iter_items_list(self): i, f = list(self.c.items())[0] assert i == 0 assert f['id'] == "0" assert f['properties']['STATE'] == 'UT' def test_iter_keys_list(self): i = list(self.c.keys())[0] assert i == 0 def test_in_keys(self): assert 0 in self.c.keys() assert 0 in self.c Fiona-1.10.1/tests/test_compound_crs.py000066400000000000000000000012321467206072700201050ustar00rootroot00000000000000"""Test of compound CRS crash avoidance""" import fiona from fiona.crs import CRS def test_compound_crs(data): """Don't crash""" prj = data.join("coutwildrnp.prj") prj.write("""COMPD_CS["unknown",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AUTHORITY["EPSG","4326"]],VERT_CS["unknown",VERT_DATUM["unknown",2005],UNIT["metre",1.0,AUTHORITY["EPSG","9001"]],AXIS["Up",UP]]]""") with fiona.open(str(data.join("coutwildrnp.shp"))) as collection: assert isinstance(collection.crs, CRS) Fiona-1.10.1/tests/test_crs.py000066400000000000000000000135511467206072700162100ustar00rootroot00000000000000"""Tests of fiona.crs.""" import pytest from .conftest import requires_gdal33 from fiona import crs from fiona.env import Env from fiona.errors import CRSError, FionaDeprecationWarning def test_proj_keys(): assert len(crs.all_proj_keys) == 87 assert 'init' in crs.all_proj_keys assert 'proj' in crs.all_proj_keys assert 'no_mayo' in crs.all_proj_keys def test_to_string(): # Make a string from a mapping with a few bogus items val = { 'proj': 'longlat', 'ellps': 'WGS84', 'datum': 'WGS84', 'no_defs': True, 'foo': True, 'axis': False, 'belgium': [1, 2]} assert crs.CRS.from_user_input(val).to_string() == "EPSG:4326" def test_to_string_utm(): # Make a string from a mapping with a few bogus items val = { 'proj': 'utm', 'ellps': 'WGS84', 'zone': 13, 'no_defs': True, 'foo': True, 'axis': False, 'belgium': [1, 2]} assert crs.CRS.from_user_input(val).to_string() == "EPSG:32613" def test_to_string_epsg(): val = {'init': 'epsg:4326', 'no_defs': True} assert crs.CRS.from_user_input(val).to_string() == "EPSG:4326" def test_from_epsg(): val = crs.CRS.from_epsg(4326) assert val['init'] == "epsg:4326" def test_from_epsg_neg(): with pytest.raises(CRSError): crs.CRS.from_epsg(-1) @pytest.mark.parametrize("invalid_input", [ "a random string that is invalid", ("a", "tuple"), "-48567=409 =2095" ]) def test_invalid_crs(invalid_input): with pytest.raises(CRSError): crs.CRS.from_user_input(invalid_input) def test_custom_crs(): class CustomCRS: def to_wkt(self): return ( 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",' '6378137,298.257223563,AUTHORITY["EPSG","7030"]],' 'AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,' 'AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,' 'AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]' ) assert crs.CRS.from_user_input(CustomCRS()).to_wkt().startswith('GEOGCS["WGS 84"') def test_crs__version(): target_crs = ( 'PROJCS["IaRCS_04_Sioux_City-Iowa_Falls_NAD_1983_2011_LCC_US_Feet",' 'GEOGCS["GCS_NAD_1983_2011",DATUM["D_NAD_1983_2011",' 'SPHEROID["GRS_1980",6378137.0,298.257222101]],' 'PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],' 'PROJECTION["Lambert_Conformal_Conic"],' 'PARAMETER["False_Easting",14500000.0],' 'PARAMETER["False_Northing",8600000.0],' 'PARAMETER["Central_Meridian",-94.83333333333333],' 'PARAMETER["Standard_Parallel_1",42.53333333333333],' 'PARAMETER["Standard_Parallel_2",42.53333333333333],' 'PARAMETER["Scale_Factor",1.000045],' 'PARAMETER["Latitude_Of_Origin",42.53333333333333],' 'UNIT["Foot_US",0.3048006096012192]]' ) assert ( crs.CRS.from_user_input(target_crs) .to_wkt(version="WKT2_2018") .startswith( 'PROJCRS["IaRCS_04_Sioux_City-Iowa_Falls_NAD_1983_2011_LCC_US_Feet"' ) ) @requires_gdal33 def test_crs__esri_only_wkt(): """https://github.com/Toblerity/Fiona/issues/977""" target_crs = ( 'PROJCS["IaRCS_04_Sioux_City-Iowa_Falls_NAD_1983_2011_LCC_US_Feet",' 'GEOGCS["GCS_NAD_1983_2011",DATUM["D_NAD_1983_2011",' 'SPHEROID["GRS_1980",6378137.0,298.257222101]],' 'PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],' 'PROJECTION["Lambert_Conformal_Conic"],' 'PARAMETER["False_Easting",14500000.0],' 'PARAMETER["False_Northing",8600000.0],' 'PARAMETER["Central_Meridian",-94.83333333333333],' 'PARAMETER["Standard_Parallel_1",42.53333333333333],' 'PARAMETER["Standard_Parallel_2",42.53333333333333],' 'PARAMETER["Scale_Factor",1.000045],' 'PARAMETER["Latitude_Of_Origin",42.53333333333333],' 'UNIT["Foot_US",0.3048006096012192]]' ) assert ( crs.CRS.from_user_input(target_crs) .to_wkt() .startswith( ( 'PROJCS["IaRCS_04_Sioux_City-Iowa_Falls_NAD_1983_2011_LCC_US_Feet"', 'PROJCRS["IaRCS_04_Sioux_City-Iowa_Falls_NAD_1983_2011_LCC_US_Feet"', # GDAL 3.3+ ) ) ) def test_to_wkt__env_version(): with Env(OSR_WKT_FORMAT="WKT2_2018"): assert crs.CRS.from_string("EPSG:4326").to_wkt().startswith('GEOGCRS["WGS 84",') def test_to_wkt__invalid_version(): with pytest.raises(CRSError): crs.CRS.from_string("EPSG:4326").to_wkt(version="invalid") @pytest.mark.parametrize( "func, arg", [ (crs.from_epsg, 4326), (crs.from_string, "EPSG:4326"), (crs.to_string, "EPSG:4326"), ], ) def test_from_func_deprecations(func, arg): with pytest.warns(FionaDeprecationWarning): _ = func(arg) def test_xx(): """Create a CRS from WKT with a vertical datum.""" wkt = """ COMPD_CS["NAD83(CSRS) / UTM zone 10N + CGVD28 height", PROJCS["NAD83(CSRS) / UTM zone 10N", GEOGCS["NAD83(CSRS)", DATUM["NAD83_Canadian_Spatial_Reference_System", SPHEROID["GRS 1980",6378137,298.257222101, AUTHORITY["EPSG","7019"]], AUTHORITY["EPSG","6140"]], PRIMEM["Greenwich",0, AUTHORITY["EPSG","8901"]], UNIT["degree",0.0174532925199433, AUTHORITY["EPSG","9122"]], AUTHORITY["EPSG","4617"]], PROJECTION["Transverse_Mercator"], PARAMETER["latitude_of_origin",0], PARAMETER["central_meridian",-123], PARAMETER["scale_factor",0.9996], PARAMETER["false_easting",500000], PARAMETER["false_northing",0], UNIT["metre",1, AUTHORITY["EPSG","9001"]], AXIS["Easting",EAST], AXIS["Northing",NORTH], AUTHORITY["EPSG","3157"]], VERT_CS["CGVD28 height", VERT_DATUM["Canadian Geodetic Vertical Datum of 1928",2005, AUTHORITY["EPSG","5114"]], UNIT["metre",1, AUTHORITY["EPSG","9001"]], AXIS["Gravity-related height",UP], AUTHORITY["EPSG","5713"]]] """ val = crs.CRS.from_wkt(wkt) assert val.wkt.startswith("COMPD_CS") Fiona-1.10.1/tests/test_cursor_interruptions.py000066400000000000000000000115601467206072700217410ustar00rootroot00000000000000import fiona import pytest from fiona.drvsupport import driver_mode_mingdal, _driver_supports_mode from fiona.errors import DriverError from tests.conftest import get_temp_filename @pytest.mark.parametrize( "driver", [ driver for driver in driver_mode_mingdal["w"].keys() if _driver_supports_mode(driver, "w") ], ) def test_write_getextent(tmpdir, driver, testdata_generator): """Test if a call to OGR_L_GetExtent has side effects for writing.""" schema, crs, records1, records2, _ = testdata_generator( driver, range(0, 10), range(10, 20) ) path = str(tmpdir.join(get_temp_filename(driver))) positions = {int(r['properties']['position']) for r in records1 + records2} with fiona.open( path, "w", driver=driver, crs=crs, schema=schema, ) as c: c.writerecords(records1) # Call to OGR_L_GetExtent try: c.bounds except DriverError: pass c.writerecords(records2) with fiona.open(path) as c: data = {int(f['properties']['position']) for f in c} assert len(positions) == len(data) for p in positions: assert p in data @pytest.mark.parametrize( "driver", [ driver for driver in driver_mode_mingdal["w"].keys() if _driver_supports_mode(driver, "w") ], ) def test_read_getextent(tmpdir, driver, testdata_generator): """Test if a call to OGR_L_GetExtent has side effects for reading.""" schema, crs, records1, records2, _ = testdata_generator( driver, range(0, 10), range(10, 20) ) path = str(tmpdir.join(get_temp_filename(driver))) positions = {int(r['properties']['position']) for r in records1 + records2} with fiona.open( path, "w", driver=driver, crs=crs, schema=schema, ) as c: c.writerecords(records1) c.writerecords(records2) with fiona.open(path) as c: data = set() for _ in range(len(records1)): f = next(c) data.add(int(f['properties']['position'])) # Call to OGR_L_GetExtent try: c.bounds except DriverError: pass for _ in range(len(records1)): f = next(c) data.add(int(f['properties']['position'])) assert len(positions) == len(data) for p in positions: assert p in data @pytest.mark.parametrize( "driver", [ driver for driver in driver_mode_mingdal["w"].keys() if _driver_supports_mode(driver, "w") ], ) def test_write_getfeaturecount(tmpdir, driver, testdata_generator): """Test if a call to OGR_L_GetFeatureCount has side effects for writing.""" schema, crs, records1, records2, _ = testdata_generator( driver, range(0, 10), range(10, 20) ) path = str(tmpdir.join(get_temp_filename(driver))) positions = {int(r['properties']['position']) for r in records1 + records2} with fiona.open( path, "w", driver=driver, crs=crs, schema=schema, ) as c: c.writerecords(records1) # Call to OGR_L_GetFeatureCount try: assert len(c) == len(records1) except TypeError: pass c.writerecords(records2) with fiona.open(path) as c: data = {int(f['properties']['position']) for f in c} assert len(positions) == len(data) for p in positions: assert p in data @pytest.mark.parametrize( "driver", [ driver for driver in driver_mode_mingdal["w"].keys() if _driver_supports_mode(driver, "w") ], ) def test_read_getfeaturecount(tmpdir, driver, testdata_generator): """Test if a call to OGR_L_GetFeatureCount has side effects for reading.""" schema, crs, records1, records2, _ = testdata_generator( driver, range(0, 10), range(10, 20) ) path = str(tmpdir.join(get_temp_filename(driver))) positions = {int(r['properties']['position']) for r in records1 + records2} with fiona.open( path, "w", driver=driver, crs=crs, schema=schema, ) as c: c.writerecords(records1) c.writerecords(records2) with fiona.open(path) as c: data = set() for _ in range(len(records1)): f = next(c) data.add(int(f['properties']['position'])) # Call to OGR_L_GetFeatureCount try: assert len(data) == len(records1) except TypeError: pass for _ in range(len(records1)): f = next(c) data.add(int(f['properties']['position'])) try: assert len(data) == len(records1 + records2) except TypeError: pass assert len(positions) == len(data) for p in positions: assert p in data Fiona-1.10.1/tests/test_curve_geometries.py000066400000000000000000000012641467206072700207660ustar00rootroot00000000000000"""Tests of features related to GDAL RFC 49 See https://trac.osgeo.org/gdal/wiki/rfc49_curve_geometries. """ import fiona def test_line_curve_conversion(path_curves_line_csv): """Convert curved geometries to linear approximations""" with fiona.open(path_curves_line_csv) as col: assert col.schema["geometry"] == "Unknown" features = list(col) assert len(features) == 9 def test_multicurve_conversion(path_multicurve_gml): """Convert curved geometries to linear approximations""" with fiona.open(path_multicurve_gml) as col: assert col.schema["geometry"] == "MultiLineString" features = list(col) assert len(features) == 1 Fiona-1.10.1/tests/test_data_paths.py000066400000000000000000000035051467206072700175270ustar00rootroot00000000000000"""Tests of GDAL and PROJ data finding""" import os.path from click.testing import CliRunner import pytest import fiona from fiona._env import GDALDataFinder, PROJDataFinder from fiona.fio.main import main_group @pytest.mark.wheel def test_gdal_data_wheel(): """Get GDAL data path from a wheel""" assert GDALDataFinder().search() == os.path.join(os.path.dirname(fiona.__file__), 'gdal_data') @pytest.mark.wheel def test_proj_data_wheel(): """Get GDAL data path from a wheel""" assert PROJDataFinder().search() == os.path.join(os.path.dirname(fiona.__file__), 'proj_data') @pytest.mark.wheel def test_env_gdal_data_wheel(): runner = CliRunner() result = runner.invoke(main_group, ['env', '--gdal-data']) assert result.exit_code == 0 assert result.output.strip() == os.path.join(os.path.dirname(fiona.__file__), 'gdal_data') @pytest.mark.wheel def test_env_proj_data_wheel(): runner = CliRunner() result = runner.invoke(main_group, ['env', '--proj-data']) assert result.exit_code == 0 assert result.output.strip() == os.path.join(os.path.dirname(fiona.__file__), 'proj_data') def test_env_gdal_data_environ(monkeypatch): monkeypatch.setenv('GDAL_DATA', '/foo/bar') runner = CliRunner() result = runner.invoke(main_group, ['env', '--gdal-data']) assert result.exit_code == 0 assert result.output.strip() == '/foo/bar' @pytest.mark.parametrize("data_directory_env", ["PROJ_LIB", "PROJ_DATA"]) def test_env_proj_data_environ(data_directory_env, monkeypatch): monkeypatch.delenv('PROJ_DATA', raising=False) monkeypatch.delenv('PROJ_LIB', raising=False) monkeypatch.setenv(data_directory_env, '/foo/bar') runner = CliRunner() result = runner.invoke(main_group, ['env', '--proj-data']) assert result.exit_code == 0 assert result.output.strip() == '/foo/bar' Fiona-1.10.1/tests/test_datetime.py000066400000000000000000000744001467206072700172150ustar00rootroot00000000000000""" See also test_rfc3339.py for datetime parser tests. """ import fiona from fiona._env import get_gdal_version_num, calc_gdal_version_num import pytest from fiona.errors import DriverSupportError from fiona.rfc3339 import parse_time, parse_datetime from .conftest import get_temp_filename from fiona.env import GDALVersion from fiona.model import Feature import datetime from fiona.drvsupport import ( supported_drivers, driver_mode_mingdal, _driver_converts_field_type_silently_to_str, _driver_supports_field, _driver_converts_to_str, _driver_supports_timezones, _driver_supports_milliseconds, _driver_supports_mode, ) import pytz from pytz import timezone gdal_version = GDALVersion.runtime() def get_schema(driver, field_type): if driver == "GPX": return { "properties": {"ele": "float", "time": field_type}, "geometry": "Point", } if driver == "GPSTrackMaker": return { "properties": { "name": "str", "comment": "str", "icon": "int", "time": field_type, }, "geometry": "Point", } if driver == "CSV": return {"properties": {"datefield": field_type}} return {"geometry": "Point", "properties": {"datefield": field_type}} def get_records(driver, values): if driver == "GPX": return [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [1, 2]}, "properties": {"ele": 0, "time": val}, } ) for val in values ] if driver == "GPSTrackMaker": return [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [1, 2]}, "properties": {"name": "", "comment": "", "icon": 48, "time": val}, } ) for val in values ] if driver == "CSV": return [ Feature.from_dict(**{"properties": {"datefield": val}}) for val in values ] return [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [1, 2]}, "properties": {"datefield": val}, } ) for val in values ] def get_schema_field(driver, schema): if driver in {"GPX", "GPSTrackMaker"}: return schema["properties"]["time"] return schema["properties"]["datefield"] def get_field(driver, f): if driver in {"GPX", "GPSTrackMaker"}: return f["properties"]["time"] return f.properties["datefield"] class TZ(datetime.tzinfo): def __init__(self, minutes): self.minutes = minutes def utcoffset(self, dt): return datetime.timedelta(minutes=self.minutes) def generate_testdata(field_type, driver): """Generate test cases for test_datefield Each test case has the format [(in_value1, true_value as datetime.*object), (in_value2, true_value as datetime.*object), ...] """ # Test data for 'date' data type if field_type == "date": return [ ("2018-03-25", datetime.date(2018, 3, 25)), (datetime.date(2018, 3, 25), datetime.date(2018, 3, 25)), ] # Test data for 'datetime' data type if field_type == "datetime": return [ ("2018-03-25T22:49:05", datetime.datetime(2018, 3, 25, 22, 49, 5)), ( datetime.datetime(2018, 3, 25, 22, 49, 5), datetime.datetime(2018, 3, 25, 22, 49, 5), ), ( "2018-03-25T22:49:05.23", datetime.datetime(2018, 3, 25, 22, 49, 5, 230000), ), ( datetime.datetime(2018, 3, 25, 22, 49, 5, 230000), datetime.datetime(2018, 3, 25, 22, 49, 5, 230000), ), ( "2018-03-25T22:49:05.123456", datetime.datetime(2018, 3, 25, 22, 49, 5, 123000), ), ( datetime.datetime(2018, 3, 25, 22, 49, 5, 123456), datetime.datetime(2018, 3, 25, 22, 49, 5, 123000), ), ( "2018-03-25T22:49:05+01:30", datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(90)), ), ( "2018-03-25T22:49:05-01:30", datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(-90)), ), ( datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(90)), datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(90)), ), ( datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(-90)), datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(-90)), ), ( datetime.datetime(2020, 1, 21, 12, 0, 0, tzinfo=pytz.utc).astimezone( timezone("Europe/Zurich") ), datetime.datetime(2020, 1, 21, 12, 0, 0, tzinfo=pytz.utc).astimezone( timezone("Europe/Zurich") ), ), ( datetime.datetime(2020, 1, 21, 12, 0, 0, tzinfo=pytz.utc).astimezone( timezone("America/Denver") ), datetime.datetime(2020, 1, 21, 12, 0, 0, tzinfo=pytz.utc).astimezone( timezone("America/Denver") ), ), ( datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(60 * 24 - 15)), datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(60 * 24 - 15)), ), ( datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(-60 * 24 + 15)), datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(-60 * 24 + 15)), ), ( "2018-03-25T22:49:05-23:45", datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(-60 * 24 + 15)), ), ( "2018-03-25T22:49:05+23:45", datetime.datetime(2018, 3, 25, 22, 49, 5, tzinfo=TZ(60 * 24 - 15)), ), ] # Test data for 'time' data type elif field_type == "time": return [ ("22:49:05", datetime.time(22, 49, 5)), (datetime.time(22, 49, 5), datetime.time(22, 49, 5)), ("22:49:05.23", datetime.time(22, 49, 5, 230000)), (datetime.time(22, 49, 5, 230000), datetime.time(22, 49, 5, 230000)), ("22:49:05.123456", datetime.time(22, 49, 5, 123000)), (datetime.time(22, 49, 5, 123456), datetime.time(22, 49, 5, 123000)), ("22:49:05+01:30", datetime.time(22, 49, 5, tzinfo=TZ(90))), ("22:49:05-01:30", datetime.time(22, 49, 5, tzinfo=TZ(-90))), ( datetime.time(22, 49, 5, tzinfo=TZ(90)), datetime.time(22, 49, 5, tzinfo=TZ(90)), ), ( datetime.time(22, 49, 5, tzinfo=TZ(-90)), datetime.time(22, 49, 5, tzinfo=TZ(-90)), ), ( datetime.time(22, 49, 5, tzinfo=TZ(60 * 24 - 15)), datetime.time(22, 49, 5, tzinfo=TZ(60 * 24 - 15)), ), ( datetime.time(22, 49, 5, tzinfo=TZ(-60 * 24 + 15)), datetime.time(22, 49, 5, tzinfo=TZ(-60 * 24 + 15)), ), ("22:49:05-23:45", datetime.time(22, 49, 5, tzinfo=TZ(-60 * 24 + 15))), ("22:49:05+23:45", datetime.time(22, 49, 5, tzinfo=TZ(60 * 24 - 15))), ] def compare_datetimes_utc(d1, d2): """Test if two time objects are the same. Native times are assumed to be UTC""" if d1.tzinfo is None: d1 = d1.replace(tzinfo=TZ(0)) if d2.tzinfo is None: d2 = d2.replace(tzinfo=TZ(0)) return d1 == d2 def test_compare_datetimes_utc(): """Test compare_datetimes_utc""" d1 = datetime.datetime(2020, 1, 21, 12, 30, 0, tzinfo=TZ(60)) d2 = datetime.datetime(2020, 1, 21, 11, 30, 0, tzinfo=TZ(0)) assert d1 == d2 assert compare_datetimes_utc(d1, d2) d1 = datetime.datetime(2020, 1, 21, 12, 30, 0, tzinfo=TZ(-60)) d2 = datetime.datetime(2020, 1, 21, 11, 30, 0, tzinfo=TZ(0)) assert not d1 == d2 assert not compare_datetimes_utc(d1, d2) d1 = datetime.datetime(2020, 1, 21, 13, 0, 0, tzinfo=TZ(60)) d2 = datetime.datetime(2020, 1, 21, 5, 0, 0, tzinfo=TZ(-60 * 7)) assert d1 == d2 assert compare_datetimes_utc(d1, d2) d1 = datetime.datetime(2020, 1, 21, 12, 0, 0, tzinfo=pytz.utc).astimezone( timezone("Europe/Zurich") ) d2 = datetime.datetime(2020, 1, 21, 12, 0, 0, tzinfo=pytz.utc) assert d1 == d2 assert compare_datetimes_utc(d1, d2) d1 = datetime.datetime(2020, 1, 21, 12, 0, 0, tzinfo=pytz.utc).astimezone( timezone("Europe/Zurich") ) d2 = datetime.datetime(2020, 1, 21, 12, 0, 0, tzinfo=pytz.utc).astimezone( timezone("America/Denver") ) assert d1 == d2 assert compare_datetimes_utc(d1, d2) d1 = datetime.datetime(2020, 6, 21, 12, 0, 0, tzinfo=pytz.utc).astimezone( timezone("Europe/Zurich") ) d2 = datetime.datetime(2020, 6, 21, 12, 0, 0, tzinfo=pytz.utc).astimezone( timezone("America/Denver") ) assert d1 == d2 assert compare_datetimes_utc(d1, d2) def convert_time_to_utc(d): """Convert datetime.time object to UTC""" d = datetime.datetime( 1900, 1, 1, d.hour, d.minute, d.second, d.microsecond, d.tzinfo ) d -= d.utcoffset() return d.time() def compare_times_utc(d1, d2): """Test if two datetime.time objects with fixed timezones have the same UTC time""" if d1.tzinfo is not None: d1 = convert_time_to_utc(d1) if d2.tzinfo is not None: d2 = convert_time_to_utc(d2) return d1.replace(tzinfo=None) == d2.replace(tzinfo=None) def test_compare_times_utc(): """ Test compare_times_utc """ d1 = datetime.time(12, 30, 0, tzinfo=TZ(60)) d2 = datetime.time(11, 30, 0, tzinfo=TZ(0)) assert compare_times_utc(d1, d2) d1 = datetime.time(12, 30, 0, tzinfo=TZ(-60)) d2 = datetime.time(11, 30, 0, tzinfo=TZ(0)) assert not compare_times_utc(d1, d2) d1 = datetime.time(13, 0, 0, tzinfo=TZ(60)) d2 = datetime.time(5, 0, 0, tzinfo=TZ(-60 * 7)) assert compare_times_utc(d1, d2) d1 = ( datetime.datetime(2020, 6, 21, 12, 0, 0, tzinfo=pytz.utc) .astimezone(timezone("MET")) .timetz() ) d2 = ( datetime.datetime(2020, 6, 21, 12, 0, 0, tzinfo=pytz.utc) .astimezone(timezone("EST")) .timetz() ) assert compare_times_utc(d1, d2) def get_tz_offset(d): """Returns a Timezone (sign, hours, minutes) tuples E.g.: for '2020-01-21T12:30:00+01:30' ('+', 1, 30) is returned """ offset_minutes = d.utcoffset().total_seconds() / 60 if offset_minutes < 0: sign = "-" else: sign = "+" hours = int(abs(offset_minutes) / 60) minutes = int(abs(offset_minutes) % 60) return sign, hours, minutes def test_get_tz_offset(): """Test get_tz_offset""" d = datetime.datetime(2020, 1, 21, 12, 30, 0, tzinfo=TZ(90)) assert get_tz_offset(d) == ("+", 1, 30) d = datetime.datetime(2020, 1, 21, 12, 30, 0, tzinfo=TZ(-90)) assert get_tz_offset(d) == ("-", 1, 30) d = datetime.datetime(2020, 1, 21, 12, 30, 0, tzinfo=TZ(60 * 24 - 15)) assert get_tz_offset(d) == ("+", 23, 45) d = datetime.datetime(2020, 1, 21, 12, 30, 0, tzinfo=TZ(-60 * 24 + 15)) assert get_tz_offset(d) == ("-", 23, 45) def generate_testcases(): """Generate test cases for drivers that support date fields, convert date fields to string or do not support date fields""" _test_cases_datefield = [] _test_cases_datefield_to_str = [] _test_cases_datefield_not_supported = [] for field_type in ["time", "datetime", "date"]: # Select only driver that are capable of writing fields for driver, raw in supported_drivers.items(): if _driver_supports_mode(driver, "w"): if _driver_supports_field(driver, field_type): if _driver_converts_field_type_silently_to_str(driver, field_type): _test_cases_datefield_to_str.append((driver, field_type)) else: _test_cases_datefield.append((driver, field_type)) else: _test_cases_datefield_not_supported.append((driver, field_type)) return ( _test_cases_datefield, _test_cases_datefield_to_str, _test_cases_datefield_not_supported, ) ( test_cases_datefield, test_cases_datefield_to_str, test_cases_datefield_not_supported, ) = generate_testcases() @pytest.mark.parametrize("driver, field_type", test_cases_datefield) @pytest.mark.gdal def test_datefield(tmpdir, driver, field_type): """ Test date, time, datetime field types. """ def _validate(val, val_exp, field_type, driver): if field_type == "date": return val == val_exp.isoformat() elif field_type == "datetime": # some drivers do not support timezones. In this case, Fiona converts datetime fields with a timezone other # than UTC to UTC. Thus, both the datetime read by Fiona, as well as expected value are first converted to # UTC before compared. # Milliseconds if _driver_supports_milliseconds(driver): y, m, d, hh, mm, ss, ms, tz = parse_datetime(val) if tz is not None: tz = TZ(tz) val_d = datetime.datetime(y, m, d, hh, mm, ss, ms, tz) return compare_datetimes_utc(val_d, val_exp) else: # No Milliseconds y, m, d, hh, mm, ss, ms, tz = parse_datetime(val) if tz is not None: tz = TZ(tz) val_d = datetime.datetime(y, m, d, hh, mm, ss, ms, tz) return compare_datetimes_utc(val_d, val_exp.replace(microsecond=0)) elif field_type == "time": # some drivers do not support timezones. In this case, Fiona converts datetime fields with a timezone other # than UTC to UTC. Thus, both the time read by Fiona, as well as expected value are first converted to UTC # before compared. # Milliseconds if _driver_supports_milliseconds(driver): y, m, d, hh, mm, ss, ms, tz = parse_time(val) if tz is not None: tz = TZ(tz) val_d = datetime.time(hh, mm, ss, ms, tz) return compare_times_utc(val_d, val_exp) else: # No Milliseconds y, m, d, hh, mm, ss, ms, tz = parse_time(val) if tz is not None: tz = TZ(tz) val_d = datetime.time(hh, mm, ss, ms, tz) return compare_times_utc(val_d, val_exp.replace(microsecond=0)) return False schema = get_schema(driver, field_type) path = str(tmpdir.join(get_temp_filename(driver))) values_in, values_exp = zip(*generate_testdata(field_type, driver)) records = get_records(driver, values_in) with fiona.open(path, "w", driver=driver, schema=schema) as c: c.writerecords(records) with fiona.open(path, "r") as c: assert get_schema_field(driver, c.schema) == field_type, f"Returned field type is {get_schema_field(driver, c.schema)}, expected {field_type}" items = [get_field(driver, f) for f in c] assert len(items) == len(values_in) for val, val_exp in zip(items, values_exp): assert _validate( val, val_exp, field_type, driver ), f"{val} does not match {val_exp.isoformat()}" @pytest.mark.parametrize("driver, field_type", test_cases_datefield_to_str) @pytest.mark.gdal def test_datefield_driver_converts_to_string(tmpdir, driver, field_type): """ Test handling of date, time, datetime for drivers that convert these types to string. As the formatting can be arbitrary, we only test if the elements of a date / datetime / time object is included in the string. E.g. for the PCIDSK driver if hour 22 from date.time(22:49:05) is in '0000/00/00 22:49:05'. """ def _validate(val, val_exp, field_type, driver): if field_type == "date": if ( str(val_exp.year) in val and str(val_exp.month) in val and str(val_exp.day) in val ): return True elif field_type == "datetime": if ( not _driver_supports_timezones(driver, field_type) and val_exp.utcoffset() is not None ): val_exp = convert_time_to_utc(val_exp) # datetime fields can, depending on the driver, support: # - Timezones # - Milliseconds, respectively Microseconds # No timezone if val_exp.utcoffset() is None: # No Milliseconds if not _driver_supports_milliseconds(driver): if ( str(val_exp.year) in val and str(val_exp.month) in val and str(val_exp.day) in val and str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val ): return True else: # Microseconds if ( str(val_exp.year) in val and str(val_exp.month) in val and str(val_exp.day) in val and str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val and str(val_exp.microsecond) in val ): return True # Milliseconds elif ( str(val_exp.year) in val and str(val_exp.month) in val and str(val_exp.day) in val and str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val and str(int(val_exp.microsecond / 1000)) in val ): return True # With timezone else: sign, hours, minutes = get_tz_offset(val_exp) if minutes > 0: tz = f"{sign}{hours:02d}{minutes:02d}" else: tz = f"{sign}{hours:02d}" print("tz", tz) # No Milliseconds if not _driver_supports_milliseconds(driver): if ( str(val_exp.year) in val and str(val_exp.month) in val and str(val_exp.day) in val and str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val and tz in val ): return True else: # Microseconds if ( str(val_exp.year) in val and str(val_exp.month) in val and str(val_exp.day) in val and str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val and str(val_exp.microsecond) in val and tz in val ): return True # Milliseconds elif ( str(val_exp.year) in val and str(val_exp.month) in val and str(val_exp.day) in val and str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val and str(int(val_exp.microsecond / 1000)) in val and tz in val ): return True elif field_type == "time": # time fields can, depending on the driver, support: # - Timezones # - Milliseconds, respectively Microseconds if ( not _driver_supports_timezones(driver, field_type) and val_exp.utcoffset() is not None ): val_exp = convert_time_to_utc(val_exp) # No timezone if val_exp.utcoffset() is None: # No Milliseconds if not _driver_supports_milliseconds(driver): if ( str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val ): return True else: # Microseconds if ( str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val and str(val_exp.microsecond) in val ): return True # Milliseconds elif ( str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val and str(int(val_exp.microsecond / 1000)) in val ): return True # With timezone else: sign, hours, minutes = get_tz_offset(val_exp) if minutes > 0: tz = f"{sign}{hours:02d}{minutes:02d}" else: tz = f"{sign}{hours:02d}" # No Milliseconds if not _driver_supports_milliseconds(driver): if ( str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val and tz in val ): return True else: # Microseconds if ( str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val and str(val_exp.microsecond) in val and tz in val ): return True # Milliseconds elif ( str(val_exp.hour) in val and str(val_exp.minute) in val and str(val_exp.second) in val and str(int(val_exp.microsecond / 1000)) in val and tz in val ): return True return False schema = get_schema(driver, field_type) path = str(tmpdir.join(get_temp_filename(driver))) values_in, values_exp = zip(*generate_testdata(field_type, driver)) records = get_records(driver, values_exp) with pytest.warns(UserWarning) as record: with fiona.open(path, "w", driver=driver, schema=schema) as c: c.writerecords(records) assert len(record) == 1 assert "silently converts" in record[0].message.args[0] with fiona.open(path, "r") as c: assert get_schema_field(driver, c.schema) == "str" items = [get_field(driver, f) for f in c] assert len(items) == len(values_in) for val, val_exp in zip(items, values_exp): assert _validate( val, val_exp, field_type, driver ), f"{val} does not match {val_exp.isoformat()}" @pytest.mark.filterwarnings("ignore:.*driver silently converts *:UserWarning") @pytest.mark.parametrize( "driver,field_type", test_cases_datefield + test_cases_datefield_to_str ) @pytest.mark.gdal def test_datefield_null(tmpdir, driver, field_type): """ Test handling of null values for date, time, datetime types for write capable drivers """ def _validate(val, val_exp, field_type, driver): if ( driver == "MapInfo File" and field_type == "time" and calc_gdal_version_num(2, 0, 0) <= get_gdal_version_num() < calc_gdal_version_num(3, 1, 1) ): return val == "00:00:00" if val is None or val == "": return True return False schema = get_schema(driver, field_type) path = str(tmpdir.join(get_temp_filename(driver))) values_in = [None] records = get_records(driver, values_in) with fiona.open(path, "w", driver=driver, schema=schema) as c: c.writerecords(records) with fiona.open(path, "r") as c: items = [get_field(driver, f) for f in c] assert len(items) == 1 assert _validate( items[0], None, field_type, driver ), f"{items[0]} does not match {None}" @pytest.mark.parametrize("driver, field_type", test_cases_datefield_not_supported) @pytest.mark.gdal def test_datetime_field_unsupported(tmpdir, driver, field_type): """Test if DriverSupportError is raised for unsupported field_types""" schema = get_schema(driver, field_type) path = str(tmpdir.join(get_temp_filename(driver))) values_in, values_out = zip(*generate_testdata(field_type, driver)) records = get_records(driver, values_in) with pytest.raises(DriverSupportError): with fiona.open(path, "w", driver=driver, schema=schema) as c: c.writerecords(records) @pytest.mark.parametrize("driver, field_type", test_cases_datefield_not_supported) @pytest.mark.gdal def test_datetime_field_type_marked_not_supported_is_not_supported( tmpdir, driver, field_type, monkeypatch ): """Test if a date/datetime/time field type marked as not not supported is really not supported Warning: Success of this test does not necessary mean that a field is not supported. E.g. errors can occur due to special schema requirements of drivers. This test only covers the standard case. """ if driver == "BNA" and GDALVersion.runtime() < GDALVersion(2, 0): pytest.skip("BNA driver segfaults with gdal 1.11") monkeypatch.delitem( fiona.drvsupport._driver_field_type_unsupported[field_type], driver ) schema = get_schema(driver, field_type) path = str(tmpdir.join(get_temp_filename(driver))) values_in, values_out = zip(*generate_testdata(field_type, driver)) records = get_records(driver, values_in) is_good = True try: with fiona.open(path, "w", driver=driver, schema=schema) as c: c.writerecords(records) with fiona.open(path, "r") as c: if not get_schema_field(driver, c.schema) == field_type: is_good = False items = [get_field(driver, f) for f in c] for val_in, val_out in zip(items, values_out): if not val_in == val_out: is_good = False except: is_good = False assert not is_good def generate_tostr_testcases(): """Flatten driver_converts_to_str to a list of (field_type, driver) tuples""" cases = [] for field_type in _driver_converts_to_str: for driver in _driver_converts_to_str[field_type]: driver_supported = driver in supported_drivers driver_can_write = _driver_supports_mode(driver, "w") field_supported = _driver_supports_field(driver, field_type) converts_to_str = _driver_converts_field_type_silently_to_str( driver, field_type ) if ( driver_supported and driver_can_write and converts_to_str and field_supported ): cases.append((field_type, driver)) return cases @pytest.mark.filterwarnings("ignore:.*driver silently converts *:UserWarning") @pytest.mark.parametrize("driver,field_type", test_cases_datefield_to_str) @pytest.mark.gdal def test_driver_marked_as_silently_converts_to_str_converts_silently_to_str( tmpdir, driver, field_type, monkeypatch ): """Test if a driver and field_type is marked in fiona.drvsupport.driver_converts_to_str to convert to str really silently converts to str If this test fails, it should be considered to replace the respective None value in fiona.drvsupport.driver_converts_to_str with a GDALVersion(major, minor) value. """ monkeypatch.delitem(fiona.drvsupport._driver_converts_to_str[field_type], driver) schema = get_schema(driver, field_type) path = str(tmpdir.join(get_temp_filename(driver))) values_in, values_out = zip(*generate_testdata(field_type, driver)) records = get_records(driver, values_in) with fiona.open(path, "w", driver=driver, schema=schema) as c: c.writerecords(records) with fiona.open(path, "r") as c: assert get_schema_field(driver, c.schema) == "str" def test_read_timezone_geojson(path_test_tz_geojson): """Test if timezones are read correctly""" with fiona.open(path_test_tz_geojson) as c: items = list(c) assert items[0]["properties"]["test"] == "2015-04-22T00:00:00+07:00" def test_property_setter_lookup(tmp_path): """Demonstrate fix for #1376.""" class MyDate(datetime.date): pass feat = Feature.from_dict( { "properties": {"when": MyDate(2024, 4, 15)}, "geometry": {"type": "Point", "coordinates": [0, 0]}, } ) with fiona.open( tmp_path / "test.gpkg", "w", driver="GPKG", crs="EPSG:4326", schema={"geometry": "Point", "properties": {"when": "date"}}, ) as colxn: colxn.writerecords([feat]) with fiona.open(tmp_path / "test.gpkg") as colxn: assert colxn.schema["properties"]["when"] == "date" feat = next(colxn) assert feat.properties["when"] == "2024-04-15" Fiona-1.10.1/tests/test_driver_options.py000066400000000000000000000016001467206072700204570ustar00rootroot00000000000000import glob import os import tempfile import fiona from fiona.model import Feature from .conftest import get_temp_filename, requires_gdal2 def test_gml_format_option(tmp_path): """Test GML dataset creation option FORMAT (see gh-968)""" schema = {"geometry": "Point", "properties": {"position": "int"}} records = [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, float(i))}, "properties": {"position": i}, } ) for i in range(10) ] fpath = tmp_path.joinpath(get_temp_filename("GML")) with fiona.open(fpath, "w", driver="GML", schema=schema, FORMAT="GML3") as out: out.writerecords(records) xsd_path = list(tmp_path.glob("*.xsd"))[0] with open(xsd_path) as f: xsd = f.read() assert "http://schemas.opengis.net/gml/3.1.1" in xsd Fiona-1.10.1/tests/test_drivers.py000066400000000000000000000016241467206072700170750ustar00rootroot00000000000000"""Tests for Fiona's OGR driver interface.""" import logging import pytest import fiona from fiona.errors import FionaDeprecationWarning def test_options(tmpdir, path_coutwildrnp_shp): """Test that setting CPL_DEBUG=ON works and that a warning is raised.""" logfile = str(tmpdir.mkdir('tests').join('test_options.log')) logger = logging.getLogger() logger.setLevel(logging.DEBUG) fh = logging.FileHandler(logfile) fh.setLevel(logging.DEBUG) logger.addHandler(fh) # fiona.drivers() will be deprecated. with pytest.warns(FionaDeprecationWarning): with fiona.drivers(CPL_DEBUG=True): c = fiona.open(path_coutwildrnp_shp) c.close() with open(logfile) as f: log = f.read() if fiona.gdal_version.major >= 2: assert "GDALOpen" in log else: assert "OGROpen" in log Fiona-1.10.1/tests/test_drvsupport.py000066400000000000000000000261721467206072700176540ustar00rootroot00000000000000"""Tests of driver support""" import logging import pytest from .conftest import requires_gdal24, get_temp_filename from fiona.drvsupport import supported_drivers, driver_mode_mingdal import fiona.drvsupport from fiona.env import GDALVersion from fiona._env import calc_gdal_version_num, get_gdal_version_num from fiona.errors import DriverError log = logging.getLogger() @requires_gdal24 @pytest.mark.gdal @pytest.mark.parametrize("format", ["GeoJSON", "ESRIJSON", "TopoJSON", "GeoJSONSeq"]) def test_geojsonseq(format): """Format is available""" assert format in fiona.drvsupport.supported_drivers.keys() @pytest.mark.parametrize( "driver", [driver for driver, raw in supported_drivers.items() if "w" in raw] ) @pytest.mark.gdal def test_write_or_driver_error(tmpdir, driver, testdata_generator): """Test if write mode works.""" if driver == "BNA" and GDALVersion.runtime() < GDALVersion(2, 0): pytest.skip("BNA driver segfaults with gdal 1.11") schema, crs, records1, _, _ = testdata_generator(driver, range(0, 10), []) path = str(tmpdir.join(get_temp_filename(driver))) if driver in driver_mode_mingdal[ "w" ] and get_gdal_version_num() < calc_gdal_version_num( *driver_mode_mingdal["w"][driver] ): # Test if DriverError is raised for gdal < driver_mode_mingdal with pytest.raises(DriverError): with fiona.open(path, "w", driver=driver, crs=crs, schema=schema) as c: c.writerecords(records1) else: # Test if we can write with fiona.open(path, "w", driver=driver, crs=crs, schema=schema) as c: c.writerecords(records1) if driver in {"FileGDB", "OpenFileGDB"}: open_driver = driver else: open_driver = None with fiona.open(path, driver=open_driver) as collection: assert collection.driver == driver assert len(list(collection)) == len(records1) # If this test fails, it should be considered to update # driver_mode_mingdal in drvsupport.py. @pytest.mark.parametrize( "driver", [driver for driver in driver_mode_mingdal["w"].keys()] ) @pytest.mark.gdal def test_write_does_not_work_when_gdal_smaller_mingdal( tmpdir, driver, testdata_generator, monkeypatch ): """Test if driver really can't write for gdal < driver_mode_mingdal.""" if driver == "BNA" and GDALVersion.runtime() < GDALVersion(2, 0): pytest.skip("BNA driver segfaults with gdal 1.11") if driver == "FlatGeobuf" and calc_gdal_version_num( 3, 1, 0 ) <= get_gdal_version_num() < calc_gdal_version_num(3, 1, 3): pytest.skip("See https://github.com/Toblerity/Fiona/pull/924") schema, crs, records1, _, _ = testdata_generator(driver, range(0, 10), []) path = str(tmpdir.join(get_temp_filename(driver))) if driver in driver_mode_mingdal[ "w" ] and get_gdal_version_num() < calc_gdal_version_num( *driver_mode_mingdal["w"][driver] ): monkeypatch.delitem(fiona.drvsupport.driver_mode_mingdal["w"], driver) with pytest.raises(Exception): with fiona.open(path, "w", driver=driver, crs=crs, schema=schema) as c: c.writerecords(records1) # Some driver only allow a specific schema. These drivers can be # excluded by adding them to blacklist_append_drivers. @pytest.mark.parametrize( "driver", [driver for driver, raw in supported_drivers.items() if "a" in raw] ) @pytest.mark.gdal def test_append_or_driver_error(tmpdir, testdata_generator, driver): """Test if driver supports append mode.""" if driver == "DGN": pytest.xfail("DGN schema has changed") if driver == "BNA" and GDALVersion.runtime() < GDALVersion(2, 0): pytest.skip("BNA driver segfaults with gdal 1.11") path = str(tmpdir.join(get_temp_filename(driver))) schema, crs, records1, records2, _ = testdata_generator( driver, range(0, 5), range(5, 10) ) # If driver is not able to write, we cannot test append if driver in driver_mode_mingdal[ "w" ] and get_gdal_version_num() < calc_gdal_version_num( *driver_mode_mingdal["w"][driver] ): return # Create test file to append to with fiona.open(path, "w", driver=driver, crs=crs, schema=schema) as c: c.writerecords(records1) if driver in driver_mode_mingdal[ "a" ] and get_gdal_version_num() < calc_gdal_version_num( *driver_mode_mingdal["a"][driver] ): # Test if DriverError is raised for gdal < driver_mode_mingdal with pytest.raises(DriverError): with fiona.open(path, "a", driver=driver) as c: c.writerecords(records2) else: # Test if we can append with fiona.open(path, "a", driver=driver) as c: c.writerecords(records2) if driver in {"FileGDB", "OpenFileGDB"}: open_driver = driver else: open_driver = None with fiona.open(path, driver=open_driver) as collection: assert collection.driver == driver assert len(list(collection)) == len(records1) + len(records2) # If this test fails, it should be considered to update # driver_mode_mingdal in drvsupport.py. @pytest.mark.parametrize( "driver", [ driver for driver in driver_mode_mingdal["a"].keys() if driver in supported_drivers ], ) @pytest.mark.gdal def test_append_does_not_work_when_gdal_smaller_mingdal( tmpdir, driver, testdata_generator, monkeypatch ): """Test if driver supports append mode.""" if driver == "BNA" and GDALVersion.runtime() < GDALVersion(2, 0): pytest.skip("BNA driver segfaults with gdal 1.11") if driver == "FlatGeobuf" and GDALVersion.runtime() < GDALVersion(3, 5): pytest.skip("FlatGeobuf segfaults with GDAL < 3.5.1") path = str(tmpdir.join(get_temp_filename(driver))) schema, crs, records1, records2, _ = testdata_generator( driver, range(0, 5), range(5, 10) ) # If driver is not able to write, we cannot test append if driver in driver_mode_mingdal[ "w" ] and get_gdal_version_num() < calc_gdal_version_num( *driver_mode_mingdal["w"][driver] ): return # Create test file to append to with fiona.open(path, "w", driver=driver, crs=crs, schema=schema) as c: c.writerecords(records1) if driver in driver_mode_mingdal[ "a" ] and get_gdal_version_num() < calc_gdal_version_num( *driver_mode_mingdal["a"][driver] ): # Test if driver really can't append for gdal < driver_mode_mingdal monkeypatch.delitem(fiona.drvsupport.driver_mode_mingdal["a"], driver) with pytest.raises(Exception): with fiona.open(path, "a", driver=driver) as c: c.writerecords(records2) if driver in {"FileGDB", "OpenFileGDB"}: open_driver = driver else: open_driver = None with fiona.open(path, driver=open_driver) as collection: assert collection.driver == driver assert len(list(collection)) == len(records1) + len(records2) # If this test fails, it should be considered to enable write support # for the respective driver in drvsupport.py. @pytest.mark.parametrize( "driver", [driver for driver, raw in supported_drivers.items() if raw == "r"] ) @pytest.mark.gdal def test_no_write_driver_cannot_write(tmpdir, driver, testdata_generator, monkeypatch): """Test if read only driver cannot write.""" monkeypatch.setitem(fiona.drvsupport.supported_drivers, driver, "rw") schema, crs, records1, _, _ = testdata_generator(driver, range(0, 5), []) if driver == "BNA" and GDALVersion.runtime() < GDALVersion(2, 0): pytest.skip("BNA driver segfaults with gdal 1.11") if driver == "FlatGeobuf": pytest.xfail("FlatGeobuf doesn't raise an error but doesn't have write support") path = str(tmpdir.join(get_temp_filename(driver))) with pytest.raises(Exception): with fiona.open(path, "w", driver=driver, crs=crs, schema=schema) as c: c.writerecords(records1) # If this test fails, it should be considered to enable append support # for the respective driver in drvsupport.py. @pytest.mark.parametrize( "driver", [ driver for driver, raw in supported_drivers.items() if "w" in raw and "a" not in raw ], ) @pytest.mark.gdal def test_no_append_driver_cannot_append( tmpdir, driver, testdata_generator, monkeypatch ): """Test if a driver that supports write and not append cannot also append.""" monkeypatch.setitem(fiona.drvsupport.supported_drivers, driver, "raw") if driver == "FlatGeobuf" and get_gdal_version_num() == calc_gdal_version_num(3, 5, 0): pytest.skip("FlatGeobuf driver segfaults with gdal 3.5.0") path = str(tmpdir.join(get_temp_filename(driver))) schema, crs, records1, records2, _ = testdata_generator( driver, range(0, 5), range(5, 10) ) # If driver is not able to write, we cannot test append if driver in driver_mode_mingdal[ "w" ] and get_gdal_version_num() < calc_gdal_version_num( *driver_mode_mingdal["w"][driver] ): return # Create test file to append to with fiona.open(path, "w", driver=driver, crs=crs, schema=schema) as c: c.writerecords(records1) try: with fiona.open(path, "a", driver=driver) as c: c.writerecords(records2) except Exception: log.exception("Caught exception in trying to append.") return if driver in {"FileGDB", "OpenFileGDB"}: open_driver = driver else: open_driver = None with fiona.open(path, driver=open_driver) as collection: assert collection.driver == driver assert len(list(collection)) == len(records1) def test_mingdal_drivers_are_supported(): """Test if mode and driver is enabled in supported_drivers""" for mode in driver_mode_mingdal: for driver in driver_mode_mingdal[mode]: # we cannot test drivers that are not present in the gdal installation if driver in supported_drivers: assert mode in supported_drivers[driver] def test_allow_unsupported_drivers(monkeypatch, tmpdir): """Test if allow unsupported drivers works as expected""" # We delete a known working driver from fiona.drvsupport so that we can use it monkeypatch.delitem(fiona.drvsupport.supported_drivers, "GPKG") schema = {"geometry": "Polygon", "properties": {}} # Test that indeed we can't create a file without allow_unsupported_drivers path1 = str(tmpdir.join("test1.gpkg")) with pytest.raises(DriverError): with fiona.open(path1, mode="w", driver="GPKG", schema=schema): pass # Test that we can create file with allow_unsupported_drivers=True path2 = str(tmpdir.join("test2.gpkg")) try: with fiona.open( path2, mode="w", driver="GPKG", schema=schema, allow_unsupported_drivers=True, ): pass except Exception as e: assert ( False ), f"Using allow_unsupported_drivers=True should not raise an exception: {e}" Fiona-1.10.1/tests/test_encoding.py000066400000000000000000000030421467206072700172010ustar00rootroot00000000000000"""Encoding tests""" from glob import glob import os import shutil import pytest import fiona from .conftest import requires_gdal2 @pytest.fixture(scope='function') def gre_shp_cp1252(tmpdir): """A tempdir containing copies of gre.* files, .cpg set to cp1252 The shapefile attributes are in fact utf-8 encoded. """ test_files = glob(os.path.join(os.path.dirname(__file__), 'data/gre.*')) tmpdir = tmpdir.mkdir('data') for filename in test_files: shutil.copy(filename, str(tmpdir)) tmpdir.join('gre.cpg').write('CP1252') yield tmpdir.join('gre.shp') @requires_gdal2 def test_broken_encoding(gre_shp_cp1252): """Reading as cp1252 mis-encodes a Russian name""" with fiona.open(str(gre_shp_cp1252)) as src: assert src.session._get_internal_encoding() == 'utf-8' feat = next(iter(src)) assert feat['properties']['name_ru'] != 'Гренада' @requires_gdal2 def test_cpg_encoding(gre_shp_cp1252): """Reads a Russian name""" gre_shp_cp1252.join('../gre.cpg').write('UTF-8') with fiona.open(str(gre_shp_cp1252)) as src: assert src.session._get_internal_encoding() == 'utf-8' feat = next(iter(src)) assert feat['properties']['name_ru'] == 'Гренада' @requires_gdal2 def test_override_encoding(gre_shp_cp1252): """utf-8 override succeeds""" with fiona.open(str(gre_shp_cp1252), encoding='utf-8') as src: assert src.session._get_internal_encoding() == 'utf-8' assert next(iter(src))['properties']['name_ru'] == 'Гренада' Fiona-1.10.1/tests/test_env.py000066400000000000000000000113141467206072700162040ustar00rootroot00000000000000"""Tests of fiona.env""" import os import sys from unittest import mock import boto3 import pytest import fiona from fiona import _env from fiona.env import getenv, hasenv, ensure_env, ensure_env_with_credentials from fiona.errors import FionaDeprecationWarning from fiona.session import AWSSession, GSSession def test_nested_credentials(monkeypatch): """Check that rasterio.open() doesn't wipe out surrounding credentials""" @ensure_env_with_credentials def fake_opener(path): return fiona.env.getenv() with fiona.env.Env( session=AWSSession(aws_access_key_id="foo", aws_secret_access_key="bar") ): assert fiona.env.getenv()["AWS_ACCESS_KEY_ID"] == "foo" assert fiona.env.getenv()["AWS_SECRET_ACCESS_KEY"] == "bar" monkeypatch.setenv("AWS_ACCESS_KEY_ID", "lol") monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "wut") gdalenv = fake_opener("s3://foo/bar") assert gdalenv["AWS_ACCESS_KEY_ID"] == "foo" assert gdalenv["AWS_SECRET_ACCESS_KEY"] == "bar" def test_ensure_env_decorator(gdalenv): @ensure_env def f(): return getenv()["FIONA_ENV"] assert f() is True def test_ensure_env_decorator_sets_gdal_data(gdalenv, monkeypatch): """fiona.env.ensure_env finds GDAL from environment""" @ensure_env def f(): return getenv()["GDAL_DATA"] monkeypatch.setenv("GDAL_DATA", "/lol/wut") assert f() == "/lol/wut" @mock.patch("fiona._env.GDALDataFinder.find_file") def test_ensure_env_decorator_sets_gdal_data_prefix( find_file, gdalenv, monkeypatch, tmpdir ): """fiona.env.ensure_env finds GDAL data under a prefix""" @ensure_env def f(): return getenv()["GDAL_DATA"] find_file.return_value = None tmpdir.ensure("share/gdal/header.dxf") monkeypatch.delenv("GDAL_DATA", raising=False) monkeypatch.setattr(_env, "__file__", str(tmpdir.join("fake.py"))) monkeypatch.setattr(sys, "prefix", str(tmpdir)) assert f() == str(tmpdir.join("share").join("gdal")) @mock.patch("fiona._env.GDALDataFinder.find_file") def test_ensure_env_decorator_sets_gdal_data_wheel( find_file, gdalenv, monkeypatch, tmpdir ): """fiona.env.ensure_env finds GDAL data in a wheel""" @ensure_env def f(): return getenv()["GDAL_DATA"] find_file.return_value = None tmpdir.ensure("gdal_data/header.dxf") monkeypatch.delenv("GDAL_DATA", raising=False) monkeypatch.setattr( _env, "__file__", str(tmpdir.join(os.path.basename(_env.__file__))) ) assert f() == str(tmpdir.join("gdal_data")) @mock.patch("fiona._env.GDALDataFinder.find_file") def test_ensure_env_with_decorator_sets_gdal_data_wheel( find_file, gdalenv, monkeypatch, tmpdir ): """fiona.env.ensure_env finds GDAL data in a wheel""" @ensure_env_with_credentials def f(*args): return getenv()["GDAL_DATA"] find_file.return_value = None tmpdir.ensure("gdal_data/header.dxf") monkeypatch.delenv("GDAL_DATA", raising=False) monkeypatch.setattr( _env, "__file__", str(tmpdir.join(os.path.basename(_env.__file__))) ) assert f("foo") == str(tmpdir.join("gdal_data")) def test_ensure_env_crs(path_coutwildrnp_shp): """Decoration of .crs works""" assert fiona.open(path_coutwildrnp_shp).crs def test_env_default_env(path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp): assert hasenv() def test_nested_gs_credentials(monkeypatch): """Check that rasterio.open() doesn't wipe out surrounding credentials""" @ensure_env_with_credentials def fake_opener(path): return fiona.env.getenv() with fiona.env.Env(session=GSSession(google_application_credentials="foo")): assert fiona.env.getenv()["GOOGLE_APPLICATION_CREDENTIALS"] == "foo" gdalenv = fake_opener("gs://foo/bar") assert gdalenv["GOOGLE_APPLICATION_CREDENTIALS"] == "foo" def test_aws_session(gdalenv): """Create an Env with a boto3 session.""" aws_session = boto3.Session( aws_access_key_id="id", aws_secret_access_key="key", aws_session_token="token", region_name="null-island-1", ) with pytest.warns(FionaDeprecationWarning): with fiona.env.Env(session=aws_session) as s: assert ( s.session._session.get_credentials().get_frozen_credentials().access_key == "id" ) assert ( s.session._session.get_credentials().get_frozen_credentials().secret_key == "key" ) assert ( s.session._session.get_credentials().get_frozen_credentials().token == "token" ) assert s.session._session.region_name == "null-island-1" Fiona-1.10.1/tests/test_feature.py000066400000000000000000000122561467206072700170550ustar00rootroot00000000000000"""Tests for feature objects.""" import logging import os import shutil import sys import tempfile import unittest import pytest import fiona from fiona import collection from fiona.collection import Collection from fiona.model import Feature from fiona.ogrext import featureRT class TestPointRoundTrip(unittest.TestCase): def setUp(self): self.tempdir = tempfile.mkdtemp() schema = {"geometry": "Point", "properties": {"title": "str"}} self.c = Collection( os.path.join(self.tempdir, "foo.shp"), "w", driver="ESRI Shapefile", schema=schema, ) def tearDdown(self): self.c.close() shutil.rmtree(self.tempdir) def test_geometry(self): f = { "id": "1", "geometry": {"type": "Point", "coordinates": (0.0, 0.0)}, "properties": {"title": "foo"}, } g = featureRT(f, self.c) assert g.geometry.type == "Point" assert g.geometry.coordinates == (0.0, 0.0) def test_properties(self): f = Feature.from_dict( **{ "id": "1", "geometry": {"type": "Point", "coordinates": (0.0, 0.0)}, "properties": {"title": "foo"}, } ) g = featureRT(f, self.c) assert g.properties["title"] == "foo" def test_none_property(self): f = Feature.from_dict( **{ "id": "1", "geometry": {"type": "Point", "coordinates": (0.0, 0.0)}, "properties": {"title": None}, } ) g = featureRT(f, self.c) assert g.properties["title"] is None class TestLineStringRoundTrip(unittest.TestCase): def setUp(self): self.tempdir = tempfile.mkdtemp() schema = {"geometry": "LineString", "properties": {"title": "str"}} self.c = Collection( os.path.join(self.tempdir, "foo.shp"), "w", "ESRI Shapefile", schema=schema ) def tearDown(self): self.c.close() shutil.rmtree(self.tempdir) def test_geometry(self): f = Feature.from_dict( **{ "id": "1", "geometry": { "type": "LineString", "coordinates": [(0.0, 0.0), (1.0, 1.0)], }, "properties": {"title": "foo"}, } ) g = featureRT(f, self.c) assert g.geometry.type == "LineString" assert g.geometry.coordinates == [(0.0, 0.0), (1.0, 1.0)] def test_properties(self): f = Feature.from_dict( **{ "id": "1", "geometry": {"type": "Point", "coordinates": (0.0, 0.0)}, "properties": {"title": "foo"}, } ) g = featureRT(f, self.c) assert g.properties["title"] == "foo" class TestPolygonRoundTrip(unittest.TestCase): def setUp(self): self.tempdir = tempfile.mkdtemp() schema = {"geometry": "Polygon", "properties": {"title": "str"}} self.c = Collection( os.path.join(self.tempdir, "foo.shp"), "w", "ESRI Shapefile", schema=schema ) def tearDown(self): self.c.close() shutil.rmtree(self.tempdir) def test_geometry(self): f = Feature.from_dict( **{ "id": "1", "geometry": { "type": "Polygon", "coordinates": [ [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)] ], }, "properties": {"title": "foo"}, } ) g = featureRT(f, self.c) assert g.geometry.type == "Polygon" assert g.geometry.coordinates == [ [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)] ] def test_properties(self): f = Feature.from_dict( **{ "id": "1", "geometry": { "type": "Polygon", "coordinates": [ [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)] ], }, "properties": {"title": "foo"}, } ) g = featureRT(f, self.c) assert g.properties["title"] == "foo" @pytest.mark.parametrize( "driver, extension", [("ESRI Shapefile", "shp"), ("GeoJSON", "geojson")] ) def test_feature_null_field(tmpdir, driver, extension): """ In GDAL 2.2 the behaviour of OGR_F_IsFieldSet slightly changed. Some drivers (e.g. GeoJSON) also require fields to be explicitly set to null. See GH #460. """ meta = { "driver": driver, "schema": {"geometry": "Point", "properties": {"RETURN_P": "str"}}, } filename = os.path.join(str(tmpdir), "test_null." + extension) with fiona.open(filename, "w", **meta) as dst: g = {"coordinates": [1.0, 2.0], "type": "Point"} feature = Feature.from_dict(**{"geometry": g, "properties": {"RETURN_P": None}}) dst.write(feature) with fiona.open(filename, "r") as src: feature = next(iter(src)) assert feature.properties["RETURN_P"] is None Fiona-1.10.1/tests/test_features.py000066400000000000000000000234771467206072700172470ustar00rootroot00000000000000# Python module tests import json import pytest # type: ignore import shapely # type: ignore from shapely.geometry import LineString, MultiPoint, Point, mapping, shape # type: ignore from fiona.errors import ReduceError from fiona.features import ( # type: ignore map_feature, reduce_features, vertex_count, area, buffer, collect, distance, dump, identity, length, unary_projectable_property_wrapper, unary_projectable_constructive_wrapper, binary_projectable_property_wrapper, ) def test_modulate_simple(): """Set a feature's geometry.""" # map_feature() is a generator. list() materializes the values. feat = list(map_feature("Point 0 0", {"type": "Feature"})) assert len(feat) == 1 feat = feat[0] assert "Point" == feat["type"] assert (0.0, 0.0) == feat["coordinates"] def test_modulate_complex(): """Exercise a fairly complicated pipeline.""" bufkwd = "resolution" if shapely.__version__.startswith("1") else "quad_segs" with open("tests/data/trio.geojson") as src: collection = json.loads(src.read()) feat = collection["features"][0] results = list( map_feature( f"simplify (buffer g (* 0.1 2) :projected false :{bufkwd} (- 4 3)) 0.001 :projected false :preserve_topology false", feat, ) ) assert 1 == len(results) geom = results[0] assert geom["type"] == "Polygon" assert len(geom["coordinates"][0]) == 5 @pytest.mark.parametrize( "obj, count", [ (Point(0, 0), 1), (MultiPoint([(0, 0), (1, 1)]), 2), (Point(0, 0).buffer(10.0).difference(Point(0, 0).buffer(1.0)), 130), ], ) def test_vertex_count(obj, count): """Check vertex counting correctness.""" assert count == vertex_count(obj) @pytest.mark.parametrize( "obj, count", [ (Point(0, 0), 1), (MultiPoint([(0, 0), (1, 1)]), 2), (Point(0, 0).buffer(10.0).difference(Point(0, 0).buffer(1.0)), 130), ], ) def test_calculate_vertex_count(obj, count): """Confirm vertex counting is in func_map.""" feat = {"type": "Feature", "properties": {}, "geometry": mapping(obj)} assert count == list(map_feature("vertex_count g", feat))[0] def test_calculate_builtin(): """Confirm builtin function evaluation.""" assert 42 == list(map_feature("int '42'", None))[0] def test_calculate_feature_attr(): """Confirm feature attr evaluation.""" assert "LOLWUT" == list(map_feature("upper f", "lolwut"))[0] def test_calculate_point(): """Confirm feature attr evaluation.""" result = list(map_feature("Point 0 0", None))[0] assert "Point" == result["type"] def test_calculate_points(): """Confirm feature attr evaluation.""" result = list(map_feature("list (Point 0 0) (buffer (Point 1 1) 1)", None)) assert 2 == len(result) assert "Point" == result[0]["type"] assert "Polygon" == result[1]["type"] def test_reduce_len(): """Reduce can count the number of input features.""" with open("tests/data/trio.seq") as seq: data = [json.loads(line) for line in seq.readlines()] # reduce() is a generator. list() materializes the values. assert 3 == list(reduce_features("len c", data))[0] def test_reduce_union(): """Reduce yields one feature by default.""" with open("tests/data/trio.seq") as seq: data = [json.loads(line) for line in seq.readlines()] # reduce() is a generator. list() materializes the values. result = list(reduce_features("unary_union c", data)) assert len(result) == 1 val = result[0] assert "GeometryCollection" == val["type"] assert 2 == len(val["geometries"]) def test_reduce_union_area(): """Reduce can yield total area using raw output.""" with open("tests/data/trio.seq") as seq: data = [json.loads(line) for line in seq.readlines()] # reduce() is a generator. result = list(reduce_features("area (unary_union c)", data)) assert len(result) == 1 val = result[0] assert isinstance(val, float) assert 3e4 < val < 4e4 def test_reduce_union_geom_type(): """Reduce and print geom_type using raw output.""" with open("tests/data/trio.seq") as seq: data = [json.loads(line) for line in seq.readlines()] # reduce() is a generator. result = list(reduce_features("geom_type (unary_union c)", data)) assert len(result) == 1 assert "GeometryCollection" == result[0] def test_reduce_error(): """Raise ReduceError when expression doesn't reduce.""" with open("tests/data/trio.seq") as seq: data = [json.loads(line) for line in seq.readlines()] with pytest.raises(ReduceError): list(reduce_features("(identity c)", data)) @pytest.mark.parametrize( "obj, count", [ (MultiPoint([(0, 0), (1, 1)]), 2), ], ) def test_dump_eval(obj, count): feature = {"type": "Feature", "properties": {}, "geometry": mapping(obj)} result = map_feature("identity g", feature, dump_parts=True) assert len(list(result)) == count def test_collect(): """Collect two points.""" geom = collect((Point(0, 0), Point(1, 1))) assert geom.geom_type == "GeometryCollection" def test_dump(): """Dump a point.""" geoms = list(dump(Point(0, 0))) assert len(geoms) == 1 assert geoms[0].geom_type == "Point" def test_dump_multi(): """Dump two points.""" geoms = list(dump(MultiPoint([(0, 0), (1, 1)]))) assert len(geoms) == 2 assert all(g.geom_type == "Point" for g in geoms) def test_identity(): """Check identity.""" geom = Point(1.1, 2.2) assert geom == identity(geom) def test_area(): """Check projected area of RMNP against QGIS.""" with open("tests/data/rmnp.geojson", "rb") as f: collection = json.load(f) geom = shape(collection["features"][0]["geometry"]) # QGIS uses a geodesic area computation and WGS84 ellipsoid. qgis_ellipsoidal_area = 1117.433937055 # kilometer squared # We expect no more than a 0.0001 km^2 difference. That's .00001%. assert round(qgis_ellipsoidal_area, 4) == round(area(geom) / 1e6, 4) @pytest.mark.parametrize( ["kwargs", "exp_distance"], [({}, 9648.6280), ({"projected": True}, 9648.6280), ({"projected": False}, 0.1)], ) def test_distance(kwargs, exp_distance): """Distance measured properly.""" assert round(exp_distance, 4) == round( distance(Point(0, 0), Point(0.1, 0), **kwargs), 4 ) @pytest.mark.parametrize( ["kwargs", "distance", "exp_area"], [ ({}, 1.0e4, 312e6), ({"projected": True}, 10000.0, 312e6), ({"projected": False}, 0.1, 0.0312), ], ) def test_buffer(kwargs, distance, exp_area): """Check area of a point buffered by 10km using 8 quadrant segments, should be ~312 km2.""" # float(f"{x:.3g}") is used to round x to 3 significant figures. assert exp_area == float( f"{area(buffer(Point(0, 0), distance, **kwargs), **kwargs):.3g}" ) @pytest.mark.parametrize( ["kwargs", "exp_length"], [({}, 9648.6280), ({"projected": True}, 9648.6280), ({"projected": False}, 0.1)], ) def test_length(kwargs, exp_length): """Length measured properly.""" assert round(exp_length, 4) == round( length(LineString([(0, 0), (0.1, 0)]), **kwargs), 4 ) @pytest.mark.parametrize( ["in_xy", "exp_xy", "kwargs"], [ ((0.1, 0.0), (9648.628, 0.0), {}), ((0.1, 0.0), (9648.628, 0.0), {"projected": True}), ((0.1, 0.0), (0.1, 0.0), {"projected": False}), ], ) def test_unary_property_wrapper(in_xy, exp_xy, kwargs): """Correctly wraps a function like shapely.area.""" def func(geom, *args, **kwargs): """Echoes its input.""" return geom, args, kwargs wrapper = unary_projectable_property_wrapper(func) assert wrapper.__doc__ == "Echoes its input." assert wrapper.__name__ == "func" g, *rest = wrapper(Point(*in_xy), "hello", this=True, **kwargs) assert rest == [("hello",), {"this": True}] assert round(g.x, 4) == round(exp_xy[0], 4) assert round(g.y, 4) == round(exp_xy[1], 4) @pytest.mark.parametrize( ["in_xy", "exp_xy", "kwargs"], [ ((0.1, 0.0), (9648.628, 0.0), {}), ((0.1, 0.0), (9648.628, 0.0), {"projected": True}), ((0.1, 0.0), (0.1, 0.0), {"projected": False}), ], ) def test_unary_projectable_constructive_wrapper(in_xy, exp_xy, kwargs): """Correctly wraps a function like shapely.buffer.""" def func(geom, required, this=False): """Echoes its input geom.""" assert round(geom.x, 4) == round(exp_xy[0], 4) assert round(geom.y, 4) == round(exp_xy[1], 4) assert this is True return geom wrapper = unary_projectable_constructive_wrapper(func) assert wrapper.__doc__ == "Echoes its input geom." assert wrapper.__name__ == "func" g = wrapper(Point(*in_xy), "hello", this=True, **kwargs) assert round(g.x, 4) == round(in_xy[0], 4) assert round(g.y, 4) == round(in_xy[1], 4) @pytest.mark.parametrize( ["in_xy", "exp_xy", "kwargs"], [ ((0.1, 0.0), (9648.628, 0.0), {}), ((0.1, 0.0), (9648.628, 0.0), {"projected": True}), ((0.1, 0.0), (0.1, 0.0), {"projected": False}), ], ) def test_binary_projectable_property_wrapper(in_xy, exp_xy, kwargs): """Correctly wraps a function like shapely.distance.""" def func(geom1, geom2, *args, **kwargs): """Echoes its inputs.""" return geom1, geom2, args, kwargs wrapper = binary_projectable_property_wrapper(func) assert wrapper.__doc__ == "Echoes its inputs." assert wrapper.__name__ == "func" g1, g2, *rest = wrapper(Point(*in_xy), Point(*in_xy), "hello", this=True, **kwargs) assert rest == [("hello",), {"this": True}] assert round(g1.x, 4) == round(exp_xy[0], 4) assert round(g1.y, 4) == round(exp_xy[1], 4) assert round(g2.x, 4) == round(exp_xy[0], 4) assert round(g2.y, 4) == round(exp_xy[1], 4) Fiona-1.10.1/tests/test_fio_bounds.py000066400000000000000000000057201467206072700175470ustar00rootroot00000000000000"""Tests for `$ fio bounds`.""" import re from fiona.fio import bounds from fiona.fio.main import main_group def test_fail(runner): result = runner.invoke(main_group, ['bounds', ], '5') assert result.exit_code == 1 def test_seq(feature_seq, runner): result = runner.invoke(main_group, ['bounds', ], feature_seq) assert result.exit_code == 0 assert result.output.count('[') == result.output.count(']') == 2 assert len(re.findall(r'\d*\.\d*', result.output)) == 8 def test_seq_rs(feature_seq_pp_rs, runner): result = runner.invoke(main_group, ['bounds', ], feature_seq_pp_rs) assert result.exit_code == 0 assert result.output.count('[') == result.output.count(']') == 2 assert len(re.findall(r'\d*\.\d*', result.output)) == 8 def test_precision(feature_seq, runner): result = runner.invoke(main_group, ['bounds', '--precision', 1], feature_seq) assert result.exit_code == 0 assert result.output.count('[') == result.output.count(']') == 2 assert len(re.findall(r'\d*\.\d{1}\D', result.output)) == 8 def test_explode(feature_collection, runner): result = runner.invoke(main_group, ['bounds', '--explode'], feature_collection) assert result.exit_code == 0 assert result.output.count('[') == result.output.count(']') == 2 assert len(re.findall(r'\d*\.\d*', result.output)) == 8 def test_explode_pp(feature_collection_pp, runner): result = runner.invoke(main_group, ['bounds', '--explode'], feature_collection_pp) assert result.exit_code == 0 assert result.output.count('[') == result.output.count(']') == 2 assert len(re.findall(r'\d*\.\d*', result.output)) == 8 def test_with_id(feature_seq, runner): result = runner.invoke(main_group, ['bounds', '--with-id'], feature_seq) assert result.exit_code == 0 assert result.output.count('id') == result.output.count('bbox') == 2 def test_explode_with_id(feature_collection, runner): result = runner.invoke( main_group, ['bounds', '--explode', '--with-id'], feature_collection) assert result.exit_code == 0 assert result.output.count('id') == result.output.count('bbox') == 2 def test_with_obj(feature_seq, runner): result = runner.invoke(main_group, ['bounds', '--with-obj'], feature_seq) assert result.exit_code == 0 assert result.output.count('geometry') == result.output.count('bbox') == 2 def test_bounds_explode_with_obj(feature_collection, runner): result = runner.invoke( main_group, ['bounds', '--explode', '--with-obj'], feature_collection) assert result.exit_code == 0 assert result.output.count('geometry') == result.output.count('bbox') == 2 def test_explode_output_rs(feature_collection, runner): result = runner.invoke(main_group, ['bounds', '--explode', '--rs'], feature_collection) assert result.exit_code == 0 assert result.output.count('\x1e') == 2 assert result.output.count('[') == result.output.count(']') == 2 assert len(re.findall(r'\d*\.\d*', result.output)) == 8 Fiona-1.10.1/tests/test_fio_calc.py000066400000000000000000000036331467206072700171600ustar00rootroot00000000000000"""Tests for `$ fio calc`.""" import json from click.testing import CliRunner from fiona.fio.main import main_group def test_fail(): runner = CliRunner() result = runner.invoke(main_group, ['calc', "TEST", "f.properties.test > 5"], '{"type": "no_properties"}') assert result.exit_code == 1 def _load(output): features = [] for x in output.splitlines(): try: features.append(json.loads(x)) except: # Click combines stdout and stderr and shapely dumps logs to # stderr that are not JSON # https://github.com/pallets/click/issues/371 pass return features def test_calc_seq(feature_seq, runner): result = runner.invoke(main_group, ['calc', "TEST", "f.properties.AREA / f.properties.PERIMETER"], feature_seq) assert result.exit_code == 0 feats = _load(result.output) assert len(feats) == 2 for feat in feats: assert feat['properties']['TEST'] == \ feat['properties']['AREA'] / feat['properties']['PERIMETER'] def test_bool_seq(feature_seq, runner): result = runner.invoke(main_group, ['calc', "TEST", "f.properties.AREA > 0.015"], feature_seq) assert result.exit_code == 0 feats = _load(result.output) assert len(feats) == 2 assert feats[0]['properties']['TEST'] assert not feats[1]['properties']['TEST'] def test_existing_property(feature_seq, runner): result = runner.invoke( main_group, ["calc", "AREA", "f.properties.AREA * 2"], feature_seq ) assert result.exit_code == 2 result = runner.invoke(main_group, ['calc', "--overwrite", "AREA", "f.properties.AREA * 2"], feature_seq) assert result.exit_code == 0 feats = _load(result.output) assert len(feats) == 2 for feat in feats: assert 'AREA' in feat['properties'] Fiona-1.10.1/tests/test_fio_cat.py000066400000000000000000000102211467206072700170140ustar00rootroot00000000000000"""Tests for `$ fio cat`.""" from click.testing import CliRunner from fiona.fio.main import main_group def test_one(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke(main_group, ['cat', path_coutwildrnp_shp]) assert result.exit_code == 0 assert result.output.count('"Feature"') == 67 def test_two(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke(main_group, ['cat', path_coutwildrnp_shp, path_coutwildrnp_shp]) assert result.exit_code == 0 assert result.output.count('"Feature"') == 134 def test_bbox_no(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke( main_group, ['cat', path_coutwildrnp_shp, '--bbox', '0,10,80,20'], catch_exceptions=False) assert result.exit_code == 0 assert result.output == "" def test_bbox_yes(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke( main_group, ['cat', path_coutwildrnp_shp, '--bbox', '-109,37,-107,39'], catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('"Feature"') == 19 def test_bbox_yes_two_files(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke( main_group, ['cat', path_coutwildrnp_shp, path_coutwildrnp_shp, '--bbox', '-109,37,-107,39'], catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('"Feature"') == 38 def test_bbox_json_yes(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke( main_group, ['cat', path_coutwildrnp_shp, '--bbox', '[-109,37,-107,39]'], catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('"Feature"') == 19 def test_bbox_where(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke( main_group, ['cat', path_coutwildrnp_shp, '--bbox', '-120,40,-100,50', '--where', "NAME LIKE 'Mount%'"], catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('"Feature"') == 4 def test_where_no(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke( main_group, ['cat', path_coutwildrnp_shp, '--where', "STATE LIKE '%foo%'"], catch_exceptions=False) assert result.exit_code == 0 assert result.output == "" def test_where_yes(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke( main_group, ['cat', path_coutwildrnp_shp, '--where', "NAME LIKE 'Mount%'"], catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('"Feature"') == 9 def test_where_yes_two_files(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke( main_group, ['cat', path_coutwildrnp_shp, path_coutwildrnp_shp, '--where', "NAME LIKE 'Mount%'"], catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('"Feature"') == 18 def test_where_fail(data_dir): runner = CliRunner() result = runner.invoke(main_group, ['cat', '--where', "NAME=3", data_dir]) assert result.exit_code != 0 def test_multi_layer(data_dir): layerdef = "1:coutwildrnp,1:coutwildrnp" runner = CliRunner() result = runner.invoke( main_group, ['cat', '--layer', layerdef, data_dir]) assert result.output.count('"Feature"') == 134 def test_multi_layer_fail(data_dir): runner = CliRunner() result = runner.invoke(main_group, ['cat', '--layer', '200000:coutlildrnp', data_dir]) assert result.exit_code != 0 def test_vfs(path_coutwildrnp_zip): runner = CliRunner() result = runner.invoke(main_group, [ 'cat', f'zip://{path_coutwildrnp_zip}']) assert result.exit_code == 0 assert result.output.count('"Feature"') == 67 def test_dst_crs_epsg3857(path_coutwildrnp_shp): """Confirm fix of issue #952""" runner = CliRunner() result = runner.invoke( main_group, ["cat", "--dst-crs", "EPSG:3857", path_coutwildrnp_shp] ) assert result.exit_code == 0 assert result.output.count('"Feature"') == 67 Fiona-1.10.1/tests/test_fio_collect.py000066400000000000000000000051371467206072700177040ustar00rootroot00000000000000"""Tests for `$ fio collect`.""" import json import sys from click.testing import CliRunner import pytest # from fiona.fio import collect from fiona.fio.main import main_group def test_collect_rs(feature_seq_pp_rs): runner = CliRunner() result = runner.invoke( main_group, ['collect', '--src-crs', 'EPSG:3857'], feature_seq_pp_rs, catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('"Feature"') == 2 def test_collect_no_rs(feature_seq): runner = CliRunner() result = runner.invoke( main_group, ['collect', '--src-crs', 'EPSG:3857'], feature_seq, catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('"Feature"') == 2 def test_collect_ld(feature_seq): runner = CliRunner() result = runner.invoke( main_group, ['collect', '--with-ld-context', '--add-ld-context-item', 'foo=bar'], feature_seq, catch_exceptions=False) assert result.exit_code == 0 assert '"@context": {' in result.output assert '"foo": "bar"' in result.output def test_collect_rec_buffered(feature_seq): runner = CliRunner() result = runner.invoke(main_group, ['collect', '--record-buffered'], feature_seq) assert result.exit_code == 0 assert '"FeatureCollection"' in result.output def test_collect_noparse(feature_seq): runner = CliRunner() result = runner.invoke( main_group, ['collect', '--no-parse'], feature_seq, catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('"Feature"') == 2 assert len(json.loads(result.output)['features']) == 2 def test_collect_noparse_records(feature_seq): runner = CliRunner() result = runner.invoke( main_group, ['collect', '--no-parse', '--record-buffered'], feature_seq, catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('"Feature"') == 2 assert len(json.loads(result.output)['features']) == 2 def test_collect_src_crs(feature_seq): runner = CliRunner() result = runner.invoke( main_group, ['collect', '--no-parse', '--src-crs', 'epsg:4326'], feature_seq, catch_exceptions=False) assert result.exit_code == 2 def test_collect_noparse_rs(feature_seq_pp_rs): runner = CliRunner() result = runner.invoke( main_group, ['collect', '--no-parse'], feature_seq_pp_rs, catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('"Feature"') == 2 assert len(json.loads(result.output)['features']) == 2 Fiona-1.10.1/tests/test_fio_distrib.py000066400000000000000000000010641467206072700177120ustar00rootroot00000000000000"""Tests for `$ fio distrib`.""" from click.testing import CliRunner from fiona.fio.main import main_group def test_distrib(feature_collection_pp): runner = CliRunner() result = runner.invoke(main_group, ['distrib', ], feature_collection_pp) assert result.exit_code == 0 assert result.output.count('"Feature"') == 2 def test_distrib_no_rs(feature_collection): runner = CliRunner() result = runner.invoke(main_group, ['distrib', ], feature_collection) assert result.exit_code == 0 assert result.output.count('"Feature"') == 2 Fiona-1.10.1/tests/test_fio_dump.py000066400000000000000000000017741467206072700172270ustar00rootroot00000000000000"""Unittests for $ fio dump""" import json from click.testing import CliRunner import pytest import fiona from fiona.fio.main import main_group def test_dump(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke(main_group, ['dump', path_coutwildrnp_shp]) assert result.exit_code == 0 assert '"FeatureCollection"' in result.output @pytest.mark.parametrize("layer", ["routes", "1", "tracks", "track_points"]) def test_dump_layer(path_gpx, layer): runner = CliRunner() result = runner.invoke(main_group, ["dump", path_gpx, "--layer", layer]) assert result.exit_code == 0 assert '"FeatureCollection"' in result.output def test_dump_layer_vfs(path_coutwildrnp_zip): path = f"zip://{path_coutwildrnp_zip}" result = CliRunner().invoke(main_group, ["dump", path]) assert result.exit_code == 0 loaded = json.loads(result.output) with fiona.open(path) as src: assert len(loaded['features']) == len(src) assert len(loaded['features']) > 0 Fiona-1.10.1/tests/test_fio_features.py000066400000000000000000000061001467206072700200640ustar00rootroot00000000000000# CLI tests from click.testing import CliRunner from fiona.fio.main import main_group # type: ignore import pytest # type: ignore def test_map_count(): """fio-map prints correct number of results.""" with open("tests/data/trio.seq") as seq: data = seq.read() runner = CliRunner() result = runner.invoke( main_group, ["map", "centroid (buffer g 1.0)"], input=data, ) assert result.exit_code == 0 assert result.output.count('"type": "Point"') == 3 @pytest.mark.parametrize("raw_opt", ["--raw", "-r"]) def test_reduce_area(raw_opt): """Reduce features to their (raw) area.""" with open("tests/data/trio.seq") as seq: data = seq.read() runner = CliRunner() result = runner.invoke( main_group, ["reduce", raw_opt, "area (unary_union c) :projected false"], input=data, ) assert result.exit_code == 0 assert 0 < float(result.output) < 1e-5 def test_reduce_union(): """Reduce features to one single feature.""" with open("tests/data/trio.seq") as seq: data = seq.read() # Define our reduce command using a mkdocs snippet. arg = """ --8<-- [start:reduce] unary_union c --8<-- [end:reduce] """.splitlines()[ 2 ].strip() runner = CliRunner() result = runner.invoke(main_group, ["reduce", arg], input=data) assert result.exit_code == 0 assert result.output.count('"type": "Polygon"') == 1 assert result.output.count('"type": "LineString"') == 1 assert result.output.count('"type": "GeometryCollection"') == 1 def test_reduce_union_zip_properties(): """Reduce features to one single feature, zipping properties.""" with open("tests/data/trio.seq") as seq: data = seq.read() runner = CliRunner() result = runner.invoke( main_group, ["reduce", "--zip-properties", "unary_union c"], input=data ) assert result.exit_code == 0 assert result.output.count('"type": "Polygon"') == 1 assert result.output.count('"type": "LineString"') == 1 assert result.output.count('"type": "GeometryCollection"') == 1 assert ( """"name": ["Le ch\\u00e2teau d\'eau", "promenade du Peyrou"]""" in result.output ) def test_filter(): """Filter features by distance.""" with open("tests/data/trio.seq") as seq: data = seq.read() # Define our reduce command using a mkdocs snippet. arg = """ --8<-- [start:filter] < (distance g (Point 4 43)) 62.5E3 --8<-- [end:filter] """.splitlines()[ 2 ].strip() runner = CliRunner() result = runner.invoke( main_group, ["filter", arg], input=data, catch_exceptions=False, ) assert result.exit_code == 0 assert result.output.count('"type": "Polygon"') == 1 @pytest.mark.parametrize("opts", [["--no-input", "--raw"], ["-rn"]]) def test_map_no_input(opts): runner = CliRunner() result = runner.invoke(main_group, ["map"] + opts + ["(Point 4 43)"]) assert result.exit_code == 0 assert result.output.count('"type": "Point"') == 1 Fiona-1.10.1/tests/test_fio_filter.py000066400000000000000000000021011467206072700175300ustar00rootroot00000000000000"""Tests for the legacy fio-filter.""" import pytest from fiona.fio.main import main_group def test_fail(runner): with pytest.warns(FutureWarning): result = runner.invoke(main_group, ['filter', "f.properties.test > 5" ], "{'type': 'no_properties'}") assert result.exit_code == 1 def test_seq(feature_seq, runner): with pytest.warns(FutureWarning): result = runner.invoke(main_group, ['filter', "f.properties.AREA > 0.01"], feature_seq, catch_exceptions=False) assert result.exit_code == 0 assert result.output.count('Feature') == 2 with pytest.warns(FutureWarning): result = runner.invoke(main_group, ['filter', "f.properties.AREA > 0.015"], feature_seq) assert result.exit_code == 0 assert result.output.count('Feature') == 1 with pytest.warns(FutureWarning): result = runner.invoke(main_group, ['filter', "f.properties.AREA > 0.02"], feature_seq) assert result.exit_code == 0 assert result.output.count('Feature') == 0 Fiona-1.10.1/tests/test_fio_info.py000066400000000000000000000060151467206072700172060ustar00rootroot00000000000000"""Tests for ``$ fio info``.""" import json import re import sys if sys.version_info < (3, 10): from importlib_metadata import entry_points else: from importlib.metadata import entry_points from click.testing import CliRunner import pytest if sys.version_info < (3, 10): from importlib_metadata import entry_points else: from importlib.metadata import entry_points from fiona.fio.main import main_group def test_info_json(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke(main_group, ['info', path_coutwildrnp_shp]) assert result.exit_code == 0 assert '"count": 67' in result.output assert '"crs": "EPSG:4326"' in result.output assert '"driver": "ESRI Shapefile"' in result.output assert '"name": "coutwildrnp"' in result.output def test_info_count(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke( main_group, ['info', '--count', path_coutwildrnp_shp]) assert result.exit_code == 0 assert result.output == "67\n" def test_info_bounds(path_coutwildrnp_shp): runner = CliRunner() result = runner.invoke( main_group, ['info', '--bounds', path_coutwildrnp_shp]) assert result.exit_code == 0 assert len(re.findall(r'\d*\.\d*', result.output)) == 4 def test_all_registered(): """Make sure all the subcommands are actually registered to the main CLI group.""" for ep in entry_points(group="fiona.fio_commands"): assert ep.name in main_group.commands def _filter_info_warning(lines): """$ fio info can issue a RuntimeWarning, but click adds stderr to stdout so we have to filter it out before decoding JSON lines.""" lines = list(filter(lambda x: 'RuntimeWarning' not in x, lines)) return lines def test_info_no_count(path_gpx): """Make sure we can still get a `$ fio info` report on datasources that do not support feature counting, AKA `len(collection)`. """ runner = CliRunner() result = runner.invoke(main_group, ['info', path_gpx]) assert result.exit_code == 0 lines = _filter_info_warning(result.output.splitlines()) assert len(lines) == 1, "First line is warning & second is JSON. No more." assert json.loads(lines[0])['count'] is None def test_info_layer(path_gpx): for layer in ('routes', '1'): runner = CliRunner() result = runner.invoke(main_group, [ 'info', path_gpx, '--layer', layer]) assert result.exit_code == 0 lines = _filter_info_warning(result.output.splitlines()) assert len(lines) == 1, "1st line is warning & 2nd is JSON - no more." assert json.loads(lines[0])['name'] == 'routes' def test_info_vfs(path_coutwildrnp_zip, path_coutwildrnp_shp): runner = CliRunner() zip_result = runner.invoke(main_group, [ 'info', f'zip://{path_coutwildrnp_zip}']) shp_result = runner.invoke(main_group, [ 'info', path_coutwildrnp_shp]) assert zip_result.exit_code == shp_result.exit_code == 0 assert zip_result.output == shp_result.output Fiona-1.10.1/tests/test_fio_load.py000066400000000000000000000145371467206072700172020ustar00rootroot00000000000000"""Tests for `$ fio load`.""" from functools import partial import json import os import shutil import pytest import fiona from fiona.fio.main import main_group from fiona.model import ObjectEncoder def test_err(runner): result = runner.invoke( main_group, ['load'], '', catch_exceptions=False) assert result.exit_code == 2 def test_exception(tmpdir, runner): tmpfile = str(tmpdir.mkdir('tests').join('test_exception.shp')) result = runner.invoke(main_group, [ 'load', '-f', 'Shapefile', tmpfile ], '42', catch_exceptions=False) assert result.exit_code == 1 def test_collection(tmpdir, feature_collection, runner): tmpfile = str(tmpdir.mkdir('tests').join('test_collection.shp')) result = runner.invoke( main_group, ['load', '-f', 'Shapefile', tmpfile], feature_collection) assert result.exit_code == 0 assert len(fiona.open(tmpfile)) == 2 def test_seq_rs(feature_seq_pp_rs, tmpdir, runner): tmpfile = str(tmpdir.mkdir('tests').join('test_seq_rs.shp')) result = runner.invoke( main_group, ['load', '-f', 'Shapefile', tmpfile], feature_seq_pp_rs) assert result.exit_code == 0 assert len(fiona.open(tmpfile)) == 2 def test_seq_no_rs(tmpdir, runner, feature_seq): tmpfile = str(tmpdir.mkdir('tests').join('test_seq_no_rs.shp')) result = runner.invoke(main_group, [ 'load', '-f', 'Shapefile', tmpfile], feature_seq) assert result.exit_code == 0 assert len(fiona.open(tmpfile)) == 2 def test_dst_crs_default_to_src_crs(tmpdir, runner, feature_seq): """When --dst-crs is not given default to --src-crs.""" tmpfile = str(tmpdir.mkdir('tests').join('test_src_vs_dst_crs.shp')) result = runner.invoke(main_group, [ 'load', '--src-crs', 'EPSG:32617', '-f', 'Shapefile', tmpfile ], feature_seq) assert result.exit_code == 0 with fiona.open(tmpfile) as src: assert src.crs == {'init': 'epsg:32617'} assert len(src) == len(feature_seq.splitlines()) def test_different_crs(tmpdir, runner, feature_seq): tmpfile = str(tmpdir.mkdir('tests').join('test_different_crs.shp')) result = runner.invoke( main_group, [ 'load', '--src-crs', 'EPSG:32617', '--dst-crs', 'EPSG:32610', '-f', 'Shapefile', tmpfile ], feature_seq) assert result.exit_code == 0 with fiona.open(tmpfile) as src: assert src.crs == {'init': 'epsg:32610'} assert len(src) == len(feature_seq.splitlines()) def test_dst_crs_no_src(tmpdir, runner, feature_seq): tmpfile = str(tmpdir.mkdir('tests').join('test_dst_crs_no_src.shp')) result = runner.invoke(main_group, [ 'load', '--dst-crs', 'EPSG:32610', '-f', 'Shapefile', tmpfile ], feature_seq) assert result.exit_code == 0 with fiona.open(tmpfile) as src: assert src.crs == {'init': 'epsg:32610'} assert len(src) == len(feature_seq.splitlines()) def test_fio_load_layer(tmpdir, runner): outdir = str(tmpdir.mkdir('tests').mkdir('test_fio_load_layer')) try: feature = { 'type': 'Feature', 'properties': {'key': 'value'}, 'geometry': { 'type': 'Point', 'coordinates': (5.0, 39.0) } } sequence = os.linesep.join(map(partial(json.dumps, cls=ObjectEncoder), [feature, feature])) result = runner.invoke(main_group, [ 'load', outdir, '--driver', 'ESRI Shapefile', '--src-crs', 'EPSG:4236', '--layer', 'test_layer'], input=sequence) assert result.exit_code == 0 with fiona.open(outdir) as src: assert len(src) == 2 assert src.name == 'test_layer' assert src.schema['geometry'] == 'Point' finally: shutil.rmtree(outdir) @pytest.mark.iconv def test_creation_options(tmpdir, runner, feature_seq): tmpfile = str(tmpdir.mkdir("tests").join("test.shp")) result = runner.invoke( main_group, ["load", "-f", "Shapefile", "--co", "ENCODING=LATIN1", tmpfile], feature_seq, ) assert result.exit_code == 0 assert tmpdir.join("tests/test.cpg").read() == "LATIN1" @pytest.mark.parametrize("extension, driver", [ ("shp", "ESRI Shapefile"), ("geojson", "GeoJSON"), ("json", "GeoJSON"), ("gpkg", "GPKG"), ("SHP", "ESRI Shapefile"), ]) def test_load__auto_detect_format(tmpdir, runner, feature_seq, extension, driver): tmpfile = str(tmpdir.mkdir('tests').join(f'test_src_vs_dst_crs.{extension}')) result = runner.invoke(main_group, [ 'load', '--src-crs', 'EPSG:32617', tmpfile ], feature_seq) assert result.exit_code == 0 with fiona.open(tmpfile.lower()) as src: assert src.crs == {'init': 'epsg:32617'} assert len(src) == len(feature_seq.splitlines()) assert src.driver == driver def test_fio_load_layer_append(tmpdir, runner): """Checking append mode.""" outdir = str(tmpdir.mkdir("tests").mkdir("test_fio_load_layer")) try: feature = { "type": "Feature", "properties": {"key": "value"}, "geometry": {"type": "Point", "coordinates": (5.0, 39.0)}, } sequence = os.linesep.join( map(partial(json.dumps, cls=ObjectEncoder), [feature, feature]) ) # Write mode to create layer. result = runner.invoke( main_group, [ "load", outdir, "--driver", "ESRI Shapefile", "--src-crs", "EPSG:4236", "--layer", "test_layer", ], input=sequence, ) assert result.exit_code == 0 # Here's the append. result = runner.invoke( main_group, [ "load", outdir, "--driver=ESRI Shapefile", "--src-crs=EPSG:4236", "--layer=test_layer", "--append", ], input=sequence, ) assert result.exit_code == 0 with fiona.open(outdir) as src: assert len(src) == 4 assert src.name == "test_layer" assert src.schema["geometry"] == "Point" finally: shutil.rmtree(outdir) Fiona-1.10.1/tests/test_fio_ls.py000066400000000000000000000033601467206072700166710ustar00rootroot00000000000000"""Unittests for `$ fio ls`""" import json import sys import os from click.testing import CliRunner import pytest import fiona from fiona.fio.main import main_group def test_fio_ls_single_layer(data_dir): result = CliRunner().invoke(main_group, ['ls', data_dir]) assert result.exit_code == 0 assert len(result.output.splitlines()) == 1 assert sorted(json.loads(result.output)) == ['coutwildrnp', 'gre', 'test_tin'] def test_fio_ls_indent(path_coutwildrnp_shp): result = CliRunner().invoke(main_group, [ 'ls', '--indent', '4', path_coutwildrnp_shp]) assert result.exit_code == 0 assert len(result.output.strip().splitlines()) == 3 assert json.loads(result.output) == ['coutwildrnp'] def test_fio_ls_multi_layer(path_coutwildrnp_shp, tmpdir): outdir = str(tmpdir.mkdir('test_fio_ls_multi_layer')) # Copy test shapefile into new directory # Shapefile driver treats a directory of shapefiles as a single # multi-layer datasource layer_names = ['l1', 'l2'] for layer in layer_names: with fiona.open(path_coutwildrnp_shp) as src, \ fiona.open(outdir, 'w', layer=layer, **src.meta) as dst: for feat in src: dst.write(feat) # Run CLI test result = CliRunner().invoke(main_group, [ 'ls', outdir]) assert result.exit_code == 0 json_result = json.loads(result.output) assert sorted(json_result) == sorted(layer_names) def test_fio_ls_vfs(path_coutwildrnp_zip): runner = CliRunner() result = runner.invoke(main_group, [ 'ls', f'zip://{path_coutwildrnp_zip}']) assert result.exit_code == 0 loaded = json.loads(result.output) assert len(loaded) == 1 assert loaded[0] == 'coutwildrnp' Fiona-1.10.1/tests/test_fio_rm.py000066400000000000000000000033371467206072700166750ustar00rootroot00000000000000import os import pytest from click.testing import CliRunner import fiona from fiona.model import Feature from fiona.fio.main import main_group def create_sample_data(filename, driver, **extra_meta): meta = {"driver": driver, "schema": {"geometry": "Point", "properties": {}}} meta.update(extra_meta) with fiona.open(filename, "w", **meta) as dst: dst.write( Feature.from_dict( **{ "geometry": { "type": "Point", "coordinates": (0, 0), }, "properties": {}, } ) ) assert os.path.exists(filename) drivers = ["ESRI Shapefile", "GeoJSON"] @pytest.mark.parametrize("driver", drivers) def test_remove(tmpdir, driver): extension = {"ESRI Shapefile": "shp", "GeoJSON": "json"}[driver] filename = f"delete_me.{extension}" filename = str(tmpdir.join(filename)) create_sample_data(filename, driver) result = CliRunner().invoke(main_group, ["rm", filename, "--yes"]) assert result.exit_code == 0 assert not os.path.exists(filename) has_gpkg = "GPKG" in fiona.supported_drivers.keys() @pytest.mark.skipif(not has_gpkg, reason="Requires GPKG driver") def test_remove_layer(tmpdir): filename = str(tmpdir.join("a_filename.gpkg")) create_sample_data(filename, "GPKG", layer="layer1") create_sample_data(filename, "GPKG", layer="layer2") assert fiona.listlayers(filename) == ["layer1", "layer2"] result = CliRunner().invoke( main_group, ["rm", filename, "--layer", "layer2", "--yes"] ) assert result.exit_code == 0 assert os.path.exists(filename) assert fiona.listlayers(filename) == ["layer1"] Fiona-1.10.1/tests/test_geojson.py000066400000000000000000000120351467206072700170610ustar00rootroot00000000000000"""Tests of behavior specific to GeoJSON""" import json import pytest import fiona from fiona.collection import supported_drivers from fiona.errors import FionaValueError, DriverError, SchemaError, CRSError from fiona.model import Feature def test_json_read(path_coutwildrnp_json): with fiona.open(path_coutwildrnp_json, "r") as c: assert len(c) == 67 def test_json(tmpdir): """Write a simple GeoJSON file""" path = str(tmpdir.join("foo.json")) with fiona.open( path, "w", driver="GeoJSON", schema={"geometry": "Unknown", "properties": [("title", "str")]}, ) as c: c.writerecords( [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [0.0, 0.0]}, "properties": {"title": "One"}, } ) ] ) c.writerecords( [ Feature.from_dict( **{ "geometry": {"type": "MultiPoint", "coordinates": [[0.0, 0.0]]}, "properties": {"title": "Two"}, } ) ] ) with fiona.open(path) as c: assert c.schema["geometry"] == "Unknown" assert len(c) == 2 def test_json_overwrite(tmpdir): """Overwrite an existing GeoJSON file""" path = str(tmpdir.join("foo.json")) driver = "GeoJSON" schema1 = {"geometry": "Unknown", "properties": [("title", "str")]} schema2 = {"geometry": "Unknown", "properties": [("other", "str")]} features1 = [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [0.0, 0.0]}, "properties": {"title": "One"}, } ), Feature.from_dict( **{ "geometry": {"type": "MultiPoint", "coordinates": [[0.0, 0.0]]}, "properties": {"title": "Two"}, } ), ] features2 = [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [0.0, 0.0]}, "properties": {"other": "Three"}, } ), ] # write some data to a file with fiona.open(path, "w", driver=driver, schema=schema1) as c: c.writerecords(features1) # test the data was written correctly with fiona.open(path, "r") as c: assert len(c) == 2 feature = next(iter(c)) assert feature.properties["title"] == "One" # attempt to overwrite the existing file with some new data with fiona.open(path, "w", driver=driver, schema=schema2) as c: c.writerecords(features2) # test the second file was written correctly with fiona.open(path, "r") as c: assert len(c) == 1 feature = next(iter(c)) assert feature.properties["other"] == "Three" def test_json_overwrite_invalid(tmpdir): """Overwrite an existing file that isn't a valid GeoJSON""" # write some invalid data to a file path = str(tmpdir.join("foo.json")) with open(path, "w") as f: f.write("This isn't a valid GeoJSON file!!!") schema1 = {"geometry": "Unknown", "properties": [("title", "str")]} features1 = [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [0.0, 0.0]}, "properties": {"title": "One"}, } ), Feature.from_dict( **{ "geometry": {"type": "MultiPoint", "coordinates": [[0.0, 0.0]]}, "properties": {"title": "Two"}, } ), ] # attempt to overwrite it with a valid file with fiona.open(path, "w", driver="GeoJSON", schema=schema1) as dst: dst.writerecords(features1) # test the data was written correctly with fiona.open(path, "r") as src: assert len(src) == 2 def test_write_json_invalid_directory(tmpdir): """Attempt to create a file in a directory that doesn't exist""" path = str(tmpdir.join("does-not-exist", "foo.json")) schema = {"geometry": "Unknown", "properties": [("title", "str")]} with pytest.raises(DriverError): fiona.open(path, "w", driver="GeoJSON", schema=schema) def test_empty_array_property(tmp_path): """Confirm fix for bug reported in gh-1227.""" tmp_path.joinpath("test.geojson").write_text( json.dumps( { "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [12, 24]}, "properties": {"array_prop": ["some_value"]}, }, { "type": "Feature", "geometry": {"type": "Point", "coordinates": [12, 24]}, "properties": {"array_prop": []}, }, ], } ) ) list(fiona.open(tmp_path.joinpath("test.geojson"))) Fiona-1.10.1/tests/test_geometry.py000066400000000000000000000114051467206072700172500ustar00rootroot00000000000000"""Tests for geometry objects.""" import pytest from fiona._geometry import GeomBuilder, geometryRT from fiona.errors import UnsupportedGeometryTypeError from fiona.model import Geometry def geometry_wkb(wkb): try: wkb = bytes.fromhex(wkb) except AttributeError: wkb = wkb.decode("hex") return GeomBuilder().build_wkb(wkb) def test_ogr_builder_exceptions(): geom = Geometry.from_dict(**{"type": "Bogus", "coordinates": None}) with pytest.raises(UnsupportedGeometryTypeError): geometryRT(geom) @pytest.mark.parametrize( "geom_type, coordinates", [ ("Point", (0.0, 0.0)), ("LineString", [(0.0, 0.0), (1.0, 1.0)]), ("Polygon", [[(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]]), ("MultiPoint", [(0.0, 0.0), (1.0, 1.0)]), ("MultiLineString", [[(0.0, 0.0), (1.0, 1.0)]]), ( "MultiPolygon", [[[(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]]], ), ], ) def test_round_tripping(geom_type, coordinates): result = geometryRT( Geometry.from_dict(**{"type": geom_type, "coordinates": coordinates}) ) assert result.type == geom_type assert result.coordinates == coordinates @pytest.mark.parametrize( "geom_type, coordinates", [ ("Polygon", [[(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]]), ("MultiPolygon", [[[(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]]]), ], ) def test_implicitly_closed_round_tripping(geom_type, coordinates): result = geometryRT( Geometry.from_dict(**{"type": geom_type, "coordinates": coordinates}) ) assert result.type == geom_type result_coordinates = result.coordinates while not isinstance(coordinates[0], tuple): result_coordinates = result_coordinates[0] coordinates = coordinates[0] assert result_coordinates[:-1] == coordinates def test_geometry_collection_round_trip(): geom = { "type": "GeometryCollection", "geometries": [ {"type": "Point", "coordinates": (0.0, 0.0)}, {"type": "LineString", "coordinates": [(0.0, 0.0), (1.0, 1.0)]}, ], } result = geometryRT(geom) assert len(result["geometries"]) == 2 assert [g["type"] for g in result["geometries"]] == ["Point", "LineString"] def test_point_wkb(): # Hex-encoded Point (0 0) wkb = "010100000000000000000000000000000000000000" geom = geometry_wkb(wkb) assert geom["type"] == "Point" assert geom["coordinates"] == (0.0, 0.0) def test_line_wkb(): # Hex-encoded LineString (0 0, 1 1) wkb = ( "01020000000200000000000000000000000000000000000000000000000000f03f" "000000000000f03f" ) geom = geometry_wkb(wkb) assert geom["type"] == "LineString" assert geom["coordinates"] == [(0.0, 0.0), (1.0, 1.0)] def test_polygon_wkb(): # 1 x 1 box (0, 0, 1, 1) wkb = ( "01030000000100000005000000000000000000f03f000000000000000000000000" "0000f03f000000000000f03f0000000000000000000000000000f03f0000000000" "0000000000000000000000000000000000f03f0000000000000000" ) geom = geometry_wkb(wkb) assert geom["type"], "Polygon" assert len(geom["coordinates"]) == 1 assert len(geom["coordinates"][0]) == 5 x, y = zip(*geom["coordinates"][0]) assert min(x) == 0.0 assert min(y) == 0.0 assert max(x) == 1.0 assert max(y) == 1.0 def test_multipoint_wkb(): wkb = ( "010400000002000000010100000000000000000000000000000000000000010100" "0000000000000000f03f000000000000f03f" ) geom = geometry_wkb(wkb) assert geom["type"] == "MultiPoint" assert geom["coordinates"] == [(0.0, 0.0), (1.0, 1.0)] def test_multilinestring_wkb(): # Hex-encoded LineString (0 0, 1 1) wkb = ( "010500000001000000010200000002000000000000000000000000000000000000" "00000000000000f03f000000000000f03f" ) geom = geometry_wkb(wkb) assert geom["type"] == "MultiLineString" assert len(geom["coordinates"]) == 1 assert len(geom["coordinates"][0]) == 2 assert geom["coordinates"][0] == [(0.0, 0.0), (1.0, 1.0)] def test_multipolygon_wkb(): # [1 x 1 box (0, 0, 1, 1)] wkb = ( "01060000000100000001030000000100000005000000000000000000f03f000000" "0000000000000000000000f03f000000000000f03f000000000000000000000000" "0000f03f00000000000000000000000000000000000000000000f03f0000000000" "000000" ) geom = geometry_wkb(wkb) assert geom["type"] == "MultiPolygon" assert len(geom["coordinates"]) == 1 assert len(geom["coordinates"][0]) == 1 assert len(geom["coordinates"][0][0]) == 5 x, y = zip(*geom["coordinates"][0][0]) assert min(x) == 0.0 assert min(y) == 0.0 assert max(x) == 1.0 assert max(y) == 1.0 Fiona-1.10.1/tests/test_geopackage.py000066400000000000000000000046771467206072700175200ustar00rootroot00000000000000import os import pytest import fiona from fiona.model import Feature from .conftest import requires_gpkg example_schema = { "geometry": "Point", "properties": [("title", "str")], } example_crs = { "a": 6370997, "lon_0": -100, "y_0": 0, "no_defs": True, "proj": "laea", "x_0": 0, "units": "m", "b": 6370997, "lat_0": 45, } example_features = [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [0.0, 0.0]}, "properties": {"title": "One"}, } ), Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [1.0, 2.0]}, "properties": {"title": "Two"}, } ), Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [3.0, 4.0]}, "properties": {"title": "Three"}, } ), ] @requires_gpkg def test_read_gpkg(path_coutwildrnp_gpkg): """ Implicitly tests writing gpkg as the fixture will create the data source on first request """ with fiona.open(path_coutwildrnp_gpkg, "r") as src: assert len(src) == 67 feature = next(iter(src)) assert feature.geometry["type"] == "Polygon" assert feature.properties["NAME"] == "Mount Naomi Wilderness" @requires_gpkg def test_write_gpkg(tmpdir): path = str(tmpdir.join("foo.gpkg")) with fiona.open( path, "w", driver="GPKG", schema=example_schema, crs=example_crs ) as dst: dst.writerecords(example_features) with fiona.open(path) as src: assert src.schema["geometry"] == "Point" assert len(src) == 3 @requires_gpkg def test_write_multilayer_gpkg(tmpdir): """ Test that writing a second layer to an existing geopackage doesn't remove and existing layer for the dataset. """ path = str(tmpdir.join("foo.gpkg")) with fiona.open( path, "w", driver="GPKG", schema=example_schema, layer="layer1", crs=example_crs ) as dst: dst.writerecords(example_features[0:2]) with fiona.open( path, "w", driver="GPKG", schema=example_schema, layer="layer2", crs=example_crs ) as dst: dst.writerecords(example_features[2:]) with fiona.open(path, layer="layer1") as src: assert src.schema["geometry"] == "Point" assert len(src) == 2 with fiona.open(path, layer="layer2") as src: assert src.schema["geometry"] == "Point" assert len(src) == 1 Fiona-1.10.1/tests/test_http_session.py000066400000000000000000000255401467206072700201440ustar00rootroot00000000000000"""Tests of session module""" from unittest import mock import pytest from fiona.session import ( DummySession, AWSSession, Session, OSSSession, GSSession, SwiftSession, AzureSession, ) def test_base_session_hascreds_notimpl(): """Session.hascreds must be overridden""" assert Session.hascreds({}) is NotImplemented def test_base_session_get_credential_options_notimpl(): """Session.get_credential_options must be overridden""" assert Session().get_credential_options() is NotImplemented def test_dummy_session(): """DummySession works""" sesh = DummySession() assert sesh._session is None assert sesh.get_credential_options() == {} def test_aws_session_class(): """AWSSession works""" sesh = AWSSession(aws_access_key_id="foo", aws_secret_access_key="bar") assert sesh._session assert sesh.get_credential_options()["AWS_ACCESS_KEY_ID"] == "foo" assert sesh.get_credential_options()["AWS_SECRET_ACCESS_KEY"] == "bar" def test_aws_session_class_session(): """AWSSession works""" boto3 = pytest.importorskip("boto3") sesh = AWSSession( session=boto3.session.Session( aws_access_key_id="foo", aws_secret_access_key="bar" ) ) assert sesh._session assert sesh.get_credential_options()["AWS_ACCESS_KEY_ID"] == "foo" assert sesh.get_credential_options()["AWS_SECRET_ACCESS_KEY"] == "bar" def test_aws_session_class_unsigned(): """AWSSession works""" pytest.importorskip("boto3") sesh = AWSSession(aws_unsigned=True, region_name="us-mountain-1") assert sesh.get_credential_options()["AWS_NO_SIGN_REQUEST"] == "YES" assert sesh.get_credential_options()["AWS_REGION"] == "us-mountain-1" def test_aws_session_class_profile(tmpdir, monkeypatch): """Confirm that profile_name kwarg works.""" pytest.importorskip("boto3") credentials_file = tmpdir.join("credentials") credentials_file.write( "[testing]\n" "aws_access_key_id = foo\n" "aws_secret_access_key = bar\n" "aws_session_token = baz" ) monkeypatch.setenv("AWS_SHARED_CREDENTIALS_FILE", str(credentials_file)) monkeypatch.setenv("AWS_SESSION_TOKEN", "ignore_me") sesh = AWSSession(profile_name="testing") assert sesh._session assert sesh.get_credential_options()["AWS_ACCESS_KEY_ID"] == "foo" assert sesh.get_credential_options()["AWS_SECRET_ACCESS_KEY"] == "bar" assert sesh.get_credential_options()["AWS_SESSION_TOKEN"] == "baz" monkeypatch.undo() def test_aws_session_class_endpoint(): """Confirm that endpoint_url kwarg works.""" pytest.importorskip("boto3") sesh = AWSSession(endpoint_url="example.com") assert sesh.get_credential_options()["AWS_S3_ENDPOINT"] == "example.com" def test_session_factory_unparsed(): """Get a DummySession for unparsed paths""" sesh = Session.from_path("/vsicurl/lolwut") assert isinstance(sesh, DummySession) def test_session_factory_empty(): """Get a DummySession for no path""" sesh = Session.from_path("") assert isinstance(sesh, DummySession) def test_session_factory_local(): """Get a DummySession for local paths""" sesh = Session.from_path("file:///lolwut") assert isinstance(sesh, DummySession) def test_session_factory_unknown(): """Get a DummySession for unknown paths""" sesh = Session.from_path("https://fancy-cloud.com/lolwut") assert isinstance(sesh, DummySession) def test_session_factory_s3(): """Get an AWSSession for s3:// paths""" pytest.importorskip("boto3") sesh = Session.from_path("s3://lol/wut") assert isinstance(sesh, AWSSession) def test_session_factory_s3_presigned_url(): """Get a DummySession for presigned URLs""" sesh = Session.from_path("https://fancy-cloud.com/lolwut?X-Amz-Signature=foo") assert isinstance(sesh, DummySession) def test_session_factory_s3_no_boto3(monkeypatch): """Get an AWSSession for s3:// paths""" pytest.importorskip("boto3") with monkeypatch.context() as mpctx: mpctx.setattr("fiona.session.boto3", None) sesh = Session.from_path("s3://lol/wut") assert isinstance(sesh, DummySession) def test_session_factory_s3_kwargs(): """Get an AWSSession for s3:// paths with keywords""" pytest.importorskip("boto3") sesh = Session.from_path( "s3://lol/wut", aws_access_key_id="foo", aws_secret_access_key="bar" ) assert isinstance(sesh, AWSSession) assert sesh._session.get_credentials().access_key == "foo" assert sesh._session.get_credentials().secret_key == "bar" def test_foreign_session_factory_dummy(): sesh = Session.from_foreign_session(None) assert isinstance(sesh, DummySession) def test_foreign_session_factory_s3(): boto3 = pytest.importorskip("boto3") aws_session = boto3.Session(aws_access_key_id="foo", aws_secret_access_key="bar") sesh = Session.from_foreign_session(aws_session, cls=AWSSession) assert isinstance(sesh, AWSSession) assert sesh._session.get_credentials().access_key == "foo" assert sesh._session.get_credentials().secret_key == "bar" def test_requester_pays(): """GDAL is configured with requester pays""" sesh = AWSSession( aws_access_key_id="foo", aws_secret_access_key="bar", requester_pays=True ) assert sesh._session assert sesh.get_credential_options()["AWS_REQUEST_PAYER"] == "requester" def test_oss_session_class(): """OSSSession works""" oss_session = OSSSession( oss_access_key_id="foo", oss_secret_access_key="bar", oss_endpoint="null-island-1", ) assert oss_session._creds assert oss_session.get_credential_options()["OSS_ACCESS_KEY_ID"] == "foo" assert oss_session.get_credential_options()["OSS_SECRET_ACCESS_KEY"] == "bar" def test_session_factory_oss_kwargs(): """Get an OSSSession for oss:// paths with keywords""" sesh = Session.from_path( "oss://lol/wut", oss_access_key_id="foo", oss_secret_access_key="bar" ) assert isinstance(sesh, OSSSession) assert sesh.get_credential_options()["OSS_ACCESS_KEY_ID"] == "foo" assert sesh.get_credential_options()["OSS_SECRET_ACCESS_KEY"] == "bar" def test_google_session_ctor_no_arg(): session = GSSession() assert not session._creds def test_gs_session_class(): """GSSession works""" gs_session = GSSession(google_application_credentials="foo") assert gs_session._creds assert ( gs_session.get_credential_options()["GOOGLE_APPLICATION_CREDENTIALS"] == "foo" ) assert gs_session.hascreds({"GOOGLE_APPLICATION_CREDENTIALS": "foo"}) def test_swift_session_class(): """SwiftSession works""" swift_session = SwiftSession( swift_storage_url="foo", swift_auth_token="bar", ) assert swift_session._creds assert swift_session.get_credential_options()["SWIFT_STORAGE_URL"] == "foo" assert swift_session.get_credential_options()["SWIFT_AUTH_TOKEN"] == "bar" def test_swift_session_by_user_key(): def mock_init( self, session=None, swift_storage_url=None, swift_auth_token=None, swift_auth_v1_url=None, swift_user=None, swift_key=None, ): self._creds = {"SWIFT_STORAGE_URL": "foo", "SWIFT_AUTH_TOKEN": "bar"} with mock.patch("fiona.session.SwiftSession.__init__", new=mock_init): swift_session = SwiftSession( swift_auth_v1_url="foo", swift_user="bar", swift_key="key" ) assert swift_session._creds assert swift_session.get_credential_options()["SWIFT_STORAGE_URL"] == "foo" assert swift_session.get_credential_options()["SWIFT_AUTH_TOKEN"] == "bar" def test_session_factory_swift_kwargs(): """Get an SwiftSession for /vsiswift/bucket/key with keywords""" sesh = Session.from_path( "/vsiswift/lol/wut", swift_storage_url="foo", swift_auth_token="bar" ) assert isinstance(sesh, DummySession) def test_session_aws_or_dummy_aws(): """Get an AWSSession when boto3 is available""" boto3 = pytest.importorskip("boto3") assert isinstance(Session.aws_or_dummy(), AWSSession) def test_session_aws_or_dummy_dummy(monkeypatch): """Get a DummySession when boto3 is not available""" boto3 = pytest.importorskip("boto3") with monkeypatch.context() as mpctx: mpctx.setattr("fiona.session.boto3", None) assert isinstance(Session.aws_or_dummy(), DummySession) def test_no_sign_request(monkeypatch): """If AWS_NO_SIGN_REQUEST is set do not default to aws_unsigned=False""" monkeypatch.setenv("AWS_NO_SIGN_REQUEST", "YES") assert AWSSession().unsigned def test_no_credentialization_if_unsigned(monkeypatch): """Don't get credentials if we're not signing, see #1984""" sesh = AWSSession(aws_unsigned=True) assert sesh._creds is None def test_azure_session_class(): """AzureSession works""" azure_session = AzureSession( azure_storage_account="foo", azure_storage_access_key="bar" ) assert azure_session._creds assert azure_session.get_credential_options()["AZURE_STORAGE_ACCOUNT"] == "foo" assert azure_session.get_credential_options()["AZURE_STORAGE_ACCESS_KEY"] == "bar" def test_azure_session_class_connection_string(): """AzureSession works""" azure_session = AzureSession( azure_storage_connection_string="AccountName=myaccount;AccountKey=MY_ACCOUNT_KEY" ) assert azure_session._creds assert ( azure_session.get_credential_options()["AZURE_STORAGE_CONNECTION_STRING"] == "AccountName=myaccount;AccountKey=MY_ACCOUNT_KEY" ) def test_session_factory_az_kwargs(): """Get an AzureSession for az:// paths with keywords""" sesh = Session.from_path( "az://lol/wut", azure_storage_account="foo", azure_storage_access_key="bar" ) assert isinstance(sesh, AzureSession) assert sesh.get_credential_options()["AZURE_STORAGE_ACCOUNT"] == "foo" assert sesh.get_credential_options()["AZURE_STORAGE_ACCESS_KEY"] == "bar" def test_session_factory_az_kwargs_connection_string(): """Get an AzureSession for az:// paths with keywords""" sesh = Session.from_path( "az://lol/wut", azure_storage_connection_string="AccountName=myaccount;AccountKey=MY_ACCOUNT_KEY", ) assert isinstance(sesh, AzureSession) assert ( sesh.get_credential_options()["AZURE_STORAGE_CONNECTION_STRING"] == "AccountName=myaccount;AccountKey=MY_ACCOUNT_KEY" ) def test_azure_no_sign_request(monkeypatch): """If AZURE_NO_SIGN_REQUEST is set do not default to azure_unsigned=False""" monkeypatch.setenv("AZURE_NO_SIGN_REQUEST", "YES") assert AzureSession().unsigned def test_azure_session_class_unsigned(): """AzureSession works""" sesh = AzureSession(azure_unsigned=True, azure_storage_account="naipblobs") assert sesh.get_credential_options()["AZURE_NO_SIGN_REQUEST"] == "YES" assert sesh.get_credential_options()["AZURE_STORAGE_ACCOUNT"] == "naipblobs" Fiona-1.10.1/tests/test_integration.py000066400000000000000000000021741467206072700177430ustar00rootroot00000000000000"""Unittests to verify Fiona is functioning properly with other software.""" from collections import UserDict import fiona from fiona.model import Feature def test_dict_subclass(tmpdir): """Rasterio now has a `CRS()` class that subclasses `collections.UserDict()`. Make sure we can receive it. `UserDict()` is a good class to test against because in Python 2 it is not a subclass of `collections.Mapping()`, so it provides an edge case. """ class CRS(UserDict): pass outfile = str(tmpdir.join("test_UserDict.geojson")) profile = { "crs": CRS(init="EPSG:4326"), "driver": "GeoJSON", "schema": {"geometry": "Point", "properties": {}}, } with fiona.open(outfile, "w", **profile) as dst: dst.write( Feature.from_dict( **{ "type": "Feature", "properties": {}, "geometry": {"type": "Point", "coordinates": (10, -10)}, } ) ) with fiona.open(outfile) as src: assert len(src) == 1 assert src.crs == {"init": "epsg:4326"} Fiona-1.10.1/tests/test_layer.py000066400000000000000000000040151467206072700165300ustar00rootroot00000000000000"""Layer tests.""" import pytest import fiona from .test_collection import TestReading def test_index_selection(path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp, 'r', layer=0) as c: assert len(c) == 67 class TestFileReading(TestReading): @pytest.fixture(autouse=True) def shapefile(self, path_coutwildrnp_shp): self.c = fiona.open(path_coutwildrnp_shp, 'r', layer='coutwildrnp') yield self.c.close() def test_open_repr(self, path_coutwildrnp_shp): assert repr(self.c) == ( f"" ) def test_closed_repr(self, path_coutwildrnp_shp): self.c.close() assert repr(self.c) == ( f"" ) def test_name(self): assert self.c.name == 'coutwildrnp' class TestDirReading(TestReading): @pytest.fixture(autouse=True) def shapefile(self, data_dir): self.c = fiona.open(data_dir, "r", layer="coutwildrnp") yield self.c.close() def test_open_repr(self, data_dir): assert repr(self.c) == ( f"" ) def test_closed_repr(self, data_dir): self.c.close() assert repr(self.c) == ( f"" ) def test_name(self): assert self.c.name == 'coutwildrnp' def test_path(self, data_dir): assert self.c.path == data_dir def test_invalid_layer(path_coutwildrnp_shp): with pytest.raises(ValueError): fiona.open(path_coutwildrnp_shp, layer="foo") def test_write_invalid_numeric_layer(path_coutwildrnp_shp, tmpdir): with pytest.raises(ValueError): fiona.open(str(tmpdir.join("test-no-iter.shp")), mode='w', layer=0) Fiona-1.10.1/tests/test_listing.py000066400000000000000000000065461467206072700171000ustar00rootroot00000000000000"""Test listing a datasource's layers.""" from pathlib import Path import os import pytest import fiona import fiona.ogrext from fiona.errors import DriverError, FionaDeprecationWarning, FionaValueError from fiona.io import ZipMemoryFile def test_single_file_private(path_coutwildrnp_shp): with fiona.Env(): assert fiona.ogrext._listlayers( path_coutwildrnp_shp) == ['coutwildrnp'] def test_single_file(path_coutwildrnp_shp): assert fiona.listlayers(path_coutwildrnp_shp) == ['coutwildrnp'] def test_directory(data_dir): assert sorted(fiona.listlayers(data_dir)) == ['coutwildrnp', 'gre', 'test_tin'] def test_directory_trailing_slash(data_dir): assert sorted(fiona.listlayers(data_dir)) == ['coutwildrnp', 'gre', 'test_tin'] def test_zip_path(path_coutwildrnp_zip): assert fiona.listlayers( f'zip://{path_coutwildrnp_zip}') == ['coutwildrnp'] def test_zip_path_arch(path_coutwildrnp_zip): vfs = f'zip://{path_coutwildrnp_zip}' with pytest.warns(FionaDeprecationWarning): assert fiona.listlayers('/coutwildrnp.shp', vfs=vfs) == ['coutwildrnp'] def test_list_not_existing(data_dir): """Test underlying Cython function correctly raises""" path = os.path.join(data_dir, "does_not_exist.geojson") with pytest.raises(DriverError): fiona.ogrext._listlayers(path) def test_invalid_path(): with pytest.raises(TypeError): fiona.listlayers(1) def test_invalid_vfs(): with pytest.raises(TypeError): fiona.listlayers("/", vfs=1) def test_invalid_path_ioerror(): with pytest.raises(DriverError): fiona.listlayers("foobar") def test_path_object(path_coutwildrnp_shp): path_obj = Path(path_coutwildrnp_shp) assert fiona.listlayers(path_obj) == ['coutwildrnp'] def test_listing_file(path_coutwildrnp_json): """list layers from an open file object""" with open(path_coutwildrnp_json, "rb") as f: assert len(fiona.listlayers(f)) == 1 def test_listing_pathobj(path_coutwildrnp_json): """list layers from a Path object""" pathlib = pytest.importorskip("pathlib") assert len(fiona.listlayers(pathlib.Path(path_coutwildrnp_json))) == 1 def test_listdir_path(path_coutwildrnp_zip): """List directories in a path""" assert sorted(fiona.listdir(f"zip://{path_coutwildrnp_zip}")) == [ "coutwildrnp.dbf", "coutwildrnp.prj", "coutwildrnp.shp", "coutwildrnp.shx", ] def test_listdir_path_not_existing(data_dir): """Test listing of a non existent directory""" path = os.path.join(data_dir, "does_not_exist.zip") with pytest.raises(FionaValueError): fiona.listdir(path) def test_listdir_invalid_path(): """List directories with invalid path""" with pytest.raises(TypeError): assert fiona.listdir(1) def test_listdir_file(path_coutwildrnp_zip): """Test list directories of a file""" with pytest.raises(FionaValueError): fiona.listdir(f"zip://{path_coutwildrnp_zip}/coutwildrnp.shp") def test_listdir_zipmemoryfile(bytes_coutwildrnp_zip): """Test list directories of a zipped memory file.""" with ZipMemoryFile(bytes_coutwildrnp_zip) as memfile: print(memfile.name) assert sorted(fiona.listdir(memfile.name)) == [ "coutwildrnp.dbf", "coutwildrnp.prj", "coutwildrnp.shp", "coutwildrnp.shx", ] Fiona-1.10.1/tests/test_logutils.py000066400000000000000000000036021467206072700172570ustar00rootroot00000000000000"""Tests of skipped field log message filtering""" import logging import os import fiona from fiona.logutils import LogFiltering, FieldSkipLogFilter def test_filtering(caplog): """Test that ordinary log messages pass""" logger = logging.getLogger() with LogFiltering(logger, FieldSkipLogFilter()): logger.warning("Attention!") logger.warning("Skipping field 1") logger.warning("Skipping field 2") logger.warning("Danger!") logger.warning("Skipping field 1") assert len(caplog.records) == 4 assert caplog.records[0].getMessage() == "Attention!" assert caplog.records[1].getMessage() == "Skipping field 1" assert caplog.records[2].getMessage() == "Skipping field 2" assert caplog.records[3].getMessage() == "Danger!" def test_skipping_slice(caplog, data_dir): """Collection filters out all but one warning message""" with fiona.open(os.path.join(data_dir, "issue627.geojson")) as src: results = list(src) assert len(results) == 3 assert not any(['skip_me' in f['properties'] for f in results]) assert len([rec for rec in caplog.records if rec.getMessage().startswith('Skipping')]) == 1 def test_skipping_list(caplog, data_dir): """Collection filters out all but one warning message""" with fiona.open(os.path.join(data_dir, "issue627.geojson")) as src: results = list(src) assert len(results) == 3 assert not any(['skip_me' in f['properties'] for f in results]) assert len([rec for rec in caplog.records if rec.getMessage().startswith('Skipping')]) == 1 def test_log_filter_exception(caplog): """FieldSkipLogFilter handles exceptions from log.exception().""" logger = logging.getLogger() with LogFiltering(logger, FieldSkipLogFilter()): logger.exception(ValueError("Oh no")) assert len(caplog.records) == 1 assert caplog.records[0].getMessage() == "Oh no" Fiona-1.10.1/tests/test_memoryfile.py000066400000000000000000000336251467206072700175750ustar00rootroot00000000000000"""Tests of MemoryFile and ZippedMemoryFile""" import os from io import BytesIO import pytest import fiona from fiona import supported_drivers from fiona.drvsupport import _driver_supports_mode from fiona.errors import DriverError from fiona.io import MemoryFile, ZipMemoryFile from fiona.meta import supports_vsi from .conftest import requires_gdal2, requires_gpkg @pytest.fixture(scope='session') def profile_first_coutwildrnp_shp(path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp) as col: return col.profile, next(iter(col)) @pytest.fixture(scope='session') def data_coutwildrnp_json(path_coutwildrnp_json): with open(path_coutwildrnp_json, 'rb') as f: return f.read() def test_memoryfile_ext(): """File extensions are handled""" assert MemoryFile(ext=".geojson").name.endswith(".geojson") def test_memoryfile_bare_ext(): """File extensions without a leading . are handled""" assert MemoryFile(ext="geojson").name.endswith(".geojson") def test_memoryfile_init(data_coutwildrnp_json): """In-memory GeoJSON file can be read""" with MemoryFile(data_coutwildrnp_json) as memfile: with memfile.open() as collection: assert len(collection) == 67 def test_memoryfile_incr_init(data_coutwildrnp_json): """In-memory GeoJSON file written in 2 parts can be read""" with MemoryFile() as memfile: memfile.write(data_coutwildrnp_json[:1000]) memfile.write(data_coutwildrnp_json[1000:]) with memfile.open() as collection: assert len(collection) == 67 def test_zip_memoryfile(bytes_coutwildrnp_zip): """In-memory zipped Shapefile can be read""" with ZipMemoryFile(bytes_coutwildrnp_zip) as memfile: with memfile.open('coutwildrnp.shp') as collection: assert len(collection) == 67 def test_zip_memoryfile_infer_layer_name(bytes_coutwildrnp_zip): """In-memory zipped Shapefile can be read with the default layer""" with ZipMemoryFile(bytes_coutwildrnp_zip) as memfile: with memfile.open() as collection: assert len(collection) == 67 def test_open_closed(): """Get an exception when opening a dataset on a closed MemoryFile""" memfile = MemoryFile() memfile.close() assert memfile.closed with pytest.raises(OSError): memfile.open() def test_open_closed_zip(): """Get an exception when opening a dataset on a closed ZipMemoryFile""" memfile = ZipMemoryFile() memfile.close() assert memfile.closed with pytest.raises(OSError): memfile.open() def test_write_memoryfile(profile_first_coutwildrnp_shp): """In-memory GeoJSON can be written""" profile, first = profile_first_coutwildrnp_shp profile['driver'] = 'GeoJSON' with MemoryFile() as memfile: with memfile.open(**profile) as col: col.write(first) memfile.seek(0) data = memfile.read() with MemoryFile(data) as memfile: with memfile.open() as col: assert len(col) == 1 @requires_gdal2 def test_memoryfile_write_extension(profile_first_coutwildrnp_shp): """In-memory shapefile gets an .shp extension by default""" profile, first = profile_first_coutwildrnp_shp profile['driver'] = 'ESRI Shapefile' with MemoryFile() as memfile: with memfile.open(**profile) as col: col.write(first) assert memfile.name.endswith(".shp") def test_memoryfile_open_file_or_bytes_read(path_coutwildrnp_json): """Test MemoryFile.open when file_or_bytes has a read attribute """ with open(path_coutwildrnp_json, 'rb') as f: with MemoryFile(f) as memfile: with memfile.open() as collection: assert len(collection) == 67 def test_memoryfile_bytesio(data_coutwildrnp_json): """GeoJSON file stored in BytesIO can be read""" with fiona.open(BytesIO(data_coutwildrnp_json)) as collection: assert len(collection) == 67 def test_memoryfile_fileobj(path_coutwildrnp_json): """GeoJSON file in an open file object can be read""" with open(path_coutwildrnp_json, 'rb') as f: with fiona.open(f) as collection: assert len(collection) == 67 def test_memoryfile_len(data_coutwildrnp_json): """Test MemoryFile.__len__ """ with MemoryFile() as memfile: assert len(memfile) == 0 memfile.write(data_coutwildrnp_json) assert len(memfile) == len(data_coutwildrnp_json) def test_memoryfile_tell(data_coutwildrnp_json): """Test MemoryFile.tell() """ with MemoryFile() as memfile: assert memfile.tell() == 0 memfile.write(data_coutwildrnp_json) assert memfile.tell() == len(data_coutwildrnp_json) def test_write_bytesio(profile_first_coutwildrnp_shp): """GeoJSON can be written to BytesIO""" profile, first = profile_first_coutwildrnp_shp profile['driver'] = 'GeoJSON' with BytesIO() as fout: with fiona.open(fout, 'w', **profile) as col: col.write(first) fout.seek(0) data = fout.read() with MemoryFile(data) as memfile: with memfile.open() as col: assert len(col) == 1 @requires_gpkg def test_read_multilayer_memoryfile(path_coutwildrnp_json, tmpdir): """Test read access to multilayer dataset in from file-like object""" with fiona.open(path_coutwildrnp_json, "r") as src: schema = src.schema features = list(src) path = os.path.join(tmpdir, "test.gpkg") with fiona.open(path, "w", driver="GPKG", schema=schema, layer="layer1") as dst: dst.writerecords(features[0:5]) with fiona.open(path, "w", driver="GPKG", schema=schema, layer="layer2") as dst: dst.writerecords(features[5:]) with open(path, "rb") as f: with fiona.open(f, layer="layer1") as src: assert src.name == "layer1" assert len(src) == 5 # Bug reported in #781 where this next section would fail with open(path, "rb") as f: with fiona.open(f, layer="layer2") as src: assert src.name == "layer2" assert len(src) == 62 def test_append_bytesio_exception(data_coutwildrnp_json): """Append is not supported, see #1027.""" with pytest.raises(OSError): fiona.open(BytesIO(data_coutwildrnp_json), "a") def test_mapinfo_raises(): """Reported to be a crasher in #937""" driver = "MapInfo File" schema = {"geometry": "Point", "properties": {"position": "str"}} with BytesIO() as fout: with pytest.raises(OSError): with fiona.open(fout, "w", driver=driver, schema=schema) as collection: collection.write( { "type": "Feature", "geometry": {"type": "Point", "coordinates": (0, 0)}, "properties": {"position": "x"}, } ) # TODO remove exclusion of MapInfo File once testdata_generator is fixed @pytest.mark.parametrize( "driver", [ driver for driver in supported_drivers if _driver_supports_mode(driver, "w") and supports_vsi(driver) and driver not in {"MapInfo File", "TileDB"} ], ) def test_write_memoryfile_drivers(driver, testdata_generator): """ Test if driver is able to write to memoryfile """ range1 = list(range(0, 5)) schema, crs, records1, _, _ = testdata_generator(driver, range1, []) with MemoryFile() as memfile: with memfile.open(driver=driver, crs="OGC:CRS84", schema=schema) as c: c.writerecords(records1) with memfile.open(driver=driver) as c: assert driver == c.driver items = list(c) assert len(items) == len(range1) def test_multiple_layer_memoryfile(testdata_generator): """ Test ability to create multiple layers in memoryfile""" driver = "GPKG" range1 = list(range(0, 5)) range2 = list(range(5, 10)) schema, crs, records1, records2, _ = testdata_generator(driver, range1, range2) with MemoryFile() as memfile: with memfile.open(mode='w', driver=driver, schema=schema, layer="layer1") as c: c.writerecords(records1) with memfile.open(mode='w', driver=driver, schema=schema, layer="layer2") as c: c.writerecords(records2) with memfile.open(driver=driver, layer="layer1") as c: assert driver == c.driver items = list(c) assert len(items) == len(range1) with memfile.open(driver=driver, layer="layer2") as c: assert driver == c.driver items = list(c) assert len(items) == len(range1) # TODO remove exclusion of MapInfo File once testdata_generator is fixed @pytest.mark.parametrize( "driver", [ driver for driver in supported_drivers if _driver_supports_mode(driver, "a") and supports_vsi(driver) and driver not in {"MapInfo File", "TileDB"} ], ) def test_append_memoryfile_drivers(driver, testdata_generator): """Test if driver is able to append to memoryfile""" range1 = list(range(0, 5)) range2 = list(range(5, 10)) schema, crs, records1, records2, _ = testdata_generator(driver, range1, range2) with MemoryFile() as memfile: with memfile.open(driver=driver, crs="OGC:CRS84", schema=schema) as c: c.writerecords(records1) # The parquet dataset does not seem to support append mode if driver == "Parquet": with memfile.open(driver=driver) as c: assert driver == c.driver items = list(c) assert len(items) == len(range1) else: with memfile.open(mode='a', driver=driver, schema=schema) as c: c.writerecords(records2) with memfile.open(driver=driver) as c: assert driver == c.driver items = list(c) assert len(items) == len(range1 + range2) def test_memoryfile_driver_does_not_support_vsi(): """An exception is raised with a driver that does not support VSI""" if "FileGDB" not in supported_drivers: pytest.skip("FileGDB driver not available") with pytest.raises(DriverError): with MemoryFile() as memfile: with memfile.open(driver="FileGDB"): pass @pytest.mark.parametrize('mode', ['r', 'a']) def test_modes_on_non_existing_memoryfile(mode): """Non existing memoryfile cannot opened in r or a mode""" with MemoryFile() as memfile: with pytest.raises(IOError): with memfile.open(mode=mode): pass def test_write_mode_on_non_existing_memoryfile(profile_first_coutwildrnp_shp): """Exception is raised if a memoryfile is opened in write mode on a non empty memoryfile""" profile, first = profile_first_coutwildrnp_shp profile['driver'] = 'GeoJSON' with MemoryFile() as memfile: with memfile.open(**profile) as col: col.write(first) with pytest.raises(IOError): with memfile.open(mode="w"): pass @requires_gpkg def test_read_multilayer_memoryfile(path_coutwildrnp_json, tmpdir): """Test read access to multilayer dataset in from file-like object""" with fiona.open(path_coutwildrnp_json, "r") as src: schema = src.schema features = list(src) path = os.path.join(tmpdir, "test.gpkg") with fiona.open(path, "w", driver="GPKG", schema=schema, layer="layer1") as dst: dst.writerecords(features[0:5]) with fiona.open(path, "w", driver="GPKG", schema=schema, layer="layer2") as dst: dst.writerecords(features[5:]) with open(path, "rb") as f: with fiona.open(f, layer="layer1") as src: assert src.name == "layer1" assert len(src) == 5 # Bug reported in #781 where this next section would fail with open(path, "rb") as f: with fiona.open(f, layer="layer2") as src: assert src.name == "layer2" assert len(src) == 62 def test_allow_unsupported_drivers(monkeypatch): """Test if allow unsupported drivers works as expected""" # We delete a known working driver from fiona.drvsupport so that we can use it monkeypatch.delitem(fiona.drvsupport.supported_drivers, "GPKG") schema = {"geometry": "Polygon", "properties": {}} # Test that indeed we can't create a file without allow_unsupported_drivers with pytest.raises(DriverError): with MemoryFile() as memfile: with memfile.open(mode="w", driver="GPKG", schema=schema): pass # Test that we can create file with allow_unsupported_drivers=True try: with MemoryFile() as memfile: with memfile.open( mode="w", driver="GPKG", schema=schema, allow_unsupported_drivers=True ): pass except Exception as e: assert ( False ), f"Using allow_unsupported_drivers=True should not raise an exception: {e}" def test_listdir_zipmemoryfile(bytes_coutwildrnp_zip): """Test list directories of a zipped memory file.""" with ZipMemoryFile(bytes_coutwildrnp_zip) as memfile: assert sorted(memfile.listdir()) == [ "coutwildrnp.dbf", "coutwildrnp.prj", "coutwildrnp.shp", "coutwildrnp.shx", ] def test_listlayers_zipmemoryfile(bytes_coutwildrnp_zip): """Test layers of a zipped memory file.""" with ZipMemoryFile(bytes_coutwildrnp_zip) as memfile: assert memfile.listlayers() == ["coutwildrnp"] def test_listdir_gdbzipmemoryfile(bytes_testopenfilegdb_zip): """Test list directories of a zipped GDB memory file.""" with ZipMemoryFile(bytes_testopenfilegdb_zip, ext=".gdb.zip") as memfile: assert memfile.listdir() == [ "testopenfilegdb.gdb", ] def test_listdir_gdbzipmemoryfile_bis(bytes_testopenfilegdb_zip): """Test list directories of a zipped GDB memory file.""" with ZipMemoryFile(bytes_testopenfilegdb_zip, filename="temp.gdb.zip") as memfile: assert memfile.listdir() == [ "testopenfilegdb.gdb", ] Fiona-1.10.1/tests/test_meta.py000066400000000000000000000040731467206072700163460ustar00rootroot00000000000000import pytest import fiona import fiona.drvsupport import fiona.meta from fiona.drvsupport import supported_drivers from fiona.errors import FionaValueError from .conftest import requires_gdal2, requires_gdal23, requires_gdal31 @requires_gdal31 @pytest.mark.parametrize("driver", supported_drivers) def test_print_driver_options(driver): """ Test fiona.meta.print_driver_options(driver) """ # do not fail fiona.meta.print_driver_options(driver) @requires_gdal2 def test_metadata_wrong_driver(): """ Test that FionaValueError is raised for non existing driver""" with pytest.raises(FionaValueError): fiona.meta.print_driver_options("Not existing driver") @requires_gdal2 @pytest.mark.parametrize("driver", supported_drivers) def test_extension(driver): """ Test fiona.meta.extension(driver) """ # do not fail extension = fiona.meta.extension(driver) assert extension is None or isinstance(extension, str) @requires_gdal2 @pytest.mark.parametrize("driver", supported_drivers) def test_extensions(driver): """ Test fiona.meta.extensions(driver) """ # do not fail extensions = fiona.meta.extensions(driver) assert extensions is None or isinstance(extensions, list) @requires_gdal2 @pytest.mark.parametrize("driver", supported_drivers) def test_supports_vsi(driver): """ Test fiona.meta.supports_vsi(driver) """ # do not fail assert fiona.meta.supports_vsi(driver) in (True, False) @requires_gdal2 @pytest.mark.parametrize("driver", supported_drivers) def test_supported_field_types(driver): """ Test fiona.meta.supported_field_types(driver) """ # do not fail field_types = fiona.meta.supported_field_types(driver) assert field_types is None or isinstance(field_types, list) @requires_gdal23 @pytest.mark.parametrize("driver", supported_drivers) def test_supported_sub_field_types(driver): """ Test fiona.meta.supported_sub_field_types(driver) """ # do not fail sub_field_types = fiona.meta.supported_sub_field_types(driver) assert sub_field_types is None or isinstance(sub_field_types, list) Fiona-1.10.1/tests/test_model.py000066400000000000000000000230201467206072700165110ustar00rootroot00000000000000"""Test of deprecations following RFC 1""" import pytest from fiona.errors import FionaDeprecationWarning from fiona.model import ( _Geometry, Feature, Geometry, Object, ObjectEncoder, Properties, decode_object, ) def test_object_len(): """object len is correct""" obj = Object(g=1) assert len(obj) == 1 def test_object_iter(): """object iter is correct""" obj = Object(g=1) assert [obj[k] for k in obj] == [1] def test_object_setitem_warning(): """Warn about __setitem__""" obj = Object() with pytest.warns(FionaDeprecationWarning, match="immutable"): obj["g"] = 1 assert "g" in obj assert obj["g"] == 1 def test_object_update_warning(): """Warn about update""" obj = Object() with pytest.warns(FionaDeprecationWarning, match="immutable"): obj.update(g=1) assert "g" in obj assert obj["g"] == 1 def test_object_popitem_warning(): """Warn about pop""" obj = Object(g=1) with pytest.warns(FionaDeprecationWarning, match="immutable"): assert obj.pop("g") == 1 assert "g" not in obj def test_object_delitem_warning(): """Warn about __delitem__""" obj = Object(g=1) with pytest.warns(FionaDeprecationWarning, match="immutable"): del obj["g"] assert "g" not in obj def test_object_setitem_delegated(): """Delegation in __setitem__ works""" class ThingDelegate: def __init__(self, value): self.value = value class Thing(Object): _delegated_properties = ["value"] def __init__(self, value=None, **data): self._delegate = ThingDelegate(value) super().__init__(**data) thing = Thing() assert thing["value"] is None with pytest.warns(FionaDeprecationWarning, match="immutable"): thing["value"] = 1 assert thing["value"] == 1 def test_object_delitem_delegated(): """Delegation in __delitem__ works""" class ThingDelegate: def __init__(self, value): self.value = value class Thing(Object): _delegated_properties = ["value"] def __init__(self, value=None, **data): self._delegate = ThingDelegate(value) super().__init__(**data) thing = Thing(1) assert thing["value"] == 1 with pytest.warns(FionaDeprecationWarning, match="immutable"): del thing["value"] assert thing["value"] is None def test__geometry_ctor(): """Construction of a _Geometry works""" geom = _Geometry(type="Point", coordinates=(0, 0)) assert geom.type == "Point" assert geom.coordinates == (0, 0) def test_geometry_type(): """Geometry has a type""" geom = Geometry(type="Point") assert geom.type == "Point" def test_geometry_coordinates(): """Geometry has coordinates""" geom = Geometry(coordinates=[(0, 0), (1, 1)]) assert geom.coordinates == [(0, 0), (1, 1)] def test_geometry__props(): """Geometry properties as a dict""" assert Geometry(coordinates=(0, 0), type="Point")._props() == { "coordinates": (0, 0), "type": "Point", "geometries": None, } def test_geometry_gi(): """Geometry __geo_interface__""" gi = Geometry(coordinates=(0, 0), type="Point", geometries=[]).__geo_interface__ assert gi["type"] == "Point" assert gi["coordinates"] == (0, 0) def test_feature_no_geometry(): """Feature has no attribute""" feat = Feature() assert feat.geometry is None def test_feature_geometry(): """Feature has a geometry attribute""" geom = Geometry(type="Point") feat = Feature(geometry=geom) assert feat.geometry is geom def test_feature_no_id(): """Feature has no id""" feat = Feature() assert feat.id is None def test_feature_id(): """Feature has an id""" feat = Feature(id="123") assert feat.id == "123" def test_feature_no_properties(): """Feature has no properties""" feat = Feature() assert len(feat.properties) == 0 def test_feature_properties(): """Feature has properties""" feat = Feature(properties=Properties(foo=1)) assert len(feat.properties) == 1 assert feat.properties["foo"] == 1 def test_feature_from_dict_kwargs(): """Feature can be created from GeoJSON kwargs""" data = { "id": "foo", "type": "Feature", "geometry": {"type": "Point", "coordinates": (0, 0)}, "properties": {"a": 0, "b": "bar"}, "extras": {"this": 1}, } feat = Feature.from_dict(**data) assert feat.id == "foo" assert feat.type == "Feature" assert feat.geometry.type == "Point" assert feat.geometry.coordinates == (0, 0) assert len(feat.properties) == 2 assert feat.properties["a"] == 0 assert feat.properties["b"] == "bar" assert feat["extras"]["this"] == 1 def test_feature_from_dict_obj(): """Feature can be created from GeoJSON obj""" data = { "id": "foo", "type": "Feature", "geometry": {"type": "Point", "coordinates": (0, 0)}, "properties": {"a": 0, "b": "bar"}, "extras": {"this": 1}, } feat = Feature.from_dict(data) assert feat.id == "foo" assert feat.type == "Feature" assert feat.geometry.type == "Point" assert feat.geometry.coordinates == (0, 0) assert len(feat.properties) == 2 assert feat.properties["a"] == 0 assert feat.properties["b"] == "bar" assert feat["extras"]["this"] == 1 def test_feature_from_dict_kwargs_2(): """From GeoJSON kwargs using Geometry and Properties""" data = { "id": "foo", "type": "Feature", "geometry": Geometry(type="Point", coordinates=(0, 0)), "properties": Properties(a=0, b="bar"), "extras": {"this": 1}, } feat = Feature.from_dict(**data) assert feat.id == "foo" assert feat.type == "Feature" assert feat.geometry.type == "Point" assert feat.geometry.coordinates == (0, 0) assert len(feat.properties) == 2 assert feat.properties["a"] == 0 assert feat.properties["b"] == "bar" assert feat["extras"]["this"] == 1 def test_geometry_encode(): """Can encode a geometry""" assert ObjectEncoder().default(Geometry(type="Point", coordinates=(0, 0))) == { "type": "Point", "coordinates": (0, 0), } def test_feature_encode(): """Can encode a feature""" o_dict = ObjectEncoder().default( Feature( id="foo", geometry=Geometry(type="Point", coordinates=(0, 0)), properties=Properties(a=1, foo="bar", bytes=b"01234"), ) ) assert o_dict["id"] == "foo" assert o_dict["geometry"]["type"] == "Point" assert o_dict["geometry"]["coordinates"] == (0, 0) assert o_dict["properties"]["bytes"] == b'3031323334' def test_decode_object_hook(): """Can decode a feature""" data = { "id": "foo", "type": "Feature", "geometry": {"type": "Point", "coordinates": (0, 0)}, "properties": {"a": 0, "b": "bar"}, "extras": {"this": 1}, } feat = decode_object(data) assert feat.id == "foo" assert feat.type == "Feature" assert feat.geometry.type == "Point" assert feat.geometry.coordinates == (0, 0) assert len(feat.properties) == 2 assert feat.properties["a"] == 0 assert feat.properties["b"] == "bar" assert feat["extras"]["this"] == 1 def test_decode_object_hook_geometry(): """Can decode a geometry""" data = {"type": "Point", "coordinates": (0, 0)} geometry = decode_object(data) assert geometry.type == "Point" assert geometry.coordinates == (0, 0) @pytest.mark.parametrize("o", [{}, {"a": 1}, {"type": "FeatureCollection"}]) def test_decode_object_hook_fallback(o): """Pass through an ordinary dict""" assert decode_object(o) == o def test_properties(): """Property factory works""" assert Properties.from_dict(a=1, foo="bar")["a"] == 1 def test_feature_gi(): """Feature __geo_interface__.""" gi = Feature( id="foo", geometry=Geometry(type="Point", coordinates=(0, 0)), properties=Properties(a=1, foo="bar"), ) assert gi["id"] == "foo" assert gi["geometry"]["type"] == "Point" assert gi["geometry"]["coordinates"] == (0, 0) def test_encode_bytes(): """Bytes are encoded using base64.""" assert ObjectEncoder().default(b"01234") == b'3031323334' def test_null_property_encoding(): """A null feature property is retained.""" # Verifies fix for gh-1270. assert ObjectEncoder().default(Properties(a=1, b=None)) == {"a": 1, "b": None} def test_null_geometry_encoding(): """A null feature geometry is retained.""" # Verifies fix for gh-1270. o_dict = ObjectEncoder().default(Feature()) assert o_dict["geometry"] is None def test_geometry_collection_encoding(): """No coordinates in a GeometryCollection.""" assert "coordinates" not in ObjectEncoder().default( Geometry(type="GeometryCollection", geometries=[]) ) def test_feature_repr(): feat = Feature( id="1", geometry=Geometry(type="LineString", coordinates=[(0, 0)] * 100), properties=Properties(a=1, foo="bar"), ) assert repr(feat) == "fiona.Feature(geometry=fiona.Geometry(coordinates=[(0, 0), ...], type='LineString'), id='1', properties=fiona.Properties(a=1, foo='bar'))" def test_issue1430(): """__getitem__() returns property, not disconnected dict.""" feat = Feature(properties=Properties()) with pytest.warns(FionaDeprecationWarning, match="immutable"): feat["properties"]["foo"] = "bar" assert feat["properties"]["foo"] == "bar" assert feat.properties["foo"] == "bar" Fiona-1.10.1/tests/test_multiconxn.py000066400000000000000000000076371467206072700176310ustar00rootroot00000000000000import pytest import fiona from fiona.model import Feature, Geometry, Properties class TestReadAccess: # To check that we'll be able to get multiple 'r' connections to layers # in a single file. def test_meta(self, path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp, "r", layer="coutwildrnp") as c: with fiona.open(path_coutwildrnp_shp, "r", layer="coutwildrnp") as c2: assert len(c) == len(c2) assert sorted(c.schema.items()) == sorted(c2.schema.items()) def test_feat(self, path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp, "r", layer="coutwildrnp") as c: f1 = next(iter(c)) with fiona.open(path_coutwildrnp_shp, "r", layer="coutwildrnp") as c2: f2 = next(iter(c2)) assert f1.id == f2.id assert f1.properties == f2.properties assert f1.geometry.type == f2.geometry.type class TestReadWriteAccess: # To check that we'll be able to read from a file that we're # writing to. @pytest.fixture(autouse=True) def multi_write_test_shp(self, tmpdir): self.shapefile_path = str(tmpdir.join("multi_write_test.shp")) self.c = fiona.open( self.shapefile_path, "w", driver="ESRI Shapefile", schema={ "geometry": "Point", "properties": [("title", "str:80"), ("date", "date")], }, crs={"init": "epsg:4326", "no_defs": True}, encoding="utf-8", ) self.f = Feature( id="0", geometry=Geometry(type="Point", coordinates=(0.0, 0.1)), properties=Properties(title="point one", date="2012-01-29"), ) self.c.writerecords([self.f]) self.c.flush() yield self.c.close() def test_meta(self): c2 = fiona.open(self.shapefile_path, "r") assert len(self.c) == len(c2) assert sorted(self.c.schema.items()) == sorted(c2.schema.items()) c2.close() def test_read(self): c2 = fiona.open(self.shapefile_path, "r") f2 = next(iter(c2)) assert self.f.id == f2.id assert self.f.properties == f2.properties assert self.f.geometry.type == f2.geometry.type c2.close() def test_read_after_close(self): c2 = fiona.open(self.shapefile_path, "r") self.c.close() f2 = next(iter(c2)) assert self.f.properties == f2.properties c2.close() class TestLayerCreation: @pytest.fixture(autouse=True) def layer_creation_shp(self, tmpdir): self.dir = tmpdir.mkdir("layer_creation") self.c = fiona.open( str(self.dir), "w", layer="write_test", driver="ESRI Shapefile", schema={ "geometry": "Point", "properties": [("title", "str:80"), ("date", "date")], }, crs={"init": "epsg:4326", "no_defs": True}, encoding="utf-8", ) self.f = Feature( geometry=Geometry(type="Point", coordinates=(0.0, 0.1)), properties={"title": "point one", "date": "2012-01-29"}, ) self.c.writerecords([self.f]) self.c.flush() yield self.c.close() def test_meta(self): c2 = fiona.open(str(self.dir.join("write_test.shp")), "r") assert len(self.c) == len(c2) assert sorted(self.c.schema.items()) == sorted(c2.schema.items()) c2.close() def test_read(self): c2 = fiona.open(str(self.dir.join("write_test.shp")), "r") f2 = next(iter(c2)) assert self.f.properties == f2.properties c2.close() def test_read_after_close(self): c2 = fiona.open(str(self.dir.join("write_test.shp")), "r") self.c.close() f2 = next(iter(c2)) assert self.f.properties == f2.properties c2.close() Fiona-1.10.1/tests/test_non_counting_layer.py000066400000000000000000000017031467206072700213110ustar00rootroot00000000000000import unittest import pytest import fiona from fiona.errors import FionaDeprecationWarning @pytest.mark.usefixtures('uttc_path_gpx') class TestNonCountingLayer(unittest.TestCase): def setUp(self): self.c = fiona.open(self.path_gpx, "r", layer="track_points") def tearDown(self): self.c.close() def test_len_fail(self): with pytest.raises(TypeError): len(self.c) def test_list(self): features = list(self.c) assert len(features) == 19 def test_getitem(self): self.c[2] def test_fail_getitem_negative_index(self): with pytest.raises(IndexError): self.c[-1] def test_slice(self): with pytest.warns(FionaDeprecationWarning): features = self.c[2:5] assert len(features) == 3 def test_warn_slice_negative_index(self): with pytest.warns((FionaDeprecationWarning, RuntimeWarning)): self.c[2:-4] Fiona-1.10.1/tests/test_open.py000066400000000000000000000032021467206072700163520ustar00rootroot00000000000000"""Tests of file opening""" import io import os import pytest import fiona from fiona.crs import CRS from fiona.errors import DriverError from fiona.model import Feature def test_open_shp(path_coutwildrnp_shp): """Open a shapefile""" assert fiona.open(path_coutwildrnp_shp) def test_open_filename_with_exclamation(data_dir): path = os.path.relpath(os.path.join(data_dir, "!test.geojson")) assert os.path.exists(path), "Missing test data" assert fiona.open(path), "Failed to open !test.geojson" def test_write_memfile_crs_wkt(): example_schema = { "geometry": "Point", "properties": [("title", "str")], } example_features = [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [0.0, 0.0]}, "properties": {"title": "One"}, } ), Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [1.0, 2.0]}, "properties": {"title": "Two"}, } ), Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": [3.0, 4.0]}, "properties": {"title": "Three"}, } ), ] with io.BytesIO() as fd: with fiona.open( fd, "w", driver="GPKG", schema=example_schema, crs_wkt=CRS.from_epsg(32611).to_wkt(), ) as dst: dst.writerecords(example_features) fd.seek(0) with fiona.open(fd) as src: assert src.driver == "GPKG" assert src.crs == "EPSG:32611" Fiona-1.10.1/tests/test_profile.py000066400000000000000000000011301467206072700170470ustar00rootroot00000000000000import os import re import fiona from .conftest import WGS84PATTERN def test_profile(path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp) as src: assert re.match(WGS84PATTERN, src.crs_wkt) def test_profile_creation_wkt(tmpdir, path_coutwildrnp_shp): outfilename = str(tmpdir.join("test.shp")) with fiona.open(path_coutwildrnp_shp) as src: profile = src.meta profile['crs'] = 'bogus' with fiona.open(outfilename, 'w', **profile) as dst: assert dst.crs == {'init': 'epsg:4326'} assert re.match(WGS84PATTERN, dst.crs_wkt) Fiona-1.10.1/tests/test_props.py000066400000000000000000000111441467206072700165600ustar00rootroot00000000000000import json import os.path import tempfile import pytest import fiona from fiona import prop_type, prop_width from fiona.model import Feature from .conftest import gdal_version def test_width_str(): assert prop_width("str:254") == 254 assert prop_width("str") == 80 def test_width_other(): assert prop_width("int") == None assert prop_width("float") == None assert prop_width("date") == None def test_types(): assert prop_type("str:254") == str assert prop_type("str") == str assert isinstance(0, prop_type("int")) assert isinstance(0.0, prop_type("float")) assert prop_type("date") == str @pytest.mark.xfail(not gdal_version.at_least("3.5"), reason="Requires at least GDAL 3.5.0") def test_read_json_object_properties(): """JSON object properties are properly serialized""" data = """ { "type": "FeatureCollection", "features": [ { "geometry": { "type": "Polygon", "coordinates": [ [ [ 87.33588, 43.53139 ], [ 87.33588, 45.66894 ], [ 90.27542, 45.66894 ], [ 90.27542, 43.53139 ], [ 87.33588, 43.53139 ] ] ] }, "type": "Feature", "properties": { "upperLeftCoordinate": { "latitude": 45.66894, "longitude": 87.91166 }, "tricky": "{gotcha" } } ] } """ tmpdir = tempfile.mkdtemp() filename = os.path.join(tmpdir, "test.json") with open(filename, "w") as f: f.write(data) with fiona.open(filename) as src: ftr = next(iter(src)) props = ftr["properties"] assert props["upperLeftCoordinate"]["latitude"] == 45.66894 assert props["upperLeftCoordinate"]["longitude"] == 87.91166 assert props["tricky"] == "{gotcha" @pytest.mark.xfail(not gdal_version.at_least("3.5"), reason="Requires at least GDAL 3.5.0") def test_write_json_object_properties(): """Python object properties are properly serialized""" data = """ { "type": "FeatureCollection", "features": [ { "geometry": { "type": "Polygon", "coordinates": [ [ [ 87.33588, 43.53139 ], [ 87.33588, 45.66894 ], [ 90.27542, 45.66894 ], [ 90.27542, 43.53139 ], [ 87.33588, 43.53139 ] ] ] }, "type": "Feature", "properties": { "upperLeftCoordinate": { "latitude": 45.66894, "longitude": 87.91166 }, "tricky": "{gotcha" } } ] } """ data = Feature.from_dict(**json.loads(data)["features"][0]) tmpdir = tempfile.mkdtemp() filename = os.path.join(tmpdir, "test.json") with fiona.open( filename, "w", driver="GeoJSON", schema={ "geometry": "Polygon", "properties": {"upperLeftCoordinate": "str", "tricky": "str"}, }, ) as dst: dst.write(data) with fiona.open(filename) as src: ftr = next(iter(src)) props = ftr["properties"] assert props["upperLeftCoordinate"]["latitude"] == 45.66894 assert props["upperLeftCoordinate"]["longitude"] == 87.91166 assert props["tricky"] == "{gotcha" def test_json_prop_decode_non_geojson_driver(): feature = Feature.from_dict( **{ "type": "Feature", "properties": { "ulc": {"latitude": 45.66894, "longitude": 87.91166}, "tricky": "{gotcha", }, "geometry": {"type": "Point", "coordinates": [10, 15]}, } ) meta = { "crs": "EPSG:4326", "driver": "ESRI Shapefile", "schema": { "geometry": "Point", "properties": {"ulc": "str:255", "tricky": "str:255"}, }, } tmpdir = tempfile.mkdtemp() filename = os.path.join(tmpdir, "test.json") with fiona.open(filename, "w", **meta) as dst: dst.write(feature) with fiona.open(filename) as src: actual = next(iter(src)) assert isinstance(actual["properties"]["ulc"], str) a = json.loads(actual["properties"]["ulc"]) e = json.loads(actual["properties"]["ulc"]) assert e == a assert actual["properties"]["tricky"].startswith("{") Fiona-1.10.1/tests/test_pyopener.py000066400000000000000000000141511467206072700172570ustar00rootroot00000000000000"""Tests of the Python opener VSI plugin.""" import io import os import fsspec import pytest import fiona from fiona.model import Feature def test_opener_io_open(path_grenada_geojson): """Use io.open as opener.""" with fiona.open(path_grenada_geojson, opener=io.open) as colxn: profile = colxn.profile assert profile["driver"] == "GeoJSON" assert len(colxn) == 1 def test_opener_fsspec_zip_fs(): """Use fsspec zip filesystem as opener.""" fs = fsspec.filesystem("zip", fo="tests/data/coutwildrnp.zip") with fiona.open("coutwildrnp.shp", opener=fs) as colxn: profile = colxn.profile assert profile["driver"] == "ESRI Shapefile" assert len(colxn) == 67 assert colxn.schema["geometry"] == "Polygon" assert "AGBUR" in colxn.schema["properties"] def test_opener_fsspec_zip_http_fs(): """Use fsspec zip+http filesystem as opener.""" fs = fsspec.filesystem( "zip", target_protocol="http", fo="https://github.com/Toblerity/Fiona/files/11151652/coutwildrnp.zip", ) with fiona.open("coutwildrnp.shp", opener=fs) as colxn: profile = colxn.profile assert profile["driver"] == "ESRI Shapefile" assert len(colxn) == 67 assert colxn.schema["geometry"] == "Polygon" assert "AGBUR" in colxn.schema["properties"] def test_opener_tiledb_file(): """Use tiledb vfs as opener.""" tiledb = pytest.importorskip("tiledb") fs = tiledb.VFS() with fiona.open("tests/data/coutwildrnp.shp", opener=fs) as colxn: profile = colxn.profile assert profile["driver"] == "ESRI Shapefile" assert len(colxn) == 67 assert colxn.schema["geometry"] == "Polygon" assert "AGBUR" in colxn.schema["properties"] def test_opener_fsspec_fs_write(tmp_path): """Write a feature via an fsspec fs opener.""" schema = {"geometry": "Point", "properties": {"zero": "int"}} feature = Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0, 0)}, "properties": {"zero": "0"}, } ) fs = fsspec.filesystem("file") outputfile = tmp_path.joinpath("test.shp") with fiona.open( str(outputfile), "w", driver="ESRI Shapefile", schema=schema, crs="OGC:CRS84", opener=fs, ) as collection: collection.write(feature) assert len(collection) == 1 assert collection.crs == "OGC:CRS84" def test_threads_context(): import io from threading import Thread def target(): with fiona.open("tests/data/coutwildrnp.shp", opener=io.open) as colxn: print(colxn.profile) assert len(colxn) == 67 thread = Thread(target=target) thread.start() thread.join() def test_overwrite(data): """Opener can overwrite data.""" schema = {"geometry": "Point", "properties": {"zero": "int"}} feature = Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0, 0)}, "properties": {"zero": "0"}, } ) fs = fsspec.filesystem("file") outputfile = os.path.join(str(data), "coutwildrnp.shp") with fiona.open( str(outputfile), "w", driver="ESRI Shapefile", schema=schema, crs="OGC:CRS84", opener=fs, ) as collection: collection.write(feature) assert len(collection) == 1 assert collection.crs == "OGC:CRS84" def test_opener_fsspec_zip_fs_listlayers(): """Use fsspec zip filesystem as opener for listlayers().""" fs = fsspec.filesystem("zip", fo="tests/data/coutwildrnp.zip") assert fiona.listlayers("coutwildrnp.shp", opener=fs) == ["coutwildrnp"] def test_opener_fsspec_zip_fs_listdir(): """Use fsspec zip filesystem as opener for listdir().""" fs = fsspec.filesystem("zip", fo="tests/data/coutwildrnp.zip") listing = fiona.listdir("/", opener=fs) assert len(listing) == 4 assert set( ["coutwildrnp.shp", "coutwildrnp.dbf", "coutwildrnp.shx", "coutwildrnp.prj"] ) & set(listing) def test_opener_fsspec_file_fs_listdir(): """Use fsspec file filesystem as opener for listdir().""" fs = fsspec.filesystem("file") listing = fiona.listdir("tests/data", opener=fs) assert len(listing) >= 33 assert set( ["coutwildrnp.shp", "coutwildrnp.dbf", "coutwildrnp.shx", "coutwildrnp.prj"] ) & set(listing) def test_opener_fsspec_file_remove(data): """Opener can remove data.""" fs = fsspec.filesystem("file") listing = fiona.listdir(str(data), opener=fs) assert len(listing) == 4 outputfile = os.path.join(str(data), "coutwildrnp.shp") fiona.remove(outputfile) listing = fiona.listdir(str(data), opener=fs) assert len(listing) == 0 assert not set( ["coutwildrnp.shp", "coutwildrnp.dbf", "coutwildrnp.shx", "coutwildrnp.prj"] ) & set(listing) def test_opener_tiledb_vfs_listdir(): """Use tiledb VFS as opener for listdir().""" tiledb = pytest.importorskip("tiledb") fs = tiledb.VFS() listing = fiona.listdir("tests/data", opener=fs) assert len(listing) >= 33 assert set( ["coutwildrnp.shp", "coutwildrnp.dbf", "coutwildrnp.shx", "coutwildrnp.prj"] ) & set(listing) def test_opener_interface(): """Demonstrate implementation of a custom opener.""" import pathlib from fiona.abc import FileContainer class CustomContainer: """GDAL's VSI ReadDir() uses 5 of FileContainer's methods.""" def isdir(self, path): return pathlib.Path(path).is_dir() def isfile(self, path): return pathlib.Path(path).is_file() def ls(self, path): return list(pathlib.Path(path).iterdir()) def mtime(self, path): return pathlib.Path(path).stat().st_mtime def size(self, path): return pathlib.Path(path).stat().st_size FileContainer.register(CustomContainer) listing = fiona.listdir("tests/data", opener=CustomContainer()) assert len(listing) >= 33 assert set( ["coutwildrnp.shp", "coutwildrnp.dbf", "coutwildrnp.shx", "coutwildrnp.prj"] ) & set(listing) Fiona-1.10.1/tests/test_read_drivers.py000066400000000000000000000012141467206072700200630ustar00rootroot00000000000000import pytest import fiona from fiona.errors import FionaValueError def test_read_fail(path_coutwildrnp_shp): with pytest.raises(FionaValueError): fiona.open(path_coutwildrnp_shp, driver='GeoJSON') with pytest.raises(FionaValueError): fiona.open(path_coutwildrnp_shp, enabled_drivers=['GeoJSON']) def test_read(path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp, driver='ESRI Shapefile') as src: assert src.driver == 'ESRI Shapefile' with fiona.open( path_coutwildrnp_shp, enabled_drivers=['GeoJSON', 'ESRI Shapefile']) as src: assert src.driver == 'ESRI Shapefile' Fiona-1.10.1/tests/test_remove.py000066400000000000000000000073401467206072700167150ustar00rootroot00000000000000import logging import sys import os import itertools from .conftest import requires_gpkg import pytest import fiona from fiona.errors import DatasetDeleteError from fiona.model import Feature def create_sample_data(filename, driver, **extra_meta): meta = {"driver": driver, "schema": {"geometry": "Point", "properties": {}}} meta.update(extra_meta) with fiona.open(filename, "w", **meta) as dst: dst.write( Feature.from_dict( **{ "geometry": { "type": "Point", "coordinates": (0, 0), }, "properties": {}, } ) ) assert os.path.exists(filename) drivers = ["ESRI Shapefile", "GeoJSON"] kinds = ["path", "collection"] specify_drivers = [True, False] test_data = itertools.product(drivers, kinds, specify_drivers) @pytest.mark.parametrize("driver, kind, specify_driver", test_data) def test_remove(tmpdir, kind, driver, specify_driver): """Test various dataset removal operations""" extension = {"ESRI Shapefile": "shp", "GeoJSON": "json"}[driver] filename = f"delete_me.{extension}" output_filename = str(tmpdir.join(filename)) create_sample_data(output_filename, driver=driver) if kind == "collection": to_delete = fiona.open(output_filename, "r") else: to_delete = output_filename assert os.path.exists(output_filename) if specify_driver: fiona.remove(to_delete, driver=driver) else: fiona.remove(to_delete) assert not os.path.exists(output_filename) def test_remove_nonexistent(tmpdir): """Attempting to remove a file that does not exist results in an OSError""" filename = str(tmpdir.join("does_not_exist.shp")) assert not os.path.exists(filename) with pytest.raises(OSError): fiona.remove(filename) @requires_gpkg def test_remove_layer(tmpdir): filename = str(tmpdir.join("a_filename.gpkg")) create_sample_data(filename, "GPKG", layer="layer1") create_sample_data(filename, "GPKG", layer="layer2") create_sample_data(filename, "GPKG", layer="layer3") create_sample_data(filename, "GPKG", layer="layer4") assert fiona.listlayers(filename) == ["layer1", "layer2", "layer3", "layer4"] # remove by index fiona.remove(filename, layer=2) assert fiona.listlayers(filename) == ["layer1", "layer2", "layer4"] # remove by name fiona.remove(filename, layer="layer2") assert fiona.listlayers(filename) == ["layer1", "layer4"] # remove by negative index fiona.remove(filename, layer=-1) assert fiona.listlayers(filename) == ["layer1"] # invalid layer name with pytest.raises(ValueError): fiona.remove(filename, layer="invalid_layer_name") # invalid layer index with pytest.raises(DatasetDeleteError): fiona.remove(filename, layer=999) def test_remove_layer_shapefile(tmpdir): """Removal of layer in shapefile actually deletes the datasource""" filename = str(tmpdir.join("a_filename.shp")) create_sample_data(filename, "ESRI Shapefile") fiona.remove(filename, layer=0) assert not os.path.exists(filename) def test_remove_layer_geojson(tmpdir): """Removal of layers is not supported by GeoJSON driver The reason for failure is slightly different between GDAL 2.2+ and < 2.2. With < 2.2 the datasource will fail to open in write mode (OSError), while with 2.2+ the datasource will open but the removal operation will fail (not supported). """ filename = str(tmpdir.join("a_filename.geojson")) create_sample_data(filename, "GeoJSON") with pytest.raises((RuntimeError, OSError)): fiona.remove(filename, layer=0) assert os.path.exists(filename) Fiona-1.10.1/tests/test_revolvingdoor.py000066400000000000000000000006221467206072700203130ustar00rootroot00000000000000# Test of opening and closing and opening import fiona def test_write_revolving_door(tmpdir, path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp) as src: meta = src.meta features = list(src) shpname = str(tmpdir.join('foo.shp')) with fiona.open(shpname, 'w', **meta) as dst: dst.writerecords(features) with fiona.open(shpname) as src: pass Fiona-1.10.1/tests/test_rfc3339.py000066400000000000000000000036261467206072700165170ustar00rootroot00000000000000"""Tests for Fiona's RFC 3339 support.""" import re import pytest from fiona.rfc3339 import parse_date, parse_datetime, parse_time from fiona.rfc3339 import group_accessor, pattern_date class TestDateParse: def test_yyyymmdd(self): assert parse_date("2012-01-29") == (2012, 1, 29, 0, 0, 0, 0.0, None) def test_error(self): with pytest.raises(ValueError): parse_date("xxx") class TestTimeParse: def test_hhmmss(self): assert parse_time("10:11:12") == (0, 0, 0, 10, 11, 12, 0.0, None) def test_hhmm(self): assert parse_time("10:11") == (0, 0, 0, 10, 11, 0, 0.0, None) def test_hhmmssff(self): assert parse_time("10:11:12.42") == (0, 0, 0, 10, 11, 12, 0.42*1000000, None) def test_hhmmssz(self): assert parse_time("10:11:12Z") == (0, 0, 0, 10, 11, 12, 0.0, None) def test_hhmmssoff(self): assert parse_time("10:11:12-01:30") == (0, 0, 0, 10, 11, 12, 0.0, -90) def test_hhmmssoff2(self): assert parse_time("10:11:12+01:30") == (0, 0, 0, 10, 11, 12, 0.0, 90) def test_error(self): with pytest.raises(ValueError): parse_time("xxx") class TestDatetimeParse: def test_yyyymmdd(self): assert ( parse_datetime("2012-01-29T10:11:12") == (2012, 1, 29, 10, 11, 12, 0.0, None)) def test_yyyymmddTZ(self): assert ( parse_datetime("2012-01-29T10:11:12+01:30") == (2012, 1, 29, 10, 11, 12, 0.0, 90)) def test_yyyymmddTZ2(self): assert ( parse_datetime("2012-01-29T10:11:12-01:30") == (2012, 1, 29, 10, 11, 12, 0.0, -90)) def test_error(self): with pytest.raises(ValueError): parse_datetime("xxx") def test_group_accessor_indexerror(): match = re.search(pattern_date, '2012-01-29') g = group_accessor(match) assert g.group(-1) == 0 assert g.group(6) == 0 Fiona-1.10.1/tests/test_rfc64_tin.py000066400000000000000000000034331467206072700172150ustar00rootroot00000000000000"""Tests of features related to GDAL RFC 64 See https://trac.osgeo.org/gdal/wiki/rfc64_triangle_polyhedralsurface_tin. """ import fiona from fiona.model import Geometry def _test_tin(geometry: Geometry) -> None: """Test if TIN (((0 0 0, 0 0 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 0 0 0))) is correctly converted to MultiPolygon. """ assert geometry["type"] == "MultiPolygon" assert geometry["coordinates"] == [ [[(0.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, 1.0, 0.0), (0.0, 0.0, 0.0)]], [[(0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0), (0.0, 0.0, 0.0)]], ] def _test_triangle(geometry: Geometry) -> None: """Test if TRIANGLE((0 0 0,0 1 0,1 1 0,0 0 0)) is correctly converted to MultiPolygon.""" assert geometry["type"] == "Polygon" assert geometry["coordinates"] == [ [(0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0), (0.0, 0.0, 0.0)] ] def test_tin_shp(path_test_tin_shp): """Convert TIN to MultiPolygon""" with fiona.open(path_test_tin_shp) as col: assert col.schema["geometry"] == "Unknown" features = list(col) assert len(features) == 1 _test_tin(features[0]["geometry"]) def test_tin_csv(path_test_tin_csv): """Convert TIN to MultiPolygon and Triangle to Polygon""" with fiona.open(path_test_tin_csv) as col: assert col.schema["geometry"] == "Unknown" feature1 = next(col) _test_tin(feature1["geometry"]) feature2 = next(col) _test_triangle(feature2["geometry"]) feature3 = next(col) assert feature3["geometry"]["type"] == "GeometryCollection" assert len(feature3["geometry"]["geometries"]) == 2 _test_tin(feature3["geometry"]["geometries"][0]) _test_triangle(feature3["geometry"]["geometries"][1]) Fiona-1.10.1/tests/test_schema.py000066400000000000000000000327731467206072700166700ustar00rootroot00000000000000import os import tempfile import pytest import fiona from fiona.drvsupport import driver_mode_mingdal from fiona.env import GDALVersion from fiona.errors import SchemaError, UnsupportedGeometryTypeError, DriverSupportError from fiona.model import Feature from fiona.schema import NAMED_FIELD_TYPES, normalize_field_type from .conftest import get_temp_filename from .conftest import requires_only_gdal1, requires_gdal2 def test_schema_ordering_items(tmpdir): name = str(tmpdir.join("test_scheme.shp")) items = [("title", "str:80"), ("date", "date")] with fiona.open( name, "w", driver="ESRI Shapefile", schema={"geometry": "LineString", "properties": items}, ) as c: assert list(c.schema["properties"].items()) == items with fiona.open(name) as c: assert list(c.schema["properties"].items()) == items def test_shapefile_schema(tmpdir): name = str(tmpdir.join("test_schema.shp")) items = sorted( { "AWATER10": "float", "CLASSFP10": "str", "ZipCodeType": "str", "EstimatedPopulation": "float", "LocationType": "str", "ALAND10": "float", "TotalWages": "float", "FUNCSTAT10": "str", "Long": "float", "City": "str", "TaxReturnsFiled": "float", "State": "str", "Location": "str", "GSrchCnt": "float", "INTPTLAT10": "str", "Lat": "float", "MTFCC10": "str", "Decommisioned": "str", "GEOID10": "str", "INTPTLON10": "str", }.items() ) with fiona.open( name, "w", driver="ESRI Shapefile", schema={"geometry": "Polygon", "properties": items}, ) as c: assert list(c.schema["properties"].items()) == items c.write( Feature.from_dict( **{ "geometry": { "coordinates": [ [ (-117.882442, 33.783633), (-117.882284, 33.783817), (-117.863348, 33.760016), (-117.863478, 33.760016), (-117.863869, 33.760017), (-117.864, 33.760017999999995), (-117.864239, 33.760019), (-117.876608, 33.755769), (-117.882886, 33.783114), (-117.882688, 33.783345), (-117.882639, 33.783401999999995), (-117.88259, 33.78346), (-117.882442, 33.783633), ] ], "type": "Polygon", }, "id": "1", "properties": { "ALAND10": 8819240.0, "AWATER10": 309767.0, "CLASSFP10": "B5", "City": "SANTA ANA", "Decommisioned": False, "EstimatedPopulation": 27773.0, "FUNCSTAT10": "S", "GEOID10": "92706", "GSrchCnt": 0.0, "INTPTLAT10": "+33.7653010", "INTPTLON10": "-117.8819759", "Lat": 33.759999999999998, "Location": "NA-US-CA-SANTA ANA", "LocationType": "PRIMARY", "Long": -117.88, "MTFCC10": "G6350", "State": "CA", "TaxReturnsFiled": 14635.0, "TotalWages": 521280485.0, "ZipCodeType": "STANDARD", }, "type": "Feature", } ) ) assert len(c) == 1 with fiona.open(name) as c: assert list(c.schema["properties"].items()) == sorted( [ ("AWATER10", "float:24.15"), ("CLASSFP10", "str:80"), ("ZipCodeTyp", "str:80"), ("EstimatedP", "float:24.15"), ("LocationTy", "str:80"), ("ALAND10", "float:24.15"), ("INTPTLAT10", "str:80"), ("FUNCSTAT10", "str:80"), ("Long", "float:24.15"), ("City", "str:80"), ("TaxReturns", "float:24.15"), ("State", "str:80"), ("Location", "str:80"), ("GSrchCnt", "float:24.15"), ("TotalWages", "float:24.15"), ("Lat", "float:24.15"), ("MTFCC10", "str:80"), ("INTPTLON10", "str:80"), ("GEOID10", "str:80"), ("Decommisio", "str:80"), ] ) f = next(iter(c)) assert f.properties["EstimatedP"] == 27773.0 def test_field_truncation_issue177(tmpdir): name = str(tmpdir.join("output.shp")) kwargs = { "driver": "ESRI Shapefile", "crs": "EPSG:4326", "schema": {"geometry": "Point", "properties": [("a_fieldname", "float")]}, } with fiona.open(name, "w", **kwargs) as dst: rec = {} rec["geometry"] = {"type": "Point", "coordinates": (0, 0)} rec["properties"] = {"a_fieldname": 3.0} dst.write(Feature.from_dict(**rec)) with fiona.open(name) as src: first = next(iter(src)) assert first.geometry.type == "Point" assert first.geometry.coordinates == (0, 0) assert first.properties["a_fieldnam"] == 3.0 def test_unsupported_geometry_type(): tmpdir = tempfile.mkdtemp() tmpfile = os.path.join(tmpdir, "test-test-geom.shp") profile = { "driver": "ESRI Shapefile", "schema": {"geometry": "BOGUS", "properties": {}}, } with pytest.raises(UnsupportedGeometryTypeError): fiona.open(tmpfile, "w", **profile) @pytest.mark.parametrize("x", list(range(1, 10))) def test_normalize_int32(x): assert normalize_field_type(f"int:{x}") == "int32" @requires_gdal2 @pytest.mark.parametrize("x", list(range(10, 20))) def test_normalize_int64(x): assert normalize_field_type(f"int:{x}") == "int64" @pytest.mark.parametrize("x", list(range(0, 20))) def test_normalize_str(x): assert normalize_field_type(f"str:{x}") == "str" def test_normalize_bool(): assert normalize_field_type("bool") == "bool" def test_normalize_float(): assert normalize_field_type("float:25.8") == "float" def test_normalize_(): assert normalize_field_type("float:25.8") == "float" def generate_field_types(): """ Produce a unique set of field types in a consistent order. This ensures that tests are able to run in parallel. """ types = set(NAMED_FIELD_TYPES.keys()) return list(sorted(types)) @pytest.mark.parametrize("x", generate_field_types()) def test_normalize_std(x): assert normalize_field_type(x) == x def test_normalize_error(): with pytest.raises(SchemaError): assert normalize_field_type("thingy") @requires_only_gdal1 @pytest.mark.parametrize("field_type", ["time", "datetime"]) def test_check_schema_driver_support_shp(tmpdir, field_type): with pytest.raises(DriverSupportError): name = str(tmpdir.join("test_scheme.shp")) items = [("field1", field_type)] with fiona.open( name, "w", driver="ESRI Shapefile", schema={"geometry": "LineString", "properties": items}, ) as c: pass @requires_only_gdal1 def test_check_schema_driver_support_gpkg(tmpdir): with pytest.raises(DriverSupportError): name = str(tmpdir.join("test_scheme.gpkg")) items = [("field1", "time")] with fiona.open( name, "w", driver="GPKG", schema={"geometry": "LineString", "properties": items}, ) as c: pass @pytest.mark.parametrize("driver", ["GPKG", "GeoJSON"]) def test_geometry_only_schema_write(tmpdir, driver): schema = { "geometry": "Polygon", # No properties defined here. } record = Feature.from_dict( **{ "geometry": { "type": "Polygon", "coordinates": [ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)] ], } } ) path = str(tmpdir.join(get_temp_filename(driver))) with fiona.open(path, mode="w", driver=driver, schema=schema) as c: c.write(record) with fiona.open(path, mode="r", driver=driver) as c: data = [f for f in c] assert len(data) == 1 assert len(data[0].properties) == 0 assert data[0].geometry.type == record.geometry["type"] @pytest.mark.parametrize("driver", ["GPKG", "GeoJSON"]) def test_geometry_only_schema_update(tmpdir, driver): # Guard unsupported drivers if driver in driver_mode_mingdal["a"] and GDALVersion.runtime() < GDALVersion( *driver_mode_mingdal["a"][driver][:2] ): return schema = { "geometry": "Polygon", # No properties defined here. } record1 = Feature.from_dict( **{ "geometry": { "type": "Polygon", "coordinates": [ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)] ], } } ) record2 = Feature.from_dict( **{ "geometry": { "type": "Polygon", "coordinates": [ [(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (2.0, 0.0), (0.0, 0.0)] ], } } ) path = str(tmpdir.join(get_temp_filename(driver))) # Create file with fiona.open(path, mode="w", driver=driver, schema=schema) as c: c.write(record1) # Append record with fiona.open(path, mode="a", driver=driver) as c: c.write(record2) with fiona.open(path, mode="r", driver=driver) as c: data = [f for f in c] assert len(data) == 2 for f in data: assert len(f.properties) == 0 assert data[0].geometry.type == record1.geometry["type"] assert data[1].geometry.type == record2.geometry["type"] @pytest.mark.parametrize("driver", ["GPKG", "GeoJSON"]) def test_property_only_schema_write(tmpdir, driver): schema = { # No geometry defined here. "properties": {"prop1": "str"} } record1 = Feature.from_dict(**{"properties": {"prop1": "one"}}) path = str(tmpdir.join(get_temp_filename(driver))) with fiona.open(path, mode="w", driver=driver, schema=schema) as c: c.write(record1) with fiona.open(path, mode="r", driver=driver) as c: data = [f for f in c] assert len(data) == 1 assert len(data[0].properties) == 1 assert "prop1" in data[0].properties and data[0].properties["prop1"] == "one" for f in data: assert f.geometry is None @pytest.mark.parametrize("driver", ["GPKG", "GeoJSON"]) def test_property_only_schema_update(tmpdir, driver): # Guard unsupported drivers if driver in driver_mode_mingdal["a"] and GDALVersion.runtime() < GDALVersion( *driver_mode_mingdal["a"][driver][:2] ): return schema = { # No geometry defined here. "properties": {"prop1": "str"} } record1 = Feature.from_dict(**{"properties": {"prop1": "one"}}) record2 = Feature.from_dict(**{"properties": {"prop1": "two"}}) path = str(tmpdir.join(get_temp_filename(driver))) # Create file with fiona.open(path, mode="w", driver=driver, schema=schema) as c: c.write(record1) # Append record with fiona.open(path, mode="a", driver=driver) as c: c.write(record2) with fiona.open(path, mode="r", driver=driver) as c: data = [f for f in c] assert len(data) == 2 for f in data: assert len(f.properties) == 1 assert f.geometry is None assert "prop1" in data[0].properties and data[0].properties["prop1"] == "one" assert "prop1" in data[1].properties and data[1].properties["prop1"] == "two" def test_schema_default_fields_wrong_type(tmpdir): """Test for SchemaError if a default field is specified with a different type""" name = str(tmpdir.join("test.gpx")) schema = { "properties": {"ele": "str", "time": "datetime"}, "geometry": "Point", } with pytest.raises(SchemaError): with fiona.open(name, "w", driver="GPX", schema=schema) as c: pass def test_schema_string_list(tmp_path): output_file = tmp_path / "fio_test.geojson" schema = { "properties": { "time_range": "str", }, "geometry": "Point", } with fiona.open( output_file, "w", driver="GeoJSON", schema=schema, crs="EPSG:4326" ) as fds: fds.writerecords( [ { "id": 1, "geometry": {"type": "Point", "coordinates": [0.0, 0.0]}, "properties": { "time_range": '["2020-01-01", "2020-01-02"]', }, }, ] ) with fiona.open(output_file) as fds: assert fds.schema["properties"] == {"time_range": "List[str]"} layers = list(fds) assert layers[0]["properties"] == { "time_range": ["2020-01-01", "2020-01-02"] } Fiona-1.10.1/tests/test_schema_geom.py000066400000000000000000000156461467206072700176770ustar00rootroot00000000000000""" Tests related to the validation of feature geometry types against the schema. """ import fiona import pytest from fiona.errors import GeometryTypeValidationError, UnsupportedGeometryTypeError from fiona.model import Feature @pytest.fixture def filename_shp(tmpdir): return str(tmpdir.join("example.shp")) @pytest.fixture def filename_json(tmpdir): return str(tmpdir.join("example.json")) properties = {"name": "str"} PROPERTIES = {"name": "example"} POINT = {"type": "Point", "coordinates": (1.0, 2.0)} LINESTRING = {"type": "LineString", "coordinates": [(1.0, 2.0), (3.0, 4.0)]} POLYGON = {"type": "Polygon", "coordinates": [[(0.0, 0.0), (1.0, 1.0), (0.0, 0.1)]]} MULTILINESTRING = { "type": "MultiLineString", "coordinates": [[(0.0, 0.0), (1.0, 1.0)], [(1.0, 2.0), (3.0, 4.0)]], } GEOMETRYCOLLECTION = { "type": "GeometryCollection", "geometries": [POINT, LINESTRING, POLYGON], } INVALID = {"type": "InvalidType", "coordinates": (42.0, 43.0)} POINT_3D = {"type": "Point", "coordinates": (1.0, 2.0, 3.0)} def write_point(collection): feature = Feature.from_dict(**{"geometry": POINT, "properties": PROPERTIES}) collection.write(feature) def write_linestring(collection): feature = Feature.from_dict(**{"geometry": LINESTRING, "properties": PROPERTIES}) collection.write(feature) def write_polygon(collection): feature = Feature.from_dict(**{"geometry": POLYGON, "properties": PROPERTIES}) collection.write(feature) def write_invalid(collection): feature = Feature.from_dict(**{"geometry": INVALID, "properties": PROPERTIES}) collection.write(feature) def write_multilinestring(collection): feature = Feature.from_dict( **{"geometry": MULTILINESTRING, "properties": PROPERTIES} ) collection.write(feature) def write_point_3d(collection): feature = Feature.from_dict(**{"geometry": POINT_3D, "properties": PROPERTIES}) collection.write(feature) def write_geometrycollection(collection): feature = Feature.from_dict( **{"geometry": GEOMETRYCOLLECTION, "properties": PROPERTIES} ) collection.write(feature) def write_null(collection): feature = Feature.from_dict(**{"geometry": None, "properties": PROPERTIES}) collection.write(feature) def test_point(filename_shp): schema = {"geometry": "Point", "properties": properties} with fiona.open( filename_shp, "w", driver="ESRI Shapefile", schema=schema ) as collection: write_point(collection) write_point_3d(collection) write_null(collection) with pytest.raises(GeometryTypeValidationError): write_linestring(collection) with pytest.raises(GeometryTypeValidationError): write_invalid(collection) def test_multi_type(filename_json): schema = {"geometry": ("Point", "LineString"), "properties": properties} with fiona.open(filename_json, "w", driver="GeoJSON", schema=schema) as collection: write_point(collection) write_linestring(collection) write_null(collection) with pytest.raises(GeometryTypeValidationError): write_polygon(collection) with pytest.raises(GeometryTypeValidationError): write_invalid(collection) def test_unknown(filename_json): """Reading and writing layers with "Unknown" (i.e. any) geometry type""" # write a layer with a mixture of geometry types schema = {"geometry": "Unknown", "properties": properties} with fiona.open(filename_json, "w", driver="GeoJSON", schema=schema) as collection: write_point(collection) write_linestring(collection) write_polygon(collection) write_geometrycollection(collection) write_null(collection) with pytest.raises(GeometryTypeValidationError): write_invalid(collection) # copy the features to a new layer, reusing the layers metadata with fiona.open(filename_json, "r", driver="GeoJSON") as src: filename_dst = filename_json.replace(".json", "_v2.json") assert src.schema["geometry"] == "Unknown" with fiona.open(filename_dst, "w", **src.meta) as dst: dst.writerecords(src) def test_any(filename_json): schema = {"geometry": "Any", "properties": properties} with fiona.open(filename_json, "w", driver="GeoJSON", schema=schema) as collection: write_point(collection) write_linestring(collection) write_polygon(collection) write_geometrycollection(collection) write_null(collection) with pytest.raises(GeometryTypeValidationError): write_invalid(collection) def test_broken(filename_json): schema = {"geometry": "NOT_VALID", "properties": properties} with pytest.raises(UnsupportedGeometryTypeError): with fiona.open(filename_json, "w", driver="GeoJSON", schema=schema): pass def test_broken_list(filename_json): schema = { "geometry": ("Point", "LineString", "NOT_VALID"), "properties": properties, } with pytest.raises(UnsupportedGeometryTypeError): collection = fiona.open(filename_json, "w", driver="GeoJSON", schema=schema) def test_invalid_schema(filename_shp): """Features match schema but geometries not supported by driver""" schema = {"geometry": ("Point", "LineString"), "properties": properties} with fiona.open( filename_shp, "w", driver="ESRI Shapefile", schema=schema ) as collection: write_linestring(collection) with pytest.raises(RuntimeError): # ESRI Shapefile can only store a single geometry type write_point(collection) def test_esri_multi_geom(filename_shp): """ESRI Shapefile doesn't differentiate between LineString/MultiLineString""" schema = {"geometry": "LineString", "properties": properties} with fiona.open( filename_shp, "w", driver="ESRI Shapefile", schema=schema ) as collection: write_linestring(collection) write_multilinestring(collection) with pytest.raises(GeometryTypeValidationError): write_point(collection) def test_3d_schema_ignored(filename_json): schema = {"geometry": "3D Point", "properties": properties} with fiona.open(filename_json, "w", driver="GeoJSON", schema=schema) as collection: write_point(collection) write_point_3d(collection) def test_geometrycollection_schema(filename_json): schema = {"geometry": "GeometryCollection", "properties": properties} with fiona.open(filename_json, "w", driver="GeoJSON", schema=schema) as collection: write_geometrycollection(collection) def test_none_schema(filename_json): schema = {"geometry": None, "properties": properties} with fiona.open(filename_json, "w", driver="GeoJSON", schema=schema) as collection: write_null(collection) with pytest.raises(GeometryTypeValidationError): write_point(collection) with pytest.raises(GeometryTypeValidationError): write_linestring(collection) Fiona-1.10.1/tests/test_session.py000066400000000000000000000064071467206072700171060ustar00rootroot00000000000000"""Tests of the ogrext.Session class""" import pytest import fiona from fiona.errors import GDALVersionError, UnsupportedOperation from .conftest import gdal_version def test_get(path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp) as col: feat3 = col.get(2) assert feat3['properties']['NAME'] == 'Mount Zirkel Wilderness' @pytest.mark.parametrize("layer, namespace, tags", [ (None, None, {"test_tag1": "test_value1", "test_tag2": "test_value2"}), (None, "test", {"test_tag1": "test_value1", "test_tag2": "test_value2"}), (None, None, {}), (None, "test", {}), ("layer", None, {"test_tag1": "test_value1", "test_tag2": "test_value2"}), ("layer", "test", {"test_tag1": "test_value1", "test_tag2": "test_value2"}), ("layer", None, {}), ("layer", "test", {}), ]) @pytest.mark.skipif(gdal_version.major < 2, reason="Broken on GDAL 1.x") def test_update_tags(layer, namespace, tags, tmpdir): test_geopackage = str(tmpdir.join("test.gpkg")) schema = {'properties': {'CDATA1': 'str:254'}, 'geometry': 'Polygon'} with fiona.Env(), fiona.open( test_geopackage, "w", driver="GPKG", schema=schema, layer=layer) as gpkg: assert gpkg.tags() == {} gpkg.update_tags(tags, ns=namespace) with fiona.Env(), fiona.open(test_geopackage, layer=layer) as gpkg: assert gpkg.tags(ns=namespace) == tags if namespace is not None: assert gpkg.tags() == {} with pytest.raises(UnsupportedOperation): gpkg.update_tags({}, ns=namespace) @pytest.mark.parametrize("layer, namespace", [ (None, None), (None, "test"), ("test", None), ("test", "test"), ]) @pytest.mark.skipif(gdal_version.major < 2, reason="Broken on GDAL 1.x") def test_update_tag_item(layer, namespace, tmpdir): test_geopackage = str(tmpdir.join("test.gpkg")) schema = {'properties': {'CDATA1': 'str:254'}, 'geometry': 'Polygon'} with fiona.Env(), fiona.open( test_geopackage, "w", driver="GPKG", schema=schema, layer=layer) as gpkg: assert gpkg.get_tag_item("test_tag1", ns=namespace) is None gpkg.update_tag_item("test_tag1", "test_value1", ns=namespace) with fiona.Env(), fiona.open(test_geopackage, layer=layer) as gpkg: if namespace is not None: assert gpkg.get_tag_item("test_tag1") is None assert gpkg.get_tag_item("test_tag1", ns=namespace) == "test_value1" with pytest.raises(UnsupportedOperation): gpkg.update_tag_item("test_tag1", "test_value1", ns=namespace) @pytest.mark.skipif(gdal_version.major >= 2, reason="Only raises on GDAL 1.x") def test_gdal_version_error(tmpdir): test_geopackage = str(tmpdir.join("test.gpkg")) schema = {'properties': {'CDATA1': 'str:254'}, 'geometry': 'Polygon'} with fiona.Env(), fiona.open( test_geopackage, "w", driver="GPKG", schema=schema, layer="layer") as gpkg: with pytest.raises(GDALVersionError): gpkg.update_tags({"test_tag1": "test_value1"}, ns="test") with pytest.raises(GDALVersionError): gpkg.update_tag_item("test_tag1", "test_value1", ns="test") with pytest.raises(GDALVersionError): gpkg.tags() with pytest.raises(GDALVersionError): gpkg.get_tag_item("test_tag1") Fiona-1.10.1/tests/test_slice.py000066400000000000000000000123071467206072700165160ustar00rootroot00000000000000"""Note well: collection slicing is deprecated!""" import tempfile import shutil import os import pytest from fiona.env import GDALVersion import fiona from fiona.drvsupport import supported_drivers, _driver_supports_mode from fiona.errors import FionaDeprecationWarning from fiona.model import Feature from .conftest import get_temp_filename gdal_version = GDALVersion.runtime() def test_collection_get(path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp) as src: result = src[5] assert result.id == "5" def test_collection_slice(path_coutwildrnp_shp): with pytest.warns(FionaDeprecationWarning), fiona.open(path_coutwildrnp_shp) as src: results = src[:5] assert isinstance(results, list) assert len(results) == 5 assert results[4].id == "4" def test_collection_iterator_slice(path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp) as src: results = list(src.items(5)) assert len(results) == 5 k, v = results[4] assert k == 4 assert v.id == "4" def test_collection_iterator_next(path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp) as src: k, v = next(src.items(5, None)) assert k == 5 assert v.id == "5" @pytest.fixture( scope="module", params=[ driver for driver in supported_drivers if _driver_supports_mode(driver, "w") and driver not in {"DGN", "MapInfo File", "GPSTrackMaker", "GPX", "BNA", "DXF"} ], ) def slice_dataset_path(request): """Create temporary datasets for test_collection_iterator_items_slice()""" driver = request.param min_id = 0 max_id = 9 def get_schema(driver): special_schemas = { "CSV": {"geometry": None, "properties": {"position": "int"}} } return special_schemas.get( driver, {"geometry": "Point", "properties": {"position": "int"}}, ) def get_records(driver, range): special_records1 = { "CSV": [ Feature.from_dict(**{"geometry": None, "properties": {"position": i}}) for i in range ], "PCIDSK": [ Feature.from_dict( **{ "geometry": { "type": "Point", "coordinates": (0.0, float(i), 0.0), }, "properties": {"position": i}, } ) for i in range ], } return special_records1.get( driver, [ Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0.0, float(i))}, "properties": {"position": i}, } ) for i in range ], ) schema = get_schema(driver) records = get_records(driver, range(min_id, max_id + 1)) create_kwargs = {} if driver == "FlatGeobuf": create_kwargs["SPATIAL_INDEX"] = False tmpdir = tempfile.mkdtemp() path = os.path.join(tmpdir, get_temp_filename(driver)) with fiona.open( path, "w", driver=driver, crs="OGC:CRS84", schema=schema, **create_kwargs ) as c: c.writerecords(records) yield path shutil.rmtree(tmpdir) @pytest.mark.parametrize( "args", [ (0, 5, None), (1, 5, None), (-5, None, None), (-5, -1, None), (0, None, None), (5, None, None), (8, None, None), (9, None, None), (10, None, None), (0, 5, 2), (0, 5, 2), (1, 5, 2), (-5, None, 2), (-5, -1, 2), (0, None, 2), (0, 8, 2), (0, 9, 2), (0, 10, 2), (1, 8, 2), (1, 9, 2), (1, 10, 2), (1, None, 2), (5, None, 2), (5, None, -1), (5, None, -2), (5, None, None), (4, None, -2), (-1, -5, -1), (-5, None, -1), (0, 5, 1), (5, 15, 1), (15, 30, 1), (5, 0, -1), (15, 5, -1), (30, 15, -1), (0, 5, 2), (5, 15, 2), (15, 30, 2), (5, 0, -2), (15, 5, -2), (30, 15, -2), ], ) @pytest.mark.filterwarnings("ignore:.*OLC_FASTFEATURECOUNT*") @pytest.mark.filterwarnings("ignore:.*OLCFastSetNextByIndex*") def test_collection_iterator_items_slice(slice_dataset_path, args): """Test if c.items(start, stop, step) returns the correct features.""" start, stop, step = args min_id = 0 max_id = 9 positions = list(range(min_id, max_id + 1))[start:stop:step] with fiona.open(slice_dataset_path, "r") as c: items = list(c.items(start, stop, step)) assert len(items) == len(positions) record_positions = [int(item[1]["properties"]["position"]) for item in items] for expected_position, record_position in zip(positions, record_positions): assert expected_position == record_position def test_collection_iterator_keys_next(path_coutwildrnp_shp): with fiona.open(path_coutwildrnp_shp) as src: k = next(src.keys(5, None)) assert k == 5 Fiona-1.10.1/tests/test_snuggs.py000066400000000000000000000011131467206072700167160ustar00rootroot00000000000000# Python module tests """Tests of the snuggs module.""" import pytest # type: ignore from fiona._vendor import snuggs @pytest.mark.parametrize("arg", ["''", "null", "false", 0]) def test_truth_false(arg): """Expression is not true.""" assert not snuggs.eval(f"(truth {arg})") @pytest.mark.parametrize("arg", ["'hi'", "true", 1]) def test_truth(arg): """Expression is true.""" assert snuggs.eval(f"(truth {arg})") @pytest.mark.parametrize("arg", ["''", "null", "false", 0]) def test_not(arg): """Expression is true.""" assert snuggs.eval(f"(not {arg})") Fiona-1.10.1/tests/test_subtypes.py000066400000000000000000000040621467206072700172740ustar00rootroot00000000000000"""Tests of schema sub-types.""" import os import fiona from fiona.model import Feature def test_read_bool_subtype(tmp_path): test_data = """{"type": "FeatureCollection", "features": [{"type": "Feature", "properties": {"bool": true, "not_bool": 1, "float": 42.5}, "geometry": null}]}""" path = tmp_path.joinpath("test_read_bool_subtype.geojson") with open(os.fspath(path), "w") as f: f.write(test_data) with fiona.open(path, "r") as src: feature = next(iter(src)) assert type(feature["properties"]["bool"]) is bool assert isinstance(feature["properties"]["not_bool"], int) assert type(feature["properties"]["float"]) is float def test_write_bool_subtype(tmp_path): path = tmp_path.joinpath("test_write_bool_subtype.geojson") schema = { "geometry": "Point", "properties": { "bool": "bool", "not_bool": "int", "float": "float", }, } feature = Feature.from_dict( **{ "geometry": None, "properties": { "bool": True, "not_bool": 1, "float": 42.5, }, } ) with fiona.open(path, "w", driver="GeoJSON", schema=schema) as dst: dst.write(feature) with open(os.fspath(path)) as f: data = f.read() assert """"bool": true""" in data assert """"not_bool": 1""" in data def test_write_int16_subtype(tmp_path): path = tmp_path.joinpath("test_write_bool_subtype.gpkg") schema = { "geometry": "Point", "properties": { "a": "int", "b": "int16", }, } feature = Feature.from_dict( **{ "geometry": None, "properties": { "a": 1, "b": 2, }, } ) with fiona.open(path, "w", driver="GPKG", schema=schema) as colxn: colxn.write(feature) with fiona.open(path) as colxn: assert colxn.schema["properties"]["a"] == "int" assert colxn.schema["properties"]["b"] == "int16" Fiona-1.10.1/tests/test_topojson.py000066400000000000000000000024761467206072700173000ustar00rootroot00000000000000""" Support for TopoJSON was added in OGR 1.11 to the `GeoJSON` driver. Starting at GDAL 2.3 support was moved to the `TopoJSON` driver. """ import os import pytest import fiona from fiona.env import GDALVersion from fiona.model import Properties gdal_version = GDALVersion.runtime() driver = "TopoJSON" if gdal_version.at_least((2, 3)) else "GeoJSON" has_driver = driver in fiona.drvsupport.supported_drivers.keys() @pytest.mark.skipif(not gdal_version.at_least((1, 11)), reason="Requires GDAL >= 1.11") @pytest.mark.skipif(not has_driver, reason=f"Requires {driver} driver") def test_read_topojson(data_dir): """Test reading a TopoJSON file The TopoJSON support in GDAL is a little unpredictable. In some versions the geometries or properties aren't parsed correctly. Here we just check that we can open the file, get the right number of features out, and that they have a geometry and some properties. See GH#722. """ with fiona.open(os.path.join(data_dir, "example.topojson"), "r") as collection: features = list(collection) assert len(features) == 3, "unexpected number of features" for feature in features: assert isinstance(feature.properties, Properties) assert len(feature.properties) > 0 assert feature.geometry.type in {"Point", "LineString", "Polygon"} Fiona-1.10.1/tests/test_transactions.py000066400000000000000000000052211467206072700201240ustar00rootroot00000000000000from collections import defaultdict import logging import os import pytest from random import uniform, randint import fiona from fiona.model import Feature import fiona.ogrext from tests.conftest import requires_gdal2 has_gpkg = "GPKG" in fiona.supported_drivers.keys() def create_records(count): for n in range(count): record = { "geometry": { "type": "Point", "coordinates": [uniform(-180, 180), uniform(-90, 90)], }, "properties": {"value": randint(0, 1000)}, } yield Feature.from_dict(**record) class DebugHandler(logging.Handler): def __init__(self, pattern): logging.Handler.__init__(self) self.pattern = pattern self.history = defaultdict(lambda: 0) def emit(self, record): if self.pattern in record.msg: self.history[record.msg] += 1 log = logging.getLogger() @requires_gdal2 @pytest.mark.skipif(not has_gpkg, reason="Requires geopackage driver") class TestTransaction: def setup_method(self): self.handler = DebugHandler(pattern="transaction") self.handler.setLevel(logging.DEBUG) log.setLevel(logging.DEBUG) log.addHandler(self.handler) def teardown_method(self): log.removeHandler(self.handler) def test_transaction(self, tmpdir): """ Test transaction start/commit is called the expected number of times, and that the default transaction size can be overloaded. The test uses a custom logging handler to listen for the debug messages produced when the transaction is started/committed. """ num_records = 250 transaction_size = 100 assert fiona.ogrext.DEFAULT_TRANSACTION_SIZE == 20000 fiona.ogrext.DEFAULT_TRANSACTION_SIZE = transaction_size assert fiona.ogrext.DEFAULT_TRANSACTION_SIZE == transaction_size path = str(tmpdir.join("output.gpkg")) schema = {"geometry": "Point", "properties": {"value": "int"}} with fiona.open(path, "w", driver="GPKG", schema=schema) as dst: dst.writerecords(create_records(num_records)) assert self.handler.history["Starting transaction (initial)"] == 1 assert ( self.handler.history["Starting transaction (intermediate)"] == num_records // transaction_size ) assert ( self.handler.history["Committing transaction (intermediate)"] == num_records // transaction_size ) assert self.handler.history["Committing transaction (final)"] == 1 with fiona.open(path, "r") as src: assert len(src) == num_records Fiona-1.10.1/tests/test_transform.py000066400000000000000000000140671467206072700174370ustar00rootroot00000000000000"""Tests of the transform submodule""" import math import pytest import fiona from fiona import transform from fiona.errors import FionaDeprecationWarning, TransformError from fiona.model import Geometry from .conftest import requires_gdal_lt_3 TEST_GEOMS = [ Geometry(type="Point", coordinates=[0.0, 0.0, 1000.0]), Geometry(type="LineString", coordinates=[[0.0, 0.0, 1000.0], [0.1, 0.1, -1000.0]]), Geometry(type="MultiPoint", coordinates=[[0.0, 0.0, 1000.0], [0.1, 0.1, -1000.0]]), Geometry( type="Polygon", coordinates=[ [ [0.0, 0.0, 1000.0], [0.1, 0.1, -1000.0], [0.1, -0.1, math.pi], [0.0, 0.0, 1000.0], ] ], ), Geometry( type="MultiPolygon", coordinates=[ [ [ [0.0, 0.0, 1000.0], [0.1, 0.1, -1000.0], [0.1, -0.1, math.pi], [0.0, 0.0, 1000.0], ] ] ], ), ] @pytest.mark.parametrize("geom", TEST_GEOMS) def test_transform_geom_with_z(geom): """Transforming a geom with Z succeeds""" transform.transform_geom("epsg:4326", "epsg:3857", geom) @pytest.mark.parametrize("geom", TEST_GEOMS) def test_transform_geom_array_z(geom): """Transforming a geom array with Z succeeds""" g2 = transform.transform_geom( "epsg:4326", "epsg:3857", [geom for _ in range(5)], ) assert isinstance(g2, list) assert len(g2) == 5 @pytest.mark.parametrize( "crs", [ "epsg:4326", "EPSG:4326", "WGS84", {"init": "epsg:4326"}, {"proj": "longlat", "datum": "WGS84", "no_defs": True}, "OGC:CRS84", ], ) def test_axis_ordering_rev(crs): """Test if transform uses traditional_axis_mapping""" expected = (-8427998.647958742, 4587905.27136252) t1 = transform.transform(crs, "epsg:3857", [-75.71], [38.06]) assert (t1[0][0], t1[1][0]) == pytest.approx(expected) geom = Geometry.from_dict(**{"type": "Point", "coordinates": [-75.71, 38.06]}) g1 = transform.transform_geom(crs, "epsg:3857", geom) assert g1["coordinates"] == pytest.approx(expected) @pytest.mark.parametrize( "crs", [ "epsg:4326", "EPSG:4326", "WGS84", {"init": "epsg:4326"}, {"proj": "longlat", "datum": "WGS84", "no_defs": True}, "OGC:CRS84", ], ) def test_axis_ordering_fwd(crs): """Test if transform uses traditional_axis_mapping""" rev_expected = (-75.71, 38.06) t2 = transform.transform("epsg:3857", crs, [-8427998.647958742], [4587905.27136252]) assert (t2[0][0], t2[1][0]) == pytest.approx(rev_expected) geom = Geometry.from_dict( **{"type": "Point", "coordinates": [-8427998.647958742, 4587905.27136252]} ) g2 = transform.transform_geom("epsg:3857", crs, geom) assert g2.coordinates == pytest.approx(rev_expected) def test_transform_issue971(): """See https://github.com/Toblerity/Fiona/issues/971""" source_crs = "EPSG:25832" dest_src = "EPSG:4326" geom = { "type": "GeometryCollection", "geometries": [ { "type": "LineString", "coordinates": [ (512381.8870945257, 5866313.311218272), (512371.23869999964, 5866322.282500001), (512364.6014999999, 5866328.260199999), ], } ], } geom_transformed = transform.transform_geom(source_crs, dest_src, geom) assert geom_transformed.geometries[0].coordinates[0] == pytest.approx( (9.18427, 52.94630) ) def test_transform_geom_precision_deprecation(): """Get a precision deprecation warning in 1.9.""" with pytest.warns(FionaDeprecationWarning): transform.transform_geom( "epsg:4326", "epsg:3857", Geometry(type="Point", coordinates=(0, 0)), precision=2, ) def test_partial_reprojection_error(): """Raise an error about full reprojection failure unless we opt in.""" geom = { "type": "Polygon", "coordinates": ( ( (6453888.0, -6453888.0), (6453888.0, 6453888.0), (-6453888.0, 6453888.0), (-6453888.0, -6453888.0), (6453888.0, -6453888.0), ), ), } src_crs = 'PROJCS["unknown",GEOGCS["unknown",DATUM["Unknown based on WGS84 ellipsoid",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]]],PROJECTION["Orthographic"],PARAMETER["latitude_of_origin",-90],PARAMETER["central_meridian",0],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH]]' dst_crs = "EPSG:4326" with pytest.raises(TransformError): _ = transform.transform_geom(src_crs, dst_crs, geom) def test_partial_reprojection_opt_in(): """Get no exception if we opt in to partial reprojection.""" geom = { "type": "Polygon", "coordinates": ( ( (6453888.0, -6453888.0), (6453888.0, 6453888.0), (-6453888.0, 6453888.0), (-6453888.0, -6453888.0), (6453888.0, -6453888.0), ), ), } src_crs = 'PROJCS["unknown",GEOGCS["unknown",DATUM["Unknown based on WGS84 ellipsoid",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]]],PROJECTION["Orthographic"],PARAMETER["latitude_of_origin",-90],PARAMETER["central_meridian",0],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH]]' dst_crs = "EPSG:4326" with fiona.Env(OGR_ENABLE_PARTIAL_REPROJECTION=True): _ = transform.transform_geom(src_crs, dst_crs, geom) Fiona-1.10.1/tests/test_unicode.py000066400000000000000000000135131467206072700170450ustar00rootroot00000000000000"""Tests of path and field encoding.""" import os import shutil import sys import tempfile import unittest import pytest import fiona from fiona.errors import SchemaError from fiona.model import Feature class TestUnicodePath(unittest.TestCase): def setUp(self): tempdir = tempfile.mkdtemp() self.dir = os.path.join(tempdir, "français") shutil.copytree(os.path.join(os.path.dirname(__file__), "data"), self.dir) def tearDown(self): shutil.rmtree(os.path.dirname(self.dir)) def test_unicode_path(self): path = self.dir + "/coutwildrnp.shp" with fiona.open(path) as c: assert len(c) == 67 def test_unicode_path_layer(self): path = self.dir layer = "coutwildrnp" with fiona.open(path, layer=layer) as c: assert len(c) == 67 def test_utf8_path(self): path = self.dir + "/coutwildrnp.shp" if sys.version_info < (3,): with fiona.open(path) as c: assert len(c) == 67 class TestUnicodeStringField(unittest.TestCase): def setUp(self): self.tempdir = tempfile.mkdtemp() def tearDown(self): shutil.rmtree(self.tempdir) @pytest.mark.xfail(reason="OGR silently fails to convert strings") def test_write_mismatch(self): """TOFIX: OGR silently fails to convert strings""" # Details: # # If we tell OGR that we want a latin-1 encoded output file and # give it a feature with a unicode property that can't be converted # to latin-1, no error is raised and OGR just writes the utf-8 # encoded bytes to the output file. # # This might be shapefile specific. # # Consequences: no error on write, but there will be an error # on reading the data and expecting latin-1. schema = {"geometry": "Point", "properties": {"label": "str", "num": "int"}} with fiona.open( os.path.join(self.tempdir, "test-write-fail.shp"), "w", driver="ESRI Shapefile", schema=schema, encoding="latin1", ) as c: c.writerecords( [ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [0, 0]}, "properties": {"label": "徐汇区", "num": 0}, } ] ) with fiona.open(os.path.join(self.tempdir), encoding="latin1") as c: f = next(iter(c)) # Next assert fails. assert f.properties["label"] == "徐汇区" def test_write_utf8(self): schema = { "geometry": "Point", "properties": {"label": "str", "verit\xe9": "int"}, } with fiona.open( os.path.join(self.tempdir, "test-write.shp"), "w", "ESRI Shapefile", schema=schema, encoding="utf-8", ) as c: c.writerecords( [ Feature.from_dict( **{ "type": "Feature", "geometry": {"type": "Point", "coordinates": [0, 0]}, "properties": {"label": "Ba\u2019kelalan", "verit\xe9": 0}, } ) ] ) with fiona.open(os.path.join(self.tempdir), encoding="utf-8") as c: f = next(iter(c)) assert f.properties["label"] == "Ba\u2019kelalan" assert f.properties["verit\xe9"] == 0 @pytest.mark.iconv def test_write_gb18030(self): """Can write a simplified Chinese shapefile""" schema = {"geometry": "Point", "properties": {"label": "str", "num": "int"}} with fiona.open( os.path.join(self.tempdir, "test-write-gb18030.shp"), "w", driver="ESRI Shapefile", schema=schema, encoding="gb18030", ) as c: c.writerecords( [ Feature.from_dict( **{ "type": "Feature", "geometry": {"type": "Point", "coordinates": [0, 0]}, "properties": {"label": "徐汇区", "num": 0}, } ) ] ) with fiona.open(os.path.join(self.tempdir), encoding="gb18030") as c: f = next(iter(c)) assert f.properties["label"] == "徐汇区" assert f.properties["num"] == 0 @pytest.mark.iconv def test_gb2312_field_wrong_encoding(self): """Attempt to create field with a name not supported by the encoding ESRI Shapefile driver defaults to ISO-8859-1 encoding if none is specified. This doesn't support the field name used. Previously this went undetected and would raise a KeyError later when the user tried to write a feature to the layer. Instead we raise a more useful error. See GH#595. """ field_name = "区县名称" meta = { "schema": { "properties": {field_name: "int"}, "geometry": "Point", }, "driver": "ESRI Shapefile", } feature = Feature.from_dict( **{ "properties": {field_name: 123}, "geometry": {"type": "Point", "coordinates": [1, 2]}, } ) # when encoding is specified, write is successful with fiona.open( os.path.join(self.tempdir, "test1.shp"), "w", encoding="GB2312", **meta ) as collection: collection.write(feature) # no encoding with pytest.raises(SchemaError): fiona.open(os.path.join(self.tempdir, "test2.shp"), "w", **meta) Fiona-1.10.1/tests/test_version.py000066400000000000000000000044761467206072700171140ustar00rootroot00000000000000import fiona import platform import re import os import sys from tests.conftest import travis_only from fiona._env import GDALVersion, get_gdal_release_name def test_version_tuple(): version = fiona.gdal_version assert version.major >= 1 and isinstance(version.major, int) assert version.minor >= 0 and isinstance(version.minor, int) assert version.revision >= 0 and isinstance(version.revision, int) def test_version_comparison(): # version against version assert GDALVersion(4, 0, 0) > GDALVersion(3, 2, 1) assert GDALVersion(2, 0, 0) < GDALVersion(3, 2, 1) assert GDALVersion(3, 2, 2) > GDALVersion(3, 2, 1) assert GDALVersion(3, 2, 0) < GDALVersion(3, 2, 1) # tuple against version assert (4, 0, 0) > GDALVersion(3, 2, 1) assert (2, 0, 0) < GDALVersion(3, 2, 1) assert (3, 2, 2) > GDALVersion(3, 2, 1) assert (3, 2, 0) < GDALVersion(3, 2, 1) @travis_only def test_show_versions(capsys): version_pattern = re.compile(r"(\d+).(\d+).(\d+)") os_info = f"{platform.system()} {platform.release()}" python_version = platform.python_version() python_exec = sys.executable msg = ("Fiona version: {fiona_version}" "\nGDAL version: {gdal_release_name}" "\nPROJ version: {proj_version}" "\n" "\nOS: {os_info}" "\nPython: {python_version}" "\nPython executable: '{python_exec}'" "\n" ) if fiona.gdal_version < GDALVersion(3, 0, 1): proj_version = "Proj version not available" else: proj_version = os.getenv("PROJVERSION") proj_version = re.match(version_pattern, proj_version).group(0) gdal_version = os.getenv("GDALVERSION") if not gdal_version == "master": gdal_version = re.match(version_pattern, gdal_version).group(0) else: gdal_version = get_gdal_release_name() msg_formatted = msg.format(fiona_version=fiona.__version__, gdal_release_name=gdal_version, proj_version=proj_version, os_info=os_info, python_version=python_version, python_exec=python_exec) fiona.show_versions() captured = capsys.readouterr() assert captured.out.strip() == msg_formatted.strip() Fiona-1.10.1/tests/test_vfs.py000066400000000000000000000137711467206072700162230ustar00rootroot00000000000000import logging import sys import os import pytest import boto3 import fiona from fiona.errors import FionaDeprecationWarning from fiona.vfs import vsi_path, parse_paths from .test_collection import TestReading from .test_collection_legacy import ReadingTest # Custom markers (from rasterio) mingdalversion = pytest.mark.skipif( fiona.gdal_version < (2, 1, 0), reason="S3 raster access requires GDAL 2.1" ) credentials = pytest.mark.skipif( not (boto3.Session()._session.get_credentials()), reason="S3 raster access requires credentials", ) # TODO: remove this once we've successfully moved the tar tests over # to TestVsiReading. class VsiReadingTest(ReadingTest): # There's a bug in GDAL 1.9.2 http://trac.osgeo.org/gdal/ticket/5093 # in which the VSI driver reports the wrong number of features. # I'm overriding ReadingTest's test_filter_1 with a function that # passes and creating a new method in this class that we can exclude # from the test runner at run time. @pytest.mark.xfail( reason="The number of features present in the archive " "differs based on the GDAL version." ) def test_filter_vsi(self): results = list(self.c.filter(bbox=(-114.0, 35.0, -104, 45.0))) assert len(results) == 67 f = results[0] assert f["id"] == "0" assert f["properties"]["STATE"] == "UT" class TestVsiReading(TestReading): # There's a bug in GDAL 1.9.2 http://trac.osgeo.org/gdal/ticket/5093 # in which the VSI driver reports the wrong number of features. # I'm overriding TestReading's test_filter_1 with a function that # passes and creating a new method in this class that we can exclude # from the test runner at run time. @pytest.mark.xfail( reason="The number of features present in the archive " "differs based on the GDAL version." ) def test_filter_vsi(self): results = list(self.c.filter(bbox=(-114.0, 35.0, -104, 45.0))) assert len(results) == 67 f = results[0] assert f["id"] == "0" assert f["properties"]["STATE"] == "UT" class TestZipReading(TestVsiReading): @pytest.fixture(autouse=True) def zipfile(self, data_dir, path_coutwildrnp_zip): self.c = fiona.open(f"zip://{path_coutwildrnp_zip}", "r") self.path = os.path.join(data_dir, "coutwildrnp.zip") yield self.c.close() def test_open_repr(self): path = f"/vsizip/{self.path}:coutwildrnp" assert repr(self.c) == ( f"" ) def test_closed_repr(self): self.c.close() path = f"/vsizip/{self.path}:coutwildrnp" assert repr(self.c) == ( f"" ) def test_path(self): assert self.c.path == f"/vsizip/{self.path}" class TestZipArchiveReading(TestVsiReading): @pytest.fixture(autouse=True) def zipfile(self, data_dir, path_coutwildrnp_zip): vfs = f"zip://{path_coutwildrnp_zip}" self.c = fiona.open(vfs + "!coutwildrnp.shp", "r") self.path = os.path.join(data_dir, "coutwildrnp.zip") yield self.c.close() def test_open_repr(self): path = f"/vsizip/{self.path}/coutwildrnp.shp:coutwildrnp" assert repr(self.c) == ( f"" ) def test_closed_repr(self): self.c.close() path = f"/vsizip/{self.path}/coutwildrnp.shp:coutwildrnp" assert repr(self.c) == ( f"" ) def test_path(self): assert self.c.path == f"/vsizip/{self.path}/coutwildrnp.shp" class TestZipArchiveReadingAbsPath(TestZipArchiveReading): @pytest.fixture(autouse=True) def zipfile(self, path_coutwildrnp_zip): vfs = f"zip://{os.path.abspath(path_coutwildrnp_zip)}" self.c = fiona.open(vfs + "!coutwildrnp.shp", "r") yield self.c.close() def test_open_repr(self): assert repr(self.c).startswith("" ) def test_closed_repr(self): self.c.close() path = f"/vsitar/{self.path}/testing/coutwildrnp.shp:coutwildrnp" assert repr(self.c) == ( f"" ) def test_path(self): assert self.c.path == f"/vsitar/{self.path}/testing/coutwildrnp.shp" @pytest.mark.network def test_open_http(): ds = fiona.open( "https://raw.githubusercontent.com/OSGeo/gdal/master/autotest/ogr/data/poly.shp" ) assert len(ds) == 10 @credentials @mingdalversion @pytest.mark.network def test_open_s3(): ds = fiona.open("zip+s3://fiona-testing/coutwildrnp.zip") assert len(ds) == 67 @credentials @pytest.mark.network def test_open_zip_https(): ds = fiona.open("zip+https://s3.amazonaws.com/fiona-testing/coutwildrnp.zip") assert len(ds) == 67 def test_parse_path(): assert parse_paths("zip://foo.zip") == ("foo.zip", "zip", None) def test_parse_path2(): assert parse_paths("foo") == ("foo", None, None) def test_parse_vfs(): assert parse_paths("/", "zip://foo.zip") == ("/", "zip", "foo.zip") Fiona-1.10.1/tests/test_write.py000066400000000000000000000112631467206072700165510ustar00rootroot00000000000000"""New tests of writing feature collections.""" import pytest from .conftest import requires_gdal33 import fiona from fiona.crs import CRS from fiona.errors import DriverError from fiona.model import Feature def test_issue771(tmpdir, caplog): """Overwrite a GeoJSON file without logging errors.""" schema = {"geometry": "Point", "properties": {"zero": "int"}} feature = Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0, 0)}, "properties": {"zero": "0"}, } ) outputfile = tmpdir.join("test.geojson") for i in range(2): with fiona.open( str(outputfile), "w", driver="GeoJSON", schema=schema, crs=CRS.from_epsg(4326), ) as collection: collection.write(feature) assert outputfile.exists() for record in caplog.records: assert record.levelname != "ERROR" @requires_gdal33 def test_write__esri_only_wkt(tmpdir): """https://github.com/Toblerity/Fiona/issues/977""" schema = {"geometry": "Point", "properties": {"zero": "int"}} feature = Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0, 0)}, "properties": {"zero": "0"}, } ) target_crs = ( 'PROJCS["IaRCS_04_Sioux_City-Iowa_Falls_NAD_1983_2011_LCC_US_Feet",' 'GEOGCS["GCS_NAD_1983_2011",DATUM["D_NAD_1983_2011",' 'SPHEROID["GRS_1980",6378137.0,298.257222101]],' 'PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],' 'PROJECTION["Lambert_Conformal_Conic"],' 'PARAMETER["False_Easting",14500000.0],' 'PARAMETER["False_Northing",8600000.0],' 'PARAMETER["Central_Meridian",-94.83333333333333],' 'PARAMETER["Standard_Parallel_1",42.53333333333333],' 'PARAMETER["Standard_Parallel_2",42.53333333333333],' 'PARAMETER["Scale_Factor",1.000045],' 'PARAMETER["Latitude_Of_Origin",42.53333333333333],' 'UNIT["Foot_US",0.3048006096012192]]' ) outputfile = tmpdir.join("test.shp") with fiona.open( str(outputfile), "w", driver="ESRI Shapefile", schema=schema, crs=target_crs, ) as collection: collection.write(feature) assert collection.crs_wkt.startswith( ( 'PROJCS["IaRCS_04_Sioux_City-Iowa_Falls_NAD_1983_2011_LCC_US_Feet"', 'PROJCRS["IaRCS_04_Sioux_City-Iowa_Falls_NAD_1983_2011_LCC_US_Feet"', # GDAL 3.3+ ) ) def test_write__wkt_version(tmpdir): """https://github.com/Toblerity/Fiona/issues/977""" schema = {"geometry": "Point", "properties": {"zero": "int"}} feature = Feature.from_dict( **{ "geometry": {"type": "Point", "coordinates": (0, 0)}, "properties": {"zero": "0"}, } ) target_crs = ( 'PROJCS["IaRCS_04_Sioux_City-Iowa_Falls_NAD_1983_2011_LCC_US_Feet",' 'GEOGCS["GCS_NAD_1983_2011",DATUM["D_NAD_1983_2011",' 'SPHEROID["GRS_1980",6378137.0,298.257222101]],' 'PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],' 'PROJECTION["Lambert_Conformal_Conic"],' 'PARAMETER["False_Easting",14500000.0],' 'PARAMETER["False_Northing",8600000.0],' 'PARAMETER["Central_Meridian",-94.83333333333333],' 'PARAMETER["Standard_Parallel_1",42.53333333333333],' 'PARAMETER["Standard_Parallel_2",42.53333333333333],' 'PARAMETER["Scale_Factor",1.000045],' 'PARAMETER["Latitude_Of_Origin",42.53333333333333],' 'UNIT["Foot_US",0.3048006096012192]]' ) outputfile = tmpdir.join("test.shp") with fiona.open( str(outputfile), "w", driver="ESRI Shapefile", schema=schema, crs=target_crs, wkt_version="WKT2_2018", ) as collection: collection.write(feature) assert collection.crs_wkt.startswith( 'PROJCRS["IaRCS_04_Sioux_City-Iowa_Falls_NAD_1983_2011_LCC_US_Feet"' ) def test_issue1169(): """Don't swallow errors when a collection can't be written.""" with pytest.raises(DriverError): with fiona.open( "s3://non-existing-bucket/test.geojson", mode="w", driver="GeoJSON", schema={"geometry": "Point"}, ) as collection: collection.writerecords( [ Feature.from_dict( **{ "id": "0", "type": "Feature", "geometry": {"type": "Point", "coordinates": (1.0, 2.0)}, } ) ] )