├── tests ├── data │ ├── areas.cpg │ ├── roads.cpg │ ├── pointslatin1.cpg │ ├── pointsutf8.cpg │ ├── areas.shp │ ├── areas.shx │ ├── roads.shp │ ├── roads.shx │ ├── pointsutf8.shp │ ├── pointsutf8.shx │ ├── pointslatin1.dbf │ ├── pointslatin1.shp │ ├── pointslatin1.shx │ ├── areas.dbf │ ├── roads.dbf │ ├── pointsutf8.dbf │ ├── areas.prj │ ├── roads.prj │ ├── pointslatin1.prj │ └── pointsutf8.prj └── test_gdaltools.py ├── requirements.txt ├── push.sh ├── .gitignore ├── setup.cfg ├── MANIFEST.in ├── requirements-dev.txt ├── gdaltools ├── metadata.py ├── __init__.py ├── api.py ├── gdalsrsinfo.py ├── basetypes.py ├── ogrinfocmd.py ├── gdalinfocmd.py └── ogr2ogrcmd.py ├── tox.ini ├── DEVEL.md ├── .github └── workflows │ └── push_pygdaltools.yml ├── README.md ├── pavement.py ├── setup.py └── LICENSE /tests/data/areas.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /tests/data/roads.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /tests/data/pointslatin1.cpg: -------------------------------------------------------------------------------- 1 | 88591 -------------------------------------------------------------------------------- /tests/data/pointsutf8.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Nothing special 2 | -------------------------------------------------------------------------------- /tests/data/areas.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scolab-dot-eu/pygdaltools/HEAD/tests/data/areas.shp -------------------------------------------------------------------------------- /tests/data/areas.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scolab-dot-eu/pygdaltools/HEAD/tests/data/areas.shx -------------------------------------------------------------------------------- /tests/data/roads.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scolab-dot-eu/pygdaltools/HEAD/tests/data/roads.shp -------------------------------------------------------------------------------- /tests/data/roads.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scolab-dot-eu/pygdaltools/HEAD/tests/data/roads.shx -------------------------------------------------------------------------------- /tests/data/pointsutf8.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scolab-dot-eu/pygdaltools/HEAD/tests/data/pointsutf8.shp -------------------------------------------------------------------------------- /tests/data/pointsutf8.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scolab-dot-eu/pygdaltools/HEAD/tests/data/pointsutf8.shx -------------------------------------------------------------------------------- /tests/data/pointslatin1.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scolab-dot-eu/pygdaltools/HEAD/tests/data/pointslatin1.dbf -------------------------------------------------------------------------------- /tests/data/pointslatin1.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scolab-dot-eu/pygdaltools/HEAD/tests/data/pointslatin1.shp -------------------------------------------------------------------------------- /tests/data/pointslatin1.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scolab-dot-eu/pygdaltools/HEAD/tests/data/pointslatin1.shx -------------------------------------------------------------------------------- /tests/data/areas.dbf: -------------------------------------------------------------------------------- 1 |  2 | ADESCC AREA1 AREA2 -------------------------------------------------------------------------------- /push.sh: -------------------------------------------------------------------------------- 1 | python3 -m build 2 | aws codeartifact login --tool twine --repository eop --domain promethee-earth --domain-owner 724395993532 --region eu-west-3 3 | twine upload --repository codeartifact dist/* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Distribution / packaging 7 | *.egg-info/ 8 | build/ 9 | dist/ 10 | .tox/ 11 | 12 | # testing 13 | .cache/ 14 | -------------------------------------------------------------------------------- /tests/data/roads.dbf: -------------------------------------------------------------------------------- 1 |  2 | a'REFCCODEN RD125 1 RD2 2 RD2 3 -------------------------------------------------------------------------------- /tests/data/pointsutf8.dbf: -------------------------------------------------------------------------------- 1 |  2 | a)NAMELOCCNAMEINTC Ποταμός River Λίμνη Lake Βουνό Mountain Κοιλάδα Valley -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3 | # 3. If at all possible, it is good practice to do this. If you cannot, you 4 | # will need to generate wheels for each Python version that you support. 5 | universal=1 6 | 7 | [metadata] 8 | description-file = README.md 9 | -------------------------------------------------------------------------------- /tests/data/areas.prj: -------------------------------------------------------------------------------- 1 | 2 | GEOGCS["ETRS89",DATUM["European_Terrestrial_Reference_System_1989",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6258"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4258"]] 3 | -------------------------------------------------------------------------------- /tests/data/roads.prj: -------------------------------------------------------------------------------- 1 | 2 | GEOGCS["ETRS89",DATUM["European_Terrestrial_Reference_System_1989",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6258"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4258"]] 3 | -------------------------------------------------------------------------------- /tests/data/pointslatin1.prj: -------------------------------------------------------------------------------- 1 | 2 | GEOGCS["ETRS89",DATUM["European_Terrestrial_Reference_System_1989",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6258"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4258"]] 3 | -------------------------------------------------------------------------------- /tests/data/pointsutf8.prj: -------------------------------------------------------------------------------- 1 | 2 | GEOGCS["ETRS89",DATUM["European_Terrestrial_Reference_System_1989",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6258"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4258"]] 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Informational files 2 | include README.md 3 | include LICENSE 4 | 5 | graft tests 6 | 7 | # Exclude any compile Python files (most likely grafted by tests/ directory). 8 | global-exclude *.pyc 9 | 10 | # Setup-related things 11 | include pavement.py 12 | include requirements-dev.txt 13 | include requirements.txt 14 | include setup.py 15 | include tox.ini 16 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # Runtime requirements 2 | --requirement requirements.txt 3 | 4 | # Testing 5 | pytest==6.2.5 6 | mock==5.0.0 7 | 8 | # Linting 9 | flake8==5.0.4 10 | mccabe==0.7.0 11 | pycodestyle==2.9.1 12 | pyflakes==2.5.0 13 | 14 | # for flake8 15 | enum34; python_version<"3.4" 16 | configparser; python_version<"3.2" 17 | 18 | # Miscellaneous 19 | Paver==1.3.4 20 | colorama==0.4.5 21 | 22 | # Pypi upload 23 | twine 24 | -------------------------------------------------------------------------------- /gdaltools/metadata.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Project metadata 3 | 4 | Information describing the project. 5 | """ 6 | 7 | # The package name, which is also the "UNIX name" for the project. 8 | package = 'pygdaltools' 9 | project = 'pygdaltools' 10 | project_no_spaces = project.replace(' ', '') 11 | version = '1.4.3' 12 | description = """Python wrapper for Gdal/OGR command line tools""" 13 | authors = ['Cesar Martinez Izquierdo - SCOLAB'] 14 | authors_string = ', '.join(authors) 15 | emails = [] 16 | license = 'AGPL3' 17 | copyright = '2016 ' + authors_string 18 | url = 'https://github.com/scolab-dot-eu/pygdaltools' 19 | -------------------------------------------------------------------------------- /gdaltools/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Python library providing wrappers for the most common Gdal/OGR command 3 | line tools""" 4 | 5 | from . import metadata 6 | from .api import ogr2ogr 7 | from .basetypes import ConnectionString, PgConnectionString, \ 8 | FileConnectionString, GdalToolsError, Wrapper 9 | from .gdalinfocmd import get_raster_stats, gdalinfo 10 | from .gdalinfocmd import GdalInfo 11 | from .gdalsrsinfo import gdalsrsinfo, GdalSrsInfo 12 | from .ogr2ogrcmd import Ogr2ogr 13 | from .ogrinfocmd import ogrinfo, OgrInfo 14 | 15 | __version__ = metadata.version 16 | __author__ = metadata.authors[0] 17 | __license__ = metadata.license 18 | __copyright__ = metadata.copyright 19 | 20 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests in 2 | # multiple virtualenvs. This configuration file will run the test 3 | # suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | # 6 | # To run tox faster, check out Detox 7 | # (https://pypi.python.org/pypi/detox), which runs your tox runs in 8 | # parallel. To use it, "pip install detox" and then run "detox" from 9 | # this directory. 10 | 11 | [tox] 12 | #envlist = py27 13 | envlist = py27,py36,py37,py38,py39,py310,py311,py312 14 | 15 | [testenv] 16 | deps = 17 | --no-deps 18 | --requirement 19 | {toxinidir}/requirements-dev.txt 20 | commands = paver test_all 21 | 22 | [flake8] 23 | max-line-length = 120 24 | 25 | 26 | -------------------------------------------------------------------------------- /gdaltools/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | gvSIG Online. 4 | Copyright (C) 2015-2016 gvSIG Association. 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License as 8 | published by the Free Software Foundation, either version 3 of the 9 | License, or (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU Affero General Public License for more details. 15 | 16 | You should have received a copy of the GNU Affero General Public License 17 | along with this program. If not, see . 18 | ''' 19 | ''' 20 | @author: Cesar Martinez Izquierdo - Scolab 21 | ''' 22 | 23 | from . import ogr2ogrcmd 24 | from . basetypes import FileConnectionString, PgConnectionString 25 | 26 | def ogr2ogr(version=1): 27 | return ogr2ogrcmd.Ogr2ogr(version) 28 | -------------------------------------------------------------------------------- /DEVEL.md: -------------------------------------------------------------------------------- 1 | # Developer information 2 | 3 | ## Dependences 4 | 5 | You can install the development dependences by running 6 | 7 | ``` 8 | pip install -r requirements-dev.txt 9 | ``` 10 | 11 | ## Installing 12 | 13 | To sdist-package, install and test your project against Python2.6 and Python2.7, just type: 14 | ``` 15 | tox 16 | ``` 17 | 18 | ## Running tests 19 | 20 | ``` 21 | paver test all 22 | ``` 23 | 24 | ## Localy installing the package 25 | 26 | ``` 27 | pip install -e . 28 | ``` 29 | 30 | ## Creating source distributions 31 | 32 | ``` 33 | python setup.py sdist 34 | ``` 35 | 36 | ## Creating binary distributions (Wheels) 37 | 38 | ``` 39 | python setup.py bdist_wheel --universal 40 | ``` 41 | 42 | ## Creating both soure & binary distributions (Wheels) 43 | 44 | ``` 45 | python setup.py sdist bdist_wheel --universal 46 | ``` 47 | 48 | ## Check distribution before uploading to Pypi 49 | 50 | ``` 51 | twine check dist/*1.3* 52 | ``` 53 | 54 | ## Uploading the distribution to PyPi 55 | 56 | ``` 57 | twine upload dist/* 58 | ``` 59 | 60 | You can specify the version to upload: 61 | 62 | ``` 63 | twine upload dist/*1.0* 64 | ``` 65 | 66 | You should first use the test repository: 67 | 68 | ``` 69 | twine upload -r pypitest dist/*1.0* 70 | ``` 71 | 72 | ## Additional info 73 | 74 | Have a look to the Packaging and Distributing Projects tutorial: 75 | 76 | https://packaging.python.org/distributing/ 77 | 78 | The package has been generated following seafisk's Python Project Template: 79 | 80 | https://github.com/seanfisk/python-project-template 81 | 82 | -------------------------------------------------------------------------------- /gdaltools/gdalsrsinfo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | ''' 4 | gvSIG Online. 5 | Copyright (C) 2007-2015 gvSIG Association. 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU Affero General Public License as 9 | published by the Free Software Foundation, either version 3 of the 10 | License, or (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU Affero General Public License for more details. 16 | 17 | You should have received a copy of the GNU Affero General Public License 18 | along with this program. If not, see . 19 | ''' 20 | ''' 21 | @author: Cesar Martinez Izquierdo - Scolab 22 | ''' 23 | import logging 24 | import re 25 | import os 26 | from .basetypes import GdalToolsError, Wrapper, ConnectionString, FileConnectionString 27 | import io 28 | 29 | def gdalsrsinfo(raster_path, **flags): 30 | """ 31 | Returns the output of gdalsrsinfo command on the provided raster. 32 | 33 | :param raster_path: The path to the raster file 34 | :param flags: flags for the gdalinfo command. See GdalInfo.set_flags for accepted parameters 35 | """ 36 | gi = GdalSrsInfo() 37 | gi.set_input(raster_path) 38 | if flags: 39 | gi.set_flags(**flags) 40 | return gi.execute() 41 | 42 | class GdalSrsInfo(Wrapper): 43 | """ 44 | Wrapper for the gdalsrsinfo command 45 | """ 46 | CMD = 'gdalsrsinfo' 47 | 48 | def __init__(self, version=1, command_path=None): 49 | Wrapper.__init__(self, version, command_path) 50 | self.set_flags() 51 | self.output = None 52 | 53 | def set_input(self, input_raster): 54 | if isinstance(input_raster, ConnectionString): 55 | self.in_ds = input_raster 56 | else: 57 | self.in_ds = FileConnectionString(input_raster) 58 | return self 59 | 60 | def set_flags( 61 | self, 62 | single_line=False, validate=False, output_type=None, search_epsg=False): 63 | """ 64 | :param mdd: None, "all" or a list of metadata domains to report 65 | :param sd: None or the number of subdataset to get info from 66 | """ 67 | self.single_line = single_line 68 | self.validate = validate 69 | self.output_type = output_type 70 | self.search_epsg = search_epsg 71 | return self 72 | 73 | def _get_flag_array(self): 74 | result = [] 75 | if self.single_line: 76 | result.append("--single-line") 77 | if self.validate: 78 | result.append("-V") 79 | if self.output_type: 80 | result.extend(["-o", self.output_type]) 81 | if self.search_epsg: 82 | result.append("-e") 83 | return result 84 | 85 | def execute(self): 86 | cmd = self._get_command() 87 | args = [cmd] + self._get_flag_array() + [self.in_ds.encode()] 88 | safe_args = [cmd] + self._get_flag_array() + [str(self.in_ds)] 89 | logging.debug(" ".join(safe_args)) 90 | self.safe_args = safe_args 91 | self.output = self._do_execute(args) 92 | return self.output -------------------------------------------------------------------------------- /.github/workflows/push_pygdaltools.yml: -------------------------------------------------------------------------------- 1 | name: pygdaltools - Build and Push 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | build: 11 | name: Build pygdaltools 12 | runs-on: ubuntu-latest 13 | environment: share 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Set up python 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: '3.14' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install -r requirements.txt 25 | pip install build 26 | - name: Build pygdaltools 27 | run: | 28 | python -m build 29 | - name: Upload artifact 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: python-dist 33 | path: dist/ 34 | 35 | push: 36 | name: Push pygdaltools 37 | runs-on: ubuntu-latest 38 | environment: share 39 | env: 40 | AWS_REGION: ${{ secrets.AWS_REGION }} 41 | AWS_DOMAIN: ${{ secrets.AWS_DOMAIN }} 42 | AWS_OWNER: ${{ secrets.AWS_OWNER }} 43 | needs: build 44 | steps: 45 | - name: Checkout 46 | uses: actions/checkout@v3 47 | 48 | - name: Set up python 49 | uses: actions/setup-python@v4 50 | with: 51 | python-version: '3.14' 52 | 53 | - name: Download artifact 54 | uses: actions/download-artifact@v4 55 | with: 56 | name: python-dist 57 | path: dist/ 58 | 59 | - name: Install dependencies 60 | run: | 61 | #python -m pip install 62 | pip install twine 63 | 64 | - name: Configure AWS credentials 65 | uses: aws-actions/configure-aws-credentials@v1 66 | with: 67 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 68 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 69 | aws-region: ${{ env.AWS_REGION }} 70 | 71 | - name: Configure AWS profile for CodeArtifact 72 | run: | 73 | aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }} --profile codeartifact 74 | aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }} --profile codeartifact 75 | aws configure set region eu-west-3 --profile codeartifact 76 | aws configure set output json --profile codeartifact 77 | 78 | - name: Get CodeArtifact Auth Token 79 | run: | 80 | echo "Retrieving CodeArtifact Auth Token..." 81 | CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token \ 82 | --profile=codeartifact \ 83 | --domain ${{ secrets.AWS_DOMAIN }} \ 84 | --domain-owner ${{ secrets.AWS_OWNER }} \ 85 | --region eu-west-3 \ 86 | --query authorizationToken \ 87 | --output text) 88 | echo "CODEARTIFACT_AUTH_TOKEN=${CODEARTIFACT_AUTH_TOKEN}" >> $GITHUB_ENV 89 | 90 | - name: Login to AWS CodeArtifact 91 | run: | 92 | aws codeartifact login --tool twine --repository eop --domain ${{ secrets.AWS_DOMAIN }} --domain-owner ${{ secrets.AWS_OWNER }} --region eu-west-3 --profile codeartifact 93 | 94 | - name: Push pygdaltools 95 | run: | 96 | python -m twine upload --repository codeartifact dist/* 97 | -------------------------------------------------------------------------------- /gdaltools/basetypes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | gvSIG Online. 5 | Copyright (C) 2015-2016 gvSIG Association. 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU Affero General Public License as 9 | published by the Free Software Foundation, either version 3 of the 10 | License, or (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU Affero General Public License for more details. 16 | 17 | You should have received a copy of the GNU Affero General Public License 18 | along with this program. If not, see . 19 | ''' 20 | ''' 21 | @author: Cesar Martinez Izquierdo - Scolab 22 | ''' 23 | 24 | import subprocess 25 | import logging 26 | import os, platform 27 | import sys 28 | import re 29 | 30 | try: 31 | sys_encoding = sys.stdout.encoding or 'utf-8' 32 | except: 33 | sys_encoding = 'utf-8' 34 | 35 | class GdalToolsError(Exception): 36 | def __init__(self, code=-1, message=None): 37 | self.code = code 38 | self.message=message 39 | 40 | 41 | class ConnectionString(): 42 | def encode(self): 43 | pass 44 | 45 | 46 | class PgConnectionString(ConnectionString): 47 | conn_string_tpl = u"PG:host='{host}' port='{port}' user='{user}' dbname='{dbname}' password='{password}'" 48 | def __init__(self, host=None, port=None, dbname=None, schema=None, user=None, password=None): 49 | self.host = host 50 | self.port = port 51 | self.dbname = dbname 52 | self.schema = schema 53 | self.user = user 54 | self.password = password 55 | 56 | def encode(self): 57 | return self.conn_string_tpl.format(host=self.host, port=self.port, user=self.user, dbname=self.dbname, password=self.password, schema=self.schema) 58 | 59 | def __unicode__(self): 60 | return '"' + self.conn_string_tpl.format(host=self.host, port=self.port, user=self.user, dbname=self.dbname, password='xxxxxx', schema=self.schema) + '"' 61 | 62 | def __str__(self): 63 | return self.__unicode__() 64 | 65 | class FileConnectionString(): 66 | 67 | def __init__(self, file_path): 68 | self.file_path = file_path 69 | 70 | def encode(self): 71 | return self.file_path 72 | 73 | def __unicode__(self): 74 | return '"' + self.file_path + '"' 75 | 76 | def __str__(self): 77 | return self.__unicode__() 78 | 79 | class Wrapper(): 80 | BASEPATH = "/usr/bin" 81 | CMD = None 82 | def __init__(self, version=1, command_path=None): 83 | self.version = version 84 | self._command = command_path 85 | 86 | def _get_command(self): 87 | if self._command: 88 | return self._command 89 | if platform.system()=='Windows': 90 | cmd = self.CMD + ".exe" 91 | else: 92 | cmd = self.CMD 93 | return os.path.join(self.BASEPATH, cmd) 94 | 95 | def get_version_str(self): 96 | self._do_execute([self._get_command(), "--version"]) 97 | return self.stdout 98 | def get_version(self): 99 | version_str = self.get_version_str() 100 | m = re.match("GDAL (\d+(?:\.\d+(?:\.\d+)?)?(?:[_\-a-zA-Z]+[_\-a-zA-Z0-9\.]*)?),", version_str) 101 | if m: 102 | return m.group(1) 103 | return '' 104 | 105 | def get_version_tuple(self): 106 | version_str = self.get_version_str() 107 | m = re.match("GDAL ((?P\d+)(?:\.(?P\d+)(?:\.(?P\d+)?)?)?(?P[\-_a-zA-Z]+[_\-a-zA-Z0-9\.]*)?),", version_str) 108 | if m: 109 | groups = {k: v for k, v in m.groupdict().items() if v is not None} 110 | return (groups.get('major', ''), groups.get('minor', ''), groups.get('patch', ''), groups.get('prerelease', '')) 111 | return ('', '', '', '') 112 | 113 | def _do_execute(self, args): 114 | self.args = args 115 | p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1) 116 | output, err = p.communicate() 117 | rc = p.returncode 118 | self.returncode = rc 119 | try: 120 | self.stdout = output.decode(sys_encoding) 121 | self.stderr = err.decode(sys_encoding) 122 | except: 123 | self.stdout = "Error decoding stdout" 124 | self.stderr = "Error decoding stderr" 125 | logging.debug("return code: " + str(rc)) 126 | if rc>0: 127 | logging.error(err) 128 | raise GdalToolsError(rc, err) 129 | return output 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pygdaltools 2 | 3 | Python library providing wrappers for the most common Gdal/OGR command line tools. Currently, ogr2ogr, ogrinfo, gdalinfo and gdalsrsinfo are supported. 4 | Note that this library requires GDAL/OGR tools to be installed in the system. 5 | 6 | ## Installation 7 | 8 | ``` 9 | pip install pygdaltools 10 | ``` 11 | 12 | This command does not automatically install GDAL/OGR tools in your system. 13 | In Debian or Ubuntu you can install them by using: 14 | 15 | ``` 16 | apt-get install gdal-bin 17 | ``` 18 | 19 | In CentOS: 20 | 21 | ``` 22 | yum -y install gdal 23 | ``` 24 | 25 | For Windows, you can install GDAL/OGR by using [OSGeo4W](https://trac.osgeo.org/osgeo4w/). 26 | You will also need to see the [Configuration section](#configuration). 27 | 28 | ## Usage 29 | 30 | Gdalinfo: 31 | 32 | 33 | ``` 34 | import gdaltools 35 | info = gdaltools.gdalinfo("/mypath/myraster.tif") 36 | print info # output is the same generated by the gdalinfo command 37 | ``` 38 | 39 | Raster stats: 40 | 41 | 42 | ``` 43 | stats = gdaltools.get_raster_stats("/mypath/myraster.tif") 44 | print stats[0] 45 | # outputs a tuple: (band0_min, band0_max, band0_mean, band0_stdev) 46 | print stats[1] 47 | # outputs a tuple: (band1_min, band1_max, band1_mean, band1_stdev) 48 | ``` 49 | 50 | Ogrinfo: 51 | 52 | ``` 53 | # Basic usage: 54 | info = gdaltools.ogrinfo("thelayer.shp", "thelayer", geom=False) 55 | print info # output is the same generated by the ogrinfo command 56 | 57 | # Other examples: 58 | ogrinfo("thedb.sqlite") 59 | gdaltools.ogrinfo("thedb.sqlite", "layer1", "layer2", geom="SUMMARY") 60 | gdaltools.ogrinfo("thedb.sqlite", sql="SELECT UpdateLayerStatistics()") 61 | ``` 62 | 63 | Ogr2ogr. From shp to geojson: 64 | 65 | ``` 66 | ogr = gdaltools.ogr2ogr() 67 | ogr.set_encoding("UTF-8") 68 | ogr.set_input("mylayer.shp", srs="EPSG:4326") 69 | ogr.set_output("mylayer.geojson") 70 | ogr.execute() 71 | ``` 72 | 73 | It can also be chained in a single line: 74 | 75 | ``` 76 | gdaltools.ogr2ogr()\ 77 | .set_encoding("UTF-8")\ 78 | .set_input("mylayer.shp", srs="EPSG:4326")\ 79 | .set_output("mylayer.geojson").execute() 80 | ``` 81 | 82 | Ogr2ogr. From postgis to shp: 83 | 84 | ``` 85 | ogr = gdaltools.ogr2ogr() 86 | conn = gdaltools.PgConnectionString(host="localhost", port=5432, dbname="scolab", schema="data", user="myuser", password="mypass") 87 | ogr.set_input(conn, table_name="roads", srs="EPSG:4326") 88 | ogr.set_output("mylayer.shp") 89 | ogr.execute() 90 | ``` 91 | 92 | Ogr2ogr. From postgis to spatialite, specifying a different output table name: 93 | 94 | ``` 95 | ogr = gdaltools.ogr2ogr() 96 | conn = gdaltools.PgConnectionString(host="localhost", port=5432, dbname="scolab", schema="data", user="myuser", password="mypass") 97 | ogr.set_input(conn, table_name="roads", srs="EPSG:4326") 98 | ogr.set_output("mydb.sqlite", table_name="roads2010") 99 | ogr.set_output_mode(data_source_mode=ogr.MODE_DS_CREATE_OR_UPDATE) # required to add the layer to an existing DB 100 | ogr.execute() 101 | ``` 102 | 103 | Ogr2ogr. From postgis to spatialite, reprojecting to "EPSG:25830": 104 | 105 | ``` 106 | ogr = gdaltools.ogr2ogr() 107 | conn = gdaltools.PgConnectionString(host="localhost", port=5432, dbname="scolab", schema="data", user="myuser", password="mypass") 108 | ogr.set_input(conn, table_name="roads", srs="EPSG:4326") 109 | ogr.set_output("mydb.sqlite", srs="EPSG:25830") 110 | ogr.execute() 111 | ``` 112 | 113 | ## Configuration 114 | 115 | By default, gdaltools assumes that Gdal/Ogr commands are installed under /usr/bin/ (the standard Linux path). 116 | In order to configure specific paths (for instance for using the library in Windows), you can use: 117 | 118 | ``` 119 | import gdaltools 120 | gdaltools.Wrapper.BASEPATH = "C/Program Files/Gdal/bin" 121 | print gdaltools.gdalinfo("mywindowsraster.tif") 122 | ``` 123 | 124 | You can also use lower level API for setting the full path for specific commands: 125 | 126 | ``` 127 | info = gdaltools.GdalInfo(command_path="C/Program Files/Gdal/bin/gdalinfo.exe") 128 | info.set_input('mywindowsraster.tif') 129 | print info.execute() 130 | print info.get_raster_stats() 131 | ``` 132 | 133 | ## FAQ 134 | 135 | Nobody asked yet, but just in case. 136 | 137 | Q - Why don't you use the Python GDAL/OGR API? 138 | A - The GDAL/OGR command line tools perform very specific, higher-level tasks, while 139 | the Python GDAL/OGR API offers a much lower level API. Therefore, in this library we 140 | try to offer this higher level functionality using a programmer-friendly interface. 141 | 142 | Q - But why do you internally call the command line tools, instead of implementing 143 | each command using the Python GDAL/OGR API? 144 | A - We believe it would take us more time to write the library using the API instead of the CLI. 145 | It also has some advantages: 1) it can use different versions of GDAL/OGR in the same computer 146 | 2) it does not require having Python GDAL bindings installed. 147 | In any case, we can try "the API way" if you are willing to fund it ;-) 148 | 149 | Q - Why don't you use the sample Python implementation of these commands that are 150 | included in the GDAL Python bindings? 151 | A - They can be used, the library allows specifying the path to the command to use. 152 | 153 | 154 | ## Authors 155 | 156 | Cesar Martinez Izquierdo - [Scolab](http://www.scolab.es) 157 | -------------------------------------------------------------------------------- /gdaltools/ogrinfocmd.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding: utf-8 -*- 3 | from __future__ import unicode_literals 4 | ''' 5 | gvSIG Online. 6 | Copyright (C) 2015-2016 gvSIG Association. 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU Affero General Public License as 10 | published by the Free Software Foundation, either version 3 of the 11 | License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU Affero General Public License for more details. 17 | 18 | You should have received a copy of the GNU Affero General Public License 19 | along with this program. If not, see . 20 | ''' 21 | ''' 22 | @author: Cesar Martinez Izquierdo - Scolab 23 | ''' 24 | 25 | 26 | import logging 27 | import os 28 | from .basetypes import Wrapper, ConnectionString, FileConnectionString 29 | 30 | 31 | def ogrinfo(datasource, *layer_names, **flags): 32 | """ 33 | Returns the output of ogrinfo command on the provided vector layer(s). 34 | 35 | Examples: 36 | ogrinfo("thelayer.shp", "thelayer", geom=False) 37 | ogrinfo("thedb.sqlite") 38 | ogrinfo("thedb.sqlite", "layer1", "layer2", geom="SUMMARY") 39 | ogrinfo("thedb.sqlite", sql="SELECT UpdateLayerStatistics()") 40 | 41 | :param datasource: The path to the data source (e.g. a SHP file) or a database 42 | connection string 43 | :param layer_names: The name(s) of the layer(s) to query 44 | :param flags: flags for the ogrinfo command. See Ogrinfo.set_flags for accepted parameters 45 | """ 46 | oinfo = OgrInfo() 47 | oinfo.set_input(datasource, *layer_names) 48 | if flags: 49 | oinfo.set_flags(**flags) 50 | return oinfo.execute() 51 | 52 | class OgrInfo(Wrapper): 53 | """ 54 | Wrapper for the ogrinfo command 55 | """ 56 | CMD = 'ogrinfo' 57 | 58 | def __init__(self, version=1, command_path=None): 59 | Wrapper.__init__(self, version, command_path) 60 | self.set_input(None) 61 | self.set_flags() 62 | 63 | def set_input(self, input_ds, *layer_names): 64 | """ 65 | Sets the input layer 66 | 67 | :param input_ds: The path to the input data source (shapefile, spatialite, etc) 68 | or a ConnectionString object 69 | :param table_name: The name of the input table name in the data source. Can be 70 | omitted for some data source types such as Shapefiles or CSVs 71 | :param srs: Defines the SRS of the input layer, using a EPSG code string 72 | (e.g. "EPSG:4326"). Ogr will try to autodetect the SRS if this parameter is omitted, 73 | but autodetection will fail in a number of situations, so it is always recommended 74 | to explicitly set the SRS parameter 75 | """ 76 | if isinstance(input_ds, ConnectionString): 77 | self.in_ds = input_ds 78 | else: 79 | self.in_ds = FileConnectionString(input_ds) 80 | self.in_tables = layer_names 81 | return self 82 | 83 | 84 | def set_flags( 85 | self, 86 | readonly=False, alltables=False, summary=False, quiet=False, 87 | where=None, sql=None, dialect=None, spat=None, geomfield=None, 88 | fid=None, fields=True, geom=True, formats=False): 89 | self.readonly = readonly 90 | self.alltables = alltables 91 | self.summary=summary 92 | self.quiet = quiet 93 | self.where = where 94 | self.sql = sql 95 | self.dialect = dialect 96 | self.spat = spat 97 | self.geomfield = geomfield 98 | self.fid = fid 99 | self.fields = fields 100 | self.geom = geom 101 | self.formats = formats 102 | 103 | 104 | def execute(self): 105 | args = [self._get_command()] 106 | 107 | if self.formats: 108 | args.append("--formats") 109 | elif self.sql: 110 | args.extend(["-sql", self.sql]) 111 | else: 112 | if self.readonly: 113 | args.append("-ro") 114 | if self.alltables: 115 | args.append("-al") 116 | if self.summary: 117 | args.append("-so") 118 | if self.quiet: 119 | args.append("-q") 120 | if self.where: 121 | args.extend(["-where", self.where]) 122 | if self.dialect: 123 | args.extend(["-dialect", self.dialect]) 124 | if self.spat and len(self.spat)==4: 125 | args.append("-spat") 126 | args.extend(self.spat) 127 | if self.geomfield: 128 | args.extend(["-geomfield", self.geomfield]) 129 | if self.fid: 130 | args.extend(["-fid", self.fid]) 131 | if not self.fields: 132 | args.append("-fields=NO") 133 | if not self.geom: 134 | args.append("-geom=NO") 135 | elif self.geom == "SUMMARY": 136 | args.append("-geom=SUMMARY") 137 | 138 | # log the command, excluding password for db connection strings 139 | safe_args = list(args) 140 | safe_args.append(str(self.in_ds)) 141 | safe_args.extend(self.in_tables) 142 | logging.debug(" ".join(safe_args)) 143 | 144 | args.append(self.in_ds.encode()) 145 | args.extend(self.in_tables) 146 | self.safe_args = safe_args 147 | return self._do_execute(args) 148 | -------------------------------------------------------------------------------- /tests/test_gdaltools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | import pytest 4 | from gdaltools.basetypes import GdalToolsError 5 | 6 | @pytest.fixture 7 | def ogr(): 8 | import gdaltools 9 | ogr = gdaltools.ogr2ogr() 10 | yield ogr 11 | 12 | 13 | """ 14 | @pytest.fixture 15 | def tmpdir(): 16 | import tempfile 17 | tmp_path = tempfile.mkdtemp() 18 | yield tmp_path 19 | import shutil 20 | shutil.rmtree(tmp_path) 21 | """ 22 | 23 | 24 | @pytest.fixture 25 | def tmp_sqlite(tmpdir): 26 | f = str(tmpdir.join("db.sqlite")) 27 | yield f 28 | import os 29 | if os.path.exists(f): 30 | os.remove(f) 31 | 32 | 33 | def test_spatialite_output(ogr, tmp_sqlite): 34 | ogr.set_input("tests/data/areas.shp", srs="EPSG:4258") 35 | ogr.set_encoding("UTF-8") 36 | ogr.set_output(tmp_sqlite) 37 | ogr.execute() 38 | assert ogr.returncode == 0 39 | import os 40 | assert os.path.isfile(tmp_sqlite) 41 | 42 | 43 | def test_creation_mode(ogr, tmp_sqlite): 44 | ogr.set_input("tests/data/areas.shp", srs="EPSG:4258") 45 | ogr.set_encoding("UTF-8") 46 | ogr.set_output(tmp_sqlite) 47 | # create areas layer in tmp_sqlite 48 | ogr.execute() 49 | assert ogr.returncode == 0 50 | 51 | """ 52 | Not true depending on the ogr2ogr version 53 | # should fail now, as it already exists 54 | from gdaltools import GdalToolsError 55 | with pytest.raises(GdalToolsError): 56 | ogr.execute() 57 | assert ogr.returncode != 0 58 | """ 59 | 60 | # should also fail if explicitly using MODE_LAYER_CREATE and MODE_DS_CREATE_OR_UPDATE 61 | ogr.set_output_mode( 62 | layer_mode=ogr.MODE_LAYER_CREATE, 63 | data_source_mode=ogr.MODE_DS_CREATE_OR_UPDATE) 64 | with pytest.raises(GdalToolsError): 65 | ogr.execute() 66 | assert ogr.returncode != 0 67 | 68 | # should work with MODE_LAYER_OVERWRITE 69 | ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_OVERWRITE, data_source_mode=ogr.MODE_DS_CREATE_OR_UPDATE) 70 | ogr.execute() 71 | assert ogr.returncode == 0 72 | 73 | # should fail with MODE_LAYER_CREATE & MODE_DS_UPDATE 74 | ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_CREATE, data_source_mode=ogr.MODE_DS_UPDATE) 75 | with pytest.raises(GdalToolsError): 76 | ogr.execute() 77 | assert ogr.returncode != 0 78 | 79 | # should work 80 | ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_OVERWRITE, data_source_mode=ogr.MODE_DS_UPDATE) 81 | ogr.execute() 82 | assert ogr.returncode == 0 83 | 84 | # I think it should fail according ogr2ogr docs, but works at least in ogr 1.11 so excluding from testing 85 | # ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_OVERWRITE, data_source_mode=ogr.MODE_DS_CREATE) 86 | # ogr.execute() 87 | 88 | # I think it should fail according ogr2ogr docs, but works at least in ogr 2.2.3 so excluding from testing 89 | #ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_CREATE, data_source_mode=ogr.MODE_DS_CREATE) 90 | #with pytest.raises(GdalToolsError): 91 | # ogr.execute() 92 | #assert ogr.returncode != 0 93 | 94 | # should fail because the db exist and using MODE_DS_CREATE 95 | # but works at least in ogr 2.2.3 so excluding from testing 96 | #ogr.set_output(tmp_sqlite, table_name="areas01") 97 | #ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_CREATE, data_source_mode=ogr.MODE_DS_CREATE) 98 | #with pytest.raises(GdalToolsError): 99 | # ogr.execute() 100 | #assert ogr.returncode != 0 101 | 102 | # should fails, the layer exists 103 | ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_CREATE, data_source_mode=ogr.MODE_DS_CREATE_OR_UPDATE) 104 | with pytest.raises(GdalToolsError): 105 | ogr.execute() 106 | assert ogr.returncode != 0 107 | 108 | # should work 109 | ogr.set_output(tmp_sqlite, table_name="areas02") 110 | ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_CREATE, data_source_mode=ogr.MODE_DS_UPDATE) 111 | ogr.execute() 112 | assert ogr.returncode == 0 113 | 114 | # should work 115 | ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_APPEND, data_source_mode=ogr.MODE_DS_UPDATE) 116 | ogr.execute() 117 | assert ogr.returncode == 0 118 | 119 | # should work 120 | ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_APPEND, data_source_mode=ogr.MODE_DS_CREATE_OR_UPDATE) 121 | ogr.execute() 122 | assert ogr.returncode == 0 123 | 124 | # I think it should fail according ogr2ogr docs, but works at least in ogr 1.11 so excluding from testing 125 | # ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_APPEND, data_source_mode=ogr.MODE_DS_CREATE) 126 | # with pytest.raises(GdalToolsError): 127 | # ogr.execute() 128 | 129 | 130 | def test_shape_encoding(ogr, tmpdir, tmp_sqlite): 131 | # import utf-8 and latin1 layers in s spatialite db 132 | ogr.set_input("tests/data/pointsutf8.shp", srs="EPSG:4258") 133 | ogr.set_output_mode(layer_mode=ogr.MODE_LAYER_CREATE, data_source_mode=ogr.MODE_DS_CREATE_OR_UPDATE) 134 | ogr.set_encoding("UTF-8") 135 | ogr.set_output(tmp_sqlite) 136 | ogr.execute() 137 | assert ogr.returncode == 0 138 | ogr.set_input("tests/data/pointslatin1.shp", srs="EPSG:4258") 139 | ogr.set_encoding("ISO-8859-1") 140 | ogr.set_output(tmp_sqlite) 141 | ogr.execute() 142 | assert ogr.returncode == 0 143 | 144 | # export as utf8 a layer having utf8 chars 145 | ogr.set_input(tmp_sqlite, table_name="pointsutf8", srs="EPSG:4258") 146 | out_shp = tmpdir.join("pointsutf8_01.shp") 147 | ogr.set_output(str(out_shp)) 148 | ogr.set_encoding("UTF-8") 149 | ogr.execute() 150 | assert ogr.returncode == 0 151 | assert out_shp.check(file=1) 152 | out_cpg = tmpdir.join("pointsutf8_01.cpg") 153 | assert out_cpg.read() =='UTF-8' 154 | 155 | 156 | # export as latin1 a layer having utf8 chars 157 | out_shp = tmpdir.join("pointsutf8_02.shp") 158 | ogr.set_output(str(out_shp)) 159 | ogr.set_encoding("ISO-8859-1") 160 | output = ogr.execute() 161 | assert ogr.returncode == 0 162 | 163 | assert out_shp.check(file=1) 164 | assert "Warning" in ogr.stderr 165 | assert "One or several characters couldn't be converted correctly from UTF-8 to ISO-8859-1" in ogr.stderr 166 | 167 | out_cpg = tmpdir.join("pointsutf8_02.cpg") 168 | assert out_cpg.read() == "ISO-8859-1" 169 | -------------------------------------------------------------------------------- /gdaltools/gdalinfocmd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | ''' 4 | gvSIG Online. 5 | Copyright (C) 2007-2015 gvSIG Association. 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU Affero General Public License as 9 | published by the Free Software Foundation, either version 3 of the 10 | License, or (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU Affero General Public License for more details. 16 | 17 | You should have received a copy of the GNU Affero General Public License 18 | along with this program. If not, see . 19 | ''' 20 | ''' 21 | @author: Cesar Martinez Izquierdo - Scolab 22 | ''' 23 | import logging 24 | import re 25 | import os 26 | from .basetypes import GdalToolsError, Wrapper, ConnectionString, FileConnectionString 27 | import io 28 | 29 | 30 | def get_raster_stats(raster_path): 31 | """ 32 | Gets the statistics of the raster using gdalinfo command. 33 | Returns an array of tuples, containing the min, max, mean and stdev values 34 | for each band. 35 | 36 | Usage: 37 | import gdaltools 38 | stats = gdaltools.get_raster_stats('path_to_my_raster') 39 | (band0_ min, band0_max, band0_mean, band0_stdev) = stats[0] 40 | (band1_ min, band1_max, band1_mean, band1_stdev) = stats[1] 41 | """ 42 | gi = GdalInfo() 43 | gi.set_input(raster_path) 44 | gi.set_flags(stats=True) 45 | gi.execute() 46 | return gi.get_raster_stats() 47 | 48 | 49 | def gdalinfo(raster_path, **flags): 50 | """ 51 | Returns the output of gdalinfo command on the provided raster. 52 | 53 | :param raster_path: The path to the raster file 54 | :param flags: flags for the gdalinfo command. See GdalInfo.set_flags for accepted parameters 55 | """ 56 | gi = GdalInfo() 57 | gi.set_input(raster_path) 58 | if flags: 59 | gi.set_flags(**flags) 60 | return gi.execute() 61 | 62 | class GdalInfo(Wrapper): 63 | """ 64 | Wrapper for the gdalinfo command 65 | """ 66 | CMD = 'gdalinfo' 67 | 68 | __BAND_PATTERN=re.compile("Band ([0-9]+).*") 69 | __BAND_STATS_PATTERN=re.compile(r' Minimum=([-+]?\d*\.\d+|\d+), Maximum=([-+]?\d*\.\d+|\d+), Mean=([-+]?\d*\.\d+|\d+), StdDev=([-+]?\d*\.\d+|\d+).*') 70 | __BAND_NO_DATA_PATTERN=re.compile(" NoData Value=(.*)") 71 | 72 | def __init__(self, version=1, command_path=None): 73 | Wrapper.__init__(self, version, command_path) 74 | self.set_flags() 75 | self.output = None 76 | 77 | """ 78 | def _get_default_command(self): 79 | return self.GDALINFO_PATH 80 | """ 81 | 82 | def set_input(self, input_raster): 83 | if isinstance(input_raster, ConnectionString): 84 | self.in_ds = input_raster 85 | else: 86 | self.in_ds = FileConnectionString(input_raster) 87 | return self 88 | 89 | def set_flags( 90 | self, 91 | stats=False, mm=False, approx_stats=False, hist=False, nogcp=False, 92 | nomd=False, nrat=False, noct=False, checksum=False, listmdd=False, mdd=None, 93 | nofl=False, sd=None, proj4=False): 94 | """ 95 | :param mdd: None, "all" or a list of metadata domains to report 96 | :param sd: None or the number of subdataset to get info from 97 | """ 98 | self.stats = stats 99 | self.mm = mm 100 | self.approx_stats = approx_stats 101 | self.hist = hist 102 | self.nogcp = nogcp 103 | self.nomd = nomd 104 | self.nrat = nrat 105 | self.noct = noct 106 | self.checksum = checksum 107 | self.listmdd = listmdd 108 | self.mdd = mdd 109 | self.nofl = nofl 110 | self.sd = sd 111 | self.proj4 = proj4 112 | return self 113 | 114 | def _get_flag_array(self): 115 | result = [] 116 | if self.stats: 117 | result.append("-stats") 118 | if self.mm: 119 | result.append("-mm") 120 | if self.approx_stats: 121 | result.append("-approx_stats") 122 | if self.hist: 123 | result.append("-hist") 124 | if self.nogcp: 125 | result.append("-nogcp") 126 | if self.nomd: 127 | result.append("-nomd") 128 | if self.nrat: 129 | result.append("-nrat") 130 | if self.noct: 131 | result.append("-noct") 132 | if self.checksum: 133 | result.append("-checksum") 134 | if self.listmdd: 135 | result.append("-listmdd") 136 | if self.mdd: 137 | if self.mdd=="all": 138 | result.extend(["-mdd", "all"]) 139 | else: 140 | try: 141 | for domain in self.mdd: 142 | result.extend(["-mdd", domain]) 143 | except: 144 | raise GdalToolsError(-1, "mdd flag only accepts 'all' or a list of metadata domains to report about") 145 | if self.nofl: 146 | result.append("-nofl") 147 | if self.sd: 148 | result.extend(["-sd", self.sd]) 149 | if self.proj4: 150 | result.append("-proj4") 151 | return result 152 | 153 | def execute(self): 154 | cmd = self._get_command() 155 | args = [cmd] + self._get_flag_array() + [self.in_ds.encode()] 156 | safe_args = [cmd] + self._get_flag_array() + [str(self.in_ds)] 157 | logging.debug(" ".join(safe_args)) 158 | self.safe_args = safe_args 159 | self.output = self._do_execute(args) 160 | return self.output 161 | 162 | def get_raster_stats(self): 163 | """ 164 | Gets the statistics of the raster using gdalinfo command. 165 | Returns an array of tuples, containing the min, max, mean and stdev values 166 | for each band. 167 | 168 | Usage: 169 | import gdal_tools 170 | stats = gdal_tools.get_raster_stats('path_to_my_raster') 171 | (band0_ min, band0_max, band0_mean, band0_stdev) = stats[0] 172 | (band1_ min, band1_max, band1_mean, band1_stdev) = stats[1] 173 | """ 174 | stats_str = self.stdout 175 | buf = io.StringIO(stats_str) 176 | result = [] 177 | band_results = self.__process_band(buf) 178 | while band_results != None: 179 | result.append(band_results) 180 | band_results = self.__process_band(buf) 181 | buf.close() 182 | return result 183 | 184 | def __process_band(self, buf): 185 | self.__find_band(buf) 186 | line = buf.readline() 187 | while line != "": 188 | m = self.__BAND_STATS_PATTERN.match(line) 189 | if m: # stats found 190 | return (float(m.group(1)), float(m.group(2)), float(m.group(3)), float(m.group(4))) 191 | line = buf.readline() 192 | return None 193 | 194 | def __find_band(self, buf): 195 | line = buf.readline() 196 | while line != "": 197 | m = self.__BAND_PATTERN.match(line) 198 | if m: # band found 199 | return 200 | line = buf.readline() 201 | -------------------------------------------------------------------------------- /pavement.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import print_function 4 | 5 | import os 6 | import sys 7 | import time 8 | import subprocess 9 | 10 | # Import parameters from the setup file. 11 | sys.path.append('.') 12 | from setup import ( 13 | setup_dict, get_project_files, print_success_message, 14 | print_failure_message, _lint, _test, _test_all, 15 | CODE_DIRECTORY, DOCS_DIRECTORY, TESTS_DIRECTORY, PYTEST_FLAGS) 16 | 17 | from paver.easy import options, task, needs, consume_args 18 | from paver.setuputils import install_distutils_tasks 19 | 20 | options(setup=setup_dict) 21 | 22 | install_distutils_tasks() 23 | 24 | ## Miscellaneous helper functions 25 | 26 | 27 | def print_passed(): 28 | # generated on http://patorjk.com/software/taag/#p=display&f=Small&t=PASSED 29 | print_success_message(r''' ___ _ ___ ___ ___ ___ 30 | | _ \/_\ / __/ __| __| \ 31 | | _/ _ \\__ \__ \ _|| |) | 32 | |_|/_/ \_\___/___/___|___/ 33 | ''') 34 | 35 | 36 | def print_failed(): 37 | # generated on http://patorjk.com/software/taag/#p=display&f=Small&t=FAILED 38 | print_failure_message(r''' ___ _ ___ _ ___ ___ 39 | | __/_\ |_ _| | | __| \ 40 | | _/ _ \ | || |__| _|| |) | 41 | |_/_/ \_\___|____|___|___/ 42 | ''') 43 | 44 | 45 | class cwd(object): 46 | """Class used for temporarily changing directories. Can be though of 47 | as a `pushd /my/dir' then a `popd' at the end. 48 | """ 49 | def __init__(self, newcwd): 50 | """:param newcwd: directory to make the cwd 51 | :type newcwd: :class:`str` 52 | """ 53 | self.newcwd = newcwd 54 | 55 | def __enter__(self): 56 | self.oldcwd = os.getcwd() 57 | os.chdir(self.newcwd) 58 | return os.getcwd() 59 | 60 | def __exit__(self, type_, value, traceback): 61 | # This acts like a `finally' clause: it will always be executed. 62 | os.chdir(self.oldcwd) 63 | 64 | 65 | ## Task-related functions 66 | 67 | def _doc_make(*make_args): 68 | """Run make in sphinx' docs directory. 69 | 70 | :return: exit code 71 | """ 72 | if sys.platform == 'win32': 73 | # Windows 74 | make_cmd = ['make.bat'] 75 | else: 76 | # Linux, Mac OS X, and others 77 | make_cmd = ['make'] 78 | make_cmd.extend(make_args) 79 | 80 | # Account for a stupid Python "bug" on Windows: 81 | # 82 | with cwd(DOCS_DIRECTORY): 83 | retcode = subprocess.call(make_cmd) 84 | return retcode 85 | 86 | 87 | ## Tasks 88 | 89 | @task 90 | @needs('doc_html', 'setuptools.command.sdist') 91 | def sdist(): 92 | """Build the HTML docs and the tarball.""" 93 | pass 94 | 95 | 96 | @task 97 | def test(): 98 | """Run the unit tests.""" 99 | raise SystemExit(_test()) 100 | 101 | 102 | @task 103 | def lint(): 104 | # This refuses to format properly when running `paver help' unless 105 | # this ugliness is used. 106 | ('Perform PEP8 style check, run PyFlakes, and run McCabe complexity ' 107 | 'metrics on the code.') 108 | raise SystemExit(_lint()) 109 | 110 | 111 | @task 112 | def test_all(): 113 | """Perform a style check and run all unit tests.""" 114 | retcode = _test_all() 115 | if retcode == 0: 116 | print_passed() 117 | else: 118 | print_failed() 119 | raise SystemExit(retcode) 120 | 121 | 122 | @task 123 | @consume_args 124 | def run(args): 125 | """Run the package's main script. All arguments are passed to it.""" 126 | # The main script expects to get the called executable's name as 127 | # argv[0]. However, paver doesn't provide that in args. Even if it did (or 128 | # we dove into sys.argv), it wouldn't be useful because it would be paver's 129 | # executable. So we just pass the package name in as the executable name, 130 | # since it's close enough. This should never be seen by an end user 131 | # installing through Setuptools anyway. 132 | #from spatialitintrospect.main import main 133 | #raise SystemExit(main([CODE_DIRECTORY] + args)) 134 | pass 135 | 136 | 137 | @task 138 | def commit(): 139 | """Commit only if all the tests pass.""" 140 | if _test_all() == 0: 141 | subprocess.check_call(['git', 'commit']) 142 | else: 143 | print_failure_message('\nTests failed, not committing.') 144 | 145 | 146 | @task 147 | def coverage(): 148 | """Run tests and show test coverage report.""" 149 | try: 150 | import pytest_cov # NOQA 151 | except ImportError: 152 | print_failure_message( 153 | 'Install the pytest coverage plugin to use this task, ' 154 | "i.e., `pip install pytest-cov'.") 155 | raise SystemExit(1) 156 | import pytest 157 | pytest.main(PYTEST_FLAGS + [ 158 | '--cov', CODE_DIRECTORY, 159 | '--cov-report', 'term-missing', 160 | TESTS_DIRECTORY]) 161 | 162 | 163 | @task # NOQA 164 | def doc_watch(): 165 | """Watch for changes in the docs and rebuild HTML docs when changed.""" 166 | try: 167 | from watchdog.events import FileSystemEventHandler 168 | from watchdog.observers import Observer 169 | except ImportError: 170 | print_failure_message('Install the watchdog package to use this task, ' 171 | "i.e., `pip install watchdog'.") 172 | raise SystemExit(1) 173 | 174 | class RebuildDocsEventHandler(FileSystemEventHandler): 175 | def __init__(self, base_paths): 176 | self.base_paths = base_paths 177 | 178 | def dispatch(self, event): 179 | """Dispatches events to the appropriate methods. 180 | :param event: The event object representing the file system event. 181 | :type event: :class:`watchdog.events.FileSystemEvent` 182 | """ 183 | for base_path in self.base_paths: 184 | if event.src_path.endswith(base_path): 185 | super(RebuildDocsEventHandler, self).dispatch(event) 186 | # We found one that matches. We're done. 187 | return 188 | 189 | def on_modified(self, event): 190 | print_failure_message('Modification detected. Rebuilding docs.') 191 | # # Strip off the path prefix. 192 | # import os 193 | # if event.src_path[len(os.getcwd()) + 1:].startswith( 194 | # CODE_DIRECTORY): 195 | # # sphinx-build doesn't always pick up changes on code files, 196 | # # even though they are used to generate the documentation. As 197 | # # a workaround, just clean before building. 198 | doc_html() 199 | print_success_message('Docs have been rebuilt.') 200 | 201 | print_success_message( 202 | 'Watching for changes in project files, press Ctrl-C to cancel...') 203 | handler = RebuildDocsEventHandler(get_project_files()) 204 | observer = Observer() 205 | observer.schedule(handler, path='.', recursive=True) 206 | observer.start() 207 | try: 208 | while True: 209 | time.sleep(1) 210 | except KeyboardInterrupt: 211 | observer.stop() 212 | observer.join() 213 | 214 | 215 | @task 216 | @needs('doc_html') 217 | def doc_open(): 218 | """Build the HTML docs and open them in a web browser.""" 219 | doc_index = os.path.join(DOCS_DIRECTORY, 'build', 'html', 'index.html') 220 | if sys.platform == 'darwin': 221 | # Mac OS X 222 | subprocess.check_call(['open', doc_index]) 223 | elif sys.platform == 'win32': 224 | # Windows 225 | subprocess.check_call(['start', doc_index], shell=True) 226 | elif sys.platform == 'linux2': 227 | # All freedesktop-compatible desktops 228 | subprocess.check_call(['xdg-open', doc_index]) 229 | else: 230 | print_failure_message( 231 | "Unsupported platform. Please open `{0}' manually.".format( 232 | doc_index)) 233 | 234 | 235 | @task 236 | def get_tasks(): 237 | """Get all paver-defined tasks.""" 238 | from paver.tasks import environment 239 | for task in environment.get_tasks(): 240 | print(task.shortname) 241 | 242 | 243 | @task 244 | def doc_html(): 245 | """Build the HTML docs.""" 246 | retcode = _doc_make('html') 247 | 248 | if retcode: 249 | raise SystemExit(retcode) 250 | 251 | 252 | @task 253 | def doc_clean(): 254 | """Clean (delete) the built docs.""" 255 | retcode = _doc_make('clean') 256 | 257 | if retcode: 258 | raise SystemExit(retcode) 259 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import print_function 3 | 4 | import os 5 | import sys 6 | import importlib.util 7 | import subprocess 8 | 9 | ## Python 2.6 subprocess.check_output compatibility. Thanks Greg Hewgill! 10 | if 'check_output' not in dir(subprocess): 11 | def check_output(cmd_args, *args, **kwargs): 12 | proc = subprocess.Popen( 13 | cmd_args, *args, 14 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) 15 | out, err = proc.communicate() 16 | if proc.returncode != 0: 17 | raise subprocess.CalledProcessError(args) 18 | return out 19 | subprocess.check_output = check_output 20 | 21 | from setuptools import setup, find_packages 22 | from setuptools.command.test import test as TestCommand 23 | from distutils import spawn 24 | 25 | try: 26 | import colorama 27 | colorama.init() # Initialize colorama on Windows 28 | except ImportError: 29 | # Don't require colorama just for running paver tasks. This allows us to 30 | # run `paver install' without requiring the user to first have colorama 31 | # installed. 32 | pass 33 | 34 | # Add the current directory to the module search path. 35 | sys.path.insert(0, os.path.abspath('.')) 36 | 37 | ## Constants 38 | CODE_DIRECTORY = 'gdaltools' 39 | DOCS_DIRECTORY = 'docs' 40 | TESTS_DIRECTORY = 'tests' 41 | PYTEST_FLAGS = ['--doctest-modules'] 42 | 43 | # Import metadata. Normally this would just be: 44 | # 45 | # from spatialiteintrospect import metadata 46 | # 47 | # However, when we do this, we also import `spatialite-introspect/__init__.py'. If this 48 | # imports names from some other modules and these modules have third-party 49 | # dependencies that need installing (which happens after this file is run), the 50 | # script will crash. What we do instead is to load the metadata module by path 51 | # instead, effectively side-stepping the dependency problem. Please make sure 52 | # metadata has no dependencies, otherwise they will need to be added to 53 | # the setup_requires keyword. 54 | spec = importlib.util.spec_from_file_location('metadata', os.path.join(CODE_DIRECTORY, 'metadata.py')) 55 | metadata = importlib.util.module_from_spec(spec) 56 | spec.loader.exec_module(metadata) 57 | 58 | ## Miscellaneous helper functions 59 | 60 | def get_project_files(): 61 | """Retrieve a list of project files, ignoring hidden files. 62 | 63 | :return: sorted list of project files 64 | :rtype: :class:`list` 65 | """ 66 | if is_git_project() and has_git(): 67 | return get_git_project_files() 68 | 69 | project_files = [] 70 | for top, subdirs, files in os.walk('.'): 71 | for subdir in subdirs: 72 | if subdir.startswith('.'): 73 | subdirs.remove(subdir) 74 | 75 | for f in files: 76 | if f.startswith('.'): 77 | continue 78 | project_files.append(os.path.join(top, f)) 79 | 80 | return project_files 81 | 82 | 83 | def is_git_project(): 84 | return os.path.isdir('.git') 85 | 86 | 87 | def has_git(): 88 | return bool(spawn.find_executable("git")) 89 | 90 | 91 | def get_git_project_files(): 92 | """Retrieve a list of all non-ignored files, including untracked files, 93 | excluding deleted files. 94 | 95 | :return: sorted list of git project files 96 | :rtype: :class:`list` 97 | """ 98 | cached_and_untracked_files = git_ls_files( 99 | '--cached', # All files cached in the index 100 | '--others', # Untracked files 101 | # Exclude untracked files that would be excluded by .gitignore, etc. 102 | '--exclude-standard') 103 | uncommitted_deleted_files = git_ls_files('--deleted') 104 | 105 | # Since sorting of files in a set is arbitrary, return a sorted list to 106 | # provide a well-defined order to tools like flake8, etc. 107 | return sorted(cached_and_untracked_files - uncommitted_deleted_files) 108 | 109 | 110 | def git_ls_files(*cmd_args): 111 | """Run ``git ls-files`` in the top-level project directory. Arguments go 112 | directly to execution call. 113 | 114 | :return: set of file names 115 | :rtype: :class:`set` 116 | """ 117 | cmd = ['git', 'ls-files'] 118 | cmd.extend(cmd_args) 119 | return set(subprocess.check_output(cmd).splitlines()) 120 | 121 | 122 | def print_success_message(message): 123 | """Print a message indicating success in green color to STDOUT. 124 | 125 | :param message: the message to print 126 | :type message: :class:`str` 127 | """ 128 | try: 129 | import colorama 130 | print(colorama.Fore.GREEN + message + colorama.Fore.RESET) 131 | except ImportError: 132 | print(message) 133 | 134 | 135 | def print_failure_message(message): 136 | """Print a message indicating failure in red color to STDERR. 137 | 138 | :param message: the message to print 139 | :type message: :class:`str` 140 | """ 141 | try: 142 | import colorama 143 | print(colorama.Fore.RED + message + colorama.Fore.RESET, 144 | file=sys.stderr) 145 | except ImportError: 146 | print(message, file=sys.stderr) 147 | 148 | 149 | def read(filename): 150 | """Return the contents of a file. 151 | 152 | :param filename: file path 153 | :type filename: :class:`str` 154 | :return: the file's content 155 | :rtype: :class:`str` 156 | """ 157 | with open(os.path.join(os.path.dirname(__file__), filename)) as f: 158 | return f.read() 159 | 160 | 161 | def _lint(): 162 | """Run lint and return an exit code.""" 163 | # Flake8 doesn't have an easy way to run checks using a Python function, so 164 | # just fork off another process to do it. 165 | 166 | # Python 3 compat: 167 | # - The result of subprocess call outputs are byte strings, meaning we need 168 | # to pass a byte string to endswith. 169 | project_python_files = [filename for filename in get_project_files() 170 | if filename.endswith(b'.py')] 171 | retcode = subprocess.call( 172 | ['flake8', '--max-complexity=10', '--max-line-length=100'] + project_python_files) 173 | if retcode == 0: 174 | print_success_message('No style errors') 175 | return retcode 176 | 177 | 178 | def _test(): 179 | """Run the unit tests. 180 | 181 | :return: exit code 182 | """ 183 | # Make sure to import pytest in this function. For the reason, see here: 184 | # # NOPEP8 185 | import pytest 186 | # This runs the unit tests. 187 | # It also runs doctest, but only on the modules in TESTS_DIRECTORY. 188 | return pytest.main(PYTEST_FLAGS + [TESTS_DIRECTORY]) 189 | 190 | 191 | def _test_all(): 192 | """Run lint and tests. 193 | 194 | :return: exit code 195 | """ 196 | return _lint() + _test() 197 | 198 | 199 | # The following code is to allow tests to be run with `python setup.py test'. 200 | # The main reason to make this possible is to allow tests to be run as part of 201 | # Setuptools' automatic run of 2to3 on the source code. The recommended way to 202 | # run tests is still `paver test_all'. 203 | # See 204 | # Code based on # NOPEP8 205 | class TestAllCommand(TestCommand): 206 | def finalize_options(self): 207 | TestCommand.finalize_options(self) 208 | # These are fake, and just set to appease distutils and setuptools. 209 | self.test_suite = True 210 | self.test_args = [] 211 | 212 | def run_tests(self): 213 | raise SystemExit(_test_all()) 214 | 215 | 216 | # define install_requires for specific Python versions 217 | python_version_specific_requires = [] 218 | 219 | from os import path 220 | this_directory = path.abspath(path.dirname(__file__)) 221 | #with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: 222 | with open(path.join(this_directory, 'README.md')) as f: 223 | long_description = f.read() 224 | 225 | # See here for more options: 226 | # 227 | setup_dict = dict( 228 | name=metadata.package, 229 | version=metadata.version, 230 | author=metadata.authors[0], 231 | author_email="", 232 | #maintainer=metadata.authors[0], 233 | #maintainer_email=metadata.emails[0], 234 | url=metadata.url, 235 | description=metadata.description, 236 | long_description=long_description, 237 | long_description_content_type='text/markdown', 238 | # Find a list of classifiers here: 239 | # 240 | classifiers=[ 241 | 'Development Status :: 4 - Beta', 242 | 'Environment :: Console', 243 | 'Intended Audience :: Developers', 244 | 'License :: OSI Approved :: GNU Affero General Public License v3', 245 | 'Natural Language :: English', 246 | 'Operating System :: OS Independent', 247 | 'Programming Language :: Python :: 2', 248 | 'Programming Language :: Python :: 2.6', 249 | 'Programming Language :: Python :: 2.7', 250 | 'Programming Language :: Python :: 3', 251 | 'Programming Language :: Python :: 3.6', 252 | 'Programming Language :: Python :: 3.7', 253 | 'Programming Language :: Python :: 3.8', 254 | 'Programming Language :: Python :: 3.9', 255 | 'Programming Language :: Python :: 3.10', 256 | 'Programming Language :: Python :: 3.11', 257 | 'Programming Language :: Python :: 3.12', 258 | 'Topic :: Scientific/Engineering :: GIS', 259 | 'Topic :: Utilities', 260 | 'Topic :: Database', 261 | 'Topic :: Software Development :: Libraries :: Python Modules', 262 | ], 263 | packages=find_packages(exclude=(TESTS_DIRECTORY,)), 264 | install_requires=[ 265 | # your module dependencies 266 | ] + python_version_specific_requires, 267 | # Allow tests to be run with `python setup.py test'. 268 | tests_require=[ 269 | 'pytest==2.5.1', 270 | 'mock==1.0.1', 271 | 'flake8==2.1.0', 272 | ], 273 | cmdclass={'test': TestAllCommand}, 274 | zip_safe=False, # don't use eggs 275 | ) 276 | 277 | 278 | def main(): 279 | setup(**setup_dict) 280 | 281 | 282 | if __name__ == '__main__': 283 | main() 284 | -------------------------------------------------------------------------------- /gdaltools/ogr2ogrcmd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | ''' 4 | gvSIG Online. 5 | Copyright (C) 2015-2016 gvSIG Association. 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU Affero General Public License as 9 | published by the Free Software Foundation, either version 3 of the 10 | License, or (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU Affero General Public License for more details. 16 | 17 | You should have received a copy of the GNU Affero General Public License 18 | along with this program. If not, see . 19 | ''' 20 | ''' 21 | @author: Cesar Martinez Izquierdo - Scolab 22 | ''' 23 | 24 | 25 | import logging 26 | import os 27 | from .basetypes import Wrapper, ConnectionString, FileConnectionString, PgConnectionString 28 | 29 | 30 | class Ogr2ogr(Wrapper): 31 | """ 32 | Wrapper for the ogr2ogr command 33 | """ 34 | MODE_LAYER_CREATE="CR" 35 | MODE_LAYER_APPEND="AP" 36 | MODE_LAYER_OVERWRITE="OW" 37 | 38 | MODE_DS_CREATE="CR" 39 | MODE_DS_UPDATE="UP" 40 | MODE_DS_CREATE_OR_UPDATE="CU" 41 | 42 | CMD = 'ogr2ogr' 43 | 44 | def __init__(self, version=1, command_path=None): 45 | Wrapper.__init__(self, version, command_path) 46 | self.set_output_mode() 47 | self._dataset_creation_options = {} 48 | self._layer_creation_options = {} 49 | self._dataset_creation_options_internal = {} 50 | self._layer_creation_options_internal = {} 51 | self._config_options = {} 52 | self._config_options_internal = {} 53 | self._open_options = {} 54 | self.geom_type = None 55 | self.encoding = None 56 | self.preserve_fid = None 57 | self.sql = None 58 | self.dim = None 59 | 60 | def set_input(self, input_ds, table_name=None, srs=None): 61 | """ 62 | Sets the input layer 63 | 64 | :param input_ds: The path to the input data source (shapefile, spatialite, etc) 65 | or a ConnectionString object 66 | :param table_name: The name of the input table name in the data source. Can be 67 | omitted for some data source types such as Shapefiles or CSVs 68 | :param srs: Defines the SRS of the input layer, using a EPSG code string 69 | (e.g. "EPSG:4326"). Ogr will try to autodetect the SRS if this parameter is omitted, 70 | but autodetection will fail in a number of situations, so it is always recommended 71 | to explicitly set the SRS parameter 72 | """ 73 | if isinstance(input_ds, ConnectionString): 74 | self.in_ds = input_ds 75 | else: 76 | self.in_ds = FileConnectionString(input_ds) 77 | self.in_table = table_name 78 | self.in_srs = srs 79 | return self 80 | 81 | def set_output(self, output_ds, file_type=None, table_name=None, srs=None): 82 | """ 83 | Sets the output layer 84 | :param output_ds: The path to the output data source (shapefile, spatialite, etc) 85 | or a ConnectionString object (for Postgresql connections, etc) 86 | :param file_type: The output data source type (e.g. "ESRI Shapefile", "GML", 87 | "GeoJSON", "PostgreSQL", etc). See ogr2ogr documentation for the full list 88 | of valid types 89 | :param table_name: The name of the output table name in the data source. If omitted, 90 | the name of the input table will be used. It will be ignored for some data source types 91 | such as Shapefiles or CSVs which don't have the concept of table 92 | :param srs: Defines a transformation from the input SRS to this SRS. 93 | It expects a EPSG code string (e.g. "EPSG:4326"). If omitted and the input SRS 94 | has been defined, then the input SRS will also be used as output SRS 95 | """ 96 | if isinstance(output_ds, ConnectionString): 97 | self.out_ds = output_ds 98 | else: 99 | self.out_ds = FileConnectionString(output_ds) 100 | 101 | if file_type: 102 | self.out_file_type = file_type 103 | else: 104 | dslower = self.out_ds.encode().lower() 105 | if dslower.endswith(".shp"): 106 | self.out_file_type = "ESRI Shapefile" 107 | elif dslower.startswith("pg:"): 108 | self.out_file_type = "PostgreSQL" 109 | elif dslower.endswith(".sqlite"): 110 | self.out_file_type = "SQLite" 111 | elif dslower.endswith(".json") or dslower.endswith(".geojson"): 112 | self.out_file_type = "GeoJSON" 113 | elif dslower.endswith(".gml"): 114 | self.out_file_type = "GML" 115 | elif dslower.endswith(".csv"): 116 | self.out_file_type = "CSV" 117 | elif dslower.endswith(".gpx"): 118 | self.out_file_type = "GPX" 119 | elif dslower.endswith(".kml"): 120 | self.out_file_type = "KML" 121 | else: 122 | self.out_file_type = "ESRI Shapefile" 123 | 124 | if self.out_file_type == "SQLite": 125 | self._dataset_creation_options_internal["SPATIALITE"] = "YES" 126 | self._layer_creation_options_internal["LAUNDER"] = "YES" 127 | elif self.out_file_type == "PostgreSQL": 128 | self._layer_creation_options_internal["LAUNDER"] = "YES" 129 | 130 | self.out_table = table_name 131 | self.out_srs = srs 132 | return self 133 | 134 | @property 135 | def geom_type(self): 136 | if self.out_file_type=="PostgreSQL" and not self._geom_type: 137 | return "PROMOTE_TO_MULTI" 138 | else: 139 | return self._geom_type 140 | 141 | @geom_type.setter 142 | def geom_type(self, geom_type): 143 | self._geom_type = geom_type 144 | 145 | def set_output_mode(self, layer_mode=MODE_LAYER_CREATE, data_source_mode=MODE_DS_CREATE): 146 | self.layer_mode = layer_mode 147 | self.data_source_mode = data_source_mode 148 | 149 | @property 150 | def dataset_creation_options(self): 151 | """ 152 | Dataset creation options, expressed as a dict of options such as 153 | such as {"SPATIALITE": "YES", "METADATA", "YES"} 154 | """ 155 | result = self._dataset_creation_options_internal.copy() 156 | result.update(self._dataset_creation_options) 157 | return result 158 | 159 | @dataset_creation_options.setter 160 | def dataset_creation_options(self, ds_creation_options): 161 | self._dataset_creation_options = ds_creation_options 162 | 163 | @property 164 | def layer_creation_options(self): 165 | """ 166 | Sets layer creation options, expressed as a dict of options such as 167 | {"SPATIAL_INDEX": "YES", "RESIZE": "YES"} 168 | """ 169 | result = self._layer_creation_options_internal.copy() 170 | if self.encoding and self.out_file_type == "ESRI Shapefile": 171 | result["ENCODING"] = self.encoding 172 | result.update(self._layer_creation_options) 173 | return result 174 | 175 | @layer_creation_options.setter 176 | def layer_creation_options(self, layer_creation_options): 177 | self._layer_creation_options = layer_creation_options 178 | 179 | @property 180 | def open_options(self): 181 | """ 182 | Sets open options, expressed as a dict of options such as 183 | {"AUTODETECT_TYPE": "YES", "KEEP_SOURCE_COLUMNS": "YES"} 184 | """ 185 | result = self._open_options.copy() 186 | result.update(self._open_options) 187 | return result 188 | 189 | @open_options.setter 190 | def open_options(self, open_options): 191 | self._open_options = open_options 192 | 193 | def set_encoding(self, encoding): 194 | """ 195 | Sets the encoding used to read and write Shapefiles. You MUST ALWAYS 196 | set the encoding when working with Shapefiles, unless you are only using ASCII 197 | characters. Note that the encoding is ignored for the rest of data sources. 198 | 199 | Ogr2ogr does not properly handle charset recoding for Shapefiles, 200 | so it is not possible to read from a Shapefile using enconding A and 201 | to write to another Shapefile using encoding B. 202 | 203 | However, it IS possible to read from a Shapefile using encoding A, 204 | write to a different format (such as Spatialite), 205 | and then do the reverse operation reading from Spatialite and writing 206 | to Shapefile using encoding B. 207 | 208 | :param encoding: A string defining the charset to use (e.g. "UTF-8" or 209 | "ISO-8859-1"). 210 | """ 211 | self.encoding = encoding 212 | 213 | def set_preserve_fid(self, preserve_fid): 214 | """ 215 | Use the FID of the source features instead of letting the output driver to 216 | automatically assign a new one. It is True by default when output mode is 217 | not append. 218 | :param preserve_fid: True or False 219 | """ 220 | self.preserve_fid = preserve_fid 221 | 222 | def set_sql(self, sql): 223 | """ 224 | Use the FID of the source features instead of letting the output driver to 225 | automatically assign a new one. It is True by default when output mode is 226 | not append. 227 | :param sql: A string defining the sql query 228 | """ 229 | self.sql = sql 230 | 231 | 232 | def set_dim(self, dim): 233 | """ 234 | Force the coordinate dimension to val (valid values are XY, XYZ, XYM, and XYZM 235 | - for backwards compatibility 2 is an alias for XY and 3 is an alias for XYZ). 236 | This affects both the layer geometry type, and feature geometries. The value 237 | can be set to layer_dim to instruct feature geometries to be promoted to the 238 | coordinate dimension declared by the layer. Support for M was added in GDAL 239 | 2.1. 240 | :param dim: A string defining the dimension 241 | """ 242 | self.dim = dim 243 | 244 | @property 245 | def config_options(self): 246 | """ 247 | Gdal/ogr config options, expressed as a dict of options such as 248 | {"SHAPE_ENCODING": "latin1"}, {"OGR_ENABLE_PARTIAL_REPROJECTION": "YES"} 249 | """ 250 | result = self._config_options_internal.copy() 251 | if self.encoding: 252 | result["SHAPE_ENCODING"] = self.encoding 253 | result.update(self._config_options) 254 | return result 255 | 256 | @config_options.setter 257 | def config_options(self, options): 258 | self._config_options = options 259 | 260 | def _is_csv_input(self): 261 | """ 262 | Check if input is a CSV file in order to add open options to args 263 | """ 264 | return isinstance(self.in_ds, FileConnectionString) and self.in_ds.encode().lower().endswith('.csv') 265 | 266 | def execute(self): 267 | args = [self._get_command()] 268 | config_options = self.config_options 269 | 270 | if self.data_source_mode == self.MODE_DS_UPDATE: 271 | args.extend(["-update"]) 272 | elif self.data_source_mode == self.MODE_DS_CREATE_OR_UPDATE: 273 | if isinstance(self.out_ds, FileConnectionString): 274 | if os.path.exists(self.out_ds.encode()): 275 | # if it is a FileConnectionString, only use -update if the file exists 276 | args.extend(["-update"]) 277 | else: 278 | args.extend(["-update"]) 279 | 280 | if self.layer_mode == self.MODE_LAYER_APPEND: 281 | args.extend(["-append"]) 282 | elif self.layer_mode == self.MODE_LAYER_OVERWRITE: 283 | if self.out_file_type == "PostgreSQL" and config_options.get("OGR_TRUNCATE") != "NO": 284 | # prefer truncate for PostgresSQL driver 285 | args.extend(['-append']) 286 | config_options["OGR_TRUNCATE"] = "YES" 287 | else: 288 | args.extend(['-overwrite']) 289 | 290 | if self.out_srs: 291 | args.extend(['-t_srs', self.out_srs]) 292 | 293 | if self.preserve_fid: 294 | args.extend(['-preserve_fid']) 295 | 296 | if self.in_srs: 297 | if not self.out_srs: 298 | args.extend(['-a_srs', self.in_srs]) 299 | args.extend(['-s_srs', self.in_srs]) 300 | 301 | args.extend(["-f", self.out_file_type]) 302 | 303 | for key, value in self.dataset_creation_options.items(): 304 | args.extend(["-dsco", key+"="+value]) 305 | if not '-append' in args: 306 | for key, value in self.layer_creation_options.items(): 307 | args.extend(["-lco", key+"="+value]) 308 | for key, value in config_options.items(): 309 | args.extend(["--config", key, value]) 310 | if self._is_csv_input(): 311 | for key, value in self._open_options.items(): 312 | args.extend(["-oo", key+"="+value]) 313 | 314 | if self.out_table: 315 | out_table = self.out_table 316 | elif self.in_table: 317 | out_table = self.in_table 318 | elif isinstance(self.in_ds, FileConnectionString): 319 | out_table = os.path.splitext(os.path.basename(self.in_ds.encode()))[0] 320 | else: 321 | out_table = None 322 | 323 | if out_table: 324 | if (isinstance(self.out_ds, PgConnectionString) and 325 | self.out_ds.schema and 326 | not out_table.startswith(self.out_ds.schema + ".")): 327 | schema = self.out_ds.schema + "." 328 | else: 329 | schema = "" 330 | args.extend(["-nln", schema + out_table]) 331 | 332 | if self.geom_type: 333 | args.extend(["-nlt", self.geom_type]) 334 | if self.sql: 335 | args.extend(["-sql", self.sql]) 336 | if self.dim: 337 | args.extend(["-dim", self.dim]) 338 | safe_args = list(args) 339 | if self.in_table: 340 | if (isinstance(self.in_ds, PgConnectionString) and 341 | self.in_ds.schema and 342 | not self.in_table.startswith(self.in_ds.schema + ".")): 343 | schema = self.in_ds.schema + "." 344 | else: 345 | schema = "" 346 | args.extend([self.out_ds.encode(), self.in_ds.encode(), schema + self.in_table]) 347 | safe_args.extend([str(self.out_ds), str(self.in_ds), schema + self.in_table]) 348 | else: 349 | args.extend([self.out_ds.encode(), self.in_ds.encode()]) 350 | safe_args.extend([str(self.out_ds), str(self.in_ds)]) 351 | 352 | logging.debug(" ".join(safe_args)) 353 | self.safe_args = safe_args 354 | return self._do_execute(args) 355 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | --------------------------------------------------------------------------------