pax_global_header00006660000000000000000000000064141660000070014504gustar00rootroot0000000000000052 comment=dce0d0eb88c654700a8a11c7aca02a49483a7a15 geoalchemy2-0.10.2/000077500000000000000000000000001416600000700137635ustar00rootroot00000000000000geoalchemy2-0.10.2/.coveragerc000066400000000000000000000000611416600000700161010ustar00rootroot00000000000000[run] source = geoalchemy2 relative_files = True geoalchemy2-0.10.2/.flake8000066400000000000000000000000351416600000700151340ustar00rootroot00000000000000[flake8] max-line-length=100 geoalchemy2-0.10.2/.github/000077500000000000000000000000001416600000700153235ustar00rootroot00000000000000geoalchemy2-0.10.2/.github/workflows/000077500000000000000000000000001416600000700173605ustar00rootroot00000000000000geoalchemy2-0.10.2/.github/workflows/test_and_publish.yml000066400000000000000000000076561416600000700234500ustar00rootroot00000000000000name: 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: env: PGPASSWORD: gis jobs: # This workflow runs the tests tests: # Setup test matrix strategy: fail-fast: false matrix: python-version: [3.6, 3.7, 3.8, 3.9, pypy3] # The type of runner that the job will run on runs-on: ubuntu-18.04 services: postgres: image: mdillon/postgis:11 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@v2 # Setup Python - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} architecture: x64 # 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;' # Check python version - name: Display Python version run: python -c "import sys; print(sys.version)" # Install dependencies - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y autotools-dev \ git \ libexpat1-dev \ libfreexl-dev \ libgeos-dev \ libproj-dev \ libreadline-dev \ libsqlite3-dev \ libsqlite3-mod-spatialite \ libxml2-dev \ zlib1g-dev pip install --upgrade pip setuptools pip install tox-gh-actions flake8==3.7.9 # Run Flake8 - name: Run Flake8 run: | # Run the library through flake8 flake8 --ignore=W503,W504 geoalchemy2 tests # Run the test suite - name: Run the tests env: SPATIALITE_LIBRARY_PATH: /usr/lib/x86_64-linux-gnu/mod_spatialite.so run: | # Run the unit test suite with SQLAlchemy=1.1.2 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 }} # This workflow aggregates coverages from all jobs and export it to Coveralls coveralls: needs: tests runs-on: ubuntu-latest 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-18.04 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') steps: - uses: actions/checkout@v2 - name: Set up Python 3.6 uses: actions/setup-python@v2 with: python-version: 3.6 # 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 wheel twine python setup.py sdist bdist_wheel twine upload dist/* geoalchemy2-0.10.2/.gitignore000066400000000000000000000010421416600000700157500ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ 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 .cache nosetests.xml coverage.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Rope .ropeproject # Django stuff: *.log *.pot # Sphinx documentation doc/_build/ doc/gallery/ geoalchemy2-0.10.2/CHANGES.txt000066400000000000000000000141231416600000700155750ustar00rootroot00000000000000GeoAlchemy 2 Changelog ====================== 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) #153 fixed a typo in the _SpatialElement class, where the attribute "extended" was incorrectly spelled "extented". So if your application code refers to this attribute on WKTElement or WKBElement objects you will need that code as well. 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.10.2/COPYING.rst000066400000000000000000000020401416600000700156210ustar00rootroot00000000000000Copyright (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.10.2/MANIFEST.in000066400000000000000000000000341416600000700155160ustar00rootroot00000000000000include *.rst include *.txt geoalchemy2-0.10.2/README.rst000066400000000000000000000013121416600000700154470ustar00rootroot00000000000000============ GeoAlchemy 2 ============ .. image:: https://travis-ci.org/geoalchemy/geoalchemy2.png?branch=master :target: http://travis-ci.org/#!/geoalchemy/geoalchemy2 .. 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 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. geoalchemy2-0.10.2/RELEASE.rst000066400000000000000000000011511416600000700155730ustar00rootroot00000000000000Release ------- 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 Git tag and push it:: $ git tag -a x.y -m 'version x.y' $ git push origin x.y 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.10.2/TEST.rst000066400000000000000000000032651416600000700153020ustar00rootroot00000000000000===== Tests ===== Install system dependencies =========================== Install PostgreSQL and PostGIS:: $ sudo apt-get install postgresql postgis 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 the Python dependencies:: $ pip install -r requirements.txt $ pip install psycopg2 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" Run Tests ========= To run the tests:: $ py.test geoalchemy2-0.10.2/doc/000077500000000000000000000000001416600000700145305ustar00rootroot00000000000000geoalchemy2-0.10.2/doc/Makefile000066400000000000000000000110021416600000700161620ustar00rootroot00000000000000# 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.10.2/doc/_static/000077500000000000000000000000001416600000700161565ustar00rootroot00000000000000geoalchemy2-0.10.2/doc/_static/geoalchemy.png000066400000000000000000000511441416600000700210060ustar00rootroot00000000000000PNG  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.10.2/doc/_static/geoalchemy.svg000066400000000000000000000415721416600000700210250ustar00rootroot00000000000000 image/svg+xml geoalchemy2-0.10.2/doc/_static/geoalchemy_small.png000066400000000000000000000417211416600000700221760ustar00rootroot00000000000000PNG  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.10.2/doc/_static/geoalchemy_small.svg000066400000000000000000000507371416600000700222200ustar00rootroot00000000000000 image/svg+xml geoalchemy2-0.10.2/doc/_templates/000077500000000000000000000000001416600000700166655ustar00rootroot00000000000000geoalchemy2-0.10.2/doc/_templates/sidebar-about.html000066400000000000000000000001671416600000700223000ustar00rootroot00000000000000

About

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

geoalchemy2-0.10.2/doc/_templates/sidebar-links.html000066400000000000000000000004431416600000700223030ustar00rootroot00000000000000

Useful Links

geoalchemy2-0.10.2/doc/_templates/sidebar-logo.html000066400000000000000000000002241416600000700221200ustar00rootroot00000000000000 geoalchemy2-0.10.2/doc/_themes/000077500000000000000000000000001416600000700161545ustar00rootroot00000000000000geoalchemy2-0.10.2/doc/_themes/LICENSE000066400000000000000000000033751416600000700171710ustar00rootroot00000000000000Copyright (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.10.2/doc/_themes/README000066400000000000000000000021051416600000700170320ustar00rootroot00000000000000Flask 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.10.2/doc/_themes/flask/000077500000000000000000000000001416600000700172545ustar00rootroot00000000000000geoalchemy2-0.10.2/doc/_themes/flask/layout.html000066400000000000000000000016511416600000700214620ustar00rootroot00000000000000{%- 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.10.2/doc/_themes/flask/relations.html000066400000000000000000000011161416600000700221410ustar00rootroot00000000000000

Related Topics

geoalchemy2-0.10.2/doc/_themes/flask/static/000077500000000000000000000000001416600000700205435ustar00rootroot00000000000000geoalchemy2-0.10.2/doc/_themes/flask/static/flasky.css_t000066400000000000000000000150031416600000700230700ustar00rootroot00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. * :license: Flask Design License, see LICENSE for details. */ {% set page_width = '940px' %} {% set sidebar_width = '220px' %} {% set text_font = "'Open Sans', Verdana, Helvetica, sans-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; } 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.10.2/doc/_themes/flask/static/small_flask.css000066400000000000000000000017201416600000700235450ustar00rootroot00000000000000/* * 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.10.2/doc/_themes/flask/theme.conf000066400000000000000000000002621416600000700212250ustar00rootroot00000000000000[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.10.2/doc/conf.py000066400000000000000000000177601416600000700160420ustar00rootroot00000000000000# -*- 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 sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) 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.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 = u'GeoAlchemy2' copyright = u'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. # import geoalchemy2 version = release = geoalchemy2.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = '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', 'localtoc.html', 'sidebar-links.html', 'searchbox.html'], '**': ['sidebar-logo.html', 'localtoc.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', u'GeoAlchemy2 Documentation', u'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', u'GeoAlchemy2 Documentation', [u'Eric Lemoine'], 1) ] # Mocks for Read the Docs import sys from unittest.mock import MagicMock class Mock(MagicMock): @classmethod def __getattr__(cls, name): if name == "_mock_methods": return name._mock_methods else: return Mock() MOCK_MODULES = ['shapely', 'shapely.wkt', 'shapely.wkb'] sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) # -- Options gallery generation ------------------------------------------------ from sphinx_gallery.sorting import ExampleTitleSortKey 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, } geoalchemy2-0.10.2/doc/core_tutorial.rst000066400000000000000000000267221416600000700201460ustar00rootroot00000000000000.. _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 respresents 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 explicitely. >>> 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.10.2/doc/elements.rst000066400000000000000000000005031416600000700170740ustar00rootroot00000000000000.. _elements: Elements ======== .. 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: geoalchemy2-0.10.2/doc/index.rst000066400000000000000000000074731416600000700164040ustar00rootroot00000000000000GeoAlchemy 2 Documentation ========================== *Using SQLAlchemy with Spatial Databases.* GeoAlchemy 2 provides extensions to `SQLAlchemy `_ for working with spatial databases. GeoAlchemy 2 focuses on `PostGIS `_. PostGIS 1.5 and PostGIS 2 are supported. SpatiaLite is also supported, but using GeoAlchemy 2 with SpatiaLite requires some specific configuration on the application side. GeoAlchemy 2 works with SpatiaLite 4.3.0 and higher. GeoAlchemy 2 aims to be simpler than its predecessor, `GeoAlchemy `_. Simpler to use, and simpler to maintain. The current version of this documentation applies to the version |version| of GeoAlchemy 2. Requirements ------------ GeoAlchemy 2 requires SQLAlchemy 0.8. GeoAlchemy 2 does not work with SQLAlchemy 0.7 and lower. 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. Reference Documentation ----------------------- .. toctree:: :maxdepth: 1 types elements spatial_functions spatial_operators shape 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) * 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. Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` geoalchemy2-0.10.2/doc/make.bat000066400000000000000000000106511416600000700161400ustar00rootroot00000000000000@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.10.2/doc/migrate.rst000066400000000000000000000051401416600000700167120ustar00rootroot00000000000000.. _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.10.2/doc/orm_tutorial.rst000066400000000000000000000326761416600000700200200ustar00rootroot00000000000000.. _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.10.2/doc/shape.rst000066400000000000000000000002421416600000700163600ustar00rootroot00000000000000.. _shape: Shapely Integration =================== .. automodule:: geoalchemy2.shape :members: :private-members: :undoc-members: :show-inheritance: geoalchemy2-0.10.2/doc/spatial_functions.rst000066400000000000000000000001601416600000700210040ustar00rootroot00000000000000.. _spatial_functions: Spatial Functions ================= .. automodule:: geoalchemy2.functions :members: geoalchemy2-0.10.2/doc/spatial_operators.rst000066400000000000000000000002341416600000700210140ustar00rootroot00000000000000.. _spatial_operators: Spatial Operators ================= .. automodule:: geoalchemy2.comparator :members: :special-members: :show-inheritance: geoalchemy2-0.10.2/doc/spatialite_tutorial.rst000066400000000000000000000150361416600000700213510ustar00rootroot00000000000000.. _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. 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 sqlalchemy import create_engine >>> from sqlalchemy.event import listen >>> >>> def load_spatialite(dbapi_conn, connection_record): ... dbapi_conn.enable_load_extension(True) ... dbapi_conn.load_extension('/usr/lib/x86_64-linux-gnu/mod_spatialite.so') ... >>> >>> 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. At this point you can test that you are able to connect to the database:: >> conn = engine.connect() 2018-05-30 17:12:02,675 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 2018-05-30 17:12:02,676 INFO sqlalchemy.engine.base.Engine () 2018-05-30 17:12:02,676 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 2018-05-30 17:12:02,676 INFO sqlalchemy.engine.base.Engine () You can also check that the ``gis.db`` SQLite database file was created on the file system. One additional step is required for using SpatiaLite: create the ``geometry_columns`` and ``spatial_ref_sys`` metadata tables. This is done by calling SpatiaLite's ``InitSpatialMetaData`` function:: >>> from sqlalchemy.sql import select, func >>> >>> conn.execute(select([func.InitSpatialMetaData()])) Note that this operation may take some time the first time it is executed for a database. 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.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(geometry_type='POLYGON', management=True)) This basically works in the way as with PostGIS. The difference is the ``management`` argument that must be set to ``True``. Setting ``management`` to ``True`` indicates that the ``AddGeometryColumn`` and ``DiscardGeometryColumn`` management functions will be used for the creation and removal of the geometry column. This is required with SpatiaLite. 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. 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.10.2/doc/types.rst000066400000000000000000000001631416600000700164260ustar00rootroot00000000000000.. _types: Types ===== .. automodule:: geoalchemy2.types :members: :private-members: :show-inheritance: geoalchemy2-0.10.2/geoalchemy2/000077500000000000000000000000001416600000700161625ustar00rootroot00000000000000geoalchemy2-0.10.2/geoalchemy2/__init__.py000066400000000000000000000172631416600000700203040ustar00rootroot00000000000000from .types import ( # NOQA Geometry, Geography, Raster ) from .elements import ( # NOQA WKTElement, WKBElement, RasterElement ) from .exc import ArgumentError from . import functions # NOQA from . import types # NOQA import sqlalchemy from sqlalchemy import Table, event from sqlalchemy.sql import select, func, expression, text from sqlalchemy.types import TypeDecorator from packaging import version _SQLALCHEMY_VERSION_BEFORE_14 = version.parse(sqlalchemy.__version__) < version.parse("1.4") def _format_select_args(*args): if _SQLALCHEMY_VERSION_BEFORE_14: return [args] else: return args def _check_spatial_type(tested_type, spatial_types, dialect): return ( isinstance(tested_type, spatial_types) or ( isinstance(tested_type, TypeDecorator) and isinstance(tested_type.load_dialect_impl(dialect), spatial_types) ) ) def _setup_ddl_event_listeners(): @event.listens_for(Table, "before_create") def before_create(target, connection, **kw): dispatch("before-create", target, connection) @event.listens_for(Table, "after_create") def after_create(target, connection, **kw): dispatch("after-create", target, connection) @event.listens_for(Table, "before_drop") def before_drop(target, connection, **kw): dispatch("before-drop", target, connection) @event.listens_for(Table, "after_drop") def after_drop(target, connection, **kw): dispatch("after-drop", target, connection) def dispatch(event, table, bind): if event in ('before-create', 'before-drop'): # Filter Geometry columns from the table with management=True # Note: Geography and PostGIS >= 2.0 don't need this gis_cols = [c for c in table.c if _check_spatial_type(c.type, Geometry, bind.dialect) and c.type.management is True] # Find all other columns that are not managed Geometries regular_cols = [x for x in table.c if x not in gis_cols] # Save original table column list for later table.info["_saved_columns"] = table.c # 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 if event == 'before-drop': # Drop the managed Geometry columns for c in gis_cols: if bind.dialect.name == 'sqlite': drop_func = 'DiscardGeometryColumn' elif bind.dialect.name == 'postgresql': drop_func = 'DropGeometryColumn' else: raise ArgumentError('dialect {} is not supported'.format(bind.dialect.name)) args = [table.schema] if table.schema else [] args.extend([table.name, c.name]) stmt = select(*_format_select_args(getattr(func, drop_func)(*args))) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) elif event == 'after-create': # Restore original column list including managed Geometry columns table.columns = table.info.pop('_saved_columns') for c in table.c: # Add the managed Geometry columns with AddGeometryColumn() if ( _check_spatial_type(c.type, Geometry, bind.dialect) and c.type.management is True ): args = [table.schema] if table.schema else [] args.extend([ table.name, c.name, c.type.srid, c.type.geometry_type, c.type.dimension ]) if c.type.use_typmod is not None: args.append(c.type.use_typmod) if bind.dialect.name == 'sqlite': args.append(not c.type.nullable) 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(c.type, (Geometry, Geography), bind.dialect) and c.type.spatial_index is True ): if bind.dialect.name == 'sqlite': stmt = select(*_format_select_args(func.CreateSpatialIndex(table.name, c.name))) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) elif bind.dialect.name == 'postgresql': index_name = '"idx_{}_{}"'.format(table.name, c.name) if c.type.use_N_D_index: gis_column = '"{}" gist_geometry_ops_nd'.format(c.name) else: gis_column = '"{}"'.format(c.name) if table.schema: table_name = '"{}"."{}"'.format(table.schema, table.name) else: table_name = '"{}"'.format(table.name) sql = "CREATE INDEX {} ON {} USING GIST ({})".format(index_name, table_name, gis_column) q = text(sql) bind.execute(q) else: raise ArgumentError('dialect {} is not supported'.format(bind.dialect.name)) if isinstance(c.type, (Geometry, Geography)) and c.type.spatial_index is False and \ c.type.use_N_D_index is True: raise ArgumentError('Arg Error(use_N_D_index): spatial_index must be True') # Add spatial indices for the Raster columns # # Note the use of ST_ConvexHull since most raster operators are # based on the convex hull of the rasters. if isinstance(c.type, Raster) and c.type.spatial_index is True: if table.schema: q = text('CREATE INDEX "idx_%s_%s" ON "%s"."%s" ' 'USING GIST (ST_ConvexHull("%s"))' % (table.name, c.name, table.schema, table.name, c.name)) else: q = text('CREATE INDEX "idx_%s_%s" ON "%s" ' 'USING GIST (ST_ConvexHull("%s"))' % (table.name, c.name, table.name, c.name)) bind.execute(q) elif event == 'after-drop': # Restore original column list including managed Geometry columns table.columns = table.info.pop('_saved_columns') _setup_ddl_event_listeners() # Get version number __version__ = "UNKNOWN VERSION" try: from pkg_resources import get_distribution, DistributionNotFound try: __version__ = get_distribution('GeoAlchemy2').version except DistributionNotFound: # pragma: no cover pass # pragma: no cover except ImportError: # pragma: no cover pass # pragma: no cover geoalchemy2-0.10.2/geoalchemy2/_functions.py000066400000000000000000001732271416600000700207170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # flake8: noqa from . import types _FUNCTIONS = [ ('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', types.Geometry, '''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, '''Return the Well-Known Binary (WKB) representation of the geometry/geography without SRID meta data.'''), ('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', types.Geometry, ('''Returns a BOX2D representing the 2D extent of the geometry.''', 'Box2D_type')), ('Box3D', types.Geometry, ('''[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', types.Geometry, '''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', types.Geometry, '''an aggregate function that returns the bounding box that bounds rows of geometries.'''), ('ST_3DExtent', types.Geometry, '''an aggregate function that returns the 3D bounding box that bounds rows of geometries.'''), ('ST_MakeBox2D', types.Geometry, '''Creates a BOX2D defined by two 2D point geometries.'''), ('ST_3DMakeBox', types.Geometry, '''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 Tesselation 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.backend', None, ('''The backend to service a function where GEOS and SFCGAL overlap. Options: geos or sfcgal. Defaults to geos.''', 'postgis_backend')), ('postgis.gdal_datapath', None, ('''A configuration option to assign the value of GDAL's GDAL_DATA option. If not set, the environmentally set GDAL_DATA variable is used.''', 'postgis_gdal_datapath')), ('postgis.gdal_enabled_drivers', None, ('''A configuration option to set the enabled GDAL drivers in the PostGIS environment. Affects the GDAL configuration variable GDAL_SKIP.''', 'postgis_gdal_enabled_drivers')), ('postgis.enable_outdb_rasters', None, ('''A boolean configuration option to enable access to out-db raster bands.''', 'postgis_enable_outdb_rasters')), ('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_AsBinary/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 prodived. 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 prodived. 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.'''), ] geoalchemy2-0.10.2/geoalchemy2/comparator.py000066400000000000000000000165061416600000700207130ustar00rootroot00000000000000""" 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) Reference --------- """ from sqlalchemy import types as sqltypes from sqlalchemy.types import UserDefinedType from sqlalchemy.dialects.postgresql import DOUBLE_PRECISION from sqlalchemy.sql import operators try: from sqlalchemy.sql.functions import _FunctionGenerator except ImportError: # SQLA < 0.9 # pragma: no cover from sqlalchemy.sql.expression import _FunctionGenerator INTERSECTS = operators.custom_op('&&') INTERSECTS_ND = operators.custom_op('&&&') OVERLAPS_OR_TO_LEFT = operators.custom_op('&<') OVERLAPS_OR_TO_RIGHT = operators.custom_op('&>') OVERLAPS_OR_BELOW = operators.custom_op('&<|') TO_LEFT = operators.custom_op('<<') BELOW = operators.custom_op('<<|') TO_RIGHT = operators.custom_op('>>') CONTAINED = operators.custom_op('@') OVERLAPS_OR_ABOVE = operators.custom_op('|&>') ABOVE = operators.custom_op('|>>') CONTAINS = operators.custom_op('~') SAME = operators.custom_op('~=') DISTANCE_CENTROID = operators.custom_op('<->') DISTANCE_BOX = operators.custom_op('<#>') 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): """ The ``&&`` operator. A's BBOX intersects B's. """ return self.operate(INTERSECTS, other, result_type=sqltypes.Boolean) def overlaps_or_to_left(self, other): """ 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): """ 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): """ 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): """ 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): """ 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): """ The ``<<|`` operator. A's BBOX is strictly below B's. """ return self.operate(BELOW, other, result_type=sqltypes.Boolean) def to_right(self, other): """ 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): """ 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): """ 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): """ 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): """ The ``|>>`` operator. A's BBOX is strictly above B's. """ return self.operate(ABOVE, other, result_type=sqltypes.Boolean) def contains(self, other, **kw): """ The ``~`` operator. A's BBOX contains B's. """ return self.operate(CONTAINS, other, result_type=sqltypes.Boolean) def same(self, other): """ 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): """ The ``<->`` operator. The distance between two points. """ return self.operate(DISTANCE_CENTROID, other, result_type=DOUBLE_PRECISION) def distance_box(self, other): """ 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): """ The ``&&&`` 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.10.2/geoalchemy2/elements.py000066400000000000000000000174471416600000700203650ustar00rootroot00000000000000import binascii import struct try: from sqlalchemy.sql import functions from sqlalchemy.sql.functions import FunctionElement except ImportError: # SQLA < 0.9 # pragma: no cover from sqlalchemy.sql import expression as functions from sqlalchemy.sql.expression import FunctionElement from sqlalchemy.types import to_instance from sqlalchemy.ext.compiler import compiles from .exc import ArgumentError BinasciiError = binascii.Error function_registry = set() class HasFunction(object): pass class _SpatialElement(HasFunction): """ The base class for :class:`geoalchemy2.elements.WKTElement` and :class:`geoalchemy2.elements.WKBElement`. The first argument passed to the constructor is the data wrapped by the ``_SpatialElement` object being constructed. Additional arguments: ``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 ``False``. """ def __init__(self, data, srid=-1, extended=False): self.srid = srid self.data = data self.extended = extended def __str__(self): return self.desc def __repr__(self): return "<%s at 0x%x; %s>" % \ (self.__class__.__name__, id(self), self) # pragma: no cover def __eq__(self, other): try: return ( self.extended == other.extended and self.srid == other.srid and self.desc == other.desc ) except AttributeError: return False def __ne__(self, other): 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 librairies 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): state = { 'srid': self.srid, 'data': str(self), 'extended': self.extended, } return state def __setstate__(self, state): self.srid = state['srid'] self.extended = state['extended'] self.data = self._data_from_desc(state['data']) @staticmethod def _data_from_desc(desc): raise NotImplementedError() 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) """ geom_from = 'ST_GeomFromText' geom_from_extended_version = 'ST_GeomFromEWKT' def __init__(self, data, srid=-1, extended=False): if extended and srid == -1: # read srid from EWKT if not data.startswith('SRID='): raise ArgumentError('invalid EWKT string {}'.format(data)) data_s = data.split(';', 1) 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): """ This element's description string. """ return self.data @staticmethod def _data_from_desc(desc): return desc 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 = 'ST_GeomFromWKB' geom_from_extended_version = 'ST_GeomFromEWKB' def __init__(self, data, srid=-1, extended=False): if extended and srid == -1: # 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 # } 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, srid = header[0], header[5:] srid = struct.unpack('I', srid)[0] _SpatialElement.__init__(self, data, srid, extended) @property def desc(self): """ This element's description string. """ if isinstance(self.data, str): # SpatiaLite case return self.data desc = str(binascii.hexlify(self.data), encoding="utf-8") return desc @staticmethod def _data_from_desc(desc): desc = desc.encode(encoding="utf-8") return binascii.unhexlify(desc) 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 = 'raster' def __init__(self, data): # 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. try: bin_data = binascii.unhexlify(data[:114]) except BinasciiError: bin_data = data data = str(binascii.hexlify(data).decode(encoding='utf-8')) byte_order = bin_data[0] srid = bin_data[53:57] srid = struct.unpack('I', srid)[0] _SpatialElement.__init__(self, data, srid, True) @property def desc(self): """ 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 = False def __init__(self, base, field, type_): self.name = field self.type = to_instance(type_) super(CompositeElement, self).__init__(base) @compiles(CompositeElement) def _compile_pgelem(expr, compiler, **kw): return '(%s).%s' % (compiler.process(expr.clauses, **kw), expr.name) geoalchemy2-0.10.2/geoalchemy2/exc.py000066400000000000000000000003601416600000700173120ustar00rootroot00000000000000""" 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.10.2/geoalchemy2/functions.py000066400000000000000000000211511416600000700205440ustar00rootroot00000000000000""" 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()) Reference --------- """ import re from sqlalchemy import inspect from sqlalchemy.sql import functions from sqlalchemy.sql.elements import ColumnElement from sqlalchemy.ext.compiler import compiles from sqlalchemy.util import with_metaclass from . import elements from ._functions import _FUNCTIONS class _GenericMeta(functions._GenericMeta): """Extend the metaclass mechanism of sqlalchemy to register the functions in a specific registry for geoalchemy2""" _register = False def __init__(cls, clsname, bases, clsdict): # Register the function elements.function_registry.add(clsname.lower()) super(_GenericMeta, cls).__init__(clsname, bases, clsdict) class TableRowElement(ColumnElement): inherit_cache = False def __init__(self, selectable): self.selectable = selectable @property def _from_objects(self): return [self.selectable] class ST_AsGeoJSON(with_metaclass(_GenericMeta, functions.GenericFunction)): """Special process for the ST_AsGeoJSON() function to be able to work with its feature version introduced in PostGIS 3.""" name = "ST_AsGeoJSON" inherit_cache = True def __init__(self, *args, **kwargs): expr = kwargs.pop('expr', None) args = list(args) if expr is not None: args = [expr] + args for idx, element in enumerate(args): if isinstance(element, functions.Function): continue elif isinstance(element, elements.HasFunction): 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[idx] = getattr(functions.func, func_name)(*func_args) else: try: insp = inspect(element) if hasattr(insp, "selectable"): args[idx] = TableRowElement(insp.selectable) except Exception: continue functions.GenericFunction.__init__(self, *args, **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(with_metaclass(_GenericMeta, functions.GenericFunction)): """ 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): expr = kwargs.pop('expr', None) args = list(args) if expr is not None: args = [expr] + args for idx, elem in enumerate(args): if isinstance(elem, elements.HasFunction): 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[idx] = getattr(functions.func, func_name)(*func_args) functions.GenericFunction.__init__(self, *args, **kwargs) # Iterate through _FUNCTIONS and create GenericFunction classes dynamically for name, type_, doc in _FUNCTIONS: attributes = { 'name': name, 'inherit_cache': True, } docs = [] if isinstance(doc, tuple): docs.append(doc[0]) docs.append('see http://postgis.net/docs/{0}.html'.format(doc[1])) elif doc is not None: docs.append(doc) docs.append('see http://postgis.net/docs/{0}.html'.format(name)) if type_ is not None: attributes['type'] = type_ type_str = '{0}.{1}'.format(type_.__module__, type_.__name__) docs.append('Return type: :class:`{0}`.'.format(type_str)) if len(docs) != 0: attributes['__doc__'] = '\n\n'.join(docs) globals()[name] = type(name, (GenericFunction,), attributes) # # 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", } # Default handlers are required for SQLAlchemy < 1.1 # See more details in https://github.com/geoalchemy/geoalchemy2/issues/213 def _compiles_default(cls): def _compile_default(element, compiler, **kw): return "{}({})".format(cls, compiler.process(element.clauses, **kw)) compiles(globals()[cls])(_compile_default) def _compiles_sqlite(cls, fn): def _compile_sqlite(element, compiler, **kw): return "{}({})".format(fn, compiler.process(element.clauses, **kw)) compiles(globals()[cls], "sqlite")(_compile_sqlite) for cls, fn in _SQLITE_FUNCTIONS.items(): _compiles_default(cls) _compiles_sqlite(cls, fn) geoalchemy2-0.10.2/geoalchemy2/shape.py000066400000000000000000000064351416600000700176440ustar00rootroot00000000000000""" 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 pkg_resources import parse_version import shapely.wkb import shapely.wkt from .elements import WKBElement, WKTElement if parse_version(shapely.__version__) < parse_version("1.7"): ###################################################################### # Backport function from Shapely 1.7 from shapely.geos import WKBWriter, lgeos from shapely.geometry.base import geom_factory def dumps(ob, hex=False, srid=None, **kw): """Dump a WKB representation of a geometry to a byte string, or a hex-encoded string if ``hex=True``. Parameters ---------- ob : geometry The geometry to export to well-known binary (WKB) representation hex : bool If true, export the WKB as a hexidecimal string. The default is to return a binary string/bytes object. srid : int Spatial reference system ID to include in the output. The default value means no SRID is included. **kw : kwargs See available keyword output settings in ``shapely.geos.WKBWriter``. """ if srid is not None: # clone the object and set the SRID before dumping geom = lgeos.GEOSGeom_clone(ob._geom) lgeos.GEOSSetSRID(geom, srid) ob = geom_factory(geom) kw["include_srid"] = True writer = WKBWriter(lgeos, **kw) if hex: return writer.write_hex(ob) else: return writer.write(ob) ###################################################################### else: from shapely.wkb import dumps # noqa def to_shape(element): """ Function to convert a :class:`geoalchemy2.types.SpatialElement` to a Shapely geometry. 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) def from_shape(shape, srid=-1, extended=False): """ Function to convert a Shapely geometry to a :class:`geoalchemy2.types.WKBElement`. Additional arguments: ``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) geoalchemy2-0.10.2/geoalchemy2/types.py000066400000000000000000000351051416600000700177040ustar00rootroot00000000000000""" This module defines the :class:`geoalchemy2.types.Geometry`, :class:`geoalchemy2.types.Geography`, and :class:`geoalchemy2.types.Raster` classes, that are used when defining geometry, geography and raster columns/properties in models. Reference --------- """ import warnings from sqlalchemy.types import UserDefinedType, Integer, Float from sqlalchemy.sql import func from sqlalchemy.dialects import postgresql from sqlalchemy.dialects.postgresql.base import ischema_names try: from .shape import to_shape SHAPELY = True except ImportError: SHAPELY = False from .comparator import BaseComparator, Comparator from .elements import WKBElement, WKTElement, RasterElement, CompositeElement from .exc import ArgumentError class _GISType(UserDefinedType): """ The base class for :class:`geoalchemy2.types.Geometry` and :class:`geoalchemy2.types.Geography`. 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. Constructor arguments: ``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. Using ``None`` here is not compatible with setting ``management`` to ``True``. Default is ``"GEOMETRY"``. ``srid`` The SRID for this column. E.g. 4326. Default is ``-1``. ``dimension`` The dimension of the geometry. Default is ``2``. With ``management`` set to ``True``, that is when ``AddGeometryColumn`` is used to add the geometry column, there are two constraints: * The ``geometry_type`` must not end with ``"ZM"``. This is due to PostGIS' ``AddGeometryColumn`` failing with ZM geometry types. Instead the "simple" geometry type (e.g. POINT rather POINTZM) should be used with ``dimension`` set to ``4``. * When the ``geometry_type`` ends with ``"Z"`` or ``"M"`` then ``dimension`` must be set to ``3``. With ``management`` set to ``False`` (the default) ``dimension`` is not taken into account, and the actual dimension is fully defined with the ``geometry_type``. ``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. ``management`` Indicate if the ``AddGeometryColumn`` and ``DropGeometryColumn`` managements functions should be called when adding and dropping the geometry column. Should be set to ``True`` for PostGIS 1.x. Default is ``False``. Note that this option has no effect for :class:`geoalchemy2.types.Geography`. ``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 taken into account if ``management`` is set to ``True`` and is only available for PostGIS 2.x. """ name = None """ Name used for defining the main geo type (geometry or geography) in CREATE TABLE statements. Set in subclasses. """ from_text = None """ The name of "from text" function for this type. Set in subclasses. """ as_binary = None """ The name of the "as binary" function for this type. Set in subclasses. """ comparator_factory = 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='GEOMETRY', srid=-1, dimension=2, spatial_index=True, use_N_D_index=False, management=False, use_typmod=None, from_text=None, name=None, nullable=True): geometry_type, srid = self.check_ctor_args( geometry_type, srid, dimension, management, 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.management = management self.use_typmod = use_typmod self.extended = self.as_binary == 'ST_AsEWKB' self.nullable = nullable 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): def process(value): if value is not None: kwargs = {} if self.srid > 0: kwargs['srid'] = self.srid if self.extended is not None: 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): def process(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 dialect.name == 'sqlite' or not bindvalue.extended: # With SpatiaLite or when the WKBElement includes a WKB value rather # than a EWKB value we use Shapely to convert the WKBElement to an # EWKT string if not SHAPELY: raise ArgumentError('Shapely is required for handling WKBElement bind ' 'values when using SpatiaLite or when the bind value ' 'is a WKB rather than an EWKB') 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 return process @staticmethod def check_ctor_args(geometry_type, srid, dimension, management, use_typmod, nullable): try: srid = int(srid) except ValueError: raise ArgumentError('srid must be convertible to an integer') if geometry_type: geometry_type = geometry_type.upper() if management: if geometry_type.endswith('ZM'): # PostGIS' AddGeometryColumn does not work with ZM geometry types. Instead # the simple geometry type (e.g. POINT rather POINTZM) should be used with # dimension set to 4 raise ArgumentError( 'with management=True use geometry_type={!r} and ' 'dimension=4 for {!r} geometries'.format(geometry_type[:-2], geometry_type)) elif geometry_type[-1] in ('Z', 'M') and dimension != 3: # If a Z or M geometry type is used then dimension must be set to 3 raise ArgumentError( 'with management=True dimension must be 3 for ' '{!r} geometries'.format(geometry_type)) else: if management: raise ArgumentError('geometry_type set to None not compatible ' 'with management') if srid > 0: warnings.warn('srid not enforced when geometry_type is None') if use_typmod and not management: warnings.warn('use_typmod ignored when management is False') 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 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. Constructor arguments: ``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, *args, **kwargs): # Enforce default values kwargs['geometry_type'] = None kwargs['srid'] = -1 super(Raster, self).__init__(*args, **kwargs) self.extended = None class CompositeType(UserDefinedType): """ 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 = {} """ 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``, consisting of 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 Postgres reflection # subsystem. ischema_names['geometry'] = Geometry ischema_names['geography'] = Geography 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. """ geoalchemy2-0.10.2/requirements-doc.txt000066400000000000000000000001261416600000700200110ustar00rootroot00000000000000# Additional requirements for building the documentation sphinx sphinx-gallery pillow geoalchemy2-0.10.2/requirements-rtd.txt000066400000000000000000000000731416600000700200360ustar00rootroot00000000000000# Install geoalchemy2 package -e . -r requirements-doc.txt geoalchemy2-0.10.2/requirements.txt000066400000000000000000000001621416600000700172460ustar00rootroot00000000000000# Additional requirements for running the testsuite and development flake8==3.7.9 pytest==3.7.4 pytest-cov==2.5.1 geoalchemy2-0.10.2/setup.cfg000066400000000000000000000016301416600000700156040ustar00rootroot00000000000000[bdist_wheel] # flag indicating that the code is written on both Python 2 and 3 universal = 1 [tox:tox] envlist = py{36,37,38,39}-sqla{11, latest}, pypy3-sqla{11, latest} requires= setuptools>42 [gh-actions] python = 3.6: py36-sqla{11, latest} 3.7: py37-sqla{11, latest} 3.8: py38-sqla{11, latest} 3.9: py35-sqla{11, latest} pypy3: pypy3-sqla{11, latest} [testenv] passenv= PYTEST_DB_URL SPATIALITE_LIBRARY_PATH SPATIALITE_DB_PATH deps= sqla11: SQLAlchemy==1.1.2 sqlalatest: SQLAlchemy py27: setuptools_scm<4 py27: pysqlite !pypy3: psycopg2 pypy3: psycopg2cffi !pypy3: Shapely>=1.3.0 pypy3: Shapely>=1.3.0, !=1.7.0 -rrequirements.txt commands= python -c "import sys; print(sys.version)" py.test -v --cov geoalchemy2 --cov-report term-missing \ {posargs} [coverage:run] relative_files = True [tool:pytest] testpaths = tests geoalchemy2-0.10.2/setup.py000066400000000000000000000023271416600000700155010ustar00rootroot00000000000000from setuptools import setup, find_packages 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.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "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/latest/', license='MIT', python_requires=">=3.6", packages=find_packages(exclude=['ez_setup', 'examples', 'tests', 'doc']), include_package_data=True, zip_safe=False, setup_requires=["setuptools_scm"], install_requires=[ 'SQLAlchemy>=1.1', 'packaging' ], entry_points=""" # -*- Entry points: -*- """, ) geoalchemy2-0.10.2/tests/000077500000000000000000000000001416600000700151255ustar00rootroot00000000000000geoalchemy2-0.10.2/tests/__init__.py000066400000000000000000000020641416600000700172400ustar00rootroot00000000000000from pkg_resources import parse_version import pytest from sqlalchemy import __version__ as SA_VERSION def skip_postgis1(postgis_version): return pytest.mark.skipif( postgis_version.startswith('1.'), reason="requires PostGIS != 1", ) def skip_postgis2(postgis_version): return pytest.mark.skipif( postgis_version.startswith('2.'), reason="requires PostGIS != 2", ) def skip_postgis3(postgis_version): return pytest.mark.skipif( postgis_version.startswith('3.'), reason="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(postgres_major_version): return pytest.mark.skipif( ( parse_version(SA_VERSION) < parse_version("1.2.17") and int(postgres_major_version) >= 12 ), reason='Reflection for PostgreSQL-12 is only supported by sqlalchemy>=1.2.17') geoalchemy2-0.10.2/tests/gallery/000077500000000000000000000000001416600000700165645ustar00rootroot00000000000000geoalchemy2-0.10.2/tests/gallery/README.rst000066400000000000000000000000361416600000700202520ustar00rootroot00000000000000.. _gallery: Gallery ======= geoalchemy2-0.10.2/tests/gallery/test_decipher_raster.py000066400000000000000000000123511416600000700233420ustar00rootroot00000000000000""" 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 create_engine from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from geoalchemy2 import Raster, WKTElement engine = create_engine('postgresql://gis:gis@localhost/gis', echo=False) metadata = MetaData(engine) Base = declarative_base(metadata=metadata) session = sessionmaker(bind=engine)() class Ocean(Base): __tablename__ = 'ocean' id = Column(Integer, primary_key=True) rast = Column(Raster) def __init__(self, rast): self.rast = rast def _format_e(endianess, struct_format): return _ENDIANESS[endianess] + 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['endianess'] = struct.unpack('b', raw[0:1])[0] e = header['endianess'] 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, endianess=1): ptype, _, psize = _PTYPE[pixtype] pix_data = data[offset + 1: offset + 1 + width * height * psize] band = [ [ struct.unpack(_format_e(endianess, 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, endianess=1): import numpy as np # noqa _, dtype, psize = _PTYPE[pixtype] dt = np.dtype(dtype) dt = dt.newbyteorder(_ENDIANESS[endianess]) 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], } _ENDIANESS = { 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["endianess"] 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 class TestDecipherRaster(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() @pytest.mark.parametrize("pixel_type", [ '1BB', '2BUI', '4BUI', '8BSI', '8BUI', '16BSI', '16BUI', '32BSI', '32BUI', '32BF', '64BF' ]) def test_decipher_raster(self, pixel_type): """Create a raster and decipher it""" # 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.10.2/tests/gallery/test_disable_wrapping.py000066400000000000000000000035561416600000700235200ustar00rootroot00000000000000""" 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 import select from sqlalchemy.ext.declarative import declarative_base from geoalchemy2 import Geometry 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): __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 \n" "FROM 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.10.2/tests/gallery/test_length_at_insert.py000066400000000000000000000041311416600000700235250ustar00rootroot00000000000000""" Compute length on insert ======================== It is possible to insert a geometry and ask PostgreSQL to compute its length at the same time. This example uses SQLAlchemy core queries. """ from sqlalchemy import bindparam from sqlalchemy import Column from sqlalchemy import create_engine from sqlalchemy import Float from sqlalchemy import func from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import select from sqlalchemy import Table from geoalchemy2 import Geometry from geoalchemy2.shape import to_shape engine = create_engine('postgresql://gis:gis@localhost/gis', echo=True) metadata = MetaData(engine) table = Table( "inserts", metadata, Column("id", Integer, primary_key=True), Column("geom", Geometry("LINESTRING", 4326)), Column("distance", Float), ) class TestLengthAtInsert(): def setup(self): self.conn = engine.connect() metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): self.conn.close() metadata.drop_all() def test_query(self): conn = self.conn # Define geometries to insert values = [ {"ewkt": "SRID=4326;LINESTRING(0 0, 1 0)"}, {"ewkt": "SRID=4326;LINESTRING(0 0, 0 1)"} ] # Define the query to compute distance (without spheroid) distance = func.ST_Length(func.ST_GeomFromText(bindparam("ewkt")), False) i = table.insert() i = i.values(geom=bindparam("ewkt"), distance=distance) # Execute the query with values as parameters conn.execute(i, values) # Check the result q = select([table]) res = conn.execute(q).fetchall() # Check results assert len(res) == 2 r1 = res[0] assert r1[0] == 1 assert r1[1].srid == 4326 assert to_shape(r1[1]).wkt == "LINESTRING (0 0, 1 0)" assert round(r1[2]) == 111195 r2 = res[1] assert r2[0] == 2 assert r2[1].srid == 4326 assert to_shape(r2[1]).wkt == "LINESTRING (0 0, 0 1)" assert round(r2[2]) == 111195 geoalchemy2-0.10.2/tests/gallery/test_raster_transform.py000066400000000000000000000072571416600000700236030ustar00rootroot00000000000000""" 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 func from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import select from sqlalchemy import Table from sqlalchemy.orm import Query from sqlalchemy.ext.declarative import declarative_base from geoalchemy2 import Geometry from geoalchemy2 import Raster 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(srid=4326)), ) class RasterTable(Base): __tablename__ = 'raster_table_orm' id = Column(Integer, primary_key=True) geom = Column(Geometry("POLYGON", 4326)) rast = Column(Raster(srid=4326)) 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.10.2/tests/gallery/test_summarystatsagg.py000066400000000000000000000070331416600000700234330ustar00rootroot00000000000000""" 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 __version__ as SA_VERSION from sqlalchemy import Column from sqlalchemy import create_engine from sqlalchemy import Float from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import select from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from geoalchemy2 import Raster, WKTElement from geoalchemy2.functions import GenericFunction from geoalchemy2.types import CompositeType 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 engine = create_engine('postgresql://gis:gis@localhost/gis', echo=True) metadata = MetaData(engine) Base = declarative_base(metadata=metadata) session = sessionmaker(bind=engine)() class Ocean(Base): __tablename__ = 'ocean' __table_args__ = {'schema': 'public'} id = Column(Integer, primary_key=True) rast = Column(Raster) def __init__(self, rast): self.rast = rast class TestSTSummaryStatsAgg(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() @pytest.mark.skipif( parse_version(SA_VERSION) < parse_version("1.4"), reason="requires SQLAlchely>1.4", ) def test_st_summary_stats_agg(self): # 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) == ( "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(" "public.ocean.rast, " "%(ST_SummaryStatsAgg_1)s, %(ST_SummaryStatsAgg_2)s, %(ST_SummaryStatsAgg_3)s" ") AS stats \n" "FROM public.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.10.2/tests/gallery/test_type_decorator.py000066400000000000000000000114341416600000700232230ustar00rootroot00000000000000""" 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 `_ """ from sqlalchemy import create_engine from sqlalchemy import MetaData from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import func from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from sqlalchemy.types import TypeDecorator from geoalchemy2 import Geometry from geoalchemy2 import shape engine = create_engine('postgresql://gis:gis@localhost/gis', echo=True) metadata = MetaData(engine) Base = declarative_base(metadata=metadata) 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 overrided 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) 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)) class Point(Base): __tablename__ = "point" id = Column(Integer, primary_key=True) raw_geom = Column(Geometry(srid=4326, geometry_type="POINT")) geom = Column( TransformedGeometry( db_srid=2154, app_srid=4326, geometry_type="POINT")) three_d_geom = Column( ThreeDGeometry(srid=4326, geometry_type="POINTZ", dimension=3)) session = sessionmaker(bind=engine)() def check_wkb(wkb, x, y): pt = shape.to_shape(wkb) assert round(pt.x, 5) == x assert round(pt.y, 5) == y class TestTypeDecorator(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() def _create_one_point(self): # 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): self._create_one_point() # 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 = "SELECT id, ST_AsEWKT(geom) AS geom FROM point;" res_q = session.execute(q).fetchone() assert res_q.id == 1 assert res_q.geom == "SRID=2154;POINT(857581.899319668 6435414.7478354)" # 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): self._create_one_point() # 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.10.2/tests/test_comparator.py000066400000000000000000000132471416600000700207140ustar00rootroot00000000000000import re import pytest from sqlalchemy import Table, MetaData, Column, select from geoalchemy2.types import Geometry 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 class TestOperator(): def test_eq(self, geometry_table): expr = geometry_table.c.geom == 'POINT(1 2)' 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): expr = geometry_table.c.geom != 'POINT(1 2)' 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): expr = geometry_table.c.geom.intersects('POINT(1 2)') eq_sql(expr, '"table".geom && ST_GeomFromEWKT(:geom_1)') def test_overlaps_or_to_left(self, geometry_table): expr = geometry_table.c.geom.overlaps_or_to_left('POINT(1 2)') eq_sql(expr, '"table".geom &< ST_GeomFromEWKT(:geom_1)') def test_overlaps_or_below(self, geometry_table): expr = geometry_table.c.geom.overlaps_or_below('POINT(1 2)') eq_sql(expr, '"table".geom &<| ST_GeomFromEWKT(:geom_1)') def test_overlaps_or_to_right(self, geometry_table): expr = geometry_table.c.geom.overlaps_or_to_right('POINT(1 2)') eq_sql(expr, '"table".geom &> ST_GeomFromEWKT(:geom_1)') def test_to_left(self, geometry_table): expr = geometry_table.c.geom.to_left('POINT(1 2)') eq_sql(expr, '"table".geom << ST_GeomFromEWKT(:geom_1)') def test_lshift(self, geometry_table): expr = geometry_table.c.geom << 'POINT(1 2)' eq_sql(expr, '"table".geom << ST_GeomFromEWKT(:geom_1)') def test_below(self, geometry_table): expr = geometry_table.c.geom.below('POINT(1 2)') eq_sql(expr, '"table".geom <<| ST_GeomFromEWKT(:geom_1)') def test_to_right(self, geometry_table): expr = geometry_table.c.geom.to_right('POINT(1 2)') eq_sql(expr, '"table".geom >> ST_GeomFromEWKT(:geom_1)') def test_rshift(self, geometry_table): expr = geometry_table.c.geom >> 'POINT(1 2)' eq_sql(expr, '"table".geom >> ST_GeomFromEWKT(:geom_1)') def test_contained(self, geometry_table): expr = geometry_table.c.geom.contained('POINT(1 2)') eq_sql(expr, '"table".geom @ ST_GeomFromEWKT(:geom_1)') def test_overlaps_or_above(self, geometry_table): expr = geometry_table.c.geom.overlaps_or_above('POINT(1 2)') eq_sql(expr, '"table".geom |&> ST_GeomFromEWKT(:geom_1)') def test_above(self, geometry_table): expr = geometry_table.c.geom.above('POINT(1 2)') eq_sql(expr, '"table".geom |>> ST_GeomFromEWKT(:geom_1)') def test_contains(self, geometry_table): expr = geometry_table.c.geom.contains('POINT(1 2)') eq_sql(expr, '"table".geom ~ ST_GeomFromEWKT(:geom_1)') def test_same(self, geometry_table): expr = geometry_table.c.geom.same('POINT(1 2)') eq_sql(expr, '"table".geom ~= ST_GeomFromEWKT(:geom_1)') def test_distance_centroid(self, geometry_table): expr = geometry_table.c.geom.distance_centroid('POINT(1 2)') eq_sql(expr, '"table".geom <-> ST_GeomFromEWKT(:geom_1)') def test_distance_centroid_select(self, geometry_table): s = geometry_table.select().order_by( geometry_table.c.geom.distance_centroid('POINT(1 2)')).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 == {u'geom_1': 'POINT(1 2)', u'param_1': 10} def test_distance_centroid_select_with_label(self, geometry_table): s = select([geometry_table.c.geom.distance_centroid('POINT(1 2)'). 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 == {u'geom_1': 'POINT(1 2)', u'param_1': 10} def test_distance_box(self, geometry_table): expr = geometry_table.c.geom.distance_box('POINT(1 2)') eq_sql(expr, '"table".geom <#> ST_GeomFromEWKT(:geom_1)') def test_distance_box_select(self, geometry_table): s = geometry_table.select().order_by( geometry_table.c.geom.distance_box('POINT(1 2)')).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 == {u'geom_1': 'POINT(1 2)', u'param_1': 10} def test_distance_box_select_with_label(self, geometry_table): s = select([geometry_table.c.geom.distance_box('POINT(1 2)'). 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 == {u'geom_1': 'POINT(1 2)', u'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.10.2/tests/test_elements.py000066400000000000000000000311701416600000700203540ustar00rootroot00000000000000from itertools import permutations import re import pytest from shapely import wkb from sqlalchemy import Table, MetaData, Column, String, func from geoalchemy2.types import Geometry from geoalchemy2.elements import ( WKTElement, WKBElement, RasterElement, CompositeElement ) from geoalchemy2.exc import ArgumentError @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(): def test_desc(self): e = WKTElement('POINT(1 2)') assert e.desc == 'POINT(1 2)' def test_function_call(self): e = WKTElement('POINT(1 2)') 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 == { u'ST_Buffer_1': 2, u'ST_GeomFromText_1': 'POINT(1 2)', u'ST_GeomFromText_2': -1 } def test_attribute_error(self): e = WKTElement('POINT(1 2)') assert not hasattr(e, 'foo') def test_pickle_unpickle(self): import pickle e = WKTElement('POINT(1 2)', srid=3, extended=True) pickled = pickle.dumps(e) unpickled = pickle.loads(pickled) assert unpickled.srid == 3 assert unpickled.extended is True assert unpickled.data == 'POINT(1 2)' f = unpickled.ST_Buffer(2) eq_sql(f, 'ST_Buffer(' 'ST_GeomFromEWKT(:ST_GeomFromEWKT_1), ' ':ST_Buffer_1)') assert f.compile().params == { u'ST_Buffer_1': 2, u'ST_GeomFromEWKT_1': 'POINT(1 2)', } def test_eq(self): a = WKTElement('POINT(1 2)') b = WKTElement('POINT(1 2)') assert a == b class TestExtendedWKTElement(): _srid = 3857 # expected srid _wkt = 'POINT (1 2 3)' # expected wkt _ewkt = 'SRID=3857;POINT (1 2 3)' # expected ewkt def test_desc(self): e = WKTElement(self._ewkt, extended=True) 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 == { u'ST_Buffer_1': 2, u'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 == { u'ST_Buffer_1': 2, u'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) def test_eq(self): a = WKTElement(self._ewkt, extended=True) b = WKTElement(self._ewkt, extended=True) assert a == b 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 == { u'ST_GeomFromText_1': 'POINT(1 2)', u'ST_GeomFromText_2': -1, u'ST_GeomFromText_3': 'POINT(1 2)', u'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 == { u'ST_GeomFromText_1': 'POINT(1 2)', u'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 == { u'ST_GeomFromEWKT_1': 'SRID=3857;POINT(1 2 3)', u'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 == { u'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 = 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@') _hex = str('010100002003000000000000000000f03f0000000000000040') _srid = 3 # expected srid _wkt = 'POINT (1 2)' # expected wkt def test_desc(self): e = WKBElement(self._bin, extended=True) assert e.desc == self._hex def test_desc_str(self): e = WKBElement(self._hex) assert e.desc == self._hex def test_function_call(self): e = WKBElement(self._bin, extended=True) f = e.ST_Buffer(2) eq_sql(f, 'ST_Buffer(' 'ST_GeomFromEWKB(:ST_GeomFromEWKB_1), ' ':ST_Buffer_1)') assert f.compile().params == { u'ST_Buffer_1': 2, u'ST_GeomFromEWKB_1': self._bin, } def test_function_str(self): e = WKBElement(self._bin, extended=True) assert isinstance(str(e), str) def test_pickle_unpickle(self): import pickle e = WKBElement(self._bin, 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) f = unpickled.ST_Buffer(2) eq_sql(f, 'ST_Buffer(' 'ST_GeomFromEWKB(:ST_GeomFromEWKB_1), ' ':ST_Buffer_1)') assert f.compile().params == { u'ST_Buffer_1': 2, u'ST_GeomFromEWKB_1': bytes(self._bin), } 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, 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, 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, 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, extended=True) b = WKBElement(self._bin, extended=True) assert a == b 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 == { u'ST_Buffer_1': 2, u'ST_GeomFromWKB_1': b'\x01\x02', u'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 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 == {u'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 == { u'raster_1': self.hex_rast_data, } 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.10.2/tests/test_functional.py000066400000000000000000001117331416600000700207060ustar00rootroot00000000000000from json import loads import os import pytest import re try: from psycopg2cffi import compat except ImportError: pass else: compat.register() del compat from pkg_resources import parse_version from sqlalchemy import create_engine from sqlalchemy import Table, MetaData, Column, Integer, String, bindparam from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.exc import DataError, IntegrityError, InternalError, ProgrammingError from sqlalchemy.sql import select, func from sqlalchemy.sql.expression import type_coerce from sqlalchemy.types import TypeDecorator from sqlalchemy import __version__ as SA_VERSION from geoalchemy2 import Geometry, Geography, Raster from geoalchemy2.elements import WKTElement, WKBElement, RasterElement from geoalchemy2.shape import from_shape from geoalchemy2.exc import ArgumentError from shapely.geometry import LineString, Point from . import skip_postgis1, skip_postgis2, skip_case_insensitivity, skip_pg12_sa1217 SQLA_LT_2 = parse_version(SA_VERSION) <= parse_version("2") if SQLA_LT_2: from sqlalchemy.engine.reflection import Inspector get_inspector = Inspector.from_engine else: from sqlalchemy import inspect as get_inspector engine = create_engine( os.environ.get('PYTEST_DB_URL', 'postgresql://gis:gis@localhost/gis'), echo=False) metadata = MetaData(engine) arg_metadata = MetaData(engine) Base = declarative_base(metadata=metadata) class Lake(Base): __tablename__ = 'lake' __table_args__ = {'schema': 'gis'} id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type='LINESTRING', srid=4326)) def __init__(self, geom): self.geom = geom class Poi(Base): __tablename__ = 'poi' __table_args__ = {'schema': 'gis'} id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type='POINT', srid=4326)) geog = Column(Geography(geometry_type='POINT', srid=4326)) def __init__(self, geog): self.geog = geog class Summit(Base): __tablename__ = 'summit' __table_args__ = {'schema': 'gis'} id = Column(Integer, primary_key=True) geom = Column(Geometry( geometry_type='POINT', srid=4326, management=True)) def __init__(self, geom): self.geom = geom 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)) class PointZ(Base): __tablename__ = "point_z" id = Column(Integer, primary_key=True) three_d_geom = Column(ThreeDGeometry(srid=4326, geometry_type="POINTZ", dimension=3)) class IndexTestWithSchema(Base): __tablename__ = 'indextestwithschema' __table_args__ = {'schema': 'gis'} id = Column(Integer, primary_key=True) geom1 = Column(Geometry(geometry_type='POINT', srid=4326)) geom2 = Column(Geometry(geometry_type='POINT', srid=4326, management=True)) class IndexTestWithNDIndex(Base): __tablename__ = 'index_test_with_nd_index' __table_args__ = {'schema': 'gis'} id = Column(Integer, primary_key=True) geom1 = Column(Geometry(geometry_type='POINTZ', dimension=3, use_N_D_index=True)) 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, management=True)) session = sessionmaker(bind=engine)() postgis_version = session.execute(func.postgis_lib_version()).scalar() postgres_major_version = re.match(r"([0-9]*)\.([0-9]*).*", session.execute( """SELECT current_setting('server_version');""").scalar()).group(1) if postgis_version.startswith('1.'): # With PostGIS 1.x the AddGeometryColumn and DropGeometryColumn # management functions should be used. Lake.__table__.c.geom.type.management = True else: # parameter use_typmod for AddGeometryColumn was added in PostGIS 2.0 Summit.__table__.c.geom.type.use_typmod = False # The raster type is only available on PostGIS 2.0 and above class Ocean(Base): __tablename__ = 'ocean' __table_args__ = {'schema': 'public'} id = Column(Integer, primary_key=True) rast = Column(Raster) def __init__(self, rast): self.rast = rast class TestIndex(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() arg_metadata.drop_all() def test_index_with_schema(self): inspector = get_inspector(engine) 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 (u'geom1', u'geom2') assert not indices[1].get('unique') assert indices[1].get('column_names')[0] in (u'geom1', u'geom2') def test_n_d_index(self): engine.connect() sql = """SELECT tablename, indexname, indexdef FROM pg_indexes WHERE tablename = 'index_test_with_nd_index' ORDER BY tablename, indexname""" r = engine.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(engine) indices = inspector.get_indexes(IndexTestWithNDIndex.__tablename__) assert len(indices) == 1 assert not indices[0].get('unique') assert indices[0].get('column_names')[0] in (u'geom1') def test_n_d_index_argument_error(self): BaseArgTest = declarative_base(metadata=arg_metadata) 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)) with pytest.raises(ArgumentError) as excinfo: NDIndexArgErrorSchema.__table__.create(engine) assert "Arg Error(use_N_D_index): spatial_index must be True" == excinfo.value.args[0] def test_index_without_schema(self): inspector = get_inspector(engine) indices = inspector.get_indexes(IndexTestWithoutSchema.__tablename__) assert len(indices) == 2 assert not indices[0].get('unique') assert indices[0].get('column_names')[0] in (u'geom1', u'geom2') assert not indices[1].get('unique') assert indices[1].get('column_names')[0] in (u'geom1', u'geom2') def test_type_decorator_index(self): inspector = get_inspector(engine) 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'] class TestTypMod(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() @skip_pg12_sa1217(postgres_major_version) def test_SummitConstraints(self): """ Make sure the geometry column of table Summit is created with `use_typmod=false` (explicit constraints are created). """ inspector = get_inspector(engine) 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 TestInsertionCore(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() self.conn = engine.connect() def teardown(self): self.conn.close() metadata.drop_all() def test_insert(self): conn = self.conn # 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 = session.execute(row[1].ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0,1 1)' srid = session.execute(row[1].ST_SRID()).scalar() assert srid == 4326 row = rows[1] assert isinstance(row[1], WKBElement) wkt = session.execute(row[1].ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0,2 2)' srid = session.execute(row[1].ST_SRID()).scalar() assert srid == 4326 row = rows[2] assert isinstance(row[1], WKBElement) wkt = session.execute(row[1].ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0,2 2)' srid = session.execute(row[1].ST_SRID()).scalar() assert srid == 4326 row = rows[3] assert isinstance(row[1], WKBElement) wkt = session.execute(row[1].ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0,3 3)' srid = session.execute(row[1].ST_SRID()).scalar() assert srid == 4326 def test_insert_geom_poi(self): conn = self.conn 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 = session.execute(row[1].ST_AsText()).scalar() assert wkt == 'POINT(1 1)' srid = session.execute(row[1].ST_SRID()).scalar() assert srid == 4326 assert row[1] == from_shape(Point(1, 1), srid=4326, extended=True) def test_insert_geog_poi(self): conn = self.conn 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 = session.execute(row[2].ST_AsText()).scalar() assert wkt == 'POINT(1 1)' srid = session.execute(row[2].ST_SRID()).scalar() assert srid == 4326 assert row[2] == from_shape(Point(1, 1), srid=4326) class TestSelectBindParam(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() self.conn = engine.connect() self.conn.execute(Lake.__table__.insert(), {'geom': 'SRID=4326;LINESTRING(0 0,1 1)'}) def teardown(self): self.conn.close() metadata.drop_all() def test_select_bindparam(self): 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 = self.conn.execute(s, **params) else: results = self.conn.execute(s, parameters=params) rows = results.fetchall() row = rows[0] assert isinstance(row[1], WKBElement) wkt = session.execute(row[1].ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0,1 1)' srid = session.execute(row[1].ST_SRID()).scalar() assert srid == 4326 def test_select_bindparam_WKBElement(self): 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 = self.conn.execute(s, **params) else: results = self.conn.execute(s, parameters=params) rows = results.fetchall() row = rows[0] assert isinstance(row[1], WKBElement) wkt = session.execute(row[1].ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0,1 1)' srid = session.execute(row[1].ST_SRID()).scalar() assert srid == 4326 def test_select_bindparam_WKBElement_extented(self): s = Lake.__table__.select() results = self.conn.execute(s) rows = results.fetchall() geom = rows[0][1] assert isinstance(geom, WKBElement) assert geom.extended is True s = Lake.__table__.select().where(Lake.__table__.c.geom == bindparam('geom')) params = {"geom": geom} if SQLA_LT_2: results = self.conn.execute(s, **params) else: results = self.conn.execute(s, parameters=params) rows = results.fetchall() row = rows[0] assert isinstance(row[1], WKBElement) wkt = session.execute(row[1].ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0,1 1)' srid = session.execute(row[1].ST_SRID()).scalar() assert srid == 4326 class TestInsertionORM(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() def test_WKT(self): # 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) lake = Lake('LINESTRING(0 0,1 1)') session.add(lake) with pytest.raises((DataError, IntegrityError)): session.flush() def test_WKTElement(self): lake = Lake(WKTElement('LINESTRING(0 0,1 1)', srid=4326)) session.add(lake) session.flush() session.expire(lake) assert isinstance(lake.geom, WKBElement) 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): 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) 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 @skip_postgis1(postgis_version) def test_Raster(self): 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 setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() def test_WKTElement(self): 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 wkt == raw_wkt srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 # 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] if SQLA_LT_2: assert session.execute(select(cols)).fetchall() == [(1, None)] else: 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 wkt == raw_wkt srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 def test_WKBElement(self): shape = LineString([[0, 0], [1, 1]]) lake = Lake(from_shape(shape, srid=4326)) session.add(lake) # 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 wkt == 'LINESTRING(0 0,1 1)' srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 # 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] if SQLA_LT_2: assert session.execute(select(cols)).fetchall() == [(1, None)] else: assert session.execute(select(*cols)).fetchall() == [(1, None)] # Reset geometry to initial value lake.geom = from_shape(shape, srid=4326) # 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 wkt == 'LINESTRING(0 0,1 1)' srid = session.execute(lake.geom.ST_SRID()).scalar() assert srid == 4326 def test_other_type_fail(self): 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 with pytest.raises(ProgrammingError): # Call __eq__() operator of _SpatialElement with 'other' argument equal to 1 session.flush() @skip_postgis1(postgis_version) def test_Raster(self): 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] if SQLA_LT_2: assert session.execute(select(cols)).fetchall() == [(1, None)] else: 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 TestPickle(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() def _create_one_lake(self): lake = Lake(WKTElement('LINESTRING(0 0,1 1)', srid=4326)) session.add(lake) session.flush() session.expire(lake) return lake.id def test_pickle_unpickle(self): import pickle lake_id = self._create_one_lake() lake = session.query(Lake).get(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 assert unpickled.geom.extended is True class TestCallFunction(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() session.expunge_all() metadata.drop_all() def _create_one_lake(self): lake = Lake(WKTElement('LINESTRING(0 0,1 1)', srid=4326)) session.add(lake) session.flush() session.expire(lake) return lake.id def _create_one_poi(self): p = Poi('POINT(5 45)') session.add(p) session.flush() session.expire(p) return p.id def test_ST_GeometryType(self): lake_id = self._create_one_lake() s = select([func.ST_GeometryType(Lake.__table__.c.geom)]) r1 = session.execute(s).scalar() assert r1 == 'ST_LineString' lake = session.query(Lake).get(lake_id) r2 = session.execute(lake.geom.ST_GeometryType()).scalar() assert r2 == 'ST_LineString' r3 = session.query(Lake.geom.ST_GeometryType()).scalar() assert r3 == 'ST_LineString' r4 = session.query(Lake).filter( Lake.geom.ST_GeometryType() == 'ST_LineString').one() assert isinstance(r4, Lake) assert r4.id == lake_id def test_ST_Buffer(self): lake_id = self._create_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 def test_ST_Dump(self): lake_id = self._create_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): lake_id = self._create_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): self._create_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): poi_id = self._create_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(self): lake_id = self._create_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 with function inside s1_func = select([func.ST_AsGeoJSON(func.ST_MakeValid(Lake.__table__.c.geom))]) r1_func = session.execute(s1_func).scalar() assert loads(r1_func) == { "type": "LineString", "coordinates": [[0, 0], [1, 1]] } @skip_postgis1(postgis_version) @skip_postgis2(postgis_version) def test_ST_AsGeoJson_feature(self): self._create_one_lake() # 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} } @skip_case_insensitivity() def test_comparator_case_insensitivity(self): lake_id = self._create_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.execute(lake.geom.st_buffer(2)).scalar() assert isinstance(r3, WKBElement) r4 = session.execute(lake.geom.St_BuFfEr(2)).scalar() assert isinstance(r4, WKBElement) r5 = session.query(Lake.geom.ST_Buffer(2)).scalar() assert isinstance(r5, WKBElement) r6 = session.query(Lake.geom.st_buffer(2)).scalar() assert isinstance(r6, WKBElement) r7 = session.query(Lake.geom.St_BuFfEr(2)).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): self._create_one_lake() s = select([func.ST_UnknownFunction(Lake.__table__.c.geom, 2)]) with pytest.raises(ProgrammingError, match="ST_UnknownFunction"): session.execute(s) def test_unknown_function_element(self): lake_id = self._create_one_lake() lake = session.query(Lake).get(lake_id) s = select([func.ST_UnknownFunction(lake.geom, 2)]) with pytest.raises(ProgrammingError): # 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): lake_id = self._create_one_lake() lake = session.query(Lake).get(lake_id) with pytest.raises(AttributeError): select([lake.geom.ST_UnknownFunction(2)]) class TestReflection(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): metadata.drop_all() @skip_pg12_sa1217(postgres_major_version) def test_reflection(self): t = Table( 'lake', MetaData(), schema='gis', autoload=True, autoload_with=engine) type_ = t.c.geom.type assert isinstance(type_, Geometry) if postgis_version.startswith('1.'): assert type_.geometry_type == 'GEOMETRY' assert type_.srid == -1 else: assert type_.geometry_type == 'LINESTRING' assert type_.srid == 4326 @skip_postgis1(postgis_version) @skip_pg12_sa1217(postgres_major_version) def test_raster_reflection(self): t = Table('ocean', MetaData(), autoload=True, autoload_with=engine) type_ = t.c.rast.type assert isinstance(type_, Raster) class TestSTAsGeoJson(): InternalBase = declarative_base() class TblWSpacesAndDots(InternalBase): """ 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) strstmt = strstmt.replace("\n", "") assert strstmt == expected def test_one(self): 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_two(self): 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', ) @skip_postgis1(postgis_version) @skip_postgis2(postgis_version) def test_three(self): 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", ) @skip_postgis1(postgis_version) @skip_postgis2(postgis_version) def test_four(self): stmt = select([func.ST_AsGeoJSON(TestSTAsGeoJson.TblWSpacesAndDots, "geom")]) self._assert_stmt( stmt, 'SELECT ST_AsGeoJSON("this is.an AWFUL.name", :ST_AsGeoJSON_2) ' 'AS "ST_AsGeoJSON_1" FROM "another AWFUL.name for.schema".' '"this is.an AWFUL.name"', ) @skip_postgis1(postgis_version) @skip_postgis2(postgis_version) def test_five(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, :ST_AsGeoJSON_3) ' 'AS "ST_AsGeoJSON_1" FROM "another AWFUL.name for.schema".' '"this is.an AWFUL.name"', ) @skip_postgis1(postgis_version) 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, :ST_MakePoint_2)' ')) AS "ST_AsGeoJSON_1"', ) @skip_postgis1(postgis_version) 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, :ST_MakePoint_2)' ')) AS "ST_AsGeoJSON_1"', ) class TestSTSummaryStatsAgg(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() def test_st_summary_stats_agg(self): # 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() if parse_version(SA_VERSION) < parse_version("1.4"): # 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") ]) else: # 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) == ( "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(" "public.ocean.rast, " "%(ST_SummaryStatsAgg_1)s, %(ST_SummaryStatsAgg_2)s, %(ST_SummaryStatsAgg_3)s" ") AS stats \n" "FROM public.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.10.2/tests/test_functional_spatialite.py000066400000000000000000000316151416600000700231250ustar00rootroot00000000000000import os import re from json import loads from pkg_resources import parse_version import pytest import platform from sqlalchemy import __version__ as SA_VERSION from sqlalchemy import create_engine, MetaData, Column, Integer, bindparam from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.event import listen from sqlalchemy.exc import IntegrityError from sqlalchemy.sql import select, func from sqlalchemy.types import TypeDecorator from geoalchemy2 import Geometry from geoalchemy2.elements import WKTElement, WKBElement from geoalchemy2.shape import from_shape, to_shape from shapely.geometry import LineString if 'SPATIALITE_LIBRARY_PATH' not in os.environ: pytest.skip('SPATIALITE_LIBRARY_PATH is not defined, skip SpatiaLite tests', allow_module_level=True) if platform.python_implementation().lower() == 'pypy': pytest.skip('skip SpatiaLite tests on PyPy', allow_module_level=True) def load_spatialite(dbapi_conn, connection_record): dbapi_conn.enable_load_extension(True) dbapi_conn.load_extension(os.environ['SPATIALITE_LIBRARY_PATH']) dbapi_conn.enable_load_extension(False) engine = create_engine( os.environ.get('SPATIALITE_DB_PATH', 'sqlite:///spatialdb'), echo=False) listen(engine, 'connect', load_spatialite) metadata = MetaData(engine) Base = declarative_base(metadata=metadata) class Lake(Base): __tablename__ = 'lake' id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type='LINESTRING', srid=4326, management=True)) def __init__(self, geom): self.geom = geom 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 overrided 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) class LocalPoint(Base): __tablename__ = "local_point" id = Column(Integer, primary_key=True) geom = Column( TransformedGeometry( db_srid=2154, app_srid=4326, geometry_type="POINT", management=True)) session = sessionmaker(bind=engine)() session.execute('SELECT InitSpatialMetaData()') class TestInsertionCore(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() self.conn = engine.connect() def teardown(self): self.conn.close() metadata.drop_all() def test_insert(self): conn = self.conn # Issue two 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': from_shape(LineString([[0, 0], [3, 3]]), srid=4326)}, {'geom': None} ]) results = conn.execute(Lake.__table__.select()) rows = results.fetchall() row = rows[0] assert isinstance(row[1], WKBElement) wkt = session.execute(row[1].ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0, 1 1)' srid = session.execute(row[1].ST_SRID()).scalar() assert srid == 4326 row = rows[1] assert isinstance(row[1], WKBElement) wkt = session.execute(row[1].ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0, 2 2)' srid = session.execute(row[1].ST_SRID()).scalar() assert srid == 4326 row = rows[2] assert isinstance(row[1], WKBElement) wkt = session.execute(row[1].ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0, 3 3)' srid = session.execute(row[1].ST_SRID()).scalar() assert srid == 4326 assert rows[3] == (4, None) class TestInsertionORM(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() def test_WKT(self): lake = Lake('LINESTRING(0 0,1 1)') session.add(lake) with pytest.raises(IntegrityError): session.flush() def test_WKTElement(self): 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) == '0102000020E6100000020000000000000000000000000000000000000000000' \ '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): 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) == '0102000020E6100000020000000000000000000000000000000000000000000' \ '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_transform(self): # Create new point instance p = LocalPoint() p.geom = "SRID=4326;POINT(5 45)" # Insert 2D geometry into 3D column # 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 = "SELECT id, ST_AsText(geom) AS geom FROM local_point;" res_q = session.execute(q).fetchone() assert res_q.id == 1 x, y = re.match(r"POINT\((\d+\.\d*) (\d+\.\d*)\)", res_q.geom).groups() assert round(float(x), 3) == 857581.899 assert round(float(y), 3) == 6435414.748 class TestShapely(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() def test_to_shape(self): 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, str) 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, str) assert lake.geom.srid == 4326 class TestCallFunction(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() def _create_one_lake(self): lake = Lake(WKTElement('LINESTRING(0 0,1 1)', srid=4326)) session.add(lake) session.flush() return lake.id def test_ST_GeometryType(self): lake_id = self._create_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_Buffer(self): lake_id = self._create_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) 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 def test_ST_GeoJSON(self): lake_id = self._create_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='Spatialite does not support the feature version of AsGeoJson() yet') def test_ST_GeoJSON_feature(self): 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): lake_id = self._create_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) r2 = session.execute(lake.geom.ST_Buffer(2)).scalar() assert isinstance(r2, WKBElement) r3 = session.execute(lake.geom.st_buffer(2)).scalar() assert isinstance(r3, WKBElement) r4 = session.execute(lake.geom.St_BuFfEr(2)).scalar() assert isinstance(r4, WKBElement) r5 = session.query(Lake.geom.ST_Buffer(2)).scalar() assert isinstance(r5, WKBElement) r6 = session.query(Lake.geom.st_buffer(2)).scalar() assert isinstance(r6, WKBElement) r7 = session.query(Lake.geom.St_BuFfEr(2)).scalar() assert isinstance(r7, WKBElement) assert ( r1.data == r2.data == r3.data == r4.data == r5.data == r6.data == r7.data) class TestNullable(): class NotNullableLake(Base): __tablename__ = 'NotNullablelake' id = Column(Integer, primary_key=True) geom = Column(Geometry(geometry_type='LINESTRING', srid=4326, management=True, nullable=False)) def __init__(self, geom): self.geom = geom def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() self.conn = engine.connect() def teardown(self): self.conn.close() metadata.drop_all() def test_insert(self): conn = self.conn # Insert geometries conn.execute(TestNullable.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(TestNullable.NotNullableLake.__table__.insert(), [ {'geom': None} ]) geoalchemy2-0.10.2/tests/test_functions.py000066400000000000000000000410511416600000700205470ustar00rootroot00000000000000import re from sqlalchemy.sql import func # # Importing geoalchemy2 actually registers the GeoAlchemy generic # functions in SQLAlchemy's function registry. # import geoalchemy2.functions # NOQA from geoalchemy2.types import Raster # NOQA 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)) # # 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_geometry_returning_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_geometry_returning_func('ST_MakeBox2D') def test_ST_3DMakeBox(): _test_geometry_returning_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.10.2/tests/test_shape.py000066400000000000000000000050601416600000700176370ustar00rootroot00000000000000from geoalchemy2.elements import WKBElement, WKTElement from geoalchemy2.shape import from_shape, to_shape import shapely.wkb from shapely.geometry import Point 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.10.2/tests/test_types.py000066400000000000000000000210551416600000700177050ustar00rootroot00000000000000import pytest import re from sqlalchemy import Table, MetaData, Column from sqlalchemy.sql import select, insert, func, text from geoalchemy2.types import Geometry, Geography, Raster from geoalchemy2.exc import ArgumentError 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_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_management_zm(self): with pytest.raises(ArgumentError): Geometry(geometry_type='POINTZM', management=True) def test_check_ctor_args_management_z(self): with pytest.raises(ArgumentError): Geometry(geometry_type='POINTZ', dimension=2, management=True) def test_check_ctor_args_management_m(self): with pytest.raises(ArgumentError): Geometry(geometry_type='POINTM', dimension=2, management=True) def test_check_ctor_args_incompatible_arguments(self): with pytest.raises(ArgumentError): Geometry(geometry_type=None, management=True) 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_ignored(self): with pytest.warns(UserWarning): Geometry(management=False, use_typmod=True) def test_check_ctor_args_use_typmod_nullable(self): with pytest.raises(ArgumentError): 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 from sqlalchemy.sql import select 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"')