├── 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 | A DESC C
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 ' REF C CODE N
RD125 1 RD2 2 RD2 3
--------------------------------------------------------------------------------
/tests/data/pointsutf8.dbf:
--------------------------------------------------------------------------------
1 |
2 | a ) NAMELOC C NAMEINT C
Ποταμός 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 |
--------------------------------------------------------------------------------