pax_global_header00006660000000000000000000000064146435517040014523gustar00rootroot0000000000000052 comment=4a5887af7337bace5f0cf26d1045448b7d3eb3e8 geoalchemy2-0.15.2/000077500000000000000000000000001464355170400140075ustar00rootroot00000000000000geoalchemy2-0.15.2/.codespellrc000066400000000000000000000002251464355170400163060ustar00rootroot00000000000000[codespell] skip = .git/*,./doc/_build/*,./doc/gallery/*,.tox/*,./reports/*,./issues/*,*.sublime-workspace,./test_container/* ignore-words-list = nd geoalchemy2-0.15.2/.coveragerc000066400000000000000000000000611464355170400161250ustar00rootroot00000000000000[run] source = geoalchemy2 relative_files = True geoalchemy2-0.15.2/.flake8000066400000000000000000000000671464355170400151650ustar00rootroot00000000000000[flake8] max-line-length=100 ignore= E203 W503 W504 geoalchemy2-0.15.2/.github/000077500000000000000000000000001464355170400153475ustar00rootroot00000000000000geoalchemy2-0.15.2/.github/ISSUE_TEMPLATE/000077500000000000000000000000001464355170400175325ustar00rootroot00000000000000geoalchemy2-0.15.2/.github/ISSUE_TEMPLATE/bug_report.yaml000066400000000000000000000076471464355170400226040ustar00rootroot00000000000000# docs https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms # https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema name: Create a bug report regarding GeoAlchemy 2 runtime behavior description: Errors and regression reports with complete reproducing test cases and/or stack traces. labels: [bug] title: "[Bug report] " body: - type: markdown attributes: value: " Thanks for taking the time to fill out this bug report! Before submitting, make sure you search in the [issue list](https://github.com/geoalchemy/geoalchemy2/issues) that a similar issue was not already reported. If it is not the case, please read the following guidelines. ### GUIDELINES FOR REPORTING BUGS Bug reports that are not properly formulated and formatted or without enough details can be very hard for us to understand and fix. In order to ensure we can help you fixing your bug, please follow these guidelines. Your reports must include the following features: 1. **succinct description** of the problem - typically a line or two at most. 2. **succinct, dependency-free code** which reproduces the problem, otherwise known as a [Minimal, Complete, and Verifiable](https://stackoverflow.com/help/mcve) example. **Please note that if you do not have a complete and runnable test case written directly in the textarea below, then your issue will be very hard for us to process and may be closed.** 3. **complete stack traces for all errors** - please avoid screenshots, use formatted text inside issues. 4. other relevant things as applicable: **dependencies**, **comparative performance timings** for performance issues, etc. " - type: textarea attributes: label: Describe the bug description: A clear and concise description of what the bug is. validations: required: true - type: input id: relevant_documentation attributes: label: Optional link from https://geoalchemy-2.readthedocs.io which documents the behavior that is expected description: " Please make sure the behavior you are seeing is definitely in contradiction to what's documented as the correct behavior. " validations: required: false - type: textarea attributes: label: To Reproduce description: " Provide your [Minimal, Complete, and Verifiable](https://stackoverflow.com/help/mcve) example here." placeholder: "# Insert code here (text area already python formatted)" render: Python validations: required: true - type: textarea attributes: label: Error description: " Provide the complete text of any errors received **including the complete stack trace**. If the message is a warning, run your program with the ``-Werror`` flag: ``python -Werror myprogram.py`` " placeholder: "# Copy the complete stack trace and error message here (text area already formatted for Python backtrace)" render: Python traceback validations: required: true - type: textarea attributes: label: Additional context description: Add any other context about the problem here. validations: required: false - type: input id: package_version attributes: label: GeoAlchemy 2 Version in Use description: e.g. 1.4.42, 2.0.2, etc or commit hash validations: required: true - type: input id: python_version attributes: label: Python Version description: Assumes cpython unless otherwise stated, e.g. 3.10, 3.11, pypy validations: required: true - type: dropdown id: os attributes: label: Operating system description: Check all that apply multiple: true options: - Linux - MacOS - Windows - Other validations: required: true - type: markdown attributes: value: "### Thanks! Have a nice day!" geoalchemy2-0.15.2/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000331464355170400215160ustar00rootroot00000000000000blank_issues_enabled: true geoalchemy2-0.15.2/.github/ISSUE_TEMPLATE/feature_request.yaml000066400000000000000000000041521464355170400236230ustar00rootroot00000000000000# docs https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms # https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema name: Request a new use case description: Support for new features labels: [enhancement] title: "[Feature request] " body: - type: markdown attributes: value: " Thanks for taking the time to fill out this feature request! Before submitting, make sure the feature does not already exist in the [documentation](https://geoalchemy-2.readthedocs.io) and that you searched in the [issue list](https://github.com/geoalchemy/geoalchemy2/issues) that a similar feature request has not already been reported. If it is not the case, please read the following guidelines. ### GUIDELINES FOR REQUESTING HELP Feature requests that are not properly formulated and formatted or without enough details can be very hard for us to understand. In order to ensure we can help you, please follow these guidelines. Your requests must include the following features: 1. **succinct description** of the feature - typically a line or two at most to describe what the new feature would do. 2. **succinct, dependency-free code** which shows how you would like to use this feature, otherwise known as a [Minimal, Complete, and Verifiable](https://stackoverflow.com/help/mcve) example. 3. Why do you need this feature? What would be the use cases? " - type: textarea attributes: label: Describe the feature description: A clear and concise description of what the feature would consist in. validations: required: true - type: textarea attributes: label: Example Use description: Provide a clear example of what the usage of this feature would look like. validations: required: true - type: textarea attributes: label: Use cases description: Add as much information as possible about the use cases here. validations: required: true - type: markdown attributes: value: "### Thanks! Have a nice day!" geoalchemy2-0.15.2/.github/ISSUE_TEMPLATE/how_to_use.yaml000066400000000000000000000065611464355170400226010ustar00rootroot00000000000000# docs https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms # https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema name: Ask for help about GeoAlchemy 2 description: Questions about GeoAlchemy 2 features. labels: [how to use] title: "[How to use] " body: - type: markdown attributes: value: " Thanks for using this package and taking the time to fill out this help request! Before submitting, make sure you read the [documentation](https://geoalchemy-2.readthedocs.io) carefully. If you still have a question, you should search in the [issue list](https://github.com/geoalchemy/geoalchemy2/issues) that a similar issue has not already been reported, you might find your answer there. If it is not the case, please read the following guidelines. ### GUIDELINES FOR REQUESTING HELP Questions that are not properly formulated and formatted or without enough details can be very hard for us to understand. In order to ensure we can help you, please follow these guidelines. Your requests must include the following features: 1. **succinct description** of the problem - typically a line or two at most to describe what you want to achieve. 2. **succinct, dependency-free code** which shows what you tried to do, otherwise known as a [Minimal, Complete, and Verifiable Example](https://stackoverflow.com/help/mcve). 3. **expected results** 4. **complete stack traces for all errors** if you have any - please avoid screenshots, use formatted text inside issues. 5. other relevant things: **dependencies**, **operating system**, **comparative performance timings** for performance issues. " - type: textarea attributes: label: Describe the problem description: A clear and concise description of what you want to achieve. validations: required: true - type: textarea attributes: label: Show what you tried to do. description: " Provide your [Minimal, Complete, and Verifiable Example](https://stackoverflow.com/help/mcve) here." placeholder: "# Insert code here (text area already python formatted)" render: Python validations: required: true - type: textarea attributes: label: Describe what you expected. description: A clear and concise description of what you which result you expected by doing this. validations: required: true - type: textarea attributes: label: Error description: " If you are facing an arror, provide the complete text of any errors received **including the complete stack trace**. If the message is a warning, run your program with the ``-Werror`` flag: ``python -Werror myprogram.py`` " placeholder: "# Copy the complete stack trace and error message here (text area already formatted for Python backtrace)" render: Python traceback validations: required: false - type: textarea attributes: label: Additional context description: Add any other context about the problem here. validations: required: false - type: input id: package_version attributes: label: GeoAlchemy 2 Version in Use description: e.g. 1.4.42, 2.0.2, etc or commit hash validations: required: true - type: markdown attributes: value: "### Thanks! Have a nice day!" geoalchemy2-0.15.2/.github/dependabot.yml000066400000000000000000000012761464355170400202050ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: # Configure check for outdated GitHub Actions actions in workflows. # See: https://docs.github.com/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot - package-ecosystem: "github-actions" directory: "/" # Check the repository's workflows under /.github/workflows/ schedule: interval: "weekly" geoalchemy2-0.15.2/.github/pull_request_template.md000066400000000000000000000024451464355170400223150ustar00rootroot00000000000000 ### Description ### Checklist This pull request is: - [ ] A documentation / typographical error fix - Good to go, no issue or tests are needed - [ ] A short code fix - [ ] Please include: `Fixes: #` in the description if it solves an existing issue (which must include a complete example of the issue). - [ ] Please include tests that fail with the `main` branch and pass with the provided fix. - [ ] A new feature implementation or update an existing feature - [ ] Please include: `Fixes: #` in the description if it solves an existing issue (which must include a complete example of the feature). - [ ] Please include tests that cover every lines of the new/updated feature. - [ ] Please update the documentation to describe the new/updated feature. geoalchemy2-0.15.2/.github/workflows/000077500000000000000000000000001464355170400174045ustar00rootroot00000000000000geoalchemy2-0.15.2/.github/workflows/test_and_publish.yml000066400000000000000000000124071464355170400234620ustar00rootroot00000000000000name: CI # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch. on: push: branches: [ master ] tags: - '*' pull_request: workflow_dispatch: env: PGPASSWORD: gis jobs: # This workflow runs the tests tests: # Set shell to work properly with Mamba defaults: run: shell: bash -l {0} # Setup test matrix strategy: fail-fast: false matrix: python-version: [ {"pkg_name": "python==3.7.*", "flag": "3.7"}, {"pkg_name": "python==3.8.*", "flag": "3.8"}, {"pkg_name": "python==3.9.*", "flag": "3.9"}, {"pkg_name": "python==3.10.*", "flag": "3.10"}, {"pkg_name": "python==3.11.*", "flag": "3.11"}, {"pkg_name": "python==3.12.*", "flag": "3.12"}, {"pkg_name": "pypy3.8", "flag": "pypy3.8"}, ] # The type of runner that the job will run on runs-on: ubuntu-22.04 services: postgres: image: postgis/postgis:16-3.4 env: POSTGRES_DB: gis POSTGRES_PASSWORD: gis POSTGRES_USER: gis ports: - 5432:5432 # Set health checks to wait until postgres has started options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v4 # Setup Conda for Python and Pypy - name: Install Conda environment with Micromamba uses: mamba-org/setup-micromamba@v1 with: environment-name: test_${{ matrix.python-version.flag }} cache-environment: true create-args: >- ${{ matrix.python-version.pkg_name }} libgdal libspatialite==5.0.1 pyproj condarc: | channels: - conda-forge - defaults channel_priority: strict # Config PostgreSQL - name: Configure PostgreSQL run: | # Create schema "gis" into database "gis" psql -h localhost -p 5432 -U gis -d gis -c 'CREATE SCHEMA gis;' # Add PostGIS extension to "gis" database psql -h localhost -p 5432 -U gis -d gis -c 'CREATE EXTENSION IF NOT EXISTS postgis SCHEMA public;' # Drop PostGIS Tiger Geocoder extension to "gis" database psql -h localhost -p 5432 -U gis -d gis -c 'DROP EXTENSION IF EXISTS postgis_tiger_geocoder CASCADE;' # Add PostGISRaster extension to "gis" database psql -h localhost -p 5432 -U gis -d gis -c 'CREATE EXTENSION IF NOT EXISTS postgis_raster SCHEMA public;' # Drop PostGIS Topology extension to "gis" database psql -h localhost -p 5432 -U gis -d gis -c 'DROP EXTENSION IF EXISTS postgis_topology;' # Setup MySQL - name: Set up MySQL run: | sudo systemctl start mysql sudo mysql --user=root --password=root --host=127.0.0.1 -e "CREATE USER 'gis'@'%' IDENTIFIED BY 'gis';" sudo mysql --user=root --password=root --host=127.0.0.1 -e "GRANT ALL PRIVILEGES ON *.* TO 'gis'@'%' WITH GRANT OPTION;" mysql --user=gis --password=gis -e "CREATE DATABASE gis;" # Check python version - name: Display Python version run: | /home/runner/micromamba-bin/micromamba info /home/runner/micromamba-bin/micromamba list python -c "import sys; print(sys.version)" # Install dependencies - name: Install dependencies run: | python -m pip install --upgrade pip setuptools pip install tox-gh-actions # Run the test suite - name: Run the tests env: SPATIALITE_LIBRARY_PATH: /home/runner/micromamba/envs/test_${{ matrix.python-version.flag }}/lib/mod_spatialite.so PROJ_LIB: /home/runner/micromamba/envs/test_${{ matrix.python-version.flag }}/share/proj COVERAGE_FILE: .coverage PYTEST_MYSQL_DB_URL: mysql://gis:gis@127.0.0.1/gis run: | # Run the unit test suite with SQLAlchemy=1.4.* and then with the latest version of SQLAlchemy tox -vv # Export coverage to Coveralls - name: Coveralls uses: AndreMiras/coveralls-python-action@v20201129 with: parallel: true flag-name: run-${{ matrix.python-version.flag }} # This workflow aggregates coverages from all jobs and export it to Coveralls coveralls: needs: tests runs-on: ubuntu-22.04 steps: - name: Coveralls Finished uses: AndreMiras/coveralls-python-action@v20201129 with: parallel-finished: true github-token: ${{ secrets.github_token }} # This workflow deploys the package deploy: needs: tests runs-on: ubuntu-22.04 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') steps: - uses: actions/checkout@v4 - name: Set up Python 3.10 uses: actions/setup-python@v5 with: python-version: "3.10" # Build distribution and deploy to Pypi - name: Build and deploy package env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | pip install --upgrade pip setuptools build twine python -m build -o dist twine upload dist/* geoalchemy2-0.15.2/.gitignore000066400000000000000000000012141464355170400157750ustar00rootroot00000000000000# IDE .vscode/ .idea/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ venv/ bin/ build/ develop-eggs/ dist/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports .tox/ .coverage .coverage-* .cache nosetests.xml coverage.xml .pytest_cache/ reports/ # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Rope .ropeproject # Django stuff: *.log *.pot # Sphinx documentation doc/_build/ doc/gallery/ # Miscellaneous *.csv *.sh *.txt issues geoalchemy2-0.15.2/.pre-commit-config.yaml000066400000000000000000000016561464355170400203000ustar00rootroot00000000000000default_language_version: python: python3.8 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-merge-conflict - id: check-symlinks - id: check-yaml - id: debug-statements - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/isort rev: 5.13.2 hooks: - id: isort - repo: https://github.com/psf/black rev: 24.4.2 hooks: - id: black - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - id: codespell - repo: https://github.com/PyCQA/pydocstyle rev: 6.3.0 hooks: - id: pydocstyle additional_dependencies: ["tomli"] exclude: "tests" - repo: https://github.com/PyCQA/flake8 rev: 7.1.0 hooks: - id: flake8 ci: autoupdate_schedule: quarterly geoalchemy2-0.15.2/.readthedocs.yaml000066400000000000000000000010711464355170400172350ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-22.04 tools: python: "3.11" # Build documentation in the docs/ directory with Sphinx sphinx: configuration: doc/conf.py # We recommend specifying your dependencies to enable reproducible builds: # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - requirements: requirements-rtd.txt geoalchemy2-0.15.2/CHANGES.txt000066400000000000000000000262741464355170400156330ustar00rootroot00000000000000GeoAlchemy 2 Changelog ====================== 0.15.2 ------ * Fix: Can handle negative coordinates with Spatialite @adrien-berchet (#517) 0.15.1 ------ * Fix: Default SRID is bypassed when using floating point coordinates @aballet (#509) * Test: Dispose of the connection pools of the test engines @adrien-berchet (#511) 0.15.0 ------ * Specific process for geometries with Z or M coordinate with SpatiaLite dialect @spd5 (#506) * Chore: Fix type hints on stubs @adrien-berchet (#504) 0.14.7 ------ * Allow geometry_type to be None at the type level to match documentation @logankaser (#500) * CI: Fix Py312 job in tox @adrien-berchet (#501) * Docs: Add Zenodo badge on the main page @adrien-berchet (#498) 0.14.6 ------ * Fix: Fix specific compilation rules for MariaDB @adrien-berchet (#495) 0.14.5 ------ * Feat: Add experimental support for MariaDB @tebrown (#491) * CI: Setup pre-commit.ci @adrien-berchet (#492) 0.14.4 ------ * Type: Fix modified check_ctor_args to pass default SRS_ID value in case of null @satyamsoni2211 (#488) * Build: Add Py312 support @adrien-berchet (#487) 0.14.3 ------ * Fix: Handle empty doctrings in optimized mode @adrien-berchet (#484) * Test: Check for max width and height in raster insertion example @adrien-berchet (#482) 0.14.2 ------ * Test: Add a container to run the tests @mbway (#479) * Feat: Added type stub generation for dynamic functions @mbway (#478) * Docs: Add new mapping style example in the gallery @adrien-berchet (#473) 0.14.1 ------ * Add type annotations @shuttle1987 (#464) * Test: Minor improvements @adrien-berchet (#465) * Docs: Add util to replace keywords in docstrings @adrien-berchet (#461) * Docs: Fix badges and link to docs @adrien-berchet (#460) 0.14.0 ------ * Split load_spatialite into load_spatialite_driver and init_spatialite @adrien-berchet (#459) * Remove the management parameter @adrien-berchet (#415) * Add GeoPackage support @adrien-berchet (#456) * CI: Use new action to setup Mamba @adrien-berchet (#455) * Fix codespell config and typo 'prodived' -> 'provided' @djm93dev (#454) * Fix ReadTheDocs generation @adrien-berchet (#452) * Fix compatibility with Alembic>1.11 @adrien-berchet (#447) * Add Py311 support @adrien-berchet (#446) * Add an example to insert raster entries in gallery @adrien-berchet (#438) * fix: handle mysql schemas in column reflection @EtienneDG (#443) * Fix: Shapely remains optional requirement @adrien-berchet (#441) * Fix CI @adrien-berchet (#439) * Add auto extended feature and methods for WKT/WKB conversion from/to extended elements @adrien-berchet (#435) * setup.py needs to know about subpackages @EdwardBetts (#434) * Reorganize tests and fix nullable propagation and some other bugs @adrien-berchet (#433) * Reorganize dialect-specific code @adrien-berchet (#432) * Add partial MySQL support @adrien-berchet (#330) 0.13.3 ------ * Fix compatibility with Alembic>1.11 @adrien-berchet (#449) 0.13.2 ------ * Install Python and Pypy using Micromamba in CI @adrien-berchet (#428) * Move SQLite functions to the relevant module @adrien-berchet (#427) * Remove schema from the engine used in tests for SQLite dialect @adrien-berchet (#426) * Improve monkeypatch robustness and related tests in Alembic helpers @adrien-berchet (#425) * Fix compatibility with SQLAlchemy>=2 @adrien-berchet (#424) 0.13.1 ------ * Fix: Use empty event for unsupported dialects @adrien-berchet (#418) 0.13.0 ------ * Remove some irrelevant checks for Raster type @adrien-berchet (#418) * Add precommit and format the code @adrien-berchet (#416) * Refactor: Reorganize functions into dialect-specific functions @adrien-berchet (#414) * Add dependabot config @adrien-berchet (#411) * Use build package in publish job and remove useless wheel config @adrien-berchet (#407) 0.12.5 ------ * Fix alembic_helpers.create_geospatial_table() use the proper schema @adrien-berchet (#404) 0.12.4 ------ * Fix alembic_helpers.create_geo_table() to handle foreign keys @adrien-berchet (#398) 0.12.3 ------ * Replace pkg_resources usage with packaging + importlib.metadata @jacob-indigo (#392) * Add a test for reflection of a view with spatial columns @adrien-berchet (#391) 0.12.2 ------ * Provide alembic_helpers.include_object again @adrien-berchet (#388) * Change doc font and increase page width @adrien-berchet (#389) * Fix doc for Alembic helpers @adrien-berchet (#386) 0.12.1 ------ * Fix semicolon in index query for postgresql @adrien-berchet (#383) 0.12.0 ------ * Improve documentation for functions applied to types other than Geometry @adrien-berchet (#380) * Improve reflection mechanism and alembic interactions @adrien-berchet (#374) * Functions returning Box types now return None types instead of Geometry types @EdwardBetts @adrien-berchet (#375) * Improve the gallery example for TypeDecorator @adrien-berchet (#373) 0.11.1 ------ * Improve doc formatting @adrien-berchet (#369) 0.11.0 ------ * Add Alembic helpers for Add/Drop spatial columns with SQLite @adrien-berchet (#362) * Add GitHub URL for PyPi @andriyor (#364) * Add a simple API to register custom mapping for SQLite function names @adrien-berchet (#360) * Example of specific compilation for sided buffer with SQLite dialect @adrien-berchet (#361) * Remove metaclass as in SQLAlchemy, rework the tests and some reformatting @adrien-berchet (#357) * Use RecoverGeometryColumn instead of AddGeometryColumn with SQLite @adrien-berchet (#354) * Fix spatial index declaration @adrien-berchet (#344) 0.10.2 ------ * Use 'load_dialect_impl()' instead of 'impl' to get the type of the TypeDecorator objects @adrien-berchet (#343) 0.10.1 ------ * Fix creation of columns using a TypeDecorator @adrien-berchet (#343) 0.10.0 ------ * Fix cache warnings @adrien-berchet (#338) * Drop support for Python < 3.6 @adrien-berchet (#337) * Change KeyError into AttributeError in the comparator_factory of CompositeType @adrien-berchet (#335) * Add SummaryStats type for the ST_SummaryStatsAgg function @adrien-berchet (#334) 0.9.4 ----- * Fix warnings in preparation for SQLAlchemy v2.0 @robintw (#331) 0.9.3 ----- * Add support for not nullable column for SQLite @adrien-berchet (#327) 0.9.2 ----- * Add support for N-D intersects '&&&' operator @dlbrittain (#324) 0.9.1 ----- * Fix quotes in index creation @adrien-berchet (#321) 0.9.0 ----- * Add many missing functions @adrien-berchet (#298) * Add support for N-D index creation @dlbrittain (#316) 0.8.5 ----- * Add dosctrings to remove sphinx warnings @adrien-berchet (#300) * Update setup.py to point to new home page location @EdwardBetts (#296) * Add an internal function registry @adrien-berchet (#293) * Update CI and tests @adrien-berchet (#286, #287, #303, #307) * Add doc for functions that are defined for both Geometry and Raster @adrien-berchet (#285) * Add new examples in the gallery @adrien-berchet (#282, #283, #299) 0.8.4 ----- * Fix ST_AsGeoJSON function @adrien-berchet (#279) * Add many missing functions @adrien-berchet (#276) 0.8.3 ----- * Use setuptools_scm to manage versions @adrien-berchet (#271) * Fix insert/update for RasterElement @adrien-berchet (#270) 0.8.2 ----- * Fix __eq__() method in _SpatialElement so it can work with any type @adrien-berchet (#265) 0.8.1 ----- * Fix ReadTheDoc compilation @adrien-berchet 0.8.0 ----- * Fix Shapely requirements for Pypy @adrien-berchet (#262) * Add a gallery with tests and doc based on examples given in #219, #244 and #251 @adrien-berchet (#260) * Add support of the feature version of St_AsGeoJson() @adrien-berchet @zzzeek (#258) * Add __eq__ to WKBElement and make shape.from_shape() able to create extended elements @adrien-berchet @matthew-emw (#247) * Extract all SQL concepts from Elements and refactor Raster type @adrien-berchet @elemoine @zzzeek (#256) 0.7.0 ----- * Update test matrix removing Python 3.4 and adding 3.6, 3.7, 3.8 @cjmayo (#253) * Add reflection example in doc @adrien-berchet (#252) * Fix tests with PostGIS 3 @elemoine (#250) * Add default compilation rules to elements and functions which are required for SQLAlchemy < 1.1 @adrien-berchet (#242) * Raise an AttributeError when a function does not starts with ST_* @adrien-berchet @elemoine (#240) * Replace ST_Distance_Sphere by ST_DistanceSphere @borisuvarov (#237) * Make shape.to_shape() work for extended WKTElement objects @adrien-berchet (#236) 0.6.3 ----- * Add some missing functions @adrien-berchet (#224) * Do not register functions.GenericFunction for sqlalchemy>=1.3.4 @adrien-berchet (#226) * Redefine the geometry_type/dimension consistency checks @elemoine (#228) * Correct a spelling mistake @EdwardBetts (#229) * Do not assume the "public" schema @elemoine (#231) * Add all geometry constructors @adrien-berchet (#232) 0.6.2 ----- * Support WKBElement objects as bind values @elemoine (#221) * Document the use of spatial functions in primaryjoin conditions @elemoine (#222) 0.6.1 ----- * Change WKBElement to read SRID from the EWKB string @SergeBouchut (#209) * Change WKTElement to read SRID from the EWKT string @adrien-berchet @elemoine (#211) 0.6.0 ----- * Add AsGeoJSON for SpatiaLite @TomGoBravo @elemoine (#204) * Remove the use_st_prefix argument and use SpatiaLite-compiled functions @elemoine (#204) 0.5.0 ----- * Add support for function ST_Azimuth @simlmx (#175) * Remove Python 3.3 from the test matrix @elemoine (#179) * Correct spelling mistakes @EdwardBetts @elemoine (#180) * Make WKTElement and WKBElement pickable @elemoine (#182) * Add SpatiaLite support @elemoine (#181) * Fix to_shape with SpatiaLite @elemoine (#185) 0.4.2 ----- * Fix ST_LineLocatePoint return type @fredj (#170) 0.4.1 ----- * Fix docstring for overlaps_or_above @dcere (#166) * Add a WKTElement extended example @movermeyer (#164) * Add checks to _GISType constructor @elemoine (#162) * Support geometry column with no typmod @elemoine (#161) * Add ST_AsTWKB function. @JacobHayes (#146) * Create MANIFEST.in. @pmlandwher (#147) * Fix build_sphinx maximum recursion depth. @ifedapoolarewaju (#148) * Fix typo in elements code. @elemoine (#153) 0.4.0 ----- * Adapt links for "Read the Docs". @adamchainz (#134) * Simplify and fix tests. @elemoine (#138) * Set result_type when using operators. @elemoine (#140) * Add use_typmod option for AddGeometryColumn. @tsauerwein (#141) 0.3.0 ----- * Read geometries with ST_AsEWKB. @loicgasser, @fredj, @elemoine (#122) * Fix SpatialElement.__str__ on Python 3 @b11z, @elemoine (#130) * Fix flake8 in tests @loicgrasser (#125) 0.2.6 ----- * Distribute GeoAlchemy as wheels (#114) 0.2.5 ----- * PyPy Support (#79) * Wrap column name in double-quotes (#83) * Add ST_Z, ST_AsEWKB and ST_AsEWKT functions (#91) * Python 3 Support (#99) * Travis config changes (#100, #102) 0.2.4 ----- * SQLAlchemy 0.9.4 compatibility. @quiqua (#75 and #76) 0.2.3 ----- * Add ST_Simplify. @nik-cars (#68) * SQLAlchemy 0.9 compatibility. @ilj (#59) 0.2.2 ----- * Support EWKT and working with DBAPI's executemany(). Addresses issues reported by @pgiraud. @elemoine (#62) 0.2.1 ----- * Fix bug where AddGeometryColumn and DropGeometryColumn were not given the actual schema of the table @dolfandringa (#55) 0.2 --- * ST_Dump support @Turbo87 (#18) * Use of Travis CI and coveralls.io @Turbo87 * New doc theme, new logo @Turbo87 (#23) * PostGIS 2 Raster support @Turbo87 (#25) 0.1 --- * Initial release (PostGIS 2 support, Geometry et Geography types) geoalchemy2-0.15.2/COPYING.rst000066400000000000000000000020401464355170400156450ustar00rootroot00000000000000Copyright (c) 2013 Eric Lemoine 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. geoalchemy2-0.15.2/GeoAlchemy2_dev.yml000066400000000000000000000001711464355170400174660ustar00rootroot00000000000000name: GeoAlchemy2_dev channels: - conda-forge dependencies: - libspatialite - python=3.7.* - pyproj - psycopg2 geoalchemy2-0.15.2/MANIFEST.in000066400000000000000000000001331464355170400155420ustar00rootroot00000000000000include *.rst include *.txt include geoalchemy2/functions.pyi include geoalchemy2/py.typed geoalchemy2-0.15.2/README.rst000066400000000000000000000015541464355170400155030ustar00rootroot00000000000000============ GeoAlchemy 2 ============ .. image:: https://github.com/geoalchemy/geoalchemy2/actions/workflows/test_and_publish.yml/badge.svg?branch=master :target: https://github.com/geoalchemy/geoalchemy2/actions .. image:: https://coveralls.io/repos/geoalchemy/geoalchemy2/badge.png?branch=master :target: https://coveralls.io/r/geoalchemy/geoalchemy2 .. image:: https://readthedocs.org/projects/geoalchemy-2/badge/?version=latest :target: https://geoalchemy-2.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://zenodo.org/badge/5638538.svg :target: https://zenodo.org/doi/10.5281/zenodo.10808783 GeoAlchemy 2 is a Python toolkit for working with spatial databases. It is based on the gorgeous `SQLAlchemy `_. Documentation is on Read the Docs: https://geoalchemy-2.readthedocs.io/en/stable. geoalchemy2-0.15.2/RELEASE.rst000066400000000000000000000011331464355170400156170ustar00rootroot00000000000000Release ------- This file provides the steps for releasing a new version of GeoAlchemy 2. Add a new section to CHANGES.txt, then create a PR with that. Proceed when the PR is merged. Make sure the CI is all green: https://github.com/geoalchemy/geoalchemy2/actions Create a new Release on GitHub. The release tag should be formatted as 'X.Y.Z'. Go to https://readthedocs.org/projects/geoalchemy-2/builds/ and run the compilation for the Latest version. Note that there's no need to manually upload the package to PyPI. This is done automatically by the CI when the release tag is pushed to GitHub. geoalchemy2-0.15.2/TEST.rst000066400000000000000000000060411464355170400153210ustar00rootroot00000000000000===== Tests ===== Test Container ============== Instead of installing the necessary requirements onto your host machine, the `test_container/` directory contains instructions for building a docker image with all the necessary requirements for running the tests across all supported python versions. When you are finished the container and associated data can be removed. Install and run the container:: $ ./test_container/build.sh $ ./test_container/run.sh Run the tests inside the container:: # tox --workdir /output -vv Remove the container and associated data:: $ sudo rm -rf test_container/output $ docker image rm geoalchemy2 $ docker system prune Host System =========== If you have a Linux system that you want to run the tests on instead of the test container, follow these steps: Install system dependencies --------------------------- (instructions for Ubuntu 22.04) Install PostgreSQL and PostGIS:: $ sudo apt-get install postgresql postgresql-14-postgis-3 postgresql-14-postgis-3-scripts Install the Python and PostgreSQL development packages:: $ sudo apt-get install python3-dev libpq-dev libgeos-dev Install SpatiaLite:: $ sudo apt-get install libsqlite3-mod-spatialite Install MySQL:: $ sudo apt-get install mysql-client mysql-server default-libmysqlclient-dev Install the Python dependencies:: $ pip install -r requirements.txt $ pip install psycopg2 Or you can use the Conda environment provided in the `GeoAlchemy2_dev.yml` file. Set up the PostGIS database --------------------------- Create the ``gis`` role:: $ sudo -u postgres psql -c "CREATE ROLE gis PASSWORD 'gis' SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;" Create the ``gis`` database:: $ sudo -u postgres createdb -E UTF-8 gis $ sudo -u postgres psql -d gis -c 'CREATE SCHEMA gis;' $ sudo -u postgres psql -c 'GRANT CREATE ON DATABASE gis TO "gis";' $ sudo -u postgres psql -d gis -c 'GRANT USAGE,CREATE ON SCHEMA gis TO "gis";' Enable PostGIS for the ``gis`` database:: $ sudo -u postgres psql -d gis -c "CREATE EXTENSION postgis;" With PostGIS 3 enable PostGIS Raster as well:: $ sudo -u postgres psql -d gis -c "CREATE EXTENSION postgis_raster;" Set the path to the SpatiaLite module ------------------------------------- By default the SpatiaLite functional tests are not run. To run them the ``SPATIALITE_LIBRARY_PATH`` environment variable must be set. For example, on Debian Sid, and relying on the official SpatiaLite Debian package, the path to the SpatiaLite library is ``/usr/lib/x86_64-linux-gnu/mod_spatialite.so``, so you would use this:: $ export SPATIALITE_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu/mod_spatialite.so" Set up the MySQL database ------------------------- Create the ``gis`` role:: $ sudo mysql -e "CREATE USER 'gis'@'%' IDENTIFIED BY 'gis';" $ sudo mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'gis'@'%' WITH GRANT OPTION;" Create the ``gis`` database:: $ mysql -u gis --password=gis -e "CREATE DATABASE gis;" Run Tests --------- To run the tests:: $ py.test geoalchemy2-0.15.2/doc/000077500000000000000000000000001464355170400145545ustar00rootroot00000000000000geoalchemy2-0.15.2/doc/Makefile000066400000000000000000000110021464355170400162060ustar00rootroot00000000000000# 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) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 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 " 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: $(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/GeoAlchemy2.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/GeoAlchemy2.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/GeoAlchemy2" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GeoAlchemy2" @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." 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." geoalchemy2-0.15.2/doc/_static/000077500000000000000000000000001464355170400162025ustar00rootroot00000000000000geoalchemy2-0.15.2/doc/_static/geoalchemy.png000066400000000000000000000511441464355170400210320ustar00rootroot00000000000000PNG  IHDRgwFsBIT|d pHYstEXtSoftwarewww.inkscape.org< IDATxyx\u_I@[@BgJh"*( ""~AADEQMe LŢ`lRdkk̐4n3I}=O$3z=)))))M۩{ )p|kwpk$бJECPinRRVG2ýbDƌjr@䛨 ]o66BkF&}FK4pa^JjKjxb#6gNX`]TYãkrf{stC ^JIyg)))fNdQMo:qlijWs}_v WNF+%Fjt0|[(_h}z1;̚aOs 6b_))UR,%%eeT-vG 2tu)L,s#7U6{II.R:޳9()) ҕu^Zv]Ͻ:Z ~aLapT{7us)))5u2_ óZfvtDL9oo! sҼtsj^KYJJ@FlV'oh@3p1Fa޺1|`!6:9\LiT;>T7X//Ouuʈ)S=ϻtЃ4f=“8wH|c)))e X`{h+Lu^0<<sN[/a/kcű>:hR}p'pF[OteY\#w'%@&l?>}^NlS)))Uf3;f|X#9_:qp8 g0C,) 9$y {_A4fx=?VS)@{g`;Oˁہ 0ZjJg=DK1k4TnInW#6YR6kܤRFg'Kns p+ǰHtu^'(kNdBu[=WW`Fڋ![ 3-'">JGs\s%z%uvYAO%0m=''%%% NK 30scsU],81a mzXZPF3Vhq<0 8)ʉ-̐"E {-njQ*0z f(@󤤤9gj?&N3p9f~?}/03F`abo[ aa۱{w`X̨\L‡uşu/`g,}*0?< ҙs 8 cz:!E7qY 3o eAg%4z57rb$&b wDU+O<#E2 Zk2p%k71l 0.4l/ޟ4Ӱfh8,J>뜅]}n,noqA=_[=_mұJDuH5veYs^ė1UPqz"f,F< =Vu\Jkp(Ͱ QXڹXRRRB2yvP[X'&IxDwgyV+F˙[X8u auR$ԗĉYm3QyQ%#Z 󦤌tf` NKN0{K"? E6aՖkI,_sgq+$oc3r:krf^"ڙ waPfY}ŏ﫿E RRV zicy &(.GxlEa0cLaL.˜KU ^=J^y 3n$!qe>ݽo? Зn^Ĵ ꅯ(GGRI^kY)WJlI۠ =g?Kn8B,$66<rF:T< mfYچ9ql%6^aaȫ\ 3,q,Ū9G 10HFHs@'mZJ̆iEj971"[¥ 4ΞÚcp&p Vw;+҉.'ZX0U::DxpFi1Qۍ1qاpϰoox1!ifw`10p9ƩqF|IUhգgDIX /DZ2*ǮXOo9BDDdNbJq`Ք[_S^K3\3K| +9 +Tix{Qm&֜^>2{I6aslIX""oA@&8C%5RR'Rto0#'Ba["M_bSČ-h!(9 cU`l>{_\| =qRhup&Cb1wq8y31]m_So,9G! 8,%eeq`IQdGK1!rl \ 3Z6uK5At6pdl/ɦTz 5/U°NK3aGB$ zaUWY:& Ѯ7v3r Uc8[}%TFNHJ{r1!iy2\ RN]Ve ̐Z>/?"…4/Q!݌ɤy͂=hiKqK Ut?vWI|d Cʩ]YJlHܰj\Θƙq$ [ ͻ fXs5Ί,3w4 A՝WcQ&P-Gk0\3N# %1G?3 H2']jc8K,l$q>V$p40%0l8QFԔv-8_ݍX ᠚dX(2NUepjpBy݈CT1F oiyՓ0lAYy]H[e i| ,˧[%9FUѯ?55gUk0:hhYaj̹̉t_v놳2UQrl 6;L! ?)nrsXc%y'^{1Ɍ0ݯeX]|3 GZUp?{6{xSYF RP5=?s*̓amex}V,{-p>_-9׳8U %kAWN:W2Wh} vӗñZٙ e0]=0q oXNRNGc܀qyh nz_PILz熕XZi q3NBW + 8hxSqE{ER]h,;&fN)iϪV*|vT{Q>?_~P}*"7fNdM^"t=6P Չ̧}nY9즰[gΝ)|oZzi Ng6 ^x396h9d?; em=+v9cG2pX+ EW<#qAu8霔bѲf`m6y)f@ӭ6Ů%<ێͻ>JF`a!ͩri"2h[݌ʢcerf] d.&8;J\4b{Qx?yʌɌ>'kz\Sf#L%s!S'D3=U3v,>'s ;nǴx9+Z9MTOE8imԅLf^v)GܯL~ٜqcW8G)rC+p@[ cRS#*@yor"-So{VLU gxJ&]YsFQEכ`Ō&g>CLia<'}g7p ֬'Ŵp((Ì˅9G1@>}c`Q̃&1 n;q4pe4Xt2*zf)j>؆d/{bLm ʷ8yTZ/hVs B8N AA-qr;$F~S$Eyn /M:'X8V ΡY/rlWXZL /]0]٦v԰Oϵw{5U+؈e܋"s|6y_kzJ &}0RqK0xC )vczf[bٳ 5`X˨EH+=_]T@Q03I0w) 9kX#[ݧcfk">uy cT*Hȹׄ,>(z>&cX:Zؤ3>$*W5=݇239krSdf-q9oMY{SXJJШc8V:[5&aq6mg|"sR0jf Y>=>wvgKfjU2ξV徑CX *݈m__iؗnGsXˤ \7`-nХ_ZW/ueC|Mؽ ؑLG.s*Z 95^+'g>;[`+<(l,扗2:[ڃ3|9QԝkFp"r5 |:sM[8g*kT L۬\6QC3ՙsQoNJT븎=DHT/-[3c/W5`;SS(,kGܷk KRH\@|7aw}@B1U C,C9{j2O/he(8SޑsN׿4 'g>\eQtU uP?ԑZΖeK<"DًN+w[2:3A>vAOha:7feK^w Μ$]  /ڻ+k:8u\:'qEEHòKb<`5一kL*x#د`Gk甹trGvdLȍd~zXw1|6*_Ͽrӹ,WH(W S$^&lu4M/n|yݽNkG8b}ˠjͧ6`|{A~LPA91-r>[Cл:[܊}xM[)pDTaweYk|bqLRqCQ}y8|΍0 ٜq{ J*W ?xT MX "[jGtLy#^9yg;n4N`r x!C=&VwosV) XoωkP^췒fmTw \3 1FȣQYR+wICY96Ob@񌫤[Cm/C|#?ɭfxz|6I,Q^D7a1+hcIwe:css-a_ :_|{>眲|;ōmлT@Qn3lʼn/zT翀US%n>~1mqValOy9&XXs &~jMiTdn߀JSɛ7,ybh_Or?,uIg%4WmDbz,)sA1()p8\3sbEoLfˣ.4R/s& L?LoYp^Ib~1u2 .Bhqw4EzE5lOI!nw'V 0IgT@4aޯ<{?yL3nۧ**6f(穿I)G+ޫ(q$&z)#M95iʻExM*wG޹oQ-V 1[ϖ_c:c_ER+G0z5c-yD8stxmTr zXjR{g'4WU4F<Y *?l>럳#ꗯgD.1U ;ڄLso a6m}xȮ$cۜwegy[8"=g*\m\ x\.r'ʾ3'NP_ X8 ߩ_o$33j:`J^FuWU d#kځ#q@hqO#HhAVsx%5?'*,Yk y}YWV?OH,l!B=suxYo:Y*dו͜WTݩsʇ_fIkw_&wRz[_pzSwOY IDATBxCDvko)|{$FQ&W0'T7R'@?5oYrY hUbp>#ЖJIQR7+P/$Xј:4/ahaÃ+ȵAW%=ۻizBs%vfH㮣&_ Ζ>ⴞYj qsjpkRۭxWqy7 xUkfuNrPBL? |k+X8CZ$I*"r΢I~kkۭ_k/OR9}%}gvzBkJ|5njʬ3&ԪQ//}ZT~HRRعd}xϪXt1(5r&Y Bcf$o#)8uO!X>P}]hU] U s Pc(TPF Q)Z2rQkwV a: bY]ϱ:\GsgMUf^3_ۺ gh{J}#;;,w^[㮣9p(*-|W| ʶ9W#wa޳ 9I52κZ2;aP8Ho7rW]ܜ2E~!>oi}'>]Ob},Κ:}̮m+0&Z=3t!zo6O,pfmեwI坩_ ^ )ZHĢI~(1AgJ)ř{\Gu)Wwa 8̋9>,SS'hJRwz 27N_qms3\qΗI + d#׋8GJl<Զ*'o߄âXX [UWNZe~?{s$ V8fP}3-D\$OQx ^oBcޑx =Ho oJRMO3 r8>!fW#3 ed,O,N=1 *󨟢=(UN'o0CꯘGoPVTs&4W,iL@FV[{ EcwPIUH}b  )S?SȓSV]Qi|OL k眩FT_'FhV_ti7#eCXl}Ҫnq.m:|weY>,){G|RA 1ؗ5 `Μ{ZSv59 "a1s9K"Y X~`Yqc,XU a\З0Q,' =>W\o6:%V(.|0u3 7ׇazk?ڋCnРE( ='Q.ͣ3ACi4q歎\}TaVB +*EQ W˶xGk՗8 #$@b8uԯ9㼳H5zBdž%y}!f5T.}B4](.$k.p-VC;8 A=QJk0hOTXu)A/Ӏ|-!ϋt™S*TE/KDh7u'9Zq'7K)scu"VaD6{B;n*[3bCUNNrAx/XaEL{ r^΍PU3uq21U&h5/A_0i)XH;cUI$WY9l)@IRDQ'\_pR 31czNqs`I爴nL_)3wb Lx\:5FKRhv\XoOlu1͸FC' ުiq8v[b-b手}}W|;IR:TQ 1oXui=iVſt߮Waʺk$ڹhRaES>2*zSS*oڡs` P<"ǰ6`V:̺_}\ 8.j4ޅY(bЗ/!E嘌d*VnΚyAM{L>.P+D? +q’A}8\叕R:ͻU#0٫XQ^T]3xl9C?Em4H&*"Կ{-`LFj.D.Aš ʎlըƙV9o34Z.jL~3![̉l0tUb,RGzLGP,k9^Gϸ>;wCR""PlP4 Gp"΋ 1j24j/}.#a &(=c.8 +Ly3eР'{Z}XlŊ&W8":T%Rw XHK;N6t9g4F__, RKZKww\TWDǒb'zXӏW']B fE/F5wXZPM^t=wxrq" zE:؇;6 bmRm}}#^ ևrуuތ5]2=bw },@JtlX^'H,NjV62AޑoqD2qLll 'V{D8i_d5fsO1] xn[X]yDAjuո-t׃y҈qj<S=,|sXyXOӨ~[5;þ`Nkh&̘60L,A){'gBiP>I:AȺ8kRӴ|>2pE4$]j,R~*G®6gI8M&Y}ۣϢ\Vn#( bT &(&.nj]UYВϹFqD=ZZ:a$ &jcsიXRylx?&q%8n7؇Z{ꁇyQվ,ʞ䝀cY&eDSC {̿Z`AbPMxƙLiz9Z `[ܫU-;s}wC<*J4{~E"WW2T@7u\#@@džgSu92BQ>Vq%j_- N=NbO0#K,Uxm01h,=۟hU$B"m'-PrPXs#@3P.S{5!5E/@ZbT/Npʵ3^Rq/2!Oi1E#%y4r<#'bq@}S.y+UQ+8SHDԱ͒uldž$~c|0ۻ w׎xW^1NnOC| }K>LJ }$02} f4vˑn_ P*\b/b˟(+ VGtyz ޤ -1❅- z3~ fSG&񻍼q<7bO"׻?W/kX>2*9ӈ>o?*=KlfWT ϙB~F)h'MZ^@Q" (l-^:RM 8f̈nh,y'v9r =VxfEr&j~?&a+ϧ|qծ&bXNXmO<Y~X>۴Ž~fyH On}Q3 tjG9rY?wc7g*͓sc.%N":&ev'W wV5yޙAJ^EV.[r:'e>򥸋IMABi,]Jhtm@V UK?8{6 }32r7V0̈03^Cx eO؛a)P1Yd5ێm+5D{yL^2za_Ea_:\(pdy+kd(./^Wo7zs:vaxmӹ,|njɌ^+ Uۻy֑'Uv~b|m^ap,]Yw_!qiIDATaY$塸 `9asSM|%q& =Q,> 4_a ,!Xrl4c0v;@+V!i;,>0fǴJ4&@tQ== =L2%֘0Clm"2UEhE{GgM9 4+++*zd y>J2HHky/{Ly{gWLx8ǫzєf~4>31s"cFg[#9PoqG! ,W8ǫ;{s $ 炨\4pTnI|}!b{wwCSr0D[4m0bܱ uH5ac+ķk' CH?^rGoֳs_j`SŒɌt;$ߌmռd*(gNdQM,'й|kߑmVW3+ZAHtS?Qsvʢp˘ {3y2˱"~[H.{0*zC44g}V:>ZM5}a^"յ0B.X qtd> 4 5u07u4ZG(KZ{5k0⠷wxGwWȹJZl[u.+߹1jɅ+`[s&ϽƄ zJky:ZEf큶oV091:Eh5i;0X}sKC`4i_Sl8rvġ Ee$ 3服]M~TA?=X>VXI XNHU,+{wXa̠m ՚/ΨTY*b8{R<>JTڈ$TZ. zhkOS}x۰rDPA3=sS&'~f IXf!:wt3D[{mUlClٌ`8 QC6MV>G.+ŝا8T[[4]cʃ'E`R0>r1h|~U% ɘpÒr>:C$y19?E%v>twa޵G笹$〉,׾<9kyalÂo/|#/[Npj0i b!<ވ?yzo`:ȿTYkwm$t)8~T*3"*W G6FKrUacs@Gory~Irb#,tF&7& ^N?).<&q*v#\AR#yd>$,|}h-F y0\E%ݙzPdžes '7Zw_6۴V]B+ =|MdȞxDƃN@O8 &B/>4A--c|>Ao)D:/swXK-ĸЈ].̸ :'{|@еPDqAYCf,z hȚwg5o,]*u:7fC2cQ]a]+2~r#D -6B"Dʋs=}O|ْYUo"pXg^R:!U0)X3/1nwV9ZK:6z݂U)޻r$)N)"fGcaMeu6DiDTLE|䨻n.{ 3q *7=1 [}б!c+'6  ԟάs\NPY a}RgR X_%"ު,\ZO1]1xRM'l}z՗ V$E,7<^aanc^1Mz)SRF:|N\o3BTSRc\'xwT9 Y&Q,7(Ɋ>2L4uicA3əybޟ!0U 8 LĔD-᧷/=wkb`k`r7 ɃIIIIIYu(ʭ܊C9IT] b&P2S\o)# #ћUoƸ 6"7%%%%ec3֕>N!ous_q&Xh3Z?a1Ћ5q)#BX[0OY} >S=kהR,qehĥ }=UIq o>!eE1_3)a{McMX~B'Fu31RRRRRRRV39 L 4hYzݦW긭1;ܛHIIIIIIYt䜓q!Ie*&›țf-gtmpQ+UCzwt8R{!˽c_fXSc%n&5RRRRRRRJH 8B><3-pjCҘxX;G:ħn-%%%%%%eU3U#OŝVXX e,GYY%7^+ΗSSXVm u;d큩Ec@NoaHC)) 8s٘%&R!n3ʰ(=O@c$IROB D#E7 ]gs-tSs~=usng=~{}9_6SW8o07\jbV% ~⊲ |` ɞmK#"""73VF͘b8fe`NxwpϫՉzБeżv5ebq6\jO$iNDDDg?nO;Z2|Sܭ*u1Hˀ~Q\c\o&""r;HchcTߎkڲ* ~#N 50ٕv""""2}g`h=݆LדKz8U$[bmɅ k_<|G[S/}) """̾,$o ?d`dag`~5km'wZEr؉+о,Y )Ls35 3{lȆ`{+S[Y 0k[KOmAX l_Iu:QZ4\DDDb,7qV2vSoafϳ+qUl]~纘2 <L*DDD$DgSX: #ǘC 4o< nz 7QW] 7j8DDDְxgk6ȥO,u:X\6@Gǎf xl6()\p[UdfWZִis13.=kqITnu H5c+L YH9H#~kLawrS񢿈HMt4f> ϖtӟv>q:N^eL 9].VřH js+_=<8n_nT`범f fLDDDoVOAr&IENDB`geoalchemy2-0.15.2/doc/_static/geoalchemy.svg000066400000000000000000000415721464355170400210510ustar00rootroot00000000000000 image/svg+xml geoalchemy2-0.15.2/doc/_static/geoalchemy_small.png000066400000000000000000000417211464355170400222220ustar00rootroot00000000000000PNG  IHDRfsBIT|d pHYs//;;tEXtSoftwarewww.inkscape.org< IDATxwXǿ{{ nFc-j5j&XbblQcO)F[~vT숕q Wi vofvvvٙ0:P@ǫ`ffGDŎ6xyyyX,f 355XXX3 )HKJJ&%%i&4IjBXzXZZ+))1J=Ͽ  o*AT}\vvv^\\,%sϞ=1x`dddӑeH2RXUF@&|>D4elڴIc:X4#%%O<ӧOSܻw=X, ~H$շ5 ifnnN>>>0 {ld ͛uV?~<@ (><Oboo@kmX;99} =B %"e˖GeQEvv68pFE...edֲ5Tk֭ں:iϞ=~ZbՋx<'M-؎þEZn"X,WҼyaÆj>?88x j5oA˷C6m:d||<ՋMN"Y6lX"7nܠcǒkrppXۿ*R+++o U۝;wTvݻw%1cPAA?~7v  .ٴyfjڴ:AN E?υLW{۷oԬY3Np*..V oܸAnnn>穽p  \zz衲 ݓTͩR hԨ?~$Rg*))+W~СjtǏwיk׮QnTU}jXLիV HΝ;I,kL.\ Λӧ*fdd͛SIIѸr uE},--Uݫppph*ќ9s@Kl9&&&4d|ϟ?ӧOS~~1qs)SR{=077[Ya655CpQDDN"t-u%%%q~$kkkrMrV(((瓩)U-XB/_T1"""hdkkjC6lׯ_ӎ;T믿Vpw.?߿OڵS:uꇋKC>ۀ()) 4/Sْm}Y%tC"֭[k޾V VZ dĉ97֮]Kvvv*ڪJWRW._޽KSlºuv\^0;öoߞ {yyѩSn: RӫW/:~8edd=zD?393%Uu ''̹f333IӦMV=AKLk׮}j+Vrtt#GhQD"]p,X@Çqɓ'˹{V6lgBBBU,l===oNQ""믿MFnݪnWz*999q1$$ 54zEjӦ wWWW9s&%''WbWő/M[ju2:D ` 򦦦RTT=~X˗T~}N۷hH%<ϡŌG}T}?Czz:nݚؤgԛ7ohJخ];ݻF;yff&"""\bTW {ٳ6mtʕѕX |//PxK8;;i޼ye:}4XPP@| }[hBj޼9gvM6s j j-11ql?$$k֬AvILKJJ+$&&"!!s$''tI&k׮eW$k׮p:ۣo߾hܸ1|}}͛rՒ@@C"""Wr̆ߟ D :PZDo_DoشZSmգ]vUi5 !!<==W܋[hѤ" GGn&&&*8lmm9FO诿Ryb1{-,,ɓ'oYYYtMhܸqԴi24,,L俕w=jʩSmE >B(ԩS'8q"Z9Bqqq=FQQ-^M߰aC7ŋlkt5ڰa 6\]]9{Vuq<4k>j>%14Ci̘1~zthuD"wU{9$ Tn]M2o<=Сö M6~hӦ mܸ q&L`˜:u/YYYԢE ]RjGqq1oߞ-שSЭٽ+SjZ!::Ue\]] :׵k8"_ל!g}%ujC:ϧ˗)*ɓ~ܹs)"8c_~Š#OAFVV'x{{9xriɬ+a><={`МرcVZ2}tN;~T9=baaZM;wc`jjJ!!!ԡCvv޽7bzzj_!///-\\\*U+L\]]!'ÇwA%-88X8m1c[^^T9۠A+U'A,--+|:ٳ͹y^^^t}ax<]zH5ݛFjUi`iiYݻwܸ\Zz5M8V^mǵkײ5e#Բsb??D, w5kצ .!+bRB%Kp qB={7QQQ̛7Spurrʁgt9>>>E"X111 ő#ӂ֭[M6ĚT=lll0k,v?==׷Lۑ$%% 4l_ƦM&FqJ!, ))kR56m^F<==@n8%;#--nU2ֿe˖ fu%uI!"{"$͟H_}'@PFiܸ'poÆ y &%%uʮR#77Ymkk+(}GDg(Sk&DfQ󸹹-\ߵkʂE"lNqO*QFʮR/<_/"EDQ>}CD~*NibggpwwBYm833jg1uuuTvO>%y n''j.Q tPwO"!!yyyCAA666055Epp0ԩ:OP@`E $+%Xx1.\hٲ%Ԯ][dN:={B,q%ԪUr+Vҥ ev.UxE=)H$t%={6uؑcOiPƍ'?\xx\ׯ_7cٹs'GݻN:͛ʮVaϞ=z"DND(VÇ4w\UiիW+$%%711acxtYc=zS1*_~#$-ZʮV@(r|>CE H"XҐ!CHQܘbH2G?Ν;)Ν;F1Ͷm8ף?6`\y qz˗/i*̙3Rڵښcye?bR73<./ʕ+{rSێnKD: /:$ m۶Mmޖ-[6iԨQZ5ڠA2ݍzC*d`kǎ]*AII G?8ɇQ\Oh>Yh&DV۷ԵkJ &(>>z VXX߈riժ7?P k<)-"@5<w~:y:u,*wtR0 Ә'""H]f711ĚT-zjkAV\PK# :m6%WYXXPVVOsy.VYD.sܸei ֭c׭[G\FDMJ"\ .tP:tHر#o7~U˗63sƦm۶by9z({ѣG ?  p5akÔ"}}}1k,߿<@NN=͛Q~}˽qi_~߮];UU9v:uШQJMգsΜ}SSP"Z+:(=MMM>Lb>88ۧ'b0HmРAZ?I|ڬT5k^ҥK+:UÇsLݺu|I~iƍDDo>RVחbbb)&}DND|Y<"*!^-Zк䧦ǎk@WF^*5HR=$z(Ӎ1zemM6UDDD^A~8Tu#ɿ=6mjpW6diiIĄ={VU?|~3 #x<&O\fG_QXX?+ѨQ#>}l@ab(2D Ν;xy";wdM JQE>rX,f4hhP3 :u*Z???>}ZhLf+aZSGOFt*txw3W(,,ի3fTbm>܈# ,,,j*'ffe?'OhIc9#88?|y۶m۷oHo~۶m+FU@]ٸ{zRo?o.'"'W_}yٳ9/^sT[y撻;{lktNS[H% PFD#GKBDe|||ˈ唱h"ZY`[kkkEC`kk˶ۊ+)g2]vM~M3%%%UD>'+Ib}EDxB'KݻwPle:yj$$$3Wh]jCD}:1DH3픪A/O\s) (kkܸ1bεw^NYV2D"Q҄,xV?׬Ys,iFD"]v 0 ٳȑ#Zә3g:Y$l|}}(77WsJfǎJmqʮVal]Z?Dt@"jI_,--QV-ppp!%%/^7s˗/nPmۆ &+Vg}>CA"L\`W]L :ܻw-[d=\\\tb^C)'N֭[ׯOUScƍ=*z0aD z?MD"G`˖-o9WQLE9[lX->2,B>( ֭Kw՜S~ug`b1֬YU&O~PRR?S/^ Ƙ1c<9"eMۗx4 ^+V^dee{x1B|||Z[Z+pA<{jg矱yfx^;[F:R}cqY={>ԫltC AÆ ի׵zK,IٙHڰ GŬY m;w.x<fΜYn+0L{a<߿?HKKC\\o"++ 999()))lllI&h֬:{2 sDPEھ'-p5f~u4'C(?kA,c8z(O8:VF!cpYV TEh~3ڵkgSgG= bq|"cA$EHٱV|T,›>-X#fff9r$tziiix-_G*}\pC tdՉ ˿A: еkW88αo0 s 5@[CQX N1 j!c$b,I߷̙ \Ykۡ?Z  5ZlΞ=iӦlӧOΝ;:IKKcH$Ho]!0L6c @-1YR--C1P[WkNXWEk^o߾J¡Ν;ݻXv-,YxkB `Μ92e .\WWWi XC) Jk<==Q^=a*k1S Hd;YYYeՌDWjNW|H$7ŋu>zwyGeXYu/_֫;aXsz#+} 7-A,Nk7|1b\\ݫӹ믿: ^~ʮrrrÙ|%b R Hw#*HھMNNv "YXYv]a}Ch۶-.]L6 ͚5K>|8D"LMMrfR\\Nfee  QXXDzEvv6l<Ϟ=ӧO24i+W*~Vqqq zSÇ;:pUBʀ 00  ɀ~,,,4 K!So3Oyw81uЪU+D"ddd[nϝ;HDx'D"/#<~/_DVV2339  ]+0;wW^رc&../ 1@ z˭j0V:ۋN/G(10a@$E H$z)%ܠzo0BBBtR̞=6mJ.joQTT;w[ng۴i/^TզMII >#<{ ̓i( ͛7"j,ZH/ʒ}`F󜤖4he,XYYA '} 0Q&&&mKJJ/۸87FѠN8 )9f~9{L4IsFFFUVx<7BBYYY PXXhuօ=zrGF˖-Q^=0  XYY֨ut4E ///t QIRs&LHk֬94,; ܖ͛7uV2T[8 D1֭[ajj-[. 1y IDATd>|6mb 5kkkrK$lذAuAAB!QPP"'|999!:uRS,㯿Œ38'^|YVZ֭>cGL}D,)((X_7˂U+5ѷ״""gϞҹ5h"2wss˒6mB 0Q4n4/_‚kJOO\ĐR;T 33&Ns4dSSSZh(Ziۀ4CyFMl>3k, t C!游ܓySȾ2cNclP ǏgϞ) d4mW^|\?#=z%KCZ D"/^/ɷ]`4.077g%0h $]܉x111h޼ڛHAEDD>ƋƎΝ;g)RSSjnCX[Wd3MМPÇcȐ!evKKK888j" 3e~W)))얓d;w7A ];r'`6L:7ofddd42mJJ^(%ggϞ26O]%KoFH񥖔5 3Ѹ I[Fͧo߾8p@֒x5ݻ(vS)X dUwww4n]tA1yd8pwQQu.eddD!#V2Fʓ*üy'GZjm4^#mN篂p zh_ѤI|8qG7H޽{˛LUbJ:up)4m 'Obʔ)Z[dzfff(0 P; *WOdyKHˏ?U3u $**3EP#vcne{ޙ3g"-- +WD=@ 0ӧOǷ~+V`ĉz%S ;'''۷СCZpss L`|qPNXxІݻwz1k֬Yϝ;ݻW9y@颣ܧ}WKfTGEDo^ -p=OAxP àcǎGpp0;AJJ ^~$$$JUuPyf }ZoGm>pS 9sNu'sHt41cƨ{vi{ꑤO"*{<{ 򉈨@׏n߾fff&'ϵ_UҥRgfѣGrDQȭѹs2(b3bnɓ'w U ]!-ܾ}{);-%H0vH4yM8   O W/LCչJJ8myV7G0`@!iii~kff&NNNI> &>>>ܿQDJM)$u^WNdɯ\tN[O?E"e2u떆+7&m^q~8)# u6ʪJ>}Z5<ଗFw<///Knn.`#VFϢ|Q:(8 -ZoGMlҥظ~>Piwo&( 5-px-+6Ͷm0|kK ׽N?6;-{@ P۷o1h +Dߜc'O.sDtiܽ{wuuJMM=9뿲$N?.``=$"p* PN:Sw #;XLa߭7[D نd[EŎ;8jRl uǏ__tx:v+N:\ٍ7r2%$UGDDԀ)"Ð.73NQQQ:_N7'm#G0l0b`hY@z )bM:9SGZ;֗Ν;#::ZipYoߞᣪR\\m۶q1Bie]hI[[[ѭ[Wcxwr cذaHJbyzN K} OR-Z !j e'N>`?Fk1@:y:Q$3M0 ˗/~eDEEk׮z@T'Ngabڵk@ԧLիWWsfPyԩG=44t͛7pD4&@Ӳejc'0vXl.UXbΝ$f ~QU[ۢ'F=хǏW^J%!!!&gs1ۗ}ÇjUۏ;>}X#UX[[([0fHFQn]:uJgʢG 2d &j'4tu~@\zmp'*d>Bf(oƐ!C8kK,Q+9Bp RD4HCԅ=2|4j=z→2dUCJu(ɨxtggg9ssurss1|pL8Wg̘tiѢ 6e8>|}}3߿G0 iD@c5U$ٳ'.\6!)CʎR=[bJ#["<<3TDFFrA0 ^۷d?k<...C! * ü43@ *&TS6eХ=d%)ÇGddè(tܹBB(:0`TD5jGcI&gڨVa8-ڟ/Ɠ'O0}ty zi!֕v FH sHpp0_r?y .,+. _s9bbbr>U`\ݻ  v9swEhhAee惯 ĺylھ Zu`b{G88oYj9! g 1xVhw͉ˑtԉ_HMy-6#G8*֑hӦ :ƍ:zADTX8}pE\|QQQx)^| ZhAaĈʕ+f a3l4_&S/[gS˜v SU|-[r=x(|z pHIIAhh(w֭[z^DdPH?UD"Ґt磠|>NNNpqqAz6;wƹsƉa1e[D!|kP1 ܇FÃӊ\ F(C߶mƍ9V\\Ν;s?=!!R%:`a|l#"'Hv[0v 055'<==*q!177ǖNMڮCV82331aܼyرc iت3dʣbެ]Z2NaxzzrKNBvv6{=%ѣJ?#SSNC82>ҵat;Pat/`ƌ-1daĈxp$hL^̩S?kCrv7o/v|/_F:u $s7x:@Xo;^'Ptzm6sM6{._̵t&3|ރyyyshke b1cN +++F!YRz6mz铬[nr = 4sV!-5'.D"ݎ3XD 6`Μ9J8qBQ˗ѯ_?Ljppp˗?42 Dd CCĂ\|Νٳgc'6ԯ_g9|Vplû{Y R⬬͚5{Ϟ=C.]p9=*0x"f՗ɓ'TچSÇ1qD=z޽{ag}6GFFW^??3@D }HD/}Ҏ;cǎdff?_CݻwD"QspvUl/wɔ?oFL"M-+oJвt.'77-Z?͙3J|"I988pԪUȅ)Vի4vX4hbzЯV* se9_tҤI,7i҄ud?`1$EGo߮t_&MSB{ruuUy_U766\\\8<==} 98DTR7@eΝ;m",, ;v)GP(:u.]^J=ذaC߸q;Ν;h׮S377,^k'|e \~~qEk׎ٹݽ7oGk̉(V&ԫWJ[(n<^|D)z@9hMbDt>#"8gv K=x@"{-lM/"Hih-[6Oaa!]rVZE}!@P=5j,k߾}JC1ggoooüjV*u| }7@@aaaԼys򅅅)58?{7שC%|2$y"r߿{#_{{{:q№Kq~43ĶCyܸqJ}aH,ӥKh̙Լys!lll_~Q{N)DGU#jbWT4k ^*;XZZUVh׮ڶmVZ!((ZrJ̙Sꥅ9NՒY(|C"g7u 6a&.IhR=HII;wn̎;؛j*̜9S9%y>჻Eœ(zL纫ìV j V }Yzk {{{ܹs "ٳ_}ƈX0 aÆaŊ*CO>ƍ\?a~~~Y666ݻg{ K,]кukm۶ Urwɻ*ՊG!00weL0y`شìkoa="b&O||۶m=䧵۷s"ŹH? y7@E/"b \2j*ܧ5JyG￯4/s?Gpٮ];,]TɬWƋ/0l0\rs7ŋ->d1ФI1 `!,, [FzZyR 6飿aÆEΥx2Nu'W4Di*w}nժUuŚ) 1X9߀eAll,ڴi\!C`޽Fnp &&&W`ggGGG899ݻwGPz{Ƙ1cb4n8ݻ=Ph<-5ťҥ F]ِͭ4"¤It4i r.@'4L*̙3c7nxtA1>>[K>}gL$ ۷pGFFz$W_}ݻl\WTT9s`ƍ|>{µD& սL|>QQ Huyl28iL7dCҕ?<ٳ+V\ٺuqx<+ks*u?zf:#0p@$$(cT 6L)L^XXN:}EEEaܸq}6總kZ.#itXܸqcE(':~0 t]e;J6 7o,--@YݨD"ܲ~:^VufJ3Z3uT%MZî]I5jcǎ,٘?>~2&$$tJRܐ-dŠ*HNNVT$3.!ZED'ee+xp9u.n:*:r߾}qE%M[MٳAAAظq#Lsss ]@ G IOF׮]5f>|8l٢ՓB(>S>˗/ @ qn6$EķW0̖+BD~Vܽ5qr_m,,.իh߾ZخX~=ۘ͜={6Ξ=^7!IH,SzZqttcO?D?VR(KII͛7 K,/ P,GHaP˶'|#77j׮ս233u:GTTۗJ{8Cq6m|Fޗ]#G^͛SڵV=uF)KDuȁa"KDOK$"0vZО(*V@%88-[͛7O>*SFm|VQn.fڴi1eՑzՀ3gθM>*ՕKzꕱ^R%Ժuk֭[:WTTDnݺ׍7~ؠAF5#"Qqq1=… m=_5Ǐ?RP}t.MQQ:uO7EEEQAAY]Ӻuv[D4Av֬Y[ (""B%Du5Ν{>00PP{Z6IDAT kJLL4W>UVQӦM˼Olݺu| wݻٙ6m$?""'lпoߺ|vIQhƍe"ٳ駟R```\ve˖/T%h )|`?ze7J hڵfAQW1ᅬ3fcOO2͛7I&Ѷm(**JY$} t}iԯ_?xڶmƹ2Pԇ&|@eddСC8z(bbbv}/VZarX ` 09Ƽ|q޽]p۷mll ???vvv=ZU"Pl ;;HJJ«W+Vppp֪UTffϟ+k0V#q^̓ԳɗT*!&&@FF aeeGGG8::͛7W>!RA\YꌮR80166v`LLLpTTIEz__ oo|p=3C=냋KGw Ƶ"erFRY4@CAjH>k.2 n} *ٲeG\\܀III =lD"Q Ckss󨜜o޼@M&)\) TPLLMM0 T"D"B'XlVRRŽs)133211Ibq\FF]K7a]I%#=Z~cIENDB`geoalchemy2-0.15.2/doc/_static/geoalchemy_small.svg000066400000000000000000000507371464355170400222440ustar00rootroot00000000000000 image/svg+xml geoalchemy2-0.15.2/doc/_templates/000077500000000000000000000000001464355170400167115ustar00rootroot00000000000000geoalchemy2-0.15.2/doc/_templates/sidebar-about.html000066400000000000000000000001671464355170400223240ustar00rootroot00000000000000

About

GeoAlchemy 2 is a support library for SQLAlchemy, that adds support for spatial databases.

geoalchemy2-0.15.2/doc/_templates/sidebar-links.html000066400000000000000000000004431464355170400223270ustar00rootroot00000000000000

Useful Links

geoalchemy2-0.15.2/doc/_templates/sidebar-logo.html000066400000000000000000000002241464355170400221440ustar00rootroot00000000000000 geoalchemy2-0.15.2/doc/_themes/000077500000000000000000000000001464355170400162005ustar00rootroot00000000000000geoalchemy2-0.15.2/doc/_themes/LICENSE000066400000000000000000000033751464355170400172150ustar00rootroot00000000000000Copyright (c) 2010 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms of the theme, 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. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. We kindly ask you to only use these themes in an unmodified manner just for Flask and Flask-related products, not for unrelated projects. If you like the visual style and want to use it for your own projects, please consider making some larger changes to the themes (such as changing font faces, sizes, colors or margins). THIS THEME 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 THEME, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. geoalchemy2-0.15.2/doc/_themes/README000066400000000000000000000021051464355170400170560ustar00rootroot00000000000000Flask Sphinx Styles =================== This repository contains sphinx styles for Flask and Flask related projects. To use this style in your Sphinx documentation, follow this guide: 1. put this folder as _themes into your docs folder. Alternatively you can also use git submodules to check out the contents there. 2. add this to your conf.py: sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] html_theme = 'flask' The following themes exist: - 'flask' - the standard flask documentation theme for large projects - 'flask_small' - small one-page theme. Intended to be used by very small addon libraries for flask. The following options exist for the flask_small theme: [options] index_logo = '' filename of a picture in _static to be used as replacement for the h1 in the index.rst file. index_logo_height = 120px height of the index logo github_fork = '' repository name on github for the "fork me" badge geoalchemy2-0.15.2/doc/_themes/flask/000077500000000000000000000000001464355170400173005ustar00rootroot00000000000000geoalchemy2-0.15.2/doc/_themes/flask/layout.html000066400000000000000000000016511464355170400215060ustar00rootroot00000000000000{%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} {% if theme_touch_icon %} {% endif %} {% endblock %} {%- block relbar2 %}{% endblock %} {% block header %} {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {%- block footer %} {% if pagename == 'index' %}
{% endif %} {%- endblock %} geoalchemy2-0.15.2/doc/_themes/flask/relations.html000066400000000000000000000011161464355170400221650ustar00rootroot00000000000000

Related Topics

geoalchemy2-0.15.2/doc/_themes/flask/static/000077500000000000000000000000001464355170400205675ustar00rootroot00000000000000geoalchemy2-0.15.2/doc/_themes/flask/static/flasky.css_t000066400000000000000000000150721464355170400231220ustar00rootroot00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. * :license: Flask Design License, see LICENSE for details. * * 2022-07-21: Minor updates by @adrien-berchet */ {% set page_width = '1140px' %} {% set sidebar_width = '220px' %} {% set text_font = "Garamond, Giorgia, serif" %} {% set title_font = "'Bree Serif', Georgia, serif" %} @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: {{ text_font }}; font-size: 17px; background-color: white; color: #000; margin: 0; padding: 0; } div.document { width: {{ page_width }}; margin: 30px auto 0 auto; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 {{ sidebar_width }}; } div.sphinxsidebar { width: {{ sidebar_width }}; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 0 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { width: {{ page_width }}; margin: 20px auto 30px auto; font-size: 14px; color: #888; text-align: right; } div.footer a { color: #888; } div.related { display: none; } div.sphinxsidebar a { color: #444; text-decoration: none; border-bottom: 1px dotted #999; } div.sphinxsidebar a:hover { border-bottom: 1px solid #999; } div.sphinxsidebar { font-size: 14px; line-height: 1.5; } div.sphinxsidebarwrapper { padding: 18px 10px; } div.sphinxsidebarwrapper p.logo { padding: 0 0 20px 0; margin: 0; text-align: center; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: {{ title_font }}; color: #444; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p.logo a, div.sphinxsidebar h3 a, div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar input { border: 1px solid #ccc; font-family: {{ text_font }}; font-size: 1em; } /* -- body styles ----------------------------------------------------------- */ a { color: #CD2103; text-decoration: underline; } a:hover { color: #FC5E1E; text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: {{ title_font }}; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } {% if theme_index_logo %} div.indexwrapper h1 { text-indent: -999999px; background: url({{ theme_index_logo }}) no-repeat center center; height: {{ theme_index_logo_height }}; } {% endif %} div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: #ddd; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition tt.xref, div.admonition a tt { border-bottom: 1px solid #fafafa; } dd div.admonition { margin-left: -60px; padding-left: 60px; } div.admonition p.admonition-title { font-family: {{ title_font }}; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight { background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.9em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; background: #fdfdfd; font-size: 0.9em; } table.footnote + table.footnote { margin-top: -15px; border-top: none; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td.label { width: 0px; padding: 0.3em 0 0.3em 0.5em; } table.footnote td { padding: 0.3em 0.5em; } dl { margin: 0; padding: 0; margin-top: 20px; } dl dd { margin-left: 30px; } blockquote { margin: 0 0 0 30px; padding: 0; } ul, ol { margin: 10px 0 10px 30px; padding: 0; } pre { background: #eee; padding: 7px 30px; margin: 15px -30px; line-height: 1.3em; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } dl pre, blockquote pre, li pre { margin-left: -60px; padding-left: 60px; } dl dl pre { margin-left: -90px; padding-left: 90px; } tt { background-color: #ecf0f3; color: #222; padding: 3px 6px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } tt.xref, a tt { background-color: #FBFBFB; border-bottom: 1px solid white; } a.reference { text-decoration: none; border-bottom: 1px dotted #CD2103; } a.reference:hover { border-bottom: 1px solid #FC5E1E; } a.footnote-reference { text-decoration: none; font-size: 0.7em; vertical-align: top; border-bottom: 1px dotted #CD2103; } a.footnote-reference:hover { border-bottom: 1px solid #FC5E1E; } a:hover tt { background: #EEE; } geoalchemy2-0.15.2/doc/_themes/flask/static/small_flask.css000066400000000000000000000017201464355170400235710ustar00rootroot00000000000000/* * small_flask.css_t * ~~~~~~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. * :license: Flask Design License, see LICENSE for details. */ body { margin: 0; padding: 20px 30px; } div.documentwrapper { float: none; background: white; } div.sphinxsidebar { display: block; float: none; width: 102.5%; margin: 50px -30px -20px -30px; padding: 10px 20px; background: #333; color: white; } div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, div.sphinxsidebar h3 a { color: white; } div.sphinxsidebar a { color: #aaa; } div.sphinxsidebar p.logo { display: none; } div.document { width: 100%; margin: 0; } div.related { display: block; margin: 0; padding: 10px 0 20px 0; } div.related ul, div.related ul li { margin: 0; padding: 0; } div.footer { display: none; } div.bodywrapper { margin: 0; } div.body { min-height: 0; padding: 0; } geoalchemy2-0.15.2/doc/_themes/flask/theme.conf000066400000000000000000000002611464355170400212500ustar00rootroot00000000000000[theme] inherit = basic stylesheet = flasky.css pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = 'geoalchemy.png' index_logo_height = 120px touch_icon = geoalchemy2-0.15.2/doc/admin.rst000066400000000000000000000016361464355170400164040ustar00rootroot00000000000000.. _admin: Administration ============== .. automodule:: geoalchemy2.admin :members: :private-members: :show-inheritance: Common objects -------------- .. automodule:: geoalchemy2.admin.dialects.common :members: :private-members: :show-inheritance: PostgreSQL-specific objects --------------------------- .. automodule:: geoalchemy2.admin.dialects.postgresql :members: :private-members: :show-inheritance: MySQL-specific objects --------------------------- .. automodule:: geoalchemy2.admin.dialects.mysql :members: :private-members: :show-inheritance: SQLite-specific objects --------------------------- .. automodule:: geoalchemy2.admin.dialects.sqlite :members: :private-members: :show-inheritance: GeoPackage-specific objects --------------------------- .. automodule:: geoalchemy2.admin.dialects.geopackage :members: :private-members: :show-inheritance: geoalchemy2-0.15.2/doc/alembic.rst000066400000000000000000000206471464355170400167130ustar00rootroot00000000000000.. _alembic_use: Use Alembic with GeoAlchemy 2 ============================= The `Alembic `_ package is a lightweight database migration tool which is able to automatically detect the table column types. Interactions between Alembic and GeoAlchemy 2 --------------------------------------------- Interactions between some features of Alembic and GeoAlchemy 2 may lead to errors in migration scripts, especially when using the ``--autogenerate`` feature of Alembic with the ``spatial_index=True`` feature of GeoAlchemy 2. In this case, the following errors occur: 1. the migration script misses the relevant imports from ``geoalchemy2``. 2. the migration script will create the indexes of the spatial columns after the table is created, but these indexes are already automatically created during table creation, which will lead to an error. For example, suppose the following table is defined: .. code-block:: python class Lake(Base): __tablename__ = 'lake' id = Column(Integer, primary_key=True) geom = Column( Geometry( geometry_type='LINESTRING', srid=4326, spatial_index=True, ) ) Then the command ``alembic revision --autogenerate -m "Create new table"`` will create the following migration script: .. code-block:: python """Create new table Revision ID: Revises: Create Date: """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = "" down_revision = "" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.create_table( "lake", sa.Column("id", sa.Integer(), nullable=False), sa.Column( "geom", geoalchemy2.types.Geometry( geometry_type="LINESTRING", srid=4326, from_text="ST_GeomFromEWKT", name="geometry", ), nullable=True, ), sa.PrimaryKeyConstraint("id"), ) op.create_index( "idx_lake_geom", "lake", ["geom"], unique=False, postgresql_using="gist", postgresql_ops={}, ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.drop_index( "idx_lake_geom", table_name="lake", postgresql_using="gist", postgresql_ops={}, ) op.drop_table("lake") # ### end Alembic commands ### In this case, we have to do the following changes to make it work: 1. add the missing import ``from geoalchemy2 import Geometry``. 2. remove the ``create_index`` statement in the ``upgrade()`` function. 3. remove the ``drop_index`` statement in the ``downgrade()`` function. Helpers ------- In order to make the use of Alembic easier, a few helpers are provided in :ref:`geoalchemy2.alembic_helpers `. These helpers can be used in the ``env.py`` file used by Alembic to auto-generate the migration scripts, like in the following example: .. code-block:: python # ... from geoalchemy2 import alembic_helpers # ... def run_migrations_offline(): # ... context.configure( # ... include_object=alembic_helpers.include_object, process_revision_directives=alembic_helpers.writer, render_item=alembic_helpers.render_item, ) # ... def run_migrations_online(): # ... context.configure( # ... include_object=alembic_helpers.include_object, process_revision_directives=alembic_helpers.writer, render_item=alembic_helpers.render_item, ) # ... As one can see, there are 3 specific functions to pass to the context: 1. :func:`geoalchemy2.alembic_helpers.include_object` ignores the internal tables managed by the spatial extensions (note that in some cases this function might need some customization, see the details in the doc of this function). 2. :obj:`geoalchemy2.alembic_helpers.writer` adds specific spatial operations to Alembic. 3. :func:`geoalchemy2.alembic_helpers.render_item` automatically adds `GeoAlchemy2` imports into the migration scripts. After running the ``alembic revision --autogenerate -m `` command, the migration script should be properly generated and should not need to be manually edited. In this migration script you will notice specific spatial operation like `create_geospatial_table`, `drop_geospatial_table`, `add_geospatial_column`, `drop_geospatial_column`, etc. These operations can of course be edited manually in the migration scripts if you don't want to use auto-generation. All specific operations can be found in :ref:`geoalchemy2.alembic_helpers `. Dealing with custom types ------------------------- .. toctree:: :hidden: gallery/test_type_decorator With ``SQLAlchemy``, users are able to define custom types, as shown in :ref:`sphx_glr_gallery_test_type_decorator.py`. In this case, you can refer to the `dedicated page of Alembic's documentation `_ for the details. A simple solution for this case is to create a new ``render_item`` function to add specific imports for these custom types. For example, if your custom type is called ``TheCustomType`` and is defined in ``my_package.custom_types``, you just have to edit the ``env.py`` file like the following: .. code-block:: python # ... from geoalchemy2 import alembic_helpers from my_package.custom_types import TheCustomType # ... def render_item(obj_type, obj, autogen_context): """Apply custom rendering for selected items.""" spatial_type = alembic_helpers.render_item(obj_type, obj, autogen_context) if spatial_type: return spatial_type # For the custom type if obj_type == 'type' and isinstance(obj, TheCustomType): import_name = obj.__class__.__name__ autogen_context.imports.add(f"from my_package.custom_types import {import_name}") return "%r" % obj # default rendering for other objects return False def run_migrations_offline(): # ... context.configure( # ... include_object=alembic_helpers.include_object, process_revision_directives=alembic_helpers.writer, render_item=render_item, ) # ... def run_migrations_online(): # ... context.configure( # ... include_object=alembic_helpers.include_object, process_revision_directives=alembic_helpers.writer, render_item=render_item, ) # ... Then the proper imports will be automatically added in the migration scripts. Dialects -------- Some dialects (like SQLite) require some specific management to alter columns or tables. In this case, other dedicated helpers are provided to handle this. For example, if one wants to add and drop columns in a SQLite database, the SpatiaLite extension should be loaded when the engine connects, thus the ``env.py`` file should look like the following: .. code-block:: python from geoalchemy2 import alembic_helpers from geoalchemy2 import load_spatialite def run_migrations_offline(): # ... context.configure( # ... include_object=alembic_helpers.include_object, process_revision_directives=alembic_helpers.writer, render_item=alembic_helpers.render_item, ) # ... def run_migrations_online(): # ... if connectable.dialect.name == "sqlite": # Load the SpatiaLite extension when the engine connects to the DB listen(connectable, 'connect', load_spatialite) with connectable.connect() as connection: # ... context.configure( # ... include_object=alembic_helpers.include_object, process_revision_directives=alembic_helpers.writer, render_item=alembic_helpers.render_item, ) # ... geoalchemy2-0.15.2/doc/alembic_helpers.rst000066400000000000000000000002061464355170400204220ustar00rootroot00000000000000.. _alembic_helpers: Alembic helpers =============== .. automodule:: geoalchemy2.alembic_helpers :members: :show-inheritance: geoalchemy2-0.15.2/doc/changelog.rst000066400000000000000000000000541464355170400172340ustar00rootroot00000000000000.. _changelog: .. include:: ../CHANGES.txt geoalchemy2-0.15.2/doc/conf.py000066400000000000000000000175201464355170400160600ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # GeoAlchemy2 documentation build configuration file, created by # sphinx-quickstart on Thu Aug 23 06:38:45 2012. # # 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 os import sys from sphinx_gallery.sorting import ExampleTitleSortKey import geoalchemy2 # 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("..")) sys.path.append(os.path.abspath("_themes")) # -- 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.napoleon", "sphinx.ext.viewcode", "sphinx_gallery.gen_gallery", ] # 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 = "GeoAlchemy2" copyright = "2012, Eric Lemoine" # 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. # version = release = geoalchemy2.__version__ # Remove some Sphinx warnings suppress_warnings = ["config.cache"] # 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 Autodoc --------------------------------------------------- autodoc_default_options = {"exclude-members": "inherit_cache"} # -- 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 = "flask" # 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 = ["_themes"] # 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 = { "index": ["sidebar-about.html", "sidebar-links.html", "searchbox.html"], "**": ["sidebar-logo.html", "relations.html", "searchbox.html"], } # 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 = False # 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 = "GeoAlchemy2doc" # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). # latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). # latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ("index", "GeoAlchemy2.tex", "GeoAlchemy2 Documentation", "Eric Lemoine", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = "_static/geoalchemy.png" # 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 # Additional stuff for the LaTeX preamble. # latex_preamble = '' # 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", "geoalchemy2", "GeoAlchemy2 Documentation", ["Eric Lemoine"], 1)] # -- Options gallery generation ------------------------------------------------ sphinx_gallery_conf = { "examples_dirs": "../tests/gallery", "gallery_dirs": "gallery", "line_numbers": True, "download_all_examples": False, "plot_gallery": False, "default_thumb_file": "_static/geoalchemy_small.png", "thumbnail_size": (100, 100), "within_subsection_order": ExampleTitleSortKey, "min_reported_time": 999, } geoalchemy2-0.15.2/doc/core_tutorial.rst000066400000000000000000000267201464355170400201700ustar00rootroot00000000000000.. _core_tutorial: Core Tutorial ============= (This tutorial is greatly inspired from the `SQLAlchemy SQL Expression Language Tutorial`_, which is recommended reading, eventually.) .. _SQLAlchemy SQL Expression Language Tutorial: http://docs.sqlalchemy.org/en/latest/core/tutorial.html This tutorial shows how to use the SQLAlchemy Expression Language (a.k.a. SQLAlchemy Core) with GeoAlchemy. As defined by the SQLAlchemy documentation itself, in contrast to the ORM's domain-centric mode of usage, the SQL Expression Language provides a schema-centric usage paradigm. Connect to the DB ----------------- For this tutorial we will use a PostGIS 2 database. To connect we use SQLAlchemy's ``create_engine()`` function:: >>> from sqlalchemy import create_engine >>> engine = create_engine('postgresql://gis:gis@localhost/gis', echo=True) In this example the name of the database, the database user, and the database password, is ``gis``. The ``echo`` flag is a shortcut to setting up SQLAlchemy logging, which is accomplished via Python's standard logging module. With it is enabled, we'll see all the generated SQL produced. The return value of ``create_engine`` is an ``Engine`` object, which represents the core interface to the database. Define a Table -------------- The very first object that we need to create is a ``Table``. Here we create a ``lake_table`` object, which will correspond to the ``lake`` table in the database:: >>> from sqlalchemy import Table, Column, Integer, String, MetaData >>> from geoalchemy2 import Geometry >>> >>> metadata = MetaData() >>> lake_table = Table('lake', metadata, ... Column('id', Integer, primary_key=True), ... Column('name', String), ... Column('geom', Geometry('POLYGON')) ... ) This table is composed of three columns, ``id``, ``name`` and ``geom``. The ``geom`` column is a :class:`geoalchemy2.types.Geometry` column whose ``geometry_type`` is ``POLYGON``. Any ``Table`` object is added to a ``MetaData`` object, which is a catalog of ``Table`` objects (and other related objects). Create the Table ---------------- With our ``Table`` being defined we're ready (to have SQLAlchemy) create it in the database:: >>> lake_table.create(engine) Calling ``create_all()`` on ``metadata`` would have worked equally well:: >>> metadata.create_all(engine) In that case every ``Table`` that's referenced to by ``metadata`` would be created in the database. The ``metadata`` object includes one ``Table`` here, our now well-known ``lake_table`` object. Reflecting tables ----------------- The `reflection system of SQLAlchemy `_ can be used on tables containing :class:`geoalchemy2.types.Geometry` or :class:`geoalchemy2.types.Geography` columns. In this case, the type must be imported to be registered into SQLAlchemy, even if it is not used explicitly. >>> from geoalchemy2 import Geometry # <= not used but must be imported >>> from sqlalchemy import create_engine, MetaData >>> engine = create_engine("postgresql://myuser:mypass@mydb.host.tld/mydbname") >>> meta = MetaData() >>> meta.reflect(bind=engine) Insertions ---------- We want to insert records into the ``lake`` table. For that we need to create an ``Insert`` object. SQLAlchemy provides multiple constructs for creating an ``Insert`` object, here's one:: >>> ins = lake_table.insert() >>> str(ins) INSERT INTO lake (id, name, geom) VALUES (:id, :name, ST_GeomFromEWKT(:geom)) The ``geom`` column being a ``Geometry`` column, the ``:geom`` bind value is wrapped in a ``ST_GeomFromEWKT`` call. To limit the columns named in the ``INSERT`` query the ``values()`` method can be used:: >>> ins = lake_table.insert().values(name='Majeur', ... geom='POLYGON((0 0,1 0,1 1,0 1,0 0))') ... >>> str(ins) INSERT INTO lake (name, geom) VALUES (:name, ST_GeomFromEWKT(:geom)) .. tip:: The string representation of the SQL expression does not include the data placed in ``values``. We got named bind parameters instead. To view the data we can get a compiled form of the expression, and ask for its ``params``:: >>> ins.compile.params() {'geom': 'POLYGON((0 0,1 0,1 1,0 1,0 0))', 'name': 'Majeur'} Up to now we've created an ``INSERT`` query but we haven't sent this query to the database yet. Before being able to send it to the database we need a database ``Connection``. We can get a ``Connection`` from the ``Engine`` object we created earlier:: >>> conn = engine.connect() We're now ready to execute our ``INSERT`` statement:: >>> result = conn.execute(ins) This is what the logging system should output:: INSERT INTO lake (name, geom) VALUES (%(name)s, ST_GeomFromEWKT(%(geom)s)) RETURNING lake.id {'geom': 'POLYGON((0 0,1 0,1 1,0 1,0 0))', 'name': 'Majeur'} COMMIT The value returned by ``conn.execute()``, stored in ``result``, is a ``sqlalchemy.engine.ResultProxy`` object. In the case of an ``INSERT`` we can get the primary key value which was generated from our statement:: >>> result.inserted_primary_key [1] Instead of using ``values()`` to specify our ``INSERT`` data, we can send the data to the ``execute()`` method on ``Connection``. So we could rewrite things as follows:: >>> conn.execute(lake_table.insert(), ... name='Majeur', geom='POLYGON((0 0,1 0,1 1,0 1,0 0))') Now let's use another form, allowing to insert multiple rows at once:: >>> conn.execute(lake_table.insert(), [ ... {'name': 'Garde', 'geom': 'POLYGON((1 0,3 0,3 2,1 2,1 0))'}, ... {'name': 'Orta', 'geom': 'POLYGON((3 0,6 0,6 3,3 3,3 0))'} ... ]) ... .. tip:: In the above examples the geometries are specified as WKT strings. Specifying them as EWKT strings is also supported. Selections ---------- Inserting involved creating an ``Insert`` object, so it'd come to no surprise that Selecting involves creating a ``Select`` object. The primary construct to generate ``SELECT`` statements is SQLAlchemy`s ``select()`` function:: >>> from sqlalchemy.sql import select >>> s = select([lake_table]) >>> str(s) SELECT lake.id, lake.name, ST_AsEWKB(lake.geom) AS geom FROM lake The ``geom`` column being a ``Geometry`` it is wrapped in a ``ST_AsEWKB`` call when specified as a column in a ``SELECT`` statement. We can now execute the statement and look at the results:: >>> result = conn.execute(s) >>> for row in result: ... print 'name:', row['name'], '; geom:', row['geom'].desc ... name: Majeur ; geom: 0103... name: Garde ; geom: 0103... name: Orta ; geom: 0103... ``row['geom']`` is a :class:`geoalchemy2.types.WKBElement` instance. In this example we just get an hexadecimal representation of the geometry's WKB value using the ``desc`` property. Spatial Query ------------- As spatial database users executing spatial queries is of a great interest to us. There comes GeoAlchemy! Spatial relationship ~~~~~~~~~~~~~~~~~~~~ Using spatial filters in SQL SELECT queries is very common. Such queries are performed by using spatial relationship functions, or operators, in the ``WHERE`` clause of the SQL query. For example, to find lakes that contain the point ``POINT(4 1)``, we can use this:: >>> from sqlalchemy import func >>> s = select([lake_table], func.ST_Contains(lake_table.c.geom, 'POINT(4 1)')) >>> str(s) SELECT lake.id, lake.name, ST_AsEWKB(lake.geom) AS geom FROM lake WHERE ST_Contains(lake.geom, :param_1) >>> result = conn.execute(s) >>> for row in result: ... print 'name:', row['name'], '; geom:', row['geom'].desc ... name: Orta ; geom: 0103... GeoAlchemy allows rewriting this more concisely:: >>> s = select([lake_table], lake_table.c.geom.ST_Contains('POINT(4 1)')) >>> str(s) SELECT lake.id, lake.name, ST_AsEWKB(lake.geom) AS geom FROM lake WHERE ST_Contains(lake.geom, :param_1) Here the ``ST_Contains`` function is applied to ``lake.c.geom``. And the generated SQL the ``lake.geom`` column is actually passed to the ``ST_Contains`` function as the first argument. Here's another spatial query, based on ``ST_Intersects``:: >>> s = select([lake_table], ... lake_table.c.geom.ST_Intersects('LINESTRING(2 1,4 1)')) >>> result = conn.execute(s) >>> for row in result: ... print 'name:', row['name'], '; geom:', row['geom'].desc ... name: Garde ; geom: 0103... name: Orta ; geom: 0103... This query selects lakes whose geometries intersect ``LINESTRING(2 1,4 1)``. The GeoAlchemy functions all start with ``ST_``. Operators are also called as functions, but the names of operator functions don't include the ``ST_`` prefix. As an example let's use PostGIS' ``&&`` operator, which allows testing whether the bounding boxes of geometries intersect. GeoAlchemy provides the ``intersects`` function for that:: >>> s = select([lake_table], ... lake_table.c.geom.intersects('LINESTRING(2 1,4 1)')) >>> result = conn.execute(s) >>> for row in result: ... print 'name:', row['name'], '; geom:', row['geom'].desc ... name: Garde ; geom: 0103... name: Orta ; geom: 0103... Processing and Measurement ~~~~~~~~~~~~~~~~~~~~~~~~~~ Here's a ``Select`` that calculates the areas of buffers for our lakes:: >>> s = select([lake_table.c.name, func.ST_Area( lake_table.c.geom.ST_Buffer(2)).label('bufferarea')]) >>> str(s) SELECT lake.name, ST_Area(ST_Buffer(lake.geom, %(param_1)s)) AS bufferarea FROM lake >>> result = conn.execute(s) >>> for row in result: ... print '%s: %f' % (row['name'], row['bufferarea']) Majeur: 21.485781 Garde: 32.485781 Orta: 45.485781 Obviously, processing and measurement functions can also be used in ``WHERE`` clauses. For example:: >>> s = select([lake_table.c.name], lake_table.c.geom.ST_Buffer(2).ST_Area() > 33) >>> str(s) SELECT lake.name FROM lake WHERE ST_Area(ST_Buffer(lake.geom, :param_1)) > :ST_Area_1 >>> result = conn.execute(s) >>> for row in result: ... print row['name'] Orta And, like any other functions supported by GeoAlchemy, processing and measurement functions can be applied to :class:`geoalchemy2.elements.WKBElement`. For example:: >>> s = select([lake_table], lake_table.c.name == 'Majeur') >>> result = conn.execute(s) >>> lake = result.fetchone() >>> bufferarea = conn.scalar(lake[lake_table.c.geom].ST_Buffer(2).ST_Area()) >>> print '%s: %f' % (lake['name'], bufferarea) Majeur: 21.485781 Use Raster functions -------------------- A few functions (like `ST_Transform()`, `ST_Union()`, `ST_SnapToGrid()`, ...) can be used on both :class:`geoalchemy2.types.Geometry` and :class:`geoalchemy2.types.Raster` types. In GeoAlchemy2, these functions are only defined for :class:`Geometry` as it can not be defined for several types at the same time. Thus using these functions on :class:`Raster` requires minor tweaking to enforce the type by passing the `type_=Raster` argument to the function: >>> s = select([func.ST_Transform( lake_table.c.raster, 2154, type_=Raster) .label('transformed_raster')]) Further Reference ----------------- * Spatial Functions Reference: :ref:`spatial_functions` * Spatial Operators Reference: :ref:`spatial_operators` * Elements Reference: :ref:`elements` geoalchemy2-0.15.2/doc/elements.rst000066400000000000000000000010071464355170400171200ustar00rootroot00000000000000.. _elements: Elements ======== .. autoclass:: geoalchemy2.elements._SpatialElement :members: :undoc-members: :show-inheritance: .. autoclass:: geoalchemy2.elements.WKTElement :members: :undoc-members: :show-inheritance: .. autoclass:: geoalchemy2.elements.WKBElement :members: :undoc-members: :show-inheritance: .. autoclass:: geoalchemy2.elements.RasterElement :members: :show-inheritance: .. autoclass:: geoalchemy2.elements.CompositeElement :members: :show-inheritance: geoalchemy2-0.15.2/doc/index.rst000066400000000000000000000113401464355170400164140ustar00rootroot00000000000000GeoAlchemy 2 Documentation ========================== *Using SQLAlchemy with Spatial Databases.* GeoAlchemy 2 provides extensions to `SQLAlchemy `_ for working with spatial databases. GeoAlchemy 2 focuses on `PostGIS `_. PostGIS 2 and PostGIS 3 are supported. GeoAlchemy 2 also supports the following dialects: * `SpatiaLite `_ >= 4.3.0 (except for alembic helpers that require SpatiaLite >= 5) * `MySQL `_ >= 8 * `MariaDB `_ >= 5.3.3 (experimental) * `GeoPackage `_ Note that using GeoAlchemy 2 with these dialects may require some specific configuration on the application side. GeoAlchemy 2 aims to be simpler than its predecessor, `GeoAlchemy `_. Simpler to use, and simpler to maintain. .. toctree:: :hidden: changelog The current version of this documentation applies to the version |version| of GeoAlchemy 2. See the :ref:`changelog` page for details on recent changes. Requirements ------------ GeoAlchemy 2 requires `SQLAlchemy `_ >= 1.4. Installation ------------ GeoAlchemy 2 is `available on the Python Package Index `_. So it can be installed with the standard `pip `_ or `easy_install `_ tools. What's New in GeoAlchemy 2 -------------------------- * GeoAlchemy 2 supports PostGIS' ``geometry`` type, as well as the ``geography`` and ``raster`` types. * The first series had its own namespace for spatial functions. With GeoAlchemy 2, spatial functions are called like any other SQLAlchemy function, using ``func``, which is SQLAlchemy's `standard way `_ of calling SQL functions. * GeoAlchemy 2 works with SQLAlchemy's ORM, as well as with SQLAlchemy's *SQL Expression Language* (a.k.a the SQLAlchemy Core). (This is thanks to SQLAlchemy's new `type-level comparator system `_.) * GeoAlchemy 2 supports `reflection `_ of geometry and geography columns. * GeoAlchemy 2 adds ``to_shape``, ``from_shape`` functions for a better integration with `Shapely `_. .. toctree:: :hidden: migrate See the :ref:`migrate` page for details on how to migrate a GeoAlchemy application to GeoAlchemy 2. Tutorials --------- GeoAlchemy 2 works with both SQLAlchemy's *Object Relational Mapping* (ORM) and *SQL Expression Language*. This documentation provides a tutorial for each system. If you're new to GeoAlchemy 2 start with this. .. toctree:: :maxdepth: 1 orm_tutorial core_tutorial spatialite_tutorial Gallery --------- .. toctree:: :hidden: gallery/index The :ref:`gallery` page shows examples of the GeoAlchemy 2's functionalities. Use with Alembic ---------------- .. toctree:: :hidden: alembic The GeoAlchemy 2 package is compatible with the migration tool `Alembic `_. The :ref:`alembic_use` page provides more details on this topic. Reference Documentation ----------------------- .. toctree:: :maxdepth: 1 admin types elements spatial_functions spatial_operators shape alembic_helpers Development ----------- The code is available on GitHub: https://github.com/geoalchemy/geoalchemy2. Contributors: * Adrien Berchet (https://github.com/adrien-berchet) * Éric Lemoine (https://github.com/elemoine) * Dolf Andringa (https://github.com/dolfandringa) * Frédéric Junod, Camptocamp SA (https://github.com/fredj) * ijl (https://github.com/ijl) * Loïc Gasser (https://github.com/loicgasser) * Marcel Radischat (https://github.com/quiqua) * Matt Broadway (https://github.com/mbway) * rapto (https://github.com/rapto) * Serge Bouchut (https://github.com/SergeBouchut) * Tobias Bieniek (https://github.com/Turbo87) * Tom Payne (https://github.com/twpayne) Many thanks to Mike Bayer for his guidance and support! He also `fostered `_ the birth of GeoAlchemy 2. Citation -------- When you use this software, we kindly ask you to cite the following DOI: .. image:: https://zenodo.org/badge/5638538.svg :target: https://zenodo.org/doi/10.5281/zenodo.10808783 Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` geoalchemy2-0.15.2/doc/make.bat000066400000000000000000000106511464355170400161640ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :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. text to make text files echo. man to make manual pages echo. changes to make an overview over 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 goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\GeoAlchemy2.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\GeoAlchemy2.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end geoalchemy2-0.15.2/doc/migrate.rst000066400000000000000000000051401464355170400167360ustar00rootroot00000000000000.. _migrate: Migrate to GeoAlchemy 2 ======================= This section describes how to migrate an application from the first series of GeoAlchemy to GeoAlchemy 2. Defining Geometry Columns ------------------------- The first series has specific types like ``Point``, ``LineString`` and ``Polygon``. These are gone, the :class:`geoalchemy2.types.Geometry` type should be used instead, and a ``geometry_type`` can be passed to it. So, for example, a ``polygon`` column that used to be defined like this:: geom = Column(Polygon) is now defined like this:: geom = Column(Geometry('POLYGON')) This change is related to GeoAlchemy 2 supporting the `geoalchemy2.types.Geography` type. Calling Spatial Functions ------------------------- The first series has its own namespace/object for calling spatial functions, namely ``geoalchemy.functions``. With GeoAlchemy 2, SQLAlchemy's ``func`` object should be used. For example, the expression :: functions.buffer(functions.centroid(box), 10, 2) would be rewritten to this with GeoAlchemy 2:: func.ST_Buffer(func.ST_Centroid(box), 10, 2) Also, as the previous example hinted it, the names of spatial functions are now all prefixed with ``ST_``. (This is to be consistent with PostGIS and the ``SQL-MM`` standard.) The ``ST_`` prefix should be used even when applying spatial functions to columns, :class:`geoalchemy2.elements.WKTElement`, or :class:`geoalchemy2.elements.WKTElement` objects:: Lake.geom.ST_Buffer(10, 2) lake_table.c.geom.ST_Buffer(10, 2) lake.geom.ST_Buffer(10, 2) WKB and WKT Elements -------------------- The first series has classes like ``PersistentSpatialElement``, ``PGPersistentSpatialElement``, ``WKTSpatialElement``. They're all gone, and replaced by two classes only: :class:`geoalchemy2.elements.WKTElement` and :class:`geoalchemy2.elements.WKBElement`. :class:`geoalchemy2.elements.WKTElement` is to be used in expressions where a geometry with a specific SRID should be specified. For example:: Lake.geom.ST_Touches(WKTElement('POINT(1 1)', srid=4326)) If no SRID need be specified, a string can used directly:: Lake.geom.ST_Touches('POINT(1 1)') * :class:`geoalchemy2.elements.WKTElement` literally replaces the first series' ``WKTSpatialElement``. * :class:`geoalchemy2.elements.WKBElement` is the type into which GeoAlchemy 2 converts geometry values read from the database. For example, the ``geom`` attributes of ``Lake`` objects loaded from the database would be references to :class:`geoalchemy2.elements.WKBElement` objects. This class replaces the first series' ``PersistentSpatialElement`` classes. geoalchemy2-0.15.2/doc/orm_tutorial.rst000066400000000000000000000326761464355170400200440ustar00rootroot00000000000000.. _orm_tutorial: ORM Tutorial ============ (This tutorial is greatly inspired by the `SQLAlchemy ORM Tutorial`_, which is recommended reading, eventually.) .. _SQLAlchemy ORM Tutorial: http://docs.sqlalchemy.org/en/latest/orm/tutorial.html GeoAlchemy does not provide an Object Relational Mapper (ORM), but works well with the SQLAlchemy ORM. This tutorial shows how to use the SQLAlchemy ORM with spatial tables, using GeoAlchemy. Connect to the DB ----------------- For this tutorial we will use a PostGIS 2 database. To connect we use SQLAlchemy's ``create_engine()`` function:: >>> from sqlalchemy import create_engine >>> engine = create_engine('postgresql://gis:gis@localhost/gis', echo=True) In this example the name of the database, the database user, and the database password, is ``gis``. The ``echo`` flag is a shortcut to setting up SQLAlchemy logging, which is accomplished via Python's standard logging module. With it is enabled, we'll see all the generated SQL produced. The return value of ``create_engine`` is an ``Engine`` object, which represents the core interface to the database. Declare a Mapping ----------------- When using the ORM, the configurational process starts by describing the database tables we'll be dealing with, and then by defining our own classes which will be mapped to those tables. In modern SQLAlchemy, these two tasks are usually performed together, using a system known as ``Declarative``, which allows us to create classes that include directives to describe the actual database table they will be mapped to. :: >>> from sqlalchemy.ext.declarative import declarative_base >>> from sqlalchemy import Column, Integer, String >>> from geoalchemy2 import Geometry >>> >>> Base = declarative_base() >>> >>> class Lake(Base): ... __tablename__ = 'lake' ... id = Column(Integer, primary_key=True) ... name = Column(String) ... geom = Column(Geometry('POLYGON')) The ``Lake`` class establishes details about the table being mapped, including the name of the table denoted by ``__tablename__``, and three columns ``id``, ``name``, and ``geom``. The ``id`` column will be the primary key of the table. The ``geom`` column is a :class:`geoalchemy2.types.Geometry` column whose ``geometry_type`` is ``POLYGON``. Create the Table in the Database -------------------------------- The ``Lake`` class has a corresponding ``Table`` object representing the database table. This ``Table`` object was created automatically by SQLAlchemy, it is referenced to by the ``Lake.__table__`` property:: >>> Lake.__table__ Table('lake', MetaData(bind=None), Column('id', Integer(), table=, primary_key=True, nullable=False), Column('name', String(), table=), Column('geom', Polygon(srid=4326), table=), schema=None) To create the ``lake`` table in the database:: >>> Lake.__table__.create(engine) If we wanted to drop the table we'd use:: >>> Lake.__table__.drop(engine) Create an Instance of the Mapped Class -------------------------------------- With the mapping declared, we can create a ``Lake`` object:: >>> lake = Lake(name='Majeur', geom='POLYGON((0 0,1 0,1 1,0 1,0 0))') >>> lake.geom 'POLYGON((0 0,1 0,1 1,0 1,0 0))' >>> str(lake.id) 'None' A WKT is passed to the ``Lake`` constructor for its geometry. This WKT represents the shape of our lake. Since we have not yet told SQLAlchemy to persist the ``lake`` object, its ``id`` is ``None``. The EWKT (Extended WKT) format is also supported. So, for example, if the spatial reference system for the geometry column were ``4326``, the string ``SRID=4326;POLYGON((0 0,1 0,1,0 1,0 0))`` could be used as the geometry representation. Create a Session ---------------- The ORM interacts with the database through a ``Session``. Let's create a ``Session`` class:: >>> from sqlalchemy.orm import sessionmaker >>> Session = sessionmaker(bind=engine) This custom-made ``Session`` class will create new ``Session`` objects which are bound to our database. Then, whenever we need to have a conversation with the database, we instantiate a ``Session``:: >>> session = Session() The above ``Session`` is associated with our PostgreSQL ``Engine``, but it hasn't opened any connection yet. Add New Objects --------------- To persist our ``Lake`` object, we ``add()`` it to the ``Session``:: >>> session.add(lake) At this point the ``lake`` object has been added to the ``Session``, but no SQL has been issued to the database. The object is in a *pending* state. To persist the object a *flush* or *commit* operation must occur (commit implies flush):: >>> session.commit() We can now query the database for ``Majeur``:: >>> our_lake = session.query(Lake).filter_by(name='Majeur').first() >>> our_lake.name u'Majeur' >>> our_lake.geom >>> our_lake.id 1 ``our_lake.geom`` is a :class:`geoalchemy2.elements.WKBElement`, which a type provided by GeoAlchemy. :class:`geoalchemy2.elements.WKBElement` wraps a WKB value returned by the database. Let's add more lakes:: >>> session.add_all([ ... Lake(name='Garde', geom='POLYGON((1 0,3 0,3 2,1 2,1 0))'), ... Lake(name='Orta', geom='POLYGON((3 0,6 0,6 3,3 3,3 0))') ... ]) >>> session.commit() Query ----- A ``Query`` object is created using the ``query()`` function on ``Session``. For example here's a ``Query`` that loads ``Lake`` instances ordered by their names:: >>> query = session.query(Lake).order_by(Lake.name) Any ``Query`` is iterable:: >>> for lake in query: ... print lake.name ... Garde Majeur Orta Another way to execute the query and get a list of ``Lake`` objects involves calling ``all()`` on the ``Query``:: >>> lakes = session.query(Lake).order_by(Lake.name).all() The SQLAlchemy ORM Tutorial's `Querying section `_ provides more examples of queries. Make Spatial Queries -------------------- Using spatial filters in SQL SELECT queries is very common. Such queries are performed by using spatial relationship functions, or operators, in the ``WHERE`` clause of the SQL query. For example, to find the ``Lake`` s that contain the point ``POINT(4 1)``, we can use this ``Query``:: >>> from sqlalchemy import func >>> query = session.query(Lake).filter( ... func.ST_Contains(Lake.geom, 'POINT(4 1)')) ... >>> for lake in query: ... print lake.name ... Orta GeoAlchemy allows rewriting this ``Query`` more concisely:: >>> query = session.query(Lake).filter(Lake.geom.ST_Contains('POINT(4 1)')) >>> for lake in query: ... print lake.name ... Orta Here the ``ST_Contains`` function is applied to the ``Lake.geom`` column property. In that case the column property is actually passed to the function, as its first argument. Here's another spatial filtering query, based on ``ST_Intersects``:: >>> query = session.query(Lake).filter( ... Lake.geom.ST_Intersects('LINESTRING(2 1,4 1)')) ... >>> for lake in query: ... print lake.name ... Garde Orta We can also apply relationship functions to :class:`geoalchemy2.elements.WKBElement`. For example:: >>> lake = session.query(Lake).filter_by(name='Garde').one() >>> print session.scalar(lake.geom.ST_Intersects('LINESTRING(2 1,4 1)')) True ``session.scalar`` allows executing a clause and returning a scalar value (a boolean value in this case). The GeoAlchemy functions all start with ``ST_``. Operators are also called as functions, but the function names don't include the ``ST_`` prefix. As an example let's use PostGIS' ``&&`` operator, which allows testing whether the bounding boxes of geometries intersect. GeoAlchemy provides the ``intersects`` function for that:: >>> query = session.query >>> query = session.query(Lake).filter( ... Lake.geom.intersects('LINESTRING(2 1,4 1)')) ... >>> for lake in query: ... print lake.name ... Garde Orta Set Spatial Relationships in the Model -------------------------------------- Let's assume that in addition to ``lake`` we have another table, ``treasure``, that includes treasure locations. And let's say that we are interested in discovering the treasures hidden at the bottom of lakes. The ``Treasure`` class is the following:: >>> class Treasure(Base): ... __tablename__ = 'treasure' ... id = Column(Integer, primary_key=True) ... geom = Column(Geometry('POINT')) We can now add a ``relationship`` to the ``Lake`` table to automatically load the treasures contained by each lake:: >>> from sqlalchemy.orm import relationship, backref >>> class Lake(Base): ... __tablename__ = 'lake' ... id = Column(Integer, primary_key=True) ... name = Column(String) ... geom = Column(Geometry('POLYGON')) ... treasures = relationship( ... 'Treasure', ... primaryjoin='func.ST_Contains(foreign(Lake.geom), Treasure.geom).as_comparison(1, 2)', ... backref=backref('lake', uselist=False), ... viewonly=True, ... uselist=True, ... ) Note the use of the ``as_comparison`` function. It is required for using an SQL function (``ST_Contains`` here) in a ``primaryjoin`` condition. This only works with SQLAlchemy 1.3, as the ``as_comparison`` function did not exist before that version. See the `Custom operators based on SQL function `_ section of the SQLAlchemy documentation for more information. Some information on the parameters used for configuring this ``relationship``: * ``backref`` is used to provide the name of property to be placed on the class that handles this relationship in the other direction, namely ``Treasure``; * ``viewonly=True`` specifies that the relationship is used only for loading objects, and not for persistence operations; * ``uselist=True`` indicates that the property should be loaded as a list, as opposed to a scalar. Also, note that the ``treasures`` property on ``lake`` objects (and the ``lake`` property on ``treasure`` objects) is loaded "lazily" when the property is first accessed. Another loading strategy may be configured in the ``relationship``. For example you'd use ``lazy='joined'`` for related items to be loaded "eagerly" in the same query as that of the parent, using a ``JOIN`` or ``LEFT OUTER JOIN``. See the `Relationships API `_ section of the SQLAlchemy documentation for more detail on the ``relationship`` function, and all the parameters that can be used to configure it. Use Other Spatial Functions --------------------------- Here's a ``Query`` that calculates the areas of buffers for our lakes:: >>> from sqlalchemy import func >>> query = session.query(Lake.name, ... func.ST_Area(func.ST_Buffer(Lake.geom, 2)) \ ... .label('bufferarea')) >>> for row in query: ... print '%s: %f' % (row.name, row.bufferarea) ... Majeur: 21.485781 Garde: 32.485781 Orta: 45.485781 This ``Query`` applies the PostGIS ``ST_Buffer`` function to the geometry column of every row of the ``lake`` table. The return value is a list of rows, where each row is actually a tuple of two values: the lake name, and the area of a buffer of the lake. Each tuple is actually an SQLAlchemy ``KeyedTuple`` object, which provides property type accessors. Again, the ``Query`` can written more concisely:: >>> query = session.query(Lake.name, ... Lake.geom.ST_Buffer(2).ST_Area().label('bufferarea')) >>> for row in query: ... print '%s: %f' % (row.name, row.bufferarea) ... Majeur: 21.485781 Garde: 32.485781 Orta: 45.485781 Obviously, processing and measurement functions can also be used in ``WHERE`` clauses. For example:: >>> lake = session.query(Lake).filter( ... Lake.geom.ST_Buffer(2).ST_Area() > 33).one() ... >>> print lake.name Orta And, like any other functions supported by GeoAlchemy, processing and measurement functions can be applied to :class:`geoalchemy2.elements.WKBElement`. For example:: >>> lake = session.query(Lake).filter_by(name='Majeur').one() >>> bufferarea = session.scalar(lake.geom.ST_Buffer(2).ST_Area()) >>> print '%s: %f' % (lake.name, bufferarea) Majeur: 21.485781 Majeur: 21.485781 Use Raster functions -------------------- A few functions (like `ST_Transform()`, `ST_Union()`, `ST_SnapToGrid()`, ...) can be used on both :class:`geoalchemy2.types.Geometry` and :class:`geoalchemy2.types.Raster` types. In GeoAlchemy2, these functions are only defined for :class:`Geometry` as it can not be defined for several types at the same time. Thus using these functions on :class:`Raster` requires minor tweaking to enforce the type by passing the `type_=Raster` argument to the function: >>> query = session.query(Lake.raster.ST_Transform(2154, type_=Raster)) Further Reference ----------------- * Spatial Functions Reference: :ref:`spatial_functions` * Spatial Operators Reference: :ref:`spatial_operators` * Elements Reference: :ref:`elements` geoalchemy2-0.15.2/doc/shape.rst000066400000000000000000000002421464355170400164040ustar00rootroot00000000000000.. _shape: Shapely Integration =================== .. automodule:: geoalchemy2.shape :members: :private-members: :undoc-members: :show-inheritance: geoalchemy2-0.15.2/doc/spatial_functions.rst000066400000000000000000000001601464355170400210300ustar00rootroot00000000000000.. _spatial_functions: Spatial Functions ================= .. automodule:: geoalchemy2.functions :members: geoalchemy2-0.15.2/doc/spatial_operators.rst000066400000000000000000000002341464355170400210400ustar00rootroot00000000000000.. _spatial_operators: Spatial Operators ================= .. automodule:: geoalchemy2.comparator :members: :special-members: :show-inheritance: geoalchemy2-0.15.2/doc/spatialite_tutorial.rst000066400000000000000000000214201464355170400213670ustar00rootroot00000000000000.. _spatialite_tutorial: SpatiaLite Tutorial =================== GeoAlchemy 2's main target is PostGIS. But GeoAlchemy 2 also supports SpatiaLite, the spatial extension to SQLite. This tutorial describes how to use GeoAlchemy 2 with SpatiaLite. It's based on the :ref:`orm_tutorial`, which you may want to read first. .. _spatialite_connect: Connect to the DB ----------------- Just like when using PostGIS connecting to a SpatiaLite database requires an ``Engine``. This is how you create one for SpatiaLite:: >>> from geoalchemy2 import load_spatialite >>> from sqlalchemy import create_engine >>> from sqlalchemy.event import listen >>> >>> engine = create_engine("sqlite:///gis.db", echo=True) >>> listen(engine, "connect", load_spatialite) The call to ``create_engine`` creates an engine bound to the database file ``gis.db``. After that a ``connect`` listener is registered on the engine. The listener is responsible for loading the SpatiaLite extension, which is a necessary operation for using SpatiaLite through SQL. The path to the ``mod_spatialite`` file should be stored in the ``SPATIALITE_LIBRARY_PATH`` environment variable before using the ``load_spatialite`` function. At this point you can test that you are able to connect to the database:: >> conn = engine.connect() Note that this call will internally call the ``load_spatialite`` function, which can take some time to execute on a new database because it actually calls the ``InitSpatialMetaData`` function from SpatiaLite (it is possible to reduce this time by loading only the required SRIDs, see :func:`geoalchemy2.admin.dialects.sqlite.load_spatialite`). Then you can also check that the ``gis.db`` SQLite database file was created on the file system. Note that when ``InitSpatialMetaData`` is executed again it will report an error:: InitSpatiaMetaData() error:"table spatial_ref_sys already exists" You can safely ignore that error. Before going further we can close the current connection:: >>> conn.close() Declare a Mapping ----------------- Now that we have a working connection we can go ahead and create a mapping between a Python class and a database table:: >>> from sqlalchemy.orm import declarative_base >>> from sqlalchemy import Column, Integer, String >>> from geoalchemy2 import Geometry >>> >>> Base = declarative_base() >>> >>> class Lake(Base): ... __tablename__ = "lake" ... id = Column(Integer, primary_key=True) ... name = Column(String) ... geom = Column(Geometry(geometry_type="POLYGON")) From the user point of view this works in the same way as with PostGIS. The difference is that internally the ``RecoverGeometryColumn`` and ``DiscardGeometryColumn`` management functions will be used for the creation and removal of the geometry column. Create the Table in the Database -------------------------------- We can now create the ``lake`` table in the ``gis.db`` database:: >>> Lake.__table__.create(engine) If we wanted to drop the table we'd use:: >>> Lake.__table__.drop(engine) There's nothing specific to SpatiaLite here. Create a Session ---------------- When using the SQLAlchemy ORM the ORM interacts with the database through a ``Session``. >>> from sqlalchemy.orm import sessionmaker >>> Session = sessionmaker(bind=engine) >>> session = Session() The session is associated with our SpatiaLite ``Engine``. Again, there's nothing specific to SpatiaLite here. Add New Objects --------------- We can now create and insert new ``Lake`` objects into the database, the same way we'd do it using GeoAlchemy 2 with PostGIS. :: >>> lake = Lake(name="Majeur", geom="POLYGON((0 0,1 0,1 1,0 1,0 0))") >>> session.add(lake) >>> session.commit() We can now query the database for ``Majeur``:: >>> our_lake = session.query(Lake).filter_by(name="Majeur").first() >>> our_lake.name u"Majeur" >>> our_lake.geom >>> our_lake.id 1 Let's add more lakes:: >>> session.add_all([ ... Lake(name="Garde", geom="POLYGON((1 0,3 0,3 2,1 2,1 0))"), ... Lake(name="Orta", geom="POLYGON((3 0,6 0,6 3,3 3,3 0))") ... ]) >>> session.commit() Query ----- Let's make a simple, non-spatial, query:: >>> query = session.query(Lake).order_by(Lake.name) >>> for lake in query: ... print(lake.name) ... Garde Majeur Orta Now a spatial query:: >>> from geolachemy2 import WKTElement >>> query = session.query(Lake).filter( ... func.ST_Contains(Lake.geom, WKTElement("POINT(4 1)"))) ... >>> for lake in query: ... print(lake.name) ... Orta Here's another spatial query, using ``ST_Intersects`` this time:: >>> query = session.query(Lake).filter( ... Lake.geom.ST_Intersects(WKTElement("LINESTRING(2 1,4 1)"))) ... >>> for lake in query: ... print(lake.name) ... Garde Orta We can also apply relationship functions to :class:`geoalchemy2.elements.WKBElement`. For example:: >>> lake = session.query(Lake).filter_by(name="Garde").one() >>> print(session.scalar(lake.geom.ST_Intersects(WKTElement("LINESTRING(2 1,4 1)")))) 1 ``session.scalar`` allows executing a clause and returning a scalar value (an integer value in this case). The value ``1`` indicates that the lake "Garde" does intersects the ``LINESTRING(2 1,4 1)`` geometry. See the SpatiaLite SQL functions reference list for more information. Function mapping ---------------- Several functions have different names in SpatiaLite than in PostGIS. The GeoAlchemy 2 package is based on the PostGIS syntax but it is possible to automatically translate the queries into SpatiaLite ones. For example, the function ``ST_GeomFromEWKT`` is automatically translated into ``GeomFromEWKT``. Unfortunately, only a few functions are automatically mapped (mainly the ones internally used by GeoAlchemy 2). Nevertheless, it is possible to define new mappings in order to translate the queries automatically. Here is an example to register a mapping for the ``ST_Buffer`` function:: >>> geoalchemy2.functions.register_sqlite_mapping( ... {"ST_Buffer": "Buffer"} ... ) After this command, all ``ST_Buffer`` calls in the queries will be translated to ``Buffer`` calls when the query is executed on a SQLite DB. A more complex example is provided for when the PostGIS function should be mapped depending on the given parameters. For example, the ``ST_Buffer`` function can actually be translate into either the ``Buffer`` function or the ``SingleSidedBuffer`` function (only when ``side=right`` or ``side=left`` is passed). See the :ref:`sphx_glr_gallery_test_specific_compilation.py` example in the gallery. GeoPackage format ----------------- Starting from the version ``4.2`` of Spatialite, it is possible to use GeoPackage files as DB containers. GeoAlchemy 2 is able to handle most of the GeoPackage features automatically if the GeoPackage dialect is used (i.e. the DB URL starts with ``gpkg:///``) and the SpatiaLite extension is loaded. Usually, this extension should be loaded using the ``load_spatialite_gpkg`` listener:: >>> from geoalchemy2 import load_spatialite_gpkg >>> from sqlalchemy import create_engine >>> from sqlalchemy.event import listen >>> >>> engine = create_engine("gpkg:///gis.gpkg", echo=True) >>> listen(engine, "connect", load_spatialite_gpkg) When using the ``load_spatialite_gpkg`` listener on a DB recognized as a GeoPackage, specific processes are activated: * the base tables are created if they are missing, * the ``Amphibious`` mode is enabled using the ``EnableGpkgAmphibiousMode`` function, * the ``VirtualGPKG`` wrapper is activated using the ``AutoGpkgStart`` function. After that it should be possible to use a GeoPackage the same way as a standard SpatiaLite database. GeoAlchemy 2 should be able to handle the following features in a transparent way for the user: * create/drop spatial tables, * automatically create/drop spatial indexes if required, * reflect spatial tables, * use spatial functions on inserted geometries. .. Note:: If you want to use the ``ST_Transform`` function you should call the :func:`geoalchemy2.admin.dialects.geopackage.create_spatial_ref_sys_view` first. Further Reference ----------------- * GeoAlchemy 2 ORM Tutotial: :ref:`orm_tutorial` * GeoAlchemy 2 Spatial Functions Reference: :ref:`spatial_functions` * GeoAlchemy 2 Spatial Operators Reference: :ref:`spatial_operators` * GeoAlchemy 2 Elements Reference: :ref:`elements` * `SpatiaLite 4.3.0 SQL functions reference list `_ geoalchemy2-0.15.2/doc/types.rst000066400000000000000000000001631464355170400164520ustar00rootroot00000000000000.. _types: Types ===== .. automodule:: geoalchemy2.types :members: :private-members: :show-inheritance: geoalchemy2-0.15.2/generate_type_stubs.py000066400000000000000000000006101464355170400204310ustar00rootroot00000000000000from pathlib import Path from geoalchemy2._functions_helpers import _generate_stubs """ this script is outside the geoalchemy2 package because the 'geoalchemy2.types' package interferes with the 'types' module in the standard library """ script_dir = Path(__file__).resolve().parent if __name__ == "__main__": (script_dir / "geoalchemy2/functions.pyi").write_text(_generate_stubs()) geoalchemy2-0.15.2/geoalchemy2/000077500000000000000000000000001464355170400162065ustar00rootroot00000000000000geoalchemy2-0.15.2/geoalchemy2/__init__.py000066400000000000000000000036631464355170400203270ustar00rootroot00000000000000"""GeoAlchemy2 package.""" from geoalchemy2 import admin from geoalchemy2 import elements # noqa from geoalchemy2 import exc # noqa from geoalchemy2 import functions # noqa from geoalchemy2 import shape # noqa from geoalchemy2 import types # noqa from geoalchemy2.admin.dialects.geopackage import load_spatialite_gpkg # noqa from geoalchemy2.admin.dialects.sqlite import load_spatialite # noqa from geoalchemy2.elements import CompositeElement # noqa from geoalchemy2.elements import RasterElement # noqa from geoalchemy2.elements import WKBElement # noqa from geoalchemy2.elements import WKTElement # noqa from geoalchemy2.exc import ArgumentError # noqa from geoalchemy2.types import Geography # noqa from geoalchemy2.types import Geometry # noqa from geoalchemy2.types import Raster # noqa admin.setup_ddl_event_listeners() # Get version number __version__ = "UNKNOWN VERSION" # Attempt to use importlib.metadata first because it's much faster # though it's only available in Python 3.8+ so we'll need to fall # back to pkg_resources for Python 3.7 support try: import importlib.metadata except ImportError: try: from pkg_resources import DistributionNotFound from pkg_resources import get_distribution except ImportError: # pragma: no cover pass else: try: __version__ = get_distribution("GeoAlchemy2").version except DistributionNotFound: # pragma: no cover pass else: try: __version__ = importlib.metadata.version("GeoAlchemy2") except importlib.metadata.PackageNotFoundError: # pragma: no cover pass __all__ = [ "__version__", "ArgumentError", "CompositeElement", "Geography", "Geometry", "Raster", "RasterElement", "WKBElement", "WKTElement", "admin", "elements", "exc", "load_spatialite", "load_spatialite_gpkg", "shape", "types", ] def __dir__(): return __all__ geoalchemy2-0.15.2/geoalchemy2/_functions.py000066400000000000000000001726171464355170400207450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # flake8: noqa from typing import List from typing import Optional from typing import Tuple from typing import Union from geoalchemy2 import types # fmt: off _FUNCTIONS: List[Tuple[str, Optional[type], Union[None, str, Tuple[str, str]]]] = [ ('AddGeometryColumn', None, '''Adds a geometry column to an existing table.'''), ('DropGeometryColumn', None, '''Removes a geometry column from a spatial table.'''), ('DropGeometryTable', None, '''Drops a table and all its references in geometry_columns.'''), ('Find_SRID', None, '''Returns the SRID defined for a geometry column.'''), ('Populate_Geometry_Columns', None, '''Ensures geometry columns are defined with type modifiers or have appropriate spatial constraints.'''), ('UpdateGeometrySRID', None, '''Updates the SRID of all features in a geometry column, and the table metadata.'''), ('ST_Collect', types.Geometry, '''Creates a GeometryCollection or Multi* geometry from a set of geometries.'''), ('ST_LineFromMultiPoint', types.Geometry, '''Creates a LineString from a MultiPoint geometry.'''), ('ST_MakeEnvelope', types.Geometry, '''Creates a rectangular Polygon from minimum and maximum coordinates.'''), ('ST_MakeLine', types.Geometry, '''Creates a Linestring from Point, MultiPoint, or LineString geometries.'''), ('ST_MakePoint', types.Geometry, '''Creates a 2D, 3DZ or 4D Point.'''), ('ST_MakePointM', types.Geometry, '''Creates a Point from X, Y and M values.'''), ('ST_MakePolygon', types.Geometry, '''Creates a Polygon from a shell and optional list of holes.'''), ('ST_Point', types.Geometry, '''Creates a Point with the given coordinate values. Alias for ST_MakePoint.'''), ('ST_Polygon', types.Geometry, '''[geometry] Creates a Polygon from a LineString with a specified SRID.\nOR\n[raster] Returns a multipolygon geometry formed by the union of pixels that have a pixel value that is not no data value. If no band number is specified, band num defaults to 1.'''), ('ST_TileEnvelope', types.Geometry, '''Creates a rectangular Polygon in Web Mercator (SRID:3857) using the XYZ tile system.'''), ('GeometryType', None, '''Returns the type of a geometry as text.'''), ('ST_Boundary', types.Geometry, '''Returns the boundary of a geometry.'''), ('ST_CoordDim', None, '''Return the coordinate dimension of a geometry.'''), ('ST_Dimension', None, '''Returns the topological dimension of a geometry.'''), ('ST_Dump', types.GeometryDump, '''Returns a set of geometry_dump rows for the components of a geometry.'''), ('ST_DumpPoints', types.GeometryDump, '''Returns a set of geometry_dump rows for the points in a geometry.'''), ('ST_DumpRings', types.GeometryDump, '''Returns a set of geometry_dump rows for the exterior and interior rings of a Polygon.'''), ('ST_EndPoint', types.Geometry, '''Returns the last point of a LineString or CircularLineString.'''), ('ST_Envelope', types.Geometry, '''[geometry] Returns a geometry representing the bounding box of a geometry.\nOR\n[raster] Returns the polygon representation of the extent of the raster.'''), ('ST_BoundingDiagonal', types.Geometry, '''Returns the diagonal of a geometry's bounding box.'''), ('ST_ExteriorRing', types.Geometry, '''Returns a LineString representing the exterior ring of a Polygon.'''), ('ST_GeometryN', types.Geometry, '''Return the Nth geometry element of a geometry collection.'''), ('ST_GeometryType', None, '''Returns the SQL-MM type of a geometry as text.'''), ('ST_HasArc', None, '''Tests if a geometry contains a circular arc'''), ('ST_InteriorRingN', types.Geometry, '''Returns the Nth interior ring (hole) of a Polygon.'''), ('ST_IsPolygonCCW', None, '''Tests if Polygons have exterior rings oriented counter-clockwise and interior rings oriented clockwise.'''), ('ST_IsPolygonCW', None, '''Tests if Polygons have exterior rings oriented clockwise and interior rings oriented counter-clockwise.'''), ('ST_IsClosed', None, '''Tests if a LineStrings's start and end points are coincident. For a PolyhedralSurface tests if it is closed (volumetric).'''), ('ST_IsCollection', None, '''Tests if a geometry is a geometry collection type.'''), ('ST_IsEmpty', None, '''[geometry] Tests if a geometry is empty.\nOR\n[raster] Returns true if the raster is empty (width = 0 and height = 0). Otherwise, returns false.'''), ('ST_IsRing', None, '''Tests if a LineString is closed and simple.'''), ('ST_IsSimple', None, '''Tests if a geometry has no points of self-intersection or self-tangency.'''), ('ST_M', None, '''Returns the M coordinate of a Point.'''), ('ST_MemSize', None, '''[geometry] Returns the amount of memory space a geometry takes.\nOR\n[raster] Returns the amount of space (in bytes) the raster takes.'''), ('ST_NDims', None, '''Returns the coordinate dimension of a geometry.'''), ('ST_NPoints', None, '''Returns the number of points (vertices) in a geometry.'''), ('ST_NRings', None, '''Returns the number of rings in a polygonal geometry.'''), ('ST_NumGeometries', None, '''Returns the number of elements in a geometry collection.'''), ('ST_NumInteriorRings', None, '''Returns the number of interior rings (holes) of a Polygon.'''), ('ST_NumInteriorRing', None, '''Returns the number of interior rings (holes) of a Polygon. Aias for ST_NumInteriorRings'''), ('ST_NumPatches', None, '''Return the number of faces on a Polyhedral Surface. Will return null for non-polyhedral geometries.'''), ('ST_NumPoints', None, '''Returns the number of points in a LineString or CircularString.'''), ('ST_PatchN', types.Geometry, '''Returns the Nth geometry (face) of a PolyhedralSurface.'''), ('ST_PointN', types.Geometry, '''Returns the Nth point in the first LineString or circular LineString in a geometry.'''), ('ST_Points', types.Geometry, '''Returns a MultiPoint containing all the coordinates of a geometry.'''), ('ST_StartPoint', types.Geometry, '''Returns the first point of a LineString.'''), ('ST_Summary', None, '''[geometry] Returns a text summary of the contents of a geometry.\nOR\n[raster] Returns a text summary of the contents of the raster.'''), ('ST_X', None, '''Returns the X coordinate of a Point.'''), ('ST_Y', None, '''Returns the Y coordinate of a Point.'''), ('ST_Z', None, '''Returns the Z coordinate of a Point.'''), ('ST_Zmflag', None, '''Returns a code indicating the ZM coordinate dimension of a geometry.'''), ('ST_AddPoint', types.Geometry, '''Add a point to a LineString.'''), ('ST_CollectionExtract', types.Geometry, '''Given a (multi)geometry, return a (multi)geometry consisting only of elements of the specified type.'''), ('ST_CollectionHomogenize', types.Geometry, '''Given a geometry collection, return the \"simplest\" representation of the contents.'''), ('ST_Force2D', types.Geometry, '''Force the geometries into a \"2-dimensional mode\".'''), ('ST_Force3D', types.Geometry, ('''Force the geometries into XYZ mode. This is an alias for ST_Force3DZ.''', 'ST_Force_3D')), ('ST_Force3DZ', types.Geometry, ('''Force the geometries into XYZ mode.''', 'ST_Force_3DZ')), ('ST_Force3DM', types.Geometry, ('''Force the geometries into XYM mode.''', 'ST_Force_3DZ')), ('ST_Force4D', types.Geometry, ('''Force the geometries into XYZM mode.''', 'ST_Force_4D')), ('ST_ForcePolygonCCW', types.Geometry, '''Orients all exterior rings counter-clockwise and all interior rings clockwise.'''), ('ST_ForceCollection', types.Geometry, ('''Convert the geometry into a GEOMETRYCOLLECTION.''', 'ST_Force_Collection')), ('ST_ForcePolygonCW', types.Geometry, '''Orients all exterior rings clockwise and all interior rings counter-clockwise.'''), ('ST_ForceSFS', types.Geometry, '''Force the geometries to use SFS 1.1 geometry types only.'''), ('ST_ForceRHR', types.Geometry, '''Force the orientation of the vertices in a polygon to follow the Right-Hand-Rule.'''), ('ST_ForceCurve', types.Geometry, '''Upcast a geometry into its curved type, if applicable.'''), ('ST_LineMerge', types.Geometry, '''Return a (set of) LineString(s) formed by sewing together a MULTILINESTRING.'''), ('ST_Multi', types.Geometry, '''Return the geometry as a MULTI* geometry.'''), ('ST_Normalize', types.Geometry, '''Return the geometry in its canonical form.'''), ('ST_QuantizeCoordinates', types.Geometry, '''Sets least significant bits of coordinates to zero'''), ('ST_RemovePoint', types.Geometry, '''Remove point from a linestring.'''), ('ST_Reverse', types.Geometry, '''Return the geometry with vertex order reversed.'''), ('ST_Segmentize', types.Geometry, '''Return a modified geometry/geography having no segment longer than the given distance.'''), ('ST_SetPoint', types.Geometry, '''Replace point of a linestring with a given point.'''), ('ST_SnapToGrid', types.Geometry, '''[geometry] Snap all points of the input geometry to a regular grid.\nOR\n[raster] Resample a raster by snapping it to a grid. New pixel values are computed using the NearestNeighbor (english or american spelling), Bilinear, Cubic, CubicSpline or Lanczos resampling algorithm. Default is NearestNeighbor.'''), ('ST_Snap', types.Geometry, '''Snap segments and vertices of input geometry to vertices of a reference geometry.'''), ('ST_SwapOrdinates', types.Geometry, '''Returns a version of the given geometry with given ordinate values swapped.'''), ('ST_IsValid', None, '''Tests if a geometry is well-formed in 2D.'''), ('ST_IsValidDetail', None, '''Returns a valid_detail row stating if a geometry is valid, and if not a reason why and a location.'''), ('ST_IsValidReason', None, '''Returns text stating if a geometry is valid, or a reason for invalidity.'''), ('ST_SetSRID', types.Geometry, '''[geometry] Set the SRID on a geometry to a particular integer value.\nOR\n[raster] Sets the SRID of a raster to a particular integer srid defined in the spatial_ref_sys table.'''), ('ST_SRID', None, '''[geometry] Returns the spatial reference identifier for the ST_Geometry as defined in spatial_ref_sys table.\nOR\n[raster] Returns the spatial reference identifier of the raster as defined in spatial_ref_sys table.'''), ('ST_Transform', types.Geometry, '''[geometry] Return a new geometry with its coordinates transformed to a different spatial reference system.\nOR\n[raster] Reprojects a raster in a known spatial reference system to another known spatial reference system using specified resampling algorithm. Options are NearestNeighbor, Bilinear, Cubic, CubicSpline, Lanczos defaulting to NearestNeighbor.'''), ('ST_BdPolyFromText', types.Geometry, '''Construct a Polygon given an arbitrary collection of closed linestrings as a MultiLineString Well-Known text representation.'''), ('ST_BdMPolyFromText', types.Geometry, '''Construct a MultiPolygon given an arbitrary collection of closed linestrings as a MultiLineString text representation Well-Known text representation.'''), ('ST_GeogFromText', types.Geography, '''Return a specified geography value from Well-Known Text representation or extended (WKT).'''), ('ST_GeographyFromText', types.Geography, '''Return a specified geography value from Well-Known Text representation or extended (WKT).'''), ('ST_GeomCollFromText', types.Geometry, '''Makes a collection Geometry from collection WKT with the given SRID. If SRID is not given, it defaults to 0.'''), ('ST_GeomFromEWKT', types.Geometry, '''Return a specified ST_Geometry value from Extended Well-Known Text representation (EWKT).'''), ('ST_GeometryFromText', types.Geometry, '''Return a specified ST_Geometry value from Well-Known Text representation (WKT). This is an alias name for ST_GeomFromText'''), ('ST_GeomFromText', types.Geometry, '''Return a specified ST_Geometry value from Well-Known Text representation (WKT).'''), ('ST_LineFromText', types.Geometry, '''Makes a Geometry from WKT representation with the given SRID. If SRID is not given, it defaults to 0.'''), ('ST_MLineFromText', types.Geometry, '''Return a specified ST_MultiLineString value from WKT representation.'''), ('ST_MPointFromText', types.Geometry, '''Makes a Geometry from WKT with the given SRID. If SRID is not given, it defaults to 0.'''), ('ST_MPolyFromText', types.Geometry, '''Makes a MultiPolygon Geometry from WKT with the given SRID. If SRID is not given, it defaults to 0.'''), ('ST_PointFromText', types.Geometry, '''Makes a point Geometry from WKT with the given SRID. If SRID is not given, it defaults to unknown.'''), ('ST_PolygonFromText', types.Geometry, '''Makes a Geometry from WKT with the given SRID. If SRID is not given, it defaults to 0.'''), ('ST_WKTToSQL', types.Geometry, '''Return a specified ST_Geometry value from Well-Known Text representation (WKT). This is an alias name for ST_GeomFromText'''), ('ST_GeogFromWKB', types.Geography, '''Creates a geography instance from a Well-Known Binary geometry representation (WKB) or extended Well Known Binary (EWKB).'''), ('ST_GeomFromEWKB', types.Geometry, '''Return a specified ST_Geometry value from Extended Well-Known Binary representation (EWKB).'''), ('ST_GeomFromWKB', types.Geometry, '''Creates a geometry instance from a Well-Known Binary geometry representation (WKB) and optional SRID.'''), ('ST_LineFromWKB', types.Geometry, '''Makes a LINESTRING from WKB with the given SRID'''), ('ST_LinestringFromWKB', types.Geometry, '''Makes a geometry from WKB with the given SRID.'''), ('ST_PointFromWKB', types.Geometry, '''Makes a geometry from WKB with the given SRID'''), ('ST_WKBToSQL', types.Geometry, '''Return a specified ST_Geometry value from Well-Known Binary representation (WKB). This is an alias name for ST_GeomFromWKB that takes no srid'''), ('ST_Box2dFromGeoHash', None, # return an unsupported Box2d object '''Return a BOX2D from a GeoHash string.'''), ('ST_GeomFromGeoHash', types.Geometry, '''Return a geometry from a GeoHash string.'''), ('ST_GeomFromGML', types.Geometry, '''Takes as input GML representation of geometry and outputs a PostGIS geometry object'''), ('ST_GeomFromGeoJSON', types.Geometry, '''Takes as input a geojson representation of a geometry and outputs a PostGIS geometry object'''), ('ST_GeomFromKML', types.Geometry, '''Takes as input KML representation of geometry and outputs a PostGIS geometry object'''), ('ST_GeomFromTWKB', types.Geometry, '''Creates a geometry instance from a TWKB (\"Tiny Well-Known Binary\") geometry representation.'''), ('ST_GMLToSQL', types.Geometry, '''Return a specified ST_Geometry value from GML representation. This is an alias name for ST_GeomFromGML'''), ('ST_LineFromEncodedPolyline', types.Geometry, '''Creates a LineString from an Encoded Polyline.'''), ('ST_PointFromGeoHash', types.Geometry, '''Return a point from a GeoHash string.'''), ('ST_AsEWKT', None, '''Return the Well-Known Text (WKT) representation of the geometry with SRID meta data.'''), ('ST_AsText', None, '''Return the Well-Known Text (WKT) representation of the geometry/geography without SRID metadata.'''), ('ST_AsBinary', None, '''[geometry] Return the Well-Known Binary (WKB) representation of the geometry/geography without SRID meta data.\nOR\n[raster] Return the Well-Known Binary (WKB) representation of the raster.'''), ('ST_AsEWKB', None, '''Return the Well-Known Binary (WKB) representation of the geometry with SRID meta data.'''), ('ST_AsHEXEWKB', None, '''Returns a Geometry in HEXEWKB format (as text) using either little-endian (NDR) or big-endian (XDR) encoding.'''), ('ST_AsEncodedPolyline', None, '''Returns an Encoded Polyline from a LineString geometry.'''), ('ST_AsGeobuf', None, '''Return a Geobuf representation of a set of rows.'''), ('ST_AsGML', None, '''Return the geometry as a GML version 2 or 3 element.'''), ('ST_AsKML', None, '''Return the geometry as a KML element. Several variants. Default version=2, default maxdecimaldigits=15'''), ('ST_AsLatLonText', None, '''Return the Degrees, Minutes, Seconds representation of the given point.'''), ('ST_AsMVTGeom', types.Geometry, '''Transform a geometry into the coordinate space of a Mapbox Vector Tile.'''), ('ST_AsMVT', None, '''Aggregate function returning a Mapbox Vector Tile representation of a set of rows.'''), ('ST_AsSVG', None, '''Returns SVG path data for a geometry.'''), ('ST_AsTWKB', None, '''Returns the geometry as TWKB, aka \"Tiny Well-Known Binary\"'''), ('ST_AsX3D', None, '''Returns a Geometry in X3D xml node element format: ISO-IEC-19776-1.2-X3DEncodings-XML'''), ('ST_GeoHash', None, '''Return a GeoHash representation of the geometry.'''), ('ST_3DIntersects', None, '''Returns TRUE if the Geometries \"spatially intersect\" in 3D - only for points, linestrings, polygons, polyhedral surface (area).'''), ('ST_Contains', None, '''[geometry] Returns true if and only if no points of B lie in the exterior of A, and at least one point of the interior of B lies in the interior of A.\nOR\n[raster] Return true if no points of raster rastB lie in the exterior of raster rastA and at least one point of the interior of rastB lies in the interior of rastA.'''), ('ST_ContainsProperly', None, '''[geometry] Returns true if B intersects the interior of A but not the boundary (or exterior). A does not contain properly itself, but does contain itself.\nOR\n[raster] Return true if rastB intersects the interior of rastA but not the boundary or exterior of rastA.'''), ('ST_Covers', None, '''[geometry] Returns 1 (TRUE) if no point in Geometry B is outside Geometry A\nOR\n[raster] Return true if no points of raster rastB lie outside raster rastA.'''), ('ST_CoveredBy', None, '''[geometry] Returns 1 (TRUE) if no point in Geometry/Geography A is outside Geometry/Geography B\nOR\n[raster] Return true if no points of raster rastA lie outside raster rastB.'''), ('ST_Crosses', None, '''Returns TRUE if the supplied geometries have some, but not all, interior points in common.'''), ('ST_LineCrossingDirection', None, '''Given 2 linestrings, returns a number between -3 and 3 denoting what kind of crossing behavior. 0 is no crossing.'''), ('ST_Disjoint', None, '''[geometry] Returns TRUE if the Geometries do not \"spatially intersect\" - if they do not share any space together.\nOR\n[raster] Return true if raster rastA does not spatially intersect rastB.'''), ('ST_Equals', None, '''Returns true if the given geometries represent the same geometry. Directionality is ignored.'''), ('ST_Intersects', None, '''[geometry] Returns TRUE if the Geometries/Geography \"spatially intersect in 2D\" - (share any portion of space) and FALSE if they don't (they are Disjoint). For geography tolerance is 0.00001 meters (so any points that close are considered to intersect)\nOR\n[raster] Return true if raster rastA spatially intersects raster rastB.'''), ('ST_OrderingEquals', None, '''Returns true if the given geometries represent the same geometry and points are in the same directional order.'''), ('ST_Overlaps', None, '''[geometry] Returns TRUE if the Geometries share space, are of the same dimension, but are not completely contained by each other.\nOR\n[raster] Return true if raster rastA and rastB intersect but one does not completely contain the other.'''), ('ST_PointInsideCircle', None, '''Is the point geometry inside the circle defined by center_x, center_y, radius'''), ('ST_Relate', None, '''Returns true if this Geometry is spatially related to anotherGeometry, by testing for intersections between the Interior, Boundary and Exterior of the two geometries as specified by the values in the intersectionMatrixPattern. If no intersectionMatrixPattern is passed in, then returns the maximum intersectionMatrixPattern that relates the 2 geometries.'''), ('ST_RelateMatch', None, '''Returns true if intersectionMattrixPattern1 implies intersectionMatrixPattern2'''), ('ST_Touches', None, '''[geometry] Returns TRUE if the geometries have at least one point in common, but their interiors do not intersect.\nOR\n[raster] Return true if raster rastA and rastB have at least one point in common but their interiors do not intersect.'''), ('ST_Within', None, '''[geometry] Returns true if the geometry A is completely inside geometry B\nOR\n[raster] Return true if no points of raster rastA lie in the exterior of raster rastB and at least one point of the interior of rastA lies in the interior of rastB.'''), ('ST_3DDWithin', None, '''For 3d (z) geometry type Returns true if two geometries 3d distance is within number of units.'''), ('ST_3DDFullyWithin', None, '''Returns true if all of the 3D geometries are within the specified distance of one another.'''), ('ST_DFullyWithin', None, '''[geometry] Returns true if all of the geometries are within the specified distance of one another\nOR\n[raster] Return true if rasters rastA and rastB are fully within the specified distance of each other.'''), ('ST_DWithin', None, '''[geometry] Returns true if the geometries are within the specified distance of one another. For geometry units are in those of spatial reference and for geography units are in meters and measurement is defaulted to use_spheroid=true (measure around spheroid), for faster check, use_spheroid=false to measure along sphere.\nOR\n[raster] Return true if rasters rastA and rastB are within the specified distance of each other.'''), ('ST_Area', None, '''Returns the area of a polygonal geometry.'''), ('ST_Azimuth', None, '''Returns the north-based azimuth as the angle in radians measured clockwise from the vertical on pointA to pointB.'''), ('ST_Angle', None, '''Returns the angle between 3 points, or between 2 vectors (4 points or 2 lines).'''), ('ST_ClosestPoint', types.Geometry, '''Returns the 2D point on g1 that is closest to g2. This is the first point of the shortest line.'''), ('ST_3DClosestPoint', types.Geometry, '''Returns the 3D point on g1 that is closest to g2. This is the first point of the 3D shortest line.'''), ('ST_Distance', None, '''Returns the distance between two geometry or geography values.'''), ('ST_3DDistance', None, '''Returns the 3D cartesian minimum distance (based on spatial ref) between two geometries in projected units.'''), ('ST_DistanceSphere', None, '''Returns minimum distance in meters between two lon/lat geometries using a spherical earth model.'''), ('ST_DistanceSpheroid', None, '''Returns the minimum distance between two lon/lat geometries using a spheroidal earth model.'''), ('ST_FrechetDistance', None, '''Returns the Fréchet distance between two geometries.'''), ('ST_HausdorffDistance', None, '''Returns the Hausdorff distance between two geometries.'''), ('ST_Length', None, '''Returns the 2D length of a linear geometry.'''), ('ST_Length2D', None, '''Returns the 2D length of a linear geometry. Alias for ST_Length'''), ('ST_3DLength', None, '''Returns the 3D length of a linear geometry.'''), ('ST_LengthSpheroid', None, '''Returns the 2D or 3D length/perimeter of a lon/lat geometry on a spheroid.'''), ('ST_LongestLine', types.Geometry, '''Returns the 2D longest line between two geometries.'''), ('ST_3DLongestLine', types.Geometry, '''Returns the 3D longest line between two geometries'''), ('ST_MaxDistance', None, '''Returns the 2D largest distance between two geometries in projected units.'''), ('ST_3DMaxDistance', None, '''Returns the 3D cartesian maximum distance (based on spatial ref) between two geometries in projected units.'''), ('ST_MinimumClearance', None, '''Returns the minimum clearance of a geometry, a measure of a geometry's robustness.'''), ('ST_MinimumClearanceLine', types.Geometry, '''Returns the two-point LineString spanning a geometry's minimum clearance.'''), ('ST_Perimeter', None, '''Returns the length of the boundary of a polygonal geometry or geography.'''), ('ST_Perimeter2D', None, '''Returns the 2D perimeter of a polygonal geometry. Alias for ST_Perimeter.'''), ('ST_3DPerimeter', None, '''Returns the 3D perimeter of a polygonal geometry.'''), ('ST_Project', types.Geography, '''Returns a point projected from a start point by a distance and bearing (azimuth).'''), ('ST_ShortestLine', types.Geometry, '''Returns the 2D shortest line between two geometries'''), ('ST_3DShortestLine', types.Geometry, '''Returns the 3D shortest line between two geometries'''), ('ST_Buffer', types.Geometry, '''(T) Returns a geometry covering all points within a given distance from the input geometry.'''), ('ST_BuildArea', types.Geometry, '''Creates an areal geometry formed by the constituent linework of given geometry'''), ('ST_Centroid', types.Geometry, '''Returns the geometric center of a geometry.'''), ('ST_ClipByBox2D', types.Geometry, '''Returns the portion of a geometry falling within a rectangle.'''), ('ST_ConcaveHull', types.Geometry, '''The concave hull of a geometry represents a possibly concave geometry that encloses all geometries within the set. You can think of it as shrink wrapping.'''), ('ST_ConvexHull', types.Geometry, '''[geometry] Computes the convex hull of a geometry.\nOR\n[raster] Return the convex hull geometry of the raster including pixel values equal to BandNoDataValue. For regular shaped and non-skewed rasters, this gives the same result as ST_Envelope so only useful for irregularly shaped or skewed rasters.'''), ('ST_CurveToLine', types.Geometry, '''Converts a CIRCULARSTRING/CURVEPOLYGON/MULTISURFACE to a LINESTRING/POLYGON/MULTIPOLYGON'''), ('ST_DelaunayTriangles', types.Geometry, '''Return a Delaunay triangulation around the given input points.'''), ('ST_Difference', types.Geometry, '''Returns a geometry that represents that part of geometry A that does not intersect with geometry B.'''), ('ST_FlipCoordinates', types.Geometry, '''Returns a version of the given geometry with X and Y axis flipped. Useful for people who have built latitude/longitude features and need to fix them.'''), ('ST_GeneratePoints', types.Geometry, '''Converts a polygon or multi-polygon into a multi-point composed of randomly location points within the original areas.'''), ('ST_GeometricMedian', types.Geometry, '''Returns the geometric median of a MultiPoint.'''), ('ST_Intersection', types.Geometry, '''[geometry] (T) Returns a geometry that represents the shared portion of geomA and geomB.\nOR\n[raster] Returns a raster or a set of geometry-pixelvalue pairs representing the shared portion of two rasters or the geometrical intersection of a vectorization of the raster and a geometry.'''), ('ST_LineToCurve', types.Geometry, '''Converts a LINESTRING/POLYGON to a CIRCULARSTRING, CURVEPOLYGON'''), ('ST_MakeValid', types.Geometry, '''Attempts to make an invalid geometry valid without losing vertices.'''), ('ST_MemUnion', types.Geometry, '''Same as ST_Union, only memory-friendly (uses less memory and more processor time).'''), ('ST_MinimumBoundingCircle', types.Geometry, '''Returns the smallest circle polygon that can fully contain a geometry. Default uses 48 segments per quarter circle.'''), ('ST_MinimumBoundingRadius', None, '''Returns the center point and radius of the smallest circle that can fully contain a geometry.'''), ('ST_OrientedEnvelope', types.Geometry, '''Returns a minimum rotated rectangle enclosing a geometry.'''), ('ST_Polygonize', types.Geometry, '''Aggregate. Creates a GeometryCollection containing possible polygons formed from the constituent linework of a set of geometries.'''), ('ST_Node', types.Geometry, '''Node a set of linestrings.'''), ('ST_OffsetCurve', types.Geometry, '''Return an offset line at a given distance and side from an input line. Useful for computing parallel lines about a center line'''), ('ST_PointOnSurface', types.Geometry, '''Returns a POINT guaranteed to lie on the surface.'''), ('ST_RemoveRepeatedPoints', types.Geometry, '''Returns a version of the given geometry with duplicated points removed.'''), ('ST_SharedPaths', types.Geometry, '''Returns a collection containing paths shared by the two input linestrings/multilinestrings.'''), ('ST_ShiftLongitude', types.Geometry, ('''Toggle geometry coordinates between -180..180 and 0..360 ranges.''', 'ST_Shift_Longitude')), ('ST_WrapX', types.Geometry, '''Wrap a geometry around an X value.'''), ('ST_Simplify', types.Geometry, '''Returns a \"simplified\" version of the given geometry using the Douglas-Peucker algorithm.'''), ('ST_SimplifyPreserveTopology', types.Geometry, '''Returns a \"simplified\" version of the given geometry using the Douglas-Peucker algorithm. Will avoid creating derived geometries (polygons in particular) that are invalid.'''), ('ST_SimplifyVW', types.Geometry, '''Returns a \"simplified\" version of the given geometry using the Visvalingam-Whyatt algorithm'''), ('ST_ChaikinSmoothing', types.Geometry, '''Returns a \"smoothed\" version of the given geometry using the Chaikin algorithm'''), ('ST_FilterByM', types.Geometry, '''Filters vertex points based on their m-value'''), ('ST_SetEffectiveArea', types.Geometry, '''Sets the effective area for each vertex, storing the value in the M ordinate. A simplified geometry can then be generated by filtering on the M ordinate.'''), ('ST_Split', types.Geometry, '''Returns a collection of geometries resulting by splitting a geometry.'''), ('ST_SymDifference', types.Geometry, '''Returns a geometry that represents the portions of A and B that do not intersect. It is called a symmetric difference because ST_SymDifference(A,B) = ST_SymDifference(B,A).'''), ('ST_Subdivide', types.Geometry, '''Returns a set of geometry where no geometry in the set has more than the specified number of vertices.'''), ('ST_Union', types.Geometry, '''[geometry] Returns a geometry that represents the point set union of the Geometries.\nOR\n[raster] Returns the union of a set of raster tiles into a single raster composed of 1 or more bands.'''), ('ST_UnaryUnion', types.Geometry, '''Like ST_Union, but working at the geometry component level.'''), ('ST_VoronoiLines', types.Geometry, '''Returns the boundaries between the cells of the Voronoi diagram constructed from the vertices of a geometry.'''), ('ST_VoronoiPolygons', types.Geometry, '''Returns the cells of the Voronoi diagram constructed from the vertices of a geometry.'''), ('ST_Affine', types.Geometry, '''Apply a 3D affine transformation to a geometry.'''), ('ST_Rotate', types.Geometry, '''Rotates a geometry about an origin point.'''), ('ST_RotateX', types.Geometry, '''Rotates a geometry about the X axis.'''), ('ST_RotateY', types.Geometry, '''Rotates a geometry about the Y axis.'''), ('ST_RotateZ', types.Geometry, '''Rotates a geometry about the Z axis.'''), ('ST_Scale', types.Geometry, '''Scales a geometry by given factors.'''), ('ST_Translate', types.Geometry, '''Translates a geometry by given offsets.'''), ('ST_TransScale', types.Geometry, '''Translates and scales a geometry by given offsets and factors.'''), ('ST_ClusterDBSCAN', None, '''Window function that returns a cluster id for each input geometry using the DBSCAN algorithm.'''), ('ST_ClusterIntersecting', types.Geometry, '''Aggregate function that clusters the input geometries into connected sets.'''), ('ST_ClusterKMeans', None, '''Window function that returns a cluster id for each input geometry using the K-means algorithm.'''), ('ST_ClusterWithin', types.Geometry, '''Aggregate function that clusters the input geometries by separation distance.'''), ('Box2D', None, # return an unsupported Box2d object ('''Returns a BOX2D representing the 2D extent of the geometry.''', 'Box2D_type')), ('Box3D', None, # return an unsupported Box3d object ('''[geometry] Returns a BOX3D representing the 3D extent of the geometry.\nOR\n[raster] Returns the box 3d representation of the enclosing box of the raster.''', 'Box3D_type')), ('ST_EstimatedExtent', None, # return an unsupported Box2d object '''Return the 'estimated' extent of a spatial table.'''), ('ST_Expand', types.Geometry, '''Returns a bounding box expanded from another bounding box or a geometry.'''), ('ST_Extent', None, # return an unsupported Box2d object '''an aggregate function that returns the bounding box that bounds rows of geometries.'''), ('ST_3DExtent', None, # return an unsupported Box3d object '''an aggregate function that returns the 3D bounding box that bounds rows of geometries.'''), ('ST_MakeBox2D', None, # return an unsupported Box2d object '''Creates a BOX2D defined by two 2D point geometries.'''), ('ST_3DMakeBox', None, # return an unsupported Box3d object '''Creates a BOX3D defined by two 3D point geometries.'''), ('ST_XMax', None, '''Returns the X maxima of a 2D or 3D bounding box or a geometry.'''), ('ST_XMin', None, '''Returns the X minima of a 2D or 3D bounding box or a geometry.'''), ('ST_YMax', None, '''Returns the Y maxima of a 2D or 3D bounding box or a geometry.'''), ('ST_YMin', None, '''Returns the Y minima of a 2D or 3D bounding box or a geometry.'''), ('ST_ZMax', None, '''Returns the Z maxima of a 2D or 3D bounding box or a geometry.'''), ('ST_ZMin', None, '''Returns the Z minima of a 2D or 3D bounding box or a geometry.'''), ('ST_LineInterpolatePoint', types.Geometry, '''Returns a point interpolated along a line. Second argument is a float8 between 0 and 1 representing fraction of total length of linestring the point has to be located.'''), ('ST_3DLineInterpolatePoint', types.Geometry, '''Returns a point interpolated along a line in 3D. Second argument is a float8 between 0 and 1 representing fraction of total length of linestring the point has to be located.'''), ('ST_LineInterpolatePoints', types.Geometry, '''Returns one or more points interpolated along a line.'''), ('ST_LineLocatePoint', None, '''Returns a float between 0 and 1 representing the location of the closest point on LineString to the given Point, as a fraction of total 2d line length.'''), ('ST_LineSubstring', types.Geometry, '''Return a linestring being a substring of the input one starting and ending at the given fractions of total 2d length. Second and third arguments are float8 values between 0 and 1.'''), ('ST_LocateAlong', types.Geometry, '''Return a derived geometry collection value with elements that match the specified measure. Polygonal elements are not supported.'''), ('ST_LocateBetween', types.Geometry, '''Return a derived geometry collection value with elements that match the specified range of measures inclusively.'''), ('ST_LocateBetweenElevations', types.Geometry, '''Return a derived geometry (collection) value with elements that intersect the specified range of elevations inclusively.'''), ('ST_InterpolatePoint', None, '''Return the value of the measure dimension of a geometry at the point closed to the provided point.'''), ('ST_AddMeasure', types.Geometry, '''Return a derived geometry with measure elements linearly interpolated between the start and end points.'''), ('ST_IsValidTrajectory', None, '''Returns true if the geometry is a valid trajectory.'''), ('ST_ClosestPointOfApproach', None, '''Returns the measure at which points interpolated along two trajectories are closest.'''), ('ST_DistanceCPA', None, '''Returns the distance between the closest point of approach of two trajectories.'''), ('ST_CPAWithin', None, '''Returns true if the closest point of approach of two trajectories is within the specified distance.'''), ('postgis_sfcgal_version', None, '''Returns the version of SFCGAL in use'''), ('ST_Extrude', types.Geometry, '''Extrude a surface to a related volume'''), ('ST_StraightSkeleton', types.Geometry, '''Compute a straight skeleton from a geometry'''), ('ST_ApproximateMedialAxis', types.Geometry, '''Compute the approximate medial axis of an areal geometry.'''), ('ST_IsPlanar', None, '''Check if a surface is or not planar'''), ('ST_Orientation', None, '''Determine surface orientation'''), ('ST_ForceLHR', types.Geometry, '''Force LHR orientation'''), ('ST_MinkowskiSum', types.Geometry, '''Performs Minkowski sum'''), ('ST_ConstrainedDelaunayTriangles', types.Geometry, '''Return a constrained Delaunay triangulation around the given input geometry.'''), ('ST_3DIntersection', types.Geometry, '''Perform 3D intersection'''), ('ST_3DDifference', types.Geometry, '''Perform 3D difference'''), ('ST_3DUnion', types.Geometry, '''Perform 3D union'''), ('ST_3DArea', None, '''Computes area of 3D surface geometries. Will return 0 for solids.'''), ('ST_Tesselate', types.Geometry, '''Perform surface Tessellation of a polygon or polyhedralsurface and returns as a TIN or collection of TINS'''), ('ST_Volume', None, '''Computes the volume of a 3D solid. If applied to surface (even closed) geometries will return 0.'''), ('ST_MakeSolid', types.Geometry, '''Cast the geometry into a solid. No check is performed. To obtain a valid solid, the input geometry must be a closed Polyhedral Surface or a closed TIN.'''), ('ST_IsSolid', None, '''Test if the geometry is a solid. No validity check is performed.'''), ('AddAuth', None, '''Adds an authorization token to be used in the current transaction.'''), ('CheckAuth', None, '''Creates a trigger on a table to prevent/allow updates and deletes of rows based on authorization token.'''), ('DisableLongTransactions', None, '''Disables long transaction support.'''), ('EnableLongTransactions', None, '''Enables long transaction support.'''), ('LockRow', None, '''Sets lock/authorization for a row in a table.'''), ('UnlockRows', None, '''Removes all locks held by an authorization token.'''), ('PostGIS_Extensions_Upgrade', None, '''Packages and upgrades postgis extensions (e.g. postgis_raster, postgis_topology, postgis_sfcgal) to latest available version.'''), ('PostGIS_Full_Version', None, '''Reports full postgis version and build configuration infos.'''), ('PostGIS_GEOS_Version', None, '''Returns the version number of the GEOS library.'''), ('PostGIS_Liblwgeom_Version', None, '''Returns the version number of the liblwgeom library. This should match the version of PostGIS.'''), ('PostGIS_LibXML_Version', None, '''Returns the version number of the libxml2 library.'''), ('PostGIS_Lib_Build_Date', None, '''Returns build date of the PostGIS library.'''), ('PostGIS_Lib_Version', None, '''Returns the version number of the PostGIS library.'''), ('PostGIS_PROJ_Version', None, '''Returns the version number of the PROJ4 library.'''), ('PostGIS_Wagyu_Version', None, '''Returns the version number of the internal Wagyu library.'''), ('PostGIS_Scripts_Build_Date', None, '''Returns build date of the PostGIS scripts.'''), ('PostGIS_Scripts_Installed', None, '''Returns version of the postgis scripts installed in this database.'''), ('PostGIS_Scripts_Released', None, '''Returns the version number of the postgis.sql script released with the installed postgis lib.'''), ('PostGIS_Version', None, '''Returns PostGIS version number and compile-time options.'''), ('PostGIS_AddBBox', types.Geometry, '''Add bounding box to the geometry.'''), ('PostGIS_DropBBox', types.Geometry, '''Drop the bounding box cache from the geometry.'''), ('PostGIS_HasBBox', None, '''Returns TRUE if the bbox of this geometry is cached, FALSE otherwise.'''), ('ST_AddBand', types.Raster, ('''Returns a raster with the new band(s) of given type added with given initial value in the given index location. If no index is specified, the band is added to the end.''', 'RT_ST_AddBand')), ('ST_AsRaster', types.Raster, ('''Converts a PostGIS geometry to a PostGIS raster.''', 'RT_ST_AsRaster')), ('ST_Band', types.Raster, ('''Returns one or more bands of an existing raster as a new raster. Useful for building new rasters from existing rasters.''', 'RT_ST_Band')), ('ST_MakeEmptyCoverage', types.Raster, ('''Cover georeferenced area with a grid of empty raster tiles.''', 'RT_ST_MakeEmptyCoverage')), ('ST_MakeEmptyRaster', types.Raster, ('''Returns an empty raster (having no bands) of given dimensions (width & height), upperleft X and Y, pixel size and rotation (scalex, scaley, skewx & skewy) and reference system (srid). If a raster is passed in, returns a new raster with the same size, alignment and SRID. If srid is left out, the spatial ref is set to unknown (0).''', 'RT_ST_MakeEmptyRaster')), ('ST_Tile', types.Raster, ('''Returns a set of rasters resulting from the split of the input raster based upon the desired dimensions of the output rasters.''', 'RT_ST_Tile')), ('ST_Retile', types.Raster, ('''Return a set of configured tiles from an arbitrarily tiled raster coverage.''', 'RT_ST_Retile')), ('ST_FromGDALRaster', types.Raster, ('''Returns a raster from a supported GDAL raster file.''', 'RT_ST_FromGDALRaster')), ('ST_GeoReference', None, ('''Returns the georeference meta data in GDAL or ESRI format as commonly seen in a world file. Default is GDAL.''', 'RT_ST_GeoReference')), ('ST_Height', None, ('''Returns the height of the raster in pixels.''', 'RT_ST_Height')), ('ST_MetaData', None, ('''Returns basic meta data about a raster object such as pixel size, rotation (skew), upper, lower left, etc.''', 'RT_ST_MetaData')), ('ST_NumBands', None, ('''Returns the number of bands in the raster object.''', 'RT_ST_NumBands')), ('ST_PixelHeight', None, ('''Returns the pixel height in geometric units of the spatial reference system.''', 'RT_ST_PixelHeight')), ('ST_PixelWidth', None, ('''Returns the pixel width in geometric units of the spatial reference system.''', 'RT_ST_PixelWidth')), ('ST_ScaleX', None, ('''Returns the X component of the pixel width in units of coordinate reference system.''', 'RT_ST_ScaleX')), ('ST_ScaleY', None, ('''Returns the Y component of the pixel height in units of coordinate reference system.''', 'RT_ST_ScaleY')), ('ST_RasterToWorldCoord', None, ('''Returns the raster's upper left corner as geometric X and Y (longitude and latitude) given a column and row. Column and row starts at 1.''', 'RT_ST_RasterToWorldCoord')), ('ST_RasterToWorldCoordX', None, ('''Returns the geometric X coordinate upper left of a raster, column and row. Numbering of columns and rows starts at 1.''', 'RT_ST_RasterToWorldCoordX')), ('ST_RasterToWorldCoordY', None, ('''Returns the geometric Y coordinate upper left corner of a raster, column and row. Numbering of columns and rows starts at 1.''', 'RT_ST_RasterToWorldCoordY')), ('ST_Rotation', None, ('''Returns the rotation of the raster in radian.''', 'RT_ST_Rotation')), ('ST_SkewX', None, ('''Returns the georeference X skew (or rotation parameter).''', 'RT_ST_SkewX')), ('ST_SkewY', None, ('''Returns the georeference Y skew (or rotation parameter).''', 'RT_ST_SkewY')), ('ST_UpperLeftX', None, ('''Returns the upper left X coordinate of raster in projected spatial ref.''', 'RT_ST_UpperLeftX')), ('ST_UpperLeftY', None, ('''Returns the upper left Y coordinate of raster in projected spatial ref.''', 'RT_ST_UpperLeftY')), ('ST_Width', None, ('''Returns the width of the raster in pixels.''', 'RT_ST_Width')), ('ST_WorldToRasterCoord', None, ('''Returns the upper left corner as column and row given geometric X and Y (longitude and latitude) or a point geometry expressed in the spatial reference coordinate system of the raster.''', 'RT_ST_WorldToRasterCoord')), ('ST_WorldToRasterCoordX', None, ('''Returns the column in the raster of the point geometry (pt) or a X and Y world coordinate (xw, yw) represented in world spatial reference system of raster.''', 'RT_ST_WorldToRasterCoordX')), ('ST_WorldToRasterCoordY', None, ('''Returns the row in the raster of the point geometry (pt) or a X and Y world coordinate (xw, yw) represented in world spatial reference system of raster.''', 'RT_ST_WorldToRasterCoordY')), ('ST_BandMetaData', None, ('''Returns basic meta data for a specific raster band. band num 1 is assumed if none-specified.''', 'RT_ST_BandMetaData')), ('ST_BandNoDataValue', None, ('''Returns the value in a given band that represents no data. If no band num 1 is assumed.''', 'RT_ST_BandNoDataValue')), ('ST_BandIsNoData', None, ('''Returns true if the band is filled with only nodata values.''', 'RT_ST_BandIsNoData')), ('ST_BandPath', None, ('''Returns system file path to a band stored in file system. If no bandnum specified, 1 is assumed.''', 'RT_ST_BandPath')), ('ST_BandFileSize', None, ('''Returns the file size of a band stored in file system. If no bandnum specified, 1 is assumed.''', 'RT_ST_BandFileSize')), ('ST_BandFileTimestamp', None, ('''Returns the file timestamp of a band stored in file system. If no bandnum specified, 1 is assumed.''', 'RT_ST_BandFileTimestamp')), ('ST_BandPixelType', None, ('''Returns the type of pixel for given band. If no bandnum specified, 1 is assumed.''', 'RT_ST_BandPixelType')), ('ST_MinPossibleValue', None, '''Returns the minimum value this pixeltype can store.'''), ('ST_HasNoBand', None, ('''Returns true if there is no band with given band number. If no band number is specified, then band number 1 is assumed.''', 'RT_ST_HasNoBand')), ('ST_PixelAsPolygon', types.Geometry, ('''Returns the polygon geometry that bounds the pixel for a particular row and column.''', 'RT_ST_PixelAsPolygon')), ('ST_PixelAsPolygons', None, ('''Returns the polygon geometry that bounds every pixel of a raster band along with the value, the X and the Y raster coordinates of each pixel.''', 'RT_ST_PixelAsPolygons')), ('ST_PixelAsPoint', types.Geometry, ('''Returns a point geometry of the pixel's upper-left corner.''', 'RT_ST_PixelAsPoint')), ('ST_PixelAsPoints', None, ('''Returns a point geometry for each pixel of a raster band along with the value, the X and the Y raster coordinates of each pixel. The coordinates of the point geometry are of the pixel's upper-left corner.''', 'RT_ST_PixelAsPoints')), ('ST_PixelAsCentroid', types.Geometry, ('''Returns the centroid (point geometry) of the area represented by a pixel.''', 'RT_ST_PixelAsCentroid')), ('ST_PixelAsCentroids', None, ('''Returns the centroid (point geometry) for each pixel of a raster band along with the value, the X and the Y raster coordinates of each pixel. The point geometry is the centroid of the area represented by a pixel.''', 'RT_ST_PixelAsCentroids')), ('ST_Value', None, ('''Returns the value of a given band in a given columnx, rowy pixel or at a particular geometric point. Band numbers start at 1 and assumed to be 1 if not specified. If exclude_nodata_value is set to false, then all pixels include nodata pixels are considered to intersect and return value. If exclude_nodata_value is not passed in then reads it from metadata of raster.''', 'RT_ST_Value')), ('ST_NearestValue', None, ('''Returns the nearest non-NODATA value of a given band's pixel specified by a columnx and rowy or a geometric point expressed in the same spatial reference coordinate system as the raster.''', 'RT_ST_NearestValue')), ('ST_Neighborhood', None, ('''Returns a 2-D double precision array of the non-NODATA values around a given band's pixel specified by either a columnX and rowY or a geometric point expressed in the same spatial reference coordinate system as the raster.''', 'RT_ST_Neighborhood')), ('ST_SetValue', types.Raster, ('''Returns modified raster resulting from setting the value of a given band in a given columnx, rowy pixel or the pixels that intersect a particular geometry. Band numbers start at 1 and assumed to be 1 if not specified.''', 'RT_ST_SetValue')), ('ST_SetValues', types.Raster, ('''Returns modified raster resulting from setting the values of a given band.''', 'RT_ST_SetValues')), ('ST_DumpValues', None, ('''Get the values of the specified band as a 2-dimension array.''', 'RT_ST_DumpValues')), ('ST_PixelOfValue', None, ('''Get the columnx, rowy coordinates of the pixel whose value equals the search value.''', 'RT_ST_PixelOfValue')), ('ST_SetGeoReference', types.Raster, ('''Set Georeference 6 georeference parameters in a single call. Numbers should be separated by white space. Accepts inputs in GDAL or ESRI format. Default is GDAL.''', 'RT_ST_SetGeoReference')), ('ST_SetRotation', types.Raster, ('''Set the rotation of the raster in radian.''', 'RT_ST_SetRotation')), ('ST_SetScale', types.Raster, ('''Sets the X and Y size of pixels in units of coordinate reference system. Number units/pixel width/height.''', 'RT_ST_SetScale')), ('ST_SetSkew', types.Raster, ('''Sets the georeference X and Y skew (or rotation parameter). If only one is passed in, sets X and Y to the same value.''', 'RT_ST_SetSkew')), ('ST_SetUpperLeft', types.Raster, ('''Sets the value of the upper left corner of the pixel of the raster to projected X and Y coordinates.''', 'RT_ST_SetUpperLeft')), ('ST_Resample', types.Raster, ('''Resample a raster using a specified resampling algorithm, new dimensions, an arbitrary grid corner and a set of raster georeferencing attributes defined or borrowed from another raster.''', 'RT_ST_Resample')), ('ST_Rescale', types.Raster, ('''Resample a raster by adjusting only its scale (or pixel size). New pixel values are computed using the NearestNeighbor (english or american spelling), Bilinear, Cubic, CubicSpline or Lanczos resampling algorithm. Default is NearestNeighbor.''', 'RT_ST_Rescale')), ('ST_Reskew', types.Raster, ('''Resample a raster by adjusting only its skew (or rotation parameters). New pixel values are computed using the NearestNeighbor (english or american spelling), Bilinear, Cubic, CubicSpline or Lanczos resampling algorithm. Default is NearestNeighbor.''', 'RT_ST_Reskew')), ('ST_Resize', types.Raster, ('''Resize a raster to a new width/height''', 'RT_ST_Resize')), ('ST_SetBandNoDataValue', types.Raster, ('''Sets the value for the given band that represents no data. Band 1 is assumed if no band is specified. To mark a band as having no nodata value, set the nodata value = NULL.''', 'RT_ST_SetBandNoDataValue')), ('ST_SetBandIsNoData', types.Raster, ('''Sets the isnodata flag of the band to TRUE.''', 'RT_ST_SetBandIsNoData')), ('ST_SetBandPath', types.Raster, ('''Update the external path and band number of an out-db band''', 'RT_ST_SetBandPath')), ('ST_SetBandIndex', types.Raster, ('''Update the external band number of an out-db band''', 'RT_ST_SetBandIndex')), ('ST_Count', None, ('''Returns the number of pixels in a given band of a raster or raster coverage. If no band is specified defaults to band 1. If exclude_nodata_value is set to true, will only count pixels that are not equal to the nodata value.''', 'RT_ST_Count')), ('ST_CountAgg', None, ('''Aggregate. Returns the number of pixels in a given band of a set of rasters. If no band is specified defaults to band 1. If exclude_nodata_value is set to true, will only count pixels that are not equal to the NODATA value.''', 'RT_ST_CountAgg')), ('ST_Histogram', None, ('''Returns a set of record summarizing a raster or raster coverage data distribution separate bin ranges. Number of bins are autocomputed if not specified.''', 'RT_ST_Histogram')), ('ST_Quantile', None, ('''Compute quantiles for a raster or raster table coverage in the context of the sample or population. Thus, a value could be examined to be at the raster's 25%, 50%, 75% percentile.''', 'RT_ST_Quantile')), ('ST_SummaryStats', None, ('''Returns summarystats consisting of count, sum, mean, stddev, min, max for a given raster band of a raster or raster coverage. Band 1 is assumed is no band is specified.''', 'RT_ST_SummaryStats')), ('ST_SummaryStatsAgg', types.SummaryStats, ('''Aggregate. Returns summarystats consisting of count, sum, mean, stddev, min, max for a given raster band of a set of raster. Band 1 is assumed is no band is specified.''', 'RT_ST_SummaryStatsAgg')), ('ST_ValueCount', None, ('''Returns a set of records containing a pixel band value and count of the number of pixels in a given band of a raster (or a raster coverage) that have a given set of values. If no band is specified defaults to band 1. By default nodata value pixels are not counted. and all other values in the pixel are output and pixel band values are rounded to the nearest integer.''', 'RT_ST_ValueCount')), ('ST_RastFromWKB', types.Raster, ('''Return a raster value from a Well-Known Binary (WKB) raster.''', 'RT_ST_RastFromWKB')), ('ST_RastFromHexWKB', types.Raster, ('''Return a raster value from a Hex representation of Well-Known Binary (WKB) raster.''', 'RT_ST_RastFromHexWKB')), ('ST_AsWKB', None, ('''Return the Well-Known Binary (WKB) representation of the raster.''', 'RT_ST_AsBinary')), ('ST_AsHexWKB', None, ('''Return the Well-Known Binary (WKB) in Hex representation of the raster.''', 'RT_ST_AsHexWKB')), ('ST_AsGDALRaster', None, ('''Return the raster tile in the designated GDAL Raster format. Raster formats are one of those supported by your compiled library. Use ST_GDALDrivers() to get a list of formats supported by your library.''', 'RT_ST_AsGDALRaster')), ('ST_AsJPEG', None, ('''Return the raster tile selected bands as a single Joint Photographic Exports Group (JPEG) image (byte array). If no band is specified and 1 or more than 3 bands, then only the first band is used. If only 3 bands then all 3 bands are used and mapped to RGB.''', 'RT_ST_AsJPEG')), ('ST_AsPNG', None, ('''Return the raster tile selected bands as a single portable network graphics (PNG) image (byte array). If 1, 3, or 4 bands in raster and no bands are specified, then all bands are used. If more 2 or more than 4 bands and no bands specified, then only band 1 is used. Bands are mapped to RGB or RGBA space.''', 'RT_ST_AsPNG')), ('ST_AsTIFF', None, ('''Return the raster selected bands as a single TIFF image (byte array). If no band is specified or any of specified bands does not exist in the raster, then will try to use all bands.''', 'RT_ST_AsTIFF')), ('ST_Clip', types.Raster, ('''Returns the raster clipped by the input geometry. If band number not is specified, all bands are processed. If crop is not specified or TRUE, the output raster is cropped.''', 'RT_ST_Clip')), ('ST_ColorMap', types.Raster, ('''Creates a new raster of up to four 8BUI bands (grayscale, RGB, RGBA) from the source raster and a specified band. Band 1 is assumed if not specified.''', 'RT_ST_ColorMap')), ('ST_Grayscale', types.Raster, ('''Creates a new one-8BUI band raster from the source raster and specified bands representing Red, Green and Blue''', 'RT_ST_Grayscale')), ('ST_MapAlgebra', None, ('''[raster] Callback function version - Returns a one-band raster given one or more input rasters, band indexes and one user-specified callback function.\nOR\n[raster] Expression version - Returns a one-band raster given one or two input rasters, band indexes and one or more user-specified SQL expressions.''', 'RT_ST_MapAlgebra')), ('ST_MapAlgebraExpr', types.Raster, ('''[raster] 1 raster band version: Creates a new one band raster formed by applying a valid PostgreSQL algebraic operation on the input raster band and of pixeltype provided. Band 1 is assumed if no band is specified.\nOR\n[raster] 2 raster band version: Creates a new one band raster formed by applying a valid PostgreSQL algebraic operation on the two input raster bands and of pixeltype provided. band 1 of each raster is assumed if no band numbers are specified. The resulting raster will be aligned (scale, skew and pixel corners) on the grid defined by the first raster and have its extent defined by the \"extenttype\" parameter. Values for \"extenttype\" can be: INTERSECTION, UNION, FIRST, SECOND.''', 'RT_ST_MapAlgebraExpr')), ('ST_MapAlgebraFct', types.Raster, ('''[raster] 1 band version - Creates a new one band raster formed by applying a valid PostgreSQL function on the input raster band and of pixeltype provided. Band 1 is assumed if no band is specified.\nOR\n[raster] 2 band version - Creates a new one band raster formed by applying a valid PostgreSQL function on the 2 input raster bands and of pixeltype provided. Band 1 is assumed if no band is specified. Extent type defaults to INTERSECTION if not specified.''', 'RT_ST_MapAlgebraFct')), ('ST_MapAlgebraFctNgb', types.Raster, ('''1-band version: Map Algebra Nearest Neighbor using user-defined PostgreSQL function. Return a raster which values are the result of a PLPGSQL user function involving a neighborhood of values from the input raster band.''', 'RT_ST_MapAlgebraFctNgb')), ('ST_Reclass', types.Raster, ('''Creates a new raster composed of band types reclassified from original. The nband is the band to be changed. If nband is not specified assumed to be 1. All other bands are returned unchanged. Use case: convert a 16BUI band to a 8BUI and so forth for simpler rendering as viewable formats.''', 'RT_ST_Reclass')), ('ST_Distinct4ma', None, ('''Raster processing function that calculates the number of unique pixel values in a neighborhood.''', 'RT_ST_Distinct4ma')), ('ST_InvDistWeight4ma', None, ('''Raster processing function that interpolates a pixel's value from the pixel's neighborhood.''', 'RT_ST_InvDistWeight4ma')), ('ST_Max4ma', None, ('''Raster processing function that calculates the maximum pixel value in a neighborhood.''', 'RT_ST_Max4ma')), ('ST_Mean4ma', None, ('''Raster processing function that calculates the mean pixel value in a neighborhood.''', 'RT_ST_Mean4ma')), ('ST_Min4ma', None, ('''Raster processing function that calculates the minimum pixel value in a neighborhood.''', 'RT_ST_Min4ma')), ('ST_MinDist4ma', None, ('''Raster processing function that returns the minimum distance (in number of pixels) between the pixel of interest and a neighboring pixel with value.''', 'RT_ST_MinDist4ma')), ('ST_Range4ma', None, ('''Raster processing function that calculates the range of pixel values in a neighborhood.''', 'RT_ST_Range4ma')), ('ST_StdDev4ma', None, ('''Raster processing function that calculates the standard deviation of pixel values in a neighborhood.''', 'RT_ST_StdDev4ma')), ('ST_Sum4ma', None, ('''Raster processing function that calculates the sum of all pixel values in a neighborhood.''', 'RT_ST_Sum4ma')), ('ST_Aspect', types.Raster, ('''Returns the aspect (in degrees by default) of an elevation raster band. Useful for analyzing terrain.''', 'RT_ST_Aspect')), ('ST_HillShade', types.Raster, ('''Returns the hypothetical illumination of an elevation raster band using provided azimuth, altitude, brightness and scale inputs.''', 'RT_ST_HillShade')), ('ST_Roughness', types.Raster, ('''Returns a raster with the calculated \"roughness\" of a DEM.''', 'RT_ST_Roughness')), ('ST_Slope', types.Raster, ('''Returns the slope (in degrees by default) of an elevation raster band. Useful for analyzing terrain.''', 'RT_ST_Slope')), ('ST_TPI', types.Raster, ('''Returns a raster with the calculated Topographic Position Index.''', 'RT_ST_TPI')), ('ST_TRI', types.Raster, ('''Returns a raster with the calculated Terrain Ruggedness Index.''', 'RT_ST_TRI')), ('ST_DumpAsPolygons', None, ('''Returns a set of geomval (geom,val) rows, from a given raster band. If no band number is specified, band num defaults to 1.''', 'RT_ST_DumpAsPolygons')), ('ST_MinConvexHull', types.Geometry, ('''Return the convex hull geometry of the raster excluding NODATA pixels.''', 'RT_ST_MinConvexHull')), ('ST_SameAlignment', None, ('''Returns true if rasters have same skew, scale, spatial ref, and offset (pixels can be put on same grid without cutting into pixels) and false if they don't with notice detailing issue.''', 'RT_ST_SameAlignment')), ('ST_NotSameAlignmentReason', None, ('''Returns text stating if rasters are aligned and if not aligned, a reason why.''', 'RT_ST_NotSameAlignmentReason')), ('ST_Distance_Sphere', None, '''Returns minimum distance in meters between two lon/lat geometries. Uses a spherical earth and radius of 6370986 meters. Faster than ``ST_Distance_Spheroid``, but less accurate. PostGIS versions prior to 1.5 only implemented for points.'''), ] # fmt: on geoalchemy2-0.15.2/geoalchemy2/_functions_helpers.py000066400000000000000000000051331464355170400224530ustar00rootroot00000000000000from textwrap import TextWrapper from typing import Optional from typing import Tuple from typing import Union def _wrap_docstring(docstring: str) -> str: wrapper = TextWrapper(width=100) lines = [] for long_line in docstring.splitlines(keepends=False): lines.extend(wrapper.wrap(long_line)) return "\n".join(lines) def _get_docstring(name: str, doc: Union[None, str, Tuple[str, str]], type_: Optional[type]) -> str: doc_string_parts = [] if isinstance(doc, tuple): doc_string_parts.append(_wrap_docstring(doc[0])) doc_string_parts.append("see https://postgis.net/docs/{0}.html".format(doc[1])) elif doc is not None: doc_string_parts.append(_wrap_docstring(doc)) doc_string_parts.append("see https://postgis.net/docs/{0}.html".format(name)) if type_ is not None: return_type_str = "{0}.{1}".format(type_.__module__, type_.__name__) doc_string_parts.append("Return type: :class:`{0}`.".format(return_type_str)) return "\n\n".join(doc_string_parts) def _replace_indent(text: str, indent: str) -> str: lines = [] for i, line in enumerate(text.splitlines()): if i == 0 or not line.strip(): lines.append(line) else: lines.append(f"{indent}{line}") return "\n".join(lines) def _generate_stubs() -> str: """Generates type stubs for the dynamic functions described in `geoalchemy2/_functions.py`.""" from geoalchemy2._functions import _FUNCTIONS from geoalchemy2.functions import ST_AsGeoJSON header = '''\ # this file is automatically generated from typing import List from sqlalchemy.sql import functions from sqlalchemy.sql.elements import ColumnElement import geoalchemy2.types class GenericFunction(functions.GenericFunction): ... class TableRowElement(ColumnElement): inherit_cache: bool = ... """The cache is disabled for this class.""" def __init__(self, selectable: bool) -> None: ... @property # type: ignore[override] def _from_objects(self) -> List[bool]: ... ''' stub_file_parts = [header] functions = _FUNCTIONS.copy() functions.insert(0, ("ST_AsGeoJSON", None, ST_AsGeoJSON.__doc__)) for name, type_, doc_parts in functions: doc = _replace_indent(_get_docstring(name, doc_parts, type_), " ") if type_ is None: type_str = "" else: type_str = f"\n\n type = {type_.__module__}.{type_.__name__}()" signature = f'''\ class {name}(GenericFunction): """ {doc} """{type_str} ''' stub_file_parts.append(signature) return "\n".join(stub_file_parts) geoalchemy2-0.15.2/geoalchemy2/admin/000077500000000000000000000000001464355170400172765ustar00rootroot00000000000000geoalchemy2-0.15.2/geoalchemy2/admin/__init__.py000066400000000000000000000076201464355170400214140ustar00rootroot00000000000000"""This module defines the functions used for administration tasks.""" from sqlalchemy import Column from sqlalchemy import Index from sqlalchemy import Table from sqlalchemy import event from sqlalchemy.sql import func from geoalchemy2.admin import dialects from geoalchemy2.admin.dialects.common import _check_spatial_type from geoalchemy2.admin.dialects.common import _spatial_idx_name from geoalchemy2.exc import ArgumentError from geoalchemy2.types import Geography from geoalchemy2.types import Geometry from geoalchemy2.types import Raster def select_dialect(dialect_name): """Select the dialect from its name.""" known_dialects = { "geopackage": dialects.geopackage, "mysql": dialects.mysql, "mariadb": dialects.mysql, "postgresql": dialects.postgresql, "sqlite": dialects.sqlite, } return known_dialects.get(dialect_name, dialects.common) def setup_ddl_event_listeners(): """Setup the DDL event listeners to automatically process spatial columns.""" @event.listens_for(Table, "before_create") def before_create(table, bind, **kw): """Handle spatial indexes.""" select_dialect(bind.dialect.name).before_create(table, bind, **kw) @event.listens_for(Table, "after_create") def after_create(table, bind, **kw): """Restore original column list including managed Geometry columns.""" select_dialect(bind.dialect.name).after_create(table, bind, **kw) @event.listens_for(Table, "before_drop") def before_drop(table, bind, **kw): """Drop the managed Geometry columns.""" select_dialect(bind.dialect.name).before_drop(table, bind, **kw) @event.listens_for(Table, "after_drop") def after_drop(table, bind, **kw): """Restore original column list including managed Geometry columns.""" select_dialect(bind.dialect.name).after_drop(table, bind, **kw) @event.listens_for(Column, "after_parent_attach") def after_parent_attach(column, table): """Automatically add spatial indexes.""" if not isinstance(table, Table): # For old versions of SQLAlchemy, subqueries might trigger the after_parent_attach event # with a selectable as table, so we want to skip this case. return if not getattr(column.type, "nullable", True): column.nullable = column.type.nullable elif hasattr(column.type, "nullable"): column.type.nullable = column.nullable if not getattr(column.type, "spatial_index", False) and getattr( column.type, "use_N_D_index", False ): raise ArgumentError("Arg Error(use_N_D_index): spatial_index must be True") if not getattr(column.type, "spatial_index", False): # If the column is managed, the indexes are created after the table return try: if column.type._spatial_index_reflected: return except AttributeError: pass kwargs = { "postgresql_using": "gist", "_column_flag": True, } col = column if _check_spatial_type(column.type, (Geometry, Geography)): if column.type.use_N_D_index: kwargs["postgresql_ops"] = {column.name: "gist_geometry_ops_nd"} elif _check_spatial_type(column.type, Raster): col = func.ST_ConvexHull(column) table.append_constraint( Index( _spatial_idx_name(table.name, column.name), col, **kwargs, ) ) @event.listens_for(Table, "column_reflect") def _reflect_geometry_column(inspector, table, column_info): select_dialect(inspector.bind.dialect.name).reflect_geometry_column( inspector, table, column_info ) __all__ = [ "dialects", "select_dialect", "setup_ddl_event_listeners", ] def __dir__(): return __all__ geoalchemy2-0.15.2/geoalchemy2/admin/dialects/000077500000000000000000000000001464355170400210665ustar00rootroot00000000000000geoalchemy2-0.15.2/geoalchemy2/admin/dialects/__init__.py000066400000000000000000000005571464355170400232060ustar00rootroot00000000000000"""This module defines some dialect-specific functions used for administration tasks.""" from geoalchemy2.admin.dialects import common # noqa from geoalchemy2.admin.dialects import geopackage # noqa from geoalchemy2.admin.dialects import mysql # noqa from geoalchemy2.admin.dialects import postgresql # noqa from geoalchemy2.admin.dialects import sqlite # noqa geoalchemy2-0.15.2/geoalchemy2/admin/dialects/common.py000066400000000000000000000055701464355170400227370ustar00rootroot00000000000000"""This module defines functions used by several dialects.""" import sqlalchemy from packaging import version from sqlalchemy import Column from sqlalchemy.sql import expression from sqlalchemy.types import TypeDecorator from geoalchemy2.types import Geometry _SQLALCHEMY_VERSION_BEFORE_14 = version.parse(sqlalchemy.__version__) < version.parse("1.4") def _spatial_idx_name(table_name, column_name): return "idx_{}_{}".format(table_name, column_name) def _format_select_args(*args): if _SQLALCHEMY_VERSION_BEFORE_14: return [args] else: return args def check_management(*args): """Default function to check management (always True by default).""" return True def _get_gis_cols(table, spatial_types, dialect, check_col_management=None): if check_col_management is not None: func = check_col_management else: func = check_management return [ col for col in table.columns if ( isinstance(col, Column) and _check_spatial_type(col.type, spatial_types, dialect) and func(col) ) ] def _check_spatial_type(tested_type, spatial_types, dialect=None): return isinstance(tested_type, spatial_types) or ( isinstance(tested_type, TypeDecorator) and isinstance(tested_type.load_dialect_impl(dialect), spatial_types) ) def _get_dispatch_info(table, bind, check_col_management=None): """Get info required for dispatch events.""" dialect = bind.dialect # Filter Geometry columns from the table # Note: Geography and PostGIS >= 2.0 don't need this gis_cols = _get_gis_cols(table, Geometry, dialect, check_col_management=check_col_management) # Find all other columns that are not managed Geometries regular_cols = [x for x in table.columns if x not in gis_cols] return dialect, gis_cols, regular_cols def _update_table_for_dispatch(table, regular_cols): """Update the table before dispatch events.""" # Save original table column list for later table.info["_saved_columns"] = table.columns # Temporarily patch a set of columns not including the # managed Geometry columns column_collection = expression.ColumnCollection() for col in regular_cols: column_collection.add(col) table.columns = column_collection def setup_create_drop(table, bind, check_col_management=None): """Prepare the table for before_create and before_drop events.""" dialect, gis_cols, regular_cols = _get_dispatch_info(table, bind, check_col_management) _update_table_for_dispatch(table, regular_cols) return dialect, gis_cols, regular_cols def reflect_geometry_column(inspector, table, column_info): return def before_create(table, bind, **kw): return def after_create(table, bind, **kw): return def before_drop(table, bind, **kw): return def after_drop(table, bind, **kw): return geoalchemy2-0.15.2/geoalchemy2/admin/dialects/geopackage.py000066400000000000000000000322351464355170400235330ustar00rootroot00000000000000"""This module defines specific functions for GeoPackage dialect. See GeoPackage specifications here: http://www.geopackage.org/spec/ """ import re from sqlalchemy import text from sqlalchemy.dialects import registry from sqlalchemy.dialects.sqlite.pysqlite import SQLiteDialect_pysqlite from sqlalchemy.ext.compiler import compiles from sqlalchemy.sql import func from sqlalchemy.sql import select from geoalchemy2 import functions from geoalchemy2.admin.dialects.common import _check_spatial_type from geoalchemy2.admin.dialects.common import _format_select_args from geoalchemy2.admin.dialects.common import _spatial_idx_name from geoalchemy2.admin.dialects.common import setup_create_drop from geoalchemy2.admin.dialects.sqlite import _SQLITE_FUNCTIONS from geoalchemy2.admin.dialects.sqlite import get_col_dim from geoalchemy2.admin.dialects.sqlite import load_spatialite_driver from geoalchemy2.types import Geography from geoalchemy2.types import Geometry from geoalchemy2.types import _DummyGeometry class GeoPackageDialect(SQLiteDialect_pysqlite): """Define a specific dialect for GeoPackage.""" name = "geopackage" driver = "gpkg" supports_statement_cache = True """Enable caching for GeoPackage dialect.""" registry.register("gpkg", "geoalchemy2.admin.dialects.geopackage", "GeoPackageDialect") def load_geopackage_driver(dbapi_conn, *args): """Load SpatiaLite extension in GeoPackage connection and set VirtualGpkg and Amphibious modes. .. Warning:: The path to the SpatiaLite module should be set in the `SPATIALITE_LIBRARY_PATH` environment variable. Args: dbapi_conn: The DBAPI connection. """ load_spatialite_driver(dbapi_conn, *args) dbapi_conn.execute("SELECT AutoGpkgStart();") dbapi_conn.execute("SELECT EnableGpkgAmphibiousMode();") def init_geopackage(dbapi_conn, *args): """Initialize GeoPackage tables. Args: dbapi_conn: The DBAPI connection. .. Warning:: No EPSG SRID is loaded in the `gpkg_spatial_ref_sys` table after initialization but it is possible to load other EPSG SRIDs afterwards using the `gpkgInsertEpsgSRID(srid)`. Nevertheless, SRIDs of newly created tables are automatically added. """ if not dbapi_conn.execute("SELECT CheckGeoPackageMetaData();").fetchone()[0]: # This only works on the main database dbapi_conn.execute("SELECT gpkgCreateBaseTables();") def load_spatialite_gpkg(*args, **kwargs): """Load SpatiaLite extension in GeoPackage and initialize internal tables. See :func:`geoalchemy2.admin.dialects.geopackage.load_geopackage_driver` and :func:`geoalchemy2.admin.dialects.geopackage.init_geopackage` functions for details about arguments. """ load_geopackage_driver(*args) init_geopackage(*args, **kwargs) def _get_spatialite_attrs(bind, table_name, col_name): attrs = bind.execute( text( """SELECT A.geometry_type_name, A.srs_id, A.z, A.m, IFNULL(B.has_index, 0) AS has_index FROM gpkg_geometry_columns AS A LEFT JOIN ( SELECT table_name, column_name, COUNT(*) AS has_index FROM gpkg_extensions WHERE LOWER(table_name) = LOWER(:table_name) AND column_name = :column_name AND extension_name = 'gpkg_rtree_index' ) AS B ON LOWER(A.table_name) = LOWER(B.table_name) AND A.column_name = B.column_name WHERE LOWER(A.table_name) = LOWER(:table_name) AND A.column_name = :column_name; """ ).bindparams(table_name=table_name, column_name=col_name) ).fetchone() if attrs is None: # If the column is not registered as a spatial column we ignore it return None geometry_type, srid, has_z, has_m, has_index = attrs coord_dimension = "XY" if has_z: coord_dimension += "Z" if has_m: coord_dimension += "M" col_attributes = geometry_type, coord_dimension, srid, has_index return col_attributes def _setup_dummy_type(table, gis_cols): """Setup dummy type for new Geometry columns so they can be updated later.""" for col in gis_cols: # Add dummy columns with GEOMETRY type type_str = re.fullmatch("(.+?)[ZMzm]*", col.type.geometry_type).group(1) col._actual_type = col.type col.type = _DummyGeometry(geometry_type=type_str) table.columns = table.info["_saved_columns"] def create_spatial_index(bind, table, col): """Create spatial index on the given column.""" stmt = select(*_format_select_args(func.gpkgAddSpatialIndex(table.name, col.name))) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) def disable_spatial_index(bind, table, col): """Disable spatial indexes if present.""" for i in ["", "_node", "_parent", "_rowid"]: bind.execute( text( "DROP TABLE IF EXISTS rtree_{}_{}{};".format( table.name, col.name, i, ) ) ) bind.execute( text( """DELETE FROM gpkg_extensions WHERE LOWER(table_name) = LOWER(:table_name) AND column_name = :column_name AND extension_name = 'gpkg_rtree_index';""" ).bindparams( table_name=table.name, column_name=col.name, ) ) def reflect_geometry_column(inspector, table, column_info): """Reflect a column of type Geometry with GeoPackage dialect.""" # Get geometry type, SRID and spatial index from the SpatiaLite metadata if not isinstance(column_info.get("type"), Geometry): return col_attributes = _get_spatialite_attrs(inspector.bind, table.name, column_info["name"]) if col_attributes is not None: geometry_type, coord_dimension, srid, spatial_index = col_attributes coord_dimension = { "XY": 2, "XYZ": 3, "XYM": 3, "XYZM": 4, }.get(coord_dimension, coord_dimension) # Set attributes column_info["type"].geometry_type = geometry_type column_info["type"].dimension = coord_dimension column_info["type"].srid = srid column_info["type"].spatial_index = bool(spatial_index) # Spatial indexes are not automatically reflected with GeoPackage dialect column_info["type"]._spatial_index_reflected = False def before_create(table, bind, **kw): """Handle spatial indexes during the before_create event.""" dialect, gis_cols, regular_cols = setup_create_drop(table, bind) # Remove the spatial indexes from the table metadata because they should not be # created during the table.create() step since the associated columns do not exist # at this time. table.info["_after_create_indexes"] = [] current_indexes = set(table.indexes) for idx in current_indexes: for col in table.info["_saved_columns"]: if _check_spatial_type(col.type, Geometry, dialect) and col in idx.columns.values(): table.indexes.remove(idx) if idx.name != _spatial_idx_name(table.name, col.name) or not getattr( col.type, "spatial_index", False ): table.info["_after_create_indexes"].append(idx) if len(gis_cols) > 1: raise ValueError("Only one geometry column is allowed for a table stored in a GeoPackage.") elif len(gis_cols) == 1: col = gis_cols[0] srid = col.type.srid if col.type.geometry_type is None: col.type.geometry_type = "GEOMETRY" # Add the SRID of the table in 'gpkg_spatial_ref_sys' if this table exists if not bind.execute( text("SELECT COUNT(*) FROM gpkg_spatial_ref_sys WHERE srs_id = :srid;").bindparams( srid=srid ) ).scalar(): bind.execute(text("SELECT gpkgInsertEpsgSRID(:srid)").bindparams(srid=srid)) _setup_dummy_type(table, gis_cols) def after_create(table, bind, **kw): """Handle spatial indexes during the after_create event.""" dialect = bind.dialect for col in table.columns: # Add the managed Geometry columns with gpkgAddGeometryColumn() if _check_spatial_type(col.type, Geometry, dialect): col.type = col._actual_type del col._actual_type dimension = get_col_dim(col) has_z = "Z" in dimension has_m = "M" in dimension bind.execute( text( """INSERT INTO gpkg_contents VALUES ( :table_name, 'features', :table_name, "", strftime('%Y-%m-%dT%H:%M:%fZ', CURRENT_TIMESTAMP), NULL, NULL, NULL, NULL, :srid );""" ).bindparams( table_name=table.name, srid=col.type.srid, ) ) bind.execute( text( """INSERT INTO gpkg_geometry_columns VALUES (:table_name, :column_name, :geometry_type, :srid, :has_z, :has_m);""" ).bindparams( table_name=table.name, column_name=col.name, geometry_type=col.type.geometry_type, srid=col.type.srid, has_z=has_z, has_m=has_m, ) ) stmt = select(*_format_select_args(func.gpkgAddGeometryTriggers(table.name, col.name))) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) for col in table.columns: # Add spatial indexes for the Geometry and Geography columns # TODO: Check that the Geography type makes sense here if ( _check_spatial_type(col.type, (Geometry, Geography), dialect) and col.type.spatial_index is True ): create_spatial_index(bind, table, col) for idx in table.info.pop("_after_create_indexes"): table.indexes.add(idx) idx.create(bind=bind) def before_drop(table, bind, **kw): """Handle spatial indexes during the before_drop event.""" dialect, gis_cols, regular_cols = setup_create_drop(table, bind) for col in gis_cols: # Disable spatial indexes if present # TODO: This is useless but if we remove it then the disable_spatial_index should be # tested separately disable_spatial_index(bind, table, col) # Remove metadata from internal tables # (this is equivalent to DiscardGeometryColumn but for GeoPackage) bind.execute( text( """DELETE FROM gpkg_extensions WHERE LOWER(table_name) = LOWER(:table_name) AND column_name = :column_name;""" ).bindparams( table_name=table.name, column_name=col.name, ) ) bind.execute( text( """DELETE FROM gpkg_geometry_columns WHERE LOWER(table_name) = LOWER(:table_name) AND column_name = :column_name;""" ).bindparams( table_name=table.name, column_name=col.name, ) ) bind.execute( text( """DELETE FROM gpkg_contents WHERE LOWER(table_name) = LOWER(:table_name);""" ).bindparams(table_name=table.name) ) def after_drop(table, bind, **kw): """Handle spatial indexes during the after_drop event.""" table.columns = table.info.pop("_saved_columns") def _compiles_gpkg(cls, fn): def _compile_gpkg(element, compiler, **kw): return "{}({})".format(fn, compiler.process(element.clauses, **kw)) compiles(getattr(functions, cls), "geopackage")(_compile_gpkg) def register_gpkg_mapping(mapping): """Register compilation mappings for the given functions. Args: mapping: Should have the following form:: { "function_name_1": "gpkg_function_name_1", "function_name_2": "gpkg_function_name_2", ... } """ for cls, fn in mapping.items(): _compiles_gpkg(cls, fn) register_gpkg_mapping(_SQLITE_FUNCTIONS) def create_spatial_ref_sys_view(bind): """Create the `spatial_ref_sys` view from the `gpkg_spatial_ref_sys` table. .. Note:: This is usually only needed to use the `ST_Transform` function on GeoPackage data because this function, when used with SpatiaLite, requires the `spatial_ref_sys` table. """ bind.execute( text( """CREATE VIEW spatial_ref_sys AS SELECT srs_id AS srid, organization AS auth_name, organization_coordsys_id AS auth_srid, definition AS srtext FROM gpkg_spatial_ref_sys;""" ) ) geoalchemy2-0.15.2/geoalchemy2/admin/dialects/mysql.py000066400000000000000000000153231464355170400226110ustar00rootroot00000000000000"""This module defines specific functions for MySQL dialect.""" from sqlalchemy import text from sqlalchemy.ext.compiler import compiles from sqlalchemy.sql.sqltypes import NullType from geoalchemy2 import functions from geoalchemy2.admin.dialects.common import _check_spatial_type from geoalchemy2.admin.dialects.common import _spatial_idx_name from geoalchemy2.admin.dialects.common import setup_create_drop from geoalchemy2.types import Geography from geoalchemy2.types import Geometry _POSSIBLE_TYPES = [ "geometry", "point", "linestring", "polygon", "multipoint", "multilinestring", "multipolygon", "geometrycollection", ] def reflect_geometry_column(inspector, table, column_info): """Reflect a column of type Geometry with Postgresql dialect.""" if not isinstance(column_info.get("type"), (Geometry, NullType)): return column_name = column_info.get("name") schema = table.schema or inspector.default_schema_name # Check geometry type, SRID and if the column is nullable geometry_type_query = """SELECT DATA_TYPE, SRS_ID, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{}' and COLUMN_NAME = '{}'""".format( table.name, column_name ) if schema is not None: geometry_type_query += """ and table_schema = '{}'""".format(schema) geometry_type, srid, nullable_str = inspector.bind.execute(text(geometry_type_query)).one() is_nullable = str(nullable_str).lower() == "yes" if geometry_type not in _POSSIBLE_TYPES: return # Check if the column has spatial index has_index_query = """SELECT DISTINCT INDEX_TYPE FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_NAME = '{}' and COLUMN_NAME = '{}'""".format( table.name, column_name ) if schema is not None: has_index_query += """ and TABLE_SCHEMA = '{}'""".format(schema) spatial_index_res = inspector.bind.execute(text(has_index_query)).scalar() spatial_index = str(spatial_index_res).lower() == "spatial" # Set attributes column_info["type"] = Geometry( geometry_type=geometry_type.upper(), srid=srid, spatial_index=spatial_index, nullable=is_nullable, _spatial_index_reflected=True, ) def before_create(table, bind, **kw): """Handle spatial indexes during the before_create event.""" dialect, gis_cols, regular_cols = setup_create_drop(table, bind) # Remove the spatial indexes from the table metadata because they should not be # created during the table.create() step since the associated columns do not exist # at this time. table.info["_after_create_indexes"] = [] current_indexes = set(table.indexes) for idx in current_indexes: for col in table.info["_saved_columns"]: if (_check_spatial_type(col.type, Geometry, dialect)) and col in idx.columns.values(): table.indexes.remove(idx) if idx.name != _spatial_idx_name(table.name, col.name) or not getattr( col.type, "spatial_index", False ): table.info["_after_create_indexes"].append(idx) table.columns = table.info.pop("_saved_columns") def after_create(table, bind, **kw): """Handle spatial indexes during the after_create event.""" # Restore original column list including managed Geometry columns dialect = bind.dialect # table.columns = table.info.pop("_saved_columns") for col in table.columns: # Add spatial indices for the Geometry and Geography columns if ( _check_spatial_type(col.type, (Geometry, Geography), dialect) and col.type.spatial_index is True ): # If the index does not exist, define it and create it if not [i for i in table.indexes if col in i.columns.values()]: sql = "ALTER TABLE {} ADD SPATIAL INDEX({});".format(table.name, col.name) q = text(sql) bind.execute(q) for idx in table.info.pop("_after_create_indexes"): table.indexes.add(idx) def before_drop(table, bind, **kw): return def after_drop(table, bind, **kw): return _MYSQL_FUNCTIONS = { "ST_AsEWKB": "ST_AsBinary", } def _compiles_mysql(cls, fn): def _compile_mysql(element, compiler, **kw): return "{}({})".format(fn, compiler.process(element.clauses, **kw)) compiles(getattr(functions, cls), "mysql")(_compile_mysql) compiles(getattr(functions, cls), "mariadb")(_compile_mysql) def register_mysql_mapping(mapping): """Register compilation mappings for the given functions. Args: mapping: Should have the following form:: { "function_name_1": "mysql_function_name_1", "function_name_2": "mysql_function_name_2", ... } """ for cls, fn in mapping.items(): _compiles_mysql(cls, fn) register_mysql_mapping(_MYSQL_FUNCTIONS) def _compile_GeomFromText_MySql(element, compiler, **kw): element.identifier = "ST_GeomFromText" compiled = compiler.process(element.clauses, **kw) srid = element.type.srid if srid > 0: return "{}({}, {})".format(element.identifier, compiled, srid) else: return "{}({})".format(element.identifier, compiled) def _compile_GeomFromWKB_MySql(element, compiler, **kw): element.identifier = "ST_GeomFromWKB" wkb_data = list(element.clauses)[0].value if isinstance(wkb_data, memoryview): list(element.clauses)[0].value = wkb_data.tobytes() compiled = compiler.process(element.clauses, **kw) srid = element.type.srid if srid > 0: return "{}({}, {})".format(element.identifier, compiled, srid) else: return "{}({})".format(element.identifier, compiled) @compiles(functions.ST_GeomFromText, "mysql") # type: ignore @compiles(functions.ST_GeomFromText, "mariadb") # type: ignore def _MySQL_ST_GeomFromText(element, compiler, **kw): return _compile_GeomFromText_MySql(element, compiler, **kw) @compiles(functions.ST_GeomFromEWKT, "mysql") # type: ignore @compiles(functions.ST_GeomFromEWKT, "mariadb") # type: ignore def _MySQL_ST_GeomFromEWKT(element, compiler, **kw): return _compile_GeomFromText_MySql(element, compiler, **kw) @compiles(functions.ST_GeomFromWKB, "mysql") # type: ignore @compiles(functions.ST_GeomFromWKB, "mariadb") # type: ignore def _MySQL_ST_GeomFromWKB(element, compiler, **kw): return _compile_GeomFromWKB_MySql(element, compiler, **kw) @compiles(functions.ST_GeomFromEWKB, "mysql") # type: ignore @compiles(functions.ST_GeomFromEWKB, "mariadb") # type: ignore def _MySQL_ST_GeomFromEWKB(element, compiler, **kw): return _compile_GeomFromWKB_MySql(element, compiler, **kw) geoalchemy2-0.15.2/geoalchemy2/admin/dialects/postgresql.py000066400000000000000000000140571464355170400236520ustar00rootroot00000000000000"""This module defines specific functions for Postgresql dialect.""" from sqlalchemy import Index from sqlalchemy import text from sqlalchemy.sql import func from sqlalchemy.sql import select from geoalchemy2.admin.dialects.common import _check_spatial_type from geoalchemy2.admin.dialects.common import _format_select_args from geoalchemy2.admin.dialects.common import _spatial_idx_name from geoalchemy2.admin.dialects.common import setup_create_drop from geoalchemy2.types import Geography from geoalchemy2.types import Geometry def check_management(column): """Check if the column should be managed.""" return getattr(column.type, "use_typmod", None) is False def create_spatial_index(bind, table, col): """Create spatial index on the given column.""" if col.type.use_N_D_index: postgresql_ops = {col.name: "gist_geometry_ops_nd"} else: postgresql_ops = {} idx = Index( _spatial_idx_name(table.name, col.name), col, postgresql_using="gist", postgresql_ops=postgresql_ops, _column_flag=True, ) idx.create(bind=bind) def reflect_geometry_column(inspector, table, column_info): """Reflect a column of type Geometry with Postgresql dialect.""" if not isinstance(column_info.get("type"), Geometry): return geo_type = column_info["type"] geometry_type = geo_type.geometry_type coord_dimension = geo_type.dimension if geometry_type.endswith("ZM"): coord_dimension = 4 elif geometry_type[-1] in ["Z", "M"]: coord_dimension = 3 # Query to check a given column has spatial index if table.schema is not None: schema_part = " AND nspname = '{}'".format(table.schema) else: schema_part = "" has_index_query = """SELECT (indexrelid IS NOT NULL) AS has_index FROM ( SELECT n.nspname, c.relname, c.oid AS relid, a.attname, a.attnum FROM pg_attribute a INNER JOIN pg_class c ON (a.attrelid=c.oid) INNER JOIN pg_type t ON (a.atttypid=t.oid) INNER JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE t.typname='geometry' AND c.relkind='r' ) g LEFT JOIN pg_index i ON (g.relid = i.indrelid AND g.attnum = ANY(i.indkey)) WHERE relname = '{}' AND attname = '{}'{}; """.format( table.name, column_info["name"], schema_part ) spatial_index = inspector.bind.execute(text(has_index_query)).scalar() # Set attributes column_info["type"].geometry_type = geometry_type column_info["type"].dimension = coord_dimension column_info["type"].spatial_index = bool(spatial_index) # Spatial indexes are automatically reflected with PostgreSQL dialect column_info["type"]._spatial_index_reflected = True def before_create(table, bind, **kw): """Handle spatial indexes during the before_create event.""" dialect, gis_cols, regular_cols = setup_create_drop(table, bind, check_management) # Remove the spatial indexes from the table metadata because they should not be # created during the table.create() step since the associated columns do not exist # at this time. table.info["_after_create_indexes"] = [] current_indexes = set(table.indexes) for idx in current_indexes: for col in table.info["_saved_columns"]: if ( _check_spatial_type(col.type, Geometry, dialect) and check_management(col) ) and col in idx.columns.values(): table.indexes.remove(idx) if idx.name != _spatial_idx_name(table.name, col.name) or not getattr( col.type, "spatial_index", False ): table.info["_after_create_indexes"].append(idx) def after_create(table, bind, **kw): """Handle spatial indexes during the after_create event.""" # Restore original column list including managed Geometry columns dialect = bind.dialect table.columns = table.info.pop("_saved_columns") for col in table.columns: # Add the managed Geometry columns with AddGeometryColumn() if _check_spatial_type(col.type, Geometry, dialect) and check_management(col): dimension = col.type.dimension args = [table.schema] if table.schema else [] args.extend([table.name, col.name, col.type.srid, col.type.geometry_type, dimension]) if col.type.use_typmod is not None: args.append(col.type.use_typmod) stmt = select(*_format_select_args(func.AddGeometryColumn(*args))) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) # Add spatial indices for the Geometry and Geography columns if ( _check_spatial_type(col.type, (Geometry, Geography), dialect) and col.type.spatial_index is True ): # If the index does not exist, define it and create it if not [i for i in table.indexes if col in i.columns.values()] and check_management( col ): create_spatial_index(bind, table, col) for idx in table.info.pop("_after_create_indexes"): table.indexes.add(idx) idx.create(bind=bind) def before_drop(table, bind, **kw): """Handle spatial indexes during the before_drop event.""" dialect, gis_cols, regular_cols = setup_create_drop(table, bind, check_management) # Drop the managed Geometry columns for col in gis_cols: args = [table.schema] if table.schema else [] args.extend([table.name, col.name]) stmt = select(*_format_select_args(func.DropGeometryColumn(*args))) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) def after_drop(table, bind, **kw): """Handle spatial indexes during the after_drop event.""" # Restore original column list including managed Geometry columns saved_cols = table.info.pop("_saved_columns", None) if saved_cols is not None: table.columns = saved_cols geoalchemy2-0.15.2/geoalchemy2/admin/dialects/sqlite.py000066400000000000000000000324351464355170400227500ustar00rootroot00000000000000"""This module defines specific functions for SQLite dialect.""" import os from typing import Optional from sqlalchemy import text from sqlalchemy.ext.compiler import compiles from sqlalchemy.sql import func from sqlalchemy.sql import select from geoalchemy2 import functions from geoalchemy2.admin.dialects.common import _check_spatial_type from geoalchemy2.admin.dialects.common import _format_select_args from geoalchemy2.admin.dialects.common import _spatial_idx_name from geoalchemy2.admin.dialects.common import setup_create_drop from geoalchemy2.types import Geography from geoalchemy2.types import Geometry from geoalchemy2.types import _DummyGeometry from geoalchemy2.utils import authorized_values_in_docstring def load_spatialite_driver(dbapi_conn, *args): """Load SpatiaLite extension in SQLite connection. .. Warning:: The path to the SpatiaLite module should be set in the `SPATIALITE_LIBRARY_PATH` environment variable. Args: dbapi_conn: The DBAPI connection. """ if "SPATIALITE_LIBRARY_PATH" not in os.environ: raise RuntimeError("The SPATIALITE_LIBRARY_PATH environment variable is not set.") dbapi_conn.enable_load_extension(True) dbapi_conn.load_extension(os.environ["SPATIALITE_LIBRARY_PATH"]) dbapi_conn.enable_load_extension(False) _JOURNAL_MODE_VALUES = ["DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"] @authorized_values_in_docstring(JOURNAL_MODE_VALUES=_JOURNAL_MODE_VALUES) def init_spatialite( dbapi_conn, *args, transaction: bool = False, init_mode: Optional[str] = None, journal_mode: Optional[str] = None, ): """Initialize internal SpatiaLite tables. Args: dbapi_conn: The DBAPI connection. init_mode: Can be `None` to load all EPSG SRIDs, `'WGS84'` to load only the ones related to WGS84 or `'EMPTY'` to not load any EPSG SRID. .. Note:: It is possible to load other EPSG SRIDs afterwards using `InsertEpsgSrid(srid)`. transaction: If set to `True` the whole operation will be handled as a single Transaction (faster). The default value is `False` (slower, but safer). journal_mode: Change the journal mode to the given value. This can make the table creation much faster. The possible values are the following: . See https://www.sqlite.org/pragma.html#pragma_journal_mode for more details. .. Warning:: Some values, like 'MEMORY' or 'OFF', can lead to corrupted databases if the process is interrupted during initialization. .. Note:: The original value is restored after the initialization. .. Note:: When using this function as a listener it is not possible to pass the `transaction`, `init_mode` or `journal_mode` arguments directly. To do this you can either create another function that calls `init_spatialite` (or :func:`geoalchemy2.admin.dialects.sqlite.load_spatialite` if you also want to load the SpatiaLite drivers) with an hard-coded `init_mode` or just use a lambda:: >>> sqlalchemy.event.listen( ... engine, ... "connect", ... lambda x, y: init_spatialite( ... x, ... y, ... transaction=True, ... init_mode="EMPTY", ... journal_mode="OFF", ... ) ... ) """ func_args = [] # Check the value of the 'transaction' parameter if not isinstance(transaction, (bool, int)): raise ValueError("The 'transaction' argument must be True or False.") else: func_args.append(str(transaction)) # Check the value of the 'init_mode' parameter init_mode_values = ["WGS84", "EMPTY"] if isinstance(init_mode, str): init_mode = init_mode.upper() if init_mode is not None: if init_mode not in init_mode_values: raise ValueError("The 'init_mode' argument must be one of {}.".format(init_mode_values)) func_args.append(f"'{init_mode}'") # Check the value of the 'journal_mode' parameter if isinstance(journal_mode, str): journal_mode = journal_mode.upper() if journal_mode is not None: if journal_mode not in _JOURNAL_MODE_VALUES: raise ValueError( "The 'journal_mode' argument must be one of {}.".format(_JOURNAL_MODE_VALUES) ) if dbapi_conn.execute("SELECT CheckSpatialMetaData();").fetchone()[0] < 1: if journal_mode is not None: current_journal_mode = dbapi_conn.execute("PRAGMA journal_mode").fetchone()[0] dbapi_conn.execute("PRAGMA journal_mode = {}".format(journal_mode)) dbapi_conn.execute("SELECT InitSpatialMetaData({});".format(", ".join(func_args))) if journal_mode is not None: dbapi_conn.execute("PRAGMA journal_mode = {}".format(current_journal_mode)) def load_spatialite(*args, **kwargs): """Load SpatiaLite extension in SQLite DB and initialize internal tables. See :func:`geoalchemy2.admin.dialects.sqlite.load_spatialite_driver` and :func:`geoalchemy2.admin.dialects.sqlite.init_spatialite` functions for details about arguments. """ load_spatialite_driver(*args) init_spatialite(*args, **kwargs) def _get_spatialite_attrs(bind, table_name, col_name): attrs = bind.execute( text( """SELECT * FROM "geometry_columns" WHERE LOWER(f_table_name) = LOWER(:table_name) AND LOWER(f_geometry_column) = LOWER(:column_name) """ ).bindparams(table_name=table_name, column_name=col_name) ).fetchone() if attrs is None: # If the column is not registered as a spatial column we ignore it return None return attrs[2:] def get_spatialite_version(bind): """Get the version of the currently loaded Spatialite extension.""" return bind.execute(text("SELECT spatialite_version();")).fetchone()[0] def _setup_dummy_type(table, gis_cols): """Setup dummy type for new Geometry columns so they can be updated later.""" for col in gis_cols: # Add dummy columns with GEOMETRY type col._actual_type = col.type col.type = _DummyGeometry() table.columns = table.info["_saved_columns"] def get_col_dim(col): """Get dimension of the column type.""" if col.type.dimension == 4: dimension = "XYZM" elif col.type.dimension == 2: dimension = "XY" else: if col.type.geometry_type.endswith("M"): dimension = "XYM" else: dimension = "XYZ" return dimension def create_spatial_index(bind, table, col): """Create spatial index on the given column.""" stmt = select(*_format_select_args(func.CreateSpatialIndex(table.name, col.name))) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) def disable_spatial_index(bind, table, col): """Disable spatial indexes if present.""" stmt = select(*_format_select_args(func.CheckSpatialIndex(table.name, col.name))) if bind.execute(stmt).fetchone()[0] is not None: stmt = select(*_format_select_args(func.DisableSpatialIndex(table.name, col.name))) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) bind.execute( text( "DROP TABLE IF EXISTS {};".format( _spatial_idx_name( table.name, col.name, ) ) ) ) def reflect_geometry_column(inspector, table, column_info): """Reflect a column of type Geometry with SQLite dialect.""" # Get geometry type, SRID and spatial index from the SpatiaLite metadata if not isinstance(column_info.get("type"), Geometry): return col_attributes = _get_spatialite_attrs(inspector.bind, table.name, column_info["name"]) if col_attributes is not None: geometry_type, coord_dimension, srid, spatial_index = col_attributes if isinstance(geometry_type, int): geometry_type_str = str(geometry_type) if geometry_type >= 1000: first_digit = geometry_type_str[0] has_z = first_digit in ["1", "3"] has_m = first_digit in ["2", "3"] else: has_z = has_m = False geometry_type = { "0": "GEOMETRY", "1": "POINT", "2": "LINESTRING", "3": "POLYGON", "4": "MULTIPOINT", "5": "MULTILINESTRING", "6": "MULTIPOLYGON", "7": "GEOMETRYCOLLECTION", }[geometry_type_str[-1]] if has_z: geometry_type += "Z" if has_m: geometry_type += "M" else: if "Z" in coord_dimension and "Z" not in geometry_type[-2:]: geometry_type += "Z" if "M" in coord_dimension and "M" not in geometry_type[-2:]: geometry_type += "M" coord_dimension = { "XY": 2, "XYZ": 3, "XYM": 3, "XYZM": 4, }.get(coord_dimension, coord_dimension) # Set attributes column_info["type"].geometry_type = geometry_type column_info["type"].dimension = coord_dimension column_info["type"].srid = srid column_info["type"].spatial_index = bool(spatial_index) # Spatial indexes are not automatically reflected with SQLite dialect column_info["type"]._spatial_index_reflected = False def before_create(table, bind, **kw): """Handle spatial indexes during the before_create event.""" dialect, gis_cols, regular_cols = setup_create_drop(table, bind) # Remove the spatial indexes from the table metadata because they should not be # created during the table.create() step since the associated columns do not exist # at this time. table.info["_after_create_indexes"] = [] current_indexes = set(table.indexes) for idx in current_indexes: for col in table.info["_saved_columns"]: if (_check_spatial_type(col.type, Geometry, dialect)) and col in idx.columns.values(): table.indexes.remove(idx) if idx.name != _spatial_idx_name(table.name, col.name) or not getattr( col.type, "spatial_index", False ): table.info["_after_create_indexes"].append(idx) _setup_dummy_type(table, gis_cols) def after_create(table, bind, **kw): """Handle spatial indexes during the after_create event.""" dialect = bind.dialect table.columns = table.info.pop("_saved_columns") for col in table.columns: # Add the managed Geometry columns with RecoverGeometryColumn() if _check_spatial_type(col.type, Geometry, dialect): col.type = col._actual_type del col._actual_type dimension = get_col_dim(col) args = [table.name, col.name, col.type.srid, col.type.geometry_type, dimension] stmt = select(*_format_select_args(func.RecoverGeometryColumn(*args))) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) for col in table.columns: # Add spatial indexes for the Geometry and Geography columns # TODO: Check that the Geography type makes sense here if ( _check_spatial_type(col.type, (Geometry, Geography), dialect) and col.type.spatial_index is True ): create_spatial_index(bind, table, col) for idx in table.info.pop("_after_create_indexes"): table.indexes.add(idx) idx.create(bind=bind) def before_drop(table, bind, **kw): """Handle spatial indexes during the before_drop event.""" dialect, gis_cols, regular_cols = setup_create_drop(table, bind) for col in gis_cols: # Disable spatial indexes if present disable_spatial_index(bind, table, col) args = [table.name, col.name] stmt = select(*_format_select_args(func.DiscardGeometryColumn(*args))) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) def after_drop(table, bind, **kw): """Handle spatial indexes during the after_drop event.""" table.columns = table.info.pop("_saved_columns") # Define compiled versions for functions in SpatiaLite whose names don't have # the ST_ prefix. _SQLITE_FUNCTIONS = { "ST_GeomFromEWKT": "GeomFromEWKT", "ST_GeomFromEWKB": "GeomFromEWKB", "ST_AsBinary": "AsBinary", "ST_AsEWKB": "AsEWKB", "ST_AsGeoJSON": "AsGeoJSON", } def _compiles_sqlite(cls, fn): def _compile_sqlite(element, compiler, **kw): return "{}({})".format(fn, compiler.process(element.clauses, **kw)) compiles(getattr(functions, cls), "sqlite")(_compile_sqlite) def register_sqlite_mapping(mapping): """Register compilation mappings for the given functions. Args: mapping: Should have the following form:: { "function_name_1": "sqlite_function_name_1", "function_name_2": "sqlite_function_name_2", ... } """ for cls, fn in mapping.items(): _compiles_sqlite(cls, fn) register_sqlite_mapping(_SQLITE_FUNCTIONS) geoalchemy2-0.15.2/geoalchemy2/alembic_helpers.py000066400000000000000000000663551464355170400217150ustar00rootroot00000000000000"""Some helpers to use with Alembic migration tool.""" from alembic.autogenerate import renderers from alembic.autogenerate import rewriter from alembic.autogenerate.render import _add_column from alembic.autogenerate.render import _add_index from alembic.autogenerate.render import _add_table from alembic.autogenerate.render import _drop_column from alembic.autogenerate.render import _drop_index from alembic.autogenerate.render import _drop_table from alembic.ddl.base import RenameTable from alembic.ddl.base import format_table_name from alembic.ddl.base import visit_rename_table from alembic.ddl.sqlite import SQLiteImpl from alembic.operations import BatchOperations from alembic.operations import Operations from alembic.operations import ops from sqlalchemy import Column from sqlalchemy import text from sqlalchemy.dialects.mysql.base import MySQLDialect from sqlalchemy.dialects.sqlite.base import SQLiteDialect from sqlalchemy.ext.compiler import compiles from sqlalchemy.schema import DropTable from sqlalchemy.sql import func from sqlalchemy.types import TypeDecorator from geoalchemy2 import Geography from geoalchemy2 import Geometry from geoalchemy2 import Raster from geoalchemy2.admin.dialects.common import _check_spatial_type from geoalchemy2.admin.dialects.common import _get_gis_cols from geoalchemy2.admin.dialects.common import _spatial_idx_name writer = rewriter.Rewriter() """Rewriter object for Alembic.""" _SPATIAL_TABLES = set() class GeoPackageImpl(SQLiteImpl): """Class to copy the Alembic implementation from SQLite to GeoPackage.""" __dialect__ = "geopackage" def _monkey_patch_get_indexes_for_sqlite(): """Monkey patch SQLAlchemy to fix spatial index reflection.""" normal_behavior = SQLiteDialect.get_indexes def spatial_behavior(self, connection, table_name, schema=None, **kw): indexes = self._get_indexes_normal_behavior(connection, table_name, schema=None, **kw) is_gpkg = connection.dialect.name == "geopackage" try: # Check that SpatiaLite was loaded into the DB is_spatial_db = connection.exec_driver_sql( """PRAGMA main.table_info({})""".format( "gpkg_geometry_columns" if is_gpkg else "geometry_columns" ) ).fetchall() if not is_spatial_db: return indexes except AttributeError: return indexes # Get spatial indexes if is_gpkg: spatial_index_query = text( """SELECT A.table_name, A.column_name, IFNULL(B.has_index, 0) AS has_index FROM "gpkg_geometry_columns" AS A LEFT JOIN ( SELECT table_name, column_name, COUNT(*) AS has_index FROM gpkg_extensions WHERE LOWER(table_name) = LOWER('{table_name}') AND extension_name = 'gpkg_rtree_index' ) AS B ON LOWER(A.table_name) = LOWER(B.table_name) WHERE LOWER(A.table_name) = LOWER('{table_name}'); """.format( table_name=table_name ) ) else: spatial_index_query = text( """SELECT * FROM geometry_columns WHERE f_table_name = '{}' ORDER BY f_table_name, f_geometry_column;""".format( table_name ) ) spatial_indexes = connection.execute(spatial_index_query).fetchall() if spatial_indexes: reflected_names = set([i["name"] for i in indexes]) for idx in spatial_indexes: idx_col = idx[1] idx_name = _spatial_idx_name(table_name, idx_col) if not bool(idx[-1]) or idx_name in reflected_names: continue indexes.append( { "name": idx_name, "column_names": [idx_col], "unique": 0, "dialect_options": {"_column_flag": True}, } ) reflected_names.add(idx_name) return indexes spatial_behavior.__doc__ = normal_behavior.__doc__ SQLiteDialect.get_indexes = spatial_behavior SQLiteDialect._get_indexes_normal_behavior = normal_behavior _monkey_patch_get_indexes_for_sqlite() def _monkey_patch_get_indexes_for_mysql(): """Monkey patch SQLAlchemy to fix spatial index reflection.""" normal_behavior = MySQLDialect.get_indexes def spatial_behavior(self, connection, table_name, schema=None, **kw): indexes = self._get_indexes_normal_behavior(connection, table_name, schema=None, **kw) # Get spatial indexes has_index_query = """SELECT DISTINCT COLUMN_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_NAME = '{}' AND INDEX_TYPE = 'SPATIAL'""".format( table_name ) if schema is not None: has_index_query += """ AND TABLE_SCHEMA = '{}'""".format(schema) spatial_indexes = connection.execute(text(has_index_query)).fetchall() if spatial_indexes: reflected_names = set([i["name"] for i in indexes]) for idx in spatial_indexes: idx_col = idx[0] idx_name = _spatial_idx_name(table_name, idx_col) if idx_name in reflected_names: continue indexes.append( { "name": idx_name, "column_names": [idx_col], "unique": 0, "dialect_options": {"_column_flag": True}, } ) reflected_names.add(idx_name) return indexes spatial_behavior.__doc__ = normal_behavior.__doc__ MySQLDialect.get_indexes = spatial_behavior MySQLDialect._get_indexes_normal_behavior = normal_behavior _monkey_patch_get_indexes_for_mysql() def render_item(obj_type, obj, autogen_context): """Add proper imports for spatial types.""" if obj_type == "type" and isinstance(obj, (Geometry, Geography, Raster)): import_name = obj.__class__.__name__ autogen_context.imports.add(f"from geoalchemy2 import {import_name}") return "%r" % obj # Default rendering for other objects return False def include_object(obj, name, obj_type, reflected, compare_to): """Do not include internal tables of spatial extensions. .. warning:: This function only checks the table names, so it might exclude tables that should not be. In such case, you should create your own function to handle your specific table names. """ if obj_type == "table" and ( name.startswith("geometry_columns") or name.startswith("spatial_ref_sys") or name.startswith("spatialite_history") or name.startswith("sqlite_sequence") or name.startswith("views_geometry_columns") or name.startswith("virts_geometry_columns") or name.startswith("idx_") or name.startswith("gpkg_") or name.startswith("vgpkg_") ): return False return True @Operations.register_operation("add_geospatial_column") @BatchOperations.register_operation("add_geospatial_column", "batch_add_geospatial_column") class AddGeospatialColumnOp(ops.AddColumnOp): """Add a Geospatial Column in an Alembic migration context. This method originates from: https://alembic.sqlalchemy.org/en/latest/api/operations.html#operation-plugins """ @classmethod def add_geospatial_column(cls, operations, table_name, column, schema=None): """Handle the different situations arising from adding geospatial column to a DB.""" op = cls(table_name, column, schema=schema) return operations.invoke(op) def reverse(self): """Used to autogenerate the downgrade function.""" return DropGeospatialColumnOp.from_column_and_tablename( self.schema, self.table_name, self.column.name ) @classmethod def batch_add_geospatial_column( cls, operations, column, insert_before=None, insert_after=None, ): """Issue an "add column" instruction using the current batch migration context.""" kw = {} if insert_before: kw["insert_before"] = insert_before if insert_after: kw["insert_after"] = insert_after op = cls( operations.impl.table_name, column, schema=operations.impl.schema, **kw, ) return operations.invoke(op) @Operations.register_operation("drop_geospatial_column") @BatchOperations.register_operation("drop_geospatial_column", "batch_drop_geospatial_column") class DropGeospatialColumnOp(ops.DropColumnOp): """Drop a Geospatial Column in an Alembic migration context.""" @classmethod def drop_geospatial_column(cls, operations, table_name, column_name, schema=None, **kw): """Handle the different situations arising from dropping geospatial column from a DB.""" op = cls(table_name, column_name, schema=schema, **kw) return operations.invoke(op) def reverse(self): """Used to autogenerate the downgrade function.""" return AddGeospatialColumnOp.from_column_and_tablename( self.schema, self.table_name, self.column ) @classmethod def batch_drop_geospatial_column(cls, operations, column_name, **kw): """Issue a "drop column" instruction using the current batch migration context.""" op = cls( operations.impl.table_name, column_name, schema=operations.impl.schema, **kw, ) return operations.invoke(op) @Operations.implementation_for(AddGeospatialColumnOp) def add_geospatial_column(operations, operation): """Handle the actual column addition according to the dialect backend. Args: operations: Operations object from alembic base, defining high level migration operations. operation: AddGeospatialColumnOp call, with attributes for table_name, column_name, column_type, and optional keywords. """ table_name = operation.table_name column_name = operation.column.name dialect = operations.get_bind().dialect if dialect.name == "sqlite": if isinstance(operation.column, TypeDecorator): # Will be either geoalchemy2.types.Geography or geoalchemy2.types.Geometry, if using a # custom type geospatial_core_type = operation.column.type.load_dialect_impl(dialect) else: geospatial_core_type = operation.column.type operations.execute( func.AddGeometryColumn( table_name, column_name, geospatial_core_type.srid, geospatial_core_type.geometry_type, geospatial_core_type.dimension, not geospatial_core_type.nullable, ) ) elif "postgresql" in dialect.name: operations.impl.add_column( table_name, operation.column, schema=operation.schema, ) @Operations.implementation_for(DropGeospatialColumnOp) def drop_geospatial_column(operations, operation): """Handle the actual column removal according to the dialect backend. Args: operations: Operations object from alembic base, defining high level migration operations. operation: AddGeospatialColumnOp call, with attributes for table_name, column_name, column_type, and optional keywords. """ table_name = operation.table_name column = operation.to_column(operations.migration_context) dialect = operations.get_bind().dialect if dialect.name == "sqlite": _SPATIAL_TABLES.add(table_name) operations.impl.drop_column(table_name, column, schema=operation.schema, **operation.kw) @compiles(RenameTable, "sqlite") def visit_rename_geospatial_table(element, compiler, **kw): """Specific compilation rule to rename spatial tables with SQLite dialect.""" table_is_spatial = element.table_name in _SPATIAL_TABLES new_table_is_spatial = element.new_table_name in _SPATIAL_TABLES if table_is_spatial or new_table_is_spatial: # Here we suppose there is only one DB attached to the current engine, so the prefix # is set to NULL return "SELECT RenameTable(NULL, '%s', '%s')" % ( format_table_name(compiler, element.table_name, element.schema), format_table_name(compiler, element.new_table_name, element.schema), ) else: return visit_rename_table(element, compiler, **kw) @compiles(DropTable, "sqlite") def visit_drop_geospatial_table(element, compiler, **kw): """Specific compilation rule to drop spatial tables with SQLite dialect.""" table_is_spatial = element.element.name in _SPATIAL_TABLES if table_is_spatial: # Here we suppose there is only one DB attached to the current engine return "SELECT DropTable(NULL, '%s')" % ( format_table_name(compiler, element.element.name, None), ) else: return compiler.visit_drop_table(element, **kw) @renderers.dispatch_for(AddGeospatialColumnOp) def render_add_geo_column(autogen_context, op): """Render the add_geospatial_column operation in migration script.""" col_render = _add_column(autogen_context, op) return col_render.replace(".add_column(", ".add_geospatial_column(") @renderers.dispatch_for(DropGeospatialColumnOp) def render_drop_geo_column(autogen_context, op): """Render the drop_geospatial_column operation in migration script.""" col_render = _drop_column(autogen_context, op) return col_render.replace(".drop_column(", ".drop_geospatial_column(") @writer.rewrites(ops.AddColumnOp) def add_geo_column(context, revision, op): """Replace the default AddColumnOp by a geospatial-specific one.""" col_type = op.column.type if isinstance(col_type, TypeDecorator): dialect = context.bind.dialect col_type = col_type.load_dialect_impl(dialect) if isinstance(col_type, (Geometry, Geography, Raster)): op.column.type.spatial_index = False op.column.type._spatial_index_reflected = None new_op = AddGeospatialColumnOp(op.table_name, op.column, schema=op.schema) else: new_op = op return new_op @writer.rewrites(ops.DropColumnOp) def drop_geo_column(context, revision, op): """Replace the default DropColumnOp by a geospatial-specific one.""" col_type = op.to_column().type if isinstance(col_type, TypeDecorator): dialect = context.bind.dialect col_type = col_type.load_dialect_impl(dialect) if isinstance(col_type, (Geometry, Geography, Raster)): new_op = DropGeospatialColumnOp(op.table_name, op.column_name, schema=op.schema) else: new_op = op return new_op @Operations.register_operation("create_geospatial_table") class CreateGeospatialTableOp(ops.CreateTableOp): """Create a Geospatial Table in an Alembic migration context. This method originates from: https://alembic.sqlalchemy.org/en/latest/api/operations.html#operation-plugins """ @classmethod def create_geospatial_table(cls, operations, table_name, *columns, **kw): """Handle the different situations arising from creating geospatial table to a DB.""" op = cls(table_name, columns, **kw) return operations.invoke(op) def reverse(self): """Used to autogenerate the downgrade function.""" return DropGeospatialColumnOp.from_table( self.to_table(), _namespace_metadata=self._namespace_metadata, ) @classmethod def from_table(cls, table, _namespace_metadata=None): obj = super().from_table(table, _namespace_metadata) return obj def to_table(self, migration_context=None): table = super().to_table(migration_context) # Set spatial_index attribute to False so the indexes are created explicitly for col in table.columns: try: if col.type.spatial_index: col.type.spatial_index = False except AttributeError: pass return table @Operations.register_operation("drop_geospatial_table") class DropGeospatialTableOp(ops.DropTableOp): @classmethod def drop_geospatial_table(cls, operations, table_name, schema=None, **kw): """Handle the different situations arising from dropping geospatial table from a DB.""" op = cls(table_name, schema=schema, table_kw=kw) return operations.invoke(op) def reverse(self): """Used to autogenerate the downgrade function.""" return CreateGeospatialTableOp.from_table( self.to_table(), _namespace_metadata=self._namespace_metadata, ) @classmethod def from_table(cls, table, _namespace_metadata=None): obj = super().from_table(table, _namespace_metadata) return obj def to_table(self, migration_context=None): table = super().to_table(migration_context) return table @Operations.implementation_for(CreateGeospatialTableOp) def create_geospatial_table(operations, operation): """Handle the actual table creation according to the dialect backend. Args: operations: Operations object from alembic base, defining high level migration operations. operation: CreateGeospatialTableOp call, with attributes for table_name, column_name, column_type, and optional keywords. """ table_name = operation.table_name bind = operations.get_bind() # For now the default events defined in geoalchemy2 are enough to handle table creation operations.create_table(table_name, *operation.columns, schema=operation.schema, **operation.kw) if bind.dialect.name == "sqlite": _SPATIAL_TABLES.add(table_name) @Operations.implementation_for(DropGeospatialTableOp) def drop_geospatial_table(operations, operation): """Handle the actual table removal according to the dialect backend. Args: operations: Operations object from alembic base, defining high level migration operations. operation: DropGeospatialTableOp call, with attributes for table_name, column_name, column_type, and optional keywords. """ table_name = operation.table_name bind = operations.get_bind() dialect = bind.dialect if dialect.name == "sqlite": _SPATIAL_TABLES.add(table_name) operations.drop_table(table_name, schema=operation.schema, **operation.table_kw) @renderers.dispatch_for(CreateGeospatialTableOp) def render_create_geo_table(autogen_context, op): """Render the create_geospatial_table operation in migration script.""" table_render = _add_table(autogen_context, op) return table_render.replace(".create_table(", ".create_geospatial_table(") @renderers.dispatch_for(DropGeospatialTableOp) def render_drop_geo_table(autogen_context, op): """Render the drop_geospatial_table operation in migration script.""" table_render = _drop_table(autogen_context, op) return table_render.replace(".drop_table(", ".drop_geospatial_table(") @writer.rewrites(ops.CreateTableOp) def create_geo_table(context, revision, op): """Replace the default CreateTableOp by a geospatial-specific one.""" dialect = context.bind.dialect gis_cols = _get_gis_cols(op, (Geometry, Geography, Raster), dialect) if gis_cols: new_op = CreateGeospatialTableOp( op.table_name, op.columns, schema=op.schema, _namespace_metadata=op._namespace_metadata, _constraints_included=op._constraints_included, ) else: new_op = op return new_op @writer.rewrites(ops.DropTableOp) def drop_geo_table(context, revision, op): """Replace the default DropTableOp by a geospatial-specific one.""" dialect = context.bind.dialect table = op.to_table() gis_cols = _get_gis_cols(table, (Geometry, Geography, Raster), dialect) if gis_cols: new_op = DropGeospatialTableOp(op.table_name, schema=op.schema) else: new_op = op return new_op @Operations.register_operation("create_geospatial_index") @BatchOperations.register_operation("create_geospatial_index", "batch_create_geospatial_index") class CreateGeospatialIndexOp(ops.CreateIndexOp): @classmethod def create_geospatial_index( cls, operations, index_name, table_name, columns, schema=None, unique=False, **kw, ): """Handle the different situations arising from creating geospatial index into a DB.""" op = cls(index_name, table_name, columns, schema=schema, unique=unique, **kw) return operations.invoke(op) def reverse(self): """Used to autogenerate the downgrade function.""" return DropGeospatialIndexOp( self.index_name, self.table_name, column_name=self.columns[0].name, schema=self.schema, ) @classmethod def batch_create_geospatial_index( cls, operations, index_name, columns, **kw, ): """Issue a "create index" instruction using the current batch migration context.""" op = cls( index_name, operations.impl.table_name, columns, schema=operations.impl.schema, **kw, ) return operations.invoke(op) @Operations.register_operation("drop_geospatial_index") @BatchOperations.register_operation("drop_geospatial_index", "batch_drop_geospatial_index") class DropGeospatialIndexOp(ops.DropIndexOp): def __init__(self, *args, column_name, **kwargs): super().__init__(*args, **kwargs) self.column_name = column_name @classmethod def drop_geospatial_index( cls, operations, index_name, table_name, column_name, schema=None, unique=False, **kw, ): """Handle the different situations arising from dropping geospatial index from a DB.""" op = cls( index_name, table_name=table_name, column_name=column_name, schema=schema, unique=unique, **kw, ) return operations.invoke(op) def reverse(self): """Used to autogenerate the downgrade function.""" return CreateGeospatialIndexOp( self.index_name, self.table_name, column_name=self.column_name, schema=self.schema, _reverse=self, **self.kw, ) @classmethod def from_index(cls, index): assert index.table is not None assert len(index.columns) == 1, "A spatial index must be set on one column only" return cls( index.name, index.table.name, column_name=index.columns[0].name, schema=index.table.schema, _reverse=CreateGeospatialIndexOp.from_index(index), **index.kwargs, ) @classmethod def batch_drop_geospatial_index(cls, operations, index_name, **kw): """Issue a "drop index" instruction using the current batch migration context.""" op = cls( index_name, table_name=operations.impl.table_name, schema=operations.impl.schema, **kw, ) return operations.invoke(op) @Operations.implementation_for(CreateGeospatialIndexOp) def create_geospatial_index(operations, operation): """Handle the actual index creation according to the dialect backend. Args: operations: Operations object from alembic base, defining high level migration operations. operation: CreateGeospatialIndexOp call, with attributes for table_name, column_name, column_type, and optional keywords. """ bind = operations.get_bind() dialect = bind.dialect if dialect.name == "sqlite": assert len(operation.columns) == 1, "A spatial index must be set on one column only" operations.execute(func.CreateSpatialIndex(operation.table_name, operation.columns[0])) else: idx = operation.to_index(operations.migration_context) operations.impl.create_index(idx) @Operations.implementation_for(DropGeospatialIndexOp) def drop_geospatial_index(operations, operation): """Handle the actual index drop according to the dialect backend. Args: operations: Operations object from alembic base, defining high level migration operations. operation: DropGeospatialIndexOp call, with attributes for table_name, column_name, column_type, and optional keywords. """ bind = operations.get_bind() dialect = bind.dialect if dialect.name == "sqlite": operations.execute(func.DisableSpatialIndex(operation.table_name, operation.column_name)) else: operations.impl.drop_index(operation.to_index(operations.migration_context)) @renderers.dispatch_for(CreateGeospatialIndexOp) def render_create_geo_index(autogen_context, op): """Render the create_geospatial_index operation in migration script.""" idx_render = _add_index(autogen_context, op) return idx_render.replace(".create_index(", ".create_geospatial_index(") @renderers.dispatch_for(DropGeospatialIndexOp) def render_drop_geo_index(autogen_context, op): """Render the drop_geospatial_index operation in migration script.""" idx_render = _drop_index(autogen_context, op) # Replace function name text = idx_render.replace(".drop_index(", ".drop_geospatial_index(") # Add column name as keyword argument text = text[:-1] + ", column_name='%s')" % (op.column_name,) return text @writer.rewrites(ops.CreateIndexOp) def create_geo_index(context, revision, op): """Replace the default CreateIndexOp by a geospatial-specific one.""" dialect = context.bind.dialect if len(op.columns) == 1: col = op.columns[0] if isinstance(col, Column) and _check_spatial_type( col.type, (Geometry, Geography, Raster), dialect ): # Fix index properties op.kw["postgresql_using"] = op.kw.get("postgresql_using", "gist") if col.type.use_N_D_index: postgresql_ops = {col.name: "gist_geometry_ops_nd"} else: postgresql_ops = {} op.kw["postgresql_ops"] = op.kw.get("postgresql_ops", postgresql_ops) return CreateGeospatialIndexOp( op.index_name, op.table_name, op.columns, schema=op.schema, unique=op.unique, **op.kw, ) return op @writer.rewrites(ops.DropIndexOp) def drop_geo_index(context, revision, op): """Replace the default DropIndexOp by a geospatial-specific one.""" dialect = context.bind.dialect idx = op.to_index() if len(idx.columns) == 1: col = idx.columns[0] if isinstance(col, Column) and _check_spatial_type( col.type, (Geometry, Geography, Raster), dialect ): return DropGeospatialIndexOp( op.index_name, table_name=op.table_name, column_name=col.name, schema=op.schema, **op.kw, ) return op geoalchemy2-0.15.2/geoalchemy2/comparator.py000066400000000000000000000176441464355170400207430ustar00rootroot00000000000000"""This module defines a ``Comparator`` class for use with geometry and geography objects. This is where spatial operators, like ``&&``, ``&<``, are defined. Spatial operators very often apply to the bounding boxes of geometries. For example, ``geom1 && geom2`` indicates if geom1's bounding box intersects geom2's. Examples -------- Select the objects whose bounding boxes are to the left of the bounding box of ``POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))``:: select([table]).where(table.c.geom.to_left( 'POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))')) The ``<<`` and ``>>`` operators are a bit specific, because they have corresponding Python operator (``__lshift__`` and ``__rshift__``). The above ``SELECT`` expression can thus be rewritten like this:: select([table]).where( table.c.geom << 'POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))') Operators can also be used when using the ORM. For example:: Session.query(Cls).filter( Cls.geom << 'POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))') Now some other examples with the ``<#>`` operator. Select the ten objects that are the closest to ``POINT(0 0)`` (typical closed neighbors problem):: select([table]).order_by(table.c.geom.distance_box('POINT(0 0)')).limit(10) Using the ORM:: Session.query(Cls).order_by(Cls.geom.distance_box('POINT(0 0)')).limit(10) """ from typing import Union from sqlalchemy import types as sqltypes from sqlalchemy.dialects.postgresql import DOUBLE_PRECISION from sqlalchemy.sql.elements import ColumnElement from sqlalchemy.sql.functions import _FunctionGenerator from sqlalchemy.sql.operators import custom_op from sqlalchemy.types import UserDefinedType from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement INTERSECTS: custom_op = custom_op("&&") INTERSECTS_ND: custom_op = custom_op("&&&") OVERLAPS_OR_TO_LEFT: custom_op = custom_op("&<") OVERLAPS_OR_TO_RIGHT: custom_op = custom_op("&>") OVERLAPS_OR_BELOW: custom_op = custom_op("&<|") TO_LEFT: custom_op = custom_op("<<") BELOW: custom_op = custom_op("<<|") TO_RIGHT: custom_op = custom_op(">>") CONTAINED: custom_op = custom_op("@") OVERLAPS_OR_ABOVE: custom_op = custom_op("|&>") ABOVE: custom_op = custom_op("|>>") CONTAINS: custom_op = custom_op("~") SAME: custom_op = custom_op("~=") DISTANCE_CENTROID: custom_op = custom_op("<->") DISTANCE_BOX: custom_op = custom_op("<#>") _COMPARATOR_INPUT_TYPE = Union[str, WKBElement, WKTElement] class BaseComparator(UserDefinedType.Comparator): """A custom comparator base class. It adds the ability to call spatial functions on columns that use this kind of comparator. It also defines functions that map to operators supported by ``Geometry``, ``Geography`` and ``Raster`` columns. This comparator is used by the :class:`geoalchemy2.types.Raster`. """ key = None def __getattr__(self, name): # Function names that don't start with "ST_" are rejected. # This is not to mess up with SQLAlchemy's use of # hasattr/getattr on Column objects. if not name.lower().startswith("st_"): raise AttributeError # We create our own _FunctionGenerator here, and use it in place of # SQLAlchemy's "func" object. This is to be able to "bind" the # function to the SQL expression. See also GenericFunction. func_ = _FunctionGenerator(expr=self.expr) return getattr(func_, name) def intersects(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``&&`` operator. A's BBOX intersects B's.""" return self.operate(INTERSECTS, other, result_type=sqltypes.Boolean) def overlaps_or_to_left(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``&<`` operator. A's BBOX overlaps or is to the left of B's.""" return self.operate(OVERLAPS_OR_TO_LEFT, other, result_type=sqltypes.Boolean) def overlaps_or_to_right(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``&>`` operator. A's BBOX overlaps or is to the right of B's.""" return self.operate(OVERLAPS_OR_TO_RIGHT, other, result_type=sqltypes.Boolean) class Comparator(BaseComparator): """A custom comparator class. Used in :class:`geoalchemy2.types.Geometry` and :class:`geoalchemy2.types.Geography`. This is where spatial operators like ``<<`` and ``<->`` are defined. """ def overlaps_or_below(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``&<|`` operator. A's BBOX overlaps or is below B's. """ return self.operate(OVERLAPS_OR_BELOW, other, result_type=sqltypes.Boolean) def to_left(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``<<`` operator. A's BBOX is strictly to the left of B's. """ return self.operate(TO_LEFT, other, result_type=sqltypes.Boolean) def __lshift__(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``<<`` operator. A's BBOX is strictly to the left of B's. Same as ``to_left``, so:: table.c.geom << 'POINT(1 2)' is the same as:: table.c.geom.to_left('POINT(1 2)') """ return self.to_left(other) def below(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``<<|`` operator. A's BBOX is strictly below B's. """ return self.operate(BELOW, other, result_type=sqltypes.Boolean) def to_right(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``>>`` operator. A's BBOX is strictly to the right of B's. """ return self.operate(TO_RIGHT, other, result_type=sqltypes.Boolean) def __rshift__(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``>>`` operator. A's BBOX is strictly to the left of B's. Same as `to_`right``, so:: table.c.geom >> 'POINT(1 2)' is the same as:: table.c.geom.to_right('POINT(1 2)') """ return self.to_right(other) def contained(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``@`` operator. A's BBOX is contained by B's. """ return self.operate(CONTAINED, other, result_type=sqltypes.Boolean) def overlaps_or_above(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``|&>`` operator. A's BBOX overlaps or is above B's. """ return self.operate(OVERLAPS_OR_ABOVE, other, result_type=sqltypes.Boolean) def above(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``|>>`` operator. A's BBOX is strictly above B's. """ return self.operate(ABOVE, other, result_type=sqltypes.Boolean) def contains(self, other: _COMPARATOR_INPUT_TYPE, **kw) -> ColumnElement: """The ``~`` operator. A's BBOX contains B's. """ return self.operate(CONTAINS, other, result_type=sqltypes.Boolean) def same(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``~=`` operator. A's BBOX is the same as B's. """ return self.operate(SAME, other, result_type=sqltypes.Boolean) def distance_centroid(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``<->`` operator. The distance between two points. """ return self.operate(DISTANCE_CENTROID, other, result_type=DOUBLE_PRECISION) def distance_box(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``<#>`` operator. The distance between bounding box of two geometries. """ return self.operate(DISTANCE_BOX, other, result_type=DOUBLE_PRECISION) def intersects_nd(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement: """The ``&&&`` operator. This operator returns TRUE if the n-D bounding box of geometry A intersects the n-D bounding box of geometry B. """ return self.operate(INTERSECTS_ND, other, result_type=sqltypes.Boolean) geoalchemy2-0.15.2/geoalchemy2/elements.py000066400000000000000000000313121464355170400203740ustar00rootroot00000000000000from __future__ import annotations import binascii import re import struct from typing import Any from typing import Dict from typing import List from typing import Optional from typing import Set from typing import Union from sqlalchemy.ext.compiler import compiles from sqlalchemy.sql import functions from sqlalchemy.sql.functions import FunctionElement from sqlalchemy.types import to_instance from geoalchemy2.exc import ArgumentError BinasciiError = binascii.Error function_registry: Set[str] = set() class _SpatialElement: """The base class for public spatial elements. Args: data: The first argument passed to the constructor is the data wrapped by the ``_SpatialElement`` object being constructed. srid: An integer representing the spatial reference system. E.g. ``4326``. Default value is ``-1``, which means no/unknown reference system. extended: A boolean indicating whether the extended format (EWKT or EWKB) is used. Default is ``None``. """ def __init__(self, data, srid: int = -1, extended: Optional[bool] = None) -> None: self.srid = srid self.data = data self.extended = extended def __str__(self) -> str: return self.desc def __repr__(self) -> str: return "<%s at 0x%x; %s>" % ( self.__class__.__name__, id(self), self, ) # pragma: no cover def __eq__(self, other) -> bool: try: return ( self.extended == other.extended and self.srid == other.srid and self.desc == other.desc ) except AttributeError: return False def __ne__(self, other) -> bool: return not self.__eq__(other) def __hash__(self): return hash((self.desc, self.srid, self.extended)) def __getattr__(self, name): # # This is how things like lake.geom.ST_Buffer(2) creates # SQL expressions of this form: # # ST_Buffer(ST_GeomFromWKB(:ST_GeomFromWKB_1), :param_1) # # Raise an AttributeError when the attribute name doesn't start # with st_. This is to be nice with other libraries that use # some ducktyping (e.g. hasattr(element, "copy")) to determine # the type of the element. if name.lower() not in function_registry: raise AttributeError # We create our own _FunctionGenerator here, and use it in place of # SQLAlchemy's "func" object. This is to be able to "bind" the # function to the SQL expression. See also GenericFunction above. func_ = functions._FunctionGenerator(expr=self) return getattr(func_, name) def __getstate__(self) -> Dict[str, Any]: state = { "srid": self.srid, "data": str(self), "extended": self.extended, } return state def __setstate__(self, state: Dict[str, Any]) -> None: self.srid = state["srid"] self.extended = state["extended"] self.data = self._data_from_desc(state["data"]) @staticmethod def _data_from_desc(desc): raise NotImplementedError() # pragma: no cover class WKTElement(_SpatialElement): """Instances of this class wrap a WKT or EWKT value. Usage examples:: wkt_element_1 = WKTElement('POINT(5 45)') wkt_element_2 = WKTElement('POINT(5 45)', srid=4326) wkt_element_3 = WKTElement('SRID=4326;POINT(5 45)', extended=True) """ _REMOVE_SRID = re.compile("(SRID=([0-9]+); ?)?(.*)") SPLIT_WKT_PATTERN = re.compile(r"((SRID=\d+) *; *)?([\w ]+) *(\([-\d\. ,\(\)]+\))") geom_from: str = "ST_GeomFromText" geom_from_extended_version: str = "ST_GeomFromEWKT" def __init__(self, data: str, srid: int = -1, extended: Optional[bool] = None) -> None: if extended is None: extended = data.startswith("SRID=") if extended and srid == -1: # read srid from EWKT data_s = data.split(";") if len(data_s) != 2: raise ArgumentError("invalid EWKT string {}".format(data)) header = data_s[0] try: srid = int(header[5:]) except ValueError: raise ArgumentError("invalid EWKT string {}".format(data)) _SpatialElement.__init__(self, data, srid, extended) @property def desc(self) -> str: """This element's description string.""" return self.data @staticmethod def _data_from_desc(desc): return desc def as_wkt(self) -> WKTElement: if self.extended: srid_match = self._REMOVE_SRID.match(self.data) assert srid_match is not None return WKTElement(srid_match.group(3), self.srid, extended=False) return WKTElement(self.data, self.srid, self.extended) def as_ewkt(self) -> WKTElement: if not self.extended and self.srid != -1: data = f"SRID={self.srid};" + self.data return WKTElement(data, extended=True) return WKTElement(self.data, self.srid, self.extended) class WKBElement(_SpatialElement): """Instances of this class wrap a WKB or EWKB value. Geometry values read from the database are converted to instances of this type. In most cases you won't need to create ``WKBElement`` instances yourself. If ``extended`` is ``True`` and ``srid`` is ``-1`` at construction time then the SRID will be read from the EWKB data. Note: you can create ``WKBElement`` objects from Shapely geometries using the :func:`geoalchemy2.shape.from_shape` function. """ geom_from: str = "ST_GeomFromWKB" geom_from_extended_version: str = "ST_GeomFromEWKB" def __init__( self, data: Union[str, bytes, memoryview], srid: int = -1, extended: Optional[bool] = None ) -> None: if srid == -1 or extended is None or extended: # read srid from the EWKB # # WKB struct { # byte byteOrder; # uint32 wkbType; # uint32 SRID; # struct geometry; # } # byteOrder enum { # WKB_XDR = 0, // Most Significant Byte First # WKB_NDR = 1, // Least Significant Byte First # } # See https://trac.osgeo.org/postgis/browser/branches/3.0/doc/ZMSgeoms.txt # for more details about WKB/EWKB specifications. if isinstance(data, str): # SpatiaLite case # assume that the string is an hex value header = binascii.unhexlify(data[:18]) else: header = data[:9] byte_order, wkb_type, wkb_srid = header[0], header[1:5], header[5:] byte_order_marker = "I" wkb_type_int = ( int(struct.unpack(byte_order_marker, wkb_type)[0]) if len(wkb_type) == 4 else 0 ) if extended is None: if not wkb_type_int: extended = False else: extended = extended or bool(wkb_type_int & 536870912) # Check SRID bit if extended and srid == -1: wkb_srid = struct.unpack(byte_order_marker, wkb_srid)[0] srid = int(wkb_srid) _SpatialElement.__init__(self, data, srid, extended) @property def desc(self) -> str: """This element's description string.""" if isinstance(self.data, str): # SpatiaLite case return self.data.lower() desc = str(binascii.hexlify(self.data), encoding="utf-8").lower() return desc @staticmethod def _data_from_desc(desc) -> bytes: desc = desc.encode(encoding="utf-8") return binascii.unhexlify(desc) def as_wkb(self) -> WKBElement: if self.extended: if isinstance(self.data, str): # SpatiaLite case # assume that the string is an hex value is_hex = True header = binascii.unhexlify(self.data[:10]) byte_order, wkb_type = header[0], header[1:5] else: is_hex = False byte_order, wkb_type = self.data[0], self.data[1:5] byte_order_marker = "I" wkb_type_int = ( int(struct.unpack(byte_order_marker, wkb_type)[0]) if len(wkb_type) == 4 else 0 ) wkb_type_int &= 3758096383 # Set SRID bit to 0 and keep all other bits if is_hex: wkb_type_hex = binascii.hexlify( wkb_type_int.to_bytes(4, "little" if byte_order else "big") ) data = self.data[:2] + wkb_type_hex.decode("ascii") + self.data[18:] else: buffer = bytearray() buffer.extend(self.data[:1]) buffer.extend(struct.pack(byte_order_marker, wkb_type_int)) buffer.extend(self.data[9:]) data = memoryview(buffer) return WKBElement(data, self.srid, extended=False) return WKBElement(self.data, self.srid) def as_ewkb(self) -> WKBElement: if not self.extended and self.srid != -1: if isinstance(self.data, str): # SpatiaLite case # assume that the string is an hex value header = binascii.unhexlify(self.data[:10]) byte_order, wkb_type = header[0], header[1:5] else: byte_order, wkb_type = self.data[0], self.data[1:5] byte_order_marker = "I" wkb_type_int = int( struct.unpack(byte_order_marker, wkb_type)[0] if len(wkb_type) == 4 else 0 ) wkb_type_int |= 536870912 # Set SRID bit to 1 and keep all other bits data: Union[str, memoryview] if isinstance(self.data, str): wkb_type_hex = binascii.hexlify( wkb_type_int.to_bytes(4, "little" if byte_order else "big") ) wkb_srid_hex = binascii.hexlify( self.srid.to_bytes(4, "little" if byte_order else "big") ) data = ( self.data[:2] + wkb_type_hex.decode("ascii") + wkb_srid_hex.decode("ascii") + self.data[10:] ) else: buffer = bytearray() buffer.extend(self.data[:1]) buffer.extend(struct.pack(byte_order_marker, wkb_type_int)) buffer.extend(struct.pack(byte_order_marker, self.srid)) buffer.extend(self.data[5:]) data = memoryview(buffer) return WKBElement(data, self.srid, extended=True) return WKBElement(self.data, self.srid) class RasterElement(_SpatialElement): """Instances of this class wrap a ``raster`` value. Raster values read from the database are converted to instances of this type. In most cases you won't need to create ``RasterElement`` instances yourself. """ geom_from_extended_version: str = "raster" def __init__(self, data: Union[str, bytes, memoryview]) -> None: # read srid from the WKB (binary or hexadecimal format) # The WKB structure is documented in the file # raster/doc/RFC2-WellKnownBinaryFormat of the PostGIS sources. bin_data: Union[str, bytes, memoryview] try: bin_data = binascii.unhexlify(data[:114]) except BinasciiError: bin_data = data data = str(binascii.hexlify(data).decode(encoding="utf-8")) # type: ignore byte_order = bin_data[0] srid = bin_data[53:57] srid = struct.unpack("I", srid)[0] # type: ignore _SpatialElement.__init__(self, data, int(srid), True) @property def desc(self) -> str: """This element's description string.""" return self.data @staticmethod def _data_from_desc(desc): return desc class CompositeElement(FunctionElement): """Instances of this class wrap a Postgres composite type.""" inherit_cache: bool = False """The cache is disabled for this class.""" def __init__(self, base, field, type_) -> None: self.name = field self.type = to_instance(type_) super(CompositeElement, self).__init__(base) @compiles(CompositeElement) def _compile_pgelem(expr, compiler, **kw) -> str: return "(%s).%s" % (compiler.process(expr.clauses, **kw), expr.name) __all__: List[str] = [ "_SpatialElement", "CompositeElement", "RasterElement", "WKBElement", "WKTElement", ] def __dir__() -> List[str]: return __all__ geoalchemy2-0.15.2/geoalchemy2/exc.py000066400000000000000000000003421464355170400173360ustar00rootroot00000000000000"""Exceptions used with GeoAlchemy2.""" class GeoAlchemyError(Exception): """Generic error class.""" class ArgumentError(GeoAlchemyError): """Raised when an invalid or conflicting function argument is supplied.""" geoalchemy2-0.15.2/geoalchemy2/functions.py000066400000000000000000000241201464355170400205670ustar00rootroot00000000000000"""This module defines the internals to map the spatial functions to the spatial columns. This module defines the :class:`GenericFunction` class, which is the base for the implementation of spatial functions in GeoAlchemy. This module is also where actual spatial functions are defined. Spatial functions supported by GeoAlchemy are defined in this module. See :class:`GenericFunction` to know how to create new spatial functions. .. note:: By convention the names of spatial functions are prefixed by ``ST_``. This is to be consistent with PostGIS', which itself is based on the ``SQL-MM`` standard. Functions created by subclassing :class:`GenericFunction` can be called in several ways: * By using the ``func`` object, which is the SQLAlchemy standard way of calling a function. For example, without the ORM:: select([func.ST_Area(lake_table.c.geom)]) and with the ORM:: Session.query(func.ST_Area(Lake.geom)) * By applying the function to a geometry column. For example, without the ORM:: select([lake_table.c.geom.ST_Area()]) and with the ORM:: Session.query(Lake.geom.ST_Area()) * By applying the function to a :class:`geoalchemy2.elements.WKBElement` object (:class:`geoalchemy2.elements.WKBElement` is the type into which GeoAlchemy converts geometry values read from the database), or to a :class:`geoalchemy2.elements.WKTElement` object. For example, without the ORM:: conn.scalar(lake['geom'].ST_Area()) and with the ORM:: session.scalar(lake.geom.ST_Area()) .. warning:: A few functions (like `ST_Transform()`, `ST_Union()`, `ST_SnapToGrid()`, ...) can be used on several spatial types (:class:`geoalchemy2.types.Geometry`, :class:`geoalchemy2.types.Geography` and / or :class:`geoalchemy2.types.Raster` types). In GeoAlchemy2, these functions are only defined for the :class:`geoalchemy2.types.Geometry` type, as it can not be defined for several types at the same time. Therefore, using these functions on :class:`geoalchemy2.types.Geography` or :class:`geoalchemy2.types.Raster` requires minor tweaking to enforce the type by passing the `type_=Geography` or `type_=Raster` argument to the function:: s = select([func.ST_Transform( lake_table.c.raster, 2154, type_=Raster) .label('transformed_raster')]) Reference --------- """ import re from typing import List from typing import Type from sqlalchemy import inspect from sqlalchemy.ext.compiler import compiles from sqlalchemy.sql import annotation from sqlalchemy.sql import functions from sqlalchemy.sql.elements import ColumnElement from geoalchemy2 import elements from geoalchemy2._functions import _FUNCTIONS from geoalchemy2._functions_helpers import _get_docstring _GeoFunctionBase: Type[functions.GenericFunction] _GeoFunctionParent: Type[functions.GenericFunction] try: # SQLAlchemy < 2 from sqlalchemy.sql.functions import _GenericMeta # type: ignore from sqlalchemy.util import with_metaclass # type: ignore class _GeoGenericMeta(_GenericMeta): """Extend the registering mechanism of sqlalchemy. The spatial functions are registered in a specific registry for geoalchemy2. """ _register = False def __init__(cls, clsname, bases, clsdict) -> None: # Register the function elements.function_registry.add(clsname.lower()) super(_GeoGenericMeta, cls).__init__(clsname, bases, clsdict) _GeoFunctionBase = with_metaclass(_GeoGenericMeta, functions.GenericFunction) _GeoFunctionParent = functions.GenericFunction except ImportError: # SQLAlchemy >= 2 class GeoGenericFunction(functions.GenericFunction): def __init_subclass__(cls) -> None: if annotation.Annotated not in cls.__mro__: cls._register_geo_function(cls.__name__, cls.__dict__) super().__init_subclass__() @classmethod def _register_geo_function(cls, clsname, clsdict) -> None: # Check _register attribute status cls._register = getattr(cls, "_register", True) # Register the function if required if cls._register: elements.function_registry.add(clsname.lower()) else: # Set _register to True to register child classes by default cls._register = True _GeoFunctionBase = GeoGenericFunction _GeoFunctionParent = GeoGenericFunction class TableRowElement(ColumnElement): inherit_cache: bool = False """The cache is disabled for this class.""" def __init__(self, selectable: bool) -> None: self.selectable = selectable @property def _from_objects(self) -> List[bool]: return [self.selectable] class ST_AsGeoJSON(_GeoFunctionBase): # type: ignore """Special process for the ST_AsGeoJSON() function. This is to be able to work with its feature version introduced in PostGIS 3. """ name: str = "ST_AsGeoJSON" inherit_cache: bool = True """The cache is enabled for this class.""" def __init__(self, *args, **kwargs) -> None: expr = kwargs.pop("expr", None) args_list = list(args) if expr is not None: args_list = [expr] + args_list for idx, element in enumerate(args_list): if isinstance(element, functions.Function): continue elif isinstance(element, elements._SpatialElement): if element.extended: func_name = element.geom_from_extended_version func_args = [element.data] else: func_name = element.geom_from func_args = [element.data, element.srid] args_list[idx] = getattr(functions.func, func_name)(*func_args) else: try: insp = inspect(element) if hasattr(insp, "selectable"): args_list[idx] = TableRowElement(insp.selectable) except Exception: continue _GeoFunctionParent.__init__(self, *args_list, **kwargs) __doc__ = ( 'Return the geometry as a GeoJSON "geometry" object, or the row as a ' 'GeoJSON feature" object (PostGIS 3 only). (Cf GeoJSON specifications RFC ' "7946). 2D and 3D Geometries are both supported. GeoJSON only support SFS " "1.1 geometry types (no curve support for example). " "See https://postgis.net/docs/ST_AsGeoJSON.html" ) @compiles(TableRowElement) def _compile_table_row_thing(element, compiler, **kw): # In order to get a name as reliably as possible, noting that some # SQL compilers don't say "table AS name" and might not have the "AS", # table and alias names can have spaces in them, etc., get it from # a column instead because that's what we want to be showing here anyway. compiled = compiler.process(list(element.selectable.columns)[0], **kw) # 1. check for exact name of the selectable is here, use that. # This way if it has dots and spaces and anything else in it, we # can get it w/ correct quoting schema = getattr(element.selectable, "schema", "") name = element.selectable.name pattern = r"(.?%s.?\.)?(.?%s.?)\." % (schema, name) m = re.match(pattern, compiled) if m: return m.group(2) # 2. just split on the dot, assume anonymized name return compiled.split(".")[0] class GenericFunction(_GeoFunctionBase): # type: ignore """The base class for GeoAlchemy functions. This class inherits from ``sqlalchemy.sql.functions.GenericFunction``, so functions defined by subclassing this class can be given a fixed return type. For example, functions like :class:`ST_Buffer` and :class:`ST_Envelope` have their ``type`` attributes set to :class:`geoalchemy2.types.Geometry`. This class allows constructs like ``Lake.geom.ST_Buffer(2)``. In that case the ``Function`` instance is bound to an expression (``Lake.geom`` here), and that expression is passed to the function when the function is actually called. If you need to use a function that GeoAlchemy does not provide you will certainly want to subclass this class. For example, if you need the ``ST_TransScale`` spatial function, which isn't (currently) natively supported by GeoAlchemy, you will write this:: from geoalchemy2 import Geometry from geoalchemy2.functions import GenericFunction class ST_TransScale(GenericFunction): name = 'ST_TransScale' type = Geometry """ # Set _register to False in order not to register this class in # sqlalchemy.sql.functions._registry. Only its children will be registered. _register = False def __init__(self, *args, **kwargs) -> None: expr = kwargs.pop("expr", None) args_list = list(args) if expr is not None: args_list = [expr] + args_list for idx, elem in enumerate(args_list): if isinstance(elem, elements._SpatialElement): if elem.extended: func_name = elem.geom_from_extended_version func_args = [elem.data] else: func_name = elem.geom_from func_args = [elem.data, elem.srid] args_list[idx] = getattr(functions.func, func_name)(*func_args) _GeoFunctionParent.__init__(self, *args_list, **kwargs) __all__ = [ "GenericFunction", "ST_AsGeoJSON", "TableRowElement", ] def _create_dynamic_functions() -> None: # Iterate through _FUNCTIONS and create GenericFunction classes dynamically for name, type_, doc in _FUNCTIONS: attributes = { "name": name, "inherit_cache": True, "__doc__": _get_docstring(name, doc, type_), } if type_ is not None: attributes["type"] = type_ globals()[name] = type(name, (GenericFunction,), attributes) __all__.append(name) _create_dynamic_functions() def __dir__() -> List[str]: return __all__ geoalchemy2-0.15.2/geoalchemy2/functions.pyi000066400000000000000000003241341464355170400207500ustar00rootroot00000000000000# this file is automatically generated from typing import List from sqlalchemy.sql import functions from sqlalchemy.sql.elements import ColumnElement import geoalchemy2.types class GenericFunction(functions.GenericFunction): ... class TableRowElement(ColumnElement): inherit_cache: bool = ... """The cache is disabled for this class.""" def __init__(self, selectable: bool) -> None: ... @property # type: ignore[override] def _from_objects(self) -> List[bool]: ... class ST_AsGeoJSON(GenericFunction): """ Return the geometry as a GeoJSON "geometry" object, or the row as a GeoJSON feature" object (PostGIS 3 only). (Cf GeoJSON specifications RFC 7946). 2D and 3D Geometries are both supported. GeoJSON only support SFS 1.1 geometry types (no curve support for example). See https://postgis.net/docs/ST_AsGeoJSON.html see https://postgis.net/docs/ST_AsGeoJSON.html """ class AddGeometryColumn(GenericFunction): """ Adds a geometry column to an existing table. see https://postgis.net/docs/AddGeometryColumn.html """ class DropGeometryColumn(GenericFunction): """ Removes a geometry column from a spatial table. see https://postgis.net/docs/DropGeometryColumn.html """ class DropGeometryTable(GenericFunction): """ Drops a table and all its references in geometry_columns. see https://postgis.net/docs/DropGeometryTable.html """ class Find_SRID(GenericFunction): """ Returns the SRID defined for a geometry column. see https://postgis.net/docs/Find_SRID.html """ class Populate_Geometry_Columns(GenericFunction): """ Ensures geometry columns are defined with type modifiers or have appropriate spatial constraints. see https://postgis.net/docs/Populate_Geometry_Columns.html """ class UpdateGeometrySRID(GenericFunction): """ Updates the SRID of all features in a geometry column, and the table metadata. see https://postgis.net/docs/UpdateGeometrySRID.html """ class ST_Collect(GenericFunction): """ Creates a GeometryCollection or Multi* geometry from a set of geometries. see https://postgis.net/docs/ST_Collect.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LineFromMultiPoint(GenericFunction): """ Creates a LineString from a MultiPoint geometry. see https://postgis.net/docs/ST_LineFromMultiPoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MakeEnvelope(GenericFunction): """ Creates a rectangular Polygon from minimum and maximum coordinates. see https://postgis.net/docs/ST_MakeEnvelope.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MakeLine(GenericFunction): """ Creates a Linestring from Point, MultiPoint, or LineString geometries. see https://postgis.net/docs/ST_MakeLine.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MakePoint(GenericFunction): """ Creates a 2D, 3DZ or 4D Point. see https://postgis.net/docs/ST_MakePoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MakePointM(GenericFunction): """ Creates a Point from X, Y and M values. see https://postgis.net/docs/ST_MakePointM.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MakePolygon(GenericFunction): """ Creates a Polygon from a shell and optional list of holes. see https://postgis.net/docs/ST_MakePolygon.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Point(GenericFunction): """ Creates a Point with the given coordinate values. Alias for ST_MakePoint. see https://postgis.net/docs/ST_Point.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Polygon(GenericFunction): """ [geometry] Creates a Polygon from a LineString with a specified SRID. OR [raster] Returns a multipolygon geometry formed by the union of pixels that have a pixel value that is not no data value. If no band number is specified, band num defaults to 1. see https://postgis.net/docs/ST_Polygon.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_TileEnvelope(GenericFunction): """ Creates a rectangular Polygon in Web Mercator (SRID:3857) using the XYZ tile system. see https://postgis.net/docs/ST_TileEnvelope.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class GeometryType(GenericFunction): """ Returns the type of a geometry as text. see https://postgis.net/docs/GeometryType.html """ class ST_Boundary(GenericFunction): """ Returns the boundary of a geometry. see https://postgis.net/docs/ST_Boundary.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_CoordDim(GenericFunction): """ Return the coordinate dimension of a geometry. see https://postgis.net/docs/ST_CoordDim.html """ class ST_Dimension(GenericFunction): """ Returns the topological dimension of a geometry. see https://postgis.net/docs/ST_Dimension.html """ class ST_Dump(GenericFunction): """ Returns a set of geometry_dump rows for the components of a geometry. see https://postgis.net/docs/ST_Dump.html Return type: :class:`geoalchemy2.types.GeometryDump`. """ type = geoalchemy2.types.GeometryDump() class ST_DumpPoints(GenericFunction): """ Returns a set of geometry_dump rows for the points in a geometry. see https://postgis.net/docs/ST_DumpPoints.html Return type: :class:`geoalchemy2.types.GeometryDump`. """ type = geoalchemy2.types.GeometryDump() class ST_DumpRings(GenericFunction): """ Returns a set of geometry_dump rows for the exterior and interior rings of a Polygon. see https://postgis.net/docs/ST_DumpRings.html Return type: :class:`geoalchemy2.types.GeometryDump`. """ type = geoalchemy2.types.GeometryDump() class ST_EndPoint(GenericFunction): """ Returns the last point of a LineString or CircularLineString. see https://postgis.net/docs/ST_EndPoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Envelope(GenericFunction): """ [geometry] Returns a geometry representing the bounding box of a geometry. OR [raster] Returns the polygon representation of the extent of the raster. see https://postgis.net/docs/ST_Envelope.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_BoundingDiagonal(GenericFunction): """ Returns the diagonal of a geometry's bounding box. see https://postgis.net/docs/ST_BoundingDiagonal.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ExteriorRing(GenericFunction): """ Returns a LineString representing the exterior ring of a Polygon. see https://postgis.net/docs/ST_ExteriorRing.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeometryN(GenericFunction): """ Return the Nth geometry element of a geometry collection. see https://postgis.net/docs/ST_GeometryN.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeometryType(GenericFunction): """ Returns the SQL-MM type of a geometry as text. see https://postgis.net/docs/ST_GeometryType.html """ class ST_HasArc(GenericFunction): """ Tests if a geometry contains a circular arc see https://postgis.net/docs/ST_HasArc.html """ class ST_InteriorRingN(GenericFunction): """ Returns the Nth interior ring (hole) of a Polygon. see https://postgis.net/docs/ST_InteriorRingN.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_IsPolygonCCW(GenericFunction): """ Tests if Polygons have exterior rings oriented counter-clockwise and interior rings oriented clockwise. see https://postgis.net/docs/ST_IsPolygonCCW.html """ class ST_IsPolygonCW(GenericFunction): """ Tests if Polygons have exterior rings oriented clockwise and interior rings oriented counter- clockwise. see https://postgis.net/docs/ST_IsPolygonCW.html """ class ST_IsClosed(GenericFunction): """ Tests if a LineStrings's start and end points are coincident. For a PolyhedralSurface tests if it is closed (volumetric). see https://postgis.net/docs/ST_IsClosed.html """ class ST_IsCollection(GenericFunction): """ Tests if a geometry is a geometry collection type. see https://postgis.net/docs/ST_IsCollection.html """ class ST_IsEmpty(GenericFunction): """ [geometry] Tests if a geometry is empty. OR [raster] Returns true if the raster is empty (width = 0 and height = 0). Otherwise, returns false. see https://postgis.net/docs/ST_IsEmpty.html """ class ST_IsRing(GenericFunction): """ Tests if a LineString is closed and simple. see https://postgis.net/docs/ST_IsRing.html """ class ST_IsSimple(GenericFunction): """ Tests if a geometry has no points of self-intersection or self-tangency. see https://postgis.net/docs/ST_IsSimple.html """ class ST_M(GenericFunction): """ Returns the M coordinate of a Point. see https://postgis.net/docs/ST_M.html """ class ST_MemSize(GenericFunction): """ [geometry] Returns the amount of memory space a geometry takes. OR [raster] Returns the amount of space (in bytes) the raster takes. see https://postgis.net/docs/ST_MemSize.html """ class ST_NDims(GenericFunction): """ Returns the coordinate dimension of a geometry. see https://postgis.net/docs/ST_NDims.html """ class ST_NPoints(GenericFunction): """ Returns the number of points (vertices) in a geometry. see https://postgis.net/docs/ST_NPoints.html """ class ST_NRings(GenericFunction): """ Returns the number of rings in a polygonal geometry. see https://postgis.net/docs/ST_NRings.html """ class ST_NumGeometries(GenericFunction): """ Returns the number of elements in a geometry collection. see https://postgis.net/docs/ST_NumGeometries.html """ class ST_NumInteriorRings(GenericFunction): """ Returns the number of interior rings (holes) of a Polygon. see https://postgis.net/docs/ST_NumInteriorRings.html """ class ST_NumInteriorRing(GenericFunction): """ Returns the number of interior rings (holes) of a Polygon. Aias for ST_NumInteriorRings see https://postgis.net/docs/ST_NumInteriorRing.html """ class ST_NumPatches(GenericFunction): """ Return the number of faces on a Polyhedral Surface. Will return null for non-polyhedral geometries. see https://postgis.net/docs/ST_NumPatches.html """ class ST_NumPoints(GenericFunction): """ Returns the number of points in a LineString or CircularString. see https://postgis.net/docs/ST_NumPoints.html """ class ST_PatchN(GenericFunction): """ Returns the Nth geometry (face) of a PolyhedralSurface. see https://postgis.net/docs/ST_PatchN.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_PointN(GenericFunction): """ Returns the Nth point in the first LineString or circular LineString in a geometry. see https://postgis.net/docs/ST_PointN.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Points(GenericFunction): """ Returns a MultiPoint containing all the coordinates of a geometry. see https://postgis.net/docs/ST_Points.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_StartPoint(GenericFunction): """ Returns the first point of a LineString. see https://postgis.net/docs/ST_StartPoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Summary(GenericFunction): """ [geometry] Returns a text summary of the contents of a geometry. OR [raster] Returns a text summary of the contents of the raster. see https://postgis.net/docs/ST_Summary.html """ class ST_X(GenericFunction): """ Returns the X coordinate of a Point. see https://postgis.net/docs/ST_X.html """ class ST_Y(GenericFunction): """ Returns the Y coordinate of a Point. see https://postgis.net/docs/ST_Y.html """ class ST_Z(GenericFunction): """ Returns the Z coordinate of a Point. see https://postgis.net/docs/ST_Z.html """ class ST_Zmflag(GenericFunction): """ Returns a code indicating the ZM coordinate dimension of a geometry. see https://postgis.net/docs/ST_Zmflag.html """ class ST_AddPoint(GenericFunction): """ Add a point to a LineString. see https://postgis.net/docs/ST_AddPoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_CollectionExtract(GenericFunction): """ Given a (multi)geometry, return a (multi)geometry consisting only of elements of the specified type. see https://postgis.net/docs/ST_CollectionExtract.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_CollectionHomogenize(GenericFunction): """ Given a geometry collection, return the "simplest" representation of the contents. see https://postgis.net/docs/ST_CollectionHomogenize.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Force2D(GenericFunction): """ Force the geometries into a "2-dimensional mode". see https://postgis.net/docs/ST_Force2D.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Force3D(GenericFunction): """ Force the geometries into XYZ mode. This is an alias for ST_Force3DZ. see https://postgis.net/docs/ST_Force_3D.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Force3DZ(GenericFunction): """ Force the geometries into XYZ mode. see https://postgis.net/docs/ST_Force_3DZ.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Force3DM(GenericFunction): """ Force the geometries into XYM mode. see https://postgis.net/docs/ST_Force_3DZ.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Force4D(GenericFunction): """ Force the geometries into XYZM mode. see https://postgis.net/docs/ST_Force_4D.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ForcePolygonCCW(GenericFunction): """ Orients all exterior rings counter-clockwise and all interior rings clockwise. see https://postgis.net/docs/ST_ForcePolygonCCW.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ForceCollection(GenericFunction): """ Convert the geometry into a GEOMETRYCOLLECTION. see https://postgis.net/docs/ST_Force_Collection.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ForcePolygonCW(GenericFunction): """ Orients all exterior rings clockwise and all interior rings counter-clockwise. see https://postgis.net/docs/ST_ForcePolygonCW.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ForceSFS(GenericFunction): """ Force the geometries to use SFS 1.1 geometry types only. see https://postgis.net/docs/ST_ForceSFS.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ForceRHR(GenericFunction): """ Force the orientation of the vertices in a polygon to follow the Right-Hand-Rule. see https://postgis.net/docs/ST_ForceRHR.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ForceCurve(GenericFunction): """ Upcast a geometry into its curved type, if applicable. see https://postgis.net/docs/ST_ForceCurve.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LineMerge(GenericFunction): """ Return a (set of) LineString(s) formed by sewing together a MULTILINESTRING. see https://postgis.net/docs/ST_LineMerge.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Multi(GenericFunction): """ Return the geometry as a MULTI* geometry. see https://postgis.net/docs/ST_Multi.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Normalize(GenericFunction): """ Return the geometry in its canonical form. see https://postgis.net/docs/ST_Normalize.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_QuantizeCoordinates(GenericFunction): """ Sets least significant bits of coordinates to zero see https://postgis.net/docs/ST_QuantizeCoordinates.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_RemovePoint(GenericFunction): """ Remove point from a linestring. see https://postgis.net/docs/ST_RemovePoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Reverse(GenericFunction): """ Return the geometry with vertex order reversed. see https://postgis.net/docs/ST_Reverse.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Segmentize(GenericFunction): """ Return a modified geometry/geography having no segment longer than the given distance. see https://postgis.net/docs/ST_Segmentize.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_SetPoint(GenericFunction): """ Replace point of a linestring with a given point. see https://postgis.net/docs/ST_SetPoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_SnapToGrid(GenericFunction): """ [geometry] Snap all points of the input geometry to a regular grid. OR [raster] Resample a raster by snapping it to a grid. New pixel values are computed using the NearestNeighbor (english or american spelling), Bilinear, Cubic, CubicSpline or Lanczos resampling algorithm. Default is NearestNeighbor. see https://postgis.net/docs/ST_SnapToGrid.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Snap(GenericFunction): """ Snap segments and vertices of input geometry to vertices of a reference geometry. see https://postgis.net/docs/ST_Snap.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_SwapOrdinates(GenericFunction): """ Returns a version of the given geometry with given ordinate values swapped. see https://postgis.net/docs/ST_SwapOrdinates.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_IsValid(GenericFunction): """ Tests if a geometry is well-formed in 2D. see https://postgis.net/docs/ST_IsValid.html """ class ST_IsValidDetail(GenericFunction): """ Returns a valid_detail row stating if a geometry is valid, and if not a reason why and a location. see https://postgis.net/docs/ST_IsValidDetail.html """ class ST_IsValidReason(GenericFunction): """ Returns text stating if a geometry is valid, or a reason for invalidity. see https://postgis.net/docs/ST_IsValidReason.html """ class ST_SetSRID(GenericFunction): """ [geometry] Set the SRID on a geometry to a particular integer value. OR [raster] Sets the SRID of a raster to a particular integer srid defined in the spatial_ref_sys table. see https://postgis.net/docs/ST_SetSRID.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_SRID(GenericFunction): """ [geometry] Returns the spatial reference identifier for the ST_Geometry as defined in spatial_ref_sys table. OR [raster] Returns the spatial reference identifier of the raster as defined in spatial_ref_sys table. see https://postgis.net/docs/ST_SRID.html """ class ST_Transform(GenericFunction): """ [geometry] Return a new geometry with its coordinates transformed to a different spatial reference system. OR [raster] Reprojects a raster in a known spatial reference system to another known spatial reference system using specified resampling algorithm. Options are NearestNeighbor, Bilinear, Cubic, CubicSpline, Lanczos defaulting to NearestNeighbor. see https://postgis.net/docs/ST_Transform.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_BdPolyFromText(GenericFunction): """ Construct a Polygon given an arbitrary collection of closed linestrings as a MultiLineString Well- Known text representation. see https://postgis.net/docs/ST_BdPolyFromText.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_BdMPolyFromText(GenericFunction): """ Construct a MultiPolygon given an arbitrary collection of closed linestrings as a MultiLineString text representation Well-Known text representation. see https://postgis.net/docs/ST_BdMPolyFromText.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeogFromText(GenericFunction): """ Return a specified geography value from Well-Known Text representation or extended (WKT). see https://postgis.net/docs/ST_GeogFromText.html Return type: :class:`geoalchemy2.types.Geography`. """ type = geoalchemy2.types.Geography() class ST_GeographyFromText(GenericFunction): """ Return a specified geography value from Well-Known Text representation or extended (WKT). see https://postgis.net/docs/ST_GeographyFromText.html Return type: :class:`geoalchemy2.types.Geography`. """ type = geoalchemy2.types.Geography() class ST_GeomCollFromText(GenericFunction): """ Makes a collection Geometry from collection WKT with the given SRID. If SRID is not given, it defaults to 0. see https://postgis.net/docs/ST_GeomCollFromText.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeomFromEWKT(GenericFunction): """ Return a specified ST_Geometry value from Extended Well-Known Text representation (EWKT). see https://postgis.net/docs/ST_GeomFromEWKT.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeometryFromText(GenericFunction): """ Return a specified ST_Geometry value from Well-Known Text representation (WKT). This is an alias name for ST_GeomFromText see https://postgis.net/docs/ST_GeometryFromText.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeomFromText(GenericFunction): """ Return a specified ST_Geometry value from Well-Known Text representation (WKT). see https://postgis.net/docs/ST_GeomFromText.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LineFromText(GenericFunction): """ Makes a Geometry from WKT representation with the given SRID. If SRID is not given, it defaults to 0. see https://postgis.net/docs/ST_LineFromText.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MLineFromText(GenericFunction): """ Return a specified ST_MultiLineString value from WKT representation. see https://postgis.net/docs/ST_MLineFromText.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MPointFromText(GenericFunction): """ Makes a Geometry from WKT with the given SRID. If SRID is not given, it defaults to 0. see https://postgis.net/docs/ST_MPointFromText.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MPolyFromText(GenericFunction): """ Makes a MultiPolygon Geometry from WKT with the given SRID. If SRID is not given, it defaults to 0. see https://postgis.net/docs/ST_MPolyFromText.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_PointFromText(GenericFunction): """ Makes a point Geometry from WKT with the given SRID. If SRID is not given, it defaults to unknown. see https://postgis.net/docs/ST_PointFromText.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_PolygonFromText(GenericFunction): """ Makes a Geometry from WKT with the given SRID. If SRID is not given, it defaults to 0. see https://postgis.net/docs/ST_PolygonFromText.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_WKTToSQL(GenericFunction): """ Return a specified ST_Geometry value from Well-Known Text representation (WKT). This is an alias name for ST_GeomFromText see https://postgis.net/docs/ST_WKTToSQL.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeogFromWKB(GenericFunction): """ Creates a geography instance from a Well-Known Binary geometry representation (WKB) or extended Well Known Binary (EWKB). see https://postgis.net/docs/ST_GeogFromWKB.html Return type: :class:`geoalchemy2.types.Geography`. """ type = geoalchemy2.types.Geography() class ST_GeomFromEWKB(GenericFunction): """ Return a specified ST_Geometry value from Extended Well-Known Binary representation (EWKB). see https://postgis.net/docs/ST_GeomFromEWKB.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeomFromWKB(GenericFunction): """ Creates a geometry instance from a Well-Known Binary geometry representation (WKB) and optional SRID. see https://postgis.net/docs/ST_GeomFromWKB.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LineFromWKB(GenericFunction): """ Makes a LINESTRING from WKB with the given SRID see https://postgis.net/docs/ST_LineFromWKB.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LinestringFromWKB(GenericFunction): """ Makes a geometry from WKB with the given SRID. see https://postgis.net/docs/ST_LinestringFromWKB.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_PointFromWKB(GenericFunction): """ Makes a geometry from WKB with the given SRID see https://postgis.net/docs/ST_PointFromWKB.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_WKBToSQL(GenericFunction): """ Return a specified ST_Geometry value from Well-Known Binary representation (WKB). This is an alias name for ST_GeomFromWKB that takes no srid see https://postgis.net/docs/ST_WKBToSQL.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Box2dFromGeoHash(GenericFunction): """ Return a BOX2D from a GeoHash string. see https://postgis.net/docs/ST_Box2dFromGeoHash.html """ class ST_GeomFromGeoHash(GenericFunction): """ Return a geometry from a GeoHash string. see https://postgis.net/docs/ST_GeomFromGeoHash.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeomFromGML(GenericFunction): """ Takes as input GML representation of geometry and outputs a PostGIS geometry object see https://postgis.net/docs/ST_GeomFromGML.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeomFromGeoJSON(GenericFunction): """ Takes as input a geojson representation of a geometry and outputs a PostGIS geometry object see https://postgis.net/docs/ST_GeomFromGeoJSON.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeomFromKML(GenericFunction): """ Takes as input KML representation of geometry and outputs a PostGIS geometry object see https://postgis.net/docs/ST_GeomFromKML.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeomFromTWKB(GenericFunction): """ Creates a geometry instance from a TWKB ("Tiny Well-Known Binary") geometry representation. see https://postgis.net/docs/ST_GeomFromTWKB.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GMLToSQL(GenericFunction): """ Return a specified ST_Geometry value from GML representation. This is an alias name for ST_GeomFromGML see https://postgis.net/docs/ST_GMLToSQL.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LineFromEncodedPolyline(GenericFunction): """ Creates a LineString from an Encoded Polyline. see https://postgis.net/docs/ST_LineFromEncodedPolyline.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_PointFromGeoHash(GenericFunction): """ Return a point from a GeoHash string. see https://postgis.net/docs/ST_PointFromGeoHash.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_AsEWKT(GenericFunction): """ Return the Well-Known Text (WKT) representation of the geometry with SRID meta data. see https://postgis.net/docs/ST_AsEWKT.html """ class ST_AsText(GenericFunction): """ Return the Well-Known Text (WKT) representation of the geometry/geography without SRID metadata. see https://postgis.net/docs/ST_AsText.html """ class ST_AsBinary(GenericFunction): """ [geometry] Return the Well-Known Binary (WKB) representation of the geometry/geography without SRID meta data. OR [raster] Return the Well-Known Binary (WKB) representation of the raster. see https://postgis.net/docs/ST_AsBinary.html """ class ST_AsEWKB(GenericFunction): """ Return the Well-Known Binary (WKB) representation of the geometry with SRID meta data. see https://postgis.net/docs/ST_AsEWKB.html """ class ST_AsHEXEWKB(GenericFunction): """ Returns a Geometry in HEXEWKB format (as text) using either little-endian (NDR) or big-endian (XDR) encoding. see https://postgis.net/docs/ST_AsHEXEWKB.html """ class ST_AsEncodedPolyline(GenericFunction): """ Returns an Encoded Polyline from a LineString geometry. see https://postgis.net/docs/ST_AsEncodedPolyline.html """ class ST_AsGeobuf(GenericFunction): """ Return a Geobuf representation of a set of rows. see https://postgis.net/docs/ST_AsGeobuf.html """ class ST_AsGML(GenericFunction): """ Return the geometry as a GML version 2 or 3 element. see https://postgis.net/docs/ST_AsGML.html """ class ST_AsKML(GenericFunction): """ Return the geometry as a KML element. Several variants. Default version=2, default maxdecimaldigits=15 see https://postgis.net/docs/ST_AsKML.html """ class ST_AsLatLonText(GenericFunction): """ Return the Degrees, Minutes, Seconds representation of the given point. see https://postgis.net/docs/ST_AsLatLonText.html """ class ST_AsMVTGeom(GenericFunction): """ Transform a geometry into the coordinate space of a Mapbox Vector Tile. see https://postgis.net/docs/ST_AsMVTGeom.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_AsMVT(GenericFunction): """ Aggregate function returning a Mapbox Vector Tile representation of a set of rows. see https://postgis.net/docs/ST_AsMVT.html """ class ST_AsSVG(GenericFunction): """ Returns SVG path data for a geometry. see https://postgis.net/docs/ST_AsSVG.html """ class ST_AsTWKB(GenericFunction): """ Returns the geometry as TWKB, aka "Tiny Well-Known Binary" see https://postgis.net/docs/ST_AsTWKB.html """ class ST_AsX3D(GenericFunction): """ Returns a Geometry in X3D xml node element format: ISO-IEC-19776-1.2-X3DEncodings-XML see https://postgis.net/docs/ST_AsX3D.html """ class ST_GeoHash(GenericFunction): """ Return a GeoHash representation of the geometry. see https://postgis.net/docs/ST_GeoHash.html """ class ST_3DIntersects(GenericFunction): """ Returns TRUE if the Geometries "spatially intersect" in 3D - only for points, linestrings, polygons, polyhedral surface (area). see https://postgis.net/docs/ST_3DIntersects.html """ class ST_Contains(GenericFunction): """ [geometry] Returns true if and only if no points of B lie in the exterior of A, and at least one point of the interior of B lies in the interior of A. OR [raster] Return true if no points of raster rastB lie in the exterior of raster rastA and at least one point of the interior of rastB lies in the interior of rastA. see https://postgis.net/docs/ST_Contains.html """ class ST_ContainsProperly(GenericFunction): """ [geometry] Returns true if B intersects the interior of A but not the boundary (or exterior). A does not contain properly itself, but does contain itself. OR [raster] Return true if rastB intersects the interior of rastA but not the boundary or exterior of rastA. see https://postgis.net/docs/ST_ContainsProperly.html """ class ST_Covers(GenericFunction): """ [geometry] Returns 1 (TRUE) if no point in Geometry B is outside Geometry A OR [raster] Return true if no points of raster rastB lie outside raster rastA. see https://postgis.net/docs/ST_Covers.html """ class ST_CoveredBy(GenericFunction): """ [geometry] Returns 1 (TRUE) if no point in Geometry/Geography A is outside Geometry/Geography B OR [raster] Return true if no points of raster rastA lie outside raster rastB. see https://postgis.net/docs/ST_CoveredBy.html """ class ST_Crosses(GenericFunction): """ Returns TRUE if the supplied geometries have some, but not all, interior points in common. see https://postgis.net/docs/ST_Crosses.html """ class ST_LineCrossingDirection(GenericFunction): """ Given 2 linestrings, returns a number between -3 and 3 denoting what kind of crossing behavior. 0 is no crossing. see https://postgis.net/docs/ST_LineCrossingDirection.html """ class ST_Disjoint(GenericFunction): """ [geometry] Returns TRUE if the Geometries do not "spatially intersect" - if they do not share any space together. OR [raster] Return true if raster rastA does not spatially intersect rastB. see https://postgis.net/docs/ST_Disjoint.html """ class ST_Equals(GenericFunction): """ Returns true if the given geometries represent the same geometry. Directionality is ignored. see https://postgis.net/docs/ST_Equals.html """ class ST_Intersects(GenericFunction): """ [geometry] Returns TRUE if the Geometries/Geography "spatially intersect in 2D" - (share any portion of space) and FALSE if they don't (they are Disjoint). For geography tolerance is 0.00001 meters (so any points that close are considered to intersect) OR [raster] Return true if raster rastA spatially intersects raster rastB. see https://postgis.net/docs/ST_Intersects.html """ class ST_OrderingEquals(GenericFunction): """ Returns true if the given geometries represent the same geometry and points are in the same directional order. see https://postgis.net/docs/ST_OrderingEquals.html """ class ST_Overlaps(GenericFunction): """ [geometry] Returns TRUE if the Geometries share space, are of the same dimension, but are not completely contained by each other. OR [raster] Return true if raster rastA and rastB intersect but one does not completely contain the other. see https://postgis.net/docs/ST_Overlaps.html """ class ST_PointInsideCircle(GenericFunction): """ Is the point geometry inside the circle defined by center_x, center_y, radius see https://postgis.net/docs/ST_PointInsideCircle.html """ class ST_Relate(GenericFunction): """ Returns true if this Geometry is spatially related to anotherGeometry, by testing for intersections between the Interior, Boundary and Exterior of the two geometries as specified by the values in the intersectionMatrixPattern. If no intersectionMatrixPattern is passed in, then returns the maximum intersectionMatrixPattern that relates the 2 geometries. see https://postgis.net/docs/ST_Relate.html """ class ST_RelateMatch(GenericFunction): """ Returns true if intersectionMattrixPattern1 implies intersectionMatrixPattern2 see https://postgis.net/docs/ST_RelateMatch.html """ class ST_Touches(GenericFunction): """ [geometry] Returns TRUE if the geometries have at least one point in common, but their interiors do not intersect. OR [raster] Return true if raster rastA and rastB have at least one point in common but their interiors do not intersect. see https://postgis.net/docs/ST_Touches.html """ class ST_Within(GenericFunction): """ [geometry] Returns true if the geometry A is completely inside geometry B OR [raster] Return true if no points of raster rastA lie in the exterior of raster rastB and at least one point of the interior of rastA lies in the interior of rastB. see https://postgis.net/docs/ST_Within.html """ class ST_3DDWithin(GenericFunction): """ For 3d (z) geometry type Returns true if two geometries 3d distance is within number of units. see https://postgis.net/docs/ST_3DDWithin.html """ class ST_3DDFullyWithin(GenericFunction): """ Returns true if all of the 3D geometries are within the specified distance of one another. see https://postgis.net/docs/ST_3DDFullyWithin.html """ class ST_DFullyWithin(GenericFunction): """ [geometry] Returns true if all of the geometries are within the specified distance of one another OR [raster] Return true if rasters rastA and rastB are fully within the specified distance of each other. see https://postgis.net/docs/ST_DFullyWithin.html """ class ST_DWithin(GenericFunction): """ [geometry] Returns true if the geometries are within the specified distance of one another. For geometry units are in those of spatial reference and for geography units are in meters and measurement is defaulted to use_spheroid=true (measure around spheroid), for faster check, use_spheroid=false to measure along sphere. OR [raster] Return true if rasters rastA and rastB are within the specified distance of each other. see https://postgis.net/docs/ST_DWithin.html """ class ST_Area(GenericFunction): """ Returns the area of a polygonal geometry. see https://postgis.net/docs/ST_Area.html """ class ST_Azimuth(GenericFunction): """ Returns the north-based azimuth as the angle in radians measured clockwise from the vertical on pointA to pointB. see https://postgis.net/docs/ST_Azimuth.html """ class ST_Angle(GenericFunction): """ Returns the angle between 3 points, or between 2 vectors (4 points or 2 lines). see https://postgis.net/docs/ST_Angle.html """ class ST_ClosestPoint(GenericFunction): """ Returns the 2D point on g1 that is closest to g2. This is the first point of the shortest line. see https://postgis.net/docs/ST_ClosestPoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_3DClosestPoint(GenericFunction): """ Returns the 3D point on g1 that is closest to g2. This is the first point of the 3D shortest line. see https://postgis.net/docs/ST_3DClosestPoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Distance(GenericFunction): """ Returns the distance between two geometry or geography values. see https://postgis.net/docs/ST_Distance.html """ class ST_3DDistance(GenericFunction): """ Returns the 3D cartesian minimum distance (based on spatial ref) between two geometries in projected units. see https://postgis.net/docs/ST_3DDistance.html """ class ST_DistanceSphere(GenericFunction): """ Returns minimum distance in meters between two lon/lat geometries using a spherical earth model. see https://postgis.net/docs/ST_DistanceSphere.html """ class ST_DistanceSpheroid(GenericFunction): """ Returns the minimum distance between two lon/lat geometries using a spheroidal earth model. see https://postgis.net/docs/ST_DistanceSpheroid.html """ class ST_FrechetDistance(GenericFunction): """ Returns the Fréchet distance between two geometries. see https://postgis.net/docs/ST_FrechetDistance.html """ class ST_HausdorffDistance(GenericFunction): """ Returns the Hausdorff distance between two geometries. see https://postgis.net/docs/ST_HausdorffDistance.html """ class ST_Length(GenericFunction): """ Returns the 2D length of a linear geometry. see https://postgis.net/docs/ST_Length.html """ class ST_Length2D(GenericFunction): """ Returns the 2D length of a linear geometry. Alias for ST_Length see https://postgis.net/docs/ST_Length2D.html """ class ST_3DLength(GenericFunction): """ Returns the 3D length of a linear geometry. see https://postgis.net/docs/ST_3DLength.html """ class ST_LengthSpheroid(GenericFunction): """ Returns the 2D or 3D length/perimeter of a lon/lat geometry on a spheroid. see https://postgis.net/docs/ST_LengthSpheroid.html """ class ST_LongestLine(GenericFunction): """ Returns the 2D longest line between two geometries. see https://postgis.net/docs/ST_LongestLine.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_3DLongestLine(GenericFunction): """ Returns the 3D longest line between two geometries see https://postgis.net/docs/ST_3DLongestLine.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MaxDistance(GenericFunction): """ Returns the 2D largest distance between two geometries in projected units. see https://postgis.net/docs/ST_MaxDistance.html """ class ST_3DMaxDistance(GenericFunction): """ Returns the 3D cartesian maximum distance (based on spatial ref) between two geometries in projected units. see https://postgis.net/docs/ST_3DMaxDistance.html """ class ST_MinimumClearance(GenericFunction): """ Returns the minimum clearance of a geometry, a measure of a geometry's robustness. see https://postgis.net/docs/ST_MinimumClearance.html """ class ST_MinimumClearanceLine(GenericFunction): """ Returns the two-point LineString spanning a geometry's minimum clearance. see https://postgis.net/docs/ST_MinimumClearanceLine.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Perimeter(GenericFunction): """ Returns the length of the boundary of a polygonal geometry or geography. see https://postgis.net/docs/ST_Perimeter.html """ class ST_Perimeter2D(GenericFunction): """ Returns the 2D perimeter of a polygonal geometry. Alias for ST_Perimeter. see https://postgis.net/docs/ST_Perimeter2D.html """ class ST_3DPerimeter(GenericFunction): """ Returns the 3D perimeter of a polygonal geometry. see https://postgis.net/docs/ST_3DPerimeter.html """ class ST_Project(GenericFunction): """ Returns a point projected from a start point by a distance and bearing (azimuth). see https://postgis.net/docs/ST_Project.html Return type: :class:`geoalchemy2.types.Geography`. """ type = geoalchemy2.types.Geography() class ST_ShortestLine(GenericFunction): """ Returns the 2D shortest line between two geometries see https://postgis.net/docs/ST_ShortestLine.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_3DShortestLine(GenericFunction): """ Returns the 3D shortest line between two geometries see https://postgis.net/docs/ST_3DShortestLine.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Buffer(GenericFunction): """ (T) Returns a geometry covering all points within a given distance from the input geometry. see https://postgis.net/docs/ST_Buffer.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_BuildArea(GenericFunction): """ Creates an areal geometry formed by the constituent linework of given geometry see https://postgis.net/docs/ST_BuildArea.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Centroid(GenericFunction): """ Returns the geometric center of a geometry. see https://postgis.net/docs/ST_Centroid.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ClipByBox2D(GenericFunction): """ Returns the portion of a geometry falling within a rectangle. see https://postgis.net/docs/ST_ClipByBox2D.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ConcaveHull(GenericFunction): """ The concave hull of a geometry represents a possibly concave geometry that encloses all geometries within the set. You can think of it as shrink wrapping. see https://postgis.net/docs/ST_ConcaveHull.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ConvexHull(GenericFunction): """ [geometry] Computes the convex hull of a geometry. OR [raster] Return the convex hull geometry of the raster including pixel values equal to BandNoDataValue. For regular shaped and non-skewed rasters, this gives the same result as ST_Envelope so only useful for irregularly shaped or skewed rasters. see https://postgis.net/docs/ST_ConvexHull.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_CurveToLine(GenericFunction): """ Converts a CIRCULARSTRING/CURVEPOLYGON/MULTISURFACE to a LINESTRING/POLYGON/MULTIPOLYGON see https://postgis.net/docs/ST_CurveToLine.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_DelaunayTriangles(GenericFunction): """ Return a Delaunay triangulation around the given input points. see https://postgis.net/docs/ST_DelaunayTriangles.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Difference(GenericFunction): """ Returns a geometry that represents that part of geometry A that does not intersect with geometry B. see https://postgis.net/docs/ST_Difference.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_FlipCoordinates(GenericFunction): """ Returns a version of the given geometry with X and Y axis flipped. Useful for people who have built latitude/longitude features and need to fix them. see https://postgis.net/docs/ST_FlipCoordinates.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeneratePoints(GenericFunction): """ Converts a polygon or multi-polygon into a multi-point composed of randomly location points within the original areas. see https://postgis.net/docs/ST_GeneratePoints.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_GeometricMedian(GenericFunction): """ Returns the geometric median of a MultiPoint. see https://postgis.net/docs/ST_GeometricMedian.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Intersection(GenericFunction): """ [geometry] (T) Returns a geometry that represents the shared portion of geomA and geomB. OR [raster] Returns a raster or a set of geometry-pixelvalue pairs representing the shared portion of two rasters or the geometrical intersection of a vectorization of the raster and a geometry. see https://postgis.net/docs/ST_Intersection.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LineToCurve(GenericFunction): """ Converts a LINESTRING/POLYGON to a CIRCULARSTRING, CURVEPOLYGON see https://postgis.net/docs/ST_LineToCurve.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MakeValid(GenericFunction): """ Attempts to make an invalid geometry valid without losing vertices. see https://postgis.net/docs/ST_MakeValid.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MemUnion(GenericFunction): """ Same as ST_Union, only memory-friendly (uses less memory and more processor time). see https://postgis.net/docs/ST_MemUnion.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MinimumBoundingCircle(GenericFunction): """ Returns the smallest circle polygon that can fully contain a geometry. Default uses 48 segments per quarter circle. see https://postgis.net/docs/ST_MinimumBoundingCircle.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MinimumBoundingRadius(GenericFunction): """ Returns the center point and radius of the smallest circle that can fully contain a geometry. see https://postgis.net/docs/ST_MinimumBoundingRadius.html """ class ST_OrientedEnvelope(GenericFunction): """ Returns a minimum rotated rectangle enclosing a geometry. see https://postgis.net/docs/ST_OrientedEnvelope.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Polygonize(GenericFunction): """ Aggregate. Creates a GeometryCollection containing possible polygons formed from the constituent linework of a set of geometries. see https://postgis.net/docs/ST_Polygonize.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Node(GenericFunction): """ Node a set of linestrings. see https://postgis.net/docs/ST_Node.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_OffsetCurve(GenericFunction): """ Return an offset line at a given distance and side from an input line. Useful for computing parallel lines about a center line see https://postgis.net/docs/ST_OffsetCurve.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_PointOnSurface(GenericFunction): """ Returns a POINT guaranteed to lie on the surface. see https://postgis.net/docs/ST_PointOnSurface.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_RemoveRepeatedPoints(GenericFunction): """ Returns a version of the given geometry with duplicated points removed. see https://postgis.net/docs/ST_RemoveRepeatedPoints.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_SharedPaths(GenericFunction): """ Returns a collection containing paths shared by the two input linestrings/multilinestrings. see https://postgis.net/docs/ST_SharedPaths.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ShiftLongitude(GenericFunction): """ Toggle geometry coordinates between -180..180 and 0..360 ranges. see https://postgis.net/docs/ST_Shift_Longitude.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_WrapX(GenericFunction): """ Wrap a geometry around an X value. see https://postgis.net/docs/ST_WrapX.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Simplify(GenericFunction): """ Returns a "simplified" version of the given geometry using the Douglas-Peucker algorithm. see https://postgis.net/docs/ST_Simplify.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_SimplifyPreserveTopology(GenericFunction): """ Returns a "simplified" version of the given geometry using the Douglas-Peucker algorithm. Will avoid creating derived geometries (polygons in particular) that are invalid. see https://postgis.net/docs/ST_SimplifyPreserveTopology.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_SimplifyVW(GenericFunction): """ Returns a "simplified" version of the given geometry using the Visvalingam-Whyatt algorithm see https://postgis.net/docs/ST_SimplifyVW.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ChaikinSmoothing(GenericFunction): """ Returns a "smoothed" version of the given geometry using the Chaikin algorithm see https://postgis.net/docs/ST_ChaikinSmoothing.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_FilterByM(GenericFunction): """ Filters vertex points based on their m-value see https://postgis.net/docs/ST_FilterByM.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_SetEffectiveArea(GenericFunction): """ Sets the effective area for each vertex, storing the value in the M ordinate. A simplified geometry can then be generated by filtering on the M ordinate. see https://postgis.net/docs/ST_SetEffectiveArea.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Split(GenericFunction): """ Returns a collection of geometries resulting by splitting a geometry. see https://postgis.net/docs/ST_Split.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_SymDifference(GenericFunction): """ Returns a geometry that represents the portions of A and B that do not intersect. It is called a symmetric difference because ST_SymDifference(A,B) = ST_SymDifference(B,A). see https://postgis.net/docs/ST_SymDifference.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Subdivide(GenericFunction): """ Returns a set of geometry where no geometry in the set has more than the specified number of vertices. see https://postgis.net/docs/ST_Subdivide.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Union(GenericFunction): """ [geometry] Returns a geometry that represents the point set union of the Geometries. OR [raster] Returns the union of a set of raster tiles into a single raster composed of 1 or more bands. see https://postgis.net/docs/ST_Union.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_UnaryUnion(GenericFunction): """ Like ST_Union, but working at the geometry component level. see https://postgis.net/docs/ST_UnaryUnion.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_VoronoiLines(GenericFunction): """ Returns the boundaries between the cells of the Voronoi diagram constructed from the vertices of a geometry. see https://postgis.net/docs/ST_VoronoiLines.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_VoronoiPolygons(GenericFunction): """ Returns the cells of the Voronoi diagram constructed from the vertices of a geometry. see https://postgis.net/docs/ST_VoronoiPolygons.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Affine(GenericFunction): """ Apply a 3D affine transformation to a geometry. see https://postgis.net/docs/ST_Affine.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Rotate(GenericFunction): """ Rotates a geometry about an origin point. see https://postgis.net/docs/ST_Rotate.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_RotateX(GenericFunction): """ Rotates a geometry about the X axis. see https://postgis.net/docs/ST_RotateX.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_RotateY(GenericFunction): """ Rotates a geometry about the Y axis. see https://postgis.net/docs/ST_RotateY.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_RotateZ(GenericFunction): """ Rotates a geometry about the Z axis. see https://postgis.net/docs/ST_RotateZ.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Scale(GenericFunction): """ Scales a geometry by given factors. see https://postgis.net/docs/ST_Scale.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Translate(GenericFunction): """ Translates a geometry by given offsets. see https://postgis.net/docs/ST_Translate.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_TransScale(GenericFunction): """ Translates and scales a geometry by given offsets and factors. see https://postgis.net/docs/ST_TransScale.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ClusterDBSCAN(GenericFunction): """ Window function that returns a cluster id for each input geometry using the DBSCAN algorithm. see https://postgis.net/docs/ST_ClusterDBSCAN.html """ class ST_ClusterIntersecting(GenericFunction): """ Aggregate function that clusters the input geometries into connected sets. see https://postgis.net/docs/ST_ClusterIntersecting.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ClusterKMeans(GenericFunction): """ Window function that returns a cluster id for each input geometry using the K-means algorithm. see https://postgis.net/docs/ST_ClusterKMeans.html """ class ST_ClusterWithin(GenericFunction): """ Aggregate function that clusters the input geometries by separation distance. see https://postgis.net/docs/ST_ClusterWithin.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class Box2D(GenericFunction): """ Returns a BOX2D representing the 2D extent of the geometry. see https://postgis.net/docs/Box2D_type.html """ class Box3D(GenericFunction): """ [geometry] Returns a BOX3D representing the 3D extent of the geometry. OR [raster] Returns the box 3d representation of the enclosing box of the raster. see https://postgis.net/docs/Box3D_type.html """ class ST_EstimatedExtent(GenericFunction): """ Return the 'estimated' extent of a spatial table. see https://postgis.net/docs/ST_EstimatedExtent.html """ class ST_Expand(GenericFunction): """ Returns a bounding box expanded from another bounding box or a geometry. see https://postgis.net/docs/ST_Expand.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Extent(GenericFunction): """ an aggregate function that returns the bounding box that bounds rows of geometries. see https://postgis.net/docs/ST_Extent.html """ class ST_3DExtent(GenericFunction): """ an aggregate function that returns the 3D bounding box that bounds rows of geometries. see https://postgis.net/docs/ST_3DExtent.html """ class ST_MakeBox2D(GenericFunction): """ Creates a BOX2D defined by two 2D point geometries. see https://postgis.net/docs/ST_MakeBox2D.html """ class ST_3DMakeBox(GenericFunction): """ Creates a BOX3D defined by two 3D point geometries. see https://postgis.net/docs/ST_3DMakeBox.html """ class ST_XMax(GenericFunction): """ Returns the X maxima of a 2D or 3D bounding box or a geometry. see https://postgis.net/docs/ST_XMax.html """ class ST_XMin(GenericFunction): """ Returns the X minima of a 2D or 3D bounding box or a geometry. see https://postgis.net/docs/ST_XMin.html """ class ST_YMax(GenericFunction): """ Returns the Y maxima of a 2D or 3D bounding box or a geometry. see https://postgis.net/docs/ST_YMax.html """ class ST_YMin(GenericFunction): """ Returns the Y minima of a 2D or 3D bounding box or a geometry. see https://postgis.net/docs/ST_YMin.html """ class ST_ZMax(GenericFunction): """ Returns the Z maxima of a 2D or 3D bounding box or a geometry. see https://postgis.net/docs/ST_ZMax.html """ class ST_ZMin(GenericFunction): """ Returns the Z minima of a 2D or 3D bounding box or a geometry. see https://postgis.net/docs/ST_ZMin.html """ class ST_LineInterpolatePoint(GenericFunction): """ Returns a point interpolated along a line. Second argument is a float8 between 0 and 1 representing fraction of total length of linestring the point has to be located. see https://postgis.net/docs/ST_LineInterpolatePoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_3DLineInterpolatePoint(GenericFunction): """ Returns a point interpolated along a line in 3D. Second argument is a float8 between 0 and 1 representing fraction of total length of linestring the point has to be located. see https://postgis.net/docs/ST_3DLineInterpolatePoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LineInterpolatePoints(GenericFunction): """ Returns one or more points interpolated along a line. see https://postgis.net/docs/ST_LineInterpolatePoints.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LineLocatePoint(GenericFunction): """ Returns a float between 0 and 1 representing the location of the closest point on LineString to the given Point, as a fraction of total 2d line length. see https://postgis.net/docs/ST_LineLocatePoint.html """ class ST_LineSubstring(GenericFunction): """ Return a linestring being a substring of the input one starting and ending at the given fractions of total 2d length. Second and third arguments are float8 values between 0 and 1. see https://postgis.net/docs/ST_LineSubstring.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LocateAlong(GenericFunction): """ Return a derived geometry collection value with elements that match the specified measure. Polygonal elements are not supported. see https://postgis.net/docs/ST_LocateAlong.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LocateBetween(GenericFunction): """ Return a derived geometry collection value with elements that match the specified range of measures inclusively. see https://postgis.net/docs/ST_LocateBetween.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_LocateBetweenElevations(GenericFunction): """ Return a derived geometry (collection) value with elements that intersect the specified range of elevations inclusively. see https://postgis.net/docs/ST_LocateBetweenElevations.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_InterpolatePoint(GenericFunction): """ Return the value of the measure dimension of a geometry at the point closed to the provided point. see https://postgis.net/docs/ST_InterpolatePoint.html """ class ST_AddMeasure(GenericFunction): """ Return a derived geometry with measure elements linearly interpolated between the start and end points. see https://postgis.net/docs/ST_AddMeasure.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_IsValidTrajectory(GenericFunction): """ Returns true if the geometry is a valid trajectory. see https://postgis.net/docs/ST_IsValidTrajectory.html """ class ST_ClosestPointOfApproach(GenericFunction): """ Returns the measure at which points interpolated along two trajectories are closest. see https://postgis.net/docs/ST_ClosestPointOfApproach.html """ class ST_DistanceCPA(GenericFunction): """ Returns the distance between the closest point of approach of two trajectories. see https://postgis.net/docs/ST_DistanceCPA.html """ class ST_CPAWithin(GenericFunction): """ Returns true if the closest point of approach of two trajectories is within the specified distance. see https://postgis.net/docs/ST_CPAWithin.html """ class postgis_sfcgal_version(GenericFunction): """ Returns the version of SFCGAL in use see https://postgis.net/docs/postgis_sfcgal_version.html """ class ST_Extrude(GenericFunction): """ Extrude a surface to a related volume see https://postgis.net/docs/ST_Extrude.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_StraightSkeleton(GenericFunction): """ Compute a straight skeleton from a geometry see https://postgis.net/docs/ST_StraightSkeleton.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ApproximateMedialAxis(GenericFunction): """ Compute the approximate medial axis of an areal geometry. see https://postgis.net/docs/ST_ApproximateMedialAxis.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_IsPlanar(GenericFunction): """ Check if a surface is or not planar see https://postgis.net/docs/ST_IsPlanar.html """ class ST_Orientation(GenericFunction): """ Determine surface orientation see https://postgis.net/docs/ST_Orientation.html """ class ST_ForceLHR(GenericFunction): """ Force LHR orientation see https://postgis.net/docs/ST_ForceLHR.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_MinkowskiSum(GenericFunction): """ Performs Minkowski sum see https://postgis.net/docs/ST_MinkowskiSum.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_ConstrainedDelaunayTriangles(GenericFunction): """ Return a constrained Delaunay triangulation around the given input geometry. see https://postgis.net/docs/ST_ConstrainedDelaunayTriangles.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_3DIntersection(GenericFunction): """ Perform 3D intersection see https://postgis.net/docs/ST_3DIntersection.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_3DDifference(GenericFunction): """ Perform 3D difference see https://postgis.net/docs/ST_3DDifference.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_3DUnion(GenericFunction): """ Perform 3D union see https://postgis.net/docs/ST_3DUnion.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_3DArea(GenericFunction): """ Computes area of 3D surface geometries. Will return 0 for solids. see https://postgis.net/docs/ST_3DArea.html """ class ST_Tesselate(GenericFunction): """ Perform surface Tessellation of a polygon or polyhedralsurface and returns as a TIN or collection of TINS see https://postgis.net/docs/ST_Tesselate.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_Volume(GenericFunction): """ Computes the volume of a 3D solid. If applied to surface (even closed) geometries will return 0. see https://postgis.net/docs/ST_Volume.html """ class ST_MakeSolid(GenericFunction): """ Cast the geometry into a solid. No check is performed. To obtain a valid solid, the input geometry must be a closed Polyhedral Surface or a closed TIN. see https://postgis.net/docs/ST_MakeSolid.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_IsSolid(GenericFunction): """ Test if the geometry is a solid. No validity check is performed. see https://postgis.net/docs/ST_IsSolid.html """ class AddAuth(GenericFunction): """ Adds an authorization token to be used in the current transaction. see https://postgis.net/docs/AddAuth.html """ class CheckAuth(GenericFunction): """ Creates a trigger on a table to prevent/allow updates and deletes of rows based on authorization token. see https://postgis.net/docs/CheckAuth.html """ class DisableLongTransactions(GenericFunction): """ Disables long transaction support. see https://postgis.net/docs/DisableLongTransactions.html """ class EnableLongTransactions(GenericFunction): """ Enables long transaction support. see https://postgis.net/docs/EnableLongTransactions.html """ class LockRow(GenericFunction): """ Sets lock/authorization for a row in a table. see https://postgis.net/docs/LockRow.html """ class UnlockRows(GenericFunction): """ Removes all locks held by an authorization token. see https://postgis.net/docs/UnlockRows.html """ class PostGIS_Extensions_Upgrade(GenericFunction): """ Packages and upgrades postgis extensions (e.g. postgis_raster, postgis_topology, postgis_sfcgal) to latest available version. see https://postgis.net/docs/PostGIS_Extensions_Upgrade.html """ class PostGIS_Full_Version(GenericFunction): """ Reports full postgis version and build configuration infos. see https://postgis.net/docs/PostGIS_Full_Version.html """ class PostGIS_GEOS_Version(GenericFunction): """ Returns the version number of the GEOS library. see https://postgis.net/docs/PostGIS_GEOS_Version.html """ class PostGIS_Liblwgeom_Version(GenericFunction): """ Returns the version number of the liblwgeom library. This should match the version of PostGIS. see https://postgis.net/docs/PostGIS_Liblwgeom_Version.html """ class PostGIS_LibXML_Version(GenericFunction): """ Returns the version number of the libxml2 library. see https://postgis.net/docs/PostGIS_LibXML_Version.html """ class PostGIS_Lib_Build_Date(GenericFunction): """ Returns build date of the PostGIS library. see https://postgis.net/docs/PostGIS_Lib_Build_Date.html """ class PostGIS_Lib_Version(GenericFunction): """ Returns the version number of the PostGIS library. see https://postgis.net/docs/PostGIS_Lib_Version.html """ class PostGIS_PROJ_Version(GenericFunction): """ Returns the version number of the PROJ4 library. see https://postgis.net/docs/PostGIS_PROJ_Version.html """ class PostGIS_Wagyu_Version(GenericFunction): """ Returns the version number of the internal Wagyu library. see https://postgis.net/docs/PostGIS_Wagyu_Version.html """ class PostGIS_Scripts_Build_Date(GenericFunction): """ Returns build date of the PostGIS scripts. see https://postgis.net/docs/PostGIS_Scripts_Build_Date.html """ class PostGIS_Scripts_Installed(GenericFunction): """ Returns version of the postgis scripts installed in this database. see https://postgis.net/docs/PostGIS_Scripts_Installed.html """ class PostGIS_Scripts_Released(GenericFunction): """ Returns the version number of the postgis.sql script released with the installed postgis lib. see https://postgis.net/docs/PostGIS_Scripts_Released.html """ class PostGIS_Version(GenericFunction): """ Returns PostGIS version number and compile-time options. see https://postgis.net/docs/PostGIS_Version.html """ class PostGIS_AddBBox(GenericFunction): """ Add bounding box to the geometry. see https://postgis.net/docs/PostGIS_AddBBox.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class PostGIS_DropBBox(GenericFunction): """ Drop the bounding box cache from the geometry. see https://postgis.net/docs/PostGIS_DropBBox.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class PostGIS_HasBBox(GenericFunction): """ Returns TRUE if the bbox of this geometry is cached, FALSE otherwise. see https://postgis.net/docs/PostGIS_HasBBox.html """ class ST_AddBand(GenericFunction): """ Returns a raster with the new band(s) of given type added with given initial value in the given index location. If no index is specified, the band is added to the end. see https://postgis.net/docs/RT_ST_AddBand.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_AsRaster(GenericFunction): """ Converts a PostGIS geometry to a PostGIS raster. see https://postgis.net/docs/RT_ST_AsRaster.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Band(GenericFunction): """ Returns one or more bands of an existing raster as a new raster. Useful for building new rasters from existing rasters. see https://postgis.net/docs/RT_ST_Band.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_MakeEmptyCoverage(GenericFunction): """ Cover georeferenced area with a grid of empty raster tiles. see https://postgis.net/docs/RT_ST_MakeEmptyCoverage.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_MakeEmptyRaster(GenericFunction): """ Returns an empty raster (having no bands) of given dimensions (width & height), upperleft X and Y, pixel size and rotation (scalex, scaley, skewx & skewy) and reference system (srid). If a raster is passed in, returns a new raster with the same size, alignment and SRID. If srid is left out, the spatial ref is set to unknown (0). see https://postgis.net/docs/RT_ST_MakeEmptyRaster.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Tile(GenericFunction): """ Returns a set of rasters resulting from the split of the input raster based upon the desired dimensions of the output rasters. see https://postgis.net/docs/RT_ST_Tile.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Retile(GenericFunction): """ Return a set of configured tiles from an arbitrarily tiled raster coverage. see https://postgis.net/docs/RT_ST_Retile.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_FromGDALRaster(GenericFunction): """ Returns a raster from a supported GDAL raster file. see https://postgis.net/docs/RT_ST_FromGDALRaster.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_GeoReference(GenericFunction): """ Returns the georeference meta data in GDAL or ESRI format as commonly seen in a world file. Default is GDAL. see https://postgis.net/docs/RT_ST_GeoReference.html """ class ST_Height(GenericFunction): """ Returns the height of the raster in pixels. see https://postgis.net/docs/RT_ST_Height.html """ class ST_MetaData(GenericFunction): """ Returns basic meta data about a raster object such as pixel size, rotation (skew), upper, lower left, etc. see https://postgis.net/docs/RT_ST_MetaData.html """ class ST_NumBands(GenericFunction): """ Returns the number of bands in the raster object. see https://postgis.net/docs/RT_ST_NumBands.html """ class ST_PixelHeight(GenericFunction): """ Returns the pixel height in geometric units of the spatial reference system. see https://postgis.net/docs/RT_ST_PixelHeight.html """ class ST_PixelWidth(GenericFunction): """ Returns the pixel width in geometric units of the spatial reference system. see https://postgis.net/docs/RT_ST_PixelWidth.html """ class ST_ScaleX(GenericFunction): """ Returns the X component of the pixel width in units of coordinate reference system. see https://postgis.net/docs/RT_ST_ScaleX.html """ class ST_ScaleY(GenericFunction): """ Returns the Y component of the pixel height in units of coordinate reference system. see https://postgis.net/docs/RT_ST_ScaleY.html """ class ST_RasterToWorldCoord(GenericFunction): """ Returns the raster's upper left corner as geometric X and Y (longitude and latitude) given a column and row. Column and row starts at 1. see https://postgis.net/docs/RT_ST_RasterToWorldCoord.html """ class ST_RasterToWorldCoordX(GenericFunction): """ Returns the geometric X coordinate upper left of a raster, column and row. Numbering of columns and rows starts at 1. see https://postgis.net/docs/RT_ST_RasterToWorldCoordX.html """ class ST_RasterToWorldCoordY(GenericFunction): """ Returns the geometric Y coordinate upper left corner of a raster, column and row. Numbering of columns and rows starts at 1. see https://postgis.net/docs/RT_ST_RasterToWorldCoordY.html """ class ST_Rotation(GenericFunction): """ Returns the rotation of the raster in radian. see https://postgis.net/docs/RT_ST_Rotation.html """ class ST_SkewX(GenericFunction): """ Returns the georeference X skew (or rotation parameter). see https://postgis.net/docs/RT_ST_SkewX.html """ class ST_SkewY(GenericFunction): """ Returns the georeference Y skew (or rotation parameter). see https://postgis.net/docs/RT_ST_SkewY.html """ class ST_UpperLeftX(GenericFunction): """ Returns the upper left X coordinate of raster in projected spatial ref. see https://postgis.net/docs/RT_ST_UpperLeftX.html """ class ST_UpperLeftY(GenericFunction): """ Returns the upper left Y coordinate of raster in projected spatial ref. see https://postgis.net/docs/RT_ST_UpperLeftY.html """ class ST_Width(GenericFunction): """ Returns the width of the raster in pixels. see https://postgis.net/docs/RT_ST_Width.html """ class ST_WorldToRasterCoord(GenericFunction): """ Returns the upper left corner as column and row given geometric X and Y (longitude and latitude) or a point geometry expressed in the spatial reference coordinate system of the raster. see https://postgis.net/docs/RT_ST_WorldToRasterCoord.html """ class ST_WorldToRasterCoordX(GenericFunction): """ Returns the column in the raster of the point geometry (pt) or a X and Y world coordinate (xw, yw) represented in world spatial reference system of raster. see https://postgis.net/docs/RT_ST_WorldToRasterCoordX.html """ class ST_WorldToRasterCoordY(GenericFunction): """ Returns the row in the raster of the point geometry (pt) or a X and Y world coordinate (xw, yw) represented in world spatial reference system of raster. see https://postgis.net/docs/RT_ST_WorldToRasterCoordY.html """ class ST_BandMetaData(GenericFunction): """ Returns basic meta data for a specific raster band. band num 1 is assumed if none-specified. see https://postgis.net/docs/RT_ST_BandMetaData.html """ class ST_BandNoDataValue(GenericFunction): """ Returns the value in a given band that represents no data. If no band num 1 is assumed. see https://postgis.net/docs/RT_ST_BandNoDataValue.html """ class ST_BandIsNoData(GenericFunction): """ Returns true if the band is filled with only nodata values. see https://postgis.net/docs/RT_ST_BandIsNoData.html """ class ST_BandPath(GenericFunction): """ Returns system file path to a band stored in file system. If no bandnum specified, 1 is assumed. see https://postgis.net/docs/RT_ST_BandPath.html """ class ST_BandFileSize(GenericFunction): """ Returns the file size of a band stored in file system. If no bandnum specified, 1 is assumed. see https://postgis.net/docs/RT_ST_BandFileSize.html """ class ST_BandFileTimestamp(GenericFunction): """ Returns the file timestamp of a band stored in file system. If no bandnum specified, 1 is assumed. see https://postgis.net/docs/RT_ST_BandFileTimestamp.html """ class ST_BandPixelType(GenericFunction): """ Returns the type of pixel for given band. If no bandnum specified, 1 is assumed. see https://postgis.net/docs/RT_ST_BandPixelType.html """ class ST_MinPossibleValue(GenericFunction): """ Returns the minimum value this pixeltype can store. see https://postgis.net/docs/ST_MinPossibleValue.html """ class ST_HasNoBand(GenericFunction): """ Returns true if there is no band with given band number. If no band number is specified, then band number 1 is assumed. see https://postgis.net/docs/RT_ST_HasNoBand.html """ class ST_PixelAsPolygon(GenericFunction): """ Returns the polygon geometry that bounds the pixel for a particular row and column. see https://postgis.net/docs/RT_ST_PixelAsPolygon.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_PixelAsPolygons(GenericFunction): """ Returns the polygon geometry that bounds every pixel of a raster band along with the value, the X and the Y raster coordinates of each pixel. see https://postgis.net/docs/RT_ST_PixelAsPolygons.html """ class ST_PixelAsPoint(GenericFunction): """ Returns a point geometry of the pixel's upper-left corner. see https://postgis.net/docs/RT_ST_PixelAsPoint.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_PixelAsPoints(GenericFunction): """ Returns a point geometry for each pixel of a raster band along with the value, the X and the Y raster coordinates of each pixel. The coordinates of the point geometry are of the pixel's upper- left corner. see https://postgis.net/docs/RT_ST_PixelAsPoints.html """ class ST_PixelAsCentroid(GenericFunction): """ Returns the centroid (point geometry) of the area represented by a pixel. see https://postgis.net/docs/RT_ST_PixelAsCentroid.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_PixelAsCentroids(GenericFunction): """ Returns the centroid (point geometry) for each pixel of a raster band along with the value, the X and the Y raster coordinates of each pixel. The point geometry is the centroid of the area represented by a pixel. see https://postgis.net/docs/RT_ST_PixelAsCentroids.html """ class ST_Value(GenericFunction): """ Returns the value of a given band in a given columnx, rowy pixel or at a particular geometric point. Band numbers start at 1 and assumed to be 1 if not specified. If exclude_nodata_value is set to false, then all pixels include nodata pixels are considered to intersect and return value. If exclude_nodata_value is not passed in then reads it from metadata of raster. see https://postgis.net/docs/RT_ST_Value.html """ class ST_NearestValue(GenericFunction): """ Returns the nearest non-NODATA value of a given band's pixel specified by a columnx and rowy or a geometric point expressed in the same spatial reference coordinate system as the raster. see https://postgis.net/docs/RT_ST_NearestValue.html """ class ST_Neighborhood(GenericFunction): """ Returns a 2-D double precision array of the non-NODATA values around a given band's pixel specified by either a columnX and rowY or a geometric point expressed in the same spatial reference coordinate system as the raster. see https://postgis.net/docs/RT_ST_Neighborhood.html """ class ST_SetValue(GenericFunction): """ Returns modified raster resulting from setting the value of a given band in a given columnx, rowy pixel or the pixels that intersect a particular geometry. Band numbers start at 1 and assumed to be 1 if not specified. see https://postgis.net/docs/RT_ST_SetValue.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_SetValues(GenericFunction): """ Returns modified raster resulting from setting the values of a given band. see https://postgis.net/docs/RT_ST_SetValues.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_DumpValues(GenericFunction): """ Get the values of the specified band as a 2-dimension array. see https://postgis.net/docs/RT_ST_DumpValues.html """ class ST_PixelOfValue(GenericFunction): """ Get the columnx, rowy coordinates of the pixel whose value equals the search value. see https://postgis.net/docs/RT_ST_PixelOfValue.html """ class ST_SetGeoReference(GenericFunction): """ Set Georeference 6 georeference parameters in a single call. Numbers should be separated by white space. Accepts inputs in GDAL or ESRI format. Default is GDAL. see https://postgis.net/docs/RT_ST_SetGeoReference.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_SetRotation(GenericFunction): """ Set the rotation of the raster in radian. see https://postgis.net/docs/RT_ST_SetRotation.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_SetScale(GenericFunction): """ Sets the X and Y size of pixels in units of coordinate reference system. Number units/pixel width/height. see https://postgis.net/docs/RT_ST_SetScale.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_SetSkew(GenericFunction): """ Sets the georeference X and Y skew (or rotation parameter). If only one is passed in, sets X and Y to the same value. see https://postgis.net/docs/RT_ST_SetSkew.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_SetUpperLeft(GenericFunction): """ Sets the value of the upper left corner of the pixel of the raster to projected X and Y coordinates. see https://postgis.net/docs/RT_ST_SetUpperLeft.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Resample(GenericFunction): """ Resample a raster using a specified resampling algorithm, new dimensions, an arbitrary grid corner and a set of raster georeferencing attributes defined or borrowed from another raster. see https://postgis.net/docs/RT_ST_Resample.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Rescale(GenericFunction): """ Resample a raster by adjusting only its scale (or pixel size). New pixel values are computed using the NearestNeighbor (english or american spelling), Bilinear, Cubic, CubicSpline or Lanczos resampling algorithm. Default is NearestNeighbor. see https://postgis.net/docs/RT_ST_Rescale.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Reskew(GenericFunction): """ Resample a raster by adjusting only its skew (or rotation parameters). New pixel values are computed using the NearestNeighbor (english or american spelling), Bilinear, Cubic, CubicSpline or Lanczos resampling algorithm. Default is NearestNeighbor. see https://postgis.net/docs/RT_ST_Reskew.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Resize(GenericFunction): """ Resize a raster to a new width/height see https://postgis.net/docs/RT_ST_Resize.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_SetBandNoDataValue(GenericFunction): """ Sets the value for the given band that represents no data. Band 1 is assumed if no band is specified. To mark a band as having no nodata value, set the nodata value = NULL. see https://postgis.net/docs/RT_ST_SetBandNoDataValue.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_SetBandIsNoData(GenericFunction): """ Sets the isnodata flag of the band to TRUE. see https://postgis.net/docs/RT_ST_SetBandIsNoData.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_SetBandPath(GenericFunction): """ Update the external path and band number of an out-db band see https://postgis.net/docs/RT_ST_SetBandPath.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_SetBandIndex(GenericFunction): """ Update the external band number of an out-db band see https://postgis.net/docs/RT_ST_SetBandIndex.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Count(GenericFunction): """ Returns the number of pixels in a given band of a raster or raster coverage. If no band is specified defaults to band 1. If exclude_nodata_value is set to true, will only count pixels that are not equal to the nodata value. see https://postgis.net/docs/RT_ST_Count.html """ class ST_CountAgg(GenericFunction): """ Aggregate. Returns the number of pixels in a given band of a set of rasters. If no band is specified defaults to band 1. If exclude_nodata_value is set to true, will only count pixels that are not equal to the NODATA value. see https://postgis.net/docs/RT_ST_CountAgg.html """ class ST_Histogram(GenericFunction): """ Returns a set of record summarizing a raster or raster coverage data distribution separate bin ranges. Number of bins are autocomputed if not specified. see https://postgis.net/docs/RT_ST_Histogram.html """ class ST_Quantile(GenericFunction): """ Compute quantiles for a raster or raster table coverage in the context of the sample or population. Thus, a value could be examined to be at the raster's 25%, 50%, 75% percentile. see https://postgis.net/docs/RT_ST_Quantile.html """ class ST_SummaryStats(GenericFunction): """ Returns summarystats consisting of count, sum, mean, stddev, min, max for a given raster band of a raster or raster coverage. Band 1 is assumed is no band is specified. see https://postgis.net/docs/RT_ST_SummaryStats.html """ class ST_SummaryStatsAgg(GenericFunction): """ Aggregate. Returns summarystats consisting of count, sum, mean, stddev, min, max for a given raster band of a set of raster. Band 1 is assumed is no band is specified. see https://postgis.net/docs/RT_ST_SummaryStatsAgg.html Return type: :class:`geoalchemy2.types.SummaryStats`. """ type = geoalchemy2.types.SummaryStats() class ST_ValueCount(GenericFunction): """ Returns a set of records containing a pixel band value and count of the number of pixels in a given band of a raster (or a raster coverage) that have a given set of values. If no band is specified defaults to band 1. By default nodata value pixels are not counted. and all other values in the pixel are output and pixel band values are rounded to the nearest integer. see https://postgis.net/docs/RT_ST_ValueCount.html """ class ST_RastFromWKB(GenericFunction): """ Return a raster value from a Well-Known Binary (WKB) raster. see https://postgis.net/docs/RT_ST_RastFromWKB.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_RastFromHexWKB(GenericFunction): """ Return a raster value from a Hex representation of Well-Known Binary (WKB) raster. see https://postgis.net/docs/RT_ST_RastFromHexWKB.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_AsWKB(GenericFunction): """ Return the Well-Known Binary (WKB) representation of the raster. see https://postgis.net/docs/RT_ST_AsBinary.html """ class ST_AsHexWKB(GenericFunction): """ Return the Well-Known Binary (WKB) in Hex representation of the raster. see https://postgis.net/docs/RT_ST_AsHexWKB.html """ class ST_AsGDALRaster(GenericFunction): """ Return the raster tile in the designated GDAL Raster format. Raster formats are one of those supported by your compiled library. Use ST_GDALDrivers() to get a list of formats supported by your library. see https://postgis.net/docs/RT_ST_AsGDALRaster.html """ class ST_AsJPEG(GenericFunction): """ Return the raster tile selected bands as a single Joint Photographic Exports Group (JPEG) image (byte array). If no band is specified and 1 or more than 3 bands, then only the first band is used. If only 3 bands then all 3 bands are used and mapped to RGB. see https://postgis.net/docs/RT_ST_AsJPEG.html """ class ST_AsPNG(GenericFunction): """ Return the raster tile selected bands as a single portable network graphics (PNG) image (byte array). If 1, 3, or 4 bands in raster and no bands are specified, then all bands are used. If more 2 or more than 4 bands and no bands specified, then only band 1 is used. Bands are mapped to RGB or RGBA space. see https://postgis.net/docs/RT_ST_AsPNG.html """ class ST_AsTIFF(GenericFunction): """ Return the raster selected bands as a single TIFF image (byte array). If no band is specified or any of specified bands does not exist in the raster, then will try to use all bands. see https://postgis.net/docs/RT_ST_AsTIFF.html """ class ST_Clip(GenericFunction): """ Returns the raster clipped by the input geometry. If band number not is specified, all bands are processed. If crop is not specified or TRUE, the output raster is cropped. see https://postgis.net/docs/RT_ST_Clip.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_ColorMap(GenericFunction): """ Creates a new raster of up to four 8BUI bands (grayscale, RGB, RGBA) from the source raster and a specified band. Band 1 is assumed if not specified. see https://postgis.net/docs/RT_ST_ColorMap.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Grayscale(GenericFunction): """ Creates a new one-8BUI band raster from the source raster and specified bands representing Red, Green and Blue see https://postgis.net/docs/RT_ST_Grayscale.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_MapAlgebra(GenericFunction): """ [raster] Callback function version - Returns a one-band raster given one or more input rasters, band indexes and one user-specified callback function. OR [raster] Expression version - Returns a one-band raster given one or two input rasters, band indexes and one or more user-specified SQL expressions. see https://postgis.net/docs/RT_ST_MapAlgebra.html """ class ST_MapAlgebraExpr(GenericFunction): """ [raster] 1 raster band version: Creates a new one band raster formed by applying a valid PostgreSQL algebraic operation on the input raster band and of pixeltype provided. Band 1 is assumed if no band is specified. OR [raster] 2 raster band version: Creates a new one band raster formed by applying a valid PostgreSQL algebraic operation on the two input raster bands and of pixeltype provided. band 1 of each raster is assumed if no band numbers are specified. The resulting raster will be aligned (scale, skew and pixel corners) on the grid defined by the first raster and have its extent defined by the "extenttype" parameter. Values for "extenttype" can be: INTERSECTION, UNION, FIRST, SECOND. see https://postgis.net/docs/RT_ST_MapAlgebraExpr.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_MapAlgebraFct(GenericFunction): """ [raster] 1 band version - Creates a new one band raster formed by applying a valid PostgreSQL function on the input raster band and of pixeltype provided. Band 1 is assumed if no band is specified. OR [raster] 2 band version - Creates a new one band raster formed by applying a valid PostgreSQL function on the 2 input raster bands and of pixeltype provided. Band 1 is assumed if no band is specified. Extent type defaults to INTERSECTION if not specified. see https://postgis.net/docs/RT_ST_MapAlgebraFct.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_MapAlgebraFctNgb(GenericFunction): """ 1-band version: Map Algebra Nearest Neighbor using user-defined PostgreSQL function. Return a raster which values are the result of a PLPGSQL user function involving a neighborhood of values from the input raster band. see https://postgis.net/docs/RT_ST_MapAlgebraFctNgb.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Reclass(GenericFunction): """ Creates a new raster composed of band types reclassified from original. The nband is the band to be changed. If nband is not specified assumed to be 1. All other bands are returned unchanged. Use case: convert a 16BUI band to a 8BUI and so forth for simpler rendering as viewable formats. see https://postgis.net/docs/RT_ST_Reclass.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Distinct4ma(GenericFunction): """ Raster processing function that calculates the number of unique pixel values in a neighborhood. see https://postgis.net/docs/RT_ST_Distinct4ma.html """ class ST_InvDistWeight4ma(GenericFunction): """ Raster processing function that interpolates a pixel's value from the pixel's neighborhood. see https://postgis.net/docs/RT_ST_InvDistWeight4ma.html """ class ST_Max4ma(GenericFunction): """ Raster processing function that calculates the maximum pixel value in a neighborhood. see https://postgis.net/docs/RT_ST_Max4ma.html """ class ST_Mean4ma(GenericFunction): """ Raster processing function that calculates the mean pixel value in a neighborhood. see https://postgis.net/docs/RT_ST_Mean4ma.html """ class ST_Min4ma(GenericFunction): """ Raster processing function that calculates the minimum pixel value in a neighborhood. see https://postgis.net/docs/RT_ST_Min4ma.html """ class ST_MinDist4ma(GenericFunction): """ Raster processing function that returns the minimum distance (in number of pixels) between the pixel of interest and a neighboring pixel with value. see https://postgis.net/docs/RT_ST_MinDist4ma.html """ class ST_Range4ma(GenericFunction): """ Raster processing function that calculates the range of pixel values in a neighborhood. see https://postgis.net/docs/RT_ST_Range4ma.html """ class ST_StdDev4ma(GenericFunction): """ Raster processing function that calculates the standard deviation of pixel values in a neighborhood. see https://postgis.net/docs/RT_ST_StdDev4ma.html """ class ST_Sum4ma(GenericFunction): """ Raster processing function that calculates the sum of all pixel values in a neighborhood. see https://postgis.net/docs/RT_ST_Sum4ma.html """ class ST_Aspect(GenericFunction): """ Returns the aspect (in degrees by default) of an elevation raster band. Useful for analyzing terrain. see https://postgis.net/docs/RT_ST_Aspect.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_HillShade(GenericFunction): """ Returns the hypothetical illumination of an elevation raster band using provided azimuth, altitude, brightness and scale inputs. see https://postgis.net/docs/RT_ST_HillShade.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Roughness(GenericFunction): """ Returns a raster with the calculated "roughness" of a DEM. see https://postgis.net/docs/RT_ST_Roughness.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_Slope(GenericFunction): """ Returns the slope (in degrees by default) of an elevation raster band. Useful for analyzing terrain. see https://postgis.net/docs/RT_ST_Slope.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_TPI(GenericFunction): """ Returns a raster with the calculated Topographic Position Index. see https://postgis.net/docs/RT_ST_TPI.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_TRI(GenericFunction): """ Returns a raster with the calculated Terrain Ruggedness Index. see https://postgis.net/docs/RT_ST_TRI.html Return type: :class:`geoalchemy2.types.Raster`. """ type = geoalchemy2.types.Raster() class ST_DumpAsPolygons(GenericFunction): """ Returns a set of geomval (geom,val) rows, from a given raster band. If no band number is specified, band num defaults to 1. see https://postgis.net/docs/RT_ST_DumpAsPolygons.html """ class ST_MinConvexHull(GenericFunction): """ Return the convex hull geometry of the raster excluding NODATA pixels. see https://postgis.net/docs/RT_ST_MinConvexHull.html Return type: :class:`geoalchemy2.types.Geometry`. """ type = geoalchemy2.types.Geometry() class ST_SameAlignment(GenericFunction): """ Returns true if rasters have same skew, scale, spatial ref, and offset (pixels can be put on same grid without cutting into pixels) and false if they don't with notice detailing issue. see https://postgis.net/docs/RT_ST_SameAlignment.html """ class ST_NotSameAlignmentReason(GenericFunction): """ Returns text stating if rasters are aligned and if not aligned, a reason why. see https://postgis.net/docs/RT_ST_NotSameAlignmentReason.html """ class ST_Distance_Sphere(GenericFunction): """ Returns minimum distance in meters between two lon/lat geometries. Uses a spherical earth and radius of 6370986 meters. Faster than ``ST_Distance_Spheroid``, but less accurate. PostGIS versions prior to 1.5 only implemented for points. see https://postgis.net/docs/ST_Distance_Sphere.html """ geoalchemy2-0.15.2/geoalchemy2/py.typed000066400000000000000000000000001464355170400176730ustar00rootroot00000000000000geoalchemy2-0.15.2/geoalchemy2/shape.py000066400000000000000000000053501464355170400176630ustar00rootroot00000000000000"""This module provides utility functions for integrating with Shapely. .. note:: As GeoAlchemy 2 itself has no dependency on `Shapely`, applications using functions of this module have to ensure that `Shapely` is available. """ from contextlib import contextmanager from typing import List from typing import Optional from typing import Union try: import shapely.wkb import shapely.wkt from shapely.wkb import dumps HAS_SHAPELY = True _shapely_exc = None except ImportError as exc: HAS_SHAPELY = False _shapely_exc = exc from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement @contextmanager def check_shapely(): if not HAS_SHAPELY: raise ImportError( "This feature needs the optional Shapely dependency. " "Please install it with 'pip install geoalchemy2[shapely]'." ) from _shapely_exc yield @check_shapely() def to_shape(element: Union[WKBElement, WKTElement]): """Function to convert a :class:`geoalchemy2.types.SpatialElement` to a Shapely geometry. Args: element: The element to convert into a ``Shapely`` object. Example:: lake = Session.query(Lake).get(1) polygon = to_shape(lake.geom) """ assert isinstance(element, (WKBElement, WKTElement)) if isinstance(element, WKBElement): data, hex = ( (element.data, True) if isinstance(element.data, str) else (bytes(element.data), False) ) return shapely.wkb.loads(data, hex=hex) elif isinstance(element, WKTElement): if element.extended: return shapely.wkt.loads(element.data.split(";", 1)[1]) else: return shapely.wkt.loads(element.data) else: raise TypeError("Only WKBElement and WKTElement objects are supported") @check_shapely() def from_shape(shape, srid: int = -1, extended: Optional[bool] = False) -> WKBElement: """Function to convert a Shapely geometry to a :class:`geoalchemy2.types.WKBElement`. Args: shape: The shape to convert. srid: An integer representing the spatial reference system. E.g. ``4326``. Default value is ``-1``, which means no/unknown reference system. extended: A boolean to switch between WKB and EWKB. Default value is False. Example:: from shapely.geometry import Point wkb_element = from_shape(Point(5, 45), srid=4326) ewkb_element = from_shape(Point(5, 45), srid=4326, extended=True) """ return WKBElement( memoryview(dumps(shape, srid=srid if extended else None)), srid=srid, extended=extended, ) __all__: List[str] = [ "from_shape", "to_shape", ] def __dir__() -> List[str]: return __all__ geoalchemy2-0.15.2/geoalchemy2/types/000077500000000000000000000000001464355170400173525ustar00rootroot00000000000000geoalchemy2-0.15.2/geoalchemy2/types/__init__.py000066400000000000000000000335521464355170400214730ustar00rootroot00000000000000"""This module defines the Column types. The :class:`geoalchemy2.types.Geometry`, :class:`geoalchemy2.types.Geography`, and :class:`geoalchemy2.types.Raster` classes are used when defining geometry, geography and raster columns/properties in models. """ import warnings from typing import Any from typing import Dict from typing import Optional from sqlalchemy.dialects import postgresql from sqlalchemy.dialects.postgresql.base import ischema_names as _postgresql_ischema_names from sqlalchemy.dialects.sqlite.base import ischema_names as _sqlite_ischema_names from sqlalchemy.ext.compiler import compiles from sqlalchemy.sql import func from sqlalchemy.types import Float from sqlalchemy.types import Integer from sqlalchemy.types import UserDefinedType try: # SQLAlchemy >= 2 from sqlalchemy.sql._typing import _TypeEngineArgument except ImportError: # SQLAlchemy < 2 _TypeEngineArgument = Any # type: ignore from geoalchemy2.comparator import BaseComparator from geoalchemy2.comparator import Comparator from geoalchemy2.elements import CompositeElement from geoalchemy2.elements import RasterElement from geoalchemy2.elements import WKBElement from geoalchemy2.exc import ArgumentError from geoalchemy2.types import dialects def select_dialect(dialect_name): """Select the dialect from its name.""" known_dialects = { "geopackage": dialects.geopackage, "mysql": dialects.mysql, "mariadb": dialects.mysql, "postgresql": dialects.postgresql, "sqlite": dialects.sqlite, } return known_dialects.get(dialect_name, dialects.common) class _GISType(UserDefinedType): """The base class for spatial types. This class defines ``bind_expression`` and ``column_expression`` methods that wrap column expressions in ``ST_GeomFromEWKT``, ``ST_GeogFromText``, or ``ST_AsEWKB`` calls. This class also defines ``result_processor`` and ``bind_processor`` methods. The function returned by ``result_processor`` converts WKB values received from the database to :class:`geoalchemy2.elements.WKBElement` objects. The function returned by ``bind_processor`` converts :class:`geoalchemy2.elements.WKTElement` objects to EWKT strings. Args: geometry_type: The geometry type. Possible values are: * ``"GEOMETRY"``, * ``"POINT"``, * ``"LINESTRING"``, * ``"POLYGON"``, * ``"MULTIPOINT"``, * ``"MULTILINESTRING"``, * ``"MULTIPOLYGON"``, * ``"GEOMETRYCOLLECTION"``, * ``"CURVE"``, * ``None``. The latter is actually not supported with :class:`geoalchemy2.types.Geography`. When set to ``None`` then no "geometry type" constraints will be attached to the geometry type declaration. Default is ``"GEOMETRY"``. srid: The SRID for this column. E.g. 4326. Default is ``-1``. dimension: The dimension of the geometry. Default is ``2``. spatial_index: Indicate if a spatial index should be created. Default is ``True``. use_N_D_index: Use the N-D index instead of the standard 2-D index. use_typmod: By default PostgreSQL type modifiers are used to create the geometry column. To use check constraints instead set ``use_typmod`` to ``False``. By default this option is not included in the call to ``AddGeometryColumn``. Note that this option is only available for PostGIS 2.x. """ name: Optional[str] = None """ Name used for defining the main geo type (geometry or geography) in CREATE TABLE statements. Set in subclasses. """ from_text: Optional[str] = None """ The name of "from text" function for this type. Set in subclasses. """ as_binary: Optional[str] = None """ The name of the "as binary" function for this type. Set in subclasses. """ comparator_factory: Any = Comparator """ This is the way by which spatial operators are defined for geometry/geography columns. """ cache_ok = False """ Disable cache for this type. """ def __init__( self, geometry_type: Optional[str] = "GEOMETRY", srid=-1, dimension=2, spatial_index=True, use_N_D_index=False, use_typmod: Optional[bool] = None, from_text: Optional[str] = None, name: Optional[str] = None, nullable=True, _spatial_index_reflected=None, ) -> None: geometry_type, srid = self.check_ctor_args( geometry_type, srid, dimension, use_typmod, nullable ) self.geometry_type = geometry_type self.srid = srid if name is not None: self.name = name if from_text is not None: self.from_text = from_text self.dimension = dimension self.spatial_index = spatial_index self.use_N_D_index = use_N_D_index self.use_typmod = use_typmod self.extended: Optional[bool] = self.as_binary == "ST_AsEWKB" self.nullable = nullable self._spatial_index_reflected = _spatial_index_reflected def get_col_spec(self): if not self.geometry_type: return self.name return "%s(%s,%d)" % (self.name, self.geometry_type, self.srid) def column_expression(self, col): """Specific column_expression that automatically adds a conversion function.""" return getattr(func, self.as_binary)(col, type_=self) def result_processor(self, dialect, coltype): """Specific result_processor that automatically process spatial elements.""" def process(value): if value is not None: kwargs = {} if self.srid > 0: kwargs["srid"] = self.srid if self.extended is not None and dialect.name not in ["mysql", "mariadb"]: kwargs["extended"] = self.extended return self.ElementType(value, **kwargs) return process def bind_expression(self, bindvalue): """Specific bind_expression that automatically adds a conversion function.""" return getattr(func, self.from_text)(bindvalue, type_=self) def bind_processor(self, dialect): """Specific bind_processor that automatically process spatial elements.""" def process(bindvalue): return select_dialect(dialect.name).bind_processor_process(self, bindvalue) return process @staticmethod def check_ctor_args(geometry_type, srid, dimension, use_typmod, nullable): try: # passing default SRID if it is NULL from DB srid = int(srid if srid is not None else -1) except (ValueError, TypeError): raise ArgumentError("srid must be convertible to an integer") if geometry_type: geometry_type = geometry_type.upper() elif srid > 0: warnings.warn("srid not enforced when geometry_type is None") if use_typmod is not None and not nullable: raise ArgumentError( 'The "nullable" and "use_typmod" arguments can not be used together' ) return geometry_type, srid @compiles(_GISType, "mariadb") @compiles(_GISType, "mysql") def get_col_spec(self, *args, **kwargs): if self.geometry_type is not None: spec = "%s" % self.geometry_type else: spec = "GEOMETRY" if not self.nullable or self.spatial_index: spec += " NOT NULL" if self.srid > 0: spec += " SRID %d" % self.srid return spec class Geometry(_GISType): """The Geometry type. Creating a geometry column is done like this:: Column(Geometry(geometry_type='POINT', srid=4326)) See :class:`geoalchemy2.types._GISType` for the list of arguments that can be passed to the constructor. If ``srid`` is set then the ``WKBElement`` objects resulting from queries will have that SRID, and, when constructing the ``WKBElement`` objects, the SRID won't be read from the data returned by the database. If ``srid`` is not set (meaning it's ``-1``) then the SRID set in ``WKBElement`` objects will be read from the data returned by the database. """ name = "geometry" """ Type name used for defining geometry columns in ``CREATE TABLE``. """ from_text = "ST_GeomFromEWKT" """ The "from text" geometry constructor. Used by the parent class' ``bind_expression`` method. """ as_binary = "ST_AsEWKB" """ The "as binary" function to use. Used by the parent class' ``column_expression`` method. """ ElementType = WKBElement """ The element class to use. Used by the parent class' ``result_processor`` method. """ cache_ok = False """ Disable cache for this type. """ class Geography(_GISType): """The Geography type. Creating a geography column is done like this:: Column(Geography(geometry_type='POINT', srid=4326)) See :class:`geoalchemy2.types._GISType` for the list of arguments that can be passed to the constructor. """ name = "geography" """ Type name used for defining geography columns in ``CREATE TABLE``. """ from_text = "ST_GeogFromText" """ The ``FromText`` geography constructor. Used by the parent class' ``bind_expression`` method. """ as_binary = "ST_AsBinary" """ The "as binary" function to use. Used by the parent class' ``column_expression`` method. """ ElementType = WKBElement """ The element class to use. Used by the parent class' ``result_processor`` method. """ cache_ok = False """ Disable cache for this type. """ class Raster(_GISType): """The Raster column type. Creating a raster column is done like this:: Column(Raster) This class defines the ``result_processor`` method, so that raster values received from the database are converted to :class:`geoalchemy2.elements.RasterElement` objects. Args: spatial_index: Indicate if a spatial index should be created. Default is ``True``. """ comparator_factory = BaseComparator """ This is the way by which spatial operators and functions are defined for raster columns. """ name = "raster" """ Type name used for defining raster columns in ``CREATE TABLE``. """ from_text = "raster" """ The "from text" raster constructor. Used by the parent class' ``bind_expression`` method. """ as_binary = "raster" """ The "as binary" function to use. Used by the parent class' ``column_expression`` method. """ ElementType = RasterElement """ The element class to use. Used by the parent class' ``result_processor`` method. """ cache_ok = False """ Disable cache for this type. """ def __init__(self, spatial_index=True, from_text=None, name=None, nullable=True) -> None: # Enforce default values super(Raster, self).__init__( geometry_type=None, srid=-1, dimension=2, spatial_index=spatial_index, use_N_D_index=False, use_typmod=False, from_text=from_text, name=name, nullable=nullable, ) self.extended = None @staticmethod def check_ctor_args(*args, **kwargs): return None, -1 class _DummyGeometry(Geometry): """A dummy type only used with SQLite.""" def get_col_spec(self): return self.geometry_type or "GEOMETRY" class CompositeType(UserDefinedType): """A composite type used by some spatial functions. A wrapper for :class:`geoalchemy2.elements.CompositeElement`, that can be used as the return type in PostgreSQL functions that return composite values. This is used as the base class of :class:`geoalchemy2.types.GeometryDump`. """ typemap: Dict[str, _TypeEngineArgument] = {} """ Dictionary used for defining the content types and their corresponding keys. Set in subclasses. """ class comparator_factory(UserDefinedType.Comparator): def __getattr__(self, key): try: type_ = self.type.typemap[key] except KeyError: raise AttributeError("Type '%s' doesn't have an attribute: '%s'" % (self.type, key)) return CompositeElement(self.expr, key, type_) class GeometryDump(CompositeType): """The return type for functions like ``ST_Dump``. The type consists in a path and a geom field. You should normally never use this class directly. """ typemap = {"path": postgresql.ARRAY(Integer), "geom": Geometry} """ Dictionary defining the contents of a ``geometry_dump``. """ cache_ok = True """ Enable cache for this type. """ # Register Geometry, Geography and Raster to SQLAlchemy's reflection subsystems. _postgresql_ischema_names["geometry"] = Geometry _postgresql_ischema_names["geography"] = Geography _postgresql_ischema_names["raster"] = Raster _sqlite_ischema_names["GEOMETRY"] = Geometry _sqlite_ischema_names["POINT"] = Geometry _sqlite_ischema_names["LINESTRING"] = Geometry _sqlite_ischema_names["POLYGON"] = Geometry _sqlite_ischema_names["MULTIPOINT"] = Geometry _sqlite_ischema_names["MULTILINESTRING"] = Geometry _sqlite_ischema_names["MULTIPOLYGON"] = Geometry _sqlite_ischema_names["CURVE"] = Geometry _sqlite_ischema_names["GEOMETRYCOLLECTION"] = Geometry _sqlite_ischema_names["RASTER"] = Raster class SummaryStats(CompositeType): """Define the composite type returned by the function ST_SummaryStatsAgg.""" typemap = { "count": Integer, "sum": Float, "mean": Float, "stddev": Float, "min": Float, "max": Float, } cache_ok = True """ Enable cache for this type. """ __all__ = [ "_GISType", "CompositeType", "Geography", "Geometry", "GeometryDump", "Raster", "SummaryStats", "dialects", "select_dialect", ] def __dir__(): return __all__ geoalchemy2-0.15.2/geoalchemy2/types/dialects/000077500000000000000000000000001464355170400211425ustar00rootroot00000000000000geoalchemy2-0.15.2/geoalchemy2/types/dialects/__init__.py000066400000000000000000000005471464355170400232610ustar00rootroot00000000000000"""This module defines some dialect-specific functions used for Column types.""" from geoalchemy2.types.dialects import common # noqa from geoalchemy2.types.dialects import geopackage # noqa from geoalchemy2.types.dialects import mysql # noqa from geoalchemy2.types.dialects import postgresql # noqa from geoalchemy2.types.dialects import sqlite # noqa geoalchemy2-0.15.2/geoalchemy2/types/dialects/common.py000066400000000000000000000002121464355170400227770ustar00rootroot00000000000000"""This module defines functions used by several dialects.""" def bind_processor_process(spatial_type, bindvalue): return bindvalue geoalchemy2-0.15.2/geoalchemy2/types/dialects/geopackage.py000066400000000000000000000002231464355170400235770ustar00rootroot00000000000000"""This module defines specific functions for GeoPackage dialect.""" from geoalchemy2.types.dialects.sqlite import bind_processor_process # noqa geoalchemy2-0.15.2/geoalchemy2/types/dialects/mysql.py000066400000000000000000000033761464355170400226720ustar00rootroot00000000000000"""This module defines specific functions for MySQL dialect.""" from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement from geoalchemy2.elements import _SpatialElement from geoalchemy2.exc import ArgumentError from geoalchemy2.shape import to_shape def bind_processor_process(spatial_type, bindvalue): if isinstance(bindvalue, str): wkt_match = WKTElement._REMOVE_SRID.match(bindvalue) srid = wkt_match.group(2) try: if srid is not None: srid = int(srid) except (ValueError, TypeError): # pragma: no cover raise ArgumentError( f"The SRID ({srid}) of the supplied value can not be casted to integer" ) if srid is not None and srid != spatial_type.srid: raise ArgumentError( f"The SRID ({srid}) of the supplied value is different " f"from the one of the column ({spatial_type.srid})" ) return wkt_match.group(3) if ( isinstance(bindvalue, _SpatialElement) and bindvalue.srid != -1 and bindvalue.srid != spatial_type.srid ): raise ArgumentError( f"The SRID ({bindvalue.srid}) of the supplied value is different " f"from the one of the column ({spatial_type.srid})" ) if isinstance(bindvalue, WKTElement): bindvalue = bindvalue.as_wkt() if bindvalue.srid == -1: bindvalue.srid = spatial_type.srid return bindvalue elif isinstance(bindvalue, WKBElement): if "wkb" not in spatial_type.from_text.lower(): # With MySQL we use Shapely to convert the WKBElement to an EWKT string return to_shape(bindvalue).wkt return bindvalue geoalchemy2-0.15.2/geoalchemy2/types/dialects/postgresql.py000066400000000000000000000021351464355170400237200ustar00rootroot00000000000000"""This module defines specific functions for Postgresql dialect.""" from geoalchemy2.elements import RasterElement from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement from geoalchemy2.shape import to_shape def bind_processor_process(spatial_type, bindvalue): if isinstance(bindvalue, WKTElement): if bindvalue.extended: return "%s" % (bindvalue.data) else: return "SRID=%d;%s" % (bindvalue.srid, bindvalue.data) elif isinstance(bindvalue, WKBElement): if not bindvalue.extended: # When the WKBElement includes a WKB value rather # than a EWKB value we use Shapely to convert the WKBElement to an # EWKT string shape = to_shape(bindvalue) return "SRID=%d;%s" % (bindvalue.srid, shape.wkt) else: # PostGIS ST_GeomFromEWKT works with EWKT strings as well # as EWKB hex strings return bindvalue.desc elif isinstance(bindvalue, RasterElement): return "%s" % (bindvalue.data) else: return bindvalue geoalchemy2-0.15.2/geoalchemy2/types/dialects/sqlite.py000066400000000000000000000040701464355170400230160ustar00rootroot00000000000000"""This module defines specific functions for SQLite dialect.""" import re import warnings from geoalchemy2.elements import RasterElement from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement from geoalchemy2.shape import to_shape def format_geom_type(wkt, default_srid=None): """Format the Geometry type for SQLite.""" match = re.match(WKTElement.SPLIT_WKT_PATTERN, wkt) if match is None: warnings.warn( "The given WKT could not be parsed by GeoAlchemy2, this could lead to undefined " f"behavior with Z, M or ZM geometries or with incorrect SRID. The WKT string is: {wkt}" ) return wkt _, srid, geom_type, coords = match.groups() geom_type = geom_type.replace(" ", "") if geom_type.endswith("ZM"): geom_type = geom_type[:-2] elif geom_type.endswith("Z"): geom_type = geom_type[:-1] if srid is None and default_srid is not None: srid = f"SRID={default_srid}" if srid is not None: return "%s;%s%s" % (srid, geom_type, coords) else: return "%s%s" % (geom_type, coords) def bind_processor_process(spatial_type, bindvalue): if isinstance(bindvalue, WKTElement): return format_geom_type( bindvalue.data, default_srid=bindvalue.srid if bindvalue.srid >= 0 else spatial_type.srid, ) elif isinstance(bindvalue, WKBElement): # With SpatiaLite we use Shapely to convert the WKBElement to an EWKT string shape = to_shape(bindvalue) # shapely.wkb.loads returns geom_type with a 'Z', for example, 'LINESTRING Z' # which is a limitation with SpatiaLite. Hence, a temporary fix. res = format_geom_type( shape.wkt, default_srid=bindvalue.srid if bindvalue.srid >= 0 else spatial_type.srid ) return res elif isinstance(bindvalue, RasterElement): return "%s" % (bindvalue.data) elif isinstance(bindvalue, str): return format_geom_type(bindvalue, default_srid=spatial_type.srid) else: return bindvalue geoalchemy2-0.15.2/geoalchemy2/utils.py000066400000000000000000000007501464355170400177220ustar00rootroot00000000000000"""Some utils for the GeoAlchemy2 package.""" def authorized_values_in_docstring(**kwargs): """Decorator to replace keywords in docstrings by the actual value of a variable. .. Note:: The keyword must be enclose by <> in the docstring, like . """ def inner(func): if func.__doc__ is not None: for k, v in kwargs.items(): func.__doc__ = func.__doc__.replace(f"<{k}>", str(v)) return func return inner geoalchemy2-0.15.2/pyproject.toml000066400000000000000000000014251464355170400167250ustar00rootroot00000000000000# SETUPTOOLS [build-system] requires = [ "setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4", ] # BLACK [tool.black] line-length = 100 target-version = [ "py37", "py38", "py39", "py310", "py311", "py312", ] # PYDOCSTYLE [tool.pydocstyle] # ignore the following: # - D107: Missing docstring in __init__ add-ignore = [ "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", ] convention = "google" # ISORT [tool.isort] profile = "black" line_length = 100 force_single_line = true # PYTEST [tool.pytest.ini_options] testpaths = [ "tests", ] # MYPY [[tool.mypy.overrides]] module = [ "importlib.*", "psycopg2cffi", "rasterio", "shapely", "shapely.*" ] ignore_missing_imports = true geoalchemy2-0.15.2/requirements-doc.txt000066400000000000000000000001461464355170400200370ustar00rootroot00000000000000# Additional requirements for building the documentation alembic shapely sphinx sphinx-gallery pillow geoalchemy2-0.15.2/requirements-mypy.txt000066400000000000000000000000211464355170400202600ustar00rootroot00000000000000types-setuptools geoalchemy2-0.15.2/requirements-rtd.txt000066400000000000000000000000731464355170400200620ustar00rootroot00000000000000# Install geoalchemy2 package -e . -r requirements-doc.txt geoalchemy2-0.15.2/requirements.txt000066400000000000000000000002141464355170400172700ustar00rootroot00000000000000# Additional requirements for running the testsuite and development alembic flake8 mysql pytest pytest-cov pytest-html pytest-mypy rasterio geoalchemy2-0.15.2/setup.py000066400000000000000000000030171464355170400155220ustar00rootroot00000000000000from setuptools import find_namespace_packages from setuptools import setup setup( name="GeoAlchemy2", use_scm_version=True, description="Using SQLAlchemy with Spatial Databases", long_description=open("README.rst", encoding="utf-8").read(), classifiers=[ "Development Status :: 4 - Beta", "Environment :: Plugins", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.7", "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", "Intended Audience :: Information Technology", "License :: OSI Approved :: MIT License", "Topic :: Scientific/Engineering :: GIS", ], keywords="geo,gis,sqlalchemy,orm", author="Eric Lemoine", author_email="eric.lemoine@gmail.com", url="https://geoalchemy-2.readthedocs.io/en/stable/", project_urls={ "Tracker": "https://github.com/geoalchemy/geoalchemy2/issues", "Source": "https://github.com/geoalchemy/geoalchemy2", }, license="MIT", python_requires=">=3.7", packages=find_namespace_packages(include=["geoalchemy2*"]), include_package_data=True, zip_safe=False, setup_requires=["setuptools_scm"], install_requires=["SQLAlchemy>=1.4", "packaging"], extras_require={ "shapely": ["Shapely>=1.7"], }, ) geoalchemy2-0.15.2/test_container/000077500000000000000000000000001464355170400170305ustar00rootroot00000000000000geoalchemy2-0.15.2/test_container/.dockerignore000066400000000000000000000000111464355170400214740ustar00rootroot00000000000000/output/ geoalchemy2-0.15.2/test_container/.gitignore000066400000000000000000000000111464355170400210100ustar00rootroot00000000000000/output/ geoalchemy2-0.15.2/test_container/Dockerfile000066400000000000000000000006561464355170400210310ustar00rootroot00000000000000FROM ubuntu:22.04 COPY ./helpers/install_requirements.sh / RUN /install_requirements.sh COPY ./helpers/init_postgres.sh / env PGDATA="/var/lib/postgresql/data" env POSTGRES_PATH="/usr/lib/postgresql/16" RUN su postgres -c /init_postgres.sh ENV SPATIALITE_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu/mod_spatialite.so" COPY ./helpers/init_mysql.sh / RUN /init_mysql.sh COPY ./helpers/entrypoint.sh / ENTRYPOINT ["/entrypoint.sh"] geoalchemy2-0.15.2/test_container/build.sh000077500000000000000000000002361464355170400204670ustar00rootroot00000000000000#!/usr/bin/env bash set -e SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) cd "${SCRIPT_DIR}" docker build -t geoalchemy2 . geoalchemy2-0.15.2/test_container/helpers/000077500000000000000000000000001464355170400204725ustar00rootroot00000000000000geoalchemy2-0.15.2/test_container/helpers/entrypoint.sh000077500000000000000000000027371464355170400232550ustar00rootroot00000000000000#!/usr/bin/env bash echo "starting postgres" (su postgres -c '${POSTGRES_PATH}/bin/pg_ctl start' > /dev/null 2>&1) & while ! ${POSTGRES_PATH}/bin/pg_isready --quiet; do sleep 0.2 done echo "starting mysql" /etc/init.d/mysql start echo "waiting for mysql to start" while ! mysqladmin ping -h 127.0.0.1 --silent; do sleep 0.2 done echo "###############################" echo "GeoAlchemy2 Test Container" echo "" echo 'run tests with `tox --workdir /output -vv`' echo "###############################" ############################### # workarounds to get the tests working while mounting the code in as read-only mkdir /geoalchemy2 find /geoalchemy2_read_only -mindepth 1 -maxdepth 1 | while read -r item; do ln -s "${item}" "/geoalchemy2/$(basename "${item}")" done cd /geoalchemy2 # remove links that would cause issues if they are present and read-only rm -f .mypy_cache .eggs *.egg-info .git .gitignore doc reports # copy these items instead of symlinking cp -r /geoalchemy2_read_only/doc /geoalchemy2/doc cp /geoalchemy2_read_only/.gitignore ./ # store reports in the output directory mkdir -p /output/reports ln -s /output/reports /geoalchemy2/reports # to allow pre-commit to run git config --global init.defaultBranch master git config --global user.email "user@example.com" git config --global user.name "user" git init > /dev/null git add --all git commit -m "dummy commit" > /dev/null export MYPY_CACHE_DIR=/output/.mypy_cache ############################### exec bash geoalchemy2-0.15.2/test_container/helpers/init_mysql.sh000077500000000000000000000007441464355170400232260ustar00rootroot00000000000000#!/usr/bin/env bash set -e if [ $(whoami) != "root" ]; then echo "must run as the root user" exit 1 fi /etc/init.d/mysql start echo "waiting for mysql to start" while ! mysqladmin ping -h 127.0.0.1 --silent; do sleep 0.2 done echo "Create the 'gis' role" mysql -e "CREATE USER 'gis'@'%' IDENTIFIED BY 'gis';" mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'gis'@'%' WITH GRANT OPTION;" echo "Create the 'gis' database" mysql -u gis --password=gis -e "CREATE DATABASE gis;" geoalchemy2-0.15.2/test_container/helpers/init_postgres.sh000077500000000000000000000017541464355170400237310ustar00rootroot00000000000000#!/usr/bin/env bash set -e if [ $(whoami) != "postgres" ]; then echo "must run as the postgres user" exit 1 fi ${POSTGRES_PATH}/bin/initdb --auth=trust --username=postgres -E 'UTF-8' # by default only listens on localhost so host cannot connect echo "listen_addresses = '0.0.0.0'" >> "${PGDATA}/postgresql.conf" (${POSTGRES_PATH}/bin/pg_ctl start > /dev/null 2>&1) & while ! ${POSTGRES_PATH}/bin/pg_isready --quiet; do sleep 0.2 done echo "Create the 'gis' role" psql -c "CREATE ROLE gis PASSWORD 'gis' SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;" echo "Create the 'gis' database" createdb -E UTF-8 gis psql -d gis -c 'CREATE SCHEMA gis;' psql -c 'GRANT CREATE ON DATABASE gis TO "gis";' psql -d gis -c 'GRANT USAGE,CREATE ON SCHEMA gis TO "gis";' echo "Enable PostGIS for the 'gis' database" psql -d gis -c "CREATE EXTENSION postgis;" echo "With PostGIS 3 enable PostGIS Raster as well" psql -d gis -c "CREATE EXTENSION postgis_raster;" ${POSTGRES_PATH}/bin/pg_ctl stop sleep 1 geoalchemy2-0.15.2/test_container/helpers/install_requirements.sh000077500000000000000000000027521464355170400253100ustar00rootroot00000000000000#!/usr/bin/env bash set -e # based on geoalchemy2/TEST.rst packages=( # for managing virtual environments tox git pypy3 pypy3-dev pypy3-venv python3.7 python3.7-dev python3.7-venv python3.8 python3.8-dev python3.8-venv python3.9 python3.9-dev python3.9-venv python3.10 python3.10-dev python3.10-venv python3.11 python3.11-dev python3.11-venv python3.12 python3.12-dev python3.12-venv # PostgreSQL and PostGIS postgresql-16 postgresql-16-postgis-3 postgresql-16-postgis-3-scripts libpq-dev libgeos-dev # SpatiaLite libsqlite3-mod-spatialite # MySQL mysql-client mysql-server default-libmysqlclient-dev # mysqlclient requirements # https://github.com/PyMySQL/mysqlclient#linux default-libmysqlclient-dev build-essential pkg-config # rasterio requirements with pypy libgdal-dev ) export DEBIAN_FRONTEND=noninteractive apt-get update -y apt-get install --no-install-recommends -y software-properties-common gnupg2 wget add-apt-repository ppa:deadsnakes/ppa sh -c 'echo "deb https://apt.PostgreSQL.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' wget --quiet -O - https://www.PostgreSQL.org/media/keys/ACCC4CF8.asc | apt-key add - apt-get update -y apt-get install --no-install-recommends -y "${packages[@]}" # clear the package list cache (populated with apt-get update) rm -rf /var/lib/apt/lists/* geoalchemy2-0.15.2/test_container/run.sh000077500000000000000000000005441464355170400201760ustar00rootroot00000000000000#!/usr/bin/env bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" mkdir -p "${SCRIPT_DIR}/output" docker run --rm -it \ --mount type=bind,source="${ROOT}",target=/geoalchemy2_read_only,ro \ --mount type=bind,source="${SCRIPT_DIR}/output",target=/output \ geoalchemy2 geoalchemy2-0.15.2/tests/000077500000000000000000000000001464355170400151515ustar00rootroot00000000000000geoalchemy2-0.15.2/tests/__init__.py000066400000000000000000000122251464355170400172640ustar00rootroot00000000000000import os import platform import re import shutil import sys import pytest from packaging.version import parse as parse_version from sqlalchemy import __version__ as SA_VERSION from sqlalchemy import create_engine from sqlalchemy import select as raw_select from sqlalchemy import text from sqlalchemy.event import listen from sqlalchemy.exc import OperationalError from sqlalchemy.sql import func from geoalchemy2 import load_spatialite from geoalchemy2 import load_spatialite_gpkg class test_only_with_dialects: def __init__(self, *dialects): self.tested_dialects = dialects def __call__(self, test_obj): test_obj.tested_dialects = self.tested_dialects return test_obj def get_postgis_major_version(bind): try: return parse_version(bind.execute(func.postgis_lib_version()).scalar()).major except OperationalError: return parse_version("0").major def get_postgres_major_version(bind): try: return re.match( r"([0-9]*)\.([0-9]*).*", bind.execute(text("""SELECT current_setting('server_version');""")).scalar(), ).group(1) except OperationalError: return "0" def skip_postgis1(bind): if get_postgis_major_version(bind) == 1: pytest.skip("requires PostGIS != 1") def skip_postgis2(bind): if get_postgis_major_version(bind) == 2: pytest.skip("requires PostGIS != 2") def skip_postgis3(bind): if get_postgis_major_version(bind) == 3: pytest.skip("requires PostGIS != 3") def skip_case_insensitivity(): return pytest.mark.skipif( parse_version(SA_VERSION) < parse_version("1.3.4"), reason="Case-insensitivity is only available for sqlalchemy>=1.3.4", ) def skip_pg12_sa1217(bind): if ( parse_version(SA_VERSION) < parse_version("1.2.17") and int(get_postgres_major_version(bind)) >= 12 ): pytest.skip("Reflection for PostgreSQL-12 is only supported by sqlalchemy>=1.2.17") def skip_pypy(msg=None): if platform.python_implementation() == "PyPy": pytest.skip(msg if msg is not None else "Incompatible with PyPy") def select(args): if parse_version(SA_VERSION) < parse_version("1.4"): return raw_select(args) else: return raw_select(*args) def format_wkt(wkt): return wkt.replace(", ", ",") def copy_and_connect_sqlite_db(input_db, tmp_db, engine_echo, dialect): if "SPATIALITE_LIBRARY_PATH" not in os.environ: pytest.skip("SPATIALITE_LIBRARY_PATH is not defined, skip SpatiaLite tests") shutil.copyfile(input_db, tmp_db) print("INPUT DB:", input_db) print("TEST DB:", tmp_db) db_url = f"{dialect}:///{tmp_db}" engine = create_engine( db_url, echo=engine_echo, execution_options={"schema_translate_map": {"gis": None}} ) if dialect == "gpkg": listen(engine, "connect", load_spatialite_gpkg) else: listen(engine, "connect", load_spatialite) with engine.begin() as connection: print( "SPATIALITE VERSION:", connection.execute(text("SELECT spatialite_version();")).fetchone()[0], ) print( "GEOS VERSION:", connection.execute(text("SELECT geos_version();")).fetchone()[0], ) if sys.version_info.minor > 7: print( "PROJ VERSION:", connection.execute(text("SELECT proj_version();")).fetchone()[0], ) print( "PROJ DB PATH:", connection.execute(text("SELECT PROJ_GetDatabasePath();")).fetchone()[0], ) return engine def check_indexes(conn, dialect_name, expected, table_name): """Check that actual indexes are equal to the expected ones.""" index_query = { "postgresql": text( """SELECT indexname, indexdef FROM pg_indexes WHERE tablename = '{}' ORDER BY indexname;""".format( table_name ) ), "sqlite": text( """SELECT * FROM geometry_columns WHERE f_table_name = '{}' ORDER BY f_table_name, f_geometry_column;""".format( table_name ) ), "geopackage": text( """SELECT table_name, column_name, extension_name FROM gpkg_extensions WHERE table_name = '{}' and extension_name = 'gpkg_rtree_index' """.format( table_name ) ), } # Query to check the indexes actual_indexes = conn.execute(index_query[dialect_name]).fetchall() expected_indexes = expected[dialect_name] if dialect_name == "postgresql": expected_indexes = [(i[0], re.sub("\n *", " ", i[1])) for i in expected_indexes] try: assert actual_indexes == expected_indexes except AssertionError as exc: print("###############################################") print("Expected indexes:") for i in expected_indexes: print(i) print("Actual indexes:") for i in actual_indexes: print(i) print("###############################################") raise exc geoalchemy2-0.15.2/tests/alembic_config/000077500000000000000000000000001464355170400200725ustar00rootroot00000000000000geoalchemy2-0.15.2/tests/alembic_config/alembic.ini000066400000000000000000000053161464355170400221740ustar00rootroot00000000000000# A generic, single database configuration. [alembic] # path to migration scripts script_location = alembic # template used to generate migration files # file_template = %%(rev)s_%%(slug)s # sys.path path, will be prepended to sys.path if present. # defaults to the current working directory. prepend_sys_path = . # timezone to use when rendering the date within the migration file # as well as the filename. # If specified, requires the python-dateutil library that can be # installed by adding `alembic[tz]` to the pip requirements # string value is passed to dateutil.tz.gettz() # leave blank for localtime # timezone = # max length of characters to apply to the # "slug" field # truncate_slug_length = 40 # set to 'true' to run the environment during # the 'revision' command, regardless of autogenerate # revision_environment = false # set to 'true' to allow .pyc and .pyo files without # a source .py file to be detected as revisions in the # versions/ directory # sourceless = false # version location specification; This defaults # to alembic/versions. When using multiple version # directories, initial revisions must be specified with --version-path. # The path separator used here should be the separator specified by "version_path_separator" # version_locations = %(here)s/bar:%(here)s/bat:alembic/versions # version path separator; As mentioned above, this is the character used to split # version_locations. Valid values are: # # version_path_separator = : # version_path_separator = ; # version_path_separator = space version_path_separator = os # default: use os.pathsep # the output encoding used when revision files # are written from script.py.mako # output_encoding = utf-8 # The URL will be set dynamically during the test initialization ; sqlalchemy.url = postgresql://gis:gis@localhost/gis ; sqlalchemy.url = sqlite:///spatialdb [post_write_hooks] # post_write_hooks defines scripts or Python functions that are run # on newly generated revision scripts. See the documentation for further # detail and examples # format using "black" - use the console_scripts runner, against the "black" entrypoint # hooks = black # black.type = console_scripts # black.entrypoint = black # black.options = -l 79 REVISION_SCRIPT_FILENAME # Logging configuration [loggers] keys = root,sqlalchemy,alembic [handlers] keys = console [formatters] keys = generic [logger_root] level = DEBUG handlers = console qualname = [logger_sqlalchemy] level = DEBUG handlers = qualname = sqlalchemy.engine [logger_alembic] level = INFO handlers = qualname = alembic [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(levelname)-5.5s [%(name)s] %(message)s datefmt = %H:%M:%S geoalchemy2-0.15.2/tests/conftest.py000066400000000000000000000175741464355170400173660ustar00rootroot00000000000000import os from pathlib import Path import pytest from sqlalchemy import MetaData from sqlalchemy import create_engine from sqlalchemy.dialects.mysql.base import MySQLDialect from sqlalchemy.dialects.sqlite.base import SQLiteDialect from sqlalchemy.orm import declarative_base from sqlalchemy.orm import sessionmaker from geoalchemy2.alembic_helpers import _monkey_patch_get_indexes_for_mysql from geoalchemy2.alembic_helpers import _monkey_patch_get_indexes_for_sqlite from . import copy_and_connect_sqlite_db from . import get_postgis_major_version from . import get_postgres_major_version from .schema_fixtures import * # noqa def pytest_addoption(parser): parser.addoption( "--postgresql_dburl", action="store", help="PostgreSQL DB URL used for tests (`postgresql://user:password@host:port/dbname`).", ) parser.addoption( "--sqlite_spatialite3_dburl", action="store", help="SQLite DB URL used for tests with SpatiaLite3 (`sqlite:///path_to_db_file`).", ) parser.addoption( "--sqlite_spatialite4_dburl", action="store", help="SQLite DB URL used for tests with SpatiaLite4 (`sqlite:///path_to_db_file`).", ) parser.addoption( "--sqlite_geopackage_dburl", action="store", help="SQLite DB URL used for tests with GeoPackage (`gpkg:///path_to_db_file.gpkg`).", ) parser.addoption( "--mysql_dburl", action="store", help="MySQL DB URL used for tests with MySQL (`mysql://user:password@host:port/dbname`).", ) parser.addoption( "--engine-echo", action="store_true", default=False, help="If set to True, all statements of the engine are logged.", ) def pytest_generate_tests(metafunc): if "db_url" in metafunc.fixturenames: sqlite_dialects = ["sqlite-spatialite3", "sqlite-spatialite4", "geopackage"] dialects = None if metafunc.module.__name__ == "tests.test_functional_postgresql": dialects = ["postgresql"] elif metafunc.module.__name__ == "tests.test_functional_sqlite": dialects = sqlite_dialects elif metafunc.module.__name__ == "tests.test_functional_mysql": dialects = ["mysql"] elif metafunc.module.__name__ == "tests.test_functional_geopackage": dialects = ["geopackage"] if getattr(metafunc.function, "tested_dialects", False): dialects = metafunc.function.tested_dialects elif getattr(metafunc.cls, "tested_dialects", False): dialects = metafunc.cls.tested_dialects if dialects is None: dialects = ["mysql", "postgresql"] + sqlite_dialects if "sqlite" in dialects: # Order dialects dialects = [i for i in dialects if i != "sqlite"] + sqlite_dialects metafunc.parametrize("db_url", dialects, indirect=True) @pytest.fixture(scope="session") def db_url_postgresql(request): return ( request.config.getoption("--postgresql_dburl") or os.getenv("PYTEST_POSTGRESQL_DB_URL") or "postgresql://gis:gis@localhost/gis" ) @pytest.fixture(scope="session") def db_url_mysql(request, tmpdir_factory): return ( request.config.getoption("--mysql_dburl") or os.getenv("PYTEST_MYSQL_DB_URL") or "mysql://gis:gis@localhost/gis" ) @pytest.fixture(scope="session") def db_url_sqlite_spatialite3(request, tmpdir_factory): return ( request.config.getoption("--sqlite_spatialite3_dburl") or os.getenv("PYTEST_SPATIALITE3_DB_URL") or f"sqlite:///{Path(__file__).parent / 'data' / 'spatialite_lt_4.sqlite'}" ) @pytest.fixture(scope="session") def db_url_sqlite_spatialite4(request, tmpdir_factory): return ( request.config.getoption("--sqlite_spatialite4_dburl") or os.getenv("PYTEST_SPATIALITE4_DB_URL") or f"sqlite:///{Path(__file__).parent / 'data' / 'spatialite_ge_4.sqlite'}" ) @pytest.fixture(scope="session") def db_url_geopackage(request, tmpdir_factory): return ( request.config.getoption("--sqlite_geopackage_dburl") or os.getenv("PYTEST_GEOPACKAGE_DB_URL") or f"gpkg:///{Path(__file__).parent / 'data' / 'spatialite_geopackage.gpkg'}" ) @pytest.fixture(scope="session") def db_url( request, db_url_postgresql, db_url_sqlite_spatialite3, db_url_sqlite_spatialite4, db_url_geopackage, db_url_mysql, ): if request.param == "postgresql": return db_url_postgresql if request.param == "mysql": return db_url_mysql elif request.param == "sqlite-spatialite3": return db_url_sqlite_spatialite3 elif request.param == "sqlite-spatialite4": return db_url_sqlite_spatialite4 elif request.param == "geopackage": return db_url_geopackage return None @pytest.fixture(scope="session") def _engine_echo(request): _engine_echo = request.config.getoption("--engine-echo") return _engine_echo @pytest.fixture def engine(tmpdir, db_url, _engine_echo): """Provide an engine to test database.""" if db_url.startswith("sqlite:///"): # Copy the input SQLite DB to a temporary file and return an engine to it input_url = str(db_url)[10:] output_file = "test_spatial_db.sqlite" current_engine = copy_and_connect_sqlite_db( input_url, tmpdir / output_file, _engine_echo, "sqlite" ) elif db_url.startswith("gpkg:///"): # Copy the input SQLite DB to a temporary file and return an engine to it input_url = str(db_url)[8:] output_file = "test_spatial_db.gpkg" current_engine = copy_and_connect_sqlite_db( input_url, tmpdir / output_file, _engine_echo, "gpkg" ) else: # For other dialects the engine is directly returned current_engine = create_engine(db_url, echo=_engine_echo) current_engine.update_execution_options(search_path=["gis", "public"]) yield current_engine current_engine.dispose() @pytest.fixture def check_spatialite(): if "SPATIALITE_LIBRARY_PATH" not in os.environ: pytest.skip("SPATIALITE_LIBRARY_PATH is not defined, skip SpatiaLite tests") @pytest.fixture def dialect_name(engine): return engine.dialect.name @pytest.fixture def conn(engine): """Provide a connection to test database.""" with engine.connect() as connection: trans = connection.begin() yield connection trans.rollback() @pytest.fixture def session(engine, conn): Session = sessionmaker(bind=conn) with Session(bind=conn) as session: yield session @pytest.fixture def schema(engine): if engine.dialect.name == "postgresql": return "gis" else: return None @pytest.fixture def metadata(): return MetaData() @pytest.fixture() def base(metadata): return declarative_base(metadata=metadata) @pytest.fixture def postgis_version(conn): return get_postgis_major_version(conn) @pytest.fixture def postgres_major_version(conn): return get_postgres_major_version(conn) @pytest.fixture(autouse=True) def reset_alembic_monkeypatch(): """Disable Alembic monkeypatching by default.""" try: normal_behavior_sqlite = SQLiteDialect._get_indexes_normal_behavior SQLiteDialect.get_indexes = normal_behavior_sqlite SQLiteDialect._get_indexes_normal_behavior = normal_behavior_sqlite normal_behavior_mysql = MySQLDialect._get_indexes_normal_behavior MySQLDialect.get_indexes = normal_behavior_mysql MySQLDialect._get_indexes_normal_behavior = normal_behavior_mysql except AttributeError: pass @pytest.fixture() def use_alembic_monkeypatch(): """Enable Alembic monkeypatching .""" _monkey_patch_get_indexes_for_sqlite() _monkey_patch_get_indexes_for_mysql() @pytest.fixture def setup_tables(conn, metadata): metadata.drop_all(conn, checkfirst=True) metadata.create_all(conn) yield geoalchemy2-0.15.2/tests/data/000077500000000000000000000000001464355170400160625ustar00rootroot00000000000000geoalchemy2-0.15.2/tests/data/spatialite_ge_4.sqlite000066400000000000000000006500001464355170400223430ustar00rootroot00000000000000SQLite format 3@ L5aL.S`5OOtableviews_geometry_columns_statisticsviews_geometry_columns_statisticsCREATE TABLE views_geometry_columns_statistics ( view_name TEXT NOT NULL, view_geometry TEXT NOT NULL, last_verified TIMESTAMP, row_count INTEGER, extent_min_x DOUBLE, extent_min_y DOUBLE, extent_max_x DOUBLE, extent_max_y DOUBLE, CONSTRAINT pk_vwgc_statistics PRIMARY KEY (view_name, view_geometry), CONSTRAINT fk_vwgc_statistics FOREIGN KEY (view_name, view_geometry) REFERENCES views_geometry_columns (view_name, view_geometry) ON DELETE CASCADE)auOindexsqlite_autoindex_views_geometry_columns_statistics_1views_geometry_columns_statistics CC'tablegeometry_columns_statisticsgeometry_columns_statistics CREATE TABLE geometry_columns_statistics ( f_table_name TEXT NOT NULL, f_geometry_column TEXT NOT NULL, last_verified TIMESTAMP, row_count INTEGER, extent_min_x DOUBLE, extent_min_y DOUBLE, extent_max_x DOUBLE, extent_max_y DOUBLE, CONSTRAINT pk_gc_statistics PRIMARY KEY (f_table_name, f_geometry_column), CONSTRAINT fk_gc_statistics FOREIGN KEY (f_table_name, f_geometry_column) REFERENCES geometry_columns (f_table_name, f_geometry_column) ON DELETE CASCADE)U iCindexsqlite_autoindex_geometry_columns_statistics_1geometry_columns_statistics 99tablevirts_geometry_columnsvirts_geometry_columns CREATE TABLE virts_geometry_columns ( virt_name TEXT NOT NULL, virt_geometry TEXT NOT NULL, geometry_type INTEGER NOT NULL, coord_dimension INTEGER NOT NULL, srid INTEGER NOT NULL, CONSTRAINT pk_geom_cols_virts PRIMARY KEY (virt_name, virt_geometry), CONSTRAINT fk_vgc_srid FOREIGN KEY (srid) REFERENCES spatial_ref_sys (srid))K _9indexsqlite_autoindex_virts_geometry_columns_1virts_geometry_columns 99Wtableviews_geometry_columnsviews_geometry_columnsCREATE TABLE views_geometry_columns ( view_name TEXT NOT NULL, view_geometry TEXT NOT NULL, view_rowid TEXT NOT NULL, f_table_name TEXT NOT NULL, f_geometry_column TEXT NOT NULL, read_only INTEGER NOT NULL, CONSTRAINT pk_geom_cols_views PRIMARY KEY (view_name, view_geometry), CONSTRAINT fk_views_geom_cols FOREIGN KEY (f_table_name, f_geometry_column) REFERENCES geometry_columns (f_table_name, f_geometry_column) ON DELETE CASCADE, CONSTRAINT ck_vw_rdonly CHECK (read_only IN (0,1)))K_9indexsqlite_autoindex_views_geometry_columns_1views_geometry_columns &33stablespatial_ref_sys_auxspatial_ref_sys_auxCREATE TABLE spatial_ref_sys_aux ( srid INTEGER NOT NULL PRIMARY KEY, is_geographic INTEGER, has_flipped_axes INTEGER, spheroid TEXT, prime_meridian TEXT, datum TEXT, projection TEXT, unit TEXT, axis_1_name TEXT, axis_1_orientation TEXT, axis_2_name TEXT, axis_2_orientation TEXT, CONSTRAINT fk_sprefsys FOREIGN KEY (srid) REFERENCES spatial_ref_sys (srid))]--mtablegeometry_columnsgeometry_columnsCREATE TABLE geometry_columns ( f_table_name TEXT NOT NULL, f_geometry_column TEXT NOT NULL, geometry_type INTEGER NOT NULL, coord_dimension INTEGER NOT NULL, srid INTEGER NOT NULL, spatial_index_enabled INTEGER NOT NULL, CONSTRAINT pk_geom_cols PRIMARY KEY (f_table_name, f_geometry_column), CONSTRAINT fk_gc_srs FOREIGN KEY (srid) REFERENCES spatial_ref_sys (srid), CONSTRAINT ck_gc_rtree CHECK (spatial_index_enabled IN (0,1,2)))?S-indexsqlite_autoindex_geometry_columns_1geometry_columnsP++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)11]tablespatialite_historyspatialite_historyCREATE TABLE spatialite_history ( event_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, table_name TEXT NOT NULL, geometry_column TEXT, event TEXT NOT NULL, timestamp TEXT NOT NULL, ver_sqlite TEXT NOT NULL, ver_splite TEXT NOT NULL)++[tablespatial_ref_sysspatial_ref_sysCREATE TABLE spatial_ref_sys ( srid INTEGER NOT NULL PRIMARY KEY, auth_name TEXT NOT NULL, auth_srid INTEGER NOT NULL, ref_sys_name TEXT NOT NULL DEFAU4j3g2b1]0X/S.N-I,D+?*8)2(,%    efSiepsgWGS 84+proj=longlat +datum=WGS84 +no_defsGEOGCS["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"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]zj 1=epsgjRGF93 / Lambert-93+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defsPROJCS["RGF93 / Lambert-93",GEOGCS["RGF93",DATUM["Reseau_Geodesique_Francais_1993",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6171"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4171"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",49],PARAMETER["standard_parallel_2",44],PARAMETER["latitude_of_origin",46.5],PARAMETER["central_meridian",3],PARAMETER["false_easting",700000],PARAMETER["false_northing",6600000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["X",EAST],AXIS["Y",NORTH],AUTHORITY["EPSG","2154"]]3K NONEUndefined - Geographic Long/LatUndefined*7 NONEUndefined - CartesianUndefined bQK ; 3 ' bW +E=spatial_ref_systable successfully populated2022-05-24T07:55:21.939Z3.36.04.3.0aj /g=geom_cols_ref_sysview 'geom_cols_ref_sys' successfully created2022-05-24T07:55:09.473Z3.36.04.3.0a -=geometry_columnstrigger 'geometry_columns_coord_dimension_update' successfully created2022-05-24T07:55:09.471Z3.36.04.3.0a -=geometry_columnstrigger 'geometry_columns_coord_dimension_insert' successfully created2022-05-24T07:55:09.469Z3.36.04.3.0a -=geometry_columnstrigger 'geometry_columns_geometry_type_update' successfully created2022-05-24T07:55:09.467Z3.36.04.3.0a -=geometry_columnstrigger 'geometry_columns_geometry_type_insert' successfully created2022-05-24T07:55:09.465Z3.36.04.3.0a -=geometry_columnstrigger 'geometry_columns_f_geometry_column_update' successfully created2022-05-24T07:55:09.463Z3.36.04.3.0a -=geometry_columnstrigger 'geometry_columns_f_geometry_column_insert' successfully created2022-05-24T07:55:09.461Z3.36.04.3.0a -=geometry_columnstrigger 'geometry_columns_f_table_name_update' successfully created2022-05-24T07:55:09.453Z3.36.04.3.0a -=geometry_columnstrigger 'geometry_columns_f_table_name_insert' successfully created2022-05-24T07:55:09.451Z3.36.04.3.0aV-A=geometry_columnstable successfully created2022-05-24T07:55:09.447Z3.36.04.3.0aU+A=spatial_ref_systable successfully created2022-05-24T07:55:09.445Z3.36.04.3.0a 1spatialite_history   JJHf WGS 84GreenwichWGS_1984nonedegreeLatitudeNorthLongitudeEasthj KCGRS 1980GreenwichReseau_Geodesique_Francais_1993Lambert_Conformal_Conic_2SPmetreXEastYNorth           u  } T 4 = ^mZ**CuOindexsqlite_autoindex_virts_geometry_columns_statistics_1virts_geometry_columns_statisticsOOtableviews_geometry_columns_statisticsviews_geometry_columns_statisticsCREATE TABLE view OOtableviews_geometry_columns_statisticsviews_geometry_columns_statisticsCREATE TABLE views_geometry_columns_statistics ( view_name TEXT NOT NULL, view_geometry TEXT NOT NULL, last_verified TIMESTAMP, row_count INTEGER, extent_min_x DOUBLE, extent_min_y DOUBLE, extent_max_x DOUBLE, extent_max_y DOUBLE, CONSTRAINT pk_vwgc_statistics PRIMARY KEY (view_name, view_geometry), CONSTRAINT fk_vwgc_statistics FOREIGN KEY (view_name, view_geometry) REFERENCES views_geometry_columns (view_name, view_geometry) ON DELETE CASCADE)U iCindexsqlite_autoindex_geometry_columns_statistics_1geometry_columns_statistics  CC'tablegeometry_columns_statisticsgeometry_columns_statistics CREATE TABLE geometry_columns_statistics ( f_table_name TEXT NOT NULL, f_geometry_column TEXT NOT NULL, last_verified TIMESTAMP, row_count INTEGER, extent_min_x DOUBLE, extent_min_y DOUBLE, extent_max_x DOUBLE, extent_max_y DOUBLE, CONSTRAINT pk_gc_statistics PRIMARY KEY (f_table_name, f_geometry_column), CONSTRAINT fk_gc_statistics FOREIGN KEY (f_table_name, f_geometry_column) REFERENCES geometry_columns (f_table_name, f_geometry_column) ON DELETE CASCADE) 99tablevirts_geometry_columnsvirts_geometry_columns CREATE TABLE virts_geometry_columns ( virt_name TEXT NOT NULL, virt_geometry TEXT NOT NULL, geometry_type INTEGER NOT NULL, coord_dimension INTEGER NOT NULL, srid INTEGER NOT NULL, CONSTRAINT pk_geom_cols_virts PRIMARY KEY (virt_name, virt_geometry), CONSTRAINT fk_vgc_srid FOREIGN KEY (srid) REFERENCES spatial_ref_sys (srid))K _9indexsqlite_autoindex_virts_geometry_columns_1virts_geometry_columns 99Wtableviews_geometry_columnsviews_geometry_columnsCREATE TABLE views_geometry_columns ( view_name TEXT NOT NULL, view_geometry TEXT NOT NULL, view_rowid TEXT NOT NULL, f_table_name TEXT NOT NULL, f_geometry_column TEXT NOT NULL, read_only INTEGER NOT NULL, CONSTRAINT pk_geom_cols_views PRIMARY KEY (view_name, view_geometry), CONSTRAINT fk_views_geom_cols FOREIGN KEY (f_table_name, f_geometry_column) REFERENCES geometry_columns (f_table_name, f_geometry_column) ON DELETE CASCADE, CONSTRAINT ck_vw_rdonly CHECK (read_only IN (0,1)))K_9indexsqlite_autoindex_views_geometry_columns_1views_geometry_columns &33stablespatial_ref_sys_auxspatial_ref_sys_auxCREATE TABLE spatial_ref_sys_aux ( srid INTEGER NOT NULL PRIMARY KEY, is_geographic INTEGER, has_flipped_axes INTEGER, spheroid TEXT, prime_meridian TEXT, datum TEXT, projection TEXT, unit TEXT, axis_1_name TEXT, axis_1_orientation TEXT, axis_2_name TEXT, axis_2_orientation TEXT, CONSTRAINT fk_sprefsys FOREIGN KEY (srid) REFERENCES spatial_ref_sys (srid))]--mtablegeometry_columnsgeometry_columnsCREATE TABLE geometry_columns ( f_table_name TEXT NOT NULL, f_geometry_column TEXT NOT NULL, geometry_type INTEGER NOT NULL, coord_dimension INTEGER NOT NULL, srid INTEGER NOT NULL, spatial_index_enabled INTEGER NOT NULL, CONSTRAINT pk_geom_cols PRIMARY KEY (f_table_name, f_geometry_column), CONSTRAINT fk_gc_srs FOREIGN KEY (srid) REFERENCES spatial_ref_sys (srid), CONSTRAINT ck_gc_rtree CHECK (spatial_index_enabled IN (0,1,2)))?S-indexsqlite_autoindex_geometry_columns_1geometry_columnsP++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)11]tablespatialite_historyspatialite_historyCREATE TABLE spatialite_history ( event_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, table_name TEXT NOT NULL, geometry_column TEXT, event TEXT NOT NULL, timestamp TEXT NOT NULL, ver_sqlite TEXT NOT NULL, ver_splite TEXT NOT NULL)++[tablespatial_ref_sysspatial_ref_sysCREATE TABLE spatial_ref_sys ( srid INTEGER NOT NULL PRIMARY KEY, auth_name TEXT NOT NULL, auth_srid INTEGER NOT NULL, ref_sys_name TEXT NOT NULL DEFAULT 'Unknown', proj4text TEXT NOT NULL, srtext TEXT NOT NULL DEFAULT 'Undefined') v a  f $' ]7indexsqlite_autoindex_geometry_columns_auth_1geometry_columns_auth ;77tablegeometry_columns_timegeometry_columns_timeCREATE TABLE geometry_columns_time ( f_table_name TEXT NOT NULL, f_geometry_column TEXT NOT NULL, last_insert TIMESTAMP NOT NULL DEFAULT '0000-01-01T00:00:00.000Z', last_update TIMESTAMP NOT NULL DEFAULT '0000-01-01T00:00:00.000Z', last_delete TIMESTAMP NOT NULL DEFAULT '0000-01-01T00:00:00.000Z', CONSTRAINT pk_gc_time PRIMARY KEY (f_table_name, f_geometry_column), CONSTRAINT fk_gc_time FOREIGN KEY (f_table_name, f_geometry_column) REFERENCES geometry_columns (f_table_name, f_geometry_column) ON DELETE CASCADE)cwQindexsqlite_autoindex_virts_geometry_columns_field_infos_1virts_geometry_columns_field_infosWQQtableviews_geometry_columns_field_infosviews_geometry_columns_field_infosCREATE TABLE views_geometry_columns_field_infos ( view_name TEXT NOT NULL, view_geometry TEXT NOT NULL, ordinal INTEGER NOT NULL, column_name TEXT NOT NULL, null_values INTEGER NOT NULL, integer_values INTEGER NOT NULL, double_values INTEGER NOT NULL, text_values INTEGER NOT NULL, blob_values INTEGER NOT NULL, max_size INTEGER, integer_min INTEGER, integer_max INTEGER, double_min DOUBLE, double_max DOUBLE, CONSTRAINT pk_vwgcfld_infos PRIMARY KEY (view_name, view_geometry, ordinal, column_name), CONSTRAINT fk_vwgcfld_infos FOREIGN KEY (view_name, view_geometry) REFERENCES views_geometry_columns (view_name, view_geometry) ON DELETE CASCADE)cwQindexsqlite_autoindex_views_geometry_columns_field_infos_1views_geometry_columns_field_infosWEE1tablegeometry_columns_field_infosgeometry_columns_field_infosCREATE TABLE geometry_columns_field_infos ( f_table_name TEXT NOT NULL, f_geometry_column TEXT NOT NULL, ordinal INTEGER NOT NULL, column_name TEXT NOT NULL, null_values INTEGER NOT NULL, integer_values INTEGER NOT NULL, double_values INTEGER NOT NULL, text_values INTEGER NOT NULL, blob_values INTEGER NOT NULL, max_size INTEGER, integer_min INTEGER, integer_max INTEGER, double_min DOUBLE, double_max DOUBLE, CONSTRAINT pk_gcfld_infos PRIMARY KEY (f_table_name, f_geometry_column, ordinal, column_name), CONSTRAINT fk_gcfld_infos FOREIGN KEY (f_table_name, f_geometry_column) REFERENCES geometry_columns (f_table_name, f_geometry_column) ON DELETE CASCADE)WkEindexsqlite_autoindex_geometry_columns_field_infos_1geometry_columns_field_infosauOindexsqlite_autoindex_virts_geometry_columns_statistics_1virts_geometry_columns_statisticsOOtablevirts_geometry_columns_statisticsvirts_geometry_columns_statisticsCREATE TABLE virts_geometry_columns_statistics ( virt_name TEXT NOT NULL, virt_geometry TEXT NOT NULL, last_verified TIMESTAMP, row_count INTEGER, extent_min_x DOUBLE, extent_min_y DOUBLE, extent_max_x DOUBLE, extent_max_y DOUBLE, CONSTRAINT pk_vrtgc_statistics PRIMARY KEY (virt_name, virt_geometry), CONSTRAINT fk_vrtgc_statistics FOREIGN KEY (virt_name, virt_geometry) REFERENCES virts_geometry_columns (virt_name, virt_geometry) ON DELETE CASCADE)auOindexsqlite_autoindex_views_geometry_columns_statistics_1views_geometry_columns_statisticsK]7indexsqlite_autoindex_geometry_columns_time_1geometry_columns_timeYQQtablevirts_geometry_columns_field_infosvirts_geometry_columns_field_infosCREATE TABLE virts_geometry_columns_field_infos ( virt_name TEXT NOT NULL, virt_geometry TEXT NOT NULL, ordinal INTEGER NOT NULL, column_name TEXT NOT NULL, null_values INTEGER NOT NULL, integer_values INTEGER NOT NULL, double_values INTEGER NOT NULL, text_values INTEGER NOT NULL, blob_values INTEGER NOT NULL, max_size INTEGER, integer_min INTEGER, integer_max INTEGER, double_min DOUBLE, double_max DOUBLE, CONSTRAINT pk_vrtgcfld_infos PRIMARY KEY (virt_name, virt_geometry, ordinal, column_name), CONSTRAINT fk_vrtgcfld_infos FOREIGN KEY (virt_name, virt_geometry) REFERENCES virts_geometry_columns (virt_name, virt_geometry) ON DELETE CASCADE)           ^ t7GJ^%U-Etriggergeometry_columns_f_table_name_updategeometry_columnsCREATE TRIGGER geometry_columns_f_table_name_update BEFORE UPDATE OF 'f_table_name' ON 'geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on geometry_columns violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on geometry_columns violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on geometry_columns violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); ENDI]7indexsqlite_autoindex_geometry_columns_time_1geometry_columns_time $U-#triggergeometry_columns_f_table_name_insertgeometry_columnsCREATE TRIGGER geometry_columns_f_table_name_insert BEFORE INSERT ON 'geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on geometry_columns violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on geometry_columns violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on geometry_columns violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); ENDk#'9indexidx_virtssridvirts_geometry_columns'CREATE INDEX idx_virtssrid ON virts_geometry_columns (srid)"'99indexidx_viewsjoinviews_geometry_columns&CREATE INDEX idx_viewsjoin ON views_geometry_columns (f_table_name, f_geometry_column)e!--indexidx_srid_geocolsgeometry_columns%CREATE INDEX idx_srid_geocols ON geometry_columns (srid)  3+1indexidx_spatial_ref_sysspatial_ref_sys$CREATE UNIQUE INDEX idx_spatial_ref_sys ON spatial_ref_sys (auth_srid, auth_name)611tablesql_statements_logsql_statements_log#CREATE TABLE sql_statements_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, time_start TIMESTAMP NOT NULL DEFAULT '0000-01-01T00:00:00.000Z', time_end TIMESTAMP NOT NULL DEFAULT '0000-01-01T00:00:00.000Z', user_agent TEXT NOT NULL, sql_statement TEXT NOT NULL, success INTEGER NOT NULL DEFAULT 0, error_cause TEXT NOT NULL DEFAULT 'ABORTED', CONSTRAINT sqllog_success CHECK (success IN (0,1)))UiCindexsqlite_autoindex_virts_geometry_columns_auth_1virts_geometry_columns_auth"ACC tablevirts_geometry_columns_authvirts_geometry_columns_auth!CREATE TABLE virts_geometry_columns_auth ( virt_name TEXT NOT NULL, virt_geometry TEXT NOT NULL, hidden INTEGER NOT NULL, CONSTRAINT pk_vrtgc_auth PRIMARY KEY (virt_name, virt_geometry), CONSTRAINT fk_vrtgc_auth FOREIGN KEY (virt_name, virt_geometry) REFERENCES virts_geometry_columns (virt_name, virt_geometry) ON DELETE CASCADE, CONSTRAINT ck_vrtgc_hidden CHECK (hidden IN (0,1)))UiCindexsqlite_autoindex_views_geometry_columns_auth_1views_geometry_columns_auth >CCtableviews_geometry_columns_authviews_geometry_columns_authCREATE TABLE views_geometry_columns_auth ( view_name TEXT NOT NULL, view_geometry TEXT NOT NULL, hidden INTEGER NOT NULL, CONSTRAINT pk_vwgc_auth PRIMARY KEY (view_name, view_geometry), CONSTRAINT fk_vwgc_auth FOREIGN KEY (view_name, view_geometry) REFERENCES views_geometry_columns (view_name, view_geometry) ON DELETE CASCADE, CONSTRAINT ck_vwgc_hidden CHECK (hidden IN (0,1)))I]7indexsqlite_autoindex_geometry_columns_auth_1geometry_columns_auth 775tablegeometry_columns_authgeometry_columns_authCREATE TABLE geometry_columns_auth ( f_table_name TEXT NOT NULL, f_geometry_column TEXT NOT NULL, read_only INTEGER NOT NULL, hidden INTEGER NOT NULL, CONSTRAINT pk_gc_auth PRIMARY KEY (f_table_name, f_geometry_column), CONSTRAINT fk_gc_auth FOREIGN KEY (f_table_name, f_geometry_column) REFERENCES geometry_columns (f_table_name, f_geometry_column) ON DELETE CASCADE, CONSTRAINT ck_gc_ronly CHECK (read_only IN (0,1)), CONSTRAINT ck_gc_hidden CHECK (hidden IN (0,1)))       epsg jepsgjNONE NONE    .I.  9 4[-etriggergeometry_columns_coord_dimension_updategeometry_columnsCREATE TRIGGER geometry_columns_coord_dimension_update BEFORE UPDATE OF 'coord_dimension' ON 'geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'coord_dimension must be one of 2,3,4') WHERE NOT(NEW.coord_dimension IN (2,3,4)); END(W-7triggergeometry_columns_geometry_type_insertgeometry_columnsCREATE TRIGGER geometry_columns_geometry_type_insert BEFORE INSERT ON 'geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'geometry_type must be one of 0,1,2,3,4,5,6,7,1000,1001,1002,1003,1004,1005,1006,1007,2000,2001,2002,2003,2004,2005,2006,2007,3000,3001,3002,3003,3004,3005,3006,3007') WHERE NOT(NEW.geometry_type IN (0,1,2,3,4,5,6,7,1000,1001,1002,1003,1004,1005,1006,1007,2000,2001,2002,2003,2004,2005,2006,2007,3000,3001,3002,3003,3004,3005,3006,3007)); ENDP'_-triggergeometry_columns_f_geometry_column_updategeometry_columnsCREATE TRIGGER geometry_columns_f_geometry_column_update BEFORE UPDATE OF 'f_geometry_column' ON 'geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on geometry_columns violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'update on geometry_columns violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'update on geometry_columns violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); END:&_-striggergeometry_columns_f_geometry_column_insertgeometry_columnsCREATE TRIGGER geometry_columns_f_geometry_column_insert BEFORE INSERT ON 'geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on geometry_columns violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'insert on geometry_columns violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'insert on geometry_columns violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); END`U-Etriggergeometry_columns_f_table_name_updategeometry_columnsCREATE TRIGGER geometry_colC,//9viewgeom_cols_ref_sysgeom_cols_ref_sysCREATE VIEW geom_cols_ref_sys AS SELECT f_table_name, f_geometry_column, geometry_type, coord_dimension, spatial_ref_sys.srid AS srid, auth_name, auth_srid, ref_sys_name, proj4text, srtext FROM geometry_columns, spatial_ref_sys WHERE geometry_columns.srid = spatial_ref_sys.srid1+[-etriggergeometry_columns_coord_dimension_updategeometry_columnsCREATE TRIGGER geometry_columns_coord_dimension_update BEFORE UPDATE OF 'coord_dimension' ON 'geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'coord_dimension must be one of 2,3,4') WHERE NOT(NEW.coord_dimension IN (2,3,4)); END*[-;triggergeometry_columns_coord_dimension_insertgeometry_columnsCREATE TRIGGER geometry_columns_coord_dimension_insert BEFORE INSERT ON 'geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'coord_dimension must be one of 2,3,4') WHERE NOT(NEW.coord_dimension IN (2,3,4)); END+)W-]triggergeometry_columns_geometry_type_updategeometry_columnsCREATE TRIGGER geometry_columns_geometry_type_update BEFORE UPDATE OF 'geometry_type' ON 'geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'geometry_type must be one of 0,1,2,3,4,5,6,7,1000,1001,1002,1003,1004,1005,1006,1007,2000,2001,2002,2003,2004,2005,2006,2007,3000,3001,3002,3003,3004,3005,3006,3007') WHERE NOT(NEW.geometry_type IN (0,1,2,3,4,5,6,7,1000,1001,1002,1003,1004,1005,1006,1007,2000,2001,2002,2003,2004,2005,2006,2007,3000,3001,3002,3003,3004,3005,3006,3007)); END y ; Ny299Gtriggervwgc_view_rowid_updateviews_geometry_columnsCREATE TRIGGER vwgc_view_rowid_update BEFORE UPDATE OF 'view_rowid' ON 'views_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: view_rowid value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: view_rowid value must not contain a double quote') WHERE NEW.view_rowid LIKE ('%"%'); SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: view_rowid value must be lower case') WHERE NEW.view_rowid <> lower(NEW.view_rowid); END0?9Ktriggervwgc_view_geometry_insertviews_geometry_columnsCREATE TRIGGER vwgc_view_geometry_insert BEFORE INSERT ON 'views_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: view_geometry value must not contain a single quote') WHERE NEW.view_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: view_geometry value must not contain a double quote') WHERE NEW.view_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: view_geometry value must be lower case') WHERE NEW.view_geometry <> lower(NEW.view_geometry); END/79'triggervwgc_view_name_updateviews_geometry_columnsCREATE TRIGGER vwgc_view_name_update BEFORE UPDATE OF 'view_name' ON 'views_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: view_name value must not contain a single quote') WHERE NEW.view_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: view_name value must not contain a double quote') WHERE NEW.view_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: view_name value must be lower case') WHERE NEW.view_name <> lower(NEW.view_name); ENDx.79 triggervwgc_view_name_insertviews_geometry_columnsCREATE TRIGGER vwgc_view_name_insert BEFORE INSERT ON 'views_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: view_name value must not contain a single quote') WHERE NEW.view_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: view_name value must not contain a double quote') WHERE NEW.view_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: view_name value must be lower case') WHERE NEW.view_name <> lower(NEW.view_name); END-33Uviewspatial_ref_sys_allspatial_ref_sys_allCREATE VIEW spatial_ref_sys_all AS SELECT a.srid AS srid, a.auth_name AS auth_name, a.auth_srid AS auth_srid, a.ref_sys_name AS ref_sys_name, b.is_geographic AS is_geographic, b.has_flipped_axes AS has_flipped_axes, b.spheroid AS spheroid, b.prime_meridian AS prime_meridian, b.datum AS datum, b.projection AS projection, b.unit AS unit, b.axis_1_name AS axis_1_name, b.axis_1_orientation AS axis_1_orientation, b.axis_2_name AS axis_2_name, b.axis_2_orientation AS axis_2_orientation, a.proj4text AS proj4text, a.srtext AS srtext FROM spatial_ref_sys AS a LEFT JOIN spatial_ref_sys_aux AS b ON (a.srid = b.srid)/1?9qtriggervwgc_view_geometry_updateviews_geometry_columnsCREATE TRIGGER vwgc_view_geometry_update BEFORE UPDATE OF 'view_geometry' ON 'views_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: view_geometry value must not contain a single quote') WHERE NEW.view_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: view_geometry value must not contain a double quote') WHERE NEW.view_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: view_geometry value must be lower case') WHERE NEW.view_geometry <> lower(NEW.view_geometry); END (  f '(x879 triggervtgc_virt_name_insertvirts_geometry_columnsCREATE TRIGGER vtgc_virt_name_insert BEFORE INSERT ON 'virts_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on virts_geometry_columns violates constraint: virt_name value must not contain a single quote') WHERE NEW.virt_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns violates constraint: virt_name value must not contain a double quote') WHERE NEW.virt_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns violates constraint: virt_name value must be lower case') WHERE NEW.virt_name <> lower(NEW.virt_name); END@6G9 triggervwgc_f_geometry_column_insertviews_geometry_columnsCREATE TRIGGER vwgc_f_geometry_column_insert BEFORE INSERT ON 'views_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); END$5=9]triggervwgc_f_table_name_updateviews_geometry_columnsCREATE TRIGGER vwgc_f_table_name_update BEFORE UPDATE OF 'f_table_name' ON 'views_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); END4=9;triggervwgc_f_table_name_insertviews_geometry_columnsCREATE TRIGGER vwgc_f_table_name_insert BEFORE INSERT ON 'views_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); END399triggervwgc_view_rowid_insertviews_geometry_columnsCREATE TRIGGER vwgc_view_rowid_insert BEFORE INSERT ON 'views_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: view_rowid value must not contain a single quote') WHERE NEW.view_rowid LIKE ('%''%'); SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: view_rowid value must not contain a double quote') WHERE NEW.view_rowid LIKE ('%"%'); SELECT RAISE(ABORT,'insert on views_geometry_columns violates constraint: view_rowid value must be lower case') WHERE NEW.view_rowid <> lower(NEW.view_rowid); ENDV7G97triggervwgc_f_geometry_column_updateviews_geometry_columnsCREATE TRIGGER vwgc_f_geometry_column_update BEFORE UPDATE OF 'f_geometry_column' ON 'views_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'update on views_geometry_columns violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); END # <?9+triggervtgc_geometry_type_insertvirts_geometry_columnsCREATE TRIGGER vtgc_geometry_type_insert BEFORE INSERT ON 'virts_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'geometry_type must be one of 0,1,2,3,4,5,6,7,1000,1001,1002,1003,1004,1005,1006,1007,2000,2001,2002,2003,2004,2005,2006,2007,3000,3001,3002,3003,3004,3005,3006,3007') WHERE NOT(NEW.geometry_type IN (0,1,2,3,4,5,6,7,1000,1001,1002,1003,1004,1005,1006,1007,2000,2001,2002,2003,2004,2005,2006,2007,3000,3001,3002,3003,3004,3005,3006,3007)); END/;?9qtriggervtgc_virt_geometry_updatevirts_geometry_columnsCREATE TRIGGER vtgc_virt_geometry_update BEFORE UPDATE OF 'virt_geometry' ON 'virts_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on virts_geometry_columns violates constraint: virt_geometry value must not contain a single quote') WHERE NEW.virt_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'update on virts_geometry_columns violates constraint: virt_geometry value must not contain a double quote') WHERE NEW.virt_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'update on virts_geometry_columns violates constraint: virt_geometry value must be lower case') WHERE NEW.virt_geometry <> lower(NEW.virt_geometry); END:?9Ktriggervtgc_virt_geometry_insertvirts_geometry_columnsCREATE TRIGGER vtgc_virt_geometry_insert BEFORE INSERT ON 'virts_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on virts_geometry_columns violates constraint: virt_geometry value must not contain a single quote') WHERE NEW.virt_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns violates constraint: virt_geometry value must not contain a double quote') WHERE NEW.virt_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns violates constraint: virt_geometry value must be lower case') WHERE NEW.virt_geometry <> lower(NEW.virt_geometry); END979'triggervtgc_virt_name_updatevirts_geometry_columnsCREATE TRIGGER vtgc_virt_name_update BEFORE UPDATE OF 'virt_name' ON 'virts_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on virts_geometry_columns violates constraint: virt_name value must not contain a single quote') WHERE NEW.virt_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on virts_geometry_columns violates constraint: virt_name value must not contain a double quote') WHERE NEW.virt_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on virts_geometry_columns violates constraint: virt_name value must be lower case') WHERE NEW.virt_name <> lower(NEW.virt_name); END79 triggervtgc_virt_name_insertvirts_geometry_columnsCREATE TRIGGER vtgc_virt_name_insert BEFORE INSERT ON 'virts_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on virts_geometry_columns violates constraint: virt_name value%?C9Ytriggervtgc_coord_dimension_updatevirts_geometry_columnsCREATE TRIGGER vtgc_coord_dimension_update BEFORE UPDATE OF 'coord_dimension' ON 'virts_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'coord_dimension must be one of 2,3,4') WHERE NOT(NEW.coord_dimension IN (2,3,4)); END>C9/triggervtgc_coord_dimension_insertvirts_geometry_columnsCREATE TRIGGER vtgc_coord_dimension_insert BEFORE INSERT ON 'virts_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'coord_dimension must be one of 2,3,4') WHERE NOT(NEW.coord_dimension IN (2,3,4)); END=?9Qtriggervtgc_geometry_type_updatevirts_geometry_columnsCREATE TRIGGER vtgc_geometry_type_update BEFORE UPDATE OF 'geometry_type' ON 'virts_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'geometry_type must be one of 0,1,2,3,4,5,6,7,1000,1001,1002,1003,1004,1005,1006,1007,2000,2001,2002,2003,2004,2005,2006,2007,3000,3001,3002,3003,3004,3005,3006,3007') WHERE NOT(NEW.geometry_type IN (0,1,2,3,4,5,6,7,1000,1001,1002,1003,1004,1005,1006,1007,2000,2001,2002,2003,2004,2005,2006,2007,3000,3001,3002,3003,3004,3005,3006,3007)); END n8^n LmCEC]triggergcs_f_geometry_column_updategeometry_columns_statisticsCREATE TRIGGER gcs_f_geometry_column_update BEFORE UPDATE OF 'f_geometry_column' ON 'geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on geometry_columns_statistics violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'update on geometry_columns_statistics violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'update on geometry_columns_statistics violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); ENDWBEC1triggergcs_f_geometry_column_insertgeometry_columns_statisticsCREATE TRIGGER gcs_f_geometry_column_insert BEFORE INSERT ON 'geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on geometry_columns_statistics violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'insert on geometry_columns_statistics violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'insert on geometry_columns_statistics violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); END;A;Ctriggergcs_f_table_name_updategeometry_columns_statisticsCREATE TRIGGER gcs_f_table_name_update BEFORE UPDATE OF 'f_table_name' ON 'geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on geometry_columns_statistics violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on geometry_columns_statistics violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on geometry_columns_statistics violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); END*@;Catriggergcs_f_table_name_insertgeometry_columns_statisticsCREATE TRIGGER gcs_f_table_name_insert BEFORE INSERT ON 'geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on geometry_columns_statistics violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on geometry_columns_statistics violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on geometry_columns_statistics violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); ENDC9Ytriggervtgc_coord_dimension_updatevirts_geometry_columnsCREATE TRIGGER vtgc_coord_dimension_update BEFORE UPDATE OF 'coord_dimension' ON 'virts_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'coord_dimension must be one of 2,3,4') WHERE NOT(NEW.coord_dimension IN (2,3,4)); END>C9/triggervtgc_coord_dimension_insertvirts_geometry_columnsCREATE TRIGGER vtgc_coord_dimension_insert BEFORE INSERT ON 'vi1D9Oetriggervwgcs_view_name_insertviews_geometry_columns_statisticsCREATE TRIGGER vwgcs_view_name_insert BEFORE INSERT ON 'views_geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on views_geometry_columns_statistics violates constraint: view_name value must not contain a single quote') WHERE NEW.view_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_statistics violates constraint: view_name value must not contain a double quote') WHERE NEW.view_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_statistics violates constraint: view_name value must be lower case') WHERE NEW.view_name <> lower(NEW.view_name); END  > f{?I9Otriggervtgcs_virt_name_updatevirts_geometry_columns_statisticsCREATE TRIGGER vtgcs_virt_name_update BEFORE UPDATE OF 'virt_name' ON 'virts_geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on virts_geometry_columns_statistics violates constraint: virt_name value must not contain a single quote') WHERE NEW.virt_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_statistics violates constraint: virt_name value must not contain a double quote') WHERE NEW.virt_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_statistics violates constraint: virt_name value must be lower case') WHERE NEW.virt_name <> lower(NEW.virt_name); END1H9Oetriggervtgcs_virt_name_insertvirts_geometry_columns_statisticsCREATE TRIGGER vtgcs_virt_name_insert BEFORE INSERT ON 'virts_geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on virts_geometry_columns_statistics violates constraint: virt_name value must not contain a single quote') WHERE NEW.virt_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_statistics violates constraint: virt_name value must not contain a double quote') WHERE NEW.virt_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_statistics violates constraint: virt_name value must be lower case') WHERE NEW.virt_name <> lower(NEW.virt_name); ENDhGAOKtriggervwgcs_view_geometry_updateviews_geometry_columns_statisticsCREATE TRIGGER vwgcs_view_geometry_update BEFORE UPDATE OF 'view_geometry' ON 'views_geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on views_geometry_columns_statistics violates constraint: view_geometry value must not contain a single quote') WHERE NEW.view_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'update on views_geometry_columns_statistics violates constraint: view_geometry value must not contain a double quote') WHERE NEW.view_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'update on views_geometry_columns_statistics violates constraint: view_geometry value must be lower case') WHERE NEW.view_geometry <> lower(NEW.view_geometry); ENDUFAO%triggervwgcs_view_geometry_insertviews_geometry_columns_statisticsCREATE TRIGGER vwgcs_view_geometry_insert BEFORE INSERT ON 'views_geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on views_geometry_columns_statistics violates constraint: view_geometry value must not contain a single quote') WHERE NEW.view_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_statistics violates constraint: view_geometry value must not contain a double quote') WHERE NEW.view_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_statistics violates constraint: view_geometry value must be lower case') WHERE NEW.view_geometry <> lower(NEW.view_geometry); END?E9Otriggervwgcs_view_name_updateviews_geometry_columns_statisticsCREATE TRIGGER vwgcs_view_name_update BEFORE UPDATE OF 'view_name' ON 'views_geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on views_geometry_columns_statistics violates constraint: view_name value must not contain a single quote') WHERE NEW.view_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on views_geometry_columns_statistics violates constraint: view_name value must not contain a double quote') WHERE NEW.view_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on views_geometry_columns_statistics violates constraint: view_name value must be lower case') WHERE NEW.view_name <> lower(NEW.view_name); END  cx ;^NGE;triggergcfi_f_geometry_column_insertgeometry_columns_field_infosCREATE TRIGGER gcfi_f_geometry_column_insert BEFORE INSERT ON 'geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on geometry_columns_field_infos violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'insert on geometry_columns_field_infos violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'insert on geometry_columns_field_infos violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); END1L=Ektriggergcfi_f_table_name_insertgeometry_columns_field_infosCREATE TRIGGER gcfi_f_table_name_insert BEFORE INSERT ON 'geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on geometry_columns_field_infos violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on geometry_columns_field_infos violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on geometry_columns_field_infos violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); ENDhKAOKtriggervtgcs_virt_geometry_updatevirts_geometry_columns_statisticsCREATE TRIGGER vtgcs_virt_geometry_update BEFORE UPDATE OF 'virt_geometry' ON 'virts_geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on virts_geometry_columns_statistics violates constraint: virt_geometry value must not contain a single quote') WHERE NEW.virt_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_statistics violates constraint: virt_geometry value must not contain a double quote') WHERE NEW.virt_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_statistics violates constraint: virt_geometry value must be lower case') WHERE NEW.virt_geometry <> lower(NEW.virt_geometry); ENDUJAO%triggervtgcs_virt_geometry_insertvirts_geometry_columns_statisticsCREATE TRIGGER vtgcs_virt_geometry_insert BEFORE INSERT ON 'virts_geometry_columns_statistics' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on virts_geometry_columns_statistics violates constraint: virt_geometry value must not contain a single quote') WHERE NEW.virt_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_statistics violates constraint: virt_geometry value must not contain a double quote') WHERE NEW.virt_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_statistics violates constraint: virt_geometry value must be lower case') WHERE NEW.virt_geometry <> lower(NEW.virt_geometry); ENDBM=E triggergcfi_f_table_name_updategeometry_columns_field_infosCREATE TRIGGER gcfi_f_table_name_update BEFORE UPDATE OF 'f_table_name' ON 'geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on geometry_columns_field_infos violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on geometry_columns_field_infos violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on geometry_columns_field_infos violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); END  NoSCQUtriggervwgcfi_view_geometry_updateviews_geometry_columns_field_infosCREATE TRIGGER vwgcfi_view_geometry_update BEFORE UPDATE OF 'view_geometry' ON 'views_geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on views_geometry_columns_field_infos violates constraint: view_geometry value must not contain a single quote') WHERE NEW.view_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'update on views_geometry_columns_field_infos violates constraint: view_geometry value must not contain a double quote') WHERE NEW.view_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'update on views_geometry_columns_field_infos violates constraint: view_geometry value must be lower case') WHERE NEW.view_geometry <> lower(NEW.view_geometry); END\RCQ/triggervwgcfi_view_geometry_insertviews_geometry_columns_field_infosCREATE TRIGGER vwgcfi_view_geometry_insert BEFORE INSERT ON 'views_geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on views_geometry_columns_field_infos violates constraint: view_geometry value must not contain a single quote') WHERE NEW.view_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_field_infos violates constraint: view_geometry value must not contain a double quote') WHERE NEW.view_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_field_infos violates constraint: view_geometry value must be lower case') WHERE NEW.view_geometry <> lower(NEW.view_geometry); ENDFQ;Q triggervwgcfi_view_name_updateviews_geometry_columns_field_infosCREATE TRIGGER vwgcfi_view_name_update BEFORE UPDATE OF 'view_name' ON 'views_geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on views_geometry_columns_field_infos violates constraint: view_name value must not contain a single quote') WHERE NEW.view_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on views_geometry_columns_field_infos violates constraint: view_name value must not contain a double quote') WHERE NEW.view_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on views_geometry_columns_field_infos violates constraint: view_name value must be lower case') WHERE NEW.view_name <> lower(NEW.view_name); END8P;Qotriggervwgcfi_view_name_insertviews_geometry_columns_field_infosCREATE TRIGGER vwgcfi_view_name_insert BEFORE INSERT ON 'views_geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on views_geometry_columns_field_infos violates constraint: view_name value must not contain a single quote') WHERE NEW.view_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_field_infos violates constraint: view_name value must not contain a double quote') WHERE NEW.view_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_field_infos violates constraint: view_name value must be lower case') WHERE NEW.view_name <> lower(NEW.view_name); ENDtOGEgtriggergcfi_f_geometry_column_updategeometry_columns_field_infosCREATE TRIGGER gcfi_f_geometry_column_update BEFORE UPDATE OF 'f_geometry_column' ON 'geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on geometry_columns_field_infos violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'update on geometry_columns_field_infos violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'update on geometry_columns_field_infos violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); END  S  CQUtriggervtgcfi_virt_geometry_updatevirts_geometry_columns_field_infosCREATE TRIGGER vtgcX=73triggergctm_f_table_name_insertgeometry_columns_timeCREATE TRIGGER gctm_f_table_name_insert BEFORE INSERT ON 'geometry_columns_time' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on geometry_columns_time violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on geometry_columns_time violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on geometry_columns_time violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); END\VCQ/triggervtgcfi_virt_geometry_insertvirts_geometry_columns_field_infosCREATE TRIGGER vtgcfi_virt_geometry_insert BEFORE INSERT ON 'virts_geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on virts_geometry_columns_field_infos violates constraint: virt_geometry value must not contain a single quote') WHERE NEW.virt_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_field_infos violates constraint: virt_geometry value must not contain a double quote') WHERE NEW.virt_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_field_infos violates constraint: virt_geometry value must be lower case') WHERE NEW.virt_geometry <> lower(NEW.virt_geometry); ENDFU;Q triggervtgcfi_virt_name_updatevirts_geometry_columns_field_infosCREATE TRIGGER vtgcfi_virt_name_update BEFORE UPDATE OF 'virt_name' ON 'virts_geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on virts_geometry_columns_field_infos violates constraint: virt_name value must not contain a single quote') WHERE NEW.virt_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_field_infos violates constraint: virt_name value must not contain a double quote') WHERE NEW.virt_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_field_infos violates constraint: virt_name value must be lower case') WHERE NEW.virt_name <> lower(NEW.virt_name); END8T;Qotriggervtgcfi_virt_name_insertvirts_geometry_columns_field_infosCREATE TRIGGER vtgcfi_virt_name_insert BEFORE INSERT ON 'virts_geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on virts_geometry_columns_field_infos violates constraint: virt_name value must not contain a single quote') WHERE NEW.virt_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_field_infos violates constraint: virt_name value must not contain a double quote') WHERE NEW.virt_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_field_infos violates constraint: virt_name value must be lower case') WHERE NEW.virt_name <> lower(NEW.virt_name); ENDoWCQUtriggervtgcfi_virt_geometry_updatevirts_geometry_columns_field_infosCREATE TRIGGER vtgcfi_virt_geometry_update BEFORE UPDATE OF 'virt_geometry' ON 'virts_geometry_columns_field_infos' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on virts_geometry_columns_field_infos violates constraint: virt_geometry value must not contain a single quote') WHERE NEW.virt_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_field_infos violates constraint: virt_geometry value must not contain a double quote') WHERE NEW.virt_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_field_infos violates constraint: virt_geometry value must be lower case') WHERE NEW.virt_geometry <> lower(NEW.virt_geometry); END }II o Q[G7/triggergctm_f_geometry_column_updategeometry_columns_timeCREATE TRIGGER gctm_f_geometry_column_update BEFORE UPDATE OF 'f_geometry_column' ON 'geometry_columns_time' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on geometry_columns_time violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'update on geometry_columns_time violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'update on geometry_columns_time violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); END;ZG7triggergctm_f_geometry_column_insertgeometry_columns_timeCREATE TRIGGER gctm_f_geometry_column_insert BEFORE INSERT ON 'geometry_columns_time' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on geometry_columns_time violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'insert on geometry_columns_time violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'insert on geometry_columns_time violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); ENDY=7Utriggergctm_f_table_name_updategeometry_columns_timeCREATE TRIGGER gctm_f_table_name_update BEFORE UPDATE OF 'f_table_name' ON 'geometry_columns_time' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on geometry_columns_time violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on geometry_columns_time violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on geometry_columns_time violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); ENDP=73triggergctm_f_table_name_insertgeometry_columns_timeCREATE TRIGGER gct]=7Utriggergcau_f_table_name_updategeometry_columns_authCREATE TRIGGER gcau_f_table_name_update BEFORE UPDATE OF 'f_table_name' ON 'geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on geometry_columns_auth violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on geometry_columns_auth violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on geometry_columns_auth violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); END\=73triggergcau_f_table_name_insertgeometry_columns_authCREATE TRIGGER gcau_f_table_name_insert BEFORE INSERT ON 'geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on geometry_columns_auth violates constraint: f_table_name value must not contain a single quote') WHERE NEW.f_table_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on geometry_columns_auth violates constraint: f_table_name value must not contain a double quote') WHERE NEW.f_table_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on geometry_columns_auth violates constraint: f_table_name value must be lower case') WHERE NEW.f_table_name <> lower(NEW.f_table_name); END t 0 Zt9bCCwtriggervwgcau_view_geometry_insertviews_geometry_columns_authCREATE TRIGGER vwgcau_view_geometry_insert BEFORE INSERT ON 'views_geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on views_geometry_columns_auth violates constraint: view_geometry value must not contain a single quote') WHERE NEW.view_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_auth violates constraint: view_geometry value must not contain a double quote') WHERE NEW.view_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_auth violates constraint: view_geometry value must be lower case') WHERE NEW.view_geometry <> lower(NEW.view_geometry); END`;C7triggervwgcau_view_name_insertviews_geometry_columns_authCREATE TRIGGER vwgcau_view_name_insert BEFORE INSERT ON 'views_geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on views_geometry_columns_auth violates constraint: view_name value must not contain a single quote') WHERE NEW.view_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_auth violates constraint: view_name value must not contain a double quote') WHERE NEW.view_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on views_geometry_columns_auth violates constraint: view_name value must be lower case') WHERE NEW.view_name <> lower(NEW.view_name); ENDQ_G7/triggergcau_f_geometry_column_updategeometry_columns_authCREATE TRIGGER gcau_f_geometry_column_update BEFORE UPDATE OF 'f_geometry_column' ON 'geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on geometry_columns_auth violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'update on geometry_columns_auth violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'update on geometry_columns_auth violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); END;^G7triggergcau_f_geometry_column_insertgeometry_columns_authCREATE TRIGGER gcau_f_geometry_column_insert BEFORE INSERT ON 'geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on geometry_columns_auth violates constraint: f_geometry_column value must not contain a single quote') WHERE NEW.f_geometry_column LIKE ('%''%'); SELECT RAISE(ABORT,'insert on geometry_columns_auth violates constraint: f_geometry_column value must not contain a double quote') WHERE NEW.f_geometry_column LIKE ('%"%'); SELECT RAISE(ABORT,'insert on geometry_columns_auth violates constraint: f_geometry_column value must be lower case') WHERE NEW.f_geometry_column <> lower(NEW.f_geometry_column); END#a;CStriggervwgcau_view_name_updateviews_geometry_columns_authCREATE TRIGGER vwgcau_view_name_update BEFORE UPDATE OF 'view_name' ON 'views_geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on views_geometry_columns_auth violates constraint: view_name value must not contain a single quote') WHERE NEW.view_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on views_geometry_columns_auth violates constraint: view_name value must not contain a double quote') WHERE NEW.view_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on views_geometry_columns_auth violates constraint: view_name value must be lower case') WHERE NEW.view_name <> lower(NEW.view_name); END g 0 6gLgCCtriggervtgcau_virt_geometry_updatevirts_geometry_columns_authCREATE TRIGGER vtgcau_virt_geometry_update BEFORE UPDATE OF 'virt_geometry' ON 'virts_geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on virts_geometry_columns_auth violates constraint: virt_geometry value must not contain a single quote') WHERE NEW.virt_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_auth violates constraint: virt_geometry value must not contain a double quote') WHERE NEW.virt_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_auth violates constraint: virt_geometry value must be lower case') WHERE NEW.virt_geometry <> lower(NEW.virt_geometry); END9fCCwtriggervtgcau_virt_geometry_insertvirts_geometry_columns_authCREATE TRIGGER vtgcau_virt_geometry_insert BEFORE INSERT ON 'virts_geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on virts_geometry_columns_auth violates constraint: virt_geometry value must not contain a single quote') WHERE NEW.virt_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_auth violates constraint: virt_geometry value must not contain a double quote') WHERE NEW.virt_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_auth violates constraint: virt_geometry value must be lower case') WHERE NEW.virt_geometry <> lower(NEW.virt_geometry); END#e;CStriggervtgcau_virt_name_updatevirts_geometry_columns_authCREATE TRIGGER vtgcau_virt_name_update BEFORE UPDATE OF 'virt_name' ON 'virts_geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on virts_geometry_columns_auth violates constraint: virt_name value must not contain a single quote') WHERE NEW.virt_name LIKE ('%''%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_auth violates constraint: virt_name value must not contain a double quote') WHERE NEW.virt_name LIKE ('%"%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_auth violates constraint: virt_name value must be lower case') WHERE NEW.virt_name <> lower(NEW.virt_name); ENDd;C7triggervtgcau_virt_name_insertvirts_geometry_columns_authCREATE TRIGGER vtgcau_virt_name_insert BEFORE INSERT ON 'virts_geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'insert on virts_geometry_columns_auth violates constraint: virt_name value must not contain a single quote') WHERE NEW.virt_name LIKE ('%''%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_auth violates constraint: virt_name value must not contain a double quote') WHERE NEW.virt_name LIKE ('%"%'); SELECT RAISE(ABORT,'insert on virts_geometry_columns_auth violates constraint: virt_name value must be lower case') WHERE NEW.virt_name <> lower(NEW.virt_name); ENDMcCCtriggervwgcau_view_geometry_updateviews_geometry_columns_authCREATE TRIGGER vwgcau_view_geometry_update BEFORE UPDATE OF 'view_geometry' ON 'views_geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on views_geometry_columns_auth violates constraint: view_geometry value must not contain a single quote') WHERE NEW.view_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'update on views_geometry_columns_auth violates constraint: view_geometry value must not contain a double quote') WHERE NEW.view_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'update on views_geometry_columns_auth violates constraint: view_geometry value must be lower case') WHERE NEW.view_geometry <> lower(NEW.view_geometry); END 1g ^zgj==7viewvector_layers_statisticsvector_layers_statisticsCREATE VIEW vector_layers_statistics AS SELECT 'SpatialTable' AS layer_type, f_table_name AS table_name, f_geometry_column AS geometry_column, last_verified AS last_verified, row_count AS row_count, extent_min_x AS extent_min_x, extent_min_y AS extent_min_y, extent_max_x AS extent_max_x, extent_max_y AS extent_max_y FROM geometry_columns_statistics UNION SELECT 'SpatialView' AS layer_type, view_name AS table_name, view_geometry AS geometry_column, last_verified AS last_verified, row_count AS row_count, extent_min_x AS extent_min_x, extent_min_y AS extent_min_y, extent_max_x AS extent_max_x, extent_max_y AS extent_max_y FROM views_geometry_columns_statistics UNION SELECT 'VirtualShape' AS layer_type, virt_name AS table_name, virt_geometry AS geometry_column, last_verified AS last_verified, row_count AS row_count, extent_min_x AS extent_min_x, extent_min_y AS extent_min_y, extent_max_x AS extent_max_x, extent_max_y AS extent_max_y FROM virts_geometry_columns_statisticsai11qviewvector_layers_authvector_layers_authCREATE VIEW vector_layers_auth AS SELECT 'SpatialTable' AS layer_type, f_table_name AS table_name, f_geometry_column AS geometry_column, read_only AS read_only, hidden AS hidden FROM geometry_columns_auth UNION SELECT 'SpatialView' AS layer_type, a.view_name AS table_name, a.view_geometry AS geometry_column, b.read_only AS read_only, a.hidden AS hidden FROM views_geometry_columns_auth AS a JOIN views_geometry_columns AS b ON (Upper(a.view_name) = Upper(b.view_name) AND Upper(a.view_geometry) = Upper(b.view_geometry)) UNION SELECT 'VirtualShape' AS layer_type, virt_name AS table_name, virt_geometry AS geometry_column, 1 AS read_only, hidden AS hidden FROM virts_geometry_columns_authPh''cviewvector_layersvector_layersCREATE VIEW vector_layers AS SELECT 'SpatialTable' AS layer_type, f_table_name AS table_name, f_geometry_column AS geometry_column, geometry_type AS geometry_type, coord_dimension AS coord_dimension, srid AS srid, spatial_index_enabled AS spatial_index_enabled FROM geometry_columns UNION SELECT 'SpatialView' AS layer_type, a.view_name AS table_name, a.view_geometry AS geometry_column, b.geometry_type AS geometry_type, b.coord_dimension AS coord_dimension, b.srid AS srid, b.spatial_index_enabled AS spatial_index_enabled FROM views_geometry_columns AS a LEFT JOIN geometry_columns AS b ON (Upper(a.f_table_name) = Upper(b.f_table_name) AND Upper(a.f_geometry_column) = Upper(b.f_geometry_column)) UNION SELECT 'VirtualShape' AS layer_type, virt_name AS table_name, virt_geometry AS geometry_column, geometry_type AS geometry_type, coord_dimension AS coord_dimension, srid AS srid, 0 AS spatial_index_enabled FROM virts_geometry_columnsCCtriggervtgcau_virt_geometry_updatevirts_geometry_columns_authCREATE TRIGGER vtgcau_virt_geometry_update BEFORE UPDATE OF 'virt_geometry' ON 'virts_geometry_columns_auth' FOR EACH ROW BEGIN SELECT RAISE(ABORT,'update on virts_geometry_columns_auth violates constraint: virt_geometry value must not contain a single quote') WHERE NEW.virt_geometry LIKE ('%''%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_auth violates constraint: virt_geometry value must not contain a double quote') WHERE NEW.virt_geometry LIKE ('%"%'); SELECT RAISE(ABORT,'update on virts_geometry_columns_auth violates constraint: virt_geometry value must be lower case') WHERE NEW.virt_geometry <> lower(NEW.virt_geometry); END  L ( Lwm55tableElementaryGeometriesElementaryGeometriesCREATE VIRTUAL TABLE ElementaryGeometries USING VirtualElementary()al%%tableSpatialIndexSpatialIndexCREATE VIRTUAL TABLE SpatialIndex USING VirtualSpatialIndex()Uk??=viewvector_layers_field_infosvector_layers_field_infosCREATE VIEW vector_layers_field_infos AS SELECT 'SpatialTable' AS layer_type, f_table_name AS table_name, f_geometry_column AS geometry_column, ordinal AS ordinal, column_name AS column_name, null_values AS null_values, integer_values AS integer_values, double_values AS double_values, text_values AS text_values, blob_values AS blob_values, max_size AS max_size, integer_min AS integer_min, integer_max AS integer_max, double_min AS double_min, double_max double_max FROM geometry_columns_field_infos UNION SELECT 'SpatialView' AS layer_type, view_name AS table_name, view_geometry AS geometry_column, ordinal AS ordinal, column_name AS column_name, null_values AS null_values, integer_values AS integer_values, double_values AS double_values, text_values AS text_values, blob_values AS blob_values, max_size AS max_size, integer_min AS integer_min, integer_max AS integer_max, double_min AS double_min, double_max double_max FROM views_geometry_columns_field_infos UNION SELECT 'VirtualShape' AS layer_type, virt_name AS table_name, virt_geometry AS geometry_column, ordinal AS ordinal, column_name AS column_name, null_values AS null_values, integer_values AS integer_values, double_values AS double_values, text_values AS text_values, blob_values AS blob_values, max_size AS max_size, integer_min AS integer_min, integer_max AS integer_max, double_min AS double_min, double_max double_max FROM virts_geometry_columns_field_infosgeoalchemy2-0.15.2/tests/data/spatialite_geopackage.gpkg000066400000000000000000003200001464355170400232340ustar00rootroot00000000000000SQLite format 3@ )$GP10).WJ&#   f WGS84epsgGEOGCS["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"]]@j1MRGF93 / Lambert-93epsgjPROJCS["RGF93 / Lambert-93",GEOGCS["RGF93",DATUM["Reseau_Geodesique_Francais_1993",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6171"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4171"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["latitude_of_origin",46.5],PARAMETER["central_meridian",3],PARAMETER["standard_parallel_1",49],PARAMETER["standard_parallel_2",44],PARAMETER["false_easting",700000],PARAMETER["false_northing",6600000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","2154"]](5Undefined GeographicNONEUndefined(3Undefined CartesianNONEUndefined                   v ! y ./ xr; ''Wtablegpkg_metadatagpkg_metadataCREATE TABLE gpkg_metadata ( id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL UNIQUE, md_scope TEXT NOT NULL DEFAULT 'dataset', md_standard_uri TEXT NOT NULL, mime_type TEXT NOT NULL DEFAULT 'text/xml', metadata TEXT NOT NULL)WkEindexsqlite_autoindex_gpkg_data_column_constraints_1gpkg_data_column_constraints`EECtablegpkg_data_column_constraintsgpkg_data_column_constraintsCREATE TABLE gpkg_data_column_constraints ( constraint_name TEXT NOT NULL, constraint_type TEXT NOT NULL, value TEXT, min NUMERIC, min_is_inclusive BOOLEAN, max NUMERIC, max_is_inclusive BOOLEAN, description TEXT, CONSTRAINT gdcc_ntv UNIQUE (constraint_name, constraint_type, value))A U/indexsqlite_autoindex_gpkg_data_columns_1gpkg_data_columns` //otablegpkg_data_columnsgpkg_data_columns CREATE TABLE gpkg_data_columns ( table_name TEXT NOT NULL, column_name TEXT NOT NULL, name TEXT, title TEXT, description TEXT, mime_type TEXT, constraint_name TEXT, CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name), CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES gpkg_contents(table_name))M --Mtablegpkg_tile_matrixgpkg_tile_matrix CREATE TABLE gpkg_tile_matrix ( table_name TEXT NOT NULL, zoom_level INTEGER NOT NULL, matrix_width INTEGER NOT NULL, matrix_height INTEGER NOT NULL, tile_width INTEGER NOT NULL, tile_height INTEGER NOT NULL, pixel_x_size DOUBLE NOT NULL, pixel_y_size DOUBLE NOT NULL, CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level), CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) REFERENCES gpkg_contents(table_name))? S-indexsqlite_autoindex_gpkg_tile_matrix_1gpkg_tile_matrix &55otablegpkg_tile_matrix_setgpkg_tile_matrix_set CREATE TABLE gpkg_tile_matrix_set ( table_name TEXT NOT NULL PRIMARY KEY, srs_id INTEGER NOT NULL, min_x DOUBLE NOT NULL, min_y DOUBLE NOT NULL, max_x DOUBLE NOT NULL, max_y DOUBLE NOT NULL, CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) REFERENCES gpkg_contents(table_name), CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys (srs_id))G [5indexsqlite_autoindex_gpkg_tile_matrix_set_1gpkg_tile_matrix_set 773tablegpkg_geometry_columnsgpkg_geometry_columnsCREATE TABLE gpkg_geometry_columns ( table_name TEXT NOT NULL, column_name TEXT NOT NULL, geometry_type_name TEXT NOT NULL, srs_id INTEGER NOT NULL, z TINYINT NOT NULL, m TINYINT NOT NULL, CONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name), CONSTRAINT uk_gc_table_name UNIQUE (table_name), CONSTRAINT fk_gc_tn FOREIGN KEY (table_name) REFERENCES gpkg_contents(table_name), CONSTRAINT fk_gc_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys (srs_id))I]7indexsqlite_autoindex_gpkg_geometry_columns_2gpkg_geometry_columnsI]7indexsqlite_autoindex_gpkg_geometry_columns_1gpkg_geometry_columns/''tablegpkg_contentsgpkg_contentsCREATE TABLE gpkg_contents ( table_name TEXT NOT NULL PRIMARY KEY, data_type TEXT NOT NULL, identifier TEXT UNIQUE, description TEXT DEFAULT '', last_change DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ',CURRENT_TIMESTAMP)), min_x DOUBLE, min_y DOUBLE, max_x DOUBLE, max_y DOUBLE, srs_id INTEGER, CONSTRAINT fk_gc_r_srid FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys(srs_id))9M'indexsqlite_autoindex_gpkg_contents_2gpkg_contents9M'indexsqlite_autoindex_gpkg_contents_1gpkg_contents 555tablegpkg_spatial_ref_sysgpkg_spatial_ref_sysCREATE TABLE gpkg_spatial_ref_sys ( srs_name TEXT NOT NULL, srs_id INTEGER NOT NULL PRIMARY KEY, organization TEXT NOT NULL, organization_coordsys_id INTEGER NOT NULL, definition TEXT NOT NULL, description TEXT )   Rg Y IU-triggergpkg_tile_matrix_pixel_y_size_insertgpkg_tile_matrixCREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' BEFORE INSERT ON 'gpkg_tile_matrix' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' violates constraint: pixel_y_size must be greater than 0') WHERE NOT (NEW.pixel_y_size > 0); ENDIU-triggergpkg_tile_matrix_pixel_x_size_insertgpkg_tile_matrixCREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' BEFORE INSERT ON 'gpkg_tile_matrix' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' violates constraint: pixel_x_size must be greater than 0') WHERE NOT (NEW.pixel_x_size > 0); ENDTU-1triggergpkg_tile_matrix_matrix_width_updategpkg_tile_matrixCREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' violates constraint: matrix_width cannot be less than 1') WHERE (NEW.matrix_width < 1); ENDDU-triggergpkg_tile_matrix_matrix_width_insertgpkg_tile_matrixCREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' BEFORE INSERT ON 'gpkg_tile_matrix' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' violates constraint: matrix_width cannot be less than 1') WHERE (NEW.matrix_width < 1); ENDJQ-!triggergpkg_tile_matrix_zoom_level_updategpkg_tile_matrixCREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' violates constraint: zoom_level cannot be less than 0') WHERE (NEW.zoom_level < 0); END<Q-triggergpkg_tile_matrix_zoom_level_insertgpkg_tile_matrixCREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' BEFORE INSERT ON 'gpkg_tile_matrix' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' violates constraint: zoom_level cannot be less than 0') WHERE (NEW.zoom_level < 0); END}++1tablegpkg_extensionsgpkg_extensionsCREATE TABLE gpkg_extensions ( table_name TEXT, column_name TEXT, extension_name TEXT NOT NULL, definition TEXT NOT NULL, scope TEXT NOT NULL, CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name))=Q+indexsqlite_autoindex_gpkg_extensions_1gpkg_extensionsb;;[tablegpkg_metadata_referencegpkg_metadata_referenceCREATE TABLE gpkg_metadata_reference ( reference_scope TEXT NOT NULL, table_name TEXT, column_name TEXT, row_id_value INTEGER, timestamp DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ',CURRENT_TIMESTAMP)), md_file_id INTEGER NOT NULL, md_parent_id INTEGER, CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES gpkg_metadata(id), CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES gpkg_metadata(id)) YU-;triggergpkg_tile_matrix_pixel_x_size_updategpkg_tile_matrixCREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' violates constraint: pixel_x_size must be greater than 0') WHERE NOT (NEW.pixel_x_size > 0); END9M'indexsqlite_autoindex_gpkg_metadata_1gpkg_metadataYW-9triggergpkg_tile_matrix_matrix_height_updategpkg_tile_matrixCREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' violates constraint: matrix_height cannot be less than 1') WHERE (NEW.matrix_height < 1); ENDHW-triggergpkg_tile_matrix_matrix_height_insertgpkg_tile_matrixCREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' BEFORE INSERT ON 'gpkg_tile_matrix' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' violates constraint: matrix_height cannot be less than 1') WHERE (NEW.matrix_height < 1); END   { ~{O#a; triggergpkg_metadata_reference_column_name_insertgpkg_metadata_referenceCREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' BEFORE INSERT ON 'gpkg_metadata_reference' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference violates constraint: column name must be NULL when reference_scope is "geopackage", "table" or "row"') WHERE (NEW.reference_scope IN ('geopackage','table','row') AND NEW.column_name IS NOT NULL); SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference violates constraint: column name must be defined for the specified table when reference_scope is "column" or "row/col"') WHERE (NEW.reference_scope IN ('column','row/col') AND NOT NEW.table_name IN (SELECT name FROM SQLITE_MASTER WHERE type = 'table' AND name = NEW.table_name AND sql LIKE ('%' || NEW.column_name || '%'))); END`"i;'triggergpkg_metadata_reference_reference_scope_updategpkg_metadata_referenceCREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference violates constraint: reference_scope must be one of "geopackage", "table", "column", "row", "row/col"') WHERE NOT NEW.reference_scope IN ('geopackage','table','column','row','row/col'); ENDK!i;}triggergpkg_metadata_reference_reference_scope_insertgpkg_metadata_referenceCREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' BEFORE INSERT ON 'gpkg_metadata_reference' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference violates constraint: reference_scope must be one of "geopackage", "table", "column", "row", "row/col"') WHERE NOT NEW.reference_scope IN ('geopackage','table','column','row','row/col'); END G'Ktriggergpkg_metadata_md_scope_updategpkg_metadataCREATE TRIGGER 'gpkg_metadata_md_scope_update' BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table gpkg_metadata violates constraint: md_scope must be one of undefined | fieldSession | collectionSession | series | dataset | featureType | feature | attributeType | attribute | tile | model | catalog | schema | taxonomy | software | service | collectionHardware | nonGeographicDataset | dimensionGroup') WHERE NOT(NEW.md_scope IN ('undefined','fieldSession','collectionSession','series','dataset', 'featureType','feature','attributeType','attribute','tile','model', 'catalog','schema','taxonomy','software','service', 'collectionHardware','nonGeographicDataset','dimensionGroup')); END G'/triggergpkg_metadata_md_scope_insertgpkg_metadataCREATE TRIGGER 'gpkg_metadata_md_scope_insert' BEFORE INSERT ON 'gpkg_metadata' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates constraint: md_scope must be one of undefined | fieldSession | collectionSession | series | dataset | featureType | feature | attributeType | attribute | tile | model | catalog | schema | taxonomy | software | service | collectionHardware | nonGeographicDataset | dimensionGroup') WHERE NOT(NEW.md_scope IN ('undefined','fieldSession','collectionSession','series','dataset', 'featureType','feature','attributeType','attribute','tile','model', 'catalog','schema','taxonomy','software','service', 'collectionHardware','nonGeographicDataset','dimensionGroup')); ENDYU-;triggergpkg_tile_matrix_pixel_y_size_updategpkg_tile_matrixCREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' violates constraint: pixel_y_size must be greater than 0') WHERE NOT (NEW.pixel_y_size > 0); END %kQ% ?)&c;?triggergpkg_metadata_reference_row_id_value_updategpkg_metadata_referenceCREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference violates constraint: row_id_value must be NULL when reference_scope is "geopackage", "table" or "column"') WHERE NEW.reference_scope IN ('geopackage','table','column') AND NEW.row_id_value IS NOT NULL; SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference violates constraint: row_id_value must exist in specified table when reference_scope is "row" or "row/col"') WHERE NEW.reference_scope IN ('row','row/col') AND NOT EXISTS (SELECT rowid FROM (SELECT NEW.table_name AS table_name) WHERE rowid = NEW.row_id_value); END%c;triggergpkg_metadata_reference_row_id_value_insertgpkg_metadata_referenceCREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' BEFORE INSERT ON 'gpkg_metadata_reference' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference violates constraint: row_id_value must be NULL when reference_scope is "geopackage", "table" or "column"') WHERE NEW.reference_scope IN ('geopackage','table','column') AND NEW.row_id_value IS NOT NULL; SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference violates constraint: row_id_value must exist in specified table when reference_scope is "row" or "row/col"') WHERE NEW.reference_scope IN ('row','row/col') AND NOT EXISTS (SELECT rowid FROM (SELECT NEW.table_name AS table_name) WHERE rowid = NEW.row_id_value); END]$a;)triggergpkg_metadata_reference_column_name_updategpkg_metadata_referenceCREATE TRIGGER 'gpkg_metadata_reference_column_name_update' BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference violates constraint: column name must be NULL when reference_scope is "geopackage", "table" or "row"') WHERE (NEW.reference_scope IN ('geopackage','table','row') AND NEW.column_nameIS NOT NULL); SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference violates constraint: column name must be defined for the specified table when reference_scope is "column" or "row/col"') WHERE (NEW.reference_scope IN ('column','row/col') AND NOT NEW.table_name IN (SELECT name FROM SQLITE_MASTER WHERE type = 'table' AND name = NEW.table_name AND sql LIKE ('%' || NEW.column_name || '%'))); END5  GB,I7triggergpkg_geometry_columns_m_updategpkg_geometry_columnsCREATE TRIGGER 'gpkg_geometry_columns_m_update' BEFORE UPDATE OF 'm' ON 'gpkg_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table gpkg_geometry_columns violates constraint: m must be one of 0, 1 or 2') WHERE NOT NEW.m IN (0, 1, 2); END<+I7triggergpkg_geometry_columns_m_insertgpkg_geometry_columnsCREATE TRIGGER 'gpkg_geometry_columns_m_insert' BEFORE INSERT ON 'gpkg_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table gpkg_geometry_columns violates constraint: m must be one of 0, 1 or 2') WHERE NOT(NEW.m IN (0, 1, 2)); ENDB*I7triggergpkg_geometry_columns_z_updategpkg_geometry_columnsCREATE TRIGGER 'gpkg_geometry_columns_z_update' BEFORE UPDATE OF 'z' ON 'gpkg_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table gpkg_geometry_columns violates constraint: z must be one of 0, 1 or 2') WHERE NOT NEW.z IN (0, 1, 2); END<)I7triggergpkg_geometry_columns_z_insertgpkg_geometry_columnsCREATE TRIGGER 'gpkg_geometry_columns_z_insert' BEFORE INSERT ON 'gpkg_geometry_columns' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table gpkg_geometry_columns violates constraint: z must be one of 0, 1 or 2') WHERE NOT(NEW.z IN (0, 1, 2)); END(];1triggergpkg_metadata_reference_timestamp_updategpkg_metadata_referenceCREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference violates constraint: timestamp must be a valid time in ISO 8601 "yyyy-mm-ddThh-mm-ss.cccZ" form') WHERE NOT (NEW.timestamp GLOB '[1-2][0-9][0-9][0-9]-[0-1][0-9]-[1-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9].[0-9][0-9][0-9]Z' AND strftime('%s',NEW.timestamp) NOT NULL); END'];triggergpkg_metadata_reference_timestamp_insertgpkg_metadata_referenceCREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' BEFORE INSERT ON 'gpkg_metadata_reference' FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference violates constraint: timestamp must be a valid time in ISO 8601 "yyyy-mm-ddThh-mm-ss.cccZ" form') WHERE NOT (NEW.timestamp GLOB '[1-2][0-9][0-9][0-9]-[0-1][0-9]-[1-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9].[0-9][0-9][0-9]Z' AND strftime('%s',NEW.timestamp) NOT NULL); ENDgeoalchemy2-0.15.2/tests/data/spatialite_lt_4.sqlite000066400000000000000000001320001464355170400223620ustar00rootroot00000000000000SQLite format 3@ -.S` ,?S-indexsqlite_autoindex_geometry_columns_1geometry_columnsP++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)11]tablespatialite_historyspatialite_historyCREATE TABLE spatialite_history ( event_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, table_name TEXT NOT NULL, geometry_column TEXT, event TEXT NOT NULL, timestamp TEXT NOT NULL, ver_sqlite TEXT NOT NULL, ver_splite TEXT NOT NULL)[++mtablespatial_ref_sysspatial_ref_sysCREATE TABLE spatial_ref_sys ( srid INTEGER NOT NULL PRIMARY KEY, auth_name TEXT NOT NULL, auth_srid INTEGER NOT NULL, re)'&#  ##&)),, zj 1=epsgjRGF93 / Lambert-93+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defsPROJCS["RGF93 / Lambert-93",GEOGCS["RGF93",DATUM["Reseau_Geodesique_Francais_1993",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6171"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4171"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",49],PARAMETER["standard_parallel_2",44],PARAMETER["latitude_of_origin",46.5],PARAMETER["central_meridian",3],PARAMETER["false_easting",700000],PARAMETER["false_northing",6600000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["X",EAST],AXIS["Y",NORTH],AUTHORITY["EPSG","2154"]]# NONEUNKNOj WT+E3spatial_ref_systable successfully populated2014-01-22 08:27:573.7.93.1.0-RC2S-A3geometry_columnstable successfully created2014-01-22 08:27:573.7.93.1.0-RC2R+A3spatial_ref_systable successfully created2014-01-22 08:27:563.7.93.1.0-RC2 1spatialite_history                   epsg jepsgj NONE jjU_9indexsqlite_autoindex_views_layer_statistics_1views_layer_statistics? S-indexsqlite_autoindex_layer_statistics_1layer_statistics} ---tablelayer_statisticslayer_statisticsCREATE TABLE layer_statistics ( raster_layer INTEGER NOT NULL, table_name TEXT NOT NULL, geometry_column TEXT NOT NULL, row_count INTEGER, extent_min_x DOUBLE, extent_min_y DOUBLE, extent_max_x DOUBLE, extent_max_y DOUBLE, CONSTRAINT pk_layer_statistics PRIMARY KEY (raster_layer, table_name, geometry_column), CONSTRAINT fk_layer_statistics FOREIGN KEY (table_name, geometry_column) REFERENCES geometry_columns (f_table_name, f_geometry_column) ON DELETE CASCADE) wwU_9indexsqlite_autoindex_virts_layer_statistics_1virts_layer_statisticsK_9indexsqlite_autoindex_views_layer_statistics_1views_layer_statisticsd99ctableviews_layer_statisticsviews_layer_statisticsCREATE TABLE views_layer_statistics ( view_name TEXT NOT NULL, view_geometry TEXT NOT NULL, row_count INTEGER, extent_min_x DOUBLE, extent_min_y DOUBLE, extent_max_x DOUBLE, extent_max_y DOUBLE, CONSTRAINT pk_views_layer_statistics PRIMARY KEY (view_name, view_geometry), CONSTRAINT fk_views_layer_statistics FOREIGN KEY (view_name, view_geometry) REFERENCES views_geometry_columns (view_name, view_geometry) ON DELETE CASCADE) $"$k --Mtablegeometry_columnsgeometry_columnsCREATE TABLE geometry_columns ( f_table_name TEXT NOT NULL, f_geometry_column TEXT NOT NULL, type TEXT NOT NULL, coord_dimension TEXT NOT NULL, srid INTEGER NOT NULL, spatial_index_enabled INTEGER NOT NULL, CONSTRAINT pk_geom_cols PRIMARY KEY (f_table_name, f_geometry_column), CONSTRAINT fk_gc_srs FOREIGN KEY (srid) REFERENCES spatial_ref_sys (srid))P++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)11]tablespatialite_historyspatialite_historyCREATE TABLE spatialite_history ( event_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, table_name TEXT NOT NULL, geometry_column TEXT, event TEXT NOT NULL, timestamp TEXT NOT NULL, ver_sqlite TEXT NOT NULL, ver_splite TEXT NOT NULL)[++mtablespatial_ref_sysspatial_ref_sysCREATE TABLE spatial_ref_sys ( srid INTEGER NOT NULL PRIMARY KEY, auth_name TEXT NOT NULL, auth_srid INTEGER NOT NULL, ref_sys_name TEXT, proj4text TEXT NOT NULL, srs_wkt TEXT) eeIS-indexsqlite_autoindex_layer_statistics_1layer_statisticsI ]7indexsqlite_autoindex_geometry_columns_auth_1geometry_columns_auth 7 77 tablegeometry_columns_authgeometry_columns_auth CREATE TABLE geometry_columns_auth ( f_table_name VARCHAR(256) NOT NULL, f_geometry_column VARCHAR(256) NOT NULL, read_only INTEGER NOT NULL, hidden INTEGER NOT NULL, CONSTRAINT pk_gc_auth PRIMARY KEY (f_table_name, f_geometry_column), CONSTRAINT fk_gc_auth FOREIGN KEY (f_table_name, f_geometry_column) REFERENCES geometry_columns (f_table_name, f_geometry_column) ON DELETE CASCADE)K _9indexsqlite_autoindex_virts_geometry_columns_1virts_geometry_columns fp\'9indexidx_virtssridvirts_geometry_columnsCREATE INDEX idx_virtssrid ON virts_geometry_columns (srid)'99indexidx_viewsjoinviews_geometry_columnsCREATE INDEX idx_viewsjoin ON views_geometry_columns (f_table_name, f_geometry_column)d--}indexidx_srid_geocolsgeometry_columnsCREATE INDEX idx_srid_geocols ON geometry_columns (srid)K_9indexsqlite_autoindex_virts_layer_statistics_1virts_layer_statisticsd99ctablevirts_layer_statisticsvirts_layer_statisticsCREATE TABLE virts_layer_statistics ( virt_name TEXT NOT NULL, virt_geometry TEXT NOT NULL, row_count INTEGER, extent_min_x DOUBLE, extent_min_y DOUBLE, extent_max_x DOUBLE, extent_max_y DOUBLE, CONSTRAINT pk_virts_layer_statistics PRIMARY KEY (virt_name, virt_geometry), CONSTRAINT fk_virts_layer_statistics FOREIGN KEY (virt_name, virt_geometry) REFERENCES virts_geometry_columns (virt_name, virt_geometry) ON DELETE CASCADE) 55<Y99Mtablevirts_geometry_columnsvirts_geometry_columns CREATE TABLE virts_geometry_columns ( virt_name TEXT NOT NULL, virt_geometry TEXT NOT NULL, type VARCHAR(30) NOT NULL, srid INTEGER NOT NULL, CONSTRAINT pk_geom_cols_virts PRIMARY KEY (virt_name, virt_geometry), CONSTRAINT fk_vgc_srid FOREIGN KEY (srid) REFERENCES spatial_ref_sys (srid))K_9indexsqlite_autoindex_views_geometry_columns_1views_geometry_columns ^99Wtableviews_geometry_columnsviews_geometry_columnsCREATE TABLE views_geometry_columns ( view_name TEXT NOT NULL, view_geometry TEXT NOT NULL, view_rowid TEXT NOT NULL, f_table_name VARCHAR(256) NOT NULL, f_geometry_column VARCHAR(256) NOT NULL, CONSTRAINT pk_geom_cols_views PRIMARY KEY (view_name, view_geometry), CONSTRAINT fk_views_geom_cols FOREIGN KEY (f_table_name, f_geometry_column) REFERENCES geometry_columns (f_table_name, f_geometry_column) ON DELETE CASCADE)?S-indexsqlite_autoindex_geometry_columns_1geometry_columns  .33viewv2_pumpstation_viewv2_pumpstation_viewCREATE VIEW v2_pumpstation_view AS SELEC!a%%tableSpatialIndexSpatialIndexCREATE VIRTUAL TABLE SpatialIndex USING VirtualSpatialIndex()2//viewgeom_cols_ref_sysgeom_cols_ref_sysCREATE VIEW geom_cols_ref_sys AS SELECT f_table_name, f_geometry_column, type, coord_dimension, spatial_ref_sys.srid AS srid, auth_name, auth_srid, ref_sys_name, proj4text FROM geometry_columns, spatial_ref_sys WHERE geometry_columns.srid = spatial_ref_sys.srid3+1indexidx_spatial_ref_sysspatial_ref_sysCREATE UNIQUE INDEX idx_spatial_ref_sys ON spatial_ref_sys (auth_srid, auth_name)k'9indexidx_virtssridvirts_geometry_columnsCREATE INDEX idx_virtssrid ON virts_geometry_columns (srid)'99indexidx_viewsjoinviews_geometry_columnsCREATE INDEX idx_viewsjoin ON views_geometry_columns (f_table_name, f_geometry_column)d--}indexidx_srid_geocolsgeometry_columnsCREATE INDEX idx_srid_geocols ON geometry_columns (srid) aazj 1=epsgjRGF93 / Lambert-93+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defsPROJCS["RGF93 / Lambert-93",GEOGCS["RGF93",DATUM["Reseau_Geodesique_Francais_1993",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6171"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4171"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",49],PARAMETER["standard_parallel_2",44],PARAMETER["latitude_of_origin",46.5],PARAMETER["central_meridian",3],PARAMETER["false_easting",700000],PARAMETER["false_northing",6600000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["X",EAST],AXIS["Y",NORTH],AUTHORITY["EPSG","2154"]]# NONEUNKNOWN SRS 7fS epsgWGS 84+proj=longlat +datum=WGS84 +no_defsGEOGCS["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"]]T pump.ROWID AS ROWID, pump.id AS pump_id, pump.display_name AS pump_display_name, pump.code AS pump_code, pump.classification AS pump_classification, pump.type AS pump_type, pump.sewerage AS pump_sewerage, pump.start_level AS pump_start_level, pump.lower_stop_level AS pump_lower_stop_level, pump.upper_stop_level AS pump_upper_stop_level, pump.capacity AS pump_capacity, pump.zoom_category AS pump_zoom_category, pump.connection_node_start_id AS pump_connection_node_start_id, pump.connection_node_end_id AS pump_connection_node_end_id, MakeLine( start_node.the_geom, end_node.the_geom ) AS the_geom FROM v2_pumpstation AS pump , v2_connection_nodes AS start_node , v2_connection_nodes AS end_node WHERE pump.connection_node_start_id = start_node.id AND pump.connection_node_end_id = end_node.idtion_type AS orf_friction_type, orf.discharge_coefficient_positive AS orf_discharge_coefficient_positive, orf.discharge_coefficient_negative AS orf_discharge_coefficient_negative, orf.zoom_category AS orf_zoom_category, orf.crest_type AS orf_crest_type, orf.connection_node_start_id AS orf_connection_node_start_id, orf.connection_node_end_id AS orf_connection_node_end_id, def.id AS def_id, def.shape AS def_shape, def.width AS def_width, def.height AS def_height, def.code AS def_code, MakeLine( start_node.the_geom, end_node.the_geom) AS the_geom FROM v2_orifice AS orf , v2_cross_section_definition AS def , v2_connection_nodes AS start_node , v2_connection_nodes AS end_node where orf.connection_node_start_id = start_node.id AND orf.connection_node_end_id = end_node.id AND orf.cross_section_definition_id = def.id G++oviewv2_manhole_viewv2_manhole_viewCREATE VIEW v2_manhole_view AS SELECT manh.ROWID$.++viewv2_orifice_viewv2_orifice_viewCREATE VIEW v2_orifice_view AS SELECT orf.ROWID AS ROWID, orf.id AS orf_id, orf.display_name AS orf_display_name, orf.code AS orf_code, orf.max_capacity AS orf_max_capacity, orf.crest_level AS orf_crest_level, orf.sewerage AS orf_sewerage, orf.cross_section_definition_id AS orf_cross_section_definition_id, orf.friction_value AS orf_friction_value, orf.fric" AS ROWID, manh.id AS manh_id, manh.display_name AS manh_display_name, manh.code AS manh_code, manh.connection_node_id AS manh_connection_node_id, manh.shape AS manh_shape, manh.width AS manh_width, manh.length AS manh_length, manh.manhole_indicator AS manh_manhole_indicator, manh.calculation_type AS manh_calculation_type, manh.bottom_level AS manh_bottom_level, manh.surface_level AS manh_surface_level, manh.drain_level AS manh_drain_level, manh.sediment_level AS manh_sediment_level, manh.zoom_category AS manh_zoom_category, node.id AS node_id, node.storage_area AS node_storage_area, node.initial_waterlevel AS node_initial_waterlevel, node.code AS node_code, node.the_geom AS the_geom, node.the_geom_linestring AS node_the_geom_linestring FROM v2_manhole AS manh , v2_connection_nodes AS node WHERE manh.connection_node_id = node.id, pipe.friction_type AS pipe_friction_type, pipe.dist_calc_points AS pipe_dist_calc_points, pipe.material AS pipe_material, pipe.pipe_quality AS pipe_pipe_quality, pipe.original_length AS pipe_original_length, pipe.zoom_category AS pipe_zoom_category, pipe.connection_node_start_id AS pipe_connection_node_start_id, pipe.connection_node_end_id AS pipe_connection_node_end_id, def.id AS def_id, def.shape AS def_shape, def.width AS def_width, def.height AS def_height, def.code AS def_code, MakeLine( start_node.the_geom, end_node.the_geom) AS the_geom FROM v2_pipe AS pipe , v2_cross_section_definition AS def , v2_connection_nodes AS start_node , v2_connection_nodes AS end_node WHERE pipe.connection_node_start_id = start_node.id AND pipe.connection_node_end_id = end_node.id AND pipe.cross_section_definition_id = def.id :%%;viewv2_pipe_viewv2_pipe_viewCREATE VIEW v2_pipe_view AS SELECT pipe.ROWID AS ROWID, pipe.id AS pipe_id, pipe.display_name AS pipe_display_name, pipe.code AS pipe_code, pipe.profile_num AS pipe_profile_num, pipe.sewerage_type AS pipe_sewerage_type, pipe.calculation_type AS pipe_calculation_type, pipe.invert_level_start_point AS pipe_invert_level_start_point, pipe.invert_level_end_point AS pipe_invert_level_end_point, pipe.cross_section_definition_id AS pipe_cross_section_definition_id, pipe.friction_value AS pipe_friction_value% 331viewv2_imp_surface_viewv2_imp_surface_viewCREATE VIEW v2_imp_surface_view AS SELECT isurf.ROWID AS ROWID, isurfmap.id AS isurfmap_id, isurfmap.impervious_surface_id AS isurfmap_impervious_surface_id, isurfmap.connection_node_id AS isurfmap_connection_node_id, isurfmap.percentage AS isurfmap_percentage, MakeLine( ST_Centroid(isurf.the_geom), connection_node.the_geom ) AS the_geom FROM v2_impervious_surface AS isurf , v2_connection_nodes AS connection_node , v2_impervious_surface_map AS isurfmap WHERE isurfmap.connection_node_id = connection_node.id AND isurfmap.impervious_surface_id = isurf.iditants AS isurf_nr_of_inhabitants, isurf.dry_weather_flow AS isurf_dry_weather_flow, isurf.function AS isurf_function, isurf.area AS isurf_area, MakeLine( ST_Centroid(isurf.the_geom), connection_node.the_geom ) AS the_geom FROM v2_impervious_surface AS isurf , v2_connection_nodes AS connection_node , v2_impervious_surface_map AS isurfmap , v2_pipe As p WHERE isurfmap.connection_node_id = connection_node.id AND isurfmap.impervious_surface_id = isurf.id AND (isurfmap.connection_node_id = p.connection_node_start_id OR isurfmap.connection_node_id = p.connection_node_end_id) GROUP BY (isurfmap.impervious_surface_id) ORDER BY isurfmap.impervious_surface_id, ST_Distance(connection_node.the_geom, isurf.the_geom) ASC j1j<%%?viewv2_weir_viewv2_weir_viewCREATE VIEW v2_weir_view AS SELECT weir.ROWID AS ROWID, weir.id AS weir_id, weir.display_name AS weir_display_name, weir.code AS weir_code, weir.crest_level AS weir_crest_level, weir.crest_type AS weir_crest_type, weir.cross_section_definition_id AS weir_cross_section_definition_id, weir.sewerage AS weir_sewerage, weir.discharge_coefficient_positive AS weir_discharge_coeffici*D--?viewv2_pipe_map_viewv2_pipe_map_viewCREATE VIEW v2_pipe_map_view AS SELECT isurf.ROWID AS ROWID, isurf.id AS isurf_id, isurf.display_name AS isurf_display_name, isurf.code AS isurf_code, isurf.surface_class AS isurf_surface_class, isurf.surface_sub_class AS isurf_surface_sub_class, isurf.surface_inclination AS isurf_surface_inclination, isurf.zoom_category AS isurf_zoom_category, isurf.nr_of_inhab(ent_positive, weir.discharge_coefficient_negative AS weir_discharge_coefficient_negative, weir.external AS weir_external, weir.zoom_category AS weir_zoom_category, weir.friction_value AS weir_friction_value, weir.friction_type AS weir_friction_type, weir.connection_node_start_id AS weir_connection_node_start_id, weir.connection_node_end_id AS weir_connection_node_end_id, def.id AS def_id, def.shape AS def_shape, def.width AS def_width, def.height AS def_height, def.code AS def_code, MakeLine( start_node.the_geom, end_node.the_geom) AS the_geom FROM v2_weir AS weir , v2_cross_section_definition AS def , v2_connection_nodes AS start_node , v2_connection_nodes AS end_node WHERE weir.connection_node_start_id = start_node.id AND weir.connection_node_end_id = end_node.id AND weir.cross_section_definition_id = def.idELECT def.ROWID AS ROWID, def.id AS def_id, def.shape AS def_shape, def.width AS def_width, def.height AS def_height, def.code AS def_code, l.id AS l_id, l.channel_id AS l_channel_id, l.definition_id AS l_definition_id, l.reference_level AS l_reference_level, l.friction_type AS l_friction_type, l.friction_value AS l_friction_value, l.bank_level AS l_bank_level, l.code AS l_code, l.the_geom AS the_geom, ch.id AS ch_id, ch.display_name AS ch_display_name, ch.code AS ch_code, ch.calculation_type AS ch_calculation_type, ch.dist_calc_points AS ch_dist_calc_points, ch.zoom_category AS ch_zoom_category, ch.connection_node_start_id AS ch_connection_node_start_id, ch.connection_node_end_id AS ch_connection_node_end_id FROM v2_cross_section_definition AS def , v2_cross_section_location AS l , v2_channel AS ch WHERE l.definition_id = def.id AND l.channel_id = ch.id = ++5viewv2_culvert_viewv2_culvert_viewCREATE VIEW v2_culvert_view AS SELECT cul.ROWID AS ROWID, cul.id AS cul_id, cul.display_name AS cul_display_name, cul-]77]viewv2_cross_section_viewv2_cross_section_viewCREATE VIEW v2_cross_section_view AS S+.code AS cul_code, cul.calculation_type AS cul_calculation_type, cul.friction_value AS cul_friction_value, cul.friction_type AS cul_friction_type, cul.dist_calc_points AS cul_dist_calc_points, cul.zoom_category AS cul_zoom_category, cul.cross_section_definition_id AS cul_cross_section_definition_id, cul.discharge_coefficient_positive AS cul_discharge_coefficient_positive, cul.discharge_coefficient_negative AS cul_discharge_coefficient_negative, cul.invert_level_start_point AS cul_invert_level_start_point, cul.invert_level_end_point AS cul_invert_level_end_point, cul.the_geom AS the_geom, cul.connection_node_start_id AS cul_connection_node_start_id, cul.connection_node_end_id AS cul_connection_node_end_id, def.id AS def_id, def.shape AS def_shape, def.width AS def_width, def.height AS def_height, def.code AS def_code FROM v2_culvert AS cul , v2_cross_section_definition AS def WHERE cul.cross_section_definition_id = def.idgeoalchemy2-0.15.2/tests/gallery/000077500000000000000000000000001464355170400166105ustar00rootroot00000000000000geoalchemy2-0.15.2/tests/gallery/README.rst000066400000000000000000000000361464355170400202760ustar00rootroot00000000000000.. _gallery: Gallery ======= geoalchemy2-0.15.2/tests/gallery/__init__.py000066400000000000000000000000001464355170400207070ustar00rootroot00000000000000geoalchemy2-0.15.2/tests/gallery/test_decipher_raster.py000066400000000000000000000122001464355170400233570ustar00rootroot00000000000000""" Decipher Raster =============== The `RasterElement` objects store the Raster data in WKB form. When using rasters it is usually better to convert them into TIFF, PNG, JPEG or whatever. Nevertheless, it is possible to decipher the WKB to get a 2D list of values. This example uses SQLAlchemy ORM queries. """ import binascii import struct import pytest from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy.orm import declarative_base from geoalchemy2 import Raster from geoalchemy2 import WKTElement # Tests imports from tests import test_only_with_dialects metadata = MetaData() Base = declarative_base(metadata=metadata) class Ocean(Base): # type: ignore __tablename__ = "ocean" id = Column(Integer, primary_key=True) rast = Column(Raster) def __init__(self, rast): self.rast = rast def _format_e(endianness, struct_format): return _ENDIANNESS[endianness] + struct_format def wkbHeader(raw): # Function to decipher the WKB header # See http://trac.osgeo.org/postgis/browser/trunk/raster/doc/RFC2-WellKnownBinaryFormat header = {} header["endianness"] = struct.unpack("b", raw[0:1])[0] e = header["endianness"] header["version"] = struct.unpack(_format_e(e, "H"), raw[1:3])[0] header["nbands"] = struct.unpack(_format_e(e, "H"), raw[3:5])[0] header["scaleX"] = struct.unpack(_format_e(e, "d"), raw[5:13])[0] header["scaleY"] = struct.unpack(_format_e(e, "d"), raw[13:21])[0] header["ipX"] = struct.unpack(_format_e(e, "d"), raw[21:29])[0] header["ipY"] = struct.unpack(_format_e(e, "d"), raw[29:37])[0] header["skewX"] = struct.unpack(_format_e(e, "d"), raw[37:45])[0] header["skewY"] = struct.unpack(_format_e(e, "d"), raw[45:53])[0] header["srid"] = struct.unpack(_format_e(e, "i"), raw[53:57])[0] header["width"] = struct.unpack(_format_e(e, "H"), raw[57:59])[0] header["height"] = struct.unpack(_format_e(e, "H"), raw[59:61])[0] return header def read_band(data, offset, pixtype, height, width, endianness=1): ptype, _, psize = _PTYPE[pixtype] pix_data = data[offset + 1 : offset + 1 + width * height * psize] band = [ [ struct.unpack( _format_e(endianness, ptype), pix_data[(i * width + j) * psize : (i * width + j + 1) * psize], )[0] for j in range(width) ] for i in range(height) ] return band def read_band_numpy(data, offset, pixtype, height, width, endianness=1): import numpy as np # noqa _, dtype, psize = _PTYPE[pixtype] dt = np.dtype(dtype) dt = dt.newbyteorder(_ENDIANNESS[endianness]) band = np.frombuffer(data, dtype=dtype, count=height * width, offset=offset + 1) band = np.reshape(band, ((height, width))) return band _PTYPE = { 0: ["?", "?", 1], 1: ["B", "B", 1], 2: ["B", "B", 1], 3: ["b", "b", 1], 4: ["B", "B", 1], 5: ["h", "i2", 2], 6: ["H", "u2", 2], 7: ["i", "i4", 4], 8: ["I", "u4", 4], 10: ["f", "f4", 4], 11: ["d", "f8", 8], } _ENDIANNESS = { 0: ">", 1: "<", } def wkbImage(raster_data, use_numpy=False): """Function to decipher the WKB raster data""" # Get binary data raw = binascii.unhexlify(raster_data) # Read header h = wkbHeader(bytes(raw)) e = h["endianness"] img = [] # array to store image bands offset = 61 # header raw length in bytes band_size = h["width"] * h["height"] # number of pixels in each band for i in range(h["nbands"]): # Determine pixtype for this band pixtype = struct.unpack(_format_e(e, "b"), raw[offset : offset + 1])[0] - 64 # Read data with either pure Python or Numpy if use_numpy: band = read_band_numpy(raw, offset, pixtype, h["height"], h["width"]) else: band = read_band(raw, offset, pixtype, h["height"], h["width"]) # Store the result img.append(band) offset = offset + 2 + band_size return img @test_only_with_dialects("postgresql") class TestDecipherRaster: @pytest.mark.parametrize( "pixel_type", [ "1BB", "2BUI", "4BUI", "8BSI", "8BUI", "16BSI", "16BUI", "32BSI", "32BUI", "32BF", "64BF", ], ) def test_decipher_raster(self, pixel_type, session, conn): """Create a raster and decipher it""" metadata.drop_all(conn, checkfirst=True) metadata.create_all(conn) # Create a new raster polygon = WKTElement("POLYGON((0 0,1 1,0 1,0 0))", srid=4326) o = Ocean(polygon.ST_AsRaster(5, 6, pixel_type)) session.add(o) session.flush() # Decipher data from each raster image = wkbImage(o.rast.data) # Define expected result expected = [ [0, 1, 1, 1, 1], [1, 1, 1, 1, 1], [0, 1, 1, 1, 0], [0, 1, 1, 0, 0], [0, 1, 0, 0, 0], [0, 0, 0, 0, 0], ] # Check results band = image[0] assert band == expected geoalchemy2-0.15.2/tests/gallery/test_disable_wrapping.py000066400000000000000000000036031464355170400235350ustar00rootroot00000000000000""" Disable wrapping in select ========================== If the application wants to build queries with GeoAlchemy 2 and gets them as strings, the wrapping of geometry columns with a `ST_AsEWKB()` function might be annoying. In this case it is possible to disable this wrapping. This example uses SQLAlchemy ORM queries. """ from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import func from sqlalchemy.orm import declarative_base from geoalchemy2 import Geometry # Tests imports from tests import select Base = declarative_base() class RawGeometry(Geometry): """This class is used to remove the 'ST_AsEWKB()'' function from select queries""" def column_expression(self, col): return col class Point(Base): # type: ignore __tablename__ = "point" id = Column(Integer, primary_key=True) geom = Column(Geometry(srid=4326, geometry_type="POINT")) raw_geom = Column(RawGeometry(srid=4326, geometry_type="POINT")) def test_no_wrapping(): # Select all columns select_query = select([Point]) # Check that the 'geom' column is wrapped by 'ST_AsEWKB()' and that the column # 'raw_geom' is not. assert str(select_query) == ( "SELECT point.id, ST_AsEWKB(point.geom) AS geom, point.raw_geom \nFROM point" ) def test_func_no_wrapping(): # Select query with function select_query = select( [ func.ST_Buffer(Point.geom), # with wrapping (default behavior) func.ST_Buffer(Point.geom, type_=Geometry), # with wrapping func.ST_Buffer(Point.geom, type_=RawGeometry), # without wrapping ] ) # Check the query assert str(select_query) == ( "SELECT " 'ST_AsEWKB(ST_Buffer(point.geom)) AS "ST_Buffer_1", ' 'ST_AsEWKB(ST_Buffer(point.geom)) AS "ST_Buffer_2", ' 'ST_Buffer(point.geom) AS "ST_Buffer_3" \n' "FROM point" ) geoalchemy2-0.15.2/tests/gallery/test_insert_raster.py000066400000000000000000000146671464355170400231230ustar00rootroot00000000000000""" Insert Raster ============= The `RasterElement` objects store the Raster data in WKB form. This WKB format is usually fetched from the database but when the data comes from another source it can be hard to format it as as a WKB. This example shows a method to convert input data into a WKB in order to insert it. This example uses SQLAlchemy ORM queries. .. warning:: The PixelType values are not always properly translated by the `Rasterio `_ library, so exporting a raster and re-importing it using this method will properly import the values but might not keep the same internal types. """ import struct from sys import byteorder import numpy as np import pytest import rasterio from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import text from sqlalchemy.orm import declarative_base from geoalchemy2 import Raster from geoalchemy2 import RasterElement # Tests imports from tests import test_only_with_dialects metadata = MetaData() Base = declarative_base(metadata=metadata) class Ocean(Base): # type: ignore __tablename__ = "ocean" id = Column(Integer, primary_key=True) rast = Column(Raster) def __init__(self, rast): self.rast = rast _DTYPE = { "?": [0, "?", 1], "u1": [2, "B", 1], "i1": [3, "b", 1], "B": [4, "B", 1], "i2": [5, "h", 2], "u2": [6, "H", 2], "i4": [7, "i", 4], "u4": [8, "I", 4], "f4": [10, "f", 4], "f8": [11, "d", 8], } def write_wkb_raster(dataset): """Creates a WKB raster from the given raster file with rasterio. :dataset: Rasterio dataset :returns: binary: Binary raster in WKB format This function was imported from https://github.com/nathancahill/wkb-raster/blob/master/wkb_raster.py and slightly adapted. """ # Define format, see https://docs.python.org/3/library/struct.html format_string = "bHHddddddIHH" if byteorder == "big": endian = ">" endian_byte = 0 elif byteorder == "little": endian = "<" endian_byte = 1 # Write the raster header data. header = bytes() transform = dataset.transform.to_gdal() version = 0 nBands = int(dataset.count) scaleX = transform[1] scaleY = transform[5] ipX = transform[0] ipY = transform[3] skewX = 0 skewY = 0 srid = int(dataset.crs.to_string().split("EPSG:")[1]) width = int(dataset.meta.get("width")) height = int(dataset.meta.get("height")) if width > 65535 or height > 65535: raise ValueError("PostGIS does not support rasters with width or height greater than 65535") fmt = f"{endian}{format_string}" header = struct.pack( fmt, endian_byte, version, nBands, scaleX, scaleY, ipX, ipY, skewX, skewY, srid, width, height, ) bands = [] # Create band header data # not used - always False isOffline = False hasNodataValue = False if "nodata" in dataset.meta: hasNodataValue = True # not used - always False isNodataValue = False # unset reserved = False # # Based on the pixel type, determine the struct format, byte size and # # numpy dtype rasterio_dtype = dataset.meta.get("dtype") dt_short = np.dtype(rasterio_dtype).str[1:] pixtype, nodata_fmt, _ = _DTYPE[dt_short] # format binary -> :b binary_str = f"{isOffline:b}{hasNodataValue:b}{isNodataValue:b}{reserved:b}{pixtype:b}" # convert to int binary_decimal = int(binary_str, 2) # pack to 1 byte # 4 bits for ifOffline, hasNodataValue, isNodataValue, reserved # 4 bit for pixtype # -> 8 bit = 1 byte band_header = struct.pack("=2`` introduced a new way to construct mappings using the ``sqlalchemy.orm.DeclarativeBase`` base class. This example shows how to use GeoAlchemy2 types in this context. """ import pytest from pkg_resources import parse_version from sqlalchemy import __version__ as SA_VERSION try: from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column except ImportError: pass from geoalchemy2 import Geometry from geoalchemy2 import WKBElement from geoalchemy2 import shape def check_wkb(wkb, x, y) -> None: pt = shape.to_shape(wkb) assert round(pt.x, 5) == x assert round(pt.y, 5) == y @pytest.mark.skipif( parse_version(SA_VERSION) < parse_version("2"), reason="New ORM mapping is only available for sqlalchemy>=2", ) def test_ORM_mapping(session, conn, schema) -> None: class Base(DeclarativeBase): pass class Lake(Base): __tablename__ = "lake" __table_args__ = {"schema": schema} id: Mapped[int] = mapped_column(primary_key=True) mapped_geom: Mapped[WKBElement] = mapped_column(Geometry(geometry_type="POINT", srid=4326)) Lake.__table__.drop(conn, checkfirst=True) # type: ignore[attr-defined] Lake.__table__.create(bind=conn) # type: ignore[attr-defined] # Create new point instance p = Lake() p.mapped_geom = "SRID=4326;POINT(5 45)" # type: ignore[assignment] # Insert point session.add(p) session.flush() session.expire(p) # Query the point and check the result pt = session.query(Lake).one() assert pt.id == 1 assert pt.mapped_geom.srid == 4326 check_wkb(pt.mapped_geom, 5, 45) geoalchemy2-0.15.2/tests/gallery/test_raster_transform.py000066400000000000000000000072651464355170400236260ustar00rootroot00000000000000""" Reproject a Raster using ST_Transform ===================================== The `ST_Transform()` function (and a few others like `ST_SnapToGrid()`) can be used on both `Geometry` and `Raster` types. In `GeoAlchemy2`, this function is only defined for `Geometry` as it can not be defined for several types at the same time. Thus using this function on `Raster` requires minor tweaking. This example uses both SQLAlchemy core and ORM queries. """ from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import Table from sqlalchemy import func from sqlalchemy.orm import Query from sqlalchemy.orm import declarative_base from geoalchemy2 import Geometry from geoalchemy2 import Raster # Tests imports from tests import select metadata = MetaData() Base = declarative_base(metadata=metadata) table = Table( "raster_table", metadata, Column("id", Integer, primary_key=True), Column("geom", Geometry("POLYGON", 4326)), Column("rast", Raster()), ) class RasterTable(Base): # type: ignore __tablename__ = "raster_table_orm" id = Column(Integer, primary_key=True) geom = Column(Geometry("POLYGON", 4326)) rast = Column(Raster()) def __init__(self, rast): self.rast = rast def test_transform_core(): # Define the transform query for both the geometry and the raster in a naive way wrong_query = select( [func.ST_Transform(table.c.geom, 2154), func.ST_Transform(table.c.rast, 2154)] ) # Check the query assert str(wrong_query) == ( "SELECT " "ST_AsEWKB(" 'ST_Transform(raster_table.geom, :ST_Transform_2)) AS "ST_Transform_1", ' "ST_AsEWKB(" # <= Note that the raster is processed as a Geometry here 'ST_Transform(raster_table.rast, :ST_Transform_4)) AS "ST_Transform_3" \n' "FROM raster_table" ) # Define the transform query for both the geometry and the raster in the correct way correct_query = select( [ func.ST_Transform(table.c.geom, 2154), func.ST_Transform(table.c.rast, 2154, type_=Raster), ] ) # Check the query assert str(correct_query) == ( "SELECT " "ST_AsEWKB(" 'ST_Transform(raster_table.geom, :ST_Transform_2)) AS "ST_Transform_1", ' "raster(" # <= This time the raster is correctly processed as a Raster 'ST_Transform(raster_table.rast, :ST_Transform_4)) AS "ST_Transform_3" \n' "FROM raster_table" ) def test_transform_ORM(): # Define the transform query for both the geometry and the raster in a naive way wrong_query = Query([RasterTable.geom.ST_Transform(2154), RasterTable.rast.ST_Transform(2154)]) # Check the query assert str(wrong_query) == ( "SELECT " "ST_AsEWKB(" 'ST_Transform(raster_table_orm.geom, :ST_Transform_2)) AS "ST_Transform_1", ' "ST_AsEWKB(" # <= Note that the raster is processed as a Geometry here 'ST_Transform(raster_table_orm.rast, :ST_Transform_4)) AS "ST_Transform_3" \n' "FROM raster_table_orm" ) # Define the transform query for both the geometry and the raster in the correct way correct_query = Query( [ RasterTable.geom.ST_Transform(2154), RasterTable.rast.ST_Transform(2154, type_=Raster), ] ) # Check the query assert str(correct_query) == ( "SELECT " "ST_AsEWKB(" 'ST_Transform(raster_table_orm.geom, :ST_Transform_2)) AS "ST_Transform_1", ' "raster(" # <= This time the raster is correctly processed as a Raster 'ST_Transform(raster_table_orm.rast, :ST_Transform_4)) AS "ST_Transform_3" \n' "FROM raster_table_orm" ) geoalchemy2-0.15.2/tests/gallery/test_specific_compilation.py000066400000000000000000000112221464355170400244020ustar00rootroot00000000000000""" Function translation for specific dialect ========================================= Some functions have different names depending on the dialect. But sometimes one function in one dialect can be mapped to several other functions in another dialect, depending on the arguments passed. For example, the `ST_Buffer` function in PostgreSQL can translate into 2 functions in SQLite: 1. if the buffer is two-sided (symmetric), the PostgreSQL function:: ST_Buffer(the_table.geom, 10) should become in SQLite:: Buffer(the_table.geom, 10) 2. if the buffer is one-sided, the PostgreSQL function:: ST_Buffer(the_table.geom, 10, 'side=right') should become in SQLite:: SingleSidedBuffer(the_table.geom, 10, 0) This case is much more complicated than just mapping a function name and we show here how to deal with it. This example uses SQLAlchemy core queries. """ from sqlalchemy import MetaData from sqlalchemy import func from sqlalchemy.ext.compiler import compiles from sqlalchemy.orm import declarative_base from sqlalchemy.sql.expression import BindParameter from geoalchemy2 import WKTElement from geoalchemy2 import functions # Tests imports from tests import format_wkt from tests import select from tests import test_only_with_dialects metadata = MetaData() Base = declarative_base(metadata=metadata) def _compile_buffer_default(element, compiler, **kw): """Compile the element in the default case (no specific dialect). This function should not be needed for SQLAlchemy >= 1.1. """ return "{}({})".format("ST_Buffer", compiler.process(element.clauses, **kw)) def _compile_buffer_sqlite(element, compiler, **kw): """Compile the element for the SQLite dialect.""" # Get the side parameters compiled = compiler.process(element.clauses, **kw) side_params = [ i for i in element.clauses if isinstance(i, BindParameter) and "side" in str(i.value) ] if side_params: side_param = side_params[0] if "right" in side_param.value: # If the given side is 'right', we translate the value into 0 and switch to the sided # function side_param.value = 0 element.identifier = "SingleSidedBuffer" elif "left" in side_param.value: # If the given side is 'left', we translate the value into 1 and switch to the sided # function side_param.value = 1 element.identifier = "SingleSidedBuffer" if element.identifier == "ST_Buffer": # If the identifier is still the default ST_Buffer we switch to the SpatiaLite function element.identifier = "Buffer" # If there is no side parameter or if the side value is 'both', we use the default function return "{}({})".format(element.identifier, compiled) # Register the specific compilation rules compiles(functions.ST_Buffer)(_compile_buffer_default) # type: ignore compiles(functions.ST_Buffer, "sqlite")(_compile_buffer_sqlite) # type: ignore compiles(functions.ST_Buffer, "geopackage")(_compile_buffer_sqlite) # type: ignore @test_only_with_dialects("postgresql", "sqlite") def test_specific_compilation(conn): # Build a query with a sided buffer query = select( [ func.ST_AsText( func.ST_Buffer(WKTElement("LINESTRING(0 0, 1 0)", srid=4326), 1, "side=left") ) ] ) # Check the compiled query: the sided buffer should appear only in the SQLite query compiled_query = str(query.compile(dialect=conn.dialect)) if conn.dialect.name in ["sqlite", "geopackage"]: assert "SingleSidedBuffer" in compiled_query assert "ST_Buffer" not in compiled_query else: assert "SingleSidedBuffer" not in compiled_query assert "ST_Buffer" in compiled_query # Check the actual result of the query res = conn.execute(query).scalar() assert format_wkt(res) == "POLYGON((1 0,0 0,0 1,1 1,1 0))" # Build a query with symmetric buffer to check nothing was broken query = select( [func.ST_AsText(func.ST_Buffer(WKTElement("LINESTRING(0 0, 1 0)", srid=4326), 1))] ) # Check the compiled query: the sided buffer should never appear in the query compiled_query = str(query.compile(dialect=conn.dialect)) assert "SingleSidedBuffer" not in compiled_query if conn.dialect.name in ["sqlite", "geopackage"]: assert "ST_Buffer" not in compiled_query assert "Buffer" in compiled_query else: assert "ST_Buffer" in compiled_query # Check the actual result of the query res = conn.execute(query).scalar() assert format_wkt(res) != "POLYGON((1 0,0 0,0 1,1 1,1 0))" assert format_wkt(res).startswith("POLYGON((1 1,1") geoalchemy2-0.15.2/tests/gallery/test_summarystatsagg.py000066400000000000000000000066241464355170400234640ustar00rootroot00000000000000""" Use CompositeType ================= Some functions return composite types. This example shows how to deal with this kind of functions. """ import pytest from pkg_resources import parse_version from sqlalchemy import Column from sqlalchemy import Float from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import __version__ as SA_VERSION from sqlalchemy.orm import declarative_base from geoalchemy2 import Raster from geoalchemy2 import WKTElement from geoalchemy2.functions import GenericFunction from geoalchemy2.types import CompositeType # Tests imports from tests import select from tests import test_only_with_dialects class SummaryStatsCustomType(CompositeType): """Define the composite type returned by the function ST_SummaryStatsAgg.""" typemap = { "count": Integer, "sum": Float, "mean": Float, "stddev": Float, "min": Float, "max": Float, } cache_ok = True class ST_SummaryStatsAgg(GenericFunction): type = SummaryStatsCustomType() # Set a specific identifier to not override the actual ST_SummaryStatsAgg function identifier = "ST_SummaryStatsAgg_custom" inherit_cache = True metadata = MetaData() Base = declarative_base(metadata=metadata) class Ocean(Base): # type: ignore __tablename__ = "ocean" id = Column(Integer, primary_key=True) rast = Column(Raster) def __init__(self, rast): self.rast = rast @test_only_with_dialects("postgresql") class TestSTSummaryStatsAgg: @pytest.mark.skipif( parse_version(SA_VERSION) < parse_version("1.4"), reason="requires SQLAlchely>1.4", ) def test_st_summary_stats_agg(self, session, conn): metadata.drop_all(conn, checkfirst=True) metadata.create_all(conn) # Create a new raster polygon = WKTElement("POLYGON((0 0,1 1,0 1,0 0))", srid=4326) o = Ocean(polygon.ST_AsRaster(5, 6)) session.add(o) session.flush() # Define the query to compute stats stats_agg = select([Ocean.rast.ST_SummaryStatsAgg_custom(1, True, 1).label("stats")]) stats_agg_alias = stats_agg.alias("stats_agg") # Use these stats query = select( [ stats_agg_alias.c.stats.count.label("count"), stats_agg_alias.c.stats.sum.label("sum"), stats_agg_alias.c.stats.mean.label("mean"), stats_agg_alias.c.stats.stddev.label("stddev"), stats_agg_alias.c.stats.min.label("min"), stats_agg_alias.c.stats.max.label("max"), ] ) # Check the query assert str(query.compile(dialect=session.bind.dialect)) == ( "SELECT " "(stats_agg.stats).count AS count, " "(stats_agg.stats).sum AS sum, " "(stats_agg.stats).mean AS mean, " "(stats_agg.stats).stddev AS stddev, " "(stats_agg.stats).min AS min, " "(stats_agg.stats).max AS max \n" "FROM (" "SELECT " "ST_SummaryStatsAgg(" "ocean.rast, " "%(ST_SummaryStatsAgg_1)s, %(ST_SummaryStatsAgg_2)s, %(ST_SummaryStatsAgg_3)s" ") AS stats \n" "FROM ocean) AS stats_agg" ) # Execute the query res = session.execute(query).fetchall() # Check the result assert res == [(15, 15.0, 1.0, 0.0, 1.0, 1.0)] geoalchemy2-0.15.2/tests/gallery/test_type_decorator.py000066400000000000000000000127221464355170400232500ustar00rootroot00000000000000""" Automatically use a function at insert or select ================================================ Sometimes the application wants to apply a function in an insert or in a select. For example, the application might need the geometry with lat/lon coordinates while they are projected in the DB. To avoid having to always tweak the query with a ``ST_Transform()``, it is possible to define a `TypeDecorator `_ """ import re from typing import Any from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import func from sqlalchemy import text from sqlalchemy.orm import declarative_base from sqlalchemy.types import TypeDecorator from geoalchemy2 import Geometry from geoalchemy2 import shape # Tests imports from tests import test_only_with_dialects metadata = MetaData() Base = declarative_base(metadata=metadata) class TransformedGeometry(TypeDecorator): """This class is used to insert a ST_Transform() in each insert or select.""" impl = Geometry cache_ok = True def __init__(self, db_srid, app_srid, **kwargs): kwargs["srid"] = db_srid super().__init__(**kwargs) self.app_srid = app_srid self.db_srid = db_srid def column_expression(self, col): """The column_expression() method is overridden to set the correct type. This is needed so that the returned element will also be decorated. In this case we don't want to transform it again afterwards so we set the same SRID to both the ``db_srid`` and ``app_srid`` arguments. Without this the SRID of the WKBElement would be wrong. """ return getattr(func, self.impl.as_binary)( func.ST_Transform(col, self.app_srid), type_=self.__class__(db_srid=self.app_srid, app_srid=self.app_srid), ) def bind_expression(self, bindvalue): return func.ST_Transform( self.impl.bind_expression(bindvalue), self.db_srid, type_=self, ) class ThreeDGeometry(TypeDecorator): """This class is used to insert a ST_Force3D() in each insert.""" impl = Geometry cache_ok = True def column_expression(self, col): """The column_expression() method is overridden to set the correct type. This is not needed in this example but it is needed if one wants to override other methods of the TypeDecorator class, like ``process_result_value()`` for example. """ return getattr(func, self.impl.as_binary)(col, type_=self) def bind_expression(self, bindvalue): return func.ST_Force3D( self.impl.bind_expression(bindvalue), type=self, ) class Point(Base): # type: ignore __tablename__ = "point" id = Column(Integer, primary_key=True) raw_geom = Column(Geometry(srid=4326, geometry_type="POINT")) geom: Column[Any] = Column( TransformedGeometry(db_srid=2154, app_srid=4326, geometry_type="POINT") ) three_d_geom: Column = Column(ThreeDGeometry(srid=4326, geometry_type="POINTZ", dimension=3)) def check_wkb(wkb, x, y): pt = shape.to_shape(wkb) assert round(pt.x, 5) == x assert round(pt.y, 5) == y @test_only_with_dialects("postgresql") class TestTypeDecorator: def _create_one_point(self, session, conn): metadata.drop_all(conn, checkfirst=True) metadata.create_all(conn) # Create new point instance p = Point() p.raw_geom = "SRID=4326;POINT(5 45)" p.geom = "SRID=4326;POINT(5 45)" p.three_d_geom = "SRID=4326;POINT(5 45)" # Insert 2D geometry into 3D column # Insert point session.add(p) session.flush() session.expire(p) return p.id def test_transform(self, session, conn): self._create_one_point(session, conn) # Query the point and check the result pt = session.query(Point).one() assert pt.id == 1 assert pt.raw_geom.srid == 4326 check_wkb(pt.raw_geom, 5, 45) assert pt.geom.srid == 4326 check_wkb(pt.geom, 5, 45) # Check that the data is correct in DB using raw query q = text("SELECT id, ST_AsEWKT(geom) AS geom FROM point;") res_q = session.execute(q).fetchone() assert res_q.id == 1 assert re.match( r"SRID=2154;POINT\(857581\.8993196681? 6435414\.7478354[0-9]*\)", res_q.geom ) # Compare geom, raw_geom with auto transform and explicit transform pt_trans = session.query( Point, Point.raw_geom, func.ST_Transform(Point.raw_geom, 2154).label("trans"), ).one() assert pt_trans[0].id == 1 assert pt_trans[0].geom.srid == 4326 check_wkb(pt_trans[0].geom, 5, 45) assert pt_trans[0].raw_geom.srid == 4326 check_wkb(pt_trans[0].raw_geom, 5, 45) assert pt_trans[1].srid == 4326 check_wkb(pt_trans[1], 5, 45) assert pt_trans[2].srid == 2154 check_wkb(pt_trans[2], 857581.89932, 6435414.74784) def test_force_3d(self, session, conn): self._create_one_point(session, conn) # Query the point and check the result pt = session.query(Point).one() assert pt.id == 1 assert pt.three_d_geom.srid == 4326 assert pt.three_d_geom.desc.lower() == ( "01010000a0e6100000000000000000144000000000008046400000000000000000" ) geoalchemy2-0.15.2/tests/schema_fixtures.py000066400000000000000000000134711464355170400207220ustar00rootroot00000000000000"""Declare tables used in tests.""" import pytest from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy.orm import declarative_base from sqlalchemy.sql import func from sqlalchemy.types import TypeDecorator from geoalchemy2 import Geography from geoalchemy2 import Geometry from geoalchemy2 import Raster @pytest.fixture def Lake(base, postgis_version, schema): class Lake(base): __tablename__ = "lake" __table_args__ = {"schema": schema} id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type="LINESTRING", srid=4326)) def __init__(self, geom): self.geom = geom return Lake @pytest.fixture def Poi(base, schema, dialect_name): class Poi(base): __tablename__ = "poi" __table_args__ = {"schema": schema} id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type="POINT", srid=4326)) geog = ( Column(Geography(geometry_type="POINT", srid=4326)) if dialect_name == "postgresql" else None ) def __init__(self, geog): self.geog = geog return Poi @pytest.fixture def Summit(base, postgis_version, schema): with_use_typemod = postgis_version == 1 class Summit(base): __tablename__ = "summit" __table_args__ = {"schema": schema} id = Column(Integer, primary_key=True) geom = Column( Geometry( geometry_type="POINT", srid=4326, use_typmod=with_use_typemod, ) ) def __init__(self, geom): self.geom = geom return Summit @pytest.fixture def Ocean(base, postgis_version): # The raster type is only available on PostGIS 2.0 and above if postgis_version == 1: pytest.skip("The raster type is only available on PostGIS 2.0 and above") class Ocean(base): __tablename__ = "ocean" id = Column(Integer, primary_key=True) rast = Column(Raster) def __init__(self, rast): self.rast = rast return Ocean class ThreeDGeometry(TypeDecorator): """This class is used to insert a ST_Force3D() in each insert.""" impl = Geometry def bind_expression(self, bindvalue): return func.ST_Force3D(self.impl.bind_expression(bindvalue)) @pytest.fixture def PointZ(base): class PointZ(base): __tablename__ = "point_z" id = Column(Integer, primary_key=True) three_d_geom = Column(ThreeDGeometry(srid=4326, geometry_type="POINTZ", dimension=3)) return PointZ class TransformedGeometry(TypeDecorator): """This class is used to insert a ST_Transform() in each insert or select.""" impl = Geometry def __init__(self, db_srid, app_srid, **kwargs): kwargs["srid"] = db_srid self.impl = self.__class__.impl(**kwargs) self.app_srid = app_srid self.db_srid = db_srid def column_expression(self, col): """The column_expression() method is overridden to ensure that the SRID of the resulting WKBElement is correct""" return getattr(func, self.impl.as_binary)( func.ST_Transform(col, self.app_srid), type_=self.__class__.impl(srid=self.app_srid), # srid could also be -1 so that the SRID is deduced from the # WKB data ) def bind_expression(self, bindvalue): return func.ST_Transform(self.impl.bind_expression(bindvalue), self.db_srid) @pytest.fixture def LocalPoint(base): class LocalPoint(base): __tablename__ = "local_point" id = Column(Integer, primary_key=True) geom = Column(TransformedGeometry(db_srid=2154, app_srid=4326, geometry_type="POINT")) managed_geom = Column( TransformedGeometry(db_srid=2154, app_srid=4326, geometry_type="POINT") ) return LocalPoint @pytest.fixture def IndexTestWithSchema(base, schema): class IndexTestWithSchema(base): __tablename__ = "indextestwithschema" __table_args__ = {"schema": schema} if schema else {} id = Column(Integer, primary_key=True) geom1 = Column(Geometry(geometry_type="POINT", srid=4326)) geom2 = Column(Geometry(geometry_type="POINT", srid=4326)) return IndexTestWithSchema @pytest.fixture def IndexTestWithNDIndex(base, schema): class IndexTestWithNDIndex(base): __tablename__ = "index_test_with_nd_index" __table_args__ = {"schema": schema} id = Column(Integer, primary_key=True) geom1 = Column(Geometry(geometry_type="POINTZ", dimension=3, use_N_D_index=True)) return IndexTestWithNDIndex @pytest.fixture def IndexTestWithoutSchema(base): class IndexTestWithoutSchema(base): __tablename__ = "indextestwithoutschema" id = Column(Integer, primary_key=True) geom1 = Column(Geometry(geometry_type="POINT", srid=4326)) geom2 = Column(Geometry(geometry_type="POINT", srid=4326)) return IndexTestWithoutSchema @pytest.fixture def reflection_tables_metadata(dialect_name): metadata = MetaData() base = declarative_base(metadata=metadata) class Lake(base): __tablename__ = "lake" id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type="LINESTRING", srid=4326)) if dialect_name != "geopackage": geom_no_idx = Column( Geometry(geometry_type="LINESTRING", srid=4326, spatial_index=False) ) if dialect_name != "mysql": geom_z = Column(Geometry(geometry_type="LINESTRINGZ", srid=4326, dimension=3)) geom_m = Column(Geometry(geometry_type="LINESTRINGM", srid=4326, dimension=3)) geom_zm = Column(Geometry(geometry_type="LINESTRINGZM", srid=4326, dimension=4)) return metadata geoalchemy2-0.15.2/tests/test_alembic_migrations.py000066400000000000000000000343221464355170400224160ustar00rootroot00000000000000"""Test alembic migrations of spatial columns.""" import pytest import sqlalchemy as sa # noqa (This import is only used in the migration scripts) from alembic import command from alembic import script from alembic.autogenerate import compare_metadata from alembic.config import Config from alembic.migration import MigrationContext from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import Table from sqlalchemy import text from geoalchemy2 import Geometry from geoalchemy2 import alembic_helpers from . import check_indexes from . import test_only_with_dialects def filter_tables(name, type_, parent_names): """Filter tables that we don't care about.""" return type_ != "table" or name in ["lake", "alembic_table"] class TestAutogenerate: def test_no_diff(self, conn, Lake, setup_tables, use_alembic_monkeypatch, dialect_name): """Check that the autogeneration detects spatial types properly.""" metadata = MetaData() Table( "lake", metadata, Column("id", Integer, primary_key=True), Column( "geom", Geometry( geometry_type="LINESTRING", srid=4326, nullable=dialect_name != "mysql", ), ), schema=Lake.__table__.schema, ) mc = MigrationContext.configure( conn, opts={ "include_name": filter_tables, "process_revision_directives": alembic_helpers.writer, }, ) diff = compare_metadata(mc, metadata) assert diff == [] def test_diff(self, conn, Lake, setup_tables, use_alembic_monkeypatch): """Check that the autogeneration detects spatial types properly.""" metadata = MetaData() Table( "lake", metadata, Column("id", Integer, primary_key=True), Column("new_col", Integer, primary_key=True), Column( "geom", Geometry( geometry_type="LINESTRING", srid=4326, ), ), Column( "new_geom_col", Geometry( geometry_type="LINESTRING", srid=4326, ), ), schema=Lake.__table__.schema, ) mc = MigrationContext.configure( conn, opts={ "include_name": filter_tables, "process_revision_directives": alembic_helpers.writer, }, ) diff = compare_metadata(mc, metadata) # Check column of type Integer add_new_col = diff[0] assert add_new_col[0] == "add_column" assert add_new_col[1] is None assert add_new_col[2] == "lake" assert add_new_col[3].name == "new_col" assert isinstance(add_new_col[3].type, Integer) assert add_new_col[3].primary_key is True assert add_new_col[3].nullable is False # Check column of type Geometry add_new_geom_col = diff[1] assert add_new_geom_col[0] == "add_column" assert add_new_geom_col[1] is None assert add_new_geom_col[2] == "lake" assert add_new_geom_col[3].name == "new_geom_col" assert isinstance(add_new_geom_col[3].type, Geometry) assert add_new_geom_col[3].primary_key is False assert add_new_geom_col[3].nullable is True assert add_new_geom_col[3].type.srid == 4326 assert add_new_geom_col[3].type.geometry_type == "LINESTRING" assert add_new_geom_col[3].type.name == "geometry" assert add_new_geom_col[3].type.dimension == 2 @pytest.fixture def alembic_dir(tmpdir): return tmpdir / "alembic_files" @pytest.fixture def alembic_config_path(alembic_dir): return alembic_dir / "test_alembic.ini" @pytest.fixture def alembic_env_path(alembic_dir): return alembic_dir / "env.py" @pytest.fixture def test_script_path(alembic_dir): return alembic_dir / "test_script.py" @pytest.fixture def alembic_env(engine, alembic_dir, alembic_config_path, alembic_env_path, test_script_path): cfg_tmp = Config(alembic_config_path) with engine.begin() as connection: connection.execute(text("DROP TABLE IF EXISTS alembic_version;")) command.init(cfg_tmp, str(alembic_dir), template="generic") with alembic_env_path.open(mode="w", encoding="utf8") as f: f.write( """ import importlib from alembic import context from sqlalchemy import MetaData, engine_from_config from sqlalchemy.event import listen from geoalchemy2 import alembic_helpers from geoalchemy2 import load_spatialite config = context.config engine = engine_from_config( config.get_section(config.config_ini_section), prefix='sqlalchemy.', echo=True, ) if engine.dialect.name == "sqlite": listen(engine, 'connect', load_spatialite) spec = importlib.util.spec_from_file_location("test_script", "{}") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) target_metadata = module.metadata connection = engine.connect() context.configure( connection=connection, target_metadata=target_metadata, version_table_pk=True, process_revision_directives=alembic_helpers.writer, render_item=alembic_helpers.render_item, include_object=alembic_helpers.include_object, render_as_batch={} ) try: with context.begin_transaction(): context.run_migrations() finally: connection.close() engine.dispose() """.format( str(test_script_path), True if engine.dialect.name == "sqlite" else False, ) ) with test_script_path.open(mode="w", encoding="utf8") as f: f.write( """ from sqlalchemy import MetaData metadata = MetaData() """ ) sc = script.ScriptDirectory.from_config(cfg_tmp) return sc @pytest.fixture def alembic_config(engine, alembic_dir, alembic_config_path, alembic_env): cfg = Config(str(alembic_config_path)) with alembic_config_path.open(mode="w", encoding="utf8") as f: f.write( """ [alembic] script_location = {} sqlalchemy.url = {} [loggers] keys = root [handlers] keys = console [logger_root] level = WARN handlers = console qualname = [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatters] keys = generic [formatter_generic] format = %%(levelname)-5.5s [%%(name)s] %%(message)s datefmt = %%H:%%M:%%S """.format( alembic_dir, str(engine.url).replace("***", engine.url.password or "") ) ) return cfg @test_only_with_dialects("postgresql", "sqlite-spatialite4") def test_migration_revision( conn, metadata, alembic_config, alembic_env_path, test_script_path, use_alembic_monkeypatch, dialect_name, ): initial_rev = command.revision( alembic_config, "Initial state", autogenerate=True, rev_id="initial", ) command.upgrade(alembic_config, initial_rev.revision) # Add a new table in metadata with test_script_path.open(mode="w", encoding="utf8") as f: f.write( """ from geoalchemy2 import Geometry from sqlalchemy import Column from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import String from sqlalchemy import Table metadata = MetaData() group_table = Table( "new_groups", metadata, Column("id", Integer, primary_key=True, autoincrement=True), Column("name", String), ) new_table = Table( "new_spatial_table", metadata, Column("id", Integer, primary_key=True), Column("group_id", Integer, ForeignKey(group_table.c.id)), Column( "geom_with_idx", Geometry( geometry_type="LINESTRING", srid=4326, ), ), Column( "geom_without_idx", Geometry( geometry_type="LINESTRING", srid=4326, spatial_index=False, ), ), Column( "geom_without_idx_2", Geometry( geometry_type="LINESTRING", srid=4326, spatial_index=False, ), ), ) """ ) # Auto-generate a new migration script rev_table = command.revision( alembic_config, "Add a new table", autogenerate=True, rev_id="table", ) # Apply the upgrade script command.upgrade(alembic_config, rev_table.revision) check_indexes( conn, dialect_name, { "postgresql": [ ( "idx_new_spatial_table_geom_with_idx", """CREATE INDEX idx_new_spatial_table_geom_with_idx ON gis.new_spatial_table USING gist (geom_with_idx)""", ), ( "new_spatial_table_pkey", """CREATE UNIQUE INDEX new_spatial_table_pkey ON gis.new_spatial_table USING btree (id)""", ), ], "sqlite": [ ("new_spatial_table", "geom_with_idx", 2, 2, 4326, 1), ("new_spatial_table", "geom_without_idx", 2, 2, 4326, 0), ("new_spatial_table", "geom_without_idx_2", 2, 2, 4326, 0), ], }, table_name="new_spatial_table", ) # Insert data in new table to check that everything works when Alembic copies the tables from_text = "GeomFromEWKT" if conn.dialect.name == "sqlite" else "ST_GeomFromEWKT" conn.execute( text( """INSERT INTO new_spatial_table ( geom_with_idx, geom_without_idx, geom_without_idx_2 ) VALUES ( {from_text}('SRID=4326;LINESTRING(0 0, 1 1)'), {from_text}('SRID=4326;LINESTRING(0 0, 1 1)'), {from_text}('SRID=4326;LINESTRING(0 0, 1 1)') ) """.format( from_text=from_text ) ) ) conn.execute(text("COMMIT")) # Remove spatial columns and add new ones with test_script_path.open(mode="w", encoding="utf8") as f: f.write( """ from geoalchemy2 import Geometry from sqlalchemy import Column from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import String from sqlalchemy import Table metadata = MetaData() group_table = Table( "new_groups", metadata, Column("id", Integer, primary_key=True, autoincrement=True), Column("name", String), ) new_table = Table( "new_spatial_table", metadata, Column("id", Integer, primary_key=True), Column("group_id", Integer, ForeignKey(group_table.c.id)), Column( "geom_with_idx", Geometry( geometry_type="LINESTRING", srid=4326, ), nullable=False, ), Column( "geom_without_idx", Geometry( geometry_type="LINESTRING", srid=4326, spatial_index=False, ), nullable=False, ), Column( "new_geom_with_idx", Geometry( geometry_type="LINESTRING", srid=4326, ), ), Column( "new_geom_without_idx", Geometry( geometry_type="LINESTRING", srid=4326, spatial_index=False, ), ), ) """ ) # Auto-generate a new migration script rev_cols = command.revision( alembic_config, "Add, alter and remove spatial columns", autogenerate=True, rev_id="columns", ) # Apply the upgrade script command.upgrade(alembic_config, rev_cols.revision) check_indexes( conn, dialect_name, { "postgresql": [ ( "idx_new_spatial_table_geom_with_idx", """CREATE INDEX idx_new_spatial_table_geom_with_idx ON gis.new_spatial_table USING gist (geom_with_idx)""", ), ( "idx_new_spatial_table_new_geom_with_idx", """CREATE INDEX idx_new_spatial_table_new_geom_with_idx ON gis.new_spatial_table USING gist (new_geom_with_idx)""", ), ( "new_spatial_table_pkey", """CREATE UNIQUE INDEX new_spatial_table_pkey ON gis.new_spatial_table USING btree (id)""", ), ], "sqlite": [ ("new_spatial_table", "geom_with_idx", 2, 2, 4326, 1), ("new_spatial_table", "geom_without_idx", 2, 2, 4326, 0), ("new_spatial_table", "new_geom_with_idx", 2, 2, 4326, 1), ("new_spatial_table", "new_geom_without_idx", 2, 2, 4326, 0), ], }, table_name="new_spatial_table", ) # Apply the downgrade script for columns command.downgrade(alembic_config, rev_table.revision) check_indexes( conn, dialect_name, { "postgresql": [ ( "idx_new_spatial_table_geom_with_idx", """CREATE INDEX idx_new_spatial_table_geom_with_idx ON gis.new_spatial_table USING gist (geom_with_idx)""", ), ( "new_spatial_table_pkey", """CREATE UNIQUE INDEX new_spatial_table_pkey ON gis.new_spatial_table USING btree (id)""", ), ], "sqlite": [ ("new_spatial_table", "geom_with_idx", 2, 2, 4326, 1), ("new_spatial_table", "geom_without_idx", 2, 2, 4326, 0), ("new_spatial_table", "geom_without_idx_2", 2, 2, 4326, 0), ], }, table_name="new_spatial_table", ) # Apply the downgrade script for tables command.downgrade(alembic_config, initial_rev.revision) check_indexes( conn, dialect_name, { "postgresql": [], "sqlite": [], }, table_name="new_spatial_table", ) geoalchemy2-0.15.2/tests/test_comparator.py000066400000000000000000000154271464355170400207420ustar00rootroot00000000000000import re import pytest from sqlalchemy import Column from sqlalchemy import MetaData from sqlalchemy import Table from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement from geoalchemy2.types import Geometry from . import select def eq_sql(a, b): a = re.sub(r"[\n\t]", "", str(a)) assert a == b @pytest.fixture def geometry_table(): table = Table("table", MetaData(), Column("geom", Geometry)) return table @pytest.fixture(params=["raw_str", "WKTElement", "WKBElement"]) def point_reference(request): wkt = "POINT(1 2)" if request.param == "WKTElement": return WKTElement(wkt) elif request.param == "WKBElement": return WKBElement( b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@" ) elif request.param == "raw_str": return wkt class TestOperator: def test_eq(self, geometry_table, point_reference): expr = geometry_table.c.geom == point_reference eq_sql(expr, '"table".geom = ST_GeomFromEWKT(:geom_1)') def test_eq_with_None(self, geometry_table): expr = geometry_table.c.geom == None # NOQA eq_sql(expr, '"table".geom IS NULL') def test_ne(self, geometry_table, point_reference): expr = geometry_table.c.geom != point_reference eq_sql(expr, '"table".geom != ST_GeomFromEWKT(:geom_1)') def test_ne_with_None(self, geometry_table): expr = geometry_table.c.geom != None # NOQA eq_sql(expr, '"table".geom IS NOT NULL') def test_intersects(self, geometry_table, point_reference): expr = geometry_table.c.geom.intersects(point_reference) eq_sql(expr, '"table".geom && ST_GeomFromEWKT(:geom_1)') def test_overlaps_or_to_left(self, geometry_table, point_reference): expr = geometry_table.c.geom.overlaps_or_to_left(point_reference) eq_sql(expr, '"table".geom &< ST_GeomFromEWKT(:geom_1)') def test_overlaps_or_below(self, geometry_table, point_reference): expr = geometry_table.c.geom.overlaps_or_below(point_reference) eq_sql(expr, '"table".geom &<| ST_GeomFromEWKT(:geom_1)') def test_overlaps_or_to_right(self, geometry_table, point_reference): expr = geometry_table.c.geom.overlaps_or_to_right(point_reference) eq_sql(expr, '"table".geom &> ST_GeomFromEWKT(:geom_1)') def test_to_left(self, geometry_table, point_reference): expr = geometry_table.c.geom.to_left(point_reference) eq_sql(expr, '"table".geom << ST_GeomFromEWKT(:geom_1)') def test_lshift(self, geometry_table, point_reference): expr = geometry_table.c.geom << point_reference eq_sql(expr, '"table".geom << ST_GeomFromEWKT(:geom_1)') def test_below(self, geometry_table, point_reference): expr = geometry_table.c.geom.below(point_reference) eq_sql(expr, '"table".geom <<| ST_GeomFromEWKT(:geom_1)') def test_to_right(self, geometry_table, point_reference): expr = geometry_table.c.geom.to_right(point_reference) eq_sql(expr, '"table".geom >> ST_GeomFromEWKT(:geom_1)') def test_rshift(self, geometry_table, point_reference): expr = geometry_table.c.geom >> point_reference eq_sql(expr, '"table".geom >> ST_GeomFromEWKT(:geom_1)') def test_contained(self, geometry_table, point_reference): expr = geometry_table.c.geom.contained(point_reference) eq_sql(expr, '"table".geom @ ST_GeomFromEWKT(:geom_1)') def test_overlaps_or_above(self, geometry_table, point_reference): expr = geometry_table.c.geom.overlaps_or_above(point_reference) eq_sql(expr, '"table".geom |&> ST_GeomFromEWKT(:geom_1)') def test_above(self, geometry_table, point_reference): expr = geometry_table.c.geom.above(point_reference) eq_sql(expr, '"table".geom |>> ST_GeomFromEWKT(:geom_1)') def test_contains(self, geometry_table, point_reference): expr = geometry_table.c.geom.contains(point_reference) eq_sql(expr, '"table".geom ~ ST_GeomFromEWKT(:geom_1)') def test_same(self, geometry_table, point_reference): expr = geometry_table.c.geom.same(point_reference) eq_sql(expr, '"table".geom ~= ST_GeomFromEWKT(:geom_1)') def test_distance_centroid(self, geometry_table, point_reference): expr = geometry_table.c.geom.distance_centroid(point_reference) eq_sql(expr, '"table".geom <-> ST_GeomFromEWKT(:geom_1)') def test_distance_centroid_select(self, geometry_table, point_reference): s = ( geometry_table.select() .order_by(geometry_table.c.geom.distance_centroid(point_reference)) .limit(10) ) eq_sql( s, 'SELECT ST_AsEWKB("table".geom) AS geom ' 'FROM "table" ' 'ORDER BY "table".geom <-> ST_GeomFromEWKT(:geom_1) ' "LIMIT :param_1", ) assert s.compile().params == {"geom_1": point_reference, "param_1": 10} def test_distance_centroid_select_with_label(self, geometry_table, point_reference): s = select([geometry_table.c.geom.distance_centroid(point_reference).label("dc")]) s = s.order_by("dc").limit(10) eq_sql( s, 'SELECT "table".geom <-> ST_GeomFromEWKT(:geom_1) AS dc ' 'FROM "table" ORDER BY dc LIMIT :param_1', ) assert s.compile().params == {"geom_1": point_reference, "param_1": 10} def test_distance_box(self, geometry_table, point_reference): expr = geometry_table.c.geom.distance_box(point_reference) eq_sql(expr, '"table".geom <#> ST_GeomFromEWKT(:geom_1)') def test_distance_box_select(self, geometry_table, point_reference): s = ( geometry_table.select() .order_by(geometry_table.c.geom.distance_box(point_reference)) .limit(10) ) eq_sql( s, 'SELECT ST_AsEWKB("table".geom) AS geom ' 'FROM "table" ' 'ORDER BY "table".geom <#> ST_GeomFromEWKT(:geom_1) ' "LIMIT :param_1", ) assert s.compile().params == {"geom_1": point_reference, "param_1": 10} def test_distance_box_select_with_label(self, geometry_table, point_reference): s = select([geometry_table.c.geom.distance_box(point_reference).label("dc")]) s = s.order_by("dc").limit(10) eq_sql( s, 'SELECT "table".geom <#> ST_GeomFromEWKT(:geom_1) AS dc ' 'FROM "table" ORDER BY dc LIMIT :param_1', ) assert s.compile().params == {"geom_1": point_reference, "param_1": 10} def test_intersects_nd(self, geometry_table): expr = geometry_table.c.geom.intersects_nd( "Box3D(ST_GeomFromEWKT('LINESTRING(1 2 3, 3 4 5, 5 6 5)'));" ) eq_sql(expr, '"table".geom &&& ST_GeomFromEWKT(:geom_1)') geoalchemy2-0.15.2/tests/test_elements.py000066400000000000000000000457541464355170400204150ustar00rootroot00000000000000import re from itertools import permutations import pytest from shapely import wkb from sqlalchemy import Column from sqlalchemy import MetaData from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import func from geoalchemy2.elements import CompositeElement from geoalchemy2.elements import RasterElement from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement from geoalchemy2.exc import ArgumentError from geoalchemy2.types import Geometry @pytest.fixture def geometry_table(): table = Table("table", MetaData(), Column("geom", Geometry)) return table def eq_sql(a, b): a = re.sub(r"[\n\t]", "", str(a)) assert a == b class TestWKTElement: _srid = 3857 # expected srid _wkt = "POINT (1 2)" # expected wkt _ewkt = "SRID=3857;POINT (1 2)" # expected ewkt def test_ctor(self): e1 = WKTElement(self._wkt) e2 = WKTElement(self._wkt, extended=False) e3 = WKTElement(self._wkt, srid=self._srid) e4 = WKTElement(self._wkt, srid=self._srid, extended=True) e5 = WKTElement(self._wkt, srid=self._srid, extended=False) assert e1.desc == e2.desc == e3.desc == e4.desc == e5.desc == self._wkt assert e1.srid == e2.srid == -1 assert e3.srid == e4.srid == e5.srid == self._srid assert e1.extended == e2.extended == e3.extended == (not e4.extended) == e5.extended def test_desc(self): e = WKTElement(self._wkt) assert e.desc == self._wkt def test_function_call(self): e = WKTElement(self._wkt) f = e.ST_Buffer(2) eq_sql( f, "ST_Buffer(" "ST_GeomFromText(:ST_GeomFromText_1, :ST_GeomFromText_2), " ":ST_Buffer_1)", ) assert f.compile().params == { "ST_Buffer_1": 2, "ST_GeomFromText_1": self._wkt, "ST_GeomFromText_2": -1, } def test_attribute_error(self): e = WKTElement(self._wkt) assert not hasattr(e, "foo") def test_pickle_unpickle(self): import pickle e = WKTElement(self._wkt, srid=3, extended=False) pickled = pickle.dumps(e) unpickled = pickle.loads(pickled) assert unpickled.srid == 3 assert unpickled.extended is False assert unpickled.data == self._wkt f = unpickled.ST_Buffer(2) eq_sql( f, "ST_Buffer(" "ST_GeomFromText(:ST_GeomFromText_1, :ST_GeomFromText_2), " ":ST_Buffer_1)", ) assert f.compile().params == { "ST_Buffer_1": 2, "ST_GeomFromText_1": self._wkt, "ST_GeomFromText_2": 3, } def test_eq(self): a = WKTElement(self._wkt) b = WKTElement(self._wkt) assert a == b def test_hash(self): a = WKTElement(self._wkt) b = WKTElement("POINT(10 20)") c = WKTElement("POINT(10 20)") assert set([a, b, c]) == set([a, b, c]) assert len(set([a, b, c])) == 2 def test_as_wkt_as_ewkt(self): e1 = WKTElement(self._wkt) e2 = WKTElement(self._wkt, srid=self._srid) # No SRID e3 = e1.as_ewkt() assert e3.desc == self._wkt assert e3.srid == -1 assert e3.extended is False # With SRID e4 = e2.as_ewkt() assert e4.desc == f"SRID={self._srid};{self._wkt}" assert e4.srid == self._srid assert e4.extended is True assert e3.as_wkt() == e3.as_wkt().as_wkt() == e1 assert e4.as_wkt() == e4.as_wkt().as_wkt() == e2 class TestExtendedWKTElement: _srid = 3857 # expected srid _wkt = "POINT (1 2 3)" # expected wkt _ewkt = "SRID=3857;POINT (1 2 3)" # expected ewkt def test_ctor(self): arbitrary_srid = self._srid + 1 e1 = WKTElement(self._ewkt) e2 = WKTElement(self._ewkt, extended=True) e3 = WKTElement(self._ewkt, srid=arbitrary_srid) e4 = WKTElement(self._ewkt, srid=arbitrary_srid, extended=True) e5 = WKTElement(self._ewkt, srid=arbitrary_srid, extended=False) assert e1.desc == e2.desc == e3.desc == e4.desc == e5.desc == self._ewkt assert e1.srid == e2.srid == self._srid assert e3.srid == e4.srid == e5.srid == arbitrary_srid assert e1.extended == e2.extended == e3.extended == e4.extended == (not e5.extended) def test_desc(self): e = WKTElement(self._ewkt) assert e.desc == self._ewkt def test_function_call(self): e = WKTElement(self._ewkt, extended=True) f = e.ST_Buffer(2) eq_sql(f, "ST_Buffer(" "ST_GeomFromEWKT(:ST_GeomFromEWKT_1), " ":ST_Buffer_1)") assert f.compile().params == {"ST_Buffer_1": 2, "ST_GeomFromEWKT_1": self._ewkt} def test_pickle_unpickle(self): import pickle e = WKTElement(self._ewkt, extended=True) pickled = pickle.dumps(e) unpickled = pickle.loads(pickled) assert unpickled.srid == self._srid assert unpickled.extended is True assert unpickled.data == self._ewkt f = unpickled.ST_Buffer(2) eq_sql(f, "ST_Buffer(" "ST_GeomFromEWKT(:ST_GeomFromEWKT_1), " ":ST_Buffer_1)") assert f.compile().params == { "ST_Buffer_1": 2, "ST_GeomFromEWKT_1": self._ewkt, } def test_unpack_srid_from_ewkt(self): """ Unpack SRID from WKT struct (when it is not provided as arg) to ensure geometry result processor preserves query SRID. """ e = WKTElement(self._ewkt, extended=True) assert e.srid == self._srid assert e.desc == self._ewkt def test_unpack_srid_from_ewkt_forcing_srid(self): e = WKTElement(self._ewkt, srid=9999, extended=True) assert e.srid == 9999 assert e.desc == self._ewkt def test_unpack_srid_from_bad_ewkt(self): with pytest.raises(ArgumentError): WKTElement("SRID=BAD SRID;POINT (1 2 3)", extended=True) with pytest.raises(ArgumentError): WKTElement("SRID=BAD SRID;POINT (1 2 3)", extended=True) with pytest.raises(ArgumentError): WKTElement("SRID=1234;1234;1234;POINT (1 2 3)", extended=True) def test_eq(self): a = WKTElement(self._ewkt, extended=True) b = WKTElement(self._ewkt, extended=True) assert a == b def test_hash(self): a = WKTElement("SRID=3857;POINT (1 2 3)", extended=True) b = WKTElement("SRID=3857;POINT (10 20 30)", extended=True) c = WKTElement("SRID=3857;POINT (10 20 30)", extended=True) assert set([a, b, c]) == set([a, b, c]) assert len(set([a, b, c])) == 2 def test_missing_srid(self): with pytest.raises(ArgumentError, match="invalid EWKT string"): WKTElement(self._wkt, extended=True) def test_missing_semi_colon(self): with pytest.raises(ArgumentError, match="invalid EWKT string"): WKTElement("SRID=3857" + self._wkt, extended=True) def test_as_wkt_as_ewkt(self): arbitrary_srid = self._srid + 1 e1 = WKTElement(self._ewkt) e2 = WKTElement(self._ewkt, srid=arbitrary_srid) # No arbitrary SRID e3 = e1.as_wkt() assert e3.desc == self._wkt assert e3.srid == self._srid assert e3.extended is False assert e3.as_ewkt() == e1 # With arbitrary SRID e4 = e2.as_wkt() assert e4.desc == self._wkt assert e4.srid == arbitrary_srid assert e4.extended is False # The arbitrary SRID overwrites the original SRID in the EWKT string assert e4.as_ewkt() == WKTElement(f"SRID={arbitrary_srid};{self._wkt}") class TestWKTElementFunction: def test_ST_Equal_WKTElement_WKTElement(self): expr = func.ST_Equals(WKTElement("POINT(1 2)"), WKTElement("POINT(1 2)")) eq_sql( expr, "ST_Equals(" "ST_GeomFromText(:ST_GeomFromText_1, :ST_GeomFromText_2), " "ST_GeomFromText(:ST_GeomFromText_3, :ST_GeomFromText_4))", ) assert expr.compile().params == { "ST_GeomFromText_1": "POINT(1 2)", "ST_GeomFromText_2": -1, "ST_GeomFromText_3": "POINT(1 2)", "ST_GeomFromText_4": -1, } def test_ST_Equal_Column_WKTElement(self, geometry_table): expr = func.ST_Equals(geometry_table.c.geom, WKTElement("POINT(1 2)")) eq_sql( expr, 'ST_Equals("table".geom, ' "ST_GeomFromText(:ST_GeomFromText_1, :ST_GeomFromText_2))", ) assert expr.compile().params == { "ST_GeomFromText_1": "POINT(1 2)", "ST_GeomFromText_2": -1, } class TestExtendedWKTElementFunction: def test_ST_Equal_WKTElement_WKTElement(self): expr = func.ST_Equals( WKTElement("SRID=3857;POINT(1 2 3)", extended=True), WKTElement("SRID=3857;POINT(1 2 3)", extended=True), ) eq_sql( expr, "ST_Equals(" "ST_GeomFromEWKT(:ST_GeomFromEWKT_1), " "ST_GeomFromEWKT(:ST_GeomFromEWKT_2))", ) assert expr.compile().params == { "ST_GeomFromEWKT_1": "SRID=3857;POINT(1 2 3)", "ST_GeomFromEWKT_2": "SRID=3857;POINT(1 2 3)", } def test_ST_Equal_Column_WKTElement(self, geometry_table): expr = func.ST_Equals( geometry_table.c.geom, WKTElement("SRID=3857;POINT(1 2 3)", extended=True) ) eq_sql(expr, 'ST_Equals("table".geom, ' "ST_GeomFromEWKT(:ST_GeomFromEWKT_1))") assert expr.compile().params == { "ST_GeomFromEWKT_1": "SRID=3857;POINT(1 2 3)", } class TestExtendedWKBElement: # _bin/_hex computed by following query: # SELECT ST_GeomFromEWKT('SRID=3;POINT(1 2)'); _bin_ewkb = memoryview( b"\x01\x01\x00\x00 \x03\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@" ) _bin_wkb = memoryview( b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@" ) _hex_ewkb = str("010100002003000000000000000000f03f0000000000000040") _hex_wkb = str("0101000000000000000000f03f0000000000000040") _srid = 3 # expected srid _wkt = "POINT (1 2)" # expected wkt def test_desc(self): e1 = WKBElement(self._bin_ewkb, extended=True) e2 = WKBElement(self._bin_ewkb) assert e1.desc == self._hex_ewkb assert e2.desc == self._hex_ewkb def test_desc_str(self): e = WKBElement(self._hex_ewkb) assert e.desc == self._hex_ewkb def test_function_call(self): e = WKBElement(self._bin_ewkb, extended=True) f = e.ST_Buffer(2) eq_sql(f, "ST_Buffer(" "ST_GeomFromEWKB(:ST_GeomFromEWKB_1), " ":ST_Buffer_1)") assert f.compile().params == { "ST_Buffer_1": 2, "ST_GeomFromEWKB_1": self._bin_ewkb, } def test_function_str(self): e = WKBElement(self._bin_ewkb, extended=True) assert isinstance(str(e), str) def test_pickle_unpickle(self): import pickle e = WKBElement(self._bin_ewkb, srid=self._srid, extended=True) pickled = pickle.dumps(e) unpickled = pickle.loads(pickled) assert unpickled.srid == self._srid assert unpickled.extended is True assert unpickled.data == bytes(self._bin_ewkb) f = unpickled.ST_Buffer(2) eq_sql(f, "ST_Buffer(" "ST_GeomFromEWKB(:ST_GeomFromEWKB_1), " ":ST_Buffer_1)") assert f.compile().params == { "ST_Buffer_1": 2, "ST_GeomFromEWKB_1": bytes(self._bin_ewkb), } def test_unpack_srid_from_bin(self): """ Unpack SRID from WKB struct (when it is not provided as arg) to ensure geometry result processor preserves query SRID. """ e = WKBElement(self._bin_ewkb, extended=True) assert e.srid == self._srid assert wkb.loads(bytes(e.data)).wkt == self._wkt def test_unpack_srid_from_bin_forcing_srid(self): e = WKBElement(self._bin_ewkb, srid=9999, extended=True) assert e.srid == 9999 assert wkb.loads(bytes(e.data)).wkt == self._wkt def test_unpack_srid_from_hex(self): e = WKBElement(self._hex_ewkb, extended=True) assert e.srid == self._srid assert wkb.loads(e.data, hex=True).wkt == self._wkt def test_eq(self): a = WKBElement(self._bin_ewkb, extended=True) b = WKBElement(self._bin_ewkb, extended=True) assert a == b def test_hash(self): a = WKBElement(str("010100002003000000000000000000f03f0000000000000040"), extended=True) b = WKBElement(str("010100002003000000000000000000f02f0000000000000040"), extended=True) c = WKBElement(str("010100002003000000000000000000f02f0000000000000040"), extended=True) assert set([a, b, c]) == set([a, b, c]) assert len(set([a, b, c])) == 2 def test_as_wkt_as_ewkt(self): arbitrary_srid = self._srid + 1 e1 = WKBElement(self._bin_ewkb) e2 = WKBElement(self._bin_ewkb, srid=arbitrary_srid) e3 = WKBElement(self._hex_ewkb) e4 = WKBElement(self._hex_ewkb, srid=arbitrary_srid) # Bin with no arbitrary SRID e5 = e1.as_wkb() assert e5.desc == self._hex_wkb assert e5.srid == self._srid assert e5.extended is False assert e5.as_ewkb() == e1 # Bin with arbitrary SRID e6 = e2.as_wkb() assert e6.desc == self._hex_wkb assert e6.srid == arbitrary_srid assert e6.extended is False # The arbitrary SRID overwrites the original SRID in the EWKB string e6_ewkb = WKBElement(self._bin_ewkb, srid=arbitrary_srid) data = bytearray(e6_ewkb.data) data[5] = 4 e6_ewkb.data = memoryview(data) assert e6.as_ewkb() == e6_ewkb # Hex with no arbitrary SRID e7 = e3.as_wkb() assert e7.desc == self._hex_wkb assert e7.srid == self._srid assert e7.extended is False assert e7.as_ewkb() == e3 # Hex with arbitrary SRID e8 = e4.as_wkb() assert e8.desc == self._hex_wkb assert e8.srid == arbitrary_srid assert e8.extended is False # The arbitrary SRID overwrites the original SRID in the EWKB string e8_ewkb = WKBElement(self._hex_ewkb, srid=arbitrary_srid) e8_ewkb.data = e8_ewkb.data[:11] + "4" + e8_ewkb.data[12:] assert e8.as_ewkb() == e8_ewkb class TestWKBElement: def test_desc(self): e = WKBElement(b"\x01\x02") assert e.desc == "0102" def test_function_call(self): e = WKBElement(b"\x01\x02") f = e.ST_Buffer(2) eq_sql( f, "ST_Buffer(" "ST_GeomFromWKB(:ST_GeomFromWKB_1, :ST_GeomFromWKB_2), " ":ST_Buffer_1)", ) assert f.compile().params == { "ST_Buffer_1": 2, "ST_GeomFromWKB_1": b"\x01\x02", "ST_GeomFromWKB_2": -1, } def test_attribute_error(self): e = WKBElement(b"\x01\x02") assert not hasattr(e, "foo") def test_function_str(self): e = WKBElement(b"\x01\x02") assert isinstance(str(e), str) def test_eq(self): a = WKBElement(b"\x01\x02") b = WKBElement(b"\x01\x02") assert a == b def test_hash(self): a = WKBElement(b"\x01\x02") b = WKBElement(b"\x01\x03") c = WKBElement(b"\x01\x03") assert set([a, b, c]) == set([a, b, c]) assert len(set([a, b, c])) == 2 class TestNotEqualSpatialElement: # _bin/_hex computed by following query: # SELECT ST_GeomFromEWKT('SRID=3;POINT(1 2)'); _ewkb = memoryview( b"\x01\x01\x00\x00 \x03\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@" ) _wkb = wkb.loads(bytes(_ewkb)).wkb _hex = str("010100002003000000000000000000f03f0000000000000040") _srid = 3 _wkt = "POINT (1 2)" _ewkt = "SRID=3;POINT (1 2)" def test_eq(self): a = WKBElement(self._ewkb, extended=True) b = WKBElement(self._wkb, srid=self._srid) c = WKTElement(self._wkt, srid=self._srid) d = WKTElement(self._ewkt, extended=True) e = WKBElement(self._hex, extended=True) assert a == a assert b == b assert c == c assert d == d assert e == e assert a == e and e == a def test_neq_other_types(self): a = WKBElement(self._ewkb, extended=True) b = WKBElement(self._wkb, srid=self._srid) c = WKTElement(self._wkt, srid=self._srid) d = WKTElement(self._ewkt, extended=True) e = WKBElement(self._hex, extended=True) all_elements = [a, b, c, d, None, 1, "test"] for i, j in permutations(all_elements, 2): assert i != j for i in all_elements[1:]: assert i != e and e != i class TestRasterElement: rast_data = ( b"\x01\x00\x00\x01\x00\x9a\x99\x99\x99\x99\x99\xc9?\x9a\x99\x99\x99\x99\x99" b"\xc9\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe6\x10\x00" b"\x00\x05\x00\x05\x00D\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x01" b"\x01\x00\x00\x01\x01\x00\x00\x00\x01\x00\x00\x00\x00" ) hex_rast_data = ( "01000001009a9999999999c93f9a9999999999c9bf0000000000000000000000000000f03" "f00000000000000000000000000000000e610000005000500440001010101010101010100" "010101000001010000000100000000" ) def test_desc(self): e = RasterElement(self.rast_data) assert e.desc == self.hex_rast_data assert e.srid == 4326 e = RasterElement(self.hex_rast_data) assert e.desc == self.hex_rast_data assert e.srid == 4326 def test_function_call(self): e = RasterElement(self.rast_data) f = e.ST_Height() eq_sql(f, "ST_Height(raster(:raster_1))") assert f.compile().params == {"raster_1": self.hex_rast_data} def test_pickle_unpickle(self): import pickle e = RasterElement(self.rast_data) assert e.srid == 4326 assert e.extended is True assert e.data == self.hex_rast_data pickled = pickle.dumps(e) unpickled = pickle.loads(pickled) assert unpickled.srid == 4326 assert unpickled.extended is True assert unpickled.data == self.hex_rast_data f = unpickled.ST_Height() eq_sql(f, "ST_Height(raster(:raster_1))") assert f.compile().params == { "raster_1": self.hex_rast_data, } def test_hash(self): new_hex_rast_data = self.hex_rast_data.replace("f", "e") a = WKBElement(self.hex_rast_data) b = WKBElement(new_hex_rast_data) c = WKBElement(new_hex_rast_data) assert set([a, b, c]) == set([a, b, c]) assert len(set([a, b, c])) == 2 class TestCompositeElement: def test_compile(self): # text fixture metadata = MetaData() foo = Table("foo", metadata, Column("one", String)) e = CompositeElement(foo.c.one, "geom", String) assert str(e) == "(foo.one).geom" geoalchemy2-0.15.2/tests/test_functional.py000066400000000000000000001274001464355170400207300ustar00rootroot00000000000000import re from json import loads import pytest try: from psycopg2cffi import compat except ImportError: pass else: compat.register() del compat from pkg_resources import parse_version from shapely.geometry import LineString from shapely.geometry import Point from sqlalchemy import CheckConstraint from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import __version__ as SA_VERSION from sqlalchemy import bindparam from sqlalchemy import text from sqlalchemy.exc import DataError from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import OperationalError from sqlalchemy.exc import ProgrammingError from sqlalchemy.exc import SAWarning from sqlalchemy.sql import func from sqlalchemy.testing.assertions import ComparesTables import geoalchemy2.admin.dialects from geoalchemy2 import Geography from geoalchemy2 import Geometry from geoalchemy2 import Raster from geoalchemy2.admin.dialects.geopackage import ( _get_spatialite_attrs as _get_spatialite_attrs_gpkg, ) from geoalchemy2.admin.dialects.sqlite import _get_spatialite_attrs as _get_spatialite_attrs_sqlite from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement from geoalchemy2.shape import from_shape from geoalchemy2.shape import to_shape from . import check_indexes from . import format_wkt from . import get_postgis_major_version from . import select from . import skip_case_insensitivity from . import skip_pg12_sa1217 from . import skip_postgis1 from . import test_only_with_dialects SQLA_LT_2 = parse_version(SA_VERSION) <= parse_version("1.999") class TestAdmin: @test_only_with_dialects("postgresql", "sqlite-spatialite3", "sqlite-spatialite4") def test_create_drop_tables( self, conn, metadata, Lake, Poi, Summit, Ocean, PointZ, LocalPoint, IndexTestWithSchema, IndexTestWithNDIndex, IndexTestWithoutSchema, ): metadata.drop_all(conn, checkfirst=True) metadata.create_all(conn) metadata.drop_all(conn, checkfirst=True) @test_only_with_dialects("postgresql", "mysql", "sqlite-spatialite3", "sqlite-spatialite4") def test_nullable(self, conn, metadata, setup_tables, dialect_name): # Define the table t = Table( "nullable_geom_type", metadata, Column("id", Integer, primary_key=True), Column( "geom_not_nullable", Geometry(geometry_type=None, srid=4326, spatial_index=False, nullable=False), ), Column( "geom_nullable", Geometry(geometry_type=None, srid=4326, spatial_index=False, nullable=True), ), Column( "geom_col_not_nullable", Geometry(geometry_type=None, srid=4326, spatial_index=False), nullable=False, ), Column( "geom_col_nullable", Geometry(geometry_type=None, srid=4326, spatial_index=False), nullable=True, ), ) # Create the table t.create(bind=conn) conn.execute( t.insert(), [ { "geom_not_nullable": "SRID=4326;LINESTRING(0 0,1 1)", "geom_nullable": "SRID=4326;LINESTRING(0 0,1 1)", "geom_col_not_nullable": "SRID=4326;LINESTRING(0 0,1 1)", "geom_col_nullable": "SRID=4326;LINESTRING(0 0,1 1)", }, { "geom_not_nullable": "SRID=4326;LINESTRING(0 0,1 1)", "geom_nullable": None, "geom_col_not_nullable": "SRID=4326;LINESTRING(0 0,1 1)", "geom_col_nullable": None, }, ], ) with pytest.raises((IntegrityError, OperationalError)): with conn.begin_nested(): conn.execute( t.insert(), [ { "geom_not_nullable": None, "geom_nullable": None, "geom_col_not_nullable": "SRID=4326;LINESTRING(0 0,1 1)", "geom_col_nullable": None, }, ], ) with pytest.raises((IntegrityError, OperationalError)): with conn.begin_nested(): conn.execute( t.insert(), [ { "geom_not_nullable": "SRID=4326;LINESTRING(0 0,1 1)", "geom_nullable": None, "geom_col_not_nullable": None, "geom_col_nullable": None, }, ], ) results = conn.execute(t.select()) rows = results.fetchall() assert len(rows) == 2 # Drop the table t.drop(bind=conn) @test_only_with_dialects("postgresql", "mysql") def test_no_geom_type(self, conn): with pytest.warns(UserWarning, match="srid not enforced when geometry_type is None"): # Define the table t = Table( "no_geom_type", MetaData(), Column("id", Integer, primary_key=True), Column("geom", Geometry(geometry_type=None)), Column("geom_with_srid", Geometry(geometry_type=None, srid=4326)), Column("geog", Geography(geometry_type=None)), Column("geog_with_srid", Geography(geometry_type=None, srid=4326)), ) # Create the table t.create(bind=conn) # Drop the table t.drop(bind=conn) def test_explicit_schema(self, conn): # Define the table t = Table( "a_table", MetaData(), Column("id", Integer, primary_key=True), Column("geom", Geometry()), schema="gis", ) # Create the table t.create(bind=conn) # Drop the table t.drop(bind=conn) @test_only_with_dialects("postgresql") def test_common_dialect(self, conn, monkeypatch, metadata, Lake): monkeypatch.setattr(conn.dialect, "name", "UNKNOWN DIALECT") marks = [] def before_create(table, bind, **kw): marks.append("before_create") return def after_create(table, bind, **kw): marks.append("after_create") return def before_drop(table, bind, **kw): marks.append("before_drop") return def after_drop(table, bind, **kw): marks.append("after_drop") return monkeypatch.setattr(geoalchemy2.admin.dialects.common, "before_create", value=before_create) monkeypatch.setattr(geoalchemy2.admin.dialects.common, "after_create", value=after_create) monkeypatch.setattr(geoalchemy2.admin.dialects.common, "before_drop", value=before_drop) monkeypatch.setattr(geoalchemy2.admin.dialects.common, "after_drop", value=after_drop) metadata.drop_all(conn, checkfirst=True) metadata.create_all(conn) metadata.drop_all(conn, checkfirst=True) assert marks == ["before_create", "after_create", "before_drop", "after_drop"] class TestInsertionCore: def test_insert(self, conn, Lake, setup_tables): # Issue inserts using DBAPI's executemany() method. This tests the # Geometry type's bind_processor and bind_expression functions. conn.execute( Lake.__table__.insert(), [ {"geom": "SRID=4326;LINESTRING(0 0,1 1)"}, {"geom": WKTElement("LINESTRING(0 0,2 2)", srid=4326)}, {"geom": WKTElement("SRID=4326;LINESTRING(0 0,2 2)", extended=True)}, {"geom": from_shape(LineString([[0, 0], [3, 3]]), srid=4326)}, ], ) results = conn.execute(Lake.__table__.select()) rows = results.fetchall() row = rows[0] assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert format_wkt(wkt) == "LINESTRING(0 0,1 1)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 row = rows[1] assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert format_wkt(wkt) == "LINESTRING(0 0,2 2)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 row = rows[2] assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert format_wkt(wkt) == "LINESTRING(0 0,2 2)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 row = rows[3] assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert format_wkt(wkt) == "LINESTRING(0 0,3 3)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 @pytest.mark.parametrize( "geom_type,wkt", [ pytest.param("POINT", "(1 2)", id="Point"), pytest.param("POINTZ", "(1 2 3)", id="Point Z"), pytest.param("POINTM", "(1 2 3)", id="Point M"), pytest.param("POINTZM", "(1 2 3 4)", id="Point ZM"), pytest.param("LINESTRING", "(1 2, 3 4)", id="LineString"), pytest.param("LINESTRINGZ", "(1 2 3, 4 5 6)", id="LineString Z"), pytest.param("LINESTRINGM", "(1 2 3, 4 5 6)", id="LineString M"), pytest.param("LINESTRINGZM", "(1 2 3 4, 5 6 7 8)", id="LineString ZM"), pytest.param("POLYGON", "((1 2, 3 4, 5 6, 1 2))", id="Polygon"), pytest.param("POLYGONZ", "((1 2 3, 4 5 6, 7 8 9, 1 2 3))", id="Polygon Z"), pytest.param("POLYGONM", "((1 2 3, 4 5 6, 7 8 9, 1 2 3))", id="Polygon M"), pytest.param( "POLYGONZM", "((1 2 3 4, 5 6 7 8, 9 10 11 12, 1 2 3 4))", id="Polygon ZM" ), pytest.param("MULTIPOINT", "(1 2, 3 4)", id="Multi Point"), pytest.param("MULTIPOINTZ", "(1 2 3, 4 5 6)", id="Multi Point Z"), pytest.param("MULTIPOINTM", "(1 2 3, 4 5 6)", id="Multi Point M"), pytest.param("MULTIPOINTZM", "(1 2 3 4, 5 6 7 8)", id="Multi Point ZM"), pytest.param("MULTILINESTRING", "((1 2, 3 4), (10 20, 30 40))", id="Multi LineString"), pytest.param( "MULTILINESTRINGZ", "((1 2 3, 4 5 6), (10 20 30, 40 50 60))", id="Multi LineString Z", ), pytest.param( "MULTILINESTRINGM", "((1 2 3, 4 5 6), (10 20 30, 40 50 60))", id="Multi LineString M", ), pytest.param( "MULTILINESTRINGZM", "((1 2 3 4, 5 6 7 8), (10 20 30 40, 50 60 70 80))", id="Multi LineString ZM", ), pytest.param( "MULTIPOLYGON", "(((1 2, 3 4, 5 6, 1 2)), ((10 20, 30 40, 50 60, 10 20)))", id="Multi Polygon", ), pytest.param( "MULTIPOLYGONZ", "(((1 2 3, 4 5 6, 7 8 9, 1 2 3)), ((10 20 30, 40 50 60, 70 80 90, 10 20 30)))", id="Multi Polygon Z", ), pytest.param( "MULTIPOLYGONM", "(((1 2 3, 4 5 6, 7 8 9, 1 2 3)), ((10 20 30, 40 50 60, 70 80 90, 10 20 30)))", id="Multi Polygon M", ), pytest.param( "MULTIPOLYGONZM", "(((1 2 3 4, 5 6 7 8, 9 10 11 12, 1 2 3 4))," " ((10 20 30 40, 50 60 70 80, 90 100 100 120, 10 20 30 40)))", id="Multi Polygon ZM", ), ], ) @pytest.mark.parametrize( "use_floating_point", [ pytest.param(True, id="Use floating point"), pytest.param(False, id="Do not use floating point"), ], ) def test_insert_all_geom_types( self, dialect_name, base, conn, metadata, geom_type, wkt, use_floating_point ): """Test insertion and selection of all geometry types.""" ndims = 2 if "Z" in geom_type[-2:]: ndims += 1 if geom_type.endswith("M"): ndims += 1 has_m = True else: has_m = False if ndims > 2 and dialect_name == "mysql": # Explicitly skip MySQL dialect to show that it can only work with 2D geometries pytest.xfail(reason="MySQL only supports 2D geometry types") class GeomTypeTable(base): __tablename__ = "test_geom_types" id = Column(Integer, primary_key=True) geom = Column(Geometry(srid=4326, geometry_type=geom_type, dimension=ndims)) metadata.drop_all(bind=conn, checkfirst=True) metadata.create_all(bind=conn) if use_floating_point: wkt = wkt.replace("1 2", "1.5 2.5") inserted_wkt = f"{geom_type}{wkt}" # Use the DB to generate the corresponding raw WKB raw_wkb = conn.execute( text("SELECT ST_AsBinary(ST_GeomFromText('{}', 4326))".format(inserted_wkt)) ).scalar() wkb_elem = WKBElement(raw_wkb, srid=4326) inserted_elements = [ {"geom": inserted_wkt}, {"geom": f"SRID=4326;{inserted_wkt}"}, {"geom": WKTElement(inserted_wkt, srid=4326)}, {"geom": WKTElement(f"SRID=4326;{inserted_wkt}")}, ] if dialect_name not in ["postgresql", "sqlite"] or not has_m: # Currently Shapely does not support geometry types with M dimension inserted_elements.append({"geom": wkb_elem}) inserted_elements.append({"geom": wkb_elem.as_ewkb()}) # Insert the elements conn.execute( GeomTypeTable.__table__.insert(), inserted_elements, ) # Select the elements query = select( [ GeomTypeTable.__table__.c.id, GeomTypeTable.__table__.c.geom.ST_AsText(), GeomTypeTable.__table__.c.geom.ST_SRID(), ], ) results = conn.execute(query) rows = results.all() # Check that the selected elements are the same as the inputs for row_id, row, srid in rows: checked_wkt = row.upper().replace(" ", "") expected_wkt = inserted_wkt.upper().replace(" ", "") if "MULTIPOINT" in geom_type: # Some dialects return MULTIPOINT geometries with nested parenthesis and others # do not so we remove them before checking the results checked_wkt = re.sub(r"\(([0-9\.]+)\)", "\\1", checked_wkt) if row_id >= 5 and dialect_name in ["geopackage"] and has_m: # Currently Shapely does not support geometry types with M dimension assert checked_wkt != expected_wkt else: assert checked_wkt == expected_wkt assert srid == 4326 @test_only_with_dialects("postgresql", "sqlite") def test_insert_geom_poi(self, conn, Poi, setup_tables): conn.execute( Poi.__table__.insert(), [ {"geom": "SRID=4326;POINT(1 1)"}, {"geom": WKTElement("POINT(1 1)", srid=4326)}, {"geom": WKTElement("SRID=4326;POINT(1 1)", extended=True)}, {"geom": from_shape(Point(1, 1), srid=4326)}, {"geom": from_shape(Point(1, 1), srid=4326, extended=True)}, ], ) results = conn.execute(Poi.__table__.select()) rows = results.fetchall() for row in rows: assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert format_wkt(wkt) == "POINT(1 1)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 assert row[1] == from_shape(Point(1, 1), srid=4326, extended=True) def test_insert_negative_coords(self, conn, Poi, setup_tables, dialect_name): conn.execute( Poi.__table__.insert(), [ {"geom": "SRID=4326;POINT(-1 1)"}, {"geom": WKTElement("POINT(-1 1)", srid=4326)}, {"geom": WKTElement("SRID=4326;POINT(-1 1)", extended=True)}, {"geom": from_shape(Point(-1, 1), srid=4326)}, {"geom": from_shape(Point(-1, 1), srid=4326, extended=True)}, ], ) results = conn.execute(Poi.__table__.select()) rows = results.fetchall() for row in rows: assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert format_wkt(wkt) == "POINT(-1 1)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 if dialect_name == "mysql": assert row[1] == from_shape(Point(-1, 1), srid=4326) else: assert row[1] == from_shape(Point(-1, 1), srid=4326, extended=True) class TestSelectBindParam: @pytest.fixture def setup_one_lake(self, conn, Lake, setup_tables): conn.execute(Lake.__table__.insert(), {"geom": "SRID=4326;LINESTRING(0 0,1 1)"}) def test_select_bindparam(self, conn, Lake, setup_one_lake): s = Lake.__table__.select().where(Lake.__table__.c.geom == bindparam("geom")) params = {"geom": "SRID=4326;LINESTRING(0 0,1 1)"} if SQLA_LT_2: results = conn.execute(s, **params) else: results = conn.execute(s, params) rows = results.fetchall() row = rows[0] assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert format_wkt(wkt) == "LINESTRING(0 0,1 1)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 def test_select_bindparam_WKBElement(self, conn, Lake, setup_one_lake): s = Lake.__table__.select().where(Lake.__table__.c.geom == bindparam("geom")) wkbelement = from_shape(LineString([[0, 0], [1, 1]]), srid=4326) params = {"geom": wkbelement} if SQLA_LT_2: results = conn.execute(s, **params) else: results = conn.execute(s, params) rows = results.fetchall() row = rows[0] assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert format_wkt(wkt) == "LINESTRING(0 0,1 1)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 def test_select_bindparam_WKBElement_extented(self, conn, Lake, setup_one_lake, dialect_name): s = Lake.__table__.select() results = conn.execute(s) rows = results.fetchall() geom = rows[0][1] assert isinstance(geom, WKBElement) if dialect_name == "mysql": assert geom.extended is False else: assert geom.extended is True s = Lake.__table__.select().where(Lake.__table__.c.geom == bindparam("geom")) params = {"geom": geom} if SQLA_LT_2: results = conn.execute(s, **params) else: results = conn.execute(s, params) rows = results.fetchall() row = rows[0] assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert format_wkt(wkt) == "LINESTRING(0 0,1 1)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 class TestInsertionORM: def test_WKT(self, session, Lake, setup_tables, dialect_name, postgis_version): # With PostGIS 1.5: # IntegrityError: (IntegrityError) new row for relation "lake" violates # check constraint "enforce_srid_geom" # # With PostGIS 2.0: # DataError: (DataError) Geometry SRID (0) does not match column SRID # (4326) # # With PostGIS 3.0: # The SRID is taken from the Column definition so no error is reported lake = Lake("LINESTRING(0 0,1 1)") session.add(lake) if dialect_name == "postgresql" and postgis_version < 3: with pytest.raises((DataError, IntegrityError)): session.flush() else: session.flush() def test_WKTElement(self, session, Lake, setup_tables, dialect_name): lake = Lake(WKTElement("LINESTRING(0 0,1 1)", srid=4326)) session.add(lake) session.flush() session.expire(lake) assert isinstance(lake.geom, WKBElement) if dialect_name == "mysql": # Not extended case assert str(lake.geom) == ( "0102000000020000000000000000000000000000000000000000000" "0000000f03f000000000000f03f" ) else: assert str(lake.geom) == ( "0102000020e6100000020000000000000000000000000000000000000000000" "0000000f03f000000000000f03f" ) wkt = session.execute(lake.geom.ST_AsText()).scalar() assert format_wkt(wkt) == "LINESTRING(0 0,1 1)" srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 def test_WKBElement(self, session, Lake, setup_tables, dialect_name): shape = LineString([[0, 0], [1, 1]]) lake = Lake(from_shape(shape, srid=4326)) session.add(lake) session.flush() session.expire(lake) assert isinstance(lake.geom, WKBElement) if dialect_name == "mysql": # Not extended case assert str(lake.geom) == ( "0102000000020000000000000000000000000000000000000000000" "0000000f03f000000000000f03f" ) else: assert str(lake.geom) == ( "0102000020e6100000020000000000000000000000000000000000000000000" "0000000f03f000000000000f03f" ) wkt = session.execute(lake.geom.ST_AsText()).scalar() assert format_wkt(wkt) == "LINESTRING(0 0,1 1)" srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 @test_only_with_dialects("postgresql", "mysql", "sqlite-spatialite3", "sqlite-spatialite4") def test_transform(self, session, LocalPoint, setup_tables): if session.bind.dialect.name == "mysql": # Explicitly skip MySQL dialect to show that there is an issue pytest.skip( reason=( "The SRID is not properly retrieved so an exception is raised. TODO: This " "should be fixed later" ) ) # Create new point instance p = LocalPoint() p.geom = "SRID=4326;POINT(5 45)" # Insert geometry with wrong SRID p.managed_geom = "SRID=4326;POINT(5 45)" # Insert geometry with wrong SRID # Insert point session.add(p) session.flush() session.expire(p) # Query the point and check the result pt = session.query(LocalPoint).one() assert pt.id == 1 assert pt.geom.srid == 4326 assert pt.managed_geom.srid == 4326 pt_wkb = to_shape(pt.geom) assert round(pt_wkb.x, 5) == 5 assert round(pt_wkb.y, 5) == 45 pt_wkb = to_shape(pt.managed_geom) assert round(pt_wkb.x, 5) == 5 assert round(pt_wkb.y, 5) == 45 # Check that the data is correct in DB using raw query q = text( """ SELECT id, ST_AsText(geom) AS geom, ST_AsText(managed_geom) AS managed_geom FROM local_point; """ ) res_q = session.execute(q).fetchone() assert res_q.id == 1 for i in [res_q.geom, res_q.managed_geom]: x, y = re.match(r"POINT\((\d+\.\d*) (\d+\.\d*)\)", i).groups() assert round(float(x), 3) == 857581.899 assert round(float(y), 3) == 6435414.748 class TestUpdateORM: def test_WKTElement(self, session, Lake, setup_tables, dialect_name): raw_wkt = "LINESTRING(0 0,1 1)" lake = Lake(WKTElement(raw_wkt, srid=4326)) session.add(lake) # Insert in DB session.flush() # Check what was inserted in DB assert isinstance(lake.geom, WKTElement) wkt = session.execute(lake.geom.ST_AsText()).scalar() assert format_wkt(wkt) == raw_wkt srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 if dialect_name != "mysql": # Set geometry to None lake.geom = None # Update in DB session.flush() # Check what was updated in DB assert lake.geom is None cols = [Lake.id, Lake.geom] assert session.execute(select(cols)).fetchall() == [(1, None)] # Reset geometry to initial value lake.geom = WKTElement(raw_wkt, srid=4326) # Update in DB session.flush() # Check what was inserted in DB assert isinstance(lake.geom, WKTElement) wkt = session.execute(lake.geom.ST_AsText()).scalar() assert format_wkt(wkt) == raw_wkt srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 def test_WKBElement(self, session, Lake, setup_tables, dialect_name): shape = LineString([[0, 0], [1, 1]]) initial_value = from_shape(shape, srid=4326) lake = Lake(initial_value) session.add(lake) # Insert in DB session.flush() # Check what was inserted in DB assert isinstance(lake.geom, WKBElement) wkt_query = lake.geom.ST_AsText() wkt = session.execute(wkt_query).scalar() assert format_wkt(wkt) == "LINESTRING(0 0,1 1)" srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 if dialect_name != "mysql": # Set geometry to None lake.geom = None # Update in DB session.flush() # Check what was updated in DB assert lake.geom is None cols = [Lake.id, Lake.geom] assert session.execute(select(cols)).fetchall() == [(1, None)] # Reset geometry to initial value lake.geom = initial_value # Insert in DB session.flush() # Check what was inserted in DB assert isinstance(lake.geom, WKBElement) wkt = session.execute(lake.geom.ST_AsText()).scalar() assert format_wkt(wkt) == "LINESTRING(0 0,1 1)" srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 session.refresh(lake) assert to_shape(lake.geom) == to_shape(initial_value) def test_other_type_fail(self, session, Lake, setup_tables, dialect_name): shape = LineString([[0, 0], [1, 1]]) lake = Lake(from_shape(shape, srid=4326)) session.add(lake) # Insert in DB session.flush() # Set geometry to 1, which is of wrong type lake.geom = 1 # Update in DB if dialect_name == "postgresql": with pytest.raises(ProgrammingError): # Call __eq__() operator of _SpatialElement with 'other' argument equal to 1 # so the lake instance is detected as different and is thus updated but with # an invalid geometry. session.flush() elif dialect_name in ["sqlite", "geopackage"]: # SQLite silently set the geom attribute to NULL session.flush() session.refresh(lake) assert lake.geom is None elif dialect_name == "mysql": with pytest.raises(OperationalError): session.flush() else: raise ValueError(f"Unexpected dialect: {dialect_name}") class TestCallFunction: @pytest.fixture def setup_one_lake(self, session, Lake, setup_tables): lake = Lake(WKTElement("LINESTRING(0 0,1 1)", srid=4326)) session.add(lake) session.flush() session.expire(lake) return lake.id @pytest.fixture def setup_one_poi(self, session, Poi, setup_tables): p = Poi("POINT(5 45)") session.add(p) session.flush() session.expire(p) return p.id def test_ST_GeometryType(self, session, Lake, setup_one_lake, dialect_name): lake_id = setup_one_lake if dialect_name == "postgresql": expected_geometry_type = "ST_LineString" else: expected_geometry_type = "LINESTRING" s = select([func.ST_GeometryType(Lake.__table__.c.geom)]) r1 = session.execute(s).scalar() assert r1 == expected_geometry_type lake = session.query(Lake).get(lake_id) r2 = session.execute(lake.geom.ST_GeometryType()).scalar() assert r2 == expected_geometry_type r3 = session.query(Lake.geom.ST_GeometryType()).scalar() assert r3 == expected_geometry_type r4 = session.query(Lake).filter(Lake.geom.ST_GeometryType() == expected_geometry_type).one() assert isinstance(r4, Lake) assert r4.id == lake_id @test_only_with_dialects("postgresql", "sqlite") def test_ST_Buffer(self, session, Lake, setup_one_lake): lake_id = setup_one_lake s = select([func.ST_Buffer(Lake.__table__.c.geom, 2)]) r1 = session.execute(s).scalar() assert isinstance(r1, WKBElement) lake = session.query(Lake).get(lake_id) assert isinstance(lake.geom, WKBElement) r2 = session.execute(lake.geom.ST_Buffer(2)).scalar() assert isinstance(r2, WKBElement) r3 = session.query(Lake.geom.ST_Buffer(2)).scalar() assert isinstance(r3, WKBElement) assert r1.data == r2.data == r3.data r4 = ( session.query(Lake) .filter(func.ST_Within(WKTElement("POINT(0 0)", srid=4326), Lake.geom.ST_Buffer(2))) .one() ) assert isinstance(r4, Lake) assert r4.id == lake_id @test_only_with_dialects("postgresql", "sqlite") def test_ST_AsGeoJson(self, session, Lake, setup_one_lake): lake_id = setup_one_lake lake = session.query(Lake).get(lake_id) # Test geometry s1 = select([func.ST_AsGeoJSON(Lake.__table__.c.geom)]) r1 = session.execute(s1).scalar() assert loads(r1) == {"type": "LineString", "coordinates": [[0, 0], [1, 1]]} # Test geometry ORM s1_orm = lake.geom.ST_AsGeoJSON() r1_orm = session.execute(s1_orm).scalar() assert loads(r1_orm) == {"type": "LineString", "coordinates": [[0, 0], [1, 1]]} # Test from WKTElement s1_wkt = WKTElement("LINESTRING(0 0,1 1)", srid=4326, extended=False).ST_AsGeoJSON() r1_wkt = session.execute(s1_wkt).scalar() assert loads(r1_wkt) == {"type": "LineString", "coordinates": [[0, 0], [1, 1]]} # Test from extended WKTElement s1_ewkt = WKTElement("SRID=4326;LINESTRING(0 0,1 1)", extended=True).ST_AsGeoJSON() r1_ewkt = session.execute(s1_ewkt).scalar() assert loads(r1_ewkt) == {"type": "LineString", "coordinates": [[0, 0], [1, 1]]} # Test with function inside s1_func = select( [func.ST_AsGeoJSON(func.ST_Translate(Lake.__table__.c.geom, 0.0, 0.0, 0.0))] ) r1_func = session.execute(s1_func).scalar() assert loads(r1_func) == {"type": "LineString", "coordinates": [[0, 0], [1, 1]]} @skip_case_insensitivity() @test_only_with_dialects("postgresql", "mysql", "sqlite-spatialite3", "sqlite-spatialite4") def test_comparator_case_insensitivity(self, session, Lake, setup_one_lake): lake_id = setup_one_lake s = select([func.ST_Transform(Lake.__table__.c.geom, 2154)]) r1 = session.execute(s).scalar() assert isinstance(r1, WKBElement) lake = session.query(Lake).get(lake_id) r2 = session.execute(lake.geom.ST_Transform(2154)).scalar() assert isinstance(r2, WKBElement) r3 = session.execute(lake.geom.st_transform(2154)).scalar() assert isinstance(r3, WKBElement) r4 = session.execute(lake.geom.St_TrAnSfOrM(2154)).scalar() assert isinstance(r4, WKBElement) r5 = session.query(Lake.geom.ST_Transform(2154)).scalar() assert isinstance(r5, WKBElement) r6 = session.query(Lake.geom.st_transform(2154)).scalar() assert isinstance(r6, WKBElement) r7 = session.query(Lake.geom.St_TrAnSfOrM(2154)).scalar() assert isinstance(r7, WKBElement) assert r1.data == r2.data == r3.data == r4.data == r5.data == r6.data == r7.data def test_unknown_function_column(self, session, Lake, setup_one_lake, dialect_name): s = select([func.ST_UnknownFunction(Lake.__table__.c.geom, 2)]) exc = ProgrammingError if dialect_name == "postgresql" else OperationalError with pytest.raises(exc, match="ST_UnknownFunction"): session.execute(s) def test_unknown_function_element(self, session, Lake, setup_one_lake, dialect_name): lake_id = setup_one_lake lake = session.query(Lake).get(lake_id) s = select([func.ST_UnknownFunction(lake.geom, 2)]) exc = ProgrammingError if dialect_name == "postgresql" else OperationalError with pytest.raises(exc): # TODO: here the query fails because of a # "(psycopg2.ProgrammingError) can't adapt type 'WKBElement'" # It would be better if it could fail because of a "UndefinedFunction" error session.execute(s) def test_unknown_function_element_ORM(self, session, Lake, setup_one_lake): lake_id = setup_one_lake lake = session.query(Lake).get(lake_id) with pytest.raises(AttributeError): select([lake.geom.ST_UnknownFunction(2)]) class TestShapely: def test_to_shape(self, session, Lake, setup_tables, dialect_name): if dialect_name in ["sqlite", "geopackage"]: data_type = str elif dialect_name == "mysql": data_type = bytes else: data_type = memoryview lake = Lake(WKTElement("LINESTRING(0 0,1 1)", srid=4326)) session.add(lake) session.flush() session.expire(lake) lake = session.query(Lake).one() assert isinstance(lake.geom, WKBElement) assert isinstance(lake.geom.data, data_type) assert lake.geom.srid == 4326 s = to_shape(lake.geom) assert isinstance(s, LineString) assert s.wkt == "LINESTRING (0 0, 1 1)" lake = Lake(lake.geom) session.add(lake) session.flush() session.expire(lake) assert isinstance(lake.geom, WKBElement) assert isinstance(lake.geom.data, data_type) assert lake.geom.srid == 4326 class TestContraint: @pytest.fixture def ConstrainedLake(self, base): class ConstrainedLake(base): __tablename__ = "contrained_lake" __table_args__ = ( CheckConstraint( "(geom is null and a_str is null) = (checked_str is null)", "check_geom_sk", ), ) id = Column(Integer, primary_key=True) a_str = Column(String, nullable=True) checked_str = Column(String, nullable=True) geom = Column(Geometry(geometry_type="LINESTRING", srid=4326)) def __init__(self, geom): self.geom = geom return ConstrainedLake @test_only_with_dialects("postgresql", "sqlite-spatialite3", "sqlite-spatialite4") def test_insert(self, conn, ConstrainedLake, setup_tables): # Insert geometries conn.execute( ConstrainedLake.__table__.insert(), [ { "a_str": None, "geom": "SRID=4326;LINESTRING(0 0,1 1)", "checked_str": "test", }, {"a_str": "test", "geom": None, "checked_str": "test"}, {"a_str": None, "geom": None, "checked_str": None}, ], ) # Fail when trying to insert null geometry with pytest.raises(IntegrityError): conn.execute( ConstrainedLake.__table__.insert(), [ {"a_str": None, "geom": None, "checked_str": "should fail"}, ], ) class TestReflection: @pytest.fixture def setup_reflection_tables(self, reflection_tables_metadata, conn): reflection_tables_metadata.drop_all(conn, checkfirst=True) reflection_tables_metadata.create_all(conn) @test_only_with_dialects("postgresql", "sqlite") def test_reflection(self, conn, setup_reflection_tables, dialect_name): skip_pg12_sa1217(conn) t = Table( "lake", MetaData(), autoload_with=conn, ) if dialect_name == "postgresql": # Check index query with explicit schema t_with_schema = Table("lake", MetaData(), autoload_with=conn, schema="gis") assert sorted([col.name for col in t.columns]) == sorted( [col.name for col in t_with_schema.columns] ) assert sorted([idx.name for idx in t.indexes]) == sorted( [idx.name for idx in t_with_schema.indexes] ) if get_postgis_major_version(conn) == 1: type_ = t.c.geom.type assert isinstance(type_, Geometry) assert type_.geometry_type == "GEOMETRY" assert type_.srid == -1 else: type_ = t.c.geom.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRING" assert type_.srid == 4326 assert type_.dimension == 2 if dialect_name != "geopackage": type_ = t.c.geom_no_idx.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRING" assert type_.srid == 4326 assert type_.dimension == 2 type_ = t.c.geom_z.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGZ" assert type_.srid == 4326 assert type_.dimension == 3 type_ = t.c.geom_m.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGM" assert type_.srid == 4326 assert type_.dimension == 3 type_ = t.c.geom_zm.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGZM" assert type_.srid == 4326 assert type_.dimension == 4 # Drop the table t.drop(bind=conn) # Check the indexes check_indexes( conn, dialect_name, { "postgresql": [], "sqlite": [], "geopackage": [], }, table_name=t.name, ) # Recreate the table to check that the reflected properties are correct t.create(bind=conn) # Check the indexes if dialect_name in ["sqlite", "geopackage"]: if dialect_name == "geopackage": col_attributes = _get_spatialite_attrs_gpkg(conn, t.name, "geom") else: col_attributes = _get_spatialite_attrs_sqlite(conn, t.name, "geom") if isinstance(col_attributes[0], int): sqlite_indexes = [ ("lake", "geom", 2, 2, 4326, 1), ("lake", "geom_m", 2002, 3, 4326, 1), ("lake", "geom_no_idx", 2, 2, 4326, 0), ("lake", "geom_z", 1002, 3, 4326, 1), ("lake", "geom_zm", 3002, 4, 4326, 1), ] else: sqlite_indexes = [ ("lake", "geom", "LINESTRING", "XY", 4326, 1), ("lake", "geom_m", "LINESTRING", "XYM", 4326, 1), ("lake", "geom_no_idx", "LINESTRING", "XY", 4326, 0), ("lake", "geom_z", "LINESTRING", "XYZ", 4326, 1), ("lake", "geom_zm", "LINESTRING", "XYZM", 4326, 1), ] else: sqlite_indexes = [] check_indexes( conn, dialect_name, { "postgresql": [ ( "idx_lake_geom", "CREATE INDEX idx_lake_geom ON gis.lake USING gist (geom)", ), ( "idx_lake_geom_m", "CREATE INDEX idx_lake_geom_m ON gis.lake USING gist (geom_m)", ), ( "idx_lake_geom_z", "CREATE INDEX idx_lake_geom_z ON gis.lake USING gist (geom_z)", ), ( "idx_lake_geom_zm", "CREATE INDEX idx_lake_geom_zm ON gis.lake USING gist (geom_zm)", ), ( "lake_pkey", "CREATE UNIQUE INDEX lake_pkey ON gis.lake USING btree (id)", ), ], "sqlite": sqlite_indexes, "geopackage": [("lake", "geom", "gpkg_rtree_index")], }, table_name=t.name, ) @test_only_with_dialects("postgresql", "sqlite") def test_raster_reflection(self, conn, Ocean, setup_tables): skip_pg12_sa1217(conn) skip_postgis1(conn) if SQLA_LT_2: with pytest.warns(SAWarning): t = Table("ocean", MetaData(), autoload_with=conn) else: t = Table("ocean", MetaData(), autoload_with=conn) type_ = t.c.rast.type assert isinstance(type_, Raster) @test_only_with_dialects("sqlite") def test_sqlite_reflection_with_discarded_col(self, conn, Lake, setup_tables, dialect_name): """Test that a discarded geometry column is not properly reflected with SQLite.""" if dialect_name == "geopackage": conn.execute(text("""DELETE FROM "gpkg_geometry_columns" WHERE table_name = 'lake';""")) else: conn.execute(text("""DELETE FROM "geometry_columns" WHERE f_table_name = 'lake';""")) t = Table( "lake", MetaData(), autoload_with=conn, ) # In this case the reflected type is generic with default values assert t.c.geom.type.geometry_type == "GEOMETRY" assert t.c.geom.type.dimension == 2 assert t.c.geom.type.extended assert t.c.geom.type.nullable assert t.c.geom.type.spatial_index assert t.c.geom.type.srid == -1 @pytest.fixture def ocean_view(self, conn, Ocean): conn.execute(text("CREATE VIEW test_view AS SELECT * FROM ocean;")) yield Ocean conn.execute(text("DROP VIEW test_view;")) @test_only_with_dialects("postgresql", "sqlite") def test_view_reflection(self, conn, Ocean, setup_tables, ocean_view): """Test reflection of a view. Note: the reflected `Table` object has spatial indexes attached. It would be nice to detect when a view is reflected to not attach any spatial index. """ skip_pg12_sa1217(conn) skip_postgis1(conn) t = Table("test_view", MetaData(), autoload_with=conn) type_ = t.c.rast.type assert isinstance(type_, Raster) class TestToMetadata(ComparesTables): def test_to_metadata(self, Lake): new_meta = MetaData() new_Lake = Lake.__table__.to_metadata(new_meta) self.assert_tables_equal(Lake.__table__, new_Lake) # Check that the spatial index was not duplicated assert len(new_Lake.indexes) == 1 geoalchemy2-0.15.2/tests/test_functional_geopackage.py000066400000000000000000000203641464355170400230770ustar00rootroot00000000000000import os import pytest from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import Table from sqlalchemy import create_engine from sqlalchemy import text from sqlalchemy.event import listen from geoalchemy2 import Geometry from geoalchemy2 import load_spatialite_gpkg from .schema_fixtures import TransformedGeometry class TestAdmin: def test_create_gpkg(self, tmpdir, _engine_echo, check_spatialite): """Test GeoPackage creation.""" # Create empty GeoPackage tmp_db = tmpdir / "test_spatial_db.gpkg" db_url = f"gpkg:///{tmp_db}" engine = create_engine( db_url, echo=_engine_echo, execution_options={"schema_translate_map": {"gis": None}} ) # Check that the DB is empty raw_conn = engine.connect() assert not raw_conn.execute( text("PRAGMA main.table_info('gpkg_geometry_columns');") ).fetchall() raw_conn.connection.dbapi_connection.enable_load_extension(True) raw_conn.connection.dbapi_connection.load_extension(os.getenv("SPATIALITE_LIBRARY_PATH")) raw_conn.connection.dbapi_connection.enable_load_extension(False) assert not raw_conn.execute( text("PRAGMA main.table_info('gpkg_geometry_columns');") ).fetchall() assert raw_conn.execute(text("SELECT HasGeoPackage();")).scalar() assert not raw_conn.execute(text("SELECT CheckGeoPackageMetaData();")).scalar() # Check that the DB is properly initialized using load_spatialite_gpkg listen(engine, "connect", load_spatialite_gpkg) conn = engine.connect() assert conn.execute(text("SELECT HasGeoPackage();")).scalar() assert conn.execute(text("SELECT CheckGeoPackageMetaData();")).scalar() # Create a new table in this manually created GeoPackage t = Table( "a_table", MetaData(), Column("id", Integer, primary_key=True), Column("geom", Geometry(srid=2154)), ) t.create(conn) t.drop(conn) def test_manual_initialization(self, tmpdir, _engine_echo, check_spatialite): """Test GeoPackage creation.""" # Create empty GeoPackage tmp_db = tmpdir / "test_spatial_db.gpkg" db_url = f"gpkg:///{tmp_db}" engine = create_engine( db_url, echo=_engine_echo, execution_options={"schema_translate_map": {"gis": None}} ) # Check that the DB is empty raw_conn = engine.connect() assert not raw_conn.execute( text("PRAGMA main.table_info('gpkg_geometry_columns');") ).fetchall() raw_conn.connection.dbapi_connection.enable_load_extension(True) raw_conn.connection.dbapi_connection.load_extension(os.environ["SPATIALITE_LIBRARY_PATH"]) raw_conn.connection.dbapi_connection.enable_load_extension(False) assert not raw_conn.execute( text("PRAGMA main.table_info('gpkg_geometry_columns');") ).fetchall() assert raw_conn.execute(text("SELECT HasGeoPackage();")).scalar() assert not raw_conn.execute(text("SELECT CheckGeoPackageMetaData();")).scalar() # Create a new table in this manually created GeoPackage t = Table( "a_table", MetaData(), Column("id", Integer, primary_key=True), ) t.create(raw_conn) # Check that the table was properly created res = raw_conn.execute(text("PRAGMA main.table_info(a_table)")).fetchall() assert res == [ (0, "id", "INTEGER", 1, None, 1), ] t.drop(raw_conn) # Manual initialization raw_conn.execute(text("SELECT gpkgCreateBaseTables();")) # Create again a new table in this manually created GeoPackage t = Table( "a_table", MetaData(), Column("id", Integer, primary_key=True), Column("geom", Geometry(srid=2154)), ) t.create(raw_conn) # Check that the table and the spatial indexes were properly created res = raw_conn.execute(text("PRAGMA main.table_info(a_table)")).fetchall() assert res == [ (0, "id", "INTEGER", 1, None, 1), (1, "geom", "GEOMETRY", 0, None, 0), ] res = raw_conn.execute(text("PRAGMA main.table_info(rtree_a_table_geom)")).fetchall() assert len(res) > 0 t.drop(raw_conn) # Check that the table and the spatial indexes were properly removed res = raw_conn.execute(text("PRAGMA main.table_info(a_table)")).fetchall() assert res == [] res = raw_conn.execute(text("PRAGMA main.table_info(rtree_a_table_geom)")).fetchall() assert res == [] # Check that the DB is properly initialized assert raw_conn.execute(text("SELECT HasGeoPackage();")).scalar() assert raw_conn.execute(text("SELECT CheckGeoPackageMetaData();")).scalar() def test_multi_geom_cols_fail(self, conn): t = Table( "a_table", MetaData(), Column("id", Integer, primary_key=True), Column("geom", Geometry()), Column("geom_2", Geometry()), ) with pytest.raises( ValueError, match=r"Only one geometry column is allowed for a table stored in a GeoPackage\.", ): t.create(conn) def test_add_srid(self, conn): t = Table( "a_table", MetaData(), Column("id", Integer, primary_key=True), Column("geom", Geometry(srid=3857)), ) check_srid_query = text("""SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_id = 3857;""") assert not conn.execute(check_srid_query).fetchall() t.create(conn) assert conn.execute(check_srid_query).fetchall() class TestIndex: def test_index_gpkg(self, conn, Lake, setup_tables): assert ( conn.execute( text( """SELECT COUNT(*) FROM gpkg_extensions WHERE table_name = 'lake' AND column_name = 'geom' AND extension_name = 'gpkg_rtree_index';""" ) ).scalar() == 1 ) def test_type_decorator_index_gpkg(self, conn, base, metadata): class LocalPoint(base): __tablename__ = "local_point" id = Column(Integer, primary_key=True) geom = Column(TransformedGeometry(db_srid=2154, app_srid=4326, geometry_type="POINT")) metadata.drop_all(conn, checkfirst=True) metadata.create_all(conn) assert ( conn.execute( text( """SELECT COUNT(*) FROM gpkg_extensions WHERE table_name = 'local_point' AND column_name = 'geom' AND extension_name = 'gpkg_rtree_index';""" ) ).scalar() == 1 ) class TestMiscellaneous: def test_load_spatialite_gpkg(self, tmpdir, _engine_echo, check_spatialite): # Create empty DB tmp_db = tmpdir / "test_spatial_db.sqlite" db_url = f"sqlite:///{tmp_db}" engine = create_engine( db_url, echo=_engine_echo, execution_options={"schema_translate_map": {"gis": None}} ) conn = engine.connect() assert not conn.execute(text("PRAGMA main.table_info('gpkg_geometry_columns')")).fetchall() assert not conn.execute(text("PRAGMA main.table_info('gpkg_spatial_ref_sys')")).fetchall() load_spatialite_gpkg(conn.connection.dbapi_connection, None) assert conn.execute(text("SELECT CheckGeoPackageMetaData();")).scalar() == 1 assert conn.execute(text("PRAGMA main.table_info('gpkg_geometry_columns')")).fetchall() assert conn.execute(text("PRAGMA main.table_info('gpkg_spatial_ref_sys')")).fetchall() # Check that spatial_ref_sys table was properly populated nb_srid = conn.execute(text("SELECT COUNT(*) FROM gpkg_spatial_ref_sys;")).scalar() assert nb_srid == 3 def test_load_spatialite_no_env_variable(self, monkeypatch, conn): monkeypatch.delenv("SPATIALITE_LIBRARY_PATH") with pytest.raises(RuntimeError): load_spatialite_gpkg(conn.connection.dbapi_connection, None) geoalchemy2-0.15.2/tests/test_functional_mysql.py000066400000000000000000000343611464355170400221600ustar00rootroot00000000000000from json import loads import pytest from pkg_resources import parse_version from shapely.geometry import LineString from sqlalchemy import MetaData from sqlalchemy import Table from sqlalchemy import __version__ as SA_VERSION from sqlalchemy import bindparam from sqlalchemy import create_engine from sqlalchemy import text from sqlalchemy.exc import OperationalError from sqlalchemy.exc import StatementError from sqlalchemy.sql import func from sqlalchemy.sql import select from geoalchemy2 import Geometry from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement from geoalchemy2.shape import from_shape from geoalchemy2.shape import to_shape from . import test_only_with_dialects class TestAdmin: def test_create_drop_tables( self, conn, metadata, Lake, Poi, Summit, LocalPoint, IndexTestWithSchema, IndexTestWithoutSchema, ): metadata.drop_all(conn, checkfirst=True) metadata.create_all(conn) metadata.drop_all(conn, checkfirst=True) class TestInsertionCore: @pytest.mark.parametrize("use_executemany", [True, False]) def test_insert(self, conn, Lake, setup_tables, use_executemany): # Issue several inserts using DBAPI's executemany() method or single inserts. This tests # the Geometry type's bind_processor and bind_expression functions. elements = [ {"geom": "SRID=4326;LINESTRING(0 0,1 1)"}, {"geom": "LINESTRING(0 0,1 1)"}, {"geom": WKTElement("LINESTRING(0 0,2 2)")}, {"geom": from_shape(LineString([[0, 0], [3, 3]]), srid=4326)}, ] if use_executemany: conn.execute(Lake.__table__.insert(), elements) else: for element in elements: query = Lake.__table__.insert().values(**element) conn.execute(query) results = conn.execute(Lake.__table__.select().order_by("id")) rows = results.fetchall() row = rows[0] assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert wkt == "LINESTRING(0 0,1 1)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 row = rows[1] assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert wkt == "LINESTRING(0 0,1 1)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 row = rows[2] assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert wkt == "LINESTRING(0 0,2 2)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 row = rows[3] assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert wkt == "LINESTRING(0 0,3 3)" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 # Check that selected elements can be inserted again for row in rows: conn.execute(Lake.__table__.insert().values(geom=row[1])) conn.execute( Lake.__table__.insert(), [{"geom": row[1]} for row in rows], ) class TestInsertionORM: def test_WKT(self, session, Lake, setup_tables): lake = Lake("LINESTRING(0 0,1 1)") session.add(lake) session.flush() def test_WKTElement(self, session, Lake, setup_tables): lake = Lake(WKTElement("LINESTRING(0 0,1 1)", srid=4326)) session.add(lake) session.flush() session.expire(lake) assert isinstance(lake.geom, WKBElement) assert ( str(lake.geom) == "0102000000020000000000000000000000000000000000000000000" "0000000f03f000000000000f03f" ) wkt = session.execute(lake.geom.ST_AsText()).scalar() assert wkt == "LINESTRING(0 0,1 1)" srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 def test_WKBElement(self, session, Lake, setup_tables): shape = LineString([[0, 0], [1, 1]]) lake = Lake(from_shape(shape, srid=4326)) session.add(lake) session.flush() session.expire(lake) assert isinstance(lake.geom, WKBElement) assert ( str(lake.geom) == "0102000000020000000000000000000000000000000000000000000" "0000000f03f000000000000f03f" ) wkt = session.execute(lake.geom.ST_AsText()).scalar() assert wkt == "LINESTRING(0 0,1 1)" srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 def test_raise_wrong_srid_str(self, session, Lake, setup_tables): lake = Lake("SRID=2154;LINESTRING(0 0,1 1)") session.add(lake) with pytest.raises(StatementError): session.flush() def test_raise_wrong_srid_WKTElement(self, session, Lake, setup_tables): lake = Lake(WKTElement("LINESTRING(0 0,1 1)", srid=2154)) session.add(lake) with pytest.raises(StatementError): session.flush() class TestShapely: def test_to_shape(self, conn, session, Lake, setup_tables): element = WKTElement("LINESTRING(0 0,1 1)", srid=4326) lake = Lake(geom=element) session.add(lake) session.flush() session.expire(lake) lake = session.query(Lake).one() assert isinstance(lake.geom, WKBElement) assert isinstance(lake.geom.data, bytes) assert lake.geom.srid == 4326 s = to_shape(lake.geom) assert isinstance(s, LineString) assert s.wkt == "LINESTRING (0 0, 1 1)" conn.execute(Lake.__table__.insert().values(geom="LINESTRING(0 0,1 1)")) conn.execute( Lake.__table__.insert(), [ {"geom": "SRID=4326;LINESTRING(0 0,1 1)"}, {"geom": WKTElement("LINESTRING(0 0,2 2)")}, {"geom": from_shape(LineString([[0, 0], [3, 3]]), srid=4326)}, ], ) lake = Lake(lake.geom) session.add(lake) session.flush() session.expire(lake) assert isinstance(lake.geom, WKBElement) assert isinstance(lake.geom.data, bytes) assert lake.geom.srid == 4326 class TestCallFunction: @pytest.fixture def setup_one_lake(self, session, Lake, setup_tables): lake = Lake(WKTElement("LINESTRING(0 0,1 1)", srid=4326)) session.add(lake) session.flush() session.expire(lake) return lake.id def test_ST_GeometryType(self, session, Lake, setup_one_lake): lake_id = setup_one_lake s = select(func.ST_GeometryType(Lake.__table__.c.geom)) r1 = session.execute(s).scalar() assert r1 == "LINESTRING" lake = session.query(Lake).get(lake_id) r2 = session.execute(lake.geom.ST_GeometryType()).scalar() assert r2 == "LINESTRING" r3 = session.query(Lake.geom.ST_GeometryType()).scalar() assert r3 == "LINESTRING" r4 = session.query(Lake).filter(Lake.geom.ST_GeometryType() == "LINESTRING").one() assert isinstance(r4, Lake) assert r4.id == lake_id def test_ST_Transform(self, session, Lake, setup_one_lake): lake_id = setup_one_lake s = select(func.ST_Transform(Lake.__table__.c.geom, 2154)) r1 = session.execute(s).scalar() assert isinstance(r1, WKBElement) lake = session.query(Lake).get(lake_id) r2 = session.execute(lake.geom.ST_Transform(2154)).scalar() assert isinstance(r2, WKBElement) r3 = session.query(Lake.geom.ST_Transform(2154)).scalar() assert isinstance(r3, WKBElement) assert r1.data == r2.data == r3.data r4 = ( session.query(Lake) .filter( func.ST_Distance( WKTElement("POINT(253531 908605)", srid=2154), Lake.geom.ST_Transform(2154), ) <= 1 ) .one() ) assert isinstance(r4, Lake) assert r4.id == lake_id def test_ST_GeoJSON(self, session, Lake, setup_one_lake): lake_id = setup_one_lake def _test(r): r = loads(r) assert r["type"] == "LineString" assert r["coordinates"] == [[0, 0], [1, 1]] s = select(func.ST_AsGeoJSON(Lake.__table__.c.geom)) r = session.execute(s).scalar() _test(r) lake = session.query(Lake).get(lake_id) r = session.execute(lake.geom.ST_AsGeoJSON()).scalar() _test(r) r = session.query(Lake.geom.ST_AsGeoJSON()).scalar() _test(r) @pytest.mark.skipif( True, reason="MySQL does not support the feature version of AsGeoJson() yet" ) def test_ST_GeoJSON_feature(self, session, Lake, setup_tables): ss3 = select(Lake, bindparam("dummy_val", 10).label("dummy_attr")).alias() s3 = select(func.ST_AsGeoJSON(ss3, "geom")) r3 = session.execute(s3).scalar() assert loads(r3) == { "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[0, 0], [1, 1]]}, "properties": {"dummy_attr": 10, "id": 1}, } @pytest.mark.skipif( parse_version(SA_VERSION) < parse_version("1.3.4"), reason="Case-insensitivity is only available for sqlalchemy>=1.3.4", ) def test_comparator_case_insensitivity(self, session, Lake, setup_one_lake): lake_id = setup_one_lake s = select(func.ST_Transform(Lake.__table__.c.geom, 2154)) r1 = session.execute(s).scalar() assert isinstance(r1, WKBElement) lake = session.query(Lake).get(lake_id) r2 = session.execute(lake.geom.ST_Transform(2154)).scalar() assert isinstance(r2, WKBElement) r3 = session.execute(lake.geom.st_transform(2154)).scalar() assert isinstance(r3, WKBElement) r4 = session.execute(lake.geom.St_TrAnSfOrM(2154)).scalar() assert isinstance(r4, WKBElement) r5 = session.query(Lake.geom.ST_Transform(2154)).scalar() assert isinstance(r5, WKBElement) r6 = session.query(Lake.geom.st_transform(2154)).scalar() assert isinstance(r6, WKBElement) r7 = session.query(Lake.geom.St_TrAnSfOrM(2154)).scalar() assert isinstance(r7, WKBElement) assert r1.data == r2.data == r3.data == r4.data == r5.data == r6.data == r7.data class TestNullable: @test_only_with_dialects("mysql") def test_insert(self, conn, Lake, setup_tables): # Insert geometries conn.execute( Lake.__table__.insert(), [ {"geom": "SRID=4326;LINESTRING(0 0,1 1)"}, {"geom": WKTElement("LINESTRING(0 0,2 2)", srid=4326)}, {"geom": from_shape(LineString([[0, 0], [3, 3]]), srid=4326)}, ], ) # Fail when trying to insert null geometry with pytest.raises(OperationalError): conn.execute(Lake.__table__.insert(), [{"geom": None}]) class TestReflection: @pytest.fixture def create_temp_db(self, request, conn, reflection_tables_metadata): """Temporary database, that is dropped on fixture teardown. Used to make sure reflection methods always uses the correct schema. """ temp_db_name = "geoalchemy_test_reflection" engine = create_engine( f"mysql://gis:gis@localhost/{temp_db_name}", echo=request.config.getoption("--engine-echo"), ) conn.execute(text(f"CREATE DATABASE IF NOT EXISTS {temp_db_name};")) with engine.connect() as connection: with connection.begin(): reflection_tables_metadata.drop_all(connection, checkfirst=True) reflection_tables_metadata.create_all(connection) yield connection conn.execute(text(f"DROP DATABASE IF EXISTS {temp_db_name};")) @pytest.fixture def setup_reflection_tables(self, reflection_tables_metadata, conn): reflection_tables_metadata.drop_all(conn, checkfirst=True) reflection_tables_metadata.create_all(conn) def test_reflection_mysql(self, conn, setup_reflection_tables, create_temp_db): t = Table("lake", MetaData(), autoload_with=conn) type_ = t.c.geom.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRING" assert type_.srid == 4326 assert type_.dimension == 2 type_ = t.c.geom_no_idx.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRING" assert type_.srid == 4326 assert type_.dimension == 2 # Drop the table t.drop(bind=conn) # Query to check the tables query_tables = text( """SELECT DISTINCT TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'gis' ORDER BY TABLE_NAME;""" ) # Query to check the indices query_indexes = text( """SELECT A.TABLE_NAME, A.COLUMN_NAME, A.DATA_TYPE, B.INDEX_TYPE FROM INFORMATION_SCHEMA.COLUMNS AS A LEFT JOIN INFORMATION_SCHEMA.STATISTICS AS B ON A.TABLE_NAME = B.TABLE_NAME AND A.COLUMN_NAME = B.COLUMN_NAME AND A.TABLE_SCHEMA = B.TABLE_SCHEMA WHERE A.TABLE_SCHEMA = 'gis' AND A.TABLE_NAME = 'lake' ORDER BY TABLE_NAME, COLUMN_NAME;""" ) # Check the indices geom_cols = conn.execute(query_indexes).fetchall() assert geom_cols == [] # Check the tables all_tables = [i[0] for i in conn.execute(query_tables).fetchall()] assert "lake" not in all_tables # Recreate the table to check that the reflected properties are correct t.create(bind=conn) # Check the actual properties geom_cols = conn.execute(query_indexes).fetchall() assert geom_cols == [ ("lake", "geom", "linestring", "SPATIAL"), ("lake", "geom_no_idx", "linestring", None), ("lake", "id", "int", "BTREE"), ] all_tables = [i[0] for i in conn.execute(query_tables).fetchall()] assert "lake" in all_tables geoalchemy2-0.15.2/tests/test_functional_postgresql.py000066400000000000000000000606211464355170400232140ustar00rootroot00000000000000import re from json import loads import pytest try: from psycopg2cffi import compat except ImportError: pass else: compat.register() del compat from pkg_resources import parse_version from shapely.geometry import Point from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import String from sqlalchemy import __version__ as SA_VERSION from sqlalchemy import bindparam from sqlalchemy import text from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2 from sqlalchemy.exc import InternalError from sqlalchemy.orm import declarative_base from sqlalchemy.sql import func from sqlalchemy.sql.expression import type_coerce from geoalchemy2 import Geography from geoalchemy2 import Geometry from geoalchemy2 import Raster from geoalchemy2.elements import RasterElement from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement from geoalchemy2.exc import ArgumentError from geoalchemy2.shape import from_shape from . import select from . import skip_pg12_sa1217 from . import skip_postgis1 from . import skip_postgis2 SQLA_LT_2 = parse_version(SA_VERSION) <= parse_version("1.4") if SQLA_LT_2: from sqlalchemy.engine.reflection import Inspector get_inspector = Inspector.from_engine else: from sqlalchemy import inspect as get_inspector # type: ignore class TestIndex: def test_index_with_schema(self, conn, IndexTestWithSchema, setup_tables): inspector = get_inspector(conn) indices = inspector.get_indexes(IndexTestWithSchema.__tablename__, schema="gis") assert len(indices) == 2 assert not indices[0].get("unique") assert indices[0].get("column_names")[0] in ("geom1", "geom2") assert not indices[1].get("unique") assert indices[1].get("column_names")[0] in ("geom1", "geom2") def test_n_d_index(self, conn, IndexTestWithNDIndex, setup_tables): sql = text( """SELECT tablename, indexname, indexdef FROM pg_indexes WHERE tablename = 'index_test_with_nd_index' ORDER BY tablename, indexname""" ) r = conn.execute(sql) results = r.fetchall() for index in results: if "geom1" in index[1]: nd_index = index[2] index_type = nd_index.split("USING ", 1)[1] assert index_type == "gist (geom1 gist_geometry_ops_nd)" inspector = get_inspector(conn) indices = inspector.get_indexes(IndexTestWithNDIndex.__tablename__) assert len(indices) == 1 assert not indices[0].get("unique") assert indices[0].get("column_names")[0] in ("geom1") def test_n_d_index_argument_error(self): BaseArgTest = declarative_base(metadata=MetaData()) with pytest.raises(ArgumentError) as excinfo: class NDIndexArgErrorSchema(BaseArgTest): __tablename__ = "nd_index_error_arg" __table_args__ = {"schema": "gis"} id = Column(Integer, primary_key=True) geom1 = Column( Geometry( geometry_type="POINTZ", dimension=3, spatial_index=False, use_N_D_index=True, ) ) assert "Arg Error(use_N_D_index): spatial_index must be True" == excinfo.value.args[0] def test_index_without_schema(self, conn, IndexTestWithoutSchema, setup_tables): inspector = get_inspector(conn) indices = inspector.get_indexes(IndexTestWithoutSchema.__tablename__) assert len(indices) == 2 assert not indices[0].get("unique") assert indices[0].get("column_names")[0] in ("geom1", "geom2") assert not indices[1].get("unique") assert indices[1].get("column_names")[0] in ("geom1", "geom2") def test_type_decorator_index(self, conn, PointZ, setup_tables): inspector = get_inspector(conn) indices = inspector.get_indexes(PointZ.__tablename__) assert len(indices) == 1 assert not indices[0].get("unique") assert indices[0].get("column_names") == ["three_d_geom"] def test_all_indexes(self, conn): BaseArgTest = declarative_base(metadata=MetaData()) class TableWithIndexes(BaseArgTest): __tablename__ = "table_with_indexes" __table_args__ = {"schema": "gis"} id = Column(Integer, primary_key=True) # Test indexes on Geometry columns. geom_not_managed_no_index = Column( Geometry( geometry_type="POINT", spatial_index=False, ) ) geom_not_managed_index = Column( Geometry( geometry_type="POINT", spatial_index=True, ) ) geom_managed_no_index = Column( Geometry( geometry_type="POINT", spatial_index=False, ) ) geom_managed_index = Column( Geometry( geometry_type="POINT", spatial_index=True, ) ) # Test indexes on Geometry columns with ND index. geom_not_managed_nd_index = Column( Geometry( geometry_type="POINTZ", dimension=3, spatial_index=True, use_N_D_index=True, ) ) geom_managed_nd_index = Column( Geometry( geometry_type="POINTZ", dimension=3, spatial_index=True, use_N_D_index=True, ) ) # Test indexes on Geography columns. geog_not_managed_no_index = Column( Geography( geometry_type="POINT", spatial_index=False, ) ) geog_not_managed_index = Column( Geography( geometry_type="POINT", spatial_index=True, ) ) geog_managed_no_index = Column( Geography( geometry_type="POINT", spatial_index=False, ) ) geog_managed_index = Column( Geography( geometry_type="POINT", spatial_index=True, ) ) # Test indexes on Raster columns. # Note: managed Raster columns are not tested because Raster columns can't be managed. rast_not_managed_no_index = Column( Raster( spatial_index=False, ) ) rast_not_managed_index = Column( Raster( spatial_index=True, ) ) TableWithIndexes.__table__.create(conn) index_query = text( """SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = 'gis' AND tablename = 'table_with_indexes';""" ) indices = sorted(conn.execute(index_query).fetchall()) expected_indices = [ ( "idx_table_with_indexes_geog_managed_index", """CREATE INDEX idx_table_with_indexes_geog_managed_index ON gis.table_with_indexes USING gist (geog_managed_index)""", ), ( "idx_table_with_indexes_geog_not_managed_index", """CREATE INDEX idx_table_with_indexes_geog_not_managed_index ON gis.table_with_indexes USING gist (geog_not_managed_index)""", ), ( "idx_table_with_indexes_geom_managed_index", """CREATE INDEX idx_table_with_indexes_geom_managed_index ON gis.table_with_indexes USING gist (geom_managed_index)""", ), ( "idx_table_with_indexes_geom_managed_nd_index", """CREATE INDEX idx_table_with_indexes_geom_managed_nd_index ON gis.table_with_indexes USING gist (geom_managed_nd_index gist_geometry_ops_nd)""", ), ( "idx_table_with_indexes_geom_not_managed_index", """CREATE INDEX idx_table_with_indexes_geom_not_managed_index ON gis.table_with_indexes USING gist (geom_not_managed_index)""", ), ( "idx_table_with_indexes_geom_not_managed_nd_index", """CREATE INDEX idx_table_with_indexes_geom_not_managed_nd_index ON gis.table_with_indexes USING gist (geom_not_managed_nd_index gist_geometry_ops_nd)""", ), ( "idx_table_with_indexes_rast_not_managed_index", """CREATE INDEX idx_table_with_indexes_rast_not_managed_index ON gis.table_with_indexes USING gist (st_convexhull(rast_not_managed_index))""", ), ( "table_with_indexes_pkey", """CREATE UNIQUE INDEX table_with_indexes_pkey ON gis.table_with_indexes USING btree (id)""", ), ] assert len(indices) == 8 for idx, expected_idx in zip(indices, expected_indices): assert idx[0] == expected_idx[0] assert idx[1] == re.sub("\n *", " ", expected_idx[1]) class TestInsertionCore: def test_insert_geog_poi(self, conn, Poi, setup_tables): conn.execute( Poi.__table__.insert(), [ {"geog": "SRID=4326;POINT(1 1)"}, {"geog": WKTElement("POINT(1 1)", srid=4326)}, {"geog": WKTElement("SRID=4326;POINT(1 1)", extended=True)}, {"geog": from_shape(Point(1, 1), srid=4326)}, ], ) results = conn.execute(Poi.__table__.select()) rows = results.fetchall() for row in rows: assert isinstance(row[2], WKBElement) wkt = conn.execute(row[2].ST_AsText()).scalar() assert wkt == "POINT(1 1)" srid = conn.execute(row[2].ST_SRID()).scalar() assert srid == 4326 assert row[2] == from_shape(Point(1, 1), srid=4326) class TestInsertionORM: def test_Raster(self, session, Ocean, setup_tables): skip_postgis1(session) polygon = WKTElement("POLYGON((0 0,1 1,0 1,0 0))", srid=4326) o = Ocean(polygon.ST_AsRaster(5, 5)) session.add(o) session.flush() session.expire(o) assert isinstance(o.rast, RasterElement) height = session.execute(o.rast.ST_Height()).scalar() assert height == 5 width = session.execute(o.rast.ST_Width()).scalar() assert width == 5 # The top left corner is covered by the polygon top_left_point = WKTElement("Point(0 1)", srid=4326) top_left = session.execute(o.rast.ST_Value(top_left_point)).scalar() assert top_left == 1 # The bottom right corner has NODATA bottom_right_point = WKTElement("Point(1 0)", srid=4326) bottom_right = session.execute(o.rast.ST_Value(bottom_right_point)).scalar() assert bottom_right is None class TestUpdateORM: def test_Raster(self, session, Ocean, setup_tables): skip_postgis1(session) polygon = WKTElement("POLYGON((0 0,1 1,0 1,0 0))", srid=4326) o = Ocean(polygon.ST_AsRaster(5, 5)) session.add(o) session.flush() session.expire(o) assert isinstance(o.rast, RasterElement) rast_data = ( "01000001009A9999999999C93F9A9999999999C9BF0000000000000000000000000000F03" "F00000000000000000000000000000000E610000005000500440001010101010101010100" "010101000001010000000100000000" ) assert o.rast.data == rast_data assert session.execute( select([Ocean.rast.ST_Height(), Ocean.rast.ST_Width()]) ).fetchall() == [(5, 5)] # Set rast to None o.rast = None # Insert in DB session.flush() session.expire(o) # Check what was updated in DB assert o.rast is None cols = [Ocean.id, Ocean.rast] assert session.execute(select(cols)).fetchall() == [(1, None)] # Reset rast to initial value o.rast = RasterElement(rast_data) # Insert in DB session.flush() session.expire(o) # Check what was updated in DB assert o.rast.data == rast_data assert session.execute( select([Ocean.rast.ST_Height(), Ocean.rast.ST_Width()]) ).fetchall() == [(5, 5)] class TestTypMod: def test_SummitConstraints(self, conn, Summit, setup_tables): """Make sure the geometry column of table Summit is created with `use_typmod=False` (explicit constraints are created). """ skip_pg12_sa1217(conn) inspector = get_inspector(conn) constraints = inspector.get_check_constraints(Summit.__tablename__, schema="gis") assert len(constraints) == 3 constraint_names = {c["name"] for c in constraints} assert "enforce_srid_geom" in constraint_names assert "enforce_dims_geom" in constraint_names assert "enforce_geotype_geom" in constraint_names class TestCallFunction: @pytest.fixture def setup_one_lake(self, session, Lake, setup_tables): lake = Lake(WKTElement("LINESTRING(0 0,1 1)", srid=4326)) session.add(lake) session.flush() session.expire(lake) return lake.id @pytest.fixture def setup_one_poi(self, session, Poi, setup_tables): p = Poi("POINT(5 45)") session.add(p) session.flush() session.expire(p) return p.id def test_ST_Dump(self, session, Lake, setup_one_lake): lake_id = setup_one_lake lake = session.query(Lake).get(lake_id) assert isinstance(lake.geom, WKBElement) s = select([func.ST_Dump(Lake.__table__.c.geom)]) r1 = session.execute(s).scalar() assert isinstance(r1, str) s = select([func.ST_Dump(Lake.__table__.c.geom).path]) r2 = session.execute(s).scalar() assert isinstance(r2, list) assert r2 == [] s = select([func.ST_Dump(Lake.__table__.c.geom).geom]) r2 = session.execute(s).scalar() assert isinstance(r2, WKBElement) assert r2.data == lake.geom.data r3 = session.execute(func.ST_Dump(lake.geom).geom).scalar() assert isinstance(r3, WKBElement) assert r3.data == lake.geom.data r4 = session.query(func.ST_Dump(Lake.geom).geom).scalar() assert isinstance(r4, WKBElement) assert r4.data == lake.geom.data r5 = session.query(Lake.geom.ST_Dump().geom).scalar() assert isinstance(r5, WKBElement) assert r5.data == lake.geom.data assert r2.data == r3.data == r4.data == r5.data def test_ST_DumpPoints(self, session, Lake, setup_one_lake): lake_id = setup_one_lake lake = session.query(Lake).get(lake_id) assert isinstance(lake.geom, WKBElement) dump = lake.geom.ST_DumpPoints() q = session.query(dump.path.label("path"), dump.geom.label("geom")).all() assert len(q) == 2 p1 = q[0] assert isinstance(p1.path, list) assert p1.path == [1] assert isinstance(p1.geom, WKBElement) p1 = session.execute(func.ST_AsText(p1.geom)).scalar() assert p1 == "POINT(0 0)" p2 = q[1] assert isinstance(p2.path, list) assert p2.path == [2] assert isinstance(p2.geom, WKBElement) p2 = session.execute(func.ST_AsText(p2.geom)).scalar() assert p2 == "POINT(1 1)" def test_ST_Buffer_Mixed_SRID(self, session, Lake, setup_one_lake): with pytest.raises(InternalError): session.query(Lake).filter(func.ST_Within("POINT(0 0)", Lake.geom.ST_Buffer(2))).one() def test_ST_Distance_type_coerce(self, session, Poi, setup_one_poi): poi_id = setup_one_poi poi = ( session.query(Poi) .filter(Poi.geog.ST_Distance(type_coerce("POINT(5 45)", Geography)) < 1000) .one() ) assert poi.id == poi_id def test_ST_AsGeoJson_feature(self, session, Lake, setup_one_lake): skip_postgis1(session) skip_postgis2(session) # Test feature s2 = select([func.ST_AsGeoJSON(Lake, "geom")]) r2 = session.execute(s2).scalar() assert loads(r2) == { "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[0, 0], [1, 1]]}, "properties": {"id": 1}, } # Test feature with subquery ss3 = select([Lake, bindparam("dummy_val", 10).label("dummy_attr")]).alias() s3 = select([func.ST_AsGeoJSON(ss3, "geom")]) r3 = session.execute(s3).scalar() assert loads(r3) == { "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[0, 0], [1, 1]]}, "properties": {"dummy_attr": 10, "id": 1}, } @pytest.mark.parametrize( "compared_element,expected_assert", [ pytest.param("LINESTRING(0 1, 1 0)", True, id="intersecting raw string WKT"), pytest.param("LINESTRING(99 99, 999 999)", False, id="not intersecting raw string WKT"), pytest.param(WKTElement("LINESTRING(0 1, 1 0)"), True, id="intersecting WKTElement"), pytest.param( WKTElement("LINESTRING(99 99, 999 999)"), False, id="not intersecting WKTElement" ), pytest.param( WKTElement("SRID=2154;LINESTRING(0 1, 1 0)"), True, id="intersecting extended WKTElement", ), pytest.param( WKTElement("SRID=2154;LINESTRING(99 99, 999 999)"), False, id="not intersecting extended WKTElement", ), pytest.param( WKBElement( "0102000000020000000000000000000000000000000000F03F000000000000F03F00000000000" "00000" ), True, id="intersecting WKBElement", ), pytest.param( WKBElement( "0102000000020000000000000000C058400000000000C058400000000000388F4000000000003" "88F40" ), False, id="not intersecting WKBElement", ), pytest.param( WKBElement( "01020000206A080000020000000000000000000000000000000000F03F000000000000F03F000" "0000000000000" ), True, id="intersecting extended WKBElement", ), pytest.param( WKBElement( "01020000206A080000020000000000000000C058400000000000C058400000000000388F40000" "0000000388F40" ), False, id="not intersecting extended WKBElement", ), ], ) def test_comparator(self, session, Lake, setup_one_lake, compared_element, expected_assert): query = Lake.__table__.select().where(Lake.__table__.c.geom.intersects(compared_element)) res = session.execute(query).fetchall() assert bool(res) == expected_assert class TestShapely: pass class TestSTAsGeoJson: InternalBase = declarative_base() class TblWSpacesAndDots(InternalBase): # type: ignore """ Dummy class to test names with dots and spaces. No metadata is attached so the dialect is default SQL, not postgresql. """ __tablename__ = "this is.an AWFUL.name" __table_args__ = {"schema": "another AWFUL.name for.schema"} id = Column(Integer, primary_key=True) geom = Column(String) @staticmethod def _assert_stmt(stmt, expected): strstmt = str(stmt.compile(dialect=PGDialect_psycopg2())) strstmt = strstmt.replace("\n", "") assert strstmt == expected def test_column(self, Lake): stmt = select([func.ST_AsGeoJSON(Lake.__table__.c.geom)]) self._assert_stmt( stmt, 'SELECT ST_AsGeoJSON(gis.lake.geom) AS "ST_AsGeoJSON_1" FROM gis.lake' ) def test_table_col(self, Lake): stmt = select([func.ST_AsGeoJSON(Lake, "geom")]) self._assert_stmt( stmt, "SELECT ST_AsGeoJSON(lake, %(ST_AsGeoJSON_2)s) AS " '"ST_AsGeoJSON_1" FROM gis.lake', ) def test_subquery(self, Lake): sq = select([Lake, bindparam("dummy_val", 10).label("dummy_attr")]).alias() stmt = select([func.ST_AsGeoJSON(sq, "geom")]) self._assert_stmt( stmt, 'SELECT ST_AsGeoJSON(anon_1, %(ST_AsGeoJSON_2)s) AS "ST_AsGeoJSON_1" ' "FROM (SELECT gis.lake.id AS id, gis.lake.geom AS geom, %(dummy_val)s AS " "dummy_attr FROM gis.lake) AS anon_1", ) def test_quotes(self): stmt = select([func.ST_AsGeoJSON(TestSTAsGeoJson.TblWSpacesAndDots, "geom")]) self._assert_stmt( stmt, 'SELECT ST_AsGeoJSON("this is.an AWFUL.name", %(ST_AsGeoJSON_2)s) ' 'AS "ST_AsGeoJSON_1" FROM "another AWFUL.name for.schema".' '"this is.an AWFUL.name"', ) def test_quotes_and_param(self): stmt = select([func.ST_AsGeoJSON(TestSTAsGeoJson.TblWSpacesAndDots, "geom", 3)]) self._assert_stmt( stmt, 'SELECT ST_AsGeoJSON("this is.an AWFUL.name", ' "%(ST_AsGeoJSON_2)s, %(ST_AsGeoJSON_3)s) " 'AS "ST_AsGeoJSON_1" FROM "another AWFUL.name for.schema".' '"this is.an AWFUL.name"', ) def test_nested_funcs(self): stmt = select([func.ST_AsGeoJSON(func.ST_MakeValid(func.ST_MakePoint(1, 2)))]) self._assert_stmt( stmt, "SELECT " "ST_AsGeoJSON(ST_MakeValid(" "ST_MakePoint(%(ST_MakePoint_1)s, %(ST_MakePoint_2)s)" ')) AS "ST_AsGeoJSON_1"', ) def test_unknown_func(self): stmt = select([func.ST_AsGeoJSON(func.ST_UnknownFunction(func.ST_MakePoint(1, 2)))]) self._assert_stmt( stmt, "SELECT " "ST_AsGeoJSON(ST_UnknownFunction(" "ST_MakePoint(%(ST_MakePoint_1)s, %(ST_MakePoint_2)s)" ')) AS "ST_AsGeoJSON_1"', ) class TestSTSummaryStatsAgg: def test_st_summary_stats_agg(self, session, Ocean, setup_tables): # Create a new raster polygon = WKTElement("POLYGON((0 0,1 1,0 1,0 0))", srid=4326) o = Ocean(polygon.ST_AsRaster(5, 6)) session.add(o) session.flush() # Define the query to compute stats stats_agg = select( [func.ST_SummaryStatsAgg(Ocean.__table__.c.rast, 1, True, 1).label("stats")] ) stats_agg_alias = stats_agg.alias("stats_agg") # Use these stats query = select( [ stats_agg_alias.c.stats.count.label("count"), stats_agg_alias.c.stats.sum.label("sum"), stats_agg_alias.c.stats.stddev.label("stddev"), stats_agg_alias.c.stats.min.label("min"), stats_agg_alias.c.stats.max.label("max"), ] ) # Check the query assert str(query.compile(dialect=PGDialect_psycopg2())) == ( "SELECT " "(stats_agg.stats).count AS count, " "(stats_agg.stats).sum AS sum, " "(stats_agg.stats).stddev AS stddev, " "(stats_agg.stats).min AS min, " "(stats_agg.stats).max AS max \n" "FROM (" "SELECT " "ST_SummaryStatsAgg(" "ocean.rast, " "%(ST_SummaryStatsAgg_1)s, %(ST_SummaryStatsAgg_2)s, %(ST_SummaryStatsAgg_3)s" ") AS stats \n" "FROM ocean) AS stats_agg" ) # Execute the query res = session.execute(query).fetchall() # Check the result assert res == [(15, 15.0, 0.0, 1.0, 1.0)] geoalchemy2-0.15.2/tests/test_functional_sqlite.py000066400000000000000000001021661464355170400223130ustar00rootroot00000000000000import re import pytest from shapely.geometry import GeometryCollection from shapely.geometry import LineString from shapely.geometry import Point from sqlalchemy import CheckConstraint from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import create_engine from sqlalchemy import text from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import OperationalError from sqlalchemy.sql import func from geoalchemy2 import Geometry from geoalchemy2 import load_spatialite from geoalchemy2.admin.dialects.geopackage import create_spatial_ref_sys_view from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement from geoalchemy2.shape import from_shape from geoalchemy2.shape import to_shape from . import format_wkt from . import select from . import skip_case_insensitivity from . import skip_pypy from . import test_only_with_dialects from .schema_fixtures import TransformedGeometry class TestAdmin: def test_create_drop_tables( self, conn, metadata, Lake, Summit, Ocean, PointZ, ): metadata.drop_all(conn, checkfirst=True) metadata.create_all(conn) metadata.drop_all(conn, checkfirst=True) @pytest.mark.parametrize("nullable", [True, False]) @pytest.mark.parametrize("level", ["col", "type"]) def test_nullable(self, conn, metadata, setup_tables, dialect_name, nullable, level): # Define the table col = Column( "geom", Geometry( geometry_type=None, srid=4326, spatial_index=False, nullable=nullable if level == "type" else True, ), nullable=nullable if level == "col" else True, ) t = Table( "nullable_geom_type", metadata, Column("id", Integer, primary_key=True), col, ) # Create the table t.create(bind=conn) elements = [] if nullable: elements.append({"geom": None}) else: elements.append({"geom": "SRID=4326;LINESTRING(0 0,1 1)"}) conn.execute(t.insert(), elements) if not nullable: with pytest.raises((IntegrityError, OperationalError)): with conn.begin_nested(): conn.execute(t.insert(), [{"geom": None}]) conn.execute(t.insert(), [{"geom": "SRID=4326;LINESTRING(0 0,1 1)"}]) results = conn.execute(t.select()) rows = results.fetchall() assert len(rows) == 2 # Drop the table t.drop(bind=conn) def test_no_geom_type(self, conn): with pytest.warns(UserWarning, match="srid not enforced when geometry_type is None"): # Define the table t = Table( "no_geom_type", MetaData(), Column("id", Integer, primary_key=True), Column("geom", Geometry(geometry_type=None, srid=4326)), ) # Create the table t.create(bind=conn) # Drop the table t.drop(bind=conn) def test_explicit_schema(self, conn): # Define the table t = Table( "a_table", MetaData(), Column("id", Integer, primary_key=True), Column("geom", Geometry()), schema="gis", ) # Create the table t.create(bind=conn) # Check that the table was properly created res = conn.execute(text("PRAGMA main.table_info(a_table)")).fetchall() assert res == [ (0, "id", "INTEGER", 1, None, 1), (1, "geom", "GEOMETRY", 0, None, 0), ] # Drop the table t.drop(bind=conn) class TestIndex: @pytest.fixture def TableWithIndexes(self, base): class TableWithIndexes(base): __tablename__ = "table_with_indexes" id = Column(Integer, primary_key=True) # Test indexes on Geometry columns. geom_not_managed_no_index = Column( Geometry( geometry_type="POINT", spatial_index=False, ) ) geom_not_managed_index = Column( Geometry( geometry_type="POINT", spatial_index=True, ) ) geom_managed_no_index = Column( Geometry( geometry_type="POINT", spatial_index=False, ) ) geom_managed_index = Column( Geometry( geometry_type="POINT", spatial_index=True, ) ) return TableWithIndexes @staticmethod def check_spatial_idx(bind, idx_name): tables = bind.execute( text( "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%';" ) ).fetchall() if idx_name in [i[0] for i in tables]: return True return False @test_only_with_dialects("sqlite-spatialite3", "sqlite-spatialite4") def test_index(self, conn, Lake, setup_tables): assert self.check_spatial_idx(conn, "idx_lake_geom") @test_only_with_dialects("sqlite-spatialite3", "sqlite-spatialite4") def test_type_decorator_index(self, conn, LocalPoint, setup_tables): assert self.check_spatial_idx(conn, "idx_local_point_geom") assert self.check_spatial_idx(conn, "idx_local_point_managed_geom") @test_only_with_dialects("sqlite-spatialite3", "sqlite-spatialite4") def test_all_indexes(self, conn, TableWithIndexes, setup_tables): expected_indices = [ "idx_table_with_indexes_geom_managed_index", "idx_table_with_indexes_geom_not_managed_index", ] for expected_idx in expected_indices: assert self.check_spatial_idx(conn, expected_idx) TableWithIndexes.__table__.drop(bind=conn) indexes_after_drop = conn.execute(text("""SELECT * FROM "geometry_columns";""")).fetchall() tables_after_drop = conn.execute( text( "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%';" ) ).fetchall() assert indexes_after_drop == [] assert [table for table in tables_after_drop if "table_with_indexes" in table.name] == [] class TestMiscellaneous: @test_only_with_dialects("sqlite-spatialite3", "sqlite-spatialite4") @pytest.mark.parametrize( [ "transaction", "init_mode", "journal_mode", ], [ pytest.param(False, "WGS84", None), pytest.param(False, "WGS84", "OFF"), pytest.param(False, "EMPTY", None), pytest.param(False, "EMPTY", "OFF"), pytest.param(True, None, None), pytest.param(True, None, "OFF"), pytest.param(True, "WGS84", None), pytest.param(True, "WGS84", "OFF"), pytest.param(True, "EMPTY", None), pytest.param(True, "EMPTY", "OFF"), ], ) def test_load_spatialite( self, tmpdir, _engine_echo, check_spatialite, transaction, init_mode, journal_mode ): if journal_mode == "OFF": skip_pypy("The journal mode can not be OFF with PyPy.") # Create empty DB tmp_db = tmpdir / "test_spatial_db.sqlite" db_url = f"sqlite:///{tmp_db}" engine = create_engine( db_url, echo=_engine_echo, execution_options={"schema_translate_map": {"gis": None}} ) conn = engine.connect() assert not conn.execute(text("PRAGMA main.table_info('geometry_columns')")).fetchall() assert not conn.execute(text("PRAGMA main.table_info('spatial_ref_sys')")).fetchall() assert conn.execute(text("PRAGMA journal_mode")).fetchone()[0].upper() == "DELETE" load_spatialite( conn.connection.dbapi_connection, transaction=transaction, init_mode=init_mode, journal_mode=journal_mode, ) assert conn.execute(text("SELECT CheckSpatialMetaData();")).scalar() == 3 assert conn.execute(text("PRAGMA main.table_info('geometry_columns')")).fetchall() assert conn.execute(text("PRAGMA main.table_info('spatial_ref_sys')")).fetchall() assert conn.execute(text("PRAGMA journal_mode")).fetchone()[0].upper() == "DELETE" # Check that spatial_ref_sys table was properly populated nb_srid = conn.execute(text("""SELECT COUNT(*) FROM spatial_ref_sys;""")).scalar() if init_mode is None: assert nb_srid > 1000 elif init_mode == "WGS84": assert nb_srid == 129 elif init_mode == "EMPTY": assert nb_srid == 0 # Check that the journal mode is properly reset even when an error is returned by the # InitSpatialMetaData() function assert conn.execute(text("PRAGMA journal_mode")).fetchone()[0].upper() == "DELETE" load_spatialite( conn.connection.dbapi_connection, transaction=transaction, init_mode=init_mode, journal_mode=journal_mode, ) assert conn.execute(text("PRAGMA journal_mode")).fetchone()[0].upper() == "DELETE" @test_only_with_dialects("sqlite-spatialite3", "sqlite-spatialite4") def test_load_spatialite_unknown_transaction(self, conn): with pytest.raises(ValueError, match=r"The 'transaction' argument must be True or False\."): load_spatialite(conn.connection.dbapi_connection, transaction="UNKNOWN MODE") @test_only_with_dialects("sqlite-spatialite3", "sqlite-spatialite4") def test_load_spatialite_unknown_init_type(self, conn): with pytest.raises( ValueError, match=r"The 'init_mode' argument must be one of \['WGS84', 'EMPTY'\]\." ): load_spatialite(conn.connection.dbapi_connection, init_mode="UNKNOWN TYPE") @test_only_with_dialects("sqlite-spatialite3", "sqlite-spatialite4") def test_load_spatialite_unknown_journal_mode(self, conn): with pytest.raises( ValueError, match=( r"The 'journal_mode' argument must be one of " r"\['DELETE', 'TRUNCATE', 'PERSIST', 'MEMORY', 'WAL', 'OFF'\]\." ), ): load_spatialite(conn.connection.dbapi_connection, journal_mode="UNKNOWN MODE") @test_only_with_dialects("sqlite-spatialite3", "sqlite-spatialite4") def test_load_spatialite_no_env_variable(self, monkeypatch, conn): monkeypatch.delenv("SPATIALITE_LIBRARY_PATH") with pytest.raises(RuntimeError): load_spatialite(conn.connection.dbapi_connection) class TestInsertionCore: @pytest.fixture def GeomObject(self, base): class GeomObject(base): __tablename__ = "any_geom_object" id = Column(Integer, primary_key=True) geom = Column(Geometry(srid=4326)) return GeomObject def test_insert_unparsable_WKT(self, conn, GeomObject, setup_tables, dialect_name): with pytest.warns( UserWarning, match=( "The given WKT could not be parsed by GeoAlchemy2, this could lead to undefined " "behavior" ), ): conn.execute( GeomObject.__table__.insert(), [ {"geom": "SRID=4326;GeometryCollection(POINT (-1 1),LINESTRING (2 2, 3 3))"}, ], ) results = conn.execute(GeomObject.__table__.select()) rows = results.fetchall() for row in rows: assert isinstance(row[1], WKBElement) wkt = conn.execute(row[1].ST_AsText()).scalar() assert format_wkt(wkt) == "GEOMETRYCOLLECTION(POINT(-1 1),LINESTRING(2 2,3 3))" srid = conn.execute(row[1].ST_SRID()).scalar() assert srid == 4326 if dialect_name == "mysql": extended = None else: extended = True assert row[1] == from_shape( GeometryCollection([Point(-1, 1), LineString([[2, 2], [3, 3]])]), srid=4326, extended=extended, ) class TestInsertionORM: @pytest.fixture def LocalPoint(self, base): class LocalPoint(base): __tablename__ = "local_point" id = Column(Integer, primary_key=True) geom = Column(TransformedGeometry(db_srid=2154, app_srid=4326, geometry_type="POINT")) return LocalPoint def test_transform(self, session, conn, LocalPoint, setup_tables, dialect_name): if dialect_name == "geopackage": # For GeoPackage we have to create the 'spatial_ref_sys' table to be able to use # the ST_Transform function. It can be created using InitSpatialMetaData() but it also # creates the 'geometry_columns' table, which is useless. So here we create the table # manually with only the required SRS IDs. create_spatial_ref_sys_view(conn) # Create new point instance p = LocalPoint() p.geom = "SRID=4326;POINT(5 45)" # Insert geometry with wrong SRID # Insert point session.add(p) session.flush() session.expire(p) # Query the point and check the result pt = session.query(LocalPoint).one() assert pt.id == 1 assert pt.geom.srid == 4326 pt_wkb = to_shape(pt.geom) assert round(pt_wkb.x, 5) == 5 assert round(pt_wkb.y, 5) == 45 # Check that the data is correct in DB using raw query q = text( """ SELECT id, ST_AsText(geom) AS geom FROM local_point; """ ) res_q = session.execute(q).fetchone() assert res_q.id == 1 for i in [res_q.geom]: x, y = re.match(r"POINT\((\d+\.\d*) (\d+\.\d*)\)", i).groups() assert round(float(x), 3) == 857581.899 assert round(float(y), 3) == 6435414.748 class TestUpdateORM: pass class TestCallFunction: def test_ST_Buffer(self, session): """Test the specific SQLite signature with the `quadrantsegments` parameter.""" s = select( [func.St_AsText(func.ST_Buffer(WKTElement("LINESTRING(0 0,1 0)", srid=4326), 2, 1))] ) r1 = session.execute(s).scalar() assert r1 == "POLYGON((1 2, 3 0, 1 -2, 0 -2, -2 0, 0 2, 1 2))" s = select( [func.St_AsText(func.ST_Buffer(WKTElement("LINESTRING(0 0,1 0)", srid=4326), 2, 2))] ) r1 = session.execute(s).scalar() assert r1 == ( "POLYGON((1 2, 2.414214 1.414214, 3 0, 2.414214 -1.414214, 1 -2, 0 -2, " "-1.414214 -1.414214, -2 0, -1.414214 1.414214, 0 2, 1 2))" ) @pytest.fixture def setup_one_lake(self, session, Lake, setup_tables): lake = Lake(WKTElement("LINESTRING(0 0,1 1)", srid=4326)) session.add(lake) session.flush() session.expire(lake) return lake.id @skip_case_insensitivity() def test_comparator_case_insensitivity(self, session, Lake, setup_one_lake): lake_id = setup_one_lake s = select([func.ST_Buffer(Lake.__table__.c.geom, 1)]) r1 = session.execute(s).scalar() assert isinstance(r1, WKBElement) lake = session.query(Lake).get(lake_id) r2 = session.execute(lake.geom.ST_Buffer(1)).scalar() assert isinstance(r2, WKBElement) r3 = session.execute(lake.geom.st_buffer(1)).scalar() assert isinstance(r3, WKBElement) r4 = session.execute(lake.geom.St_BuFfEr(1)).scalar() assert isinstance(r4, WKBElement) r5 = session.query(Lake.geom.ST_Buffer(1)).scalar() assert isinstance(r5, WKBElement) r6 = session.query(Lake.geom.st_buffer(1)).scalar() assert isinstance(r6, WKBElement) r7 = session.query(Lake.geom.St_BuFfEr(1)).scalar() assert isinstance(r7, WKBElement) assert r1.data == r2.data == r3.data == r4.data == r5.data == r6.data == r7.data class TestShapely: pass class TestNullable: @pytest.fixture def NotNullableLake(self, base): class NotNullableLake(base): __tablename__ = "NotNullablelake" id = Column(Integer, primary_key=True) geom = Column( Geometry( geometry_type="LINESTRING", srid=4326, nullable=False, ) ) def __init__(self, geom): self.geom = geom return NotNullableLake def test_insert(self, conn, NotNullableLake, setup_tables): # Insert geometries conn.execute( NotNullableLake.__table__.insert(), [ {"geom": "SRID=4326;LINESTRING(0 0,1 1)"}, {"geom": WKTElement("LINESTRING(0 0,2 2)", srid=4326)}, {"geom": from_shape(LineString([[0, 0], [3, 3]]), srid=4326)}, ], ) # Fail when trying to insert null geometry with pytest.raises(IntegrityError): conn.execute(NotNullableLake.__table__.insert(), [{"geom": None}]) class TestContraint: @pytest.fixture def ConstrainedLake(self, base): class ConstrainedLake(base): __tablename__ = "contrained_lake" __table_args__ = ( CheckConstraint( "(geom is null and a_str is null) = (checked_str is null)", "check_geom_sk", ), ) id = Column(Integer, primary_key=True) a_str = Column(String, nullable=True) checked_str = Column(String, nullable=True) geom = Column(Geometry(geometry_type="LINESTRING", srid=4326)) def __init__(self, geom): self.geom = geom return ConstrainedLake def test_insert(self, conn, ConstrainedLake, setup_tables): # Insert geometries conn.execute( ConstrainedLake.__table__.insert(), [ { "a_str": None, "geom": "SRID=4326;LINESTRING(0 0,1 1)", "checked_str": "test", }, {"a_str": "test", "geom": None, "checked_str": "test"}, {"a_str": None, "geom": None, "checked_str": None}, ], ) # Fail when trying to insert null geometry with pytest.raises(IntegrityError): conn.execute( ConstrainedLake.__table__.insert(), [ {"a_str": None, "geom": None, "checked_str": "should fail"}, ], ) class TestReflection: @pytest.fixture def setup_reflection_tables(self, reflection_tables_metadata, conn): reflection_tables_metadata.drop_all(conn, checkfirst=True) reflection_tables_metadata.create_all(conn) @test_only_with_dialects("sqlite-spatialite3") def test_reflection_spatialite_lt_4(self, conn, setup_reflection_tables): t = Table("lake", MetaData(), autoload_with=conn) type_ = t.c.geom.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRING" assert type_.srid == 4326 assert type_.dimension == 2 type_ = t.c.geom_no_idx.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRING" assert type_.srid == 4326 assert type_.dimension == 2 type_ = t.c.geom_z.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGZ" assert type_.srid == 4326 assert type_.dimension == 3 type_ = t.c.geom_m.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGM" assert type_.srid == 4326 assert type_.dimension == 3 type_ = t.c.geom_zm.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGZM" assert type_.srid == 4326 assert type_.dimension == 4 # Drop the table t.drop(bind=conn) # Query to check the tables query_tables = text( """SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY tbl_name;""" ) # Query to check the indices query_indexes = text( """SELECT * FROM geometry_columns ORDER BY f_table_name, f_geometry_column;""" ) # Check the indices geom_cols = conn.execute(query_indexes).fetchall() assert geom_cols == [] # Check the tables all_tables = [i[0] for i in conn.execute(query_tables).fetchall()] assert all_tables == [ "SpatialIndex", "geometry_columns", "geometry_columns_auth", "layer_statistics", "spatial_ref_sys", "spatialite_history", "views_geometry_columns", "views_layer_statistics", "virts_geometry_columns", "virts_layer_statistics", ] # Recreate the table to check that the reflected properties are correct t.create(bind=conn) # Check the actual properties geom_cols = conn.execute(query_indexes).fetchall() assert geom_cols == [ ("lake", "geom", "LINESTRING", "XY", 4326, 1), ("lake", "geom_m", "LINESTRING", "XYM", 4326, 1), ("lake", "geom_no_idx", "LINESTRING", "XY", 4326, 0), ("lake", "geom_z", "LINESTRING", "XYZ", 4326, 1), ("lake", "geom_zm", "LINESTRING", "XYZM", 4326, 1), ] all_tables = [i[0] for i in conn.execute(query_tables).fetchall()] assert all_tables == [ "SpatialIndex", "geometry_columns", "geometry_columns_auth", "idx_lake_geom", "idx_lake_geom_m", "idx_lake_geom_m_node", "idx_lake_geom_m_parent", "idx_lake_geom_m_rowid", "idx_lake_geom_node", "idx_lake_geom_parent", "idx_lake_geom_rowid", "idx_lake_geom_z", "idx_lake_geom_z_node", "idx_lake_geom_z_parent", "idx_lake_geom_z_rowid", "idx_lake_geom_zm", "idx_lake_geom_zm_node", "idx_lake_geom_zm_parent", "idx_lake_geom_zm_rowid", "lake", "layer_statistics", "spatial_ref_sys", "spatialite_history", "views_geometry_columns", "views_layer_statistics", "virts_geometry_columns", "virts_layer_statistics", ] @test_only_with_dialects("sqlite-spatialite4") def test_reflection_spatialite_ge_4(self, conn, setup_reflection_tables): t = Table("lake", MetaData(), autoload_with=conn) type_ = t.c.geom.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRING" assert type_.srid == 4326 assert type_.dimension == 2 type_ = t.c.geom_no_idx.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRING" assert type_.srid == 4326 assert type_.dimension == 2 type_ = t.c.geom_z.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGZ" assert type_.srid == 4326 assert type_.dimension == 3 type_ = t.c.geom_m.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGM" assert type_.srid == 4326 assert type_.dimension == 3 type_ = t.c.geom_zm.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGZM" assert type_.srid == 4326 assert type_.dimension == 4 # Drop the table t.drop(bind=conn) # Query to check the tables query_tables = text( """SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY tbl_name;""" ) # Query to check the indices query_indexes = text( """SELECT * FROM geometry_columns ORDER BY f_table_name, f_geometry_column;""" ) # Check the indices geom_cols = conn.execute(query_indexes).fetchall() assert geom_cols == [] # Check the tables all_tables = [i[0] for i in conn.execute(query_tables).fetchall()] assert all_tables == [ "ElementaryGeometries", "SpatialIndex", "geometry_columns", "geometry_columns_auth", "geometry_columns_field_infos", "geometry_columns_statistics", "geometry_columns_time", "spatial_ref_sys", "spatial_ref_sys_aux", "spatialite_history", "sql_statements_log", "views_geometry_columns", "views_geometry_columns_auth", "views_geometry_columns_field_infos", "views_geometry_columns_statistics", "virts_geometry_columns", "virts_geometry_columns_auth", "virts_geometry_columns_field_infos", "virts_geometry_columns_statistics", ] # Recreate the table to check that the reflected properties are correct t.create(bind=conn) # Check the actual properties geom_cols = conn.execute(query_indexes).fetchall() assert geom_cols == [ ("lake", "geom", 2, 2, 4326, 1), ("lake", "geom_m", 2002, 3, 4326, 1), ("lake", "geom_no_idx", 2, 2, 4326, 0), ("lake", "geom_z", 1002, 3, 4326, 1), ("lake", "geom_zm", 3002, 4, 4326, 1), ] all_tables = [i[0] for i in conn.execute(query_tables).fetchall()] assert all_tables == [ "ElementaryGeometries", "SpatialIndex", "geometry_columns", "geometry_columns_auth", "geometry_columns_field_infos", "geometry_columns_statistics", "geometry_columns_time", "idx_lake_geom", "idx_lake_geom_m", "idx_lake_geom_m_node", "idx_lake_geom_m_parent", "idx_lake_geom_m_rowid", "idx_lake_geom_node", "idx_lake_geom_parent", "idx_lake_geom_rowid", "idx_lake_geom_z", "idx_lake_geom_z_node", "idx_lake_geom_z_parent", "idx_lake_geom_z_rowid", "idx_lake_geom_zm", "idx_lake_geom_zm_node", "idx_lake_geom_zm_parent", "idx_lake_geom_zm_rowid", "lake", "spatial_ref_sys", "spatial_ref_sys_aux", "spatialite_history", "sql_statements_log", "views_geometry_columns", "views_geometry_columns_auth", "views_geometry_columns_field_infos", "views_geometry_columns_statistics", "virts_geometry_columns", "virts_geometry_columns_auth", "virts_geometry_columns_field_infos", "virts_geometry_columns_statistics", ] @pytest.fixture def reflection_tables_metadata_multi(self, base): class Lake(base): __tablename__ = "lake" id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type="LINESTRING", srid=4326)) class LakeNoIdx(base): __tablename__ = "lake_no_idx" id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type="LINESTRING", srid=4326, spatial_index=False)) class LakeZ(base): __tablename__ = "lake_z" id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type="LINESTRINGZ", srid=4326, dimension=3)) class LakeM(base): __tablename__ = "lake_m" id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type="LINESTRINGM", srid=4326, dimension=3)) class LakeZM(base): __tablename__ = "lake_zm" id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type="LINESTRINGZM", srid=4326, dimension=4)) yield @pytest.fixture def setup_reflection_multiple_tables(self, reflection_tables_metadata_multi, metadata, conn): metadata.drop_all(conn, checkfirst=True) metadata.create_all(conn) @test_only_with_dialects("sqlite-spatialite3", "geopackage") def test_reflection_mutliple_tables(self, conn, setup_reflection_multiple_tables, dialect_name): reflected_metadata = MetaData() t_lake = Table("lake", reflected_metadata, autoload_with=conn) t_lake_no_idx = Table("lake_no_idx", reflected_metadata, autoload_with=conn) t_lake_z = Table("lake_z", reflected_metadata, autoload_with=conn) t_lake_m = Table("lake_m", reflected_metadata, autoload_with=conn) t_lake_zm = Table("lake_zm", reflected_metadata, autoload_with=conn) type_ = t_lake.c.geom.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRING" assert type_.srid == 4326 assert type_.dimension == 2 type_ = t_lake_no_idx.c.geom.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRING" assert type_.srid == 4326 assert type_.dimension == 2 type_ = t_lake_z.c.geom.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGZ" assert type_.srid == 4326 assert type_.dimension == 3 type_ = t_lake_m.c.geom.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGM" assert type_.srid == 4326 assert type_.dimension == 3 type_ = t_lake_zm.c.geom.type assert isinstance(type_, Geometry) assert type_.geometry_type == "LINESTRINGZM" assert type_.srid == 4326 assert type_.dimension == 4 # Drop the tables reflected_metadata.drop_all(conn, checkfirst=True) # Query to check the tables query_tables = text( """SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE 'lake%' ORDER BY tbl_name;""" ) if dialect_name == "geopackage": # Query to check the indices query_indexes = text( """SELECT A.table_name, A.column_name, A.geometry_type_name, A.z, A.m, A.srs_id, IFNULL(B.has_index, 0) AS has_index FROM gpkg_geometry_columns AS A LEFT JOIN ( SELECT table_name, column_name, COUNT(*) AS has_index FROM gpkg_extensions WHERE extension_name = 'gpkg_rtree_index' GROUP BY table_name, column_name ) AS B ON A.table_name = B.table_name AND A.column_name = B.column_name ORDER BY A.table_name; """ ) else: # Query to check the indices query_indexes = text( """SELECT * FROM geometry_columns ORDER BY f_table_name, f_geometry_column;""" ) # Check the indices geom_cols = conn.execute(query_indexes).fetchall() assert geom_cols == [] # Check the tables all_tables = [i[0] for i in conn.execute(query_tables).fetchall()] assert all_tables == [] # Recreate the table to check that the reflected properties are correct reflected_metadata.create_all(conn) # Check the actual properties geom_cols = conn.execute(query_indexes).fetchall() if dialect_name == "geopackage": assert geom_cols == [ ("lake", "geom", "LINESTRING", 0, 0, 4326, 1), ("lake_m", "geom", "LINESTRINGM", 0, 1, 4326, 1), ("lake_no_idx", "geom", "LINESTRING", 0, 0, 4326, 0), ("lake_z", "geom", "LINESTRINGZ", 1, 0, 4326, 1), ("lake_zm", "geom", "LINESTRINGZM", 1, 1, 4326, 1), ] else: assert geom_cols == [ ("lake", "geom", "LINESTRING", "XY", 4326, 1), ("lake_m", "geom", "LINESTRING", "XYM", 4326, 1), ("lake_no_idx", "geom", "LINESTRING", "XY", 4326, 0), ("lake_z", "geom", "LINESTRING", "XYZ", 4326, 1), ("lake_zm", "geom", "LINESTRING", "XYZM", 4326, 1), ] all_tables = [i[0] for i in conn.execute(query_tables).fetchall()] assert all_tables == [ "lake", "lake_m", "lake_no_idx", "lake_z", "lake_zm", ] geoalchemy2-0.15.2/tests/test_functions.py000066400000000000000000000422321464355170400205750ustar00rootroot00000000000000import re from pathlib import Path from sqlalchemy import select from sqlalchemy.sql import func import geoalchemy2.functions from geoalchemy2._functions_helpers import _generate_stubs from geoalchemy2.types import Raster # NOQA # # Importing geoalchemy2 actually registers the GeoAlchemy generic # functions in SQLAlchemy's function registry. # def eq_sql(a, b): a = re.sub(r"[\n\t]", "", str(a)) assert a == b def _test_simple_func(name): eq_sql( getattr(func, name)(1).select(), 'SELECT %(name)s(:%(name)s_2) AS "%(name)s_1"' % dict(name=name), ) def _test_geometry_returning_func(name): eq_sql( getattr(func, name)(1).select(), 'SELECT ST_AsEWKB(%(name)s(:%(name)s_2)) AS "%(name)s_1"' % dict(name=name), ) def _test_geography_returning_func(name): eq_sql( getattr(func, name)(1).select(), 'SELECT ST_AsBinary(%(name)s(:%(name)s_2)) AS "%(name)s_1"' % dict(name=name), ) def _test_raster_returning_func(name, *args, **kwargs): eq_sql( getattr(func, name)(1, *args, **kwargs).select(), 'SELECT raster(%(name)s(:%(name)s_2)) AS "%(name)s_1"' % dict(name=name), ) def test_stubs_up_to_date() -> None: geoalchemy2_path = Path(geoalchemy2.__file__).parent current_stubs = (geoalchemy2_path / "functions.pyi").read_text() generated_stubs = _generate_stubs() assert current_stubs == generated_stubs def test_predefined_function() -> None: geom = geoalchemy2.functions.ST_GeomFromText("POINT(0, 0)") eq_sql( select(geoalchemy2.functions.ST_Buffer(geom, 1.0)), "SELECT ST_AsEWKB(ST_Buffer(ST_GeomFromText(:ST_GeomFromText_1), " ':ST_Buffer_2)) AS "ST_Buffer_1"', ) # # Geometry Constructors # def test_ST_Collect(): _test_geometry_returning_func("ST_Collect") def test_ST_BdPolyFromText(): _test_geometry_returning_func("ST_BdPolyFromText") def test_ST_BdMPolyFromText(): _test_geometry_returning_func("ST_BdMPolyFromText") def test_ST_Box2dFromGeoHash(): _test_simple_func("ST_Box2dFromGeoHash") def test_ST_GeogFromText(): _test_geography_returning_func("ST_GeogFromText") def test_ST_GeographyFromText(): _test_geography_returning_func("ST_GeographyFromText") def test_ST_GeogFromWKB(): _test_geography_returning_func("ST_GeogFromWKB") def test_ST_GeomFromTWKB(): _test_geometry_returning_func("ST_GeomFromTWKB") def test_ST_GeomCollFromText(): _test_geometry_returning_func("ST_GeomCollFromText") def test_ST_GeomFromEWKB(): _test_geometry_returning_func("ST_GeomFromEWKB") def test_ST_GeomFromEWKT(): _test_geometry_returning_func("ST_GeomFromEWKT") def test_ST_GeometryFromText(): _test_geometry_returning_func("ST_GeometryFromText") def test_ST_GeomFromGeoHash(): _test_geometry_returning_func("ST_GeomFromGeoHash") def test_ST_GeomFromGML(): _test_geometry_returning_func("ST_GeomFromGML") def test_ST_GeomFromGeoJSON(): _test_geometry_returning_func("ST_GeomFromGeoJSON") def test_ST_GeomFromKML(): _test_geometry_returning_func("ST_GeomFromKML") def test_ST_GMLToSQL(): _test_geometry_returning_func("ST_GMLToSQL") def test_ST_GeomFromText(): _test_geometry_returning_func("ST_GeomFromText") def test_ST_GeomFromWKB(): _test_geometry_returning_func("ST_GeomFromWKB") def test_ST_LineFromEncodedPolyline(): _test_geometry_returning_func("ST_LineFromEncodedPolyline") def test_ST_LineFromMultiPoint(): _test_geometry_returning_func("ST_LineFromMultiPoint") def test_ST_LineFromText(): _test_geometry_returning_func("ST_LineFromText") def test_ST_LineFromWKB(): _test_geometry_returning_func("ST_LineFromWKB") def test_ST_LinestringFromWKB(): _test_geometry_returning_func("ST_LinestringFromWKB") def test_ST_MakeBox2D(): _test_simple_func("ST_MakeBox2D") def test_ST_3DMakeBox(): _test_simple_func("ST_3DMakeBox") def test_ST_MakeLine(): _test_geometry_returning_func("ST_MakeLine") def test_ST_MakeEnvelope(): _test_geometry_returning_func("ST_MakeEnvelope") def test_ST_MakePolygon(): _test_geometry_returning_func("ST_MakePolygon") def test_ST_MakePoint(): _test_geometry_returning_func("ST_MakePoint") def test_ST_MakePointM(): _test_geometry_returning_func("ST_MakePointM") def test_ST_MLineFromText(): _test_geometry_returning_func("ST_MLineFromText") def test_ST_MPointFromText(): _test_geometry_returning_func("ST_MPointFromText") def test_ST_MPolyFromText(): _test_geometry_returning_func("ST_MPolyFromText") def test_ST_Point(): _test_geometry_returning_func("ST_Point") def test_ST_PointFromGeoHash(): _test_geometry_returning_func("ST_PointFromGeoHash") def test_ST_PointFromText(): _test_geometry_returning_func("ST_PointFromText") def test_ST_PointFromWKB(): _test_geometry_returning_func("ST_PointFromWKB") def test_ST_Polygon(): _test_geometry_returning_func("ST_Polygon") def test_ST_PolygonFromText(): _test_geometry_returning_func("ST_PolygonFromText") def test_ST_TileEnvelope(): _test_geometry_returning_func("ST_TileEnvelope") def test_ST_WKBToSQL(): _test_geometry_returning_func("ST_WKBToSQL") def test_ST_WKTToSQL(): _test_geometry_returning_func("ST_WKTToSQL") # # Geometry Accessors # def test_ST_Boundary(): _test_geometry_returning_func("ST_Boundary") def test_ST_BoundingDiagonal(): _test_geometry_returning_func("ST_BoundingDiagonal") def test_ST_EndPoint(): _test_geometry_returning_func("ST_EndPoint") def test_ST_Envelope(): _test_geometry_returning_func("ST_Envelope") def test_ST_GeometryN(): _test_geometry_returning_func("ST_GeometryN") def test_ST_GeometryType(): _test_simple_func("ST_GeometryType") def test_ST_InteriorRingN(): _test_geometry_returning_func("ST_InteriorRingN") def test_ST_IsValid(): _test_simple_func("ST_IsValid") def test_ST_NPoints(): _test_simple_func("ST_NPoints") def test_ST_PatchN(): _test_geometry_returning_func("ST_PatchN") def test_ST_PointN(): _test_geometry_returning_func("ST_PointN") def test_ST_Points(): _test_geometry_returning_func("ST_Points") def test_ST_SRID(): _test_simple_func("ST_SRID") def test_ST_StartPoint(): _test_geometry_returning_func("ST_StartPoint") def test_ST_X(): _test_simple_func("ST_X") def test_ST_Y(): _test_simple_func("ST_Y") def test_ST_Z(): _test_simple_func("ST_Z") # # Geometry Editors # def test_ST_AddPoint(): _test_geometry_returning_func("ST_AddPoint") def test_ST_Affine(): _test_geometry_returning_func("ST_Affine") def test_ST_CollectionExtract(): _test_geometry_returning_func("ST_CollectionExtract") def test_ST_CollectionHomogenize(): _test_geometry_returning_func("ST_CollectionHomogenize") def test_ST_ExteriorRing(): _test_geometry_returning_func("ST_ExteriorRing") def test_ST_Force2D(): _test_geometry_returning_func("ST_Force2D") def test_ST_Force3D(): _test_geometry_returning_func("ST_Force3D") def test_ST_Force3DM(): _test_geometry_returning_func("ST_Force3DM") def test_ST_Force3DZ(): _test_geometry_returning_func("ST_Force3DZ") def test_ST_Force4D(): _test_geometry_returning_func("ST_Force4D") def test_ST_ForceCollection(): _test_geometry_returning_func("ST_ForceCollection") def test_ST_ForceCurve(): _test_geometry_returning_func("ST_ForceCurve") def test_ST_ForcePolygonCCW(): _test_geometry_returning_func("ST_ForcePolygonCCW") def test_ST_ForcePolygonCW(): _test_geometry_returning_func("ST_ForcePolygonCW") def test_ST_ForceRHR(): _test_geometry_returning_func("ST_ForceRHR") def test_ST_ForceSFS(): _test_geometry_returning_func("ST_ForceSFS") def test_ST_M(): _test_simple_func("ST_M") def test_ST_Multi(): _test_geometry_returning_func("ST_Multi") def test_ST_Normalize(): _test_geometry_returning_func("ST_Normalize") def test_ST_QuantizeCoordinates(): _test_geometry_returning_func("ST_QuantizeCoordinates") def test_ST_RemovePoint(): _test_geometry_returning_func("ST_RemovePoint") def test_ST_Reverse(): _test_geometry_returning_func("ST_Reverse") def test_ST_Rotate(): _test_geometry_returning_func("ST_Rotate") def test_ST_RotateX(): _test_geometry_returning_func("ST_RotateX") def test_ST_RotateY(): _test_geometry_returning_func("ST_RotateY") def test_ST_RotateZ(): _test_geometry_returning_func("ST_RotateZ") def test_ST_Scale(): _test_geometry_returning_func("ST_Scale") def test_ST_Segmentize(): _test_geometry_returning_func("ST_Segmentize") def test_ST_SetPoint(): _test_geometry_returning_func("ST_SetPoint") def test_ST_SetSRID(): _test_geometry_returning_func("ST_SetSRID") def test_ST_Snap(): _test_geometry_returning_func("ST_Snap") def test_ST_SnapToGrid(): _test_geometry_returning_func("ST_SnapToGrid") def test_ST_SwapOrdinates(): _test_geometry_returning_func("ST_SwapOrdinates") def test_ST_Transform(): _test_geometry_returning_func("ST_Transform") def test_ST_Translate(): _test_geometry_returning_func("ST_Translate") def test_ST_TransScale(): _test_geometry_returning_func("ST_TransScale") # # Geometry Outputs # def test_ST_AsBinary(): _test_simple_func("ST_AsBinary") def test_ST_AsEWKB(): _test_simple_func("ST_AsEWKB") def test_ST_AsTWKB(): _test_simple_func("ST_AsTWKB") def test_ST_AsGeoJSON(): _test_simple_func("ST_AsGeoJSON") def test_ST_AsGML(): _test_simple_func("ST_AsGML") def test_ST_AsKML(): _test_simple_func("ST_AsKML") def test_ST_AsSVG(): _test_simple_func("ST_AsSVG") def test_ST_AsText(): _test_simple_func("ST_AsText") def test_ST_AsEWKT(): _test_simple_func("ST_AsEWKT") def test_ST_AsMVTGeom(): _test_geometry_returning_func("ST_AsMVTGeom") # # Spatial Relationships and Measurements # def test_ST_Area(): _test_simple_func("ST_Area") def test_ST_Azimuth(): _test_simple_func("ST_Azimuth") def test_ST_Centroid(): _test_geometry_returning_func("ST_Centroid") def test_ST_ClosestPoint(): _test_geometry_returning_func("ST_ClosestPoint") def test_ST_3DClosestPoint(): _test_geometry_returning_func("ST_3DClosestPoint") def test_ST_Contains(): _test_simple_func("ST_Contains") def test_ST_ContainsProperly(): _test_simple_func("ST_ContainsProperly") def test_ST_Covers(): _test_simple_func("ST_Covers") def test_ST_CoveredBy(): _test_simple_func("ST_CoveredBy") def test_ST_Crosses(): _test_simple_func("ST_Crosses") def test_ST_Disjoint(): _test_simple_func("ST_Disjoint") def test_ST_Distance(): _test_simple_func("ST_Distance") def test_ST_Distance_Sphere(): _test_simple_func("ST_Distance_Sphere") def test_ST_DistanceSphere(): _test_simple_func("ST_DistanceSphere") def test_ST_DFullyWithin(): _test_simple_func("ST_DFullyWithin") def test_ST_DWithin(): _test_simple_func("ST_DWithin") def test_ST_Equals(): _test_simple_func("ST_Equals") def test_ST_Intersects(): _test_simple_func("ST_Intersects") def test_ST_Length(): _test_simple_func("ST_Length") def test_ST_LineLocatePoint(): _test_simple_func("ST_LineLocatePoint") def test_ST_LongestLine(): _test_geometry_returning_func("ST_LongestLine") def test_ST_3DLongestLine(): _test_geometry_returning_func("ST_3DLongestLine") def test_ST_MinimumClearanceLine(): _test_geometry_returning_func("ST_MinimumClearanceLine") def test_ST_OrderingEquals(): _test_simple_func("ST_OrderingEquals") def test_ST_Overlaps(): _test_simple_func("ST_Overlaps") def test_ST_Perimeter(): _test_simple_func("ST_Perimeter") def test_ST_Project(): _test_geography_returning_func("ST_Project") def test_ST_Relate(): _test_simple_func("ST_Relate") def test_ST_ShortestLine(): _test_geometry_returning_func("ST_ShortestLine") def test_ST_3DShortestLine(): _test_geometry_returning_func("ST_3DShortestLine") def test_ST_Touches(): _test_simple_func("ST_Touches") def test_ST_Within(): _test_simple_func("ST_Within") # # Geometry Processing # def test_ST_Buffer(): _test_geometry_returning_func("ST_Buffer") def test_ST_BuildArea(): _test_geometry_returning_func("ST_BuildArea") def test_ST_ClipByBox2D(): _test_geometry_returning_func("ST_ClipByBox2D") def test_ST_ChaikinSmoothing(): _test_geometry_returning_func("ST_ChaikinSmoothing") def test_ST_ConcaveHull(): _test_geometry_returning_func("ST_ConcaveHull") def test_ST_ConvexHull(): _test_geometry_returning_func("ST_ConvexHull") def test_ST_CurveToLine(): _test_geometry_returning_func("ST_CurveToLine") def test_ST_DelaunayTriangles(): _test_geometry_returning_func("ST_DelaunayTriangles") def test_ST_Difference(): _test_geometry_returning_func("ST_Difference") def test_ST_Dump(): _test_simple_func("ST_Dump") def test_ST_DumpPoints(): _test_simple_func("ST_DumpPoints") def test_ST_FilterByM(): _test_geometry_returning_func("ST_FilterByM") def test_ST_FlipCoordinates(): _test_geometry_returning_func("ST_FlipCoordinates") def test_ST_GeneratePoints(): _test_geometry_returning_func("ST_GeneratePoints") def test_ST_GeometricMedian(): _test_geometry_returning_func("ST_GeometricMedian") def test_ST_Intersection(): _test_geometry_returning_func("ST_Intersection") def test_ST_LineToCurve(): _test_geometry_returning_func("ST_LineToCurve") def test_ST_LineMerge(): _test_geometry_returning_func("ST_LineMerge") def test_ST_LineSubstring(): _test_geometry_returning_func("ST_LineSubstring") def test_ST_MakeValid(): _test_geometry_returning_func("ST_MakeValid") def test_ST_MemUnion(): _test_geometry_returning_func("ST_MemUnion") def test_ST_MinimumBoundingCircle(): _test_geometry_returning_func("ST_MinimumBoundingCircle") def test_ST_Node(): _test_geometry_returning_func("ST_Node") def test_ST_OffsetCurve(): _test_geometry_returning_func("ST_OffsetCurve") def test_ST_OrientedEnvelope(): _test_geometry_returning_func("ST_OrientedEnvelope") def test_ST_PointOnSurface(): _test_geometry_returning_func("ST_PointOnSurface") def test_ST_Polygonize(): _test_geometry_returning_func("ST_Polygonize") def test_ST_RemoveRepeatedPoints(): _test_geometry_returning_func("ST_RemoveRepeatedPoints") def test_ST_SetEffectiveArea(): _test_geometry_returning_func("ST_SetEffectiveArea") def test_ST_SharedPaths(): _test_geometry_returning_func("ST_SharedPaths") def test_ST_ShiftLongitude(): _test_geometry_returning_func("ST_ShiftLongitude") def test_ST_Simplify(): _test_geometry_returning_func("ST_Simplify") def test_ST_SimplifyPreserveTopology(): _test_geometry_returning_func("ST_SimplifyPreserveTopology") def test_ST_SimplifyVW(): _test_geometry_returning_func("ST_SimplifyVW") def test_ST_Split(): _test_geometry_returning_func("ST_Split") def test_ST_Subdivide(): _test_geometry_returning_func("ST_Subdivide") def test_ST_SymDifference(): _test_geometry_returning_func("ST_SymDifference") def test_ST_Union(): _test_geometry_returning_func("ST_Union") def test_ST_UnaryUnion(): _test_geometry_returning_func("ST_UnaryUnion") def test_ST_VoronoiLines(): _test_geometry_returning_func("ST_VoronoiLines") def test_ST_VoronoiPolygons(): _test_geometry_returning_func("ST_VoronoiPolygons") def test_ST_WrapX(): _test_geometry_returning_func("ST_WrapX") # # Bounding Box Functions # def test_ST_Expand(): _test_geometry_returning_func("ST_Expand") # # Linear Referencing # def test_ST_AddMeasure(): _test_geometry_returning_func("ST_AddMeasure") def test_ST_LineInterpolatePoint(): _test_geometry_returning_func("ST_LineInterpolatePoint") def test_ST_LineInterpolatePoints(): _test_geometry_returning_func("ST_LineInterpolatePoints") def test_ST_LocateAlong(): _test_geometry_returning_func("ST_LocateAlong") def test_ST_LocateBetween(): _test_geometry_returning_func("ST_LocateBetween") def test_ST_LocateBetweenElevations(): _test_geometry_returning_func("ST_LocateBetweenElevations") def test_ST_3DLineInterpolatePoint(): _test_geometry_returning_func("ST_3DLineInterpolatePoint") # # Raster Constructors # def test_ST_AddBand(): _test_raster_returning_func("ST_AddBand") def test_ST_AsRaster(): _test_raster_returning_func("ST_AsRaster") # # Raster Editors # def test_ST_Resample(): _test_raster_returning_func("ST_Resample") def test_ST_Rescale(): _test_raster_returning_func("ST_Rescale") def test_ST_Reskew(): _test_raster_returning_func("ST_Reskew") # ST_SnapToGrid already exists for Geometry type so it can not be duplicated def test_ST_SnapToGrid_raster(): _test_raster_returning_func("ST_SnapToGrid", type_=Raster) def test_ST_Resize(): _test_raster_returning_func("ST_Resize") # # Raster Accessors # def test_ST_Height(): _test_simple_func("ST_Height") def test_ST_Width(): _test_simple_func("ST_Width") # # Raster Pixel Accessors and Setters # def test_ST_Value(): _test_simple_func("ST_Value") # # Raster Band Statistics and Analytics # def test_ST_ValueCount(): _test_simple_func("ST_ValueCount") # # DEM (Elevation) # def test_ST_HillShade(): _test_raster_returning_func("ST_HillShade") geoalchemy2-0.15.2/tests/test_pickle.py000066400000000000000000000031061464355170400200310ustar00rootroot00000000000000import pytest from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy.orm import declarative_base from geoalchemy2 import Geometry from geoalchemy2 import WKBElement from geoalchemy2 import WKTElement metadata = MetaData() Base = declarative_base(metadata=metadata) class PickledLake(Base): # type: ignore __tablename__ = "pickled_lake" id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type="LINESTRING", srid=4326)) def __init__(self, geom): self.geom = geom class TestPickle: @pytest.fixture def setup_one_lake(self, session): conn = session.bind metadata.drop_all(conn, checkfirst=True) metadata.create_all(conn) lake = PickledLake(WKTElement("LINESTRING(0 0,1 1)", srid=4326)) session.add(lake) session.flush() session.expire(lake) yield lake.id session.rollback() metadata.drop_all(session.bind, checkfirst=True) def test_pickle_unpickle(self, session, setup_one_lake, dialect_name): import pickle lake_id = setup_one_lake lake = session.get(PickledLake, lake_id) assert isinstance(lake.geom, WKBElement) data_desc = str(lake.geom) pickled = pickle.dumps(lake) unpickled = pickle.loads(pickled) assert unpickled.geom.srid == 4326 assert str(unpickled.geom) == data_desc if dialect_name == "mysql": assert unpickled.geom.extended is False else: assert unpickled.geom.extended is True geoalchemy2-0.15.2/tests/test_shape.py000066400000000000000000000051051464355170400176630ustar00rootroot00000000000000import shapely.wkb from shapely.geometry import Point from geoalchemy2.elements import WKBElement from geoalchemy2.elements import WKTElement from geoalchemy2.shape import from_shape from geoalchemy2.shape import to_shape def test_to_shape_WKBElement(): # POINT(1 2) e = WKBElement( b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@" ) s = to_shape(e) assert isinstance(s, Point) assert s.x == 1 assert s.y == 2 def test_to_shape_WKBElement_str(): # POINT(1 2) e = WKBElement(str("0101000000000000000000f03f0000000000000040")) s = to_shape(e) assert isinstance(s, Point) assert s.x == 1 assert s.y == 2 def test_to_shape_ExtendedWKBElement(): # SRID=3857;POINT(1 2 3) e = WKBElement( b"\x01\x01\x00\x00\xa0\x11\x0f\x00\x00\x00" b"\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00" b"\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@", extended=True, ) s = to_shape(e) assert isinstance(s, Point) assert s.x == 1 assert s.y == 2 assert s.z == 3 def test_to_shape_ExtendedWKTElement(): e = WKTElement("SRID=3857;POINT(1 2)", extended=True) s = to_shape(e) assert isinstance(s, Point) assert s.x == 1 assert s.y == 2 def test_to_shape_WKTElement(): e = WKTElement("POINT(1 2)") s = to_shape(e) assert isinstance(s, Point) assert s.x == 1 assert s.y == 2 def test_from_shape(): # Standard case: POINT(1 2) expected = WKBElement(str("0101000000000000000000f03f0000000000000040")) p = Point(1, 2) e = from_shape(p) assert isinstance(e, WKBElement) assert isinstance(e.data, memoryview) assert e == expected s = shapely.wkb.loads(bytes(e.data)) assert isinstance(s, Point) assert s.equals(p) # Standard case with SRID: SRID=2145;POINT(1 2) expected2 = WKBElement(str("0101000000000000000000f03f0000000000000040"), srid=2154) p = Point(1, 2) e2 = from_shape(p, srid=2154) assert isinstance(e2, WKBElement) assert isinstance(e2.data, memoryview) assert e2 == expected2 s2 = shapely.wkb.loads(bytes(e2.data)) assert isinstance(s2, Point) assert s2.equals(p) # Extended case: SRID=2145;POINT(1 2) expected3 = WKBElement(str("01010000206a080000000000000000f03f0000000000000040"), extended=True) e3 = from_shape(p, srid=2154, extended=True) assert isinstance(e3, WKBElement) assert isinstance(e3.data, memoryview) assert e3 == expected3 s3 = shapely.wkb.loads(bytes(e3.data)) assert isinstance(s, Point) assert s3.equals(p) geoalchemy2-0.15.2/tests/test_types.py000066400000000000000000000205501464355170400177300ustar00rootroot00000000000000import re import pytest from sqlalchemy import Column from sqlalchemy import MetaData from sqlalchemy import Table from sqlalchemy.sql import func from sqlalchemy.sql import insert from sqlalchemy.sql import text from geoalchemy2.exc import ArgumentError from geoalchemy2.types import Geography from geoalchemy2.types import Geometry from geoalchemy2.types import Raster from . import select def eq_sql(a, b): a = re.sub(r"[\n\t]", "", str(a)) assert a == b @pytest.fixture def geometry_table(): table = Table("table", MetaData(), Column("geom", Geometry)) return table @pytest.fixture def geography_table(): table = Table("table", MetaData(), Column("geom", Geography)) return table @pytest.fixture def raster_table(): table = Table("table", MetaData(), Column("rast", Raster)) return table class TestGeometry: def test_get_col_spec(self): g = Geometry(srid=900913) assert g.get_col_spec() == "geometry(GEOMETRY,900913)" def test_get_col_spec_no_srid(self): g = Geometry(srid=None) assert g.get_col_spec() == "geometry(GEOMETRY,-1)" def test_get_col_spec_invalid_srid(self): with pytest.raises(ArgumentError) as e: g = Geometry(srid="foo") g.get_col_spec() assert str(e.value) == "srid must be convertible to an integer" def test_get_col_spec_no_typmod(self): g = Geometry(geometry_type=None) assert g.get_col_spec() == "geometry" def test_check_ctor_args_bad_srid(self): with pytest.raises(ArgumentError): Geometry(srid="foo") def test_get_col_spec_geometryzm(self): g = Geometry(geometry_type="GEOMETRYZM", srid=900913) assert g.get_col_spec() == "geometry(GEOMETRYZM,900913)" def test_get_col_spec_geometryz(self): g = Geometry(geometry_type="GEOMETRYZ", srid=900913) assert g.get_col_spec() == "geometry(GEOMETRYZ,900913)" def test_get_col_spec_geometrym(self): g = Geometry(geometry_type="GEOMETRYM", srid=900913) assert g.get_col_spec() == "geometry(GEOMETRYM,900913)" def test_check_ctor_args_srid_not_enforced(self): with pytest.warns(UserWarning): Geometry(geometry_type=None, srid=4326) def test_check_ctor_args_use_typmod_nullable(self): with pytest.raises( ArgumentError, match='The "nullable" and "use_typmod" arguments can not be used together', ): Geometry(use_typmod=True, nullable=False) def test_column_expression(self, geometry_table): s = select([geometry_table.c.geom]) eq_sql(s, 'SELECT ST_AsEWKB("table".geom) AS geom FROM "table"') def test_select_bind_expression(self, geometry_table): s = select([text("foo")]).where(geometry_table.c.geom == "POINT(1 2)") eq_sql( s, 'SELECT foo FROM "table" WHERE ' '"table".geom = ST_GeomFromEWKT(:geom_1)', ) assert s.compile().params == {"geom_1": "POINT(1 2)"} def test_insert_bind_expression(self, geometry_table): i = insert(geometry_table).values(geom="POINT(1 2)") eq_sql(i, 'INSERT INTO "table" (geom) VALUES (ST_GeomFromEWKT(:geom))') assert i.compile().params == {"geom": "POINT(1 2)"} def test_function_call(self, geometry_table): s = select([geometry_table.c.geom.ST_Buffer(2)]) eq_sql( s, 'SELECT ST_AsEWKB(ST_Buffer("table".geom, :ST_Buffer_2)) ' 'AS "ST_Buffer_1" FROM "table"', ) def test_non_ST_function_call(self, geometry_table): with pytest.raises(AttributeError): geometry_table.c.geom.Buffer(2) def test_subquery(self, geometry_table): # test for geometry columns not delivered to the result # http://hg.sqlalchemy.org/sqlalchemy/rev/f1efb20c6d61 s = select([geometry_table]).alias("name").select() eq_sql( s, "SELECT ST_AsEWKB(name.geom) AS geom FROM " '(SELECT "table".geom AS geom FROM "table") AS name', ) class TestGeography: def test_get_col_spec(self): g = Geography(srid=900913) assert g.get_col_spec() == "geography(GEOMETRY,900913)" def test_get_col_spec_no_typmod(self): g = Geography(geometry_type=None) assert g.get_col_spec() == "geography" def test_column_expression(self, geography_table): s = select([geography_table.c.geom]) eq_sql(s, 'SELECT ST_AsBinary("table".geom) AS geom FROM "table"') def test_select_bind_expression(self, geography_table): s = select([text("foo")]).where(geography_table.c.geom == "POINT(1 2)") eq_sql( s, 'SELECT foo FROM "table" WHERE ' '"table".geom = ST_GeogFromText(:geom_1)', ) assert s.compile().params == {"geom_1": "POINT(1 2)"} def test_insert_bind_expression(self, geography_table): i = insert(geography_table).values(geom="POINT(1 2)") eq_sql(i, 'INSERT INTO "table" (geom) VALUES (ST_GeogFromText(:geom))') assert i.compile().params == {"geom": "POINT(1 2)"} def test_function_call(self, geography_table): s = select([geography_table.c.geom.ST_Buffer(2)]) eq_sql( s, 'SELECT ST_AsEWKB(ST_Buffer("table".geom, :ST_Buffer_2)) ' 'AS "ST_Buffer_1" FROM "table"', ) def test_non_ST_function_call(self, geography_table): with pytest.raises(AttributeError): geography_table.c.geom.Buffer(2) def test_subquery(self, geography_table): # test for geography columns not delivered to the result # http://hg.sqlalchemy.org/sqlalchemy/rev/f1efb20c6d61 s = select([geography_table]).alias("name").select() eq_sql( s, "SELECT ST_AsBinary(name.geom) AS geom FROM " '(SELECT "table".geom AS geom FROM "table") AS name', ) class TestPoint: def test_get_col_spec(self): g = Geometry(geometry_type="POINT", srid=900913) assert g.get_col_spec() == "geometry(POINT,900913)" class TestCurve: def test_get_col_spec(self): g = Geometry(geometry_type="CURVE", srid=900913) assert g.get_col_spec() == "geometry(CURVE,900913)" class TestLineString: def test_get_col_spec(self): g = Geometry(geometry_type="LINESTRING", srid=900913) assert g.get_col_spec() == "geometry(LINESTRING,900913)" class TestPolygon: def test_get_col_spec(self): g = Geometry(geometry_type="POLYGON", srid=900913) assert g.get_col_spec() == "geometry(POLYGON,900913)" class TestMultiPoint: def test_get_col_spec(self): g = Geometry(geometry_type="MULTIPOINT", srid=900913) assert g.get_col_spec() == "geometry(MULTIPOINT,900913)" class TestMultiLineString: def test_get_col_spec(self): g = Geometry(geometry_type="MULTILINESTRING", srid=900913) assert g.get_col_spec() == "geometry(MULTILINESTRING,900913)" class TestMultiPolygon: def test_get_col_spec(self): g = Geometry(geometry_type="MULTIPOLYGON", srid=900913) assert g.get_col_spec() == "geometry(MULTIPOLYGON,900913)" class TestGeometryCollection: def test_get_col_spec(self): g = Geometry(geometry_type="GEOMETRYCOLLECTION", srid=900913) assert g.get_col_spec() == "geometry(GEOMETRYCOLLECTION,900913)" class TestRaster: def test_get_col_spec(self): r = Raster() assert r.get_col_spec() == "raster" def test_column_expression(self, raster_table): s = select([raster_table.c.rast]) eq_sql(s, 'SELECT raster("table".rast) AS rast FROM "table"') def test_insert_bind_expression(self, raster_table): i = insert(raster_table).values(rast=b"\x01\x02") eq_sql(i, 'INSERT INTO "table" (rast) VALUES (raster(:rast))') assert i.compile().params == {"rast": b"\x01\x02"} def test_function_call(self, raster_table): s = select([raster_table.c.rast.ST_Height()]) eq_sql(s, 'SELECT ST_Height("table".rast) ' 'AS "ST_Height_1" FROM "table"') def test_non_ST_function_call(self, raster_table): with pytest.raises(AttributeError): raster_table.c.geom.Height() class TestCompositeType: def test_ST_Dump(self, geography_table): s = select([func.ST_Dump(geography_table.c.geom).geom.label("geom")]) eq_sql(s, 'SELECT ST_AsEWKB((ST_Dump("table".geom)).geom) AS geom ' 'FROM "table"') geoalchemy2-0.15.2/tox.ini000066400000000000000000000042271464355170400153270ustar00rootroot00000000000000[tox] envlist = py{37,38,39,310,311,312}-sqla{14,latest}, pypy3-sqla{14,latest}, lint, coverage, docs requires= setuptools>42 [gh-actions] python = 3.7: py37-sqla{14, latest} 3.8: py38-sqla{14, latest}, lint 3.9: py39-sqla{14, latest} 3.10: py310-sqla{14, latest} 3.11: py311-sqla{14, latest}, docs 3.12: py312-sqla{14, latest} pypy-3.8: pypy3-sqla{14, latest} [testenv] passenv= PROJ_LIB PYTEST_POSTGRESQL_DB_URL PYTEST_MYSQL_DB_URL PYTEST_SPATIALITE3_DB_URL PYTEST_SPATIALITE4_DB_URL SPATIALITE_LIBRARY_PATH setenv= COVERAGE_FILE = {env:COVERAGE_FILE:.coverage-{envname}} EXPECTED_COV = 93 pypy3: EXPECTED_COV = 85 sqla14: PYTEST_ADDOPTS = {env:PYTEST_ADDOPTS:} --mypy-ignore-missing-imports deps= sqla14: SQLAlchemy==1.4.* sqlalatest: SQLAlchemy sqla14: Alembic==1.10.* sqlalatest: Alembic !pypy3: psycopg2 pypy3: psycopg2cffi !pypy3: Shapely>=1.3.0 pypy3: Shapely>=1.3.0, !=1.7.0 -rrequirements.txt -rrequirements-mypy.txt commands= pip freeze --all pytest -v \ --basetemp={envtmpdir} \ --cov=geoalchemy2 \ --cov-branch \ --cov-report term-missing \ --cov-report html:reports/coverage-{envname} \ --cov-report xml:reports/coverage-{envname}.xml \ --cov-fail-under={env:EXPECTED_COV} \ --html reports/pytest-{envname}.html \ --junit-xml=reports/pytest-{envname}.xml \ --self-contained-html \ --mypy \ {posargs} [testenv:coverage] skip_install = true deps = coverage allowlist_externals = /bin/bash commands = /bin/bash -c 'coverage combine .coverage-py*' coverage xml coverage report -m [testenv:lint] basepython = python3.8 skip_install = true deps = pre-commit commands = pre-commit run --all-files --show-diff-on-failure [testenv:format] basepython = python3.8 skip_install = true deps = codespell pre-commit commands = codespell -i 3 -w pre-commit run --all-files [testenv:docs] basepython = python3.11 changedir = doc allowlist_externals = make deps = -rrequirements-doc.txt commands = make clean make html SPHINXOPTS=-W