pax_global_header00006660000000000000000000000064135000167660014516gustar00rootroot0000000000000052 comment=334dcd50c2abca6330ec589060d4690f6e742954 geoalchemy2-0.6.3/000077500000000000000000000000001350001676600137235ustar00rootroot00000000000000geoalchemy2-0.6.3/.coveragerc000066400000000000000000000000311350001676600160360ustar00rootroot00000000000000[run] source=geoalchemy2 geoalchemy2-0.6.3/.flake8000066400000000000000000000000351350001676600150740ustar00rootroot00000000000000[flake8] max-line-length=100 geoalchemy2-0.6.3/.gitignore000066400000000000000000000010251350001676600157110ustar00rootroot00000000000000# 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/ geoalchemy2-0.6.3/.travis.yml000066400000000000000000000044131350001676600160360ustar00rootroot00000000000000language: python sudo: true dist: trusty addons: postgresql: 9.3 apt: packages: - postgresql-9.3-postgis-2.3 - python2.7-dev - python3-dev - autotools-dev - libexpat1-dev - libfreexl-dev - libgeos-dev - libproj-dev - libreadline-dev - libsqlite3-dev - libxml2-dev - zlib1g-dev notifications: email: on_failure: change services: - postgresql cache: directories: - libspatialite-4.3.0a matrix: include: - python: 2.7 env: - TOX_ENV=py27sqla11 - python: 3.4 env: - TOX_ENV=py34sqla11 - python: 3.5 env: - TOX_ENV=py35sqla11 - python: pypy env: - TOX_ENV=pypysqla11 env: global: - SPATIALITE_LIBRARY_PATH="/usr/local/lib/mod_spatialite.so" install: # Install Spatialite - ./install-spatialite.sh # Install tox - pip install tox # Install flake8 style checker - pip install -r requirements.txt before_script: # Create database user "gis" - psql -c "CREATE ROLE gis PASSWORD 'gis' SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;" -U postgres # Create database "gis" - psql -c 'CREATE DATABASE gis;' -U postgres # Create schema "gis" into database "gis" - psql -d gis -c 'CREATE SCHEMA gis;' -U postgres # Grant CREATE permission on database "gis" to role "gis" - psql -c 'GRANT CREATE ON DATABASE gis TO "gis";' -U postgres # Grant USAGE and CREATE permission on schema "gis" to role "gis" - psql -d gis -c 'GRANT USAGE,CREATE ON SCHEMA gis TO "gis";' -U postgres # Add PostGIS extension to "gis" database - psql -d gis -c 'CREATE EXTENSION postgis;' -U postgres script: # Run the library through flake8 - flake8 geoalchemy2 tests --ignore=E711 --ignore=W503 # Run the unit test suite - tox -e $TOX_ENV --sitepackages -- -v --cov geoalchemy2 --cov-report term-missing after_script: # Report coverage results to coveralls.io - pip install coveralls - coveralls deploy: provider: pypi distributions: "sdist bdist_wheel" user: erilem password: secure: eYqYIWlsCp/awFekONU9fRKjYeleO49rzf2qWNYG3HxWvp8+1mgjLubnMTIARHiKbB6/iF3PEfGQd7+ICIi6dxWQFxWaBu7HOFaOu0wOXweNbrwhDqKrnJRJ3/2CPRTlYARr7uJhXrVTQVOIC//mEtUX7atF5EVs8fW8BM3/iT0= on: tags: true repo: geoalchemy/geoalchemy2 geoalchemy2-0.6.3/CHANGES.txt000066400000000000000000000064351350001676600155440ustar00rootroot00000000000000GeoAlchemy 2 Changelog ====================== 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-forcity @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.6.3/COPYING.rst000066400000000000000000000020401350001676600155610ustar00rootroot00000000000000Copyright (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.6.3/MANIFEST.in000066400000000000000000000000341350001676600154560ustar00rootroot00000000000000include *.rst include *.txt geoalchemy2-0.6.3/README.rst000066400000000000000000000013121350001676600154070ustar00rootroot00000000000000============ 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.6.3/RELEASE.rst000066400000000000000000000012251350001676600155350ustar00rootroot00000000000000Release ------- This file provides the steps for releasing a new version of GeoAlchemy 2. Add a new section to CHANGES.txt, change the version number in ``setup.py`` and ``docs/conf.py``, then create a PR with that. Proceed when the PR is merged. Make sure Travis is all green: https://travis-ci.org/geoalchemy/geoalchemy2. Create Git tag and push it:: $ git tag -a x.y -m 'version x.y' $ git push origin x.y Go to http://readthedocs.org/dashboard/geoalchemy-2/edit/ and set "Default version" to x.y. Note that there's no need to manually upload the package to PyPI. This is done automatically by Travis when the release tag is pushed to GitHub. geoalchemy2-0.6.3/TEST.rst000066400000000000000000000031111350001676600152300ustar00rootroot00000000000000===== 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 python2.7-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 -U postgres -c "CREATE EXTENSION postgis;" 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.6.3/doc/000077500000000000000000000000001350001676600144705ustar00rootroot00000000000000geoalchemy2-0.6.3/doc/Makefile000066400000000000000000000110021350001676600161220ustar00rootroot00000000000000# 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.6.3/doc/_static/000077500000000000000000000000001350001676600161165ustar00rootroot00000000000000geoalchemy2-0.6.3/doc/_static/geoalchemy.png000066400000000000000000000511441350001676600207460ustar00rootroot00000000000000PNG  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.6.3/doc/_static/geoalchemy.svg000066400000000000000000000415721350001676600207650ustar00rootroot00000000000000 image/svg+xml geoalchemy2-0.6.3/doc/_static/geoalchemy_small.png000066400000000000000000000417211350001676600221360ustar00rootroot00000000000000PNG  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.6.3/doc/_static/geoalchemy_small.svg000066400000000000000000000507371350001676600221600ustar00rootroot00000000000000 image/svg+xml geoalchemy2-0.6.3/doc/_templates/000077500000000000000000000000001350001676600166255ustar00rootroot00000000000000geoalchemy2-0.6.3/doc/_templates/sidebar-about.html000066400000000000000000000001671350001676600222400ustar00rootroot00000000000000

