pax_global_header 0000666 0000000 0000000 00000000064 14160355441 0014515 g ustar 00root root 0000000 0000000 52 comment=cbe85285ab6f05afe9beabc66a86e05a4d125c43
geopython-pygml-5ce8eb0/ 0000775 0000000 0000000 00000000000 14160355441 0015354 5 ustar 00root root 0000000 0000000 geopython-pygml-5ce8eb0/.bumpversion.cfg 0000664 0000000 0000000 00000000627 14160355441 0020471 0 ustar 00root root 0000000 0000000 [bumpversion]
current_version = 0.2.2
commit = True
tag = True
tag_name = release-{new_version}
[bumpversion:file:pygml/__init__.py]
search = __version__ = '{current_version}'
replace = __version__ = '{new_version}'
[bumpversion:file:.bumpversion.cfg]
search = current_version = {current_version}
[bumpversion:file:docs/conf.py]
search = release = '{current_version}'
replace = release = '{new_version}'
geopython-pygml-5ce8eb0/.github/ 0000775 0000000 0000000 00000000000 14160355441 0016714 5 ustar 00root root 0000000 0000000 geopython-pygml-5ce8eb0/.github/workflows/ 0000775 0000000 0000000 00000000000 14160355441 0020751 5 ustar 00root root 0000000 0000000 geopython-pygml-5ce8eb0/.github/workflows/publish.yaml 0000664 0000000 0000000 00000001171 14160355441 0023303 0 ustar 00root root 0000000 0000000 name: publish
on:
push:
tags:
- release-*
jobs:
publish:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
name: Setup Python
with:
python-version: "3.x"
- name: Install build dependency
run: pip install wheel
- name: Build package
run: python setup.py sdist bdist_wheel --universal
- name: Publish package
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }} geopython-pygml-5ce8eb0/.github/workflows/test.yaml 0000664 0000000 0000000 00000001024 14160355441 0022611 0 ustar 00root root 0000000 0000000 name: test
on: [ push, pull_request ]
jobs:
test:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
name: Setup Python ${{ matrix.python-version }}
with:
python-version: ${{ matrix.python-version }}
- name: Install requirements 📦
run: |
pip install -r requirements-test.txt
pip install .
- name: Run unit tests ⚙️
run: |
pytest
geopython-pygml-5ce8eb0/.gitignore 0000664 0000000 0000000 00000003417 14160355441 0017351 0 ustar 00root root 0000000 0000000 # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
.vscode geopython-pygml-5ce8eb0/.readthedocs.yaml 0000664 0000000 0000000 00000000656 14160355441 0020612 0 ustar 00root root 0000000 0000000 # .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# Optionally set requirements required to build your docs
python:
version: "3.8"
install:
- requirements: docs/requirements.txt
- requirements: requirements-test.txt
geopython-pygml-5ce8eb0/CHANGELOG.md 0000664 0000000 0000000 00000000422 14160355441 0017163 0 ustar 00root root 0000000 0000000 # 0.2.1 (2021-08-31)
- Fixing case handling when parsing CRS strings
# 0.2.0 (2021-08-09)
- Adding parsing and encoding of GML 3.x (< 3.2) and GML 3.3 compact encoding
- Adding GeoRSS encoding support
# 0.1.0
- Adding GeoRSS parsing support
# 0.0.1
- Initial release
geopython-pygml-5ce8eb0/CONTRIBUTING.md 0000664 0000000 0000000 00000011462 14160355441 0017611 0 ustar 00root root 0000000 0000000 # Contributing
We welcome contributions to pygml, in the form of issues, bug fixes, documentation or suggestions for enhancements. This document sets out our guidelines and best practices for such contributions.
It's based on the [Contributing to pygeoapi](https://github.com/geopython/pygeoapi/blob/master/CONTRIBUTING.md) guide which is based on the [Contributing to Open Source Projects
Guide](https://contribution-guide-org.readthedocs.io/).
pygml has the following modes of contribution:
- GitHub Commit Access
- GitHub Pull Requests
## Code of Conduct
Contributors to this project are expected to act respectfully toward others in accordance with the [OSGeo Code of Conduct](https://www.osgeo.org/code_of_conduct).
## Submitting Bugs
### Due Diligence
Before submitting a bug, please do the following:
* Perform __basic troubleshooting__ steps:
* Make sure you're on the latest version. If you're not on the most
recent version, your problem may have been solved already! Upgrading is
always the best first step.
* [Search the issue tracker](https://github.com/geopython/pygml/issues)
to make sure it's not a known issue.
### What to put in your bug report
Make sure your report gets the attention it deserves: bug reports with missing information may be ignored or punted back to you, delaying a fix. The below constitutes a bare minimum; more info is almost always better:
* __What version of Python are you using?__ For example, are you using Python 2.7, Python 3.7, PyPy 2.0?
* __What operating system are you using?__ Windows (7, 8, 10, 32-bit, 64-bit), Mac OS X, (10.7.4, 10.9.0), GNU/Linux (which distribution, which version?) Again, more detail is better.
* __Which version or versions of the software are you using?__ Ideally, you've followed the advice above and are on the latest version, but please confirm this.
* __How can the we recreate your problem?__ Imagine that we have never used pygml before and have downloaded it for the first time. Exactly what steps do we need to take to reproduce your problem?
## Contributions and Licensing
### Contributor License Agreement
Your contribution will be under our [license](https://github.com/geopython/pygml/blob/main/LICENSE) as per [GitHub's terms of service](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license).
### GitHub Commit Access
* Proposals to provide developers with GitHub commit access shall be raised on the pygml [discussions page](https://github.com/geopython/pygml/discussions). Committers shall be added by the project admin.
* Removal of commit access shall be handled in the same manner.
### GitHub Pull Requests
* Pull requests may include copyright in the source code header by the contributor if the contribution is significant or the contributor wants to claim copyright on their contribution.
* All contributors shall be listed at https://github.com/geopython/pygml/graphs/contributors
* Unclaimed copyright, by default, is assigned to the main copyright holders as specified in https://github.com/geopython/pygml/blob/main/LICENSE
### Version Control Branching
* Always __make a new branch__ for your work, no matter how small. This makes it easy for others to take just that one set of changes from your repository, in case you have multiple unrelated changes floating around.
* __Don't submit unrelated changes in the same branch/pull request!__ If it is not possible to review your changes quickly and easily, we may reject your request.
* __Base your new branch off of the appropriate branch__ on the main repository:
* In general the released version of pygml is based on the ``main`` (default) branch whereas development work is done under other non-default branches. Unless you are sure that your issue affects a non-default branch, __base your branch off the ``main`` one__.
* Note that depending on how long it takes for the dev team to merge your
patch, the copy of ``main`` you worked off of may get out of date!
* If you find yourself 'bumping' a pull request that's been sidelined for a while, __make sure you rebase or merge to latest ``main``__ to ensure a speedier resolution.
### Documentation
* documentation is managed in `docs/`, in reStructuredText format
* [Sphinx](https://www.sphinx-doc.org) is used to generate the documentation
* See the [reStructuredText Primer](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) on rST markup and syntax
### Code Formatting
* __Please follow the coding conventions and style used in the pygml repository.__
* pygml follows the [PEP-8](http://www.python.org/dev/peps/pep-0008/) guidelines
* 80 characters
* spaces, not tabs
* pygml, instead of PyGML, pyGml, etc.
## Suggesting Enhancements
We welcome suggestions for enhancements, but reserve the right to reject them if they do not follow future plans for pygml.
geopython-pygml-5ce8eb0/LICENSE 0000664 0000000 0000000 00000002052 14160355441 0016360 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2021 geopython
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.
geopython-pygml-5ce8eb0/README.md 0000664 0000000 0000000 00000002634 14160355441 0016640 0 ustar 00root root 0000000 0000000 # pygml
A pure python parser and encoder for OGC GML Geometries.
[](https://badge.fury.io/py/pygml)
[](https://github.com/geopython/pygml/actions/workflows/test.yaml)
[](https://pygml.readthedocs.io/en/latest/?badge=latest)
## Installation
```bash
$ pip install pygml
```
## Features
Parse GML 3.1, 3.2, compact encoded GML 3.3 and GeoRSS geometries to a [Geo Interface](https://gist.github.com/sgillies/2217756) compliant class.
```python
>>> import pygml
>>> geom = pygml.parse("""
...
... 1.0 1.0
...
... """)
>>> print(geom)
Geometry(geometry={'type': 'Point', 'coordinates': (1.0, 1.0)})
>>> print(geom.__geo_interface__)
{'type': 'Point', 'coordinates': (1.0, 1.0)}
```
Conversely, it is possible to encode GeoJSON or Geo Interfaces to GML
```python
>>> from pygml.v32 import encode_v32
>>> from lxml import etree
>>> tree = encode_v32({'type': 'Point', 'coordinates': (1.0, 1.0)}, 'ID')
>>> print(etree.tostring(tree, pretty_print=True).decode())
1.0 1.0
>>>
``` geopython-pygml-5ce8eb0/SECURITY.md 0000664 0000000 0000000 00000001052 14160355441 0017143 0 ustar 00root root 0000000 0000000 # pygml Security Policy
## Supported Versions
Security/vulnerability reports **should not** be submitted through GitHub issues or public discussions, but instead please send your report
to **geopython-security nospam @ lists.osgeo.org** - (remove the blanks and 'nospam').
## Supported Versions
pygml developers will release patches for security vulnerabilities for the following versions:
| Version | Supported |
| ------- | ------------------ |
| latest stable version | :white_check_mark: |
| previous versions | :x: |
geopython-pygml-5ce8eb0/docs/ 0000775 0000000 0000000 00000000000 14160355441 0016304 5 ustar 00root root 0000000 0000000 geopython-pygml-5ce8eb0/docs/.gitignore 0000664 0000000 0000000 00000000004 14160355441 0020266 0 ustar 00root root 0000000 0000000 api
geopython-pygml-5ce8eb0/docs/Makefile 0000664 0000000 0000000 00000001104 14160355441 0017740 0 ustar 00root root 0000000 0000000 # Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) geopython-pygml-5ce8eb0/docs/conf.py 0000664 0000000 0000000 00000013060 14160355441 0017603 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# 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.
#
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
# -- Project information -----------------------------------------------------
project = 'pygml'
copyright = '2021, Fabian Schindler'
author = 'Fabian Schindler'
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '0.2.2'
# -- 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.intersphinx',
'sphinxcontrib.apidoc',
'm2r2',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
# -- 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 = 'pydata_sphinx_theme'
html_theme_options = {
"github_url": "https://github.com/geopython/pygml",
}
# 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 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']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'pygmldoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'pygml.tex', 'pygml Documentation',
'Fabian Schindler', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'pygml', 'pygml Documentation',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'pygml', 'pygml Documentation',
author, 'pygml', 'One line description of project.',
'Miscellaneous'),
]
# -- Options for Epub output -------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# -- Extension configuration -------------------------------------------------
intersphinx_mapping = {
'python': ('https://python.readthedocs.org/en/latest/', None),
}
# apidoc configs:
apidoc_module_dir = '../pygml'
apidoc_output_dir = 'api'
# apidoc_excluded_paths = ['tests']
# apidoc_separate_modules = True
# apidoc_module_first = True
geopython-pygml-5ce8eb0/docs/contributing.rst 0000664 0000000 0000000 00000000041 14160355441 0021540 0 ustar 00root root 0000000 0000000 .. mdinclude:: ../CONTRIBUTING.md geopython-pygml-5ce8eb0/docs/index.rst 0000664 0000000 0000000 00000000330 14160355441 0020141 0 ustar 00root root 0000000 0000000 .. mdinclude:: ../README.md
.. toctree::
:maxdepth: 2
:caption: Contents:
license
contributing
api/modules
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
geopython-pygml-5ce8eb0/docs/license.rst 0000664 0000000 0000000 00000000065 14160355441 0020461 0 ustar 00root root 0000000 0000000 License
=======
.. include:: ../LICENSE
:literal: geopython-pygml-5ce8eb0/docs/make.bat 0000664 0000000 0000000 00000001427 14160355441 0017715 0 ustar 00root root 0000000 0000000 @ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd
geopython-pygml-5ce8eb0/docs/requirements.txt 0000664 0000000 0000000 00000000056 14160355441 0021571 0 ustar 00root root 0000000 0000000 sphinxcontrib-apidoc
pydata-sphinx-theme
m2r2
geopython-pygml-5ce8eb0/pygml/ 0000775 0000000 0000000 00000000000 14160355441 0016504 5 ustar 00root root 0000000 0000000 geopython-pygml-5ce8eb0/pygml/__init__.py 0000664 0000000 0000000 00000003002 14160355441 0020610 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from .parse import parse
__version__ = '0.2.2'
__all__ = ['parse']
geopython-pygml-5ce8eb0/pygml/axisorder.py 0000664 0000000 0000000 00000034263 14160355441 0021066 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this 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.
# ------------------------------------------------------------------------------
import re
from typing import Union
# copied from:
# https://github.com/geopython/OWSLib/blob/a9c1be25676ab530fd0327c2450922f288ca25f4/owslib/crs.py
AXISORDER_YX = {
4326, 4258, 31466, 31467, 31468, 31469, 2166, 2167, 2168, 2036, 2044, 2045,
2065, 2081, 2082, 2083, 2085, 2086, 2091, 2092, 2093, 2096, 2097, 2098,
2105, 2106, 2107, 2108, 2109, 2110, 2111, 2112, 2113, 2114, 2115, 2116,
2117, 2118, 2119, 2120, 2121, 2122, 2123, 2124, 2125, 2126, 2127, 2128,
2129, 2130, 2131, 2132, 2169, 2170, 2171, 2172, 2173, 2174, 2175, 2176,
2177, 2178, 2179, 2180, 2193, 2199, 2200, 2206, 2207, 2208, 2209, 2210,
2211, 2212, 2319, 2320, 2321, 2322, 2323, 2324, 2325, 2326, 2327, 2328,
2329, 2330, 2331, 2332, 2333, 2334, 2335, 2336, 2337, 2338, 2339, 2340,
2341, 2342, 2343, 2344, 2345, 2346, 2347, 2348, 2349, 2350, 2351, 2352,
2353, 2354, 2355, 2356, 2357, 2358, 2359, 2360, 2361, 2362, 2363, 2364,
2365, 2366, 2367, 2368, 2369, 2370, 2371, 2372, 2373, 2374, 2375, 2376,
2377, 2378, 2379, 2380, 2381, 2382, 2383, 2384, 2385, 2386, 2387, 2388,
2389, 2390, 2391, 2392, 2393, 2394, 2395, 2396, 2397, 2398, 2399, 2400,
2401, 2402, 2403, 2404, 2405, 2406, 2407, 2408, 2409, 2410, 2411, 2412,
2413, 2414, 2415, 2416, 2417, 2418, 2419, 2420, 2421, 2422, 2423, 2424,
2425, 2426, 2427, 2428, 2429, 2430, 2431, 2432, 2433, 2434, 2435, 2436,
2437, 2438, 2439, 2440, 2441, 2442, 2443, 2444, 2445, 2446, 2447, 2448,
2449, 2450, 2451, 2452, 2453, 2454, 2455, 2456, 2457, 2458, 2459, 2460,
2461, 2462, 2463, 2464, 2465, 2466, 2467, 2468, 2469, 2470, 2471, 2472,
2473, 2474, 2475, 2476, 2477, 2478, 2479, 2480, 2481, 2482, 2483, 2484,
2485, 2486, 2487, 2488, 2489, 2490, 2491, 2492, 2493, 2494, 2495, 2496,
2497, 2498, 2499, 2500, 2501, 2502, 2503, 2504, 2505, 2506, 2507, 2508,
2509, 2510, 2511, 2512, 2513, 2514, 2515, 2516, 2517, 2518, 2519, 2520,
2521, 2522, 2523, 2524, 2525, 2526, 2527, 2528, 2529, 2530, 2531, 2532,
2533, 2534, 2535, 2536, 2537, 2538, 2539, 2540, 2541, 2542, 2543, 2544,
2545, 2546, 2547, 2548, 2549, 2551, 2552, 2553, 2554, 2555, 2556, 2557,
2558, 2559, 2560, 2561, 2562, 2563, 2564, 2565, 2566, 2567, 2568, 2569,
2570, 2571, 2572, 2573, 2574, 2575, 2576, 2577, 2578, 2579, 2580, 2581,
2582, 2583, 2584, 2585, 2586, 2587, 2588, 2589, 2590, 2591, 2592, 2593,
2594, 2595, 2596, 2597, 2598, 2599, 2600, 2601, 2602, 2603, 2604, 2605,
2606, 2607, 2608, 2609, 2610, 2611, 2612, 2613, 2614, 2615, 2616, 2617,
2618, 2619, 2620, 2621, 2622, 2623, 2624, 2625, 2626, 2627, 2628, 2629,
2630, 2631, 2632, 2633, 2634, 2635, 2636, 2637, 2638, 2639, 2640, 2641,
2642, 2643, 2644, 2645, 2646, 2647, 2648, 2649, 2650, 2651, 2652, 2653,
2654, 2655, 2656, 2657, 2658, 2659, 2660, 2661, 2662, 2663, 2664, 2665,
2666, 2667, 2668, 2669, 2670, 2671, 2672, 2673, 2674, 2675, 2676, 2677,
2678, 2679, 2680, 2681, 2682, 2683, 2684, 2685, 2686, 2687, 2688, 2689,
2690, 2691, 2692, 2693, 2694, 2695, 2696, 2697, 2698, 2699, 2700, 2701,
2702, 2703, 2704, 2705, 2706, 2707, 2708, 2709, 2710, 2711, 2712, 2713,
2714, 2715, 2716, 2717, 2718, 2719, 2720, 2721, 2722, 2723, 2724, 2725,
2726, 2727, 2728, 2729, 2730, 2731, 2732, 2733, 2734, 2735, 2738, 2739,
2740, 2741, 2742, 2743, 2744, 2745, 2746, 2747, 2748, 2749, 2750, 2751,
2752, 2753, 2754, 2755, 2756, 2757, 2758, 2935, 2936, 2937, 2938, 2939,
2940, 2941, 2953, 2963, 3006, 3007, 3008, 3009, 3010, 3011, 3012, 3013,
3014, 3015, 3016, 3017, 3018, 3019, 3020, 3021, 3022, 3023, 3024, 3025,
3026, 3027, 3028, 3029, 3030, 3034, 3035, 3038, 3039, 3040, 3041, 3042,
3043, 3044, 3045, 3046, 3047, 3048, 3049, 3050, 3051, 3058, 3059, 3068,
3114, 3115, 3116, 3117, 3118, 3120, 3126, 3127, 3128, 3129, 3130, 3131,
3132, 3133, 3134, 3135, 3136, 3137, 3138, 3139, 3140, 3146, 3147, 3150,
3151, 3152, 3300, 3301, 3328, 3329, 3330, 3331, 3332, 3333, 3334, 3335,
3346, 3350, 3351, 3352, 3366, 3386, 3387, 3388, 3389, 3390, 3396, 3397,
3398, 3399, 3407, 3414, 3416, 3764, 3788, 3789, 3790, 3791, 3793, 3795,
3796, 3819, 3821, 3823, 3824, 3833, 3834, 3835, 3836, 3837, 3838, 3839,
3840, 3841, 3842, 3843, 3844, 3845, 3846, 3847, 3848, 3849, 3850, 3851,
3852, 3854, 3873, 3874, 3875, 3876, 3877, 3878, 3879, 3880, 3881, 3882,
3883, 3884, 3885, 3888, 3889, 3906, 3907, 3908, 3909, 3910, 3911, 4001,
4002, 4003, 4004, 4005, 4006, 4007, 4008, 4009, 4010, 4011, 4012, 4013,
4014, 4015, 4016, 4017, 4018, 4019, 4020, 4021, 4022, 4023, 4024, 4025,
4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 4034, 4035, 4036, 4037,
4038, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4052, 4053, 4054,
4055, 4074, 4075, 4080, 4081, 4120, 4121, 4122, 4123, 4124, 4125, 4126,
4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138,
4139, 4140, 4141, 4142, 4143, 4144, 4145, 4146, 4147, 4148, 4149, 4150,
4151, 4152, 4153, 4154, 4155, 4156, 4157, 4158, 4159, 4160, 4161, 4162,
4163, 4164, 4165, 4166, 4167, 4168, 4169, 4170, 4171, 4172, 4173, 4174,
4175, 4176, 4178, 4179, 4180, 4181, 4182, 4183, 4184, 4185, 4188, 4189,
4190, 4191, 4192, 4193, 4194, 4195, 4196, 4197, 4198, 4199, 4200, 4201,
4202, 4203, 4204, 4205, 4206, 4207, 4208, 4209, 4210, 4211, 4212, 4213,
4214, 4215, 4216, 4218, 4219, 4220, 4221, 4222, 4223, 4224, 4225, 4226,
4227, 4228, 4229, 4230, 4231, 4232, 4233, 4234, 4235, 4236, 4237, 4238,
4239, 4240, 4241, 4242, 4243, 4244, 4245, 4246, 4247, 4248, 4249, 4250,
4251, 4252, 4253, 4254, 4255, 4256, 4257, 4259, 4260, 4261, 4262, 4263,
4264, 4265, 4266, 4267, 4268, 4269, 4270, 4271, 4272, 4273, 4274, 4275,
4276, 4277, 4278, 4279, 4280, 4281, 4282, 4283, 4284, 4285, 4286, 4287,
4288, 4289, 4291, 4292, 4293, 4294, 4295, 4296, 4297, 4298, 4299, 4300,
4301, 4302, 4303, 4304, 4306, 4307, 4308, 4309, 4310, 4311, 4312, 4313,
4314, 4315, 4316, 4317, 4318, 4319, 4322, 4324, 4327, 4329, 4339, 4341,
4343, 4345, 4347, 4349, 4351, 4353, 4355, 4357, 4359, 4361, 4363, 4365,
4367, 4369, 4371, 4373, 4375, 4377, 4379, 4381, 4383, 4386, 4388, 4417,
4434, 4463, 4466, 4469, 4470, 4472, 4475, 4480, 4482, 4483, 4490, 4491,
4492, 4493, 4494, 4495, 4496, 4497, 4498, 4499, 4500, 4501, 4502, 4503,
4504, 4505, 4506, 4507, 4508, 4509, 4510, 4511, 4512, 4513, 4514, 4515,
4516, 4517, 4518, 4519, 4520, 4521, 4522, 4523, 4524, 4525, 4526, 4527,
4528, 4529, 4530, 4531, 4532, 4533, 4534, 4535, 4536, 4537, 4538, 4539,
4540, 4541, 4542, 4543, 4544, 4545, 4546, 4547, 4548, 4549, 4550, 4551,
4552, 4553, 4554, 4555, 4557, 4558, 4568, 4569, 4570, 4571, 4572, 4573,
4574, 4575, 4576, 4577, 4578, 4579, 4580, 4581, 4582, 4583, 4584, 4585,
4586, 4587, 4588, 4589, 4600, 4601, 4602, 4603, 4604, 4605, 4606, 4607,
4608, 4609, 4610, 4611, 4612, 4613, 4614, 4615, 4616, 4617, 4618, 4619,
4620, 4621, 4622, 4623, 4624, 4625, 4626, 4627, 4628, 4629, 4630, 4631,
4632, 4633, 4634, 4635, 4636, 4637, 4638, 4639, 4640, 4641, 4642, 4643,
4644, 4645, 4646, 4652, 4653, 4654, 4655, 4656, 4657, 4658, 4659, 4660,
4661, 4662, 4663, 4664, 4665, 4666, 4667, 4668, 4669, 4670, 4671, 4672,
4673, 4674, 4675, 4676, 4677, 4678, 4679, 4680, 4681, 4682, 4683, 4684,
4685, 4686, 4687, 4688, 4689, 4690, 4691, 4692, 4693, 4694, 4695, 4696,
4697, 4698, 4699, 4700, 4701, 4702, 4703, 4704, 4705, 4706, 4707, 4708,
4709, 4710, 4711, 4712, 4713, 4714, 4715, 4716, 4717, 4718, 4719, 4720,
4721, 4722, 4723, 4724, 4725, 4726, 4727, 4728, 4729, 4730, 4731, 4732,
4733, 4734, 4735, 4736, 4737, 4738, 4739, 4740, 4741, 4742, 4743, 4744,
4745, 4746, 4747, 4748, 4749, 4750, 4751, 4752, 4753, 4754, 4755, 4756,
4757, 4758, 4759, 4760, 4761, 4762, 4763, 4764, 4765, 4766, 4767, 4768,
4769, 4770, 4771, 4772, 4773, 4774, 4775, 4776, 4777, 4778, 4779, 4780,
4781, 4782, 4783, 4784, 4785, 4786, 4787, 4788, 4789, 4790, 4791, 4792,
4793, 4794, 4795, 4796, 4797, 4798, 4799, 4800, 4801, 4802, 4803, 4804,
4805, 4806, 4807, 4808, 4809, 4810, 4811, 4812, 4813, 4814, 4815, 4816,
4817, 4818, 4819, 4820, 4821, 4822, 4823, 4824, 4839, 4855, 4856, 4857,
4858, 4859, 4860, 4861, 4862, 4863, 4864, 4865, 4866, 4867, 4868, 4869,
4870, 4871, 4872, 4873, 4874, 4875, 4876, 4877, 4878, 4879, 4880, 4883,
4885, 4887, 4889, 4891, 4893, 4895, 4898, 4900, 4901, 4902, 4903, 4904,
4907, 4909, 4921, 4923, 4925, 4927, 4929, 4931, 4933, 4935, 4937, 4939,
4941, 4943, 4945, 4947, 4949, 4951, 4953, 4955, 4957, 4959, 4961, 4963,
4965, 4967, 4969, 4971, 4973, 4975, 4977, 4979, 4981, 4983, 4985, 4987,
4989, 4991, 4993, 4995, 4997, 4999, 5012, 5013, 5017, 5048, 5105, 5106,
5107, 5108, 5109, 5110, 5111, 5112, 5113, 5114, 5115, 5116, 5117, 5118,
5119, 5120, 5121, 5122, 5123, 5124, 5125, 5126, 5127, 5128, 5129, 5130,
5132, 5167, 5168, 5169, 5170, 5171, 5172, 5173, 5174, 5175, 5176, 5177,
5178, 5179, 5180, 5181, 5182, 5183, 5184, 5185, 5186, 5187, 5188, 5224,
5228, 5229, 5233, 5245, 5246, 5251, 5252, 5253, 5254, 5255, 5256, 5257,
5258, 5259, 5263, 5264, 5269, 5270, 5271, 5272, 5273, 5274, 5275, 5801,
5802, 5803, 5804, 5808, 5809, 5810, 5811, 5812, 5813, 5814, 5815, 5816,
20004, 20005, 20006, 20007, 20008, 20009, 20010, 20011, 20012, 20013,
20014, 20015, 20016, 20017, 20018, 20019, 20020, 20021, 20022, 20023,
20024, 20025, 20026, 20027, 20028, 20029, 20030, 20031, 20032, 20064,
20065, 20066, 20067, 20068, 20069, 20070, 20071, 20072, 20073, 20074,
20075, 20076, 20077, 20078, 20079, 20080, 20081, 20082, 20083, 20084,
20085, 20086, 20087, 20088, 20089, 20090, 20091, 20092, 21413, 21414,
21415, 21416, 21417, 21418, 21419, 21420, 21421, 21422, 21423, 21453,
21454, 21455, 21456, 21457, 21458, 21459, 21460, 21461, 21462, 21463,
21473, 21474, 21475, 21476, 21477, 21478, 21479, 21480, 21481, 21482,
21483, 21896, 21897, 21898, 21899, 22171, 22172, 22173, 22174, 22175,
22176, 22177, 22181, 22182, 22183, 22184, 22185, 22186, 22187, 22191,
22192, 22193, 22194, 22195, 22196, 22197, 25884, 27205, 27206, 27207,
27208, 27209, 27210, 27211, 27212, 27213, 27214, 27215, 27216, 27217,
27218, 27219, 27220, 27221, 27222, 27223, 27224, 27225, 27226, 27227,
27228, 27229, 27230, 27231, 27232, 27391, 27392, 27393, 27394, 27395,
27396, 27397, 27398, 27492, 28402, 28403, 28404, 28405, 28406, 28407,
28408, 28409, 28410, 28411, 28412, 28413, 28414, 28415, 28416, 28417,
28418, 28419, 28420, 28421, 28422, 28423, 28424, 28425, 28426, 28427,
28428, 28429, 28430, 28431, 28432, 28462, 28463, 28464, 28465, 28466,
28467, 28468, 28469, 28470, 28471, 28472, 28473, 28474, 28475, 28476,
28477, 28478, 28479, 28480, 28481, 28482, 28483, 28484, 28485, 28486,
28487, 28488, 28489, 28490, 28491, 28492, 29701, 29702, 30161, 30162,
30163, 30164, 30165, 30166, 30167, 30168, 30169, 30170, 30171, 30172,
30173, 30174, 30175, 30176, 30177, 30178, 30179, 30800, 31251, 31252,
31253, 31254, 31255, 31256, 31257, 31258, 31259, 31275, 31276, 31277,
31278, 31279, 31281, 31282, 31283, 31284, 31285, 31286, 31287, 31288,
31289, 31290, 31700,
}
RE_CRS_CODE = re.compile(
r'(EPSG:|'
r'http://www\.opengis\.net/def/crs/epsg/0/|'
r'http://www\.opengis\.net/gml/srs/epsg\.xml\#|'
r'urn:EPSG:geographicCRS:|'
r'urn:ogc:def:crs:EPSG::|'
r'urn:ogc:def:crs:OGC::|'
r'urn:ogc:def:crs:EPSG:)([0-9]+|CRS84)',
re.IGNORECASE,
)
def get_crs_code(crs: str) -> Union[int, str]:
""" Extract the CRS code from the given CRS identifier string,
which can be one of:
* ``EPSG:``
* ``http://www.opengis.net/def/crs/EPSG/0/``
* ``http://www.opengis.net/gml/srs/epsg.xml#``
* ``urn:EPSG:geographicCRS:``
* ``urn:ogc:def:crs:EPSG::``
* ``urn:ogc:def:crs:OGC::``
* ``urn:ogc:def:crs:EPSG:``
Returns the code as an integer in case of EPSG code or as the
string ``'CRS84'``.
>>> get_crs_code('EPSG:4326')
4326
>>> get_crs_code('urn:ogc:def:crs:OGC::CRS84')
'CRS84'
>>> get_crs_code('something')
Traceback (most recent call last):
...
ValueError: Failed to retrieve CRS code
"""
match = RE_CRS_CODE.match(crs)
if not match:
raise ValueError('Failed to retrieve CRS code')
value_group = match.groups()[1]
try:
return int(value_group)
except ValueError:
return value_group
def is_crs_yx(crs: str) -> bool:
""" Determines whether the given CRS uses Y/X (or latitude/longitude)
axis order.
>>> is_crs_yx('EPSG:4326')
True
>>> is_crs_yx('EPSG:3857')
False
>>> is_crs_yx('urn:ogc:def:crs:OGC::CRS84')
False
"""
code = get_crs_code(crs)
return code in AXISORDER_YX
geopython-pygml-5ce8eb0/pygml/basics.py 0000664 0000000 0000000 00000012040 14160355441 0020317 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from typing import Callable
from .types import Coordinates, Coordinate
def _make_number_parser(decimal: str) -> Callable[[str], float]:
""" Helper to create a number parser with a potentially custom
decimal separator. When this is not the '.' character, each
number will replace the given decimal separator with '.'
before calling the built-in `float` function.
"""
if decimal == '.':
return float
def inner(value: str) -> float:
return float(value.replace(decimal, '.'))
return inner
def parse_coordinates(value: str, cs: str = ',', ts: str = ' ',
decimal: str = '.') -> Coordinates:
""" Parses the the values of a gml:coordinates node to a list of
lists of floats. Takes the coordinate separator and tuple
separator into account, and also custom decimal separators.
>>> parse_coordinates('12.34 56.7,89.10 11.12')
[(12.34, 56.7), (89.1, 11.12)]
>>> parse_coordinates('12.34 56.7;89.10 11.12', cs=';')
[(12.34, 56.7), (89.1, 11.12)]
>>> parse_coordinates('12.34:56.7,89.10:11.12', ts=':')
[(12.34, 56.7), (89.1, 11.12)]
>>> parse_coordinates('12.34:56.7;89.10:11.12', cs=';', ts=':')
[(12.34, 56.7), (89.1, 11.12)]
>>> parse_coordinates(
... '12,34:56,7;89,10:11,12', cs=';', ts=':', decimal=','
... )
[(12.34, 56.7), (89.1, 11.12)]
"""
number_parser = _make_number_parser(decimal)
return [
tuple(
number_parser(number)
for number in coordinate.strip().split(ts)
)
for coordinate in value.strip().split(cs)
]
def parse_poslist(value: str, dimensions: int = 2) -> Coordinates:
""" Parses the value of a single gml:posList to a `Coordinates`
structure.
>>> parse_poslist('12.34 56.7 89.10 11.12')
[(12.34, 56.7), (89.1, 11.12)]
>>> parse_poslist('12.34 56.7 89.10 11.12 13.14 15.16', dimensions=3)
[(12.34, 56.7, 89.1), (11.12, 13.14, 15.16)]
>>> parse_poslist('12.34 56.7 89.10 11.12', dimensions=3)
Traceback (most recent call last):
...
ValueError: Invalid dimensionality of pos list
"""
raw = [float(v) for v in value.split()]
if len(raw) % dimensions > 0:
raise ValueError('Invalid dimensionality of pos list')
return [
tuple(raw[i:i + dimensions])
for i in range(0, len(raw), dimensions)
]
def parse_pos(value: str) -> Coordinate:
""" Parses a single gml:pos to a `Coordinate` structure.
>>> parse_pos('12.34 56.7')
(12.34, 56.7)
>>> parse_pos('12.34 56.7 89.10')
(12.34, 56.7, 89.1)
"""
return tuple(float(v) for v in value.split())
def swap_coordinate_xy(coordinate: Coordinate) -> Coordinate:
""" Swaps the X and Y coordinates of a given coordinate
>>> swap_coordinate_xy((12.34, 56.7))
(56.7, 12.34)
>>> swap_coordinate_xy((12.34, 56.7, 89.10))
(56.7, 12.34, 89.1)
"""
return (coordinate[1], coordinate[0], *coordinate[2:])
def swap_coordinates_xy(coordinates: Coordinates) -> Coordinates:
""" Swaps the X and Y coordinates of a given coordinates list
>>> swap_coordinates_xy(
... [(12.34, 56.7), (89.10, 11.12)]
... )
[(56.7, 12.34), (11.12, 89.1)]
>>> swap_coordinates_xy(
... [(12.34, 56.7, 89.10), (11.12, 13.14, 15.16)]
... )
[(56.7, 12.34, 89.1), (13.14, 11.12, 15.16)]
"""
return [
(coordinate[1], coordinate[0], *coordinate[2:])
for coordinate in coordinates
]
geopython-pygml-5ce8eb0/pygml/dimensionality.py 0000664 0000000 0000000 00000006277 14160355441 0022122 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from typing import Optional
from collections.abc import Sequence
from .types import GeomDict
def get_dimensionality(geometry: GeomDict) -> Optional[int]:
""" Returns the dimensionality of a given GeoJSON geometry.
This is obtained by descending into the first coordinate
and using its length.
When no coordinates can be retrieved (e.g: in case of
GeometryCollections) None is returned.
>>> get_dimensionality({
... 'type': 'Polygon',
... 'coordinates': [
... [
... (0.5, 1.0),
... (0.5, 2.0),
... (1.5, 2.0),
... (1.5, 1.0),
... (0.5, 1.0)
... ]
... ]
... })
2
>>> get_dimensionality({
... 'type': 'MultiPoint',
... 'coordinates': [
... (1.0, 1.0, 1.0),
... (2.0, 2.0, 1.0),
... ]
... })
3
>>> get_dimensionality({
... 'type': 'GeometryCollection',
... 'geometries': [
... {
... 'type': 'Point',
... 'coordinates': (1.0, 1.0)
... },
... {
... 'type': 'Polygon',
... 'coordinates': [
... [(1.0, 1.0)],
... [(1.0, 1.0)],
... ]
... },
... ]
... })
"""
coordinates = geometry.get('coordinates')
if coordinates:
# drill down into nested coordinates
while isinstance(coordinates[0], Sequence):
coordinates = coordinates[0]
return len(coordinates)
return None
geopython-pygml-5ce8eb0/pygml/georss.py 0000664 0000000 0000000 00000015472 14160355441 0020371 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from pygml.axisorder import get_crs_code
from typing import Callable, List
from lxml import etree
from lxml.builder import ElementMaker
from .basics import (
parse_pos, parse_poslist, swap_coordinate_xy, swap_coordinates_xy
)
from .dimensionality import get_dimensionality
from .types import GeomDict
from .pre_v32 import (
NAMESPACE as NAMESPACE_PRE32, parse_pre_v32, encode_pre_v32
)
from .v32 import NAMESPACE as NAMESPACE_32, parse_v32
from .v33 import NAMESPACE as NAMESPACE_33_CE, parse_v33_ce
NAMESPACE = 'http://www.georss.org/georss'
NSMAP = {'georss': NAMESPACE}
Element = etree._Element
Elements = List[Element]
def parse_georss(element: Element) -> GeomDict:
""" Parses the GeoRSS basic elements to their respective GeoJSON
representation. As all coordinates in GeoRSS are expressed in
WGS84 and in Latitude/Longitude order, the coordinates are
swapped to XY order.
In case of georss:where, it is expected that it contains a
single GML element which is parsed as either GML 3.1.1, GML 3.2
or GML 3.3 CE.
"""
qname = etree.QName(element.tag)
if qname.namespace != NAMESPACE:
raise ValueError(f'Unsupported namespace {qname.namespace}')
bbox = None
localname = qname.localname
if localname == 'point':
type_ = 'Point'
coordinates = swap_coordinate_xy(parse_pos(element.text))
elif localname == 'line':
type_ = 'LineString'
coordinates = swap_coordinates_xy(parse_poslist(element.text))
elif localname == 'box':
# boxes are expanded to Polygons, but store the 'bbox' value
type_ = 'Polygon'
low, high = swap_coordinates_xy(parse_poslist(element.text))
lx, ly = low
hx, hy = high
coordinates = [
[
(lx, ly),
(lx, hy),
(hx, hy),
(hx, ly),
(lx, ly),
]
]
bbox = (lx, ly, hx, hy)
elif localname == 'polygon':
type_ = 'Polygon'
coordinates = [swap_coordinates_xy(parse_poslist(element.text))]
elif localname == 'where':
# special handling here: defer to the gml definition. Although,
# only GML 3.1.1 is officially supported, we also allow GML 3.2 and 3.3
if not len(element) == 1:
raise ValueError(
'Invalid number of child elements in georss:where'
)
child = element[0]
child_namespace = etree.QName(child.tag).namespace
if child_namespace == NAMESPACE_PRE32:
return parse_pre_v32(child)
elif child_namespace == NAMESPACE_32:
return parse_v32(child)
elif child_namespace == NAMESPACE_33_CE:
return parse_v33_ce(child)
else:
raise ValueError(
f'Unsupported child element in georss:where: {child.tag}'
)
else:
raise ValueError(f'Unsupported georss element: {localname}')
result = {
'type': type_,
'coordinates': coordinates,
}
if bbox:
result['bbox'] = bbox
return result
GEORSS = ElementMaker(namespace=NAMESPACE, nsmap=NSMAP)
GmlEncoder = Callable[[GeomDict, str], Element]
def encode_georss(geometry: GeomDict,
gml_encoder: GmlEncoder = encode_pre_v32) -> Element:
""" Encodes a GeoJSON geometry as a GeoRSS ``lxml.etree.Element``.
Tries to use the native GeoRSS elements ``point``, ``line``,
or ``polygon`` when possible. Falls back to ``georss:where``
with using the ``gml_encoder`` function (defaulting to GML 3.2):
- MultiPoint, MultiLineString, MultiPolygon geometries
- Polygons with interiors
- GeometryCollections
- any geometry with CRS other than CRS84 or EPSG:4326
- when dealing with >2D geometries
"""
type_ = geometry['type']
coordinates = geometry.get('coordinates')
crs = geometry.get('crs')
dims = get_dimensionality(geometry)
code = None
if crs:
crs_name = crs.get('properties', {}).get('name')
code = get_crs_code(crs_name)
if code in (None, 4326, 'CRS84') and dims == 2:
if type_ == 'Point':
return GEORSS(
'point',
' '.join(
str(v) for v in swap_coordinate_xy(coordinates)
)
)
elif type_ == 'LineString':
return GEORSS(
'line',
' '.join(
' '.join(
str(v) for v in coordinate
) for coordinate in swap_coordinates_xy(coordinates)
)
)
elif type_ == 'Polygon':
# only exterior
if len(coordinates) == 1:
return GEORSS(
'polygon',
' '.join(
' '.join(
str(v) for v in coordinate
) for coordinate in swap_coordinates_xy(coordinates[0])
)
)
# fall back to GML encoding when we have:
# - MultiPoint, MultiLineString, MultiPolygon geometries
# - Polygons with interiors
# - GeometryCollections
# - any geometry with CRS other than CRS84 or EPSG4326
# - when dealing with >2D geometries
return GEORSS(
'where',
gml_encoder(geometry, 'ID')
)
geopython-pygml-5ce8eb0/pygml/parse.py 0000664 0000000 0000000 00000004500 14160355441 0020167 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from typing import Union
from lxml import etree
from .georss import NAMESPACE as NAMESPACE_GEORSS, parse_georss
from .pre_v32 import NAMESPACE as NAMESPACE_PRE_v32, parse_pre_v32
from .v32 import NAMESPACE as NAMESPACE_32, parse_v32
from .v33 import NAMESPACE as NAMESPACE_33_CE, parse_v33_ce
from .types import Geometry
def parse(source: Union[etree._Element, str]) -> Geometry:
"""
"""
if etree.iselement(source):
element = source
else:
element = etree.fromstring(source)
namespace = etree.QName(element.tag).namespace
if namespace == NAMESPACE_PRE_v32:
result = parse_pre_v32(element)
elif namespace == NAMESPACE_32:
result = parse_v32(element)
elif namespace == NAMESPACE_33_CE:
result = parse_v33_ce(element)
elif namespace == NAMESPACE_GEORSS:
result = parse_georss(element)
return Geometry(result)
geopython-pygml-5ce8eb0/pygml/pre_v32.py 0000664 0000000 0000000 00000011433 14160355441 0020340 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from typing import List
from lxml import etree
from .types import GeomDict
from .v3_common import (
GML3Encoder, GML3Parser, parse_envelope, parse_multi_linestring,
parse_multi_polygon, parse_point, parse_linestring_or_linear_ring,
parse_multi_curve, parse_polygon, parse_multi_point, parse_multi_surface,
parse_multi_geometry,
)
NAMESPACE = 'http://www.opengis.net/gml'
NSMAP = {'gml': NAMESPACE}
Element = etree._Element
Elements = List[Element]
# set up a parser
GML_PRE32_PARSER = GML3Parser(NAMESPACE, NSMAP, {
'Point': parse_point,
'MultiPoint': parse_multi_point,
'LineString': parse_linestring_or_linear_ring,
'MultiLineString': parse_multi_linestring,
'MultiCurve': parse_multi_curve,
'Polygon': parse_polygon,
'Envelope': parse_envelope,
'MultiPolygon': parse_multi_polygon,
'MultiSurface': parse_multi_surface,
'MultiGeometry': parse_multi_geometry,
})
def parse_pre_v32(element: Element) -> GeomDict:
""" Main parsing function for GML 3.0 and 3.1 XML structures.
The following XML tags can be parsed to their respective GeoJSON
counterpart:
- gml:Point -> Point
- gml:MultiPoint -> MultiPoint
- gml:LineString -> LineString
- gml:MultiCurve (with only gml:LineString curve members)
-> MultiLineString
- gml:MultiLineString -> MultiLineString
- gml:Polygon -> Polygon
- gml:MultiPolygon -> MultiPolygon
- gml:MultiSurface (with only gml:Polygon surface members)
-> MultiPolygon
- gml:MultiGeometry (with any of the aforementioned types as
geometry members) -> GeometryCollection
The SRS of the geometry is determined and the coordinates are
flipped to XY order in GeoJSON when they are in YX order in GML.
Returns:
the parsed GeoJSON geometry as a dict. Contains a 'type'
field, a 'coordinates' field and potentially a 'crs' field
when the geometries SRS could be determined. This field
follows the structure laid out in the
`draft for GeoJSON `_.
"""
return GML_PRE32_PARSER.parse(element)
GML_PRE32_ENCODER = GML3Encoder(NAMESPACE, NSMAP, True)
def encode_pre_v32(geometry: GeomDict, identifier: str = None) -> Element:
""" Encodes the given GeoJSON dict to its most simple GML 3
representation.
In preparation of the encoding, the coordinates may have to be
swapped from XY order to YX order, depending on the used CRS.
This includes the case when no CRS is specified, as this means
the default WGS84 in GeoJSON, which in turn uses
latitude/longitude ordering GML.
This function returns an ``lxml.etree._Element`` which can be
altered or serialized.
>>> from pygml.pre_v32 import encode_pre_v32
>>> from lxml import etree
>>> tree = encode_pre_v32({
... 'type': 'Point',
... 'coordinates': (1.0, 1.0)
... }, 'ID')
>>> print(etree.tostring(tree, pretty_print=True).decode())
1.0 1.0
"""
return GML_PRE32_ENCODER.encode(geometry, identifier)
geopython-pygml-5ce8eb0/pygml/types.py 0000664 0000000 0000000 00000004107 14160355441 0020224 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from dataclasses import dataclass
from typing import List, Optional, Tuple, Union
try:
from typing import TypedDict
class GeomDict(TypedDict, total=True):
type: str
coordinates: Union[Tuple, List]
crs: Optional[dict]
except ImportError:
GeomDict = dict
# Definition of a coordinate list
Coordinate = Tuple[float, ...]
Coordinates = List[Coordinate]
@dataclass(frozen=True)
class Geometry:
""" Simple container class to hold a geometry and expose it via the
``__geo_interface__`` property
"""
geometry: GeomDict
@property
def __geo_interface__(self):
return self.geometry
geopython-pygml-5ce8eb0/pygml/v32.py 0000664 0000000 0000000 00000011154 14160355441 0017472 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from typing import List
from lxml import etree
from .types import GeomDict
from .v3_common import (
GML3Encoder, GML3Parser, parse_envelope, parse_point, parse_multi_point,
parse_linestring_or_linear_ring,
parse_multi_curve, parse_polygon, parse_multi_surface,
parse_multi_geometry
)
NAMESPACE = 'http://www.opengis.net/gml/3.2'
NSMAP = {'gml': NAMESPACE}
Element = etree._Element
Elements = List[Element]
# set up a parser
GML32_PARSER = GML3Parser([NAMESPACE], NSMAP, {
'Point': parse_point,
'MultiPoint': parse_multi_point,
'LineString': parse_linestring_or_linear_ring,
'MultiCurve': parse_multi_curve,
'Polygon': parse_polygon,
'Envelope': parse_envelope,
'MultiSurface': parse_multi_surface,
'MultiGeometry': parse_multi_geometry,
})
def parse_v32(element: Element) -> GeomDict:
""" Main parsing function for GML 3.2 XML structures.
The following XML tags can be parsed to their respective GeoJSON
counterpart:
- gml:Point -> Point
- gml:MultiPoint -> MultiPoint
- gml:LineString -> LineString
- gml:MultiCurve (with only gml:LineString curve members)
-> MultiLineString
- gml:Polygon -> Polygon
- gml:MultiSurface (with only gml:Polygon surface members)
-> MultiPolygon
- gml:MultiGeometry (with any of the aforementioned types as
geometry members) -> GeometryCollection
The SRS of the geometry is determined and the coordinates are
flipped to XY order in GeoJSON when they are in YX order in GML.
Returns:
the parsed GeoJSON geometry as a dict. Contains a 'type'
field, a 'coordinates' field and potentially a 'crs' field
when the geometries SRS could be determined. This field
follows the structure laid out in the
`draft for GeoJSON `_.
"""
return GML32_PARSER.parse(element)
GML32_ENCODER = GML3Encoder(NAMESPACE, NSMAP, True)
def encode_v32(geometry: GeomDict, identifier: str) -> Element:
""" Encodes the given GeoJSON dict to its most simple GML 3.2
representation. As in GML 3.2 the gml:id attribute is mandatory,
the identifier must be passed as well.
In preparation of the encoding, the coordinates may have to be
swapped from XY order to YX order, depending on the used CRS.
This includes the case when no CRS is specified, as this means
the default WGS84 in GeoJSON, which in turn uses
latitude/longitude ordering GML.
This function returns an ``lxml.etree._Element`` which can be
altered or serialized.
>>> from pygml.v32 import encode_v32
>>> from lxml import etree
>>> tree = encode_v32({
... 'type': 'Point',
... 'coordinates': (1.0, 1.0)
... }, 'ID')
>>> print(etree.tostring(tree, pretty_print=True).decode())
1.0 1.0
"""
return GML32_ENCODER.encode(geometry, identifier)
geopython-pygml-5ce8eb0/pygml/v33.py 0000664 0000000 0000000 00000017351 14160355441 0017500 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from lxml import etree
from lxml.builder import ElementMaker
from .types import Coordinates, GeomDict
from .v3_common import (
GML3Encoder, GML3Parser, determine_srs,
parse_envelope, parse_point, parse_multi_point,
parse_linestring_or_linear_ring, parse_multi_curve, parse_polygon,
parse_multi_surface, parse_multi_geometry,
NameSpaceMap, Element, ParseResult
)
from .v32 import NAMESPACE as NAMESPACE_32, GML32_ENCODER
NAMESPACE = 'http://www.opengis.net/gml/3.3/ce'
NSMAP: NameSpaceMap = {
'gmlce': NAMESPACE,
'gml': NAMESPACE_32
}
def parse_simple_triangle_or_rectangle(element: Element,
nsmap: NameSpaceMap) -> ParseResult:
exterior, srs = parse_linestring_or_linear_ring(
element, nsmap
)
exterior = exterior['coordinates']
exterior.append(exterior[0])
return {
'type': 'Polygon',
'coordinates': [exterior]
}, srs
def parse_simple_polygon(element: Element, nsmap: NameSpaceMap) -> ParseResult:
exterior, srs = parse_linestring_or_linear_ring(
element, nsmap
)
exterior = exterior['coordinates']
exterior.append(exterior[0])
return {
'type': 'Polygon',
'coordinates': [exterior]
}, srs
def parse_simple_multi_point(element: Element,
nsmap: NameSpaceMap) -> ParseResult:
sub_elements = element.xpath(
'gml:MultiPoint|gmlce:SimpleMultiPoint', namespaces=nsmap
)
multi_points, srss = zip(*(
parse_multi_point(sub_elem) if etree.QName().localname == 'MultiPoint'
else parse_simple_multi_point(sub_elem)
for sub_elem in sub_elements
))
srs = determine_srs(*srss)
# merge the possibly nested multi points into a single list of coordinates
coordinates = [
coord
for multi_point in multi_points
for coord in multi_point['coordinates']
]
return {
'type': 'MultiPoint',
'coordinates': coordinates
}, srs
GML33_CE_PARSER = GML3Parser([NAMESPACE, NAMESPACE_32], NSMAP, {
'Point': parse_point,
'MultiPoint': parse_multi_point,
'LineString': parse_linestring_or_linear_ring,
'MultiCurve': parse_multi_curve,
'Polygon': parse_polygon,
'Envelope': parse_envelope,
'MultiSurface': parse_multi_surface,
'MultiGeometry': parse_multi_geometry,
'SimpleTriangle': parse_simple_triangle_or_rectangle,
'SimpleRectangle': parse_simple_triangle_or_rectangle,
'SimplePolygon': parse_simple_polygon,
'SimpleMultiPoint': parse_simple_multi_point,
})
def parse_v33_ce(element: Element) -> GeomDict:
""" Main parsing function for GML 3.3 CE XML structures.
The following XML tags can be parsed to their respective GeoJSON
counterpart:
- gmlce:SimpleTriangle -> Polygon
- gmlce:SimpleRectangel -> Polygon
- gmlce:SimplePolygon -> Polygon
- TODO: gmlce:SimpleMultiPoint -> MultiPoint
- gml:Point -> Point
- gml:MultiPoint -> MultiPoint
- gml:LineString -> LineString
- gml:MultiCurve (with only gml:LineString curve members)
-> MultiLineString
- gml:Polygon -> Polygon
- gml:MultiSurface (with only gml:Polygon surface members)
-> MultiPolygon
- gml:MultiGeometry (with any of the aforementioned types as
geometry members) -> GeometryCollection
The SRS of the geometry is determined and the coordinates are
flipped to XY order in GeoJSON when they are in YX order in GML.
Returns:
the parsed GeoJSON geometry as a dict. Contains a 'type'
field, a 'coordinates' field and potentially a 'crs' field
when the geometries SRS could be determined. This field
follows the structure laid out in the
`draft for GeoJSON `_.
"""
return GML33_CE_PARSER.parse(element)
class GML33CEEncoder(GML3Encoder):
def __init__(self):
super().__init__(NAMESPACE_32, NSMAP, True)
self.gmlce = ElementMaker(namespace=NAMESPACE, nsmap=NSMAP)
def encode_polygon(self, coordinates: Coordinates, attrs: dict) -> Element:
if len(coordinates) == 1:
exterior = coordinates[0]
tag_name = None
if len(exterior) == 4:
tag_name = 'SimpleTriangle'
elif len(exterior) == 5:
tag_name = 'SimpleRectangle'
else:
tag_name = 'SimplePolygon'
return self.gmlce(
tag_name,
GML32_ENCODER._encode_pos_list(exterior[:-1]),
**attrs
)
return super().encode_polygon(coordinates, attrs)
GML33CE_ENCODER = GML33CEEncoder()
def encode_v33_ce(geometry: GeomDict, identifier: str) -> Element:
""" Encodes the given GeoJSON dict to its most simple GML 3.3 CE
representation, with a fallback to encoding it GML 3.2 when the
compact encoding is not possible. As in GML 3.2 the gml:id attribute
is mandatory, the identifier must be passed as well.
In preparation of the encoding, the coordinates may have to be
swapped from XY order to YX order, depending on the used CRS.
This includes the case when no CRS is specified, as this means
the default WGS84 in GeoJSON, which in turn uses
latitude/longitude ordering GML.
This function returns an ``lxml.etree._Element`` which can be
altered or serialized.
>>> from pygml.v33 import encode_v33_ce
>>> from lxml import etree
>>> tree = encode_v33_ce({
... 'type': 'Polygon',
... 'coordinates': [
... [
... (1.0, 2.0),
... (1.0, 3.0),
... (2.0, 2.0),
... (1.0, 2.0),
... ],
... ],
... }, 'ID')
>>> print(etree.tostring(tree, pretty_print=True).decode())
1.0 2.0 1.0 3.0 2.0 2.0
"""
return GML33CE_ENCODER.encode(geometry, identifier)
geopython-pygml-5ce8eb0/pygml/v3_common.py 0000664 0000000 0000000 00000050310 14160355441 0020755 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from typing import Callable, List, Optional, Tuple, Dict
from lxml import etree
from lxml.builder import ElementMaker
from .axisorder import is_crs_yx
from .basics import (
parse_coordinates, parse_pos, parse_poslist, swap_coordinates_xy
)
from .types import Coordinate, Coordinates, GeomDict
# type aliases
NameSpaceMap = Dict[str, str]
Element = etree._Element
Elements = List[Element]
ParseResult = Tuple[GeomDict, str]
HandlerFunc = Callable[[Element, NameSpaceMap], ParseResult]
class GML3Parser:
def __init__(self, namespaces: List[str], nsmap: NameSpaceMap,
handlers: Dict[str, HandlerFunc]):
self.namespaces = namespaces
self.nsmap = nsmap
self.handlers = handlers
def parse(self, element: Element) -> GeomDict:
qname = etree.QName(element.tag)
if qname.namespace not in self.namespaces:
raise ValueError(f'Namespace {qname.namespace} is not supported')
# get a registered handler function
handler = self.handlers.get(qname.localname)
if not handler:
raise ValueError(
f'XML nodes of type {qname.localname} are not supported.'
)
# parse the geometry
if qname.localname == 'MultiGeometry':
geometry, srs = handler(element, self.nsmap, self.parse)
else:
geometry, srs = handler(element, self.nsmap)
# handle SRS: maybe swap YX ordered coordinates to XY
# and store the SRS as a crs field in the geometry
if srs:
geometry = maybe_swap_coordinates(geometry, srs)
geometry['crs'] = {
'type': 'name',
'properties': {
'name': srs
}
}
return geometry
def maybe_swap_coordinates(geometry: GeomDict, srs: str) -> GeomDict:
if is_crs_yx(srs):
type_ = geometry['type']
coordinates = geometry['coordinates']
if type_ == 'Point':
coordinates = (coordinates[1], coordinates[0], *coordinates[2:])
elif type_ in ('MultiPoint', 'LineString'):
coordinates = swap_coordinates_xy(coordinates)
elif type_ in ('MultiLineString', 'Polygon'):
coordinates = [
swap_coordinates_xy(line)
for line in coordinates
]
elif type_ == 'MultiPolygon':
coordinates = [
[
swap_coordinates_xy(line)
for line in polygon
] for polygon in coordinates
]
geometry['coordinates'] = coordinates
return geometry
else:
return geometry
def determine_srs(*srss: List[Optional[str]]) -> Optional[str]:
srss = set(srss)
if None in srss:
srss.remove(None)
if len(srss) > 1:
raise ValueError(f'Conflicting SRS definitions: {", ".join(srss)}')
try:
return srss.pop()
except KeyError:
return None
def parse_coord(element: Element, nsmap: NameSpaceMap) -> Coordinate:
x = float(element.xpath('gml:X/text()')[0])
y = element.xpath('gml:X/text()')
z = element.xpath('gml:X/text()')
if y and z:
return (x, float(y[0]), float(z[0]))
elif y:
return (x, float(y[0]))
return (x,)
def parse_point(element: Element, nsmap: NameSpaceMap) -> ParseResult:
positions = element.xpath('gml:pos', namespaces=nsmap)
coordinates = element.xpath('gml:coordinates', namespaces=nsmap)
coord_elemss = element.xpath('gml:coord', namespaces=nsmap)
srs = None
if positions:
if len(positions) > 1:
raise ValueError('Too many gml:pos elements')
coords = parse_pos(positions[0].text)
srs = positions[0].attrib.get('srsName')
elif coordinates:
if len(coordinates) > 1:
raise ValueError('Too many gml:coordinates elements')
coordinates0 = coordinates[0]
coords = parse_coordinates(
coordinates0.text,
cs=coordinates0.attrib.get('cs', ','),
ts=coordinates0.attrib.get('ts', ' '),
decimal=coordinates0.attrib.get('decimal', '.'),
)[0]
elif coord_elemss:
if len(coord_elemss) > 1:
raise ValueError('Too many gml:coord elements')
coords = parse_coord(coord_elemss[0])
else:
raise ValueError(
'Neither gml:pos nor gml:coordinates found'
)
srs = determine_srs(element.attrib.get('srsName'), srs)
return {
'type': 'Point',
'coordinates': coords
}, srs
def parse_multi_point(element: Element, nsmap: NameSpaceMap) -> ParseResult:
points, srss = zip(*(
parse_point(point_elem, nsmap)
for point_elem in element.xpath(
'(gml:pointMember|gml:pointMembers)/*', namespaces=nsmap
)
))
srs = determine_srs(element.attrib.get('srsName'), *srss)
return {
'type': 'MultiPoint',
'coordinates': [
point['coordinates']
for point in points
]
}, srs
def parse_linestring_or_linear_ring(element: Element,
nsmap: NameSpaceMap) -> ParseResult:
pos_lists = element.xpath('gml:posList', namespaces=nsmap)
poss = element.xpath('gml:pos', namespaces=nsmap)
coordinates_elems = element.xpath('gml:coordinates', namespaces=nsmap)
coords = element.xpath('gml:coord', namespaces=nsmap)
if pos_lists:
if len(pos_lists) > 1:
raise ValueError('Too many gml:posList elements')
pos_list0 = pos_lists[0]
coordinates = parse_poslist(
pos_list0.text,
int(pos_list0.attrib.get('srsDimension', 2))
)
srs = pos_list0.attrib.get('srsName')
elif poss:
coordinates = [
parse_pos(pos.text)
for pos in poss
]
srs = determine_srs(
*element.xpath('gml:pos/@srsName', namespaces=nsmap)
)
elif coordinates_elems:
if len(coordinates_elems) > 1:
raise ValueError('Too many gml:coordinates elements')
coordinates0 = coordinates_elems[0]
coordinates = parse_coordinates(
coordinates0.text,
cs=coordinates0.attrib.get('cs', ','),
ts=coordinates0.attrib.get('ts', ' '),
decimal=coordinates0.attrib.get('decimal', '.'),
)
srs = None
elif coords:
coordinates = [
parse_coord(coord)
for coord in coords
]
srs = None
else:
raise ValueError('No gml:posList, gml:pos or gml:coordinates found')
srs = determine_srs(element.attrib.get('srsName'), srs)
return {
'type': 'LineString',
'coordinates': coordinates
}, srs
def parse_multi_curve(element: Element, nsmap: NameSpaceMap) -> ParseResult:
linestring_elements = element.xpath(
'(gml:curveMember|gml:curveMembers)/gml:LineString',
namespaces=nsmap
)
are_not_linestrings = (
etree.QName(e.tag).localname != 'LineString'
for e in linestring_elements
)
if any(are_not_linestrings):
raise ValueError(
'Only gml:LineString elements are supported for gml:MultiCurves'
)
linestrings, srss = zip(*(
parse_linestring_or_linear_ring(linestring_element, nsmap)
for linestring_element in linestring_elements
))
srs = determine_srs(element.attrib.get('srsName'), *srss)
return {
'type': 'MultiLineString',
'coordinates': [
linestring['coordinates']
for linestring in linestrings
]
}, srs
def parse_multi_linestring(element: Element,
nsmap: NameSpaceMap) -> ParseResult:
linestring_elements = element.xpath(
'gml:lineStringMember/gml:LineString',
namespaces=nsmap
)
linestrings, srss = zip(*(
parse_linestring_or_linear_ring(linestring_element, nsmap)
for linestring_element in linestring_elements
))
srs = determine_srs(element.attrib.get('srsName'), *srss)
return {
'type': 'MultiLineString',
'coordinates': [
linestring['coordinates']
for linestring in linestrings
]
}, srs
def parse_polygon(element: Element, nsmap: NameSpaceMap) -> ParseResult:
exterior_rings = element.xpath(
'gml:exterior/gml:LinearRing', namespaces=nsmap
)
if not exterior_rings:
raise ValueError('No gml:exterior/gml:LinearRing')
elif len(exterior_rings) > 1:
raise ValueError('Too many gml:exterior/gml:LinearRing elements')
exterior, ext_srs = parse_linestring_or_linear_ring(
exterior_rings[0], nsmap
)
exterior = exterior['coordinates']
interior_elems = element.xpath(
'gml:interior/gml:LinearRing', namespaces=nsmap
)
if len(interior_elems) > 0:
interior_rings, int_srss = zip(*(
parse_linestring_or_linear_ring(linear_ring, nsmap)
for linear_ring in interior_elems
))
interiors = [
ring['coordinates']
for ring in interior_rings
]
else:
interiors = []
int_srss = []
srs = determine_srs(element.attrib.get('srsName'), ext_srs, *int_srss)
return {
'type': 'Polygon',
'coordinates': [exterior, *interiors]
}, srs
def parse_multi_surface(element: Element, nsmap: NameSpaceMap) -> ParseResult:
polygon_elements = element.xpath(
'(gml:surfaceMember|gml:surfaceMembers)/gml:Polygon',
namespaces=nsmap
)
are_not_polygons = (
etree.QName(e.tag).localname != 'Polygon' for e in polygon_elements
)
if any(are_not_polygons):
raise ValueError(
'Only gml:Polygon elements are supported for gml:MultiSurfaces'
)
polygons, srss = zip(*(
parse_polygon(polygon_element, nsmap)
for polygon_element in polygon_elements
))
srs = determine_srs(element.attrib.get('srsName'), *srss)
return {
'type': 'MultiPolygon',
'coordinates': [
polygon['coordinates']
for polygon in polygons
]
}, srs
def parse_multi_polygon(element: Element, nsmap: NameSpaceMap) -> ParseResult:
polygon_elements = element.xpath(
'gml:polygonMember/gml:Polygon',
namespaces=nsmap
)
polygons, srss = zip(*(
parse_polygon(polygon_element, nsmap)
for polygon_element in polygon_elements
))
srs = determine_srs(element.attrib.get('srsName'), *srss)
return {
'type': 'MultiPolygon',
'coordinates': [
polygon['coordinates']
for polygon in polygons
]
}, srs
def parse_envelope(element: Element, nsmap: NameSpaceMap) -> ParseResult:
lower = element.xpath('gml:lowerCorner', namespaces=nsmap)
upper = element.xpath('gml:upperCorner', namespaces=nsmap)
pos_elems = element.xpath('gml:pos', namespaces=nsmap)
coordinates = element.xpath('gml:coordinates', namespaces=nsmap)
coords = element.xpath('gml:coord', namespaces=nsmap)
if lower and upper:
lower = lower[0]
upper = upper[0]
srs = determine_srs(
lower.attrib.get('srsName'),
upper.attrib.get('srsName')
)
lower = parse_pos(lower.text)
upper = parse_pos(upper.text)
elif pos_elems:
lower, upper = [
parse_pos(pos_elem.text)
for pos_elem in pos_elems
]
srs = determine_srs(*(
pos_elem.attrib.get('srsName') for pos_elem in pos_elems
))
elif coordinates:
coordinates0 = coordinates[0]
lower, upper = parse_coordinates(
coordinates0.text,
cs=coordinates0.attrib.get('cs', ','),
ts=coordinates0.attrib.get('ts', ' '),
decimal=coordinates0.attrib.get('decimal', '.'),
)
srs = None
elif coords:
lower, upper = [
parse_coord(coord)
for coord in coords
]
srs = None
else:
raise ValueError(
'Missing gml:lowerCorner, gml:upperCorner, gml:pos or '
'gml:coordinates.'
)
lx, ly = lower
hx, hy = upper
return {
'type': 'Polygon',
'coordinates': [
[
(lx, ly),
(lx, hy),
(hx, hy),
(hx, ly),
(lx, ly),
]
]
}, srs
SubParser = Callable[[Element], GeomDict]
def parse_multi_geometry(element: Element, nsmap: NameSpaceMap,
geometry_parser: SubParser) -> ParseResult:
sub_elements = element.xpath(
'(gml:geometryMember|gml:geometryMembers)/*', namespaces=nsmap
)
return {
'type': 'GeometryCollection',
'geometries': [
geometry_parser(sub_element)
for sub_element in sub_elements
]
}, element.attrib.get('srsName')
class GML3Encoder:
def __init__(self, namespace: str, nsmap: NameSpaceMap, id_required: bool):
self.namespace = namespace
self.nsmap = nsmap
self.id_required = id_required
self.gml = ElementMaker(namespace=namespace, nsmap=nsmap)
def encode(self, geometry: GeomDict, identifier: str = None) -> Element:
if not identifier and self.id_required:
raise TypeError(
"Missing 1 required positional argument: 'identifier'"
)
gml = self.gml
crs = geometry.get('crs')
srs = None
if identifier:
id_attr = {f'{{{self.namespace}}}id': identifier}
else:
id_attr = {}
if crs:
srs = crs.get('properties', {}).get('name')
else:
# GeoJSON is by default in CRS84
srs = 'urn:ogc:def:crs:OGC::CRS84'
attrs = {
'srsName': srs,
**id_attr
}
geometry = maybe_swap_coordinates(geometry, srs)
type_ = geometry['type']
# GeometryCollections have no coordinates
coordinates = geometry.get('coordinates')
if type_ == 'Point':
return self.encode_point(coordinates, attrs)
elif type_ == 'MultiPoint':
return self.encode_multi_point(coordinates, identifier, attrs)
elif type_ == 'LineString':
return self.encode_line_string(coordinates, attrs)
elif type_ == 'MultiLineString':
return self.encode_multi_line_string(
coordinates, identifier, attrs
)
elif type_ == 'Polygon':
return self.encode_polygon(coordinates, attrs)
elif type_ == 'MultiPolygon':
return self.encode_multi_polygon(coordinates, identifier, attrs)
elif type_ == 'GeometryCollection':
geometries = geometry['geometries']
return gml(
'MultiGeometry',
gml(
'geometryMembers', *[
self.encode(sub_geometry, f'{identifier}_{i}')
for i, sub_geometry in enumerate(geometries)
]
),
**id_attr
)
raise ValueError(f'Unable to encode geometry of type {type_}')
def encode_point(self, coordinates: Coordinates, attrs: dict) -> Element:
return self.gml(
'Point',
self.gml('pos', ' '.join(str(c) for c in coordinates)),
**attrs
)
def encode_multi_point(self, coordinates: Coordinates, identifier: str,
attrs: dict) -> Element:
return self.gml(
'MultiPoint',
self.gml('geometryMembers', *[
self.gml(
'Point',
self.gml('pos', ' '.join(str(c) for c in coordinate)),
**{f'{{{self.namespace}}}id': f'{identifier}_{i}'}
)
for i, coordinate in enumerate(coordinates)
]),
**attrs
)
def encode_line_string(self, coordinates: Coordinates,
attrs: dict) -> Element:
return self.gml(
'LineString',
self._encode_pos_list(coordinates),
**attrs
)
def encode_multi_line_string(self, coordinates: Coordinates,
identifier: str, attrs: dict) -> Element:
return self.gml(
'MultiCurve',
self.gml(
'curveMembers', *[
self.gml(
'LineString',
self._encode_pos_list(linestring),
**{f'{{{self.namespace}}}id': f'{identifier}_{i}'}
)
for i, linestring in enumerate(coordinates)
]
),
**attrs
)
def encode_polygon(self, coordinates: Coordinates,
attrs: dict) -> Element:
return self.gml(
'Polygon',
self.gml(
'exterior',
self.gml(
'LinearRing',
self._encode_pos_list(coordinates[0]),
)
), *[
self.gml(
'interior',
self.gml(
'LinearRing',
self._encode_pos_list(linear_ring),
)
)
for linear_ring in coordinates[1:]
],
**attrs
)
def encode_multi_polygon(self, coordinates: Coordinates,
identifier: str, attrs: dict) -> Element:
return self.gml(
'MultiSurface',
self.gml(
'surfaceMembers', *[
self.gml(
'Polygon',
self.gml(
'exterior',
self.gml(
'LinearRing',
self._encode_pos_list(polygon[0]),
)
), *[
self.gml(
'interior',
self.gml(
'LinearRing',
self._encode_pos_list(linear_ring),
)
)
for linear_ring in polygon[1:]
],
**{f'{{{self.namespace}}}id': f'{identifier}_{i}'}
)
for i, polygon in enumerate(coordinates)
]
),
**attrs
)
def _encode_pos_list(self, coordinates: Coordinates) -> Element:
return self.gml(
'posList',
' '.join(
' '.join(str(c) for c in coordinate)
for coordinate in coordinates
)
)
geopython-pygml-5ce8eb0/requirements-test.txt 0000664 0000000 0000000 00000000013 14160355441 0021607 0 ustar 00root root 0000000 0000000 pytest
lxml geopython-pygml-5ce8eb0/setup.py 0000664 0000000 0000000 00000005766 14160355441 0017104 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this 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.
# ------------------------------------------------------------------------------
"""Install pygml."""
from setuptools import find_packages, setup
import os
import os.path
# don't install dependencies when building win readthedocs
on_rtd = os.environ.get('READTHEDOCS') == 'True'
# get version number
# from https://github.com/mapbox/rasterio/blob/master/setup.py#L55
with open(os.path.join(os.path.dirname(__file__), 'pygml/__init__.py')) as f:
for line in f:
if line.find("__version__") >= 0:
version = line.split("=")[1].strip()
version = version.strip('"')
version = version.strip("'")
break
# use README.md for project long_description
with open('README.md') as f:
readme = f.read()
setup(
name='pygml',
version=version,
description='Parsing GML geometries',
long_description=readme,
long_description_content_type="text/markdown",
author='Fabian Schindler',
author_email='fabian.schindler@eox.at',
url='https://github.com/geopython/pygml',
license='MIT',
packages=find_packages(),
include_package_data=True,
install_requires=[
"dataclasses;python_version<'3.7'",
"lxml",
] if not on_rtd else [],
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'Topic :: Scientific/Engineering :: GIS',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
],
tests_require=['pytest']
)
geopython-pygml-5ce8eb0/tests/ 0000775 0000000 0000000 00000000000 14160355441 0016516 5 ustar 00root root 0000000 0000000 geopython-pygml-5ce8eb0/tests/__init__.py 0000664 0000000 0000000 00000000000 14160355441 0020615 0 ustar 00root root 0000000 0000000 geopython-pygml-5ce8eb0/tests/test_axisorder.py 0000664 0000000 0000000 00000003060 14160355441 0022126 0 ustar 00root root 0000000 0000000 import pytest
from pygml.axisorder import is_crs_yx, get_crs_code
def test_get_crs_code():
# test with a reversed code
assert get_crs_code('EPSG:4326') == 4326
assert get_crs_code('http://www.opengis.net/def/crs/EPSG/0/4326') == 4326
assert get_crs_code('http://www.opengis.net/gml/srs/epsg.xml#4326') == 4326
assert get_crs_code('urn:EPSG:geographicCRS:4326') == 4326
assert get_crs_code('urn:ogc:def:crs:EPSG::4326') == 4326
assert get_crs_code('urn:ogc:def:crs:EPSG:4326') == 4326
assert get_crs_code('urn:ogc:def:crs:OGC::CRS84') == 'CRS84'
# test with some garbage format
with pytest.raises(ValueError):
get_crs_code('abcd:4326')
def test_is_crs_yx():
# test with a reversed code
assert is_crs_yx('EPSG:4326')
assert is_crs_yx('http://www.opengis.net/def/crs/EPSG/0/4326')
assert is_crs_yx('http://www.opengis.net/gml/srs/epsg.xml#4326')
assert is_crs_yx('urn:EPSG:geographicCRS:4326')
assert is_crs_yx('urn:ogc:def:crs:EPSG::4326')
assert is_crs_yx('urn:ogc:def:crs:EPSG:4326')
# test with a non-reversed code
assert is_crs_yx('EPSG:3857') is False
assert is_crs_yx('http://www.opengis.net/def/crs/EPSG/0/3857') is False
assert is_crs_yx('http://www.opengis.net/gml/srs/epsg.xml#3857') is False
assert is_crs_yx('urn:EPSG:geographicCRS:3857') is False
assert is_crs_yx('urn:ogc:def:crs:EPSG::3857') is False
assert is_crs_yx('urn:ogc:def:crs:EPSG:3857') is False
# test with some garbage format
with pytest.raises(ValueError):
is_crs_yx('abcd:4326')
geopython-pygml-5ce8eb0/tests/test_basics.py 0000664 0000000 0000000 00000004514 14160355441 0021377 0 ustar 00root root 0000000 0000000 import pytest
from pygml.basics import (
parse_coordinates, parse_poslist, parse_pos, swap_coordinate_xy,
swap_coordinates_xy
)
def test_parse_coordinates():
# basic test
result = parse_coordinates('12.34 56.7,89.10 11.12')
assert result == [(12.34, 56.7), (89.10, 11.12)]
# ignore some whitespace
result = parse_coordinates('12.34 56.7, 89.10 11.12')
assert result == [(12.34, 56.7), (89.10, 11.12)]
# custom cs
result = parse_coordinates('12.34 56.7;89.10 11.12', cs=';')
assert result == [(12.34, 56.7), (89.10, 11.12)]
# custom ts
result = parse_coordinates('12.34:56.7,89.10:11.12', ts=':')
assert result == [(12.34, 56.7), (89.10, 11.12)]
# custom cs/ts
result = parse_coordinates('12.34:56.7;89.10:11.12', cs=';', ts=':')
assert result == [(12.34, 56.7), (89.10, 11.12)]
# custom cs/ts and decimal
result = parse_coordinates(
'12,34:56,7;89,10:11,12', cs=';', ts=':', decimal=','
)
assert result == [(12.34, 56.7), (89.10, 11.12)]
def test_parse_poslist():
# basic test
result = parse_poslist('12.34 56.7 89.10 11.12')
assert result == [(12.34, 56.7), (89.10, 11.12)]
# 3D coordinates
result = parse_poslist('12.34 56.7 89.10 11.12 13.14 15.16', dimensions=3)
assert result == [(12.34, 56.7, 89.10), (11.12, 13.14, 15.16)]
# exception on wrong dimensionality
with pytest.raises(ValueError):
parse_poslist('12.34 56.7 89.10 11.12', dimensions=3)
def test_parse_pos():
# basic test
result = parse_pos('12.34 56.7')
assert result == (12.34, 56.7)
# 3D pos
result = parse_pos('12.34 56.7 89.10')
assert result == (12.34, 56.7, 89.10)
def test_swap_coordinate_xy():
# basic test
swapped = swap_coordinate_xy((12.34, 56.7))
assert swapped == (56.7, 12.34)
# 3D coords, only X/Y are to be swapped
swapped = swap_coordinate_xy((12.34, 56.7, 89.10))
assert swapped == (56.7, 12.34, 89.10)
def test_swap_coordinates_xy():
# basic test
swapped = swap_coordinates_xy(
[(12.34, 56.7), (89.10, 11.12)]
)
assert swapped == [(56.7, 12.34), (11.12, 89.10)]
# 3D coords, only X/Y are to be swapped
swapped = swap_coordinates_xy(
[(12.34, 56.7, 89.10), (11.12, 13.14, 15.16)]
)
assert swapped == [(56.7, 12.34, 89.10), (13.14, 11.12, 15.16)]
geopython-pygml-5ce8eb0/tests/test_dimensionality.py 0000664 0000000 0000000 00000013523 14160355441 0023163 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from pygml.dimensionality import get_dimensionality
def test_dimensionality_point():
assert 2 == get_dimensionality({
'type': 'Point',
'coordinates': (1.0, 1.0)
})
assert 3 == get_dimensionality({
'type': 'Point',
'coordinates': (1.0, 1.0, 1.0)
})
def test_dimensionality_multi_point():
assert 2 == get_dimensionality({
'type': 'MultiPoint',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
]
})
assert 3 == get_dimensionality({
'type': 'MultiPoint',
'coordinates': [
(1.0, 1.0, 1.0),
(2.0, 2.0, 1.0),
]
})
def test_dimensionality_linestring():
assert 2 == get_dimensionality({
'type': 'LineString',
'coordinates': [
(2.0, 1.0),
(1.0, 2.0)
]
})
assert 3 == get_dimensionality({
'type': 'LineString',
'coordinates': [
(2.0, 1.0, 1.0),
(1.0, 2.0, 1.0)
]
})
def test_dimensionality_multi_linestring():
assert 2 == get_dimensionality({
'type': 'MultiLineString',
'coordinates': [
[
(1.0, 1.0),
(2.0, 2.0)
], [
(3.0, 3.0),
(4.0, 4.0)
],
]
})
assert 3 == get_dimensionality({
'type': 'MultiLineString',
'coordinates': [
[
(1.0, 1.0, 1.0),
(2.0, 2.0, 1.0)
], [
(3.0, 3.0, 1.0),
(4.0, 4.0, 1.0)
],
]
})
def test_dimensionality_polygon():
assert 2 == get_dimensionality({
'type': 'Polygon',
'coordinates': [
[
(0.5, 1.0),
(0.5, 2.0),
(1.5, 2.0),
(1.5, 1.0),
(0.5, 1.0)
]
]
})
assert 3 == get_dimensionality({
'type': 'Polygon',
'coordinates': [
[
(0.5, 1.0, 1.0),
(0.5, 2.0, 1.0),
(1.5, 2.0, 1.0),
(1.5, 1.0, 1.0),
(0.5, 1.0, 1.0)
]
]
})
def test_dimensionality_multi_polygon():
assert 2 == get_dimensionality({
'type': 'MultiPolygon',
'coordinates': [
[
[
(0.0, 0.0),
(1.0, 0.0),
(0.0, 1.0),
(0.0, 0.0)
],
[
(0.2, 0.2),
(0.5, 0.2),
(0.2, 0.5),
(0.2, 0.2)
],
],
[
[
(10.0, 10.0),
(11.0, 10.0),
(10.0, 11.0),
(10.0, 10.0)
],
[
(10.2, 10.2),
(10.5, 10.2),
(10.2, 10.5),
(10.2, 10.2)
],
]
]
})
assert 3 == get_dimensionality({
'type': 'MultiPolygon',
'coordinates': [
[
[
(0.0, 0.0, 1.0),
(1.0, 0.0, 1.0),
(0.0, 1.0, 1.0),
(0.0, 0.0, 1.0)
],
[
(0.2, 0.2, 1.0),
(0.5, 0.2, 1.0),
(0.2, 0.5, 1.0),
(0.2, 0.2, 1.0)
],
],
[
[
(10.0, 10.0, 1.0),
(11.0, 10.0, 1.0),
(10.0, 11.0, 1.0),
(10.0, 10.0, 1.0)
],
[
(10.2, 10.2, 1.0),
(10.5, 10.2, 1.0),
(10.2, 10.5, 1.0),
(10.2, 10.2, 1.0)
],
]
]
})
def test_dimensionality_geometrycollection():
assert None is get_dimensionality({
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 1.0)
},
{
'type': 'Polygon',
'coordinates': [
[(1.0, 1.0)],
[(1.0, 1.0)],
]
},
]
})
geopython-pygml-5ce8eb0/tests/test_georss.py 0000664 0000000 0000000 00000025055 14160355441 0021440 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from lxml import etree
from pygml.georss import encode_georss, parse_georss
from .util import compare_trees
def test_parse_point():
# basic test
result = parse_georss(
etree.fromstring("""
1.0 1.0
""")
)
assert result == {'type': 'Point', 'coordinates': (1.0, 1.0)}
def test_parse_line():
# basic test
result = parse_georss(
etree.fromstring("""
1.0 2.0 2.0 1.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(2.0, 1.0),
(1.0, 2.0)
]
}
def test_parse_box():
# basic test
result = parse_georss(
etree.fromstring("""
1.0 0.5 2.0 1.5
""")
)
assert result == {
'type': 'Polygon',
'bbox': (0.5, 1.0, 1.5, 2.0),
'coordinates': [
[
(0.5, 1.0),
(0.5, 2.0),
(1.5, 2.0),
(1.5, 1.0),
(0.5, 1.0)
]
]
}
def test_parse_polygon():
# basic test
result = parse_georss(
etree.fromstring("""
1.0 0.5 2.0 0.5 2.0 1.5 1.0 1.5 1.0 0.5
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.5, 1.0),
(0.5, 2.0),
(1.5, 2.0),
(1.5, 1.0),
(0.5, 1.0)
]
]
}
def test_parse_where():
# TODO: add tests as soon as gml 3.1 is done
pass
def test_encode_point():
# test that simple points can be encoded
result = encode_georss({'type': 'Point', 'coordinates': (1.0, 2.0)})
expected = etree.fromstring("""
2.0 1.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# test that >2D geometries or with a specific CRS
# can only be encoded using georss:where and GML
result = encode_georss({'type': 'Point', 'coordinates': (1.0, 2.0, 1.0)})
assert result.tag == '{http://www.georss.org/georss}where'
assert etree.QName(result[0].tag).localname == 'Point'
result = encode_georss({
'type': 'Point',
'coordinates': (1.0, 2.0),
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:3857'
}
}
})
assert result.tag == '{http://www.georss.org/georss}where'
assert etree.QName(result[0].tag).localname == 'Point'
def test_encode_multi_point():
result = encode_georss({
'type': 'MultiPoint',
'coordinates': [
(1.0, 2.0),
(3.0, 4.0),
]
})
assert result.tag == '{http://www.georss.org/georss}where'
assert etree.QName(result[0].tag).localname == 'MultiPoint'
def test_encode_linestring():
# test that simple points can be encoded
result = encode_georss({
'type': 'LineString',
'coordinates': [
(1.0, 2.0),
(3.0, 4.0),
]
})
expected = etree.fromstring("""
2.0 1.0 4.0 3.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# test that >2D geometries or with a specific CRS
# can only be encoded using georss:where and GML
result = encode_georss({
'type': 'LineString',
'coordinates': [
(1.0, 2.0, 1.0),
(3.0, 4.0, 1.0),
]
})
assert result.tag == '{http://www.georss.org/georss}where'
assert etree.QName(result[0].tag).localname == 'LineString'
result = encode_georss({
'type': 'LineString',
'coordinates': [
(1.0, 2.0),
(3.0, 4.0),
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:3857'
}
}
})
assert result.tag == '{http://www.georss.org/georss}where'
assert etree.QName(result[0].tag).localname == 'LineString'
def test_encode_multi_linestring():
result = encode_georss({
'type': 'MultiLineString',
'coordinates': [
[
(1.0, 2.0),
(3.0, 4.0),
], [
(11.0, 12.0),
(13.0, 14.0),
]
]
})
assert result.tag == '{http://www.georss.org/georss}where'
assert etree.QName(result[0].tag).localname == 'MultiCurve'
def test_encode_polygon():
result = encode_georss({
'type': 'Polygon',
'coordinates': [
[
(0.5, 1.0),
(0.5, 2.0),
(1.5, 2.0),
(1.5, 1.0),
(0.5, 1.0)
]
]
})
expected = etree.fromstring("""
1.0 0.5 2.0 0.5 2.0 1.5 1.0 1.5 1.0 0.5
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# test that >2D geometries or with a specific CRS or polygons with holes
# can only be encoded using georss:where and GML
result = encode_georss({
'type': 'Polygon',
'coordinates': [
[
(0.5, 1.0),
(0.5, 2.0),
(1.5, 2.0),
(1.5, 1.0),
(0.5, 1.0)
], [
(0.6, 1.1),
(0.6, 1.9),
(1.4, 1.9),
(1.4, 1.1),
(0.6, 1.1)
]
]
})
assert result.tag == '{http://www.georss.org/georss}where'
assert etree.QName(result[0].tag).localname == 'Polygon'
result = encode_georss({
'type': 'Polygon',
'coordinates': [
[
(0.5, 1.0, 1.0),
(0.5, 2.0, 1.0),
(1.5, 2.0, 1.0),
(1.5, 1.0, 1.0),
(0.5, 1.0, 1.0)
]
]
})
assert result.tag == '{http://www.georss.org/georss}where'
assert etree.QName(result[0].tag).localname == 'Polygon'
result = encode_georss({
'type': 'Polygon',
'coordinates': [
[
(0.5, 1.0),
(0.5, 2.0),
(1.5, 2.0),
(1.5, 1.0),
(0.5, 1.0)
]
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:3857'
}
}
})
assert result.tag == '{http://www.georss.org/georss}where'
assert etree.QName(result[0].tag).localname == 'Polygon'
def test_encode_multi_polygon():
result = encode_georss({
'type': 'MultiPolygon',
'coordinates': [
[
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
], [
[
(11.0, 12.0),
(11.0, 13.0),
(12.0, 13.0),
(12.0, 12.0),
(11.0, 12.0),
], [
(11.4, 12.4),
(11.4, 12.6),
(11.6, 12.6),
(11.6, 12.4),
(11.4, 12.4),
],
],
],
})
assert result.tag == '{http://www.georss.org/georss}where'
assert etree.QName(result[0].tag).localname == 'MultiSurface'
def test_encode_geometry_collection():
result = encode_georss({
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 2.0),
},
{
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
],
},
]
})
assert result.tag == '{http://www.georss.org/georss}where'
assert etree.QName(result[0].tag).localname == 'MultiGeometry'
geopython-pygml-5ce8eb0/tests/test_pre_v32.py 0000664 0000000 0000000 00000151147 14160355441 0021420 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from lxml import etree
import pytest
from pygml.pre_v32 import encode_pre_v32, parse_pre_v32
from .util import compare_trees
def test_parse_point():
# basic test
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0
""")
)
assert result == {'type': 'Point', 'coordinates': (1.0, 1.0)}
# using gml:coordinates instead
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0
""")
)
assert result == {'type': 'Point', 'coordinates': (1.0, 1.0)}
# axis order swapping with srsName in pos or Point
result = parse_pre_v32(
etree.fromstring("""
2.0 1.0
""")
)
assert result == {
'type': 'Point',
'coordinates': (1.0, 2.0),
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
result = parse_pre_v32(
etree.fromstring("""
2.0 1.0
""")
)
assert result == {
'type': 'Point',
'coordinates': (1.0, 2.0),
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# conflicting srsName
with pytest.raises(ValueError):
parse_pre_v32(
etree.fromstring("""
2.0 1.0
""")
)
def test_parse_multi_point():
# using gml:pointMember
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
""")
)
assert result == {
'type': 'MultiPoint',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
]
}
# using gml:pointMembers
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
""")
)
assert result == {
'type': 'MultiPoint',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
]
}
# using gml:pointMember and gml:pointMembers
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
3.0 3.0
""")
)
assert result == {
'type': 'MultiPoint',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
(3.0, 3.0),
]
}
# conflicting srsName
with pytest.raises(ValueError):
parse_pre_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
""")
)
def test_parse_linestring():
# from gml:posList
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
]
}
# from gml:pos elements
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
]
}
# from gml:coordinates
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0,2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
]
}
# from gml:pos elements with srsName
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# from gml:posList element with srsName
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# from gml:coordinates element with srsName
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0,2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# srsName conflict
with pytest.raises(ValueError):
parse_pre_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
""")
)
def test_parse_multi_curve():
# using gml:curveMember elements
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
assert result == {
'type': 'MultiLineString',
'coordinates': [
[(1.0, 1.0), (2.0, 2.0)],
[(3.0, 3.0), (4.0, 4.0)],
]
}
# using gml:curveMembers element
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
assert result == {
'type': 'MultiLineString',
'coordinates': [
[(1.0, 1.0), (2.0, 2.0)],
[(3.0, 3.0), (4.0, 4.0)],
]
}
# determine srsName from MultiCurve
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
assert result == {
'type': 'MultiLineString',
'coordinates': [
[(1.0, 1.0), (2.0, 2.0)],
[(3.0, 3.0), (4.0, 4.0)],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# determine srsName from first LineString
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
assert result == {
'type': 'MultiLineString',
'coordinates': [
[(1.0, 1.0), (2.0, 2.0)],
[(3.0, 3.0), (4.0, 4.0)],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# srsName conflict
with pytest.raises(ValueError):
parse_pre_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
with pytest.raises(ValueError):
parse_pre_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
def test_parse_polygon():
# using gml:posList
result = parse_pre_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.5, 0.2), (0.2, 0.5), (0.2, 0.2)
],
]
}
# using gml:posList with no interiors
result = parse_pre_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
]
}
# using gml:pos elements
result = parse_pre_v32(
etree.fromstring("""
0.0 0.0
1.0 0.0
0.0 1.0
0.0 0.0
0.2 0.2
0.5 0.2
0.2 0.5
0.2 0.2
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.5, 0.2), (0.2, 0.5), (0.2, 0.2)
],
]
}
# using gml:coordinates
result = parse_pre_v32(
etree.fromstring("""
0.0 0.0,1.0 0.0,0.0 1.0,0.0 0.0
0.2 0.2,0.5 0.2,0.2 0.5,0.2 0.2
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.5, 0.2), (0.2, 0.5), (0.2, 0.2)
],
]
}
# using gml:posList with srsName
result = parse_pre_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.2, 0.5), (0.5, 0.2), (0.2, 0.2)
],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
def test_parse_envelope():
# using gml:lowerCorner/gml:upperCorner
result = parse_pre_v32(
etree.fromstring("""
0.0 1.0
2.0 3.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 1.0),
(0.0, 3.0),
(2.0, 3.0),
(2.0, 1.0),
(0.0, 1.0),
],
]
}
# using gml:pos elements
result = parse_pre_v32(
etree.fromstring("""
0.0 1.0
2.0 3.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 1.0),
(0.0, 3.0),
(2.0, 3.0),
(2.0, 1.0),
(0.0, 1.0),
],
]
}
# using gml:coordinates
result = parse_pre_v32(
etree.fromstring("""
0.0 1.0,2.0 3.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 1.0),
(0.0, 3.0),
(2.0, 3.0),
(2.0, 1.0),
(0.0, 1.0),
],
]
}
# using gml:lowerCorner/gml:upperCorner with srsName
result = parse_pre_v32(
etree.fromstring("""
0.0 1.0
2.0 3.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(1.0, 0.0),
(3.0, 0.0),
(3.0, 2.0),
(1.0, 2.0),
(1.0, 0.0),
],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
def test_parse_multi_polygon():
# using gml:surfaceMember elements
result = parse_pre_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
10.0 10.0 11.0 10.0 10.0 11.0 10.0 10.0
10.2 10.2 10.5 10.2 10.2 10.5 10.2 10.2
""")
)
assert result == {
'type': 'MultiPolygon',
'coordinates': [
[
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.5, 0.2), (0.2, 0.5), (0.2, 0.2)
],
],
[
[
(10.0, 10.0), (11.0, 10.0), (10.0, 11.0), (10.0, 10.0)
],
[
(10.2, 10.2), (10.5, 10.2), (10.2, 10.5), (10.2, 10.2)
],
]
]
}
# using gml:surfaceMember elements with no interiors
result = parse_pre_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
10.0 10.0 11.0 10.0 10.0 11.0 10.0 10.0
""")
)
assert result == {
'type': 'MultiPolygon',
'coordinates': [
[
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
],
[
[
(10.0, 10.0), (11.0, 10.0), (10.0, 11.0), (10.0, 10.0)
],
]
]
}
# using gml:surfaceMembers
result = parse_pre_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
10.0 10.0 11.0 10.0 10.0 11.0 10.0 10.0
10.2 10.2 10.5 10.2 10.2 10.5 10.2 10.2
""")
)
assert result == {
'type': 'MultiPolygon',
'coordinates': [
[
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.5, 0.2), (0.2, 0.5), (0.2, 0.2)
],
],
[
[
(10.0, 10.0), (11.0, 10.0), (10.0, 11.0), (10.0, 10.0)
],
[
(10.2, 10.2), (10.5, 10.2), (10.2, 10.5), (10.2, 10.2)
],
]
]
}
# using gml:surfaceMembers with srsName
result = parse_pre_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
10.0 10.0 11.0 10.0 10.0 11.0 10.0 10.0
10.2 10.2 10.5 10.2 10.2 10.5 10.2 10.2
""")
)
assert result == {
'type': 'MultiPolygon',
'coordinates': [
[
[
(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.2, 0.5), (0.5, 0.2), (0.2, 0.2)
],
],
[
[
(10.0, 10.0), (10.0, 11.0), (11.0, 10.0), (10.0, 10.0)
],
[
(10.2, 10.2), (10.2, 10.5), (10.5, 10.2), (10.2, 10.2)
],
]
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
with pytest.raises(ValueError):
parse_pre_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
10.0 10.0 11.0 10.0 10.0 11.0 10.0 10.0
10.2 10.2 10.5 10.2 10.2 10.5 10.2 10.2
""")
)
def test_parse_multi_geometry():
# using geometryMembers
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0
1.0 1.0
1.0 1.0
""")
)
assert result == {
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 1.0)
},
{
'type': 'Polygon',
'coordinates': [
[(1.0, 1.0)],
[(1.0, 1.0)],
]
},
]
}
# using geometryMember
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0
1.0 1.0
1.0 1.0
""")
)
assert result == {
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 1.0)
},
{
'type': 'Polygon',
'coordinates': [
[(1.0, 1.0)],
[(1.0, 1.0)],
]
},
]
}
# allow varying srsNames
result = parse_pre_v32(
etree.fromstring("""
1.0 1.0
1.0 1.0
1.0 1.0
""")
)
assert result == {
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 1.0),
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
},
{
'type': 'Polygon',
'coordinates': [
[(1.0, 1.0)],
[(1.0, 1.0)],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:3857'
}
}
},
]
}
def test_encode_pre_v32_point():
# encode Point
result = encode_pre_v32({'type': 'Point', 'coordinates': (1.0, 2.0)}, 'ID')
expected = etree.fromstring("""
1.0 2.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode Point with EPSG:4326
result = encode_pre_v32({
'type': 'Point',
'coordinates': (1.0, 2.0),
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}, 'ID')
expected = etree.fromstring("""
2.0 1.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
def test_encode_pre_v32_multi_point():
# encode MultiPoint
result = encode_pre_v32({
'type': 'MultiPoint',
'coordinates': [
(1.0, 2.0),
(3.0, 4.0),
]
}, 'ID')
expected = etree.fromstring("""
1.0 2.0
3.0 4.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode MultiPoint with EPSG:4326
result = encode_pre_v32({
'type': 'MultiPoint',
'coordinates': [
(1.0, 2.0),
(3.0, 4.0),
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}, 'ID')
expected = etree.fromstring("""
2.0 1.0
4.0 3.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
def test_encode_pre_v32_linestring():
# encode LineString
result = encode_pre_v32({
'type': 'LineString',
'coordinates': [
(1.0, 2.0),
(3.0, 4.0),
],
}, 'ID')
expected = etree.fromstring("""
1.0 2.0 3.0 4.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode LineString with EPSG:4326
result = encode_pre_v32({
'type': 'LineString',
'coordinates': [
(1.0, 2.0),
(3.0, 4.0),
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}, 'ID')
expected = etree.fromstring("""
2.0 1.0 4.0 3.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
def test_encode_pre_v32_polygon():
# encode Polygon
result = encode_pre_v32({
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
],
}, 'ID')
expected = etree.fromstring("""
1.0 2.0 1.0 3.0 2.0 3.0 2.0 2.0 1.0 2.0
1.4 2.4 1.4 2.6 1.6 2.6 1.6 2.4 1.4 2.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode Polygon with EPSG:4326
result = encode_pre_v32({
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}, 'ID')
expected = etree.fromstring("""
2.0 1.0 3.0 1.0 3.0 2.0 2.0 2.0 2.0 1.0
2.4 1.4 2.6 1.4 2.6 1.6 2.4 1.6 2.4 1.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
def test_encode_pre_v32_multi_polygon():
# encode MultiPolygon
result = encode_pre_v32({
'type': 'MultiPolygon',
'coordinates': [
[
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
], [
[
(11.0, 12.0),
(11.0, 13.0),
(12.0, 13.0),
(12.0, 12.0),
(11.0, 12.0),
], [
(11.4, 12.4),
(11.4, 12.6),
(11.6, 12.6),
(11.6, 12.4),
(11.4, 12.4),
],
],
],
}, 'ID')
expected = etree.fromstring("""
1.0 2.0 1.0 3.0 2.0 3.0 2.0 2.0 1.0 2.0
1.4 2.4 1.4 2.6 1.6 2.6 1.6 2.4 1.4 2.4
11.0 12.0 11.0 13.0 12.0 13.0 12.0 12.0 11.0 12.0
11.4 12.4 11.4 12.6 11.6 12.6 11.6 12.4 11.4 12.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode MultiPolygon with EPSG:4326
result = encode_pre_v32({
'type': 'MultiPolygon',
'coordinates': [
[
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
], [
[
(11.0, 12.0),
(11.0, 13.0),
(12.0, 13.0),
(12.0, 12.0),
(11.0, 12.0),
], [
(11.4, 12.4),
(11.4, 12.6),
(11.6, 12.6),
(11.6, 12.4),
(11.4, 12.4),
],
],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}, 'ID')
expected = etree.fromstring("""
2.0 1.0 3.0 1.0 3.0 2.0 2.0 2.0 2.0 1.0
2.4 1.4 2.6 1.4 2.6 1.6 2.4 1.6 2.4 1.4
12.0 11.0 13.0 11.0 13.0 12.0 12.0 12.0 12.0 11.0
12.4 11.4 12.6 11.4 12.6 11.6 12.4 11.6 12.4 11.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
def test_encode_pre_v32_geometry_collection():
result = encode_pre_v32({
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 2.0),
},
{
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
],
},
]
}, 'ID')
expected = etree.fromstring("""
1.0 2.0
1.0 2.0 1.0 3.0 2.0 3.0 2.0 2.0 1.0 2.0
1.4 2.4 1.4 2.6 1.6 2.6 1.6 2.4 1.4 2.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
result = encode_pre_v32({
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 2.0),
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
},
{
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
},
]
}, 'ID')
expected = etree.fromstring("""
2.0 1.0
2.0 1.0 3.0 1.0 3.0 2.0 2.0 2.0 2.0 1.0
2.4 1.4 2.6 1.4 2.6 1.6 2.4 1.6 2.4 1.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
geopython-pygml-5ce8eb0/tests/test_v32.py 0000664 0000000 0000000 00000151120 14160355441 0020541 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from lxml import etree
import pytest
from pygml.v32 import encode_v32, parse_v32
from .util import compare_trees
def test_parse_point():
# basic test
result = parse_v32(
etree.fromstring("""
1.0 1.0
""")
)
assert result == {'type': 'Point', 'coordinates': (1.0, 1.0)}
# using gml:coordinates instead
result = parse_v32(
etree.fromstring("""
1.0 1.0
""")
)
assert result == {'type': 'Point', 'coordinates': (1.0, 1.0)}
# axis order swapping with srsName in pos or Point
result = parse_v32(
etree.fromstring("""
2.0 1.0
""")
)
assert result == {
'type': 'Point',
'coordinates': (1.0, 2.0),
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
result = parse_v32(
etree.fromstring("""
2.0 1.0
""")
)
assert result == {
'type': 'Point',
'coordinates': (1.0, 2.0),
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# conflicting srsName
with pytest.raises(ValueError):
parse_v32(
etree.fromstring("""
2.0 1.0
""")
)
def test_parse_multi_point():
# using gml:pointMember
result = parse_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
""")
)
assert result == {
'type': 'MultiPoint',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
]
}
# using gml:pointMembers
result = parse_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
""")
)
assert result == {
'type': 'MultiPoint',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
]
}
# using gml:pointMember and gml:pointMembers
result = parse_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
3.0 3.0
""")
)
assert result == {
'type': 'MultiPoint',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
(3.0, 3.0),
]
}
# conflicting srsName
with pytest.raises(ValueError):
parse_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
""")
)
def test_parse_linestring():
# from gml:posList
result = parse_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
]
}
# from gml:pos elements
result = parse_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
]
}
# from gml:coordinates
result = parse_v32(
etree.fromstring("""
1.0 1.0,2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
]
}
# from gml:pos elements with srsName
result = parse_v32(
etree.fromstring("""
1.0 1.0
2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# from gml:posList element with srsName
result = parse_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# from gml:coordinates element with srsName
result = parse_v32(
etree.fromstring("""
1.0 1.0,2.0 2.0
""")
)
assert result == {
'type': 'LineString',
'coordinates': [
(1.0, 1.0),
(2.0, 2.0),
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# srsName conflict
with pytest.raises(ValueError):
parse_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
""")
)
def test_parse_multi_curve():
# using gml:curveMember elements
result = parse_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
assert result == {
'type': 'MultiLineString',
'coordinates': [
[(1.0, 1.0), (2.0, 2.0)],
[(3.0, 3.0), (4.0, 4.0)],
]
}
# using gml:curveMembers element
result = parse_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
assert result == {
'type': 'MultiLineString',
'coordinates': [
[(1.0, 1.0), (2.0, 2.0)],
[(3.0, 3.0), (4.0, 4.0)],
]
}
# determine srsName from MultiCurve
result = parse_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
assert result == {
'type': 'MultiLineString',
'coordinates': [
[(1.0, 1.0), (2.0, 2.0)],
[(3.0, 3.0), (4.0, 4.0)],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# determine srsName from first LineString
result = parse_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
assert result == {
'type': 'MultiLineString',
'coordinates': [
[(1.0, 1.0), (2.0, 2.0)],
[(3.0, 3.0), (4.0, 4.0)],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
# srsName conflict
with pytest.raises(ValueError):
parse_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
with pytest.raises(ValueError):
parse_v32(
etree.fromstring("""
1.0 1.0 2.0 2.0
3.0 3.0 4.0 4.0
""")
)
def test_parse_polygon():
# using gml:posList
result = parse_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.5, 0.2), (0.2, 0.5), (0.2, 0.2)
],
]
}
# using gml:posList with only exterior
result = parse_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
]
}
# using gml:pos elements
result = parse_v32(
etree.fromstring("""
0.0 0.0
1.0 0.0
0.0 1.0
0.0 0.0
0.2 0.2
0.5 0.2
0.2 0.5
0.2 0.2
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.5, 0.2), (0.2, 0.5), (0.2, 0.2)
],
]
}
# using gml:coordinates
result = parse_v32(
etree.fromstring("""
0.0 0.0,1.0 0.0,0.0 1.0,0.0 0.0
0.2 0.2,0.5 0.2,0.2 0.5,0.2 0.2
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.5, 0.2), (0.2, 0.5), (0.2, 0.2)
],
]
}
# using gml:posList with srsName
result = parse_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.2, 0.5), (0.5, 0.2), (0.2, 0.2)
],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
def test_parse_envelope():
# using gml:lowerCorner/gml:upperCorner
result = parse_v32(
etree.fromstring("""
0.0 1.0
2.0 3.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 1.0),
(0.0, 3.0),
(2.0, 3.0),
(2.0, 1.0),
(0.0, 1.0),
],
]
}
# using gml:pos elements
result = parse_v32(
etree.fromstring("""
0.0 1.0
2.0 3.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 1.0),
(0.0, 3.0),
(2.0, 3.0),
(2.0, 1.0),
(0.0, 1.0),
],
]
}
# using gml:coordinates
result = parse_v32(
etree.fromstring("""
0.0 1.0,2.0 3.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(0.0, 1.0),
(0.0, 3.0),
(2.0, 3.0),
(2.0, 1.0),
(0.0, 1.0),
],
]
}
# using gml:lowerCorner/gml:upperCorner with srsName
result = parse_v32(
etree.fromstring("""
0.0 1.0
2.0 3.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(1.0, 0.0),
(3.0, 0.0),
(3.0, 2.0),
(1.0, 2.0),
(1.0, 0.0),
],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
def test_parse_multi_polygon():
# using gml:surfaceMember elements
result = parse_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
10.0 10.0 11.0 10.0 10.0 11.0 10.0 10.0
10.2 10.2 10.5 10.2 10.2 10.5 10.2 10.2
""")
)
assert result == {
'type': 'MultiPolygon',
'coordinates': [
[
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.5, 0.2), (0.2, 0.5), (0.2, 0.2)
],
],
[
[
(10.0, 10.0), (11.0, 10.0), (10.0, 11.0), (10.0, 10.0)
],
[
(10.2, 10.2), (10.5, 10.2), (10.2, 10.5), (10.2, 10.2)
],
]
]
}
# using gml:surfaceMember elements with no interiors
result = parse_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
10.0 10.0 11.0 10.0 10.0 11.0 10.0 10.0
""")
)
assert result == {
'type': 'MultiPolygon',
'coordinates': [
[
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
],
[
[
(10.0, 10.0), (11.0, 10.0), (10.0, 11.0), (10.0, 10.0)
],
]
]
}
# using gml:surfaceMembers
result = parse_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
10.0 10.0 11.0 10.0 10.0 11.0 10.0 10.0
10.2 10.2 10.5 10.2 10.2 10.5 10.2 10.2
""")
)
assert result == {
'type': 'MultiPolygon',
'coordinates': [
[
[
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.5, 0.2), (0.2, 0.5), (0.2, 0.2)
],
],
[
[
(10.0, 10.0), (11.0, 10.0), (10.0, 11.0), (10.0, 10.0)
],
[
(10.2, 10.2), (10.5, 10.2), (10.2, 10.5), (10.2, 10.2)
],
]
]
}
# using gml:surfaceMembers with srsName
result = parse_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
10.0 10.0 11.0 10.0 10.0 11.0 10.0 10.0
10.2 10.2 10.5 10.2 10.2 10.5 10.2 10.2
""")
)
assert result == {
'type': 'MultiPolygon',
'coordinates': [
[
[
(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (0.0, 0.0)
],
[
(0.2, 0.2), (0.2, 0.5), (0.5, 0.2), (0.2, 0.2)
],
],
[
[
(10.0, 10.0), (10.0, 11.0), (11.0, 10.0), (10.0, 10.0)
],
[
(10.2, 10.2), (10.2, 10.5), (10.5, 10.2), (10.2, 10.2)
],
]
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
with pytest.raises(ValueError):
parse_v32(
etree.fromstring("""
0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
0.2 0.2 0.5 0.2 0.2 0.5 0.2 0.2
10.0 10.0 11.0 10.0 10.0 11.0 10.0 10.0
10.2 10.2 10.5 10.2 10.2 10.5 10.2 10.2
""")
)
def test_parse_multi_geometry():
# using geometryMembers
result = parse_v32(
etree.fromstring("""
1.0 1.0
1.0 1.0
1.0 1.0
""")
)
assert result == {
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 1.0)
},
{
'type': 'Polygon',
'coordinates': [
[(1.0, 1.0)],
[(1.0, 1.0)],
]
},
]
}
# using geometryMember
result = parse_v32(
etree.fromstring("""
1.0 1.0
1.0 1.0
1.0 1.0
""")
)
assert result == {
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 1.0)
},
{
'type': 'Polygon',
'coordinates': [
[(1.0, 1.0)],
[(1.0, 1.0)],
]
},
]
}
# allow varying srsNames
result = parse_v32(
etree.fromstring("""
1.0 1.0
1.0 1.0
1.0 1.0
""")
)
assert result == {
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 1.0),
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
},
{
'type': 'Polygon',
'coordinates': [
[(1.0, 1.0)],
[(1.0, 1.0)],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:3857'
}
}
},
]
}
def test_encode_v32_point():
# encode Point
result = encode_v32({'type': 'Point', 'coordinates': (1.0, 2.0)}, 'ID')
expected = etree.fromstring("""
1.0 2.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode Point with EPSG:4326
result = encode_v32({
'type': 'Point',
'coordinates': (1.0, 2.0),
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}, 'ID')
expected = etree.fromstring("""
2.0 1.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
def test_encode_v32_multi_point():
# encode MultiPoint
result = encode_v32({
'type': 'MultiPoint',
'coordinates': [
(1.0, 2.0),
(3.0, 4.0),
]
}, 'ID')
expected = etree.fromstring("""
1.0 2.0
3.0 4.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode MultiPoint with EPSG:4326
result = encode_v32({
'type': 'MultiPoint',
'coordinates': [
(1.0, 2.0),
(3.0, 4.0),
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}, 'ID')
expected = etree.fromstring("""
2.0 1.0
4.0 3.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
def test_encode_v32_linestring():
# encode LineString
result = encode_v32({
'type': 'LineString',
'coordinates': [
(1.0, 2.0),
(3.0, 4.0),
],
}, 'ID')
expected = etree.fromstring("""
1.0 2.0 3.0 4.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode LineString with EPSG:4326
result = encode_v32({
'type': 'LineString',
'coordinates': [
(1.0, 2.0),
(3.0, 4.0),
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}, 'ID')
expected = etree.fromstring("""
2.0 1.0 4.0 3.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
def test_encode_v32_polygon():
# encode Polygon
result = encode_v32({
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
],
}, 'ID')
expected = etree.fromstring("""
1.0 2.0 1.0 3.0 2.0 3.0 2.0 2.0 1.0 2.0
1.4 2.4 1.4 2.6 1.6 2.6 1.6 2.4 1.4 2.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode Polygon with EPSG:4326
result = encode_v32({
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}, 'ID')
expected = etree.fromstring("""
2.0 1.0 3.0 1.0 3.0 2.0 2.0 2.0 2.0 1.0
2.4 1.4 2.6 1.4 2.6 1.6 2.4 1.6 2.4 1.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
def test_encode_v32_multi_polygon():
# encode MultiPolygon
result = encode_v32({
'type': 'MultiPolygon',
'coordinates': [
[
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
], [
[
(11.0, 12.0),
(11.0, 13.0),
(12.0, 13.0),
(12.0, 12.0),
(11.0, 12.0),
], [
(11.4, 12.4),
(11.4, 12.6),
(11.6, 12.6),
(11.6, 12.4),
(11.4, 12.4),
],
],
],
}, 'ID')
expected = etree.fromstring("""
1.0 2.0 1.0 3.0 2.0 3.0 2.0 2.0 1.0 2.0
1.4 2.4 1.4 2.6 1.6 2.6 1.6 2.4 1.4 2.4
11.0 12.0 11.0 13.0 12.0 13.0 12.0 12.0 11.0 12.0
11.4 12.4 11.4 12.6 11.6 12.6 11.6 12.4 11.4 12.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode MultiPolygon with EPSG:4326
result = encode_v32({
'type': 'MultiPolygon',
'coordinates': [
[
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
], [
[
(11.0, 12.0),
(11.0, 13.0),
(12.0, 13.0),
(12.0, 12.0),
(11.0, 12.0),
], [
(11.4, 12.4),
(11.4, 12.6),
(11.6, 12.6),
(11.6, 12.4),
(11.4, 12.4),
],
],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}, 'ID')
expected = etree.fromstring("""
2.0 1.0 3.0 1.0 3.0 2.0 2.0 2.0 2.0 1.0
2.4 1.4 2.6 1.4 2.6 1.6 2.4 1.6 2.4 1.4
12.0 11.0 13.0 11.0 13.0 12.0 12.0 12.0 12.0 11.0
12.4 11.4 12.6 11.4 12.6 11.6 12.4 11.6 12.4 11.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
def test_encode_v32_geometry_collection():
result = encode_v32({
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 2.0),
},
{
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
],
},
]
}, 'ID')
expected = etree.fromstring("""
1.0 2.0
1.0 2.0 1.0 3.0 2.0 3.0 2.0 2.0 1.0 2.0
1.4 2.4 1.4 2.6 1.6 2.6 1.6 2.4 1.4 2.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
result = encode_v32({
'type': 'GeometryCollection',
'geometries': [
{
'type': 'Point',
'coordinates': (1.0, 2.0),
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
},
{
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
},
]
}, 'ID')
expected = etree.fromstring("""
2.0 1.0
2.0 1.0 3.0 1.0 3.0 2.0 2.0 2.0 2.0 1.0
2.4 1.4 2.6 1.4 2.6 1.6 2.4 1.6 2.4 1.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
geopython-pygml-5ce8eb0/tests/test_v33.py 0000664 0000000 0000000 00000030436 14160355441 0020550 0 ustar 00root root 0000000 0000000 # ------------------------------------------------------------------------------
#
# Project: pygml
# Authors: Fabian Schindler
#
# ------------------------------------------------------------------------------
# Copyright (C) 2021 EOX IT Services GmbH
#
# 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 of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------
from lxml import etree
# import pytest
from pygml.v33 import encode_v33_ce, parse_v33_ce
from .util import compare_trees
def test_parse_simple_triangle():
# using gml:pos elements
result = parse_v33_ce(
etree.fromstring("""
1.0 1.0
1.0 2.0
2.0 1.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(1.0, 1.0),
(1.0, 2.0),
(2.0, 1.0),
(1.0, 1.0),
]
]
}
# using gml:posList element
result = parse_v33_ce(
etree.fromstring("""
1.0 1.0 1.0 2.0 2.0 1.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(1.0, 1.0),
(1.0, 2.0),
(2.0, 1.0),
(1.0, 1.0),
]
]
}
# swapped coordinates with EPSG:4326
result = parse_v33_ce(
etree.fromstring("""
0.0 1.0
0.0 2.0
1.0 1.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(1.0, 0.0),
(2.0, 0.0),
(1.0, 1.0),
(1.0, 0.0),
]
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
def test_parse_simple_rectangle():
# using gml:pos elements
result = parse_v33_ce(
etree.fromstring("""
1.0 1.0
1.0 2.0
2.0 2.0
2.0 1.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(1.0, 1.0),
(1.0, 2.0),
(2.0, 2.0),
(2.0, 1.0),
(1.0, 1.0),
]
]
}
# using gml:posList element
result = parse_v33_ce(
etree.fromstring("""
1.0 1.0 1.0 2.0 2.0 2.0 2.0 1.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(1.0, 1.0),
(1.0, 2.0),
(2.0, 2.0),
(2.0, 1.0),
(1.0, 1.0),
]
]
}
# swapped coordinates with EPSG:4326
result = parse_v33_ce(
etree.fromstring("""
0.0 1.0
0.0 2.0
1.0 2.0
1.0 1.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(1.0, 0.0),
(2.0, 0.0),
(2.0, 1.0),
(1.0, 1.0),
(1.0, 0.0),
]
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
def test_parse_simple_polygon():
# using gml:pos elements
result = parse_v33_ce(
etree.fromstring("""
1.0 1.0
1.0 2.0
2.0 2.0
2.0 1.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(1.0, 1.0),
(1.0, 2.0),
(2.0, 2.0),
(2.0, 1.0),
(1.0, 1.0),
]
]
}
# using gml:posList element
result = parse_v33_ce(
etree.fromstring("""
1.0 1.0 1.0 2.0 2.0 2.0 2.0 1.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(1.0, 1.0),
(1.0, 2.0),
(2.0, 2.0),
(2.0, 1.0),
(1.0, 1.0),
]
]
}
# swapped coordinates with EPSG:4326
result = parse_v33_ce(
etree.fromstring("""
0.0 1.0
0.0 2.0
1.0 2.0
1.0 1.0
""")
)
assert result == {
'type': 'Polygon',
'coordinates': [
[
(1.0, 0.0),
(2.0, 0.0),
(2.0, 1.0),
(1.0, 1.0),
(1.0, 0.0),
]
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}
def test_encode_v32_polygon():
# encode Polygon as SimpleTriangle
result = encode_v33_ce({
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
],
],
}, 'ID')
expected = etree.fromstring("""
1.0 2.0 1.0 3.0 2.0 2.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode Polygon as SimpleRectangle
result = encode_v33_ce({
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
],
],
}, 'ID')
expected = etree.fromstring("""
1.0 2.0 1.0 3.0 2.0 3.0 2.0 2.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode Polygon as SimplePolygon when more than 4 distinct
# coordinates and no interiors (with EPSG:4326)
result = encode_v33_ce({
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(1.5, 3.5),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
],
],
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:4326'
}
}
}, 'ID')
expected = etree.fromstring("""
2.0 1.0 3.0 1.0 3.5 1.5 3.0 2.0 2.0 2.0
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
# encode Polygon (fallback to gml 3.2 Polygon when interiors)
result = encode_v33_ce({
'type': 'Polygon',
'coordinates': [
[
(1.0, 2.0),
(1.0, 3.0),
(2.0, 3.0),
(2.0, 2.0),
(1.0, 2.0),
], [
(1.4, 2.4),
(1.4, 2.6),
(1.6, 2.6),
(1.6, 2.4),
(1.4, 2.4),
],
],
}, 'ID')
expected = etree.fromstring("""
1.0 2.0 1.0 3.0 2.0 3.0 2.0 2.0 1.0 2.0
1.4 2.4 1.4 2.6 1.6 2.6 1.6 2.4 1.4 2.4
""")
assert compare_trees(
expected, result
), f'{etree.tostring(expected)} != {etree.tostring(result)}'
geopython-pygml-5ce8eb0/tests/util.py 0000664 0000000 0000000 00000001247 14160355441 0020051 0 ustar 00root root 0000000 0000000 def compare_trees(e1, e2):
# from https://stackoverflow.com/a/24349916/746961
if e1.tag != e2.tag:
raise AssertionError(f'Tag: {e1.tag} != {e2.tag}')
if (e1.text or '').strip() != (e2.text or '').strip():
raise AssertionError(f'Text: {e1.text} != {e2.text}')
if (e1.tail or '').strip() != (e2.tail or '').strip():
raise AssertionError(f'Tail: {e1.tail} != {e2.tail}')
if e1.attrib != e2.attrib:
raise AssertionError(f'Attributes: {e1.attrib} != {e2.attrib}')
if len(e1) != len(e2):
raise AssertionError(f'Child nodes: {len(e1.tag)} != {len(e2.tag)}')
return all(compare_trees(c1, c2) for c1, c2 in zip(e1, e2))