About

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

geoalchemy2-0.6.3/doc/_templates/sidebar-links.html000066400000000000000000000004431350001676600222430ustar00rootroot00000000000000

Useful Links

geoalchemy2-0.6.3/doc/_templates/sidebar-logo.html000066400000000000000000000002241350001676600220600ustar00rootroot00000000000000 geoalchemy2-0.6.3/doc/_themes/000077500000000000000000000000001350001676600161145ustar00rootroot00000000000000geoalchemy2-0.6.3/doc/_themes/LICENSE000066400000000000000000000033751350001676600171310ustar00rootroot00000000000000Copyright (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.6.3/doc/_themes/README000066400000000000000000000021051350001676600167720ustar00rootroot00000000000000Flask 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.6.3/doc/_themes/flask/000077500000000000000000000000001350001676600172145ustar00rootroot00000000000000geoalchemy2-0.6.3/doc/_themes/flask/layout.html000066400000000000000000000016511350001676600214220ustar00rootroot00000000000000{%- 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.6.3/doc/_themes/flask/relations.html000066400000000000000000000011161350001676600221010ustar00rootroot00000000000000

Related Topics

geoalchemy2-0.6.3/doc/_themes/flask/static/000077500000000000000000000000001350001676600205035ustar00rootroot00000000000000geoalchemy2-0.6.3/doc/_themes/flask/static/flasky.css_t000066400000000000000000000150031350001676600230300ustar00rootroot00000000000000/* * 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.6.3/doc/_themes/flask/static/small_flask.css000066400000000000000000000017201350001676600235050ustar00rootroot00000000000000/* * 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.6.3/doc/_themes/flask/theme.conf000066400000000000000000000002621350001676600211650ustar00rootroot00000000000000[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.6.3/doc/conf.py000066400000000000000000000170601350001676600157730ustar00rootroot00000000000000# -*- 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'] # 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. # # The short X.Y.Z version. version = '0.6.3' # The full version, including alpha/beta/rc tags. release = '0.6.3' # 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) geoalchemy2-0.6.3/doc/core_tutorial.rst000066400000000000000000000242001350001676600200730ustar00rootroot00000000000000.. _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. 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 Further Reference ----------------- * Spatial Functions Reference: :ref:`spatial_functions` * Spatial Operators Reference: :ref:`spatial_operators` * Elements Reference: :ref:`elements` geoalchemy2-0.6.3/doc/elements.rst000066400000000000000000000005031350001676600170340ustar00rootroot00000000000000.. _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.6.3/doc/index.rst000066400000000000000000000071161350001676600163360ustar00rootroot00000000000000GeoAlchemy 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. 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 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.6.3/doc/make.bat000066400000000000000000000106511350001676600161000ustar00rootroot00000000000000@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.6.3/doc/migrate.rst000066400000000000000000000051401350001676600166520ustar00rootroot00000000000000.. _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.6.3/doc/orm_tutorial.rst000066400000000000000000000315461350001676600177530ustar00rootroot00000000000000.. _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 Further Reference ----------------- * Spatial Functions Reference: :ref:`spatial_functions` * Spatial Operators Reference: :ref:`spatial_operators` * Elements Reference: :ref:`elements` geoalchemy2-0.6.3/doc/shape.rst000066400000000000000000000002421350001676600163200ustar00rootroot00000000000000.. _shape: Shapely Integration =================== .. automodule:: geoalchemy2.shape :members: :private-members: :undoc-members: :show-inheritance: geoalchemy2-0.6.3/doc/spatial_functions.rst000066400000000000000000000001601350001676600207440ustar00rootroot00000000000000.. _spatial_functions: Spatial Functions ================= .. automodule:: geoalchemy2.functions :members: geoalchemy2-0.6.3/doc/spatial_operators.rst000066400000000000000000000002341350001676600207540ustar00rootroot00000000000000.. _spatial_operators: Spatial Operators ================= .. automodule:: geoalchemy2.comparator :members: :special-members: :show-inheritance: geoalchemy2-0.6.3/doc/spatialite_tutorial.rst000066400000000000000000000150361350001676600213110ustar00rootroot00000000000000.. _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.6.3/doc/types.rst000066400000000000000000000001631350001676600163660ustar00rootroot00000000000000.. _types: Types ===== .. automodule:: geoalchemy2.types :members: :private-members: :show-inheritance: geoalchemy2-0.6.3/geoalchemy2/000077500000000000000000000000001350001676600161225ustar00rootroot00000000000000geoalchemy2-0.6.3/geoalchemy2/__init__.py000066400000000000000000000134551350001676600202430ustar00rootroot00000000000000from .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 from sqlalchemy import Table, event from sqlalchemy.sql import select, func, expression 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 isinstance(c.type, Geometry) 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([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 isinstance(c.type, Geometry) 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) stmt = select([func.AddGeometryColumn(*args)]) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) # Add spatial indices for the Geometry and Geography columns if isinstance(c.type, (Geometry, Geography)) and \ c.type.spatial_index is True: if bind.dialect.name == 'sqlite': stmt = select([func.CreateSpatialIndex(table.name, c.name)]) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) elif bind.dialect.name == 'postgresql': if table.schema: bind.execute('CREATE INDEX "idx_%s_%s" ON "%s"."%s" ' 'USING GIST ("%s")' % (table.name, c.name, table.schema, table.name, c.name)) else: bind.execute('CREATE INDEX "idx_%s_%s" ON "%s" ' 'USING GIST ("%s")' % (table.name, c.name, table.name, c.name)) else: raise ArgumentError('dialect {} is not supported'.format(bind.dialect.name)) # 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: bind.execute('CREATE INDEX "idx_%s_%s" ON "%s"."%s" ' 'USING GIST (ST_ConvexHull("%s"))' % (table.name, c.name, table.schema, table.name, c.name)) else: bind.execute('CREATE INDEX "idx_%s_%s" ON "%s" ' 'USING GIST (ST_ConvexHull("%s"))' % (table.name, c.name, table.name, c.name)) elif event == 'after-drop': # Restore original column list including managed Geometry columns table.columns = table.info.pop('_saved_columns') _setup_ddl_event_listeners() geoalchemy2-0.6.3/geoalchemy2/comparator.py000066400000000000000000000160071350001676600206470ustar00rootroot00000000000000""" 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('&&') 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) geoalchemy2-0.6.3/geoalchemy2/compat.py000066400000000000000000000010061350001676600177540ustar00rootroot00000000000000""" Python 2 and 3 compatibility: - Py3k `memoryview()` made an alias for Py2k `buffer()` - Py3k `bytes()` made an alias for Py2k `str()` """ try: import __builtin__ as builtins except ImportError: import builtins import sys if sys.version_info[0] == 2: PY3 = False buffer = getattr(builtins, 'buffer') bytes = str str = getattr(builtins, 'unicode') else: PY3 = True # Python 2.6 flake8 workaround buffer = getattr(builtins, 'memoryview') bytes = bytes str = str geoalchemy2-0.6.3/geoalchemy2/elements.py000066400000000000000000000214721350001676600203160ustar00rootroot00000000000000import 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 .compat import PY3, str as str_ from .exc import ArgumentError class _SpatialElement(functions.Function): """ 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 if self.extended: args = [self.geom_from_extended_version, self.data] else: args = [self.geom_from, self.data, self.srid] functions.Function.__init__(self, *args) 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 __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) # # 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, 'name': self.name, } return state def __setstate__(self, state): self.__dict__.update(state) self.data = self._data_from_desc(state['data']) args = [self.name, self.data] if not self.extended: args.append(self.srid) # we need to call Function.__init__ to properly initialize SQLAlchemy's # internal states functions.Function.__init__(self, *args) @staticmethod def _data_from_desc(desc): raise NotImplementedError() @compiles(_SpatialElement, 'sqlite') def compile_spatialelement(element, compiler, **kw): return "{}({})".format(element.name.lstrip("ST_"), compiler.process(element.clauses, **kw)) 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] if not PY3: header = bytearray(header) 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 = binascii.hexlify(self.data) if PY3: # hexlify returns a bytes object on py3 desc = str(desc, encoding="utf-8") return desc @staticmethod def _data_from_desc(desc): if PY3: desc = desc.encode(encoding="utf-8") return binascii.unhexlify(desc) class RasterElement(FunctionElement): """ 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. """ name = 'raster' def __init__(self, data): self.data = data FunctionElement.__init__(self, self.data) def __str__(self): return self.desc # pragma: no cover def __repr__(self): return "<%s at 0x%x; %r>" % \ (self.__class__.__name__, id(self), self.desc) # pragma: no cover @property def desc(self): """ This element's description string. """ desc = binascii.hexlify(self.data) if PY3: # hexlify returns a bytes object on py3 desc = str(desc, encoding="utf-8") if len(desc) < 30: return desc return desc[:30] + '...' # pragma: no cover def __getattr__(self, name): # # This is how things like ocean.rast.ST_Value(...) creates # SQL expressions of this form: # # ST_Value(:ST_GeomFromWKB_1), :param_1) # # 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_ = functions._FunctionGenerator(expr=self) return getattr(func_, name) @compiles(RasterElement) def compile_RasterElement(element, compiler, **kw): """ This function makes sure the :class:`geoalchemy2.elements.RasterElement` contents are correctly casted to the ``raster`` type before using it. The other elements in this module don't need such a function because they are derived from :class:`functions.Function`. For the :class:`geoalchemy2.elements.RasterElement` class however it would not be of any use to have it compile to ``raster('...')`` so it is compiled to ``'...'::raster`` by this function. """ return "%s::raster" % compiler.process(element.clauses) class CompositeElement(FunctionElement): """ Instances of this class wrap a Postgres composite type. """ 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.6.3/geoalchemy2/exc.py000066400000000000000000000003601350001676600172520ustar00rootroot00000000000000""" 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.6.3/geoalchemy2/functions.py000066400000000000000000000625771350001676600205250ustar00rootroot00000000000000""" 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 --------- """ from sqlalchemy.sql import functions from sqlalchemy.ext.compiler import compiles from . import types class GenericFunction(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) if expr is not None: args = (expr,) + args functions.GenericFunction.__init__(self, *args, **kwargs) # Functions are classified as in the PostGIS doc. # _FUNCTIONS = [ # # Geometry Constructors # ('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_Box2dFromGeoHash', types.Geometry, 'Return a BOX2D from a GeoHash string.'), ('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_GeogFromWKB', types.Geography, 'Creates a geography instance from a Well-Known Binary geometry ' 'representation (WKB) or extended Well Known Binary (EWKB).'), ('ST_GeomFromTWKB', types.Geometry, 'Creates a geometry instance from a TWKB ("Tiny Well-Known Binary") ' 'geometry representation.'), ('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_GeomFromEWKB', types.Geometry, 'Return a specified ST_Geometry value from Extended Well-Known Binary ' 'representation (EWKB).'), ('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_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_GMLToSQL', types.Geometry, 'Return a specified ST_Geometry value from GML representation. This is an' ' alias name for ST_GeomFromGML'), ('ST_GeomFromText', types.Geometry, 'Return a specified ST_Geometry value from Well-Known Text representation' ' (WKT).'), ('ST_GeomFromWKB', types.Geometry, 'Creates a geometry instance from a Well-Known Binary geometry ' 'representation (WKB) and optional SRID.'), ('ST_LineFromEncodedPolyline', types.Geometry, 'Creates a LineString from an Encoded Polyline.'), ('ST_LineFromMultiPoint', types.Geometry, 'Creates a LineString from a MultiPoint geometry.'), ('ST_LineFromText', types.Geometry, 'Makes a Geometry from WKT representation with the given SRID. If SRID is' ' not given, it defaults to 0.'), ('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_MakeBox2D', types.Geometry, 'Creates a BOX2D defined by the given point geometries.'), ('ST_3DMakeBox', types.Geometry, 'Creates a BOX3D defined by the given 3d point geometries.'), ('ST_MakeLine', types.Geometry, 'Creates a Linestring from point, multipoint, or line geometries.'), ('ST_MakeEnvelope', types.Geometry, 'Creates a rectangular Polygon formed from the given minimums and ' 'maximums. Input values must be in SRS specified by the SRID.'), ('ST_MakePolygon', types.Geometry, 'Creates a Polygon formed by the given shell. Input geometries must be ' 'closed LINESTRINGS.'), ('ST_MakePoint', types.Geometry, 'Creates a 2D, 3DZ or 4D point geometry.'), ('ST_MakePointM', types.Geometry, 'Creates a point geometry with an x y and m coordinate.'), ('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_Point', types.Geometry, 'Returns an ST_Point with the given coordinate values. OGC alias for ' 'ST_MakePoint.'), ('ST_PointFromGeoHash', types.Geometry, 'Return a point from a GeoHash string.'), ('ST_PointFromText', types.Geometry, 'Makes a point Geometry from WKT with the given SRID. If SRID is not ' 'given, it defaults to unknown.'), ('ST_PointFromWKB', types.Geometry, 'Makes a geometry from WKB with the given SRID'), ('ST_Polygon', types.Geometry, 'Returns a polygon built from the specified linestring and SRID.'), ('ST_PolygonFromText', types.Geometry, 'Makes a Geometry from WKT with the given SRID. If SRID is not given, it ' 'defaults to 0.'), ('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_WKTToSQL', types.Geometry, 'Return a specified ST_Geometry value from Well-Known Text representation' ' (WKT). This is an alias name for ST_GeomFromText'), # # Geometry Accessors # ('ST_Boundary', types.Geometry, 'Returns the closure of the combinatorial boundary of this Geometry.'), ('ST_BoundingDiagonal', types.Geometry, 'Returns the diagonal of the supplied geometry\'s bounding box.'), ('ST_EndPoint', types.Geometry, 'Returns the last point of a ``LINESTRING`` or ``CIRCULARLINESTRING`` ' 'geometry as a ``POINT``.'), ('ST_Envelope', types.Geometry, 'Returns a geometry representing the double precision (float8) bounding' 'box of the supplied geometry.'), ('ST_GeometryN', types.Geometry, 'Return the 1-based Nth geometry if the geometry is a ' '``GEOMETRYCOLLECTION``, ``(MULTI)POINT``, ``(MULTI)LINESTRING``, ' '``MULTICURVE`` or ``(MULTI)POLYGON``, ``POLYHEDRALSURFACE`` Otherwise, ' 'return ``None``.'), ('ST_GeometryType', None, 'Return the geometry type of the ``ST_Geometry`` value.'), ('ST_InteriorRingN', types.Geometry, 'Return the Nth interior linestring ring of the polygon geometry. Return ' '``NULL`` if the geometry is not a polygon or the given N is out of ' 'range.'), ('ST_IsValid', None, 'Returns ``True`` if the ``ST_Geometry`` is well formed.'), ('ST_NPoints', None, 'Return the number of points (vertices) in a geometry.'), ('ST_PatchN', types.Geometry, 'Return the 1-based Nth geometry (face) if the geometry is a ' '``POLYHEDRALSURFACE``, ``POLYHEDRALSURFACEM``. Otherwise, return ' '``NULL``.'), ('ST_PointN', types.Geometry, 'Return the Nth point in the first LineString or circular LineString in ' 'the geometry. Negative values are counted backwards from the end of the ' 'LineString. Returns ``NULL`` if there is no linestring in the geometry.' ), ('ST_Points', types.Geometry, 'Returns a MultiPoint containing all of the coordinates of a geometry.'), ('ST_SRID', None, 'Returns the spatial reference identifier for the ``ST_Geometry`` as ' 'defined in ``spatial_ref_sys`` table.'), ('ST_StartPoint', types.Geometry, 'Returns the first point of a ``LINESTRING`` geometry as a ``POINT``.'), ('ST_X', None, 'Return the X coordinate of the point, or ``None`` if not available. ' 'Input must be a point.'), ('ST_Y', None, 'Return the Y coordinate of the point, or ``None`` if not available. ' 'Input must be a point.'), ('ST_Z', None, 'Return the Z coordinate of the point, or ``None`` if not available. ' 'Input must be a point.'), # # Geometry Editors # ('ST_AddPoint', types.Geometry, 'Add a point to a LineString.'), ('ST_Affine', types.Geometry, 'Apply a 3d affine transformation to a geometry.'), ('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_ExteriorRing', types.Geometry, 'Returns a line string representing the exterior ring of the ``POLYGON`` ' 'geometry. Return ``NULL`` if the geometry is not a polygon. Will not ' 'work with ``MULTIPOLYGON``.'), ('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_Force3DM', types.Geometry, ('Force the geometries into XYM mode.', 'ST_Force_3DM')), ('ST_Force3DZ', types.Geometry, ('Force the geometries into XYZ mode.', 'ST_Force_3DZ')), ('ST_Force4D', types.Geometry, ('Force the geometries into XYZM mode.', 'ST_Force_4D')), ('ST_ForceCollection', types.Geometry, ('Convert the geometry into a ``GEOMETRYCOLLECTION``.', 'ST_Force_Collection')), ('ST_ForceCurve', types.Geometry, 'Upcast a geometry into its curved type, if applicable.'), ('ST_ForcePolygonCCW', types.Geometry, 'Orients all exterior rings counter-clockwise and all interior rings ' 'clockwise.'), ('ST_ForcePolygonCW', types.Geometry, 'Orients all exterior rings clockwise and all interior rings ' 'counter-clockwise.'), ('ST_ForceRHR', types.Geometry, 'Force the orientation of the vertices in a polygon to follow the ' 'Right-Hand-Rule.'), ('ST_ForceSFS', types.Geometry, 'Force the geometries to use SFS 1.1 geometry types only.'), ('ST_M', None, 'Return the M coordinate of the point, or ``NULL`` if not available. ' 'Input must be a point.'), ('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_Rotate', types.Geometry, 'Rotate a geometry rotRadians counter-clockwise about an origin.'), ('ST_RotateX', types.Geometry, 'Rotate a geometry rotRadians about the X axis.'), ('ST_RotateY', types.Geometry, 'Rotate a geometry rotRadians about the Y axis.'), ('ST_RotateZ', types.Geometry, 'Rotate a geometry rotRadians about the Z axis.'), ('ST_Scale', types.Geometry, 'Scale a geometry by given factors.'), ('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_SetSRID', types.Geometry, 'Set the SRID on a geometry to a particular integer value.'), ('ST_Snap', types.Geometry, 'Snap segments and vertices of input geometry to vertices of a reference ' 'geometry.'), ('ST_SnapToGrid', types.Geometry, 'Snap all points of the input geometry to a regular grid.'), ('ST_Transform', types.Geometry, 'Return a new geometry with its coordinates transformed to the SRID ' 'referenced by the integer parameter.'), ('ST_Translate', types.Geometry, 'Translate a geometry by given offsets.'), ('ST_TransScale', types.Geometry, 'Translate a geometry by given factors and offsets.'), # # Geometry Outputs # ('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/' 'geography with SRID meta data.'), ('ST_AsTWKB', None, 'Returns the geometry as TWKB, aka "Tiny Well-Known Binary"'), ('ST_AsGeoJSON', None, 'Return the geometry as a GeoJSON element.'), ('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 precision=15'), ('ST_AsSVG', None, 'Returns a Geometry in SVG path data given a geometry or geography ' 'object.'), ('ST_AsText', None, 'Return the Well-Known Text (WKT) representation of the geometry/' 'geography without SRID metadata.'), ('ST_AsEWKT', None, 'Return the Well-Known Text (WKT) representation of the geometry/' 'geography with SRID metadata.'), # # Spatial Relationships and Measurements # ('ST_Area', None, 'Returns the area of the surface if it is a polygon or multi-polygon. ' 'For ``geometry`` type area is in SRID units. For ``geography`` area is ' 'in square meters.'), ('ST_Azimuth', None, 'Returns the angle in radians from the horizontal of the ' 'vector defined by pointA and pointB. Angle is computed clockwise from ' 'down-to-up: on the clock: 12=0; 3=PI/2; 6=PI; 9=3PI/2.'), ('ST_Centroid', types.Geometry, 'Returns the geometric center of a geometry.'), ('ST_Contains', None, '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.'), ('ST_ContainsProperly', None, '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.'), ('ST_Covers', None, 'Returns ``True`` if no point in Geometry B is outside Geometry A'), ('ST_CoveredBy', None, 'Returns ``True`` if no point in Geometry/Geography A is outside Geometry' '/Geography B'), ('ST_Crosses', None, 'Returns ``True`` if the supplied geometries have some, but not all, ' 'interior points in common.'), ('ST_Disjoint', None, 'Returns ``True`` if the Geometries do not "spatially intersect" - if ' 'they do not share any space together.'), ('ST_Distance', None, 'For geometry type Returns the 2-dimensional cartesian minimum distance ' '(based on spatial ref) between two geometries in projected units. For ' 'geography type defaults to return spheroidal minimum distance between ' 'two geographies in meters.'), ('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.'), ('ST_DFullyWithin', None, 'Returns ``True`` if all of the geometries are within the specified ' 'distance of one another'), ('ST_DWithin', None, '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.'), ('ST_Equals', None, 'Returns ``True`` if the given geometries represent the same geometry. ' 'Directionality is ignored.'), ('ST_Intersects', None, '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)'), ('ST_Length', None, 'Returns the 2d length of the geometry if it is a linestring or ' 'multilinestring. geometry are in units of spatial reference and ' 'geography are in meters (default spheroid)'), ('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_OrderingEquals', None, 'Returns ``True`` if the given geometries represent the same geometry ' 'and points are in the same directional order.'), ('ST_Overlaps', None, 'Returns ``True`` if the Geometries share space, are of the same ' 'dimension, but are not completely contained by each other.'), ('ST_Perimeter', None, 'Return the length measurement of the boundary of an ST_Surface or ' 'ST_MultiSurface geometry or geography. (Polygon, Multipolygon). ' 'geometry measurement is in units of spatial reference and geography is ' 'in meters.'), ('ST_Project', types.Geography, 'Returns a ``POINT`` projected from a start point using a distance in ' 'meters and bearing (azimuth) in radians.'), ('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_Touches', None, 'Returns ``True`` if the geometries have at least one point in common, ' 'but their interiors do not intersect.'), ('ST_Within', None, 'Returns ``True`` if the geometry A is completely inside geometry B'), # # Geometry Processing # ('ST_Buffer', types.Geometry, 'For geometry: Returns a geometry that represents all points whose ' 'distance from this Geometry is less than or equal to distance. ' 'Calculations are in the Spatial Reference System of this Geometry.\n\n' 'For geography: Uses a planar transform wrapper. Introduced in 1.5 ' 'support for different end cap and mitre settings to control shape.'), ('ST_Difference', types.Geometry, 'Returns a geometry that represents that part of geometry A that does ' 'not intersect with geometry B.'), ('ST_Dump', types.GeometryDump, 'Returns a set of geometry_dump (geom,path) rows, that make up a ' 'geometry g1.'), ('ST_DumpPoints', types.GeometryDump, 'Returns a set of geometry_dump (geom,path) rows of all points that ' 'make up a geometry.'), ('ST_Intersection', types.Geometry, 'Returns a geometry that represents the shared portion of geomA and ' 'geomB. The geography implementation does a transform to geometry to do ' 'the intersection and then transform back to WGS84.'), ('ST_LineMerge', types.Geometry, 'Returns a (set of) LineString(s) formed by sewing together the ' 'constituent line work of a MULTILINESTRING.'), ('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. This only works with ' 'LINESTRINGs. To use with contiguous MULTILINESTRINGs use in ' 'conjunction with ST_LineMerge.' '' 'If \'start\' and \'end\' have the same value this is equivalent ' 'to ST_LineInterpolatePoint.'), ('ST_Simplify', types.Geometry, 'Returns a "simplified" version of the given geometry using the ' 'Douglas-Peucker algorithm.'), ('ST_Union', types.Geometry, 'Returns a geometry that represents the point set union of the ' 'Geometries.'), # # Raster Constructors # ('ST_AsRaster', types.Raster, ('Converts a PostGIS geometry to a PostGIS raster.', 'RT_ST_AsRaster')), # # Raster Accessors # ('ST_Height', None, ('Returns the height of the raster in pixels.', 'RT_ST_Height')), ('ST_Width', None, ('Returns the width of the raster in pixels.', 'RT_ST_Width')), # # Raster Pixel Accessors and Setters # ('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')), ] # Iterate through _FUNCTION and create GenericFunction classes dynamically for name, type_, doc in _FUNCTIONS: attributes = {'name': name} 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", } def _compiles(cls, fn): def _compile(element, compiler, **kw): return "{}({})".format(fn, compiler.process(element.clauses, **kw)) compiles(globals()[cls], "sqlite")(_compile) for cls, fn in _SQLITE_FUNCTIONS.items(): _compiles(cls, fn) geoalchemy2-0.6.3/geoalchemy2/shape.py000066400000000000000000000026571350001676600176060ustar00rootroot00000000000000""" 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. """ import shapely.wkb import shapely.wkt from .elements import WKBElement, WKTElement from .compat import buffer, bytes, str 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): return shapely.wkt.loads(element.data) def from_shape(shape, srid=-1): """ 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. Example:: from shapely.geometry import Point wkb_element = from_shape(Point(5, 45), srid=4326) """ return WKBElement(buffer(shape.wkb), srid=srid) geoalchemy2-0.6.3/geoalchemy2/types.py000066400000000000000000000304011350001676600176360ustar00rootroot00000000000000""" 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 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``. ``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. """ def __init__(self, geometry_type='GEOMETRY', srid=-1, dimension=2, spatial_index=True, management=False, use_typmod=None): geometry_type, srid = self.check_ctor_args( geometry_type, srid, dimension, management, use_typmod) self.geometry_type = geometry_type self.srid = srid self.dimension = dimension self.spatial_index = spatial_index self.management = management self.use_typmod = use_typmod self.extended = self.as_binary == 'ST_AsEWKB' 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): return getattr(func, self.as_binary)(col, type_=self) def result_processor(self, dialect, coltype): def process(value): if value is not None: return WKBElement(value, srid=self.srid, extended=self.extended) return process def bind_expression(self, bindvalue): 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 else: return bindvalue return process @staticmethod def check_ctor_args(geometry_type, srid, dimension, management, use_typmod): 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') 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. """ 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. """ class Raster(UserDefinedType): """ 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. """ def __init__(self, spatial_index=True): self.spatial_index = spatial_index def get_col_spec(self): return 'raster' def result_processor(self, dialect, coltype): def process(value): if value is not None: return RasterElement(value) return process 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 KeyError("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``. """ # Register Geometry, Geography and Raster to SQLAlchemy's Postgres reflection # subsystem. ischema_names['geometry'] = Geometry ischema_names['geography'] = Geography ischema_names['raster'] = Raster geoalchemy2-0.6.3/install-spatialite.sh000077500000000000000000000005371350001676600200720ustar00rootroot00000000000000#!/bin/bash set -e LIBSPATIALITE="libspatialite-4.3.0a" if [[ ! -d ${LIBSPATIALITE}/src/.libs ]]; then wget http://www.gaia-gis.it/gaia-sins/${LIBSPATIALITE}.tar.gz tar xvzf ${LIBSPATIALITE}.tar.gz cd ${LIBSPATIALITE} ./configure --disable-freexl --disable-libxml2 make -j2 else cd ${LIBSPATIALITE} fi sudo make install geoalchemy2-0.6.3/requirements-rtd.txt000066400000000000000000000000431350001676600177730ustar00rootroot00000000000000# Install geoalchemy2 package -e . geoalchemy2-0.6.3/requirements.txt000066400000000000000000000002711350001676600172070ustar00rootroot00000000000000# Install geoalchemy2 package -e . # Additional requirements for running the testsuite and development pycodestyle==2.2.0 flake8==3.2.0 pytest==3.7.4 pytest-cov==2.5.1 Shapely>=1.3.0 geoalchemy2-0.6.3/setup.cfg000066400000000000000000000001361350001676600155440ustar00rootroot00000000000000[bdist_wheel] # flag indicating that the code is written on both Python 2 and 3 universal = 1 geoalchemy2-0.6.3/setup.py000066400000000000000000000022251350001676600154360ustar00rootroot00000000000000from setuptools import setup, find_packages version = '0.6.3' setup( name='GeoAlchemy2', version=version, description="Using SQLAlchemy with Spatial Databases", long_description=open('README.rst').read(), classifiers=[ "Development Status :: 3 - Alpha", "Environment :: Plugins", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "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='http://geoalchemy.org/', license='MIT', packages=find_packages(exclude=['ez_setup', 'examples', 'tests', "doc"]), include_package_data=True, zip_safe=False, install_requires=[ 'SQLAlchemy>=0.8', ], entry_points=""" # -*- Entry points: -*- """, ) geoalchemy2-0.6.3/tests/000077500000000000000000000000001350001676600150655ustar00rootroot00000000000000geoalchemy2-0.6.3/tests/__init__.py000066400000000000000000000000001350001676600171640ustar00rootroot00000000000000geoalchemy2-0.6.3/tests/test_comparator.py000066400000000000000000000126641350001676600206560ustar00rootroot00000000000000import 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} geoalchemy2-0.6.3/tests/test_elements.py000066400000000000000000000225441350001676600203210ustar00rootroot00000000000000import 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.compat import buffer as buffer_, bytes as bytes_, str as str_ 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_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)' assert unpickled.name == 'ST_GeomFromEWKT' 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)', } 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 assert unpickled.name == 'ST_GeomFromEWKT' 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) 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 = buffer_(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) assert unpickled.name == 'ST_GeomFromEWKB' 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 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_function_str(self): e = WKBElement(b'\x01\x02') assert isinstance(str(e), str) class TestRasterElement(): def test_desc(self): e = RasterElement(b'\x01\x02') assert e.desc == '0102' def test_function_call(self): e = RasterElement(b'\x01\x02') f = e.ST_Height() eq_sql(f, 'ST_Height(:raster_1::raster)') assert f.compile().params == {u'raster_1': b'\x01\x02'} 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.6.3/tests/test_functional.py000066400000000000000000000445061350001676600206510ustar00rootroot00000000000000from pkg_resources import parse_version import pytest try: from psycopg2cffi import compat except ImportError: pass else: compat.register() del compat from sqlalchemy import __version__ as SA_VERSION from sqlalchemy import create_engine, Table, MetaData, Column, Integer, bindparam from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.engine import reflection from sqlalchemy.exc import DataError, IntegrityError, InternalError from sqlalchemy.sql import select, func from sqlalchemy.sql.expression import type_coerce from geoalchemy2 import Geometry, Geography, Raster from geoalchemy2.elements import WKTElement, WKBElement, RasterElement from geoalchemy2.shape import from_shape from shapely.geometry import LineString engine = create_engine('postgresql://gis:gis@localhost/gis', echo=True) 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) 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 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 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_version()).scalar() if not postgis_version.startswith('2.'): # 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 postgis2_required = pytest.mark.skipif( not postgis_version.startswith('2.'), reason="requires PostGIS 2.x", ) class TestIndex(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() def test_index_with_schema(self): inspector = reflection.Inspector.from_engine(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_index_without_schema(self): inspector = reflection.Inspector.from_engine(engine) indices = inspector.get_indexes(IndexTestWithSchema.__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') class TestTypMod(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): session.rollback() metadata.drop_all() def test_SummitConstraints(self): """ Make sure the geometry column of table Summit is created with `use_typmod=false` (explicit constraints are created). """ inspector = reflection.Inspector.from_engine(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 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')) results = self.conn.execute(s, geom='SRID=4326;LINESTRING(0 0,1 1)') 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) results = self.conn.execute(s, geom=wkbelement) 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.extented s = Lake.__table__.select().where(Lake.__table__.c.geom == bindparam('geom')) results = self.conn.execute(s, geom=geom) 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) l = Lake('LINESTRING(0 0,1 1)') session.add(l) with pytest.raises((DataError, IntegrityError)): session.flush() def test_WKTElement(self): l = Lake(WKTElement('LINESTRING(0 0,1 1)', srid=4326)) session.add(l) session.flush() session.expire(l) assert isinstance(l.geom, WKBElement) wkt = session.execute(l.geom.ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0,1 1)' srid = session.execute(l.geom.ST_SRID()).scalar() assert srid == 4326 def test_WKBElement(self): shape = LineString([[0, 0], [1, 1]]) l = Lake(from_shape(shape, srid=4326)) session.add(l) session.flush() session.expire(l) assert isinstance(l.geom, WKBElement) wkt = session.execute(l.geom.ST_AsText()).scalar() assert wkt == 'LINESTRING(0 0,1 1)' srid = session.execute(l.geom.ST_SRID()).scalar() assert srid == 4326 @postgis2_required 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 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): l = Lake(WKTElement('LINESTRING(0 0,1 1)', srid=4326)) session.add(l) session.flush() return l.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 assert unpickled.geom.name == 'ST_GeomFromEWKB' 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): l = Lake(WKTElement('LINESTRING(0 0,1 1)', srid=4326)) session.add(l) session.flush() return l.id def _create_one_poi(self): p = Poi('POINT(5 45)') session.add(p) session.flush() 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) 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) 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) 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 @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 TestReflection(): def setup(self): metadata.drop_all(checkfirst=True) metadata.create_all() def teardown(self): metadata.drop_all() 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 not postgis_version.startswith('2.'): assert type_.geometry_type == 'GEOMETRY' assert type_.srid == -1 else: assert type_.geometry_type == 'LINESTRING' assert type_.srid == 4326 @postgis2_required def test_raster_reflection(self): t = Table('ocean', MetaData(), autoload=True, autoload_with=engine) type_ = t.c.rast.type assert isinstance(type_, Raster) geoalchemy2-0.6.3/tests/test_functional_spatialite.py000066400000000000000000000220461350001676600230630ustar00rootroot00000000000000from pkg_resources import parse_version import os import pytest import platform import json from sqlalchemy import __version__ as SA_VERSION from sqlalchemy import create_engine, MetaData, Column, Integer 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 geoalchemy2 import Geometry from geoalchemy2.elements import WKTElement, WKBElement from geoalchemy2.shape import from_shape, to_shape from geoalchemy2.compat import str as str_ 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('sqlite:///spatialdb', echo=True) 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 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)} ]) 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 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 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 = json.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( 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) geoalchemy2-0.6.3/tests/test_functions.py000066400000000000000000000261601350001676600205130ustar00rootroot00000000000000import re from sqlalchemy.sql import func # # Importing geoalchemy2 actually registers the GeoAlchemy generic # functions in SQLAlchemy's function registry. # import geoalchemy2.functions # 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)) # # Geometry Constructors # 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_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_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') # # 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_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_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_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_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_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_Intersection(): _test_geometry_returning_func('ST_Intersection') def test_ST_LineMerge(): _test_geometry_returning_func('ST_LineMerge') def test_ST_LineSubstring(): _test_geometry_returning_func('ST_LineSubstring') def test_ST_Simplify(): _test_geometry_returning_func('ST_Simplify') def test_ST_Union(): _test_geometry_returning_func('ST_Union') # # Raster Constructors # def test_ST_AsRaster(): _test_simple_func('ST_AsRaster') # # 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') geoalchemy2-0.6.3/tests/test_shape.py000066400000000000000000000027411350001676600176020ustar00rootroot00000000000000from geoalchemy2.compat import buffer, bytes, str from 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_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(): p = Point(1, 2) e = from_shape(p) assert isinstance(e, WKBElement) assert isinstance(e.data, buffer) s = shapely.wkb.loads(bytes(e.data)) assert isinstance(s, Point) assert p.equals(p) geoalchemy2-0.6.3/tests/test_types.py000066400000000000000000000205541350001676600176500ustar00rootroot00000000000000import 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_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 "table".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 (: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]) eq_sql(s, 'SELECT ST_AsEWKB((ST_Dump("table".geom)).geom) AS geom ' 'FROM "table"') geoalchemy2-0.6.3/tox.ini000066400000000000000000000011121350001676600152310ustar00rootroot00000000000000[tox] envlist=py27sqla11, py34sqla11, py35sqla11, pypysqla11 [testenv] passenv=SPATIALITE_LIBRARY_PATH commands= py.test \ {posargs} [testenv:py27sqla11] basepython=python2.7 deps= SQLAlchemy==1.1.2 psycopg2 pysqlite -rrequirements.txt [testenv:py34sqla11] basepython=python3.4 deps= SQLAlchemy==1.1.2 psycopg2 -rrequirements.txt [testenv:py35sqla11] basepython=python3.5 deps= SQLAlchemy==1.1.2 psycopg2 -rrequirements.txt [testenv:pypysqla11] basepython=pypy deps= SQLAlchemy==1.1.2 psycopg2cffi -rrequirements.txt