terrakio-api 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- terrakio_api-0.1.0/LICENSE +0 -0
- terrakio_api-0.1.0/PKG-INFO +52 -0
- terrakio_api-0.1.0/README.md +8 -0
- terrakio_api-0.1.0/pyproject.toml +3 -0
- terrakio_api-0.1.0/setup.cfg +4 -0
- terrakio_api-0.1.0/setup.py +48 -0
- terrakio_api-0.1.0/terrakio_api/__init__.py +20 -0
- terrakio_api-0.1.0/terrakio_api/api.py +48 -0
- terrakio_api-0.1.0/terrakio_api/client.py +158 -0
- terrakio_api-0.1.0/terrakio_api/config.py +81 -0
- terrakio_api-0.1.0/terrakio_api/exceptions.py +18 -0
- terrakio_api-0.1.0/terrakio_api/utils/__init__.py +9 -0
- terrakio_api-0.1.0/terrakio_api/utils/download.py +51 -0
- terrakio_api-0.1.0/terrakio_api/utils/validation.py +85 -0
- terrakio_api-0.1.0/terrakio_api.egg-info/PKG-INFO +52 -0
- terrakio_api-0.1.0/terrakio_api.egg-info/SOURCES.txt +17 -0
- terrakio_api-0.1.0/terrakio_api.egg-info/dependency_links.txt +1 -0
- terrakio_api-0.1.0/terrakio_api.egg-info/requires.txt +13 -0
- terrakio_api-0.1.0/terrakio_api.egg-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: terrakio_api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A client library for Terrakio's WCS API service
|
|
5
|
+
Home-page: https://github.com/HaizeaAnalytics/terrakio-python-api
|
|
6
|
+
Author: Yupeng Chao
|
|
7
|
+
Author-email: yupeng@haizea.com.au
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering
|
|
18
|
+
Requires-Python: >=3.7
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: requests>=2.25.0
|
|
22
|
+
Requires-Dist: pyyaml>=5.1
|
|
23
|
+
Requires-Dist: xarray>=2023.1.0
|
|
24
|
+
Requires-Dist: netcdf4>=1.6.0
|
|
25
|
+
Requires-Dist: pandas>=1.5.0
|
|
26
|
+
Requires-Dist: numpy>=1.22.0
|
|
27
|
+
Requires-Dist: scipy>=1.8.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=6.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=2.10.0; extra == "dev"
|
|
31
|
+
Requires-Dist: black>=20.8b1; extra == "dev"
|
|
32
|
+
Requires-Dist: flake8>=3.8.0; extra == "dev"
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: author-email
|
|
35
|
+
Dynamic: classifier
|
|
36
|
+
Dynamic: description
|
|
37
|
+
Dynamic: description-content-type
|
|
38
|
+
Dynamic: home-page
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
Dynamic: provides-extra
|
|
41
|
+
Dynamic: requires-dist
|
|
42
|
+
Dynamic: requires-python
|
|
43
|
+
Dynamic: summary
|
|
44
|
+
|
|
45
|
+
# Terrakio API Client
|
|
46
|
+
|
|
47
|
+
A Python client for Terrakio's Web Coverage Service (WCS) API.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install terrakio-api
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="terrakio_api",
|
|
8
|
+
version="0.1.0",
|
|
9
|
+
author="Yupeng Chao",
|
|
10
|
+
author_email="yupeng@haizea.com.au",
|
|
11
|
+
description="A client library for Terrakio's WCS API service",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/HaizeaAnalytics/terrakio-python-api",
|
|
15
|
+
# packages=find_packages(),
|
|
16
|
+
packages=["terrakio_api", "terrakio_api.utils"], # Explicitly list packages instead of find_packages()
|
|
17
|
+
classifiers=[
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.7",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Development Status :: 4 - Beta",
|
|
26
|
+
"Intended Audience :: Science/Research",
|
|
27
|
+
"Topic :: Scientific/Engineering",
|
|
28
|
+
],
|
|
29
|
+
python_requires=">=3.7",
|
|
30
|
+
install_requires=[
|
|
31
|
+
"requests>=2.25.0",
|
|
32
|
+
"pyyaml>=5.1",
|
|
33
|
+
"xarray>=2023.1.0",
|
|
34
|
+
"netcdf4>=1.6.0",
|
|
35
|
+
"pandas>=1.5.0",
|
|
36
|
+
"numpy>=1.22.0",
|
|
37
|
+
"scipy>=1.8.0", # Added SciPy dependency
|
|
38
|
+
],
|
|
39
|
+
extras_require={
|
|
40
|
+
"dev": [
|
|
41
|
+
"pytest>=6.0.0",
|
|
42
|
+
"pytest-cov>=2.10.0",
|
|
43
|
+
"black>=20.8b1",
|
|
44
|
+
"flake8>=3.8.0",
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
metadata_version='2.2'
|
|
48
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Terrakio API Client
|
|
3
|
+
|
|
4
|
+
A Python client for accessing Terrakio's Web Coverage Service (WCS) API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
from .client import Client
|
|
10
|
+
from .config import create_default_config
|
|
11
|
+
from .exceptions import APIError, ConfigurationError, DownloadError, ValidationError
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'Client',
|
|
15
|
+
'create_default_config',
|
|
16
|
+
'APIError',
|
|
17
|
+
'ConfigurationError',
|
|
18
|
+
'DownloadError',
|
|
19
|
+
'ValidationError',
|
|
20
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
from typing import Dict, Any, Tuple, Optional
|
|
4
|
+
|
|
5
|
+
from .exceptions import APIError
|
|
6
|
+
|
|
7
|
+
def make_request(session: requests.Session, url: str, payload: Dict[str, Any],
|
|
8
|
+
timeout: int = 60, verify: bool = True):
|
|
9
|
+
"""
|
|
10
|
+
Make an API request to the Terrakio API.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
session: Requests session with authentication
|
|
14
|
+
url: API endpoint URL
|
|
15
|
+
payload: Request payload
|
|
16
|
+
timeout: Request timeout in seconds
|
|
17
|
+
verify: Verify SSL certificates
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Response object
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
APIError: If the API returns an error
|
|
24
|
+
"""
|
|
25
|
+
try:
|
|
26
|
+
response = session.post(
|
|
27
|
+
url,
|
|
28
|
+
json=payload,
|
|
29
|
+
timeout=timeout,
|
|
30
|
+
verify=verify
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Handle HTTP errors
|
|
34
|
+
if not response.ok:
|
|
35
|
+
error_msg = f"API request failed: {response.status_code} {response.reason}"
|
|
36
|
+
try:
|
|
37
|
+
error_data = response.json()
|
|
38
|
+
if "detail" in error_data:
|
|
39
|
+
error_msg += f" - {error_data['detail']}"
|
|
40
|
+
except:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
raise APIError(error_msg)
|
|
44
|
+
|
|
45
|
+
return response
|
|
46
|
+
|
|
47
|
+
except requests.RequestException as e:
|
|
48
|
+
raise APIError(f"Request failed: {str(e)}")
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import xarray as xr
|
|
3
|
+
import time
|
|
4
|
+
from io import BytesIO
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Any, Optional, Union, BinaryIO
|
|
7
|
+
|
|
8
|
+
from .config import read_config_file, DEFAULT_CONFIG_FILE
|
|
9
|
+
from .exceptions import APIError, ConfigurationError, DownloadError
|
|
10
|
+
from .api import make_request
|
|
11
|
+
from .utils.validation import validate_feature
|
|
12
|
+
|
|
13
|
+
class Client:
|
|
14
|
+
def __init__(self, url: Optional[str] = None, key: Optional[str] = None,
|
|
15
|
+
quiet: bool = False, config_file: Optional[str] = None,
|
|
16
|
+
verify: bool = True, timeout: int = 60):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the Terrakio API client.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
url: API base URL (optional, will use config file if not provided)
|
|
22
|
+
key: API key or token (optional, will use config file if not provided)
|
|
23
|
+
quiet: If True, suppress progress messages
|
|
24
|
+
config_file: Path to configuration file (default is ~/.terrakioapirc)
|
|
25
|
+
verify: Verify SSL certificates
|
|
26
|
+
timeout: Request timeout in seconds
|
|
27
|
+
"""
|
|
28
|
+
self.quiet = quiet
|
|
29
|
+
self.verify = verify
|
|
30
|
+
self.timeout = timeout
|
|
31
|
+
|
|
32
|
+
# Try to get config from parameters first, then config file
|
|
33
|
+
if url is not None and key is not None:
|
|
34
|
+
self.url = url
|
|
35
|
+
self.key = key
|
|
36
|
+
else:
|
|
37
|
+
if config_file is None:
|
|
38
|
+
config_file = DEFAULT_CONFIG_FILE
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
config = read_config_file(config_file)
|
|
42
|
+
self.url = config.get('url')
|
|
43
|
+
self.key = config.get('key')
|
|
44
|
+
except Exception as e:
|
|
45
|
+
raise ConfigurationError(
|
|
46
|
+
f"Failed to read configuration: {e}\n\n"
|
|
47
|
+
"To fix this issue:\n"
|
|
48
|
+
"1. Create a file at ~/.terrakioapirc with:\n"
|
|
49
|
+
"url: https://terrakio-server-candidate-d4w6vamyxq-ts.a.run.app/wcs_secure\n"
|
|
50
|
+
"key: your-api-key\n\n"
|
|
51
|
+
"OR\n\n"
|
|
52
|
+
"2. Initialize the client with explicit parameters:\n"
|
|
53
|
+
"client = terrakio_api.Client(\n"
|
|
54
|
+
" url='https://terrakio-server-candidate-d4w6vamyxq-ts.a.run.app/wcs_secure',\n"
|
|
55
|
+
" key='your-api-key'\n"
|
|
56
|
+
")"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Validate configuration
|
|
60
|
+
if not self.url:
|
|
61
|
+
raise ConfigurationError("Missing API URL in configuration")
|
|
62
|
+
if not self.key:
|
|
63
|
+
raise ConfigurationError("Missing API key in configuration")
|
|
64
|
+
|
|
65
|
+
# Ensure URL doesn't end with slash
|
|
66
|
+
self.url = self.url.rstrip('/')
|
|
67
|
+
|
|
68
|
+
if not self.quiet:
|
|
69
|
+
print(f"Using Terrakio API at: {self.url}")
|
|
70
|
+
|
|
71
|
+
# Initialize session for connection pooling
|
|
72
|
+
self.session = requests.Session()
|
|
73
|
+
self.session.headers.update({
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
'x-api-key': self.key
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
def wcs(self, expr: str, feature: Dict[str, Any], in_crs: str = "epsg:4326",
|
|
79
|
+
out_crs: str = "epsg:4326", output: str = "csv", resolution: int = -1,
|
|
80
|
+
**kwargs):
|
|
81
|
+
"""
|
|
82
|
+
Make a WCS request to the Terrakio API.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
expr: Expression string for data selection
|
|
86
|
+
feature: GeoJSON Feature dictionary containing geometry information
|
|
87
|
+
in_crs: Input coordinate reference system (default: "epsg:4326")
|
|
88
|
+
out_crs: Output coordinate reference system (default: "epsg:4326")
|
|
89
|
+
output: Output format (default: "csv")
|
|
90
|
+
resolution: Resolution value (default: -1)
|
|
91
|
+
**kwargs: Additional parameters to pass to the API
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Data in the requested format (xr.Dataset for netcdf, pd.DataFrame for csv)
|
|
95
|
+
"""
|
|
96
|
+
# Validate the feature object
|
|
97
|
+
validate_feature(feature)
|
|
98
|
+
|
|
99
|
+
# Prepare the payload
|
|
100
|
+
payload = {
|
|
101
|
+
"feature": feature,
|
|
102
|
+
"in_crs": in_crs,
|
|
103
|
+
"out_crs": out_crs,
|
|
104
|
+
"output": output,
|
|
105
|
+
"resolution": resolution,
|
|
106
|
+
"expr": expr,
|
|
107
|
+
**kwargs
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if not self.quiet:
|
|
111
|
+
print(f"Requesting data with expression: {expr}")
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
# Make the API request
|
|
115
|
+
response = self.session.post(self.url, json=payload, timeout=self.timeout, verify=self.verify)
|
|
116
|
+
|
|
117
|
+
# Handle HTTP errors
|
|
118
|
+
if not response.ok:
|
|
119
|
+
error_msg = f"API request failed: {response.status_code} {response.reason}"
|
|
120
|
+
try:
|
|
121
|
+
error_data = response.json()
|
|
122
|
+
if "detail" in error_data:
|
|
123
|
+
error_msg += f" - {error_data['detail']}"
|
|
124
|
+
except:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
raise APIError(error_msg)
|
|
128
|
+
|
|
129
|
+
# Handle different output formats
|
|
130
|
+
if output.lower() == "csv":
|
|
131
|
+
import pandas as pd
|
|
132
|
+
return pd.read_csv(BytesIO(response.content))
|
|
133
|
+
elif output.lower() == "netcdf":
|
|
134
|
+
return xr.open_dataset(BytesIO(response.content))
|
|
135
|
+
else:
|
|
136
|
+
# Try to determine the format and use appropriate reader
|
|
137
|
+
try:
|
|
138
|
+
return xr.open_dataset(BytesIO(response.content))
|
|
139
|
+
except ValueError:
|
|
140
|
+
import pandas as pd
|
|
141
|
+
try:
|
|
142
|
+
return pd.read_csv(BytesIO(response.content))
|
|
143
|
+
except:
|
|
144
|
+
# If all else fails, return the raw content
|
|
145
|
+
return response.content
|
|
146
|
+
|
|
147
|
+
except requests.RequestException as e:
|
|
148
|
+
raise APIError(f"Request failed: {str(e)}")
|
|
149
|
+
|
|
150
|
+
def close(self):
|
|
151
|
+
"""Close the client session."""
|
|
152
|
+
self.session.close()
|
|
153
|
+
|
|
154
|
+
def __enter__(self):
|
|
155
|
+
return self
|
|
156
|
+
|
|
157
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
158
|
+
self.close()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
|
|
6
|
+
from .exceptions import ConfigurationError
|
|
7
|
+
|
|
8
|
+
# Default configuration file location
|
|
9
|
+
DEFAULT_CONFIG_FILE = os.path.expanduser("~/.tkioapirc")
|
|
10
|
+
|
|
11
|
+
def read_config_file(config_file: str = DEFAULT_CONFIG_FILE) -> Dict[str, Any]:
|
|
12
|
+
"""
|
|
13
|
+
Read and parse the configuration file.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
config_file: Path to the configuration file
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Dict[str, Any]: Configuration parameters
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
ConfigurationError: If the configuration file can't be read or parsed
|
|
23
|
+
"""
|
|
24
|
+
config_path = Path(os.path.expanduser(config_file))
|
|
25
|
+
|
|
26
|
+
if not config_path.exists():
|
|
27
|
+
raise ConfigurationError(
|
|
28
|
+
f"Configuration file not found: {config_file}\n"
|
|
29
|
+
f"Please create a file at {config_file} with the following format:\n"
|
|
30
|
+
"url: https://terrakio-server-candidate-d4w6vamyxq-ts.a.run.app/wcs_secure\n"
|
|
31
|
+
"key: your-api-key-here"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
with open(config_path, 'r') as f:
|
|
36
|
+
content = f.read().strip()
|
|
37
|
+
|
|
38
|
+
# Support both YAML and simple key: value format
|
|
39
|
+
if ':' in content and '{' not in content:
|
|
40
|
+
# Simple key: value format (like CDSAPI uses)
|
|
41
|
+
config = {}
|
|
42
|
+
for line in content.split('\n'):
|
|
43
|
+
line = line.strip()
|
|
44
|
+
if line and not line.startswith('#'):
|
|
45
|
+
try:
|
|
46
|
+
key, value = [x.strip() for x in line.split(':', 1)]
|
|
47
|
+
config[key] = value
|
|
48
|
+
except ValueError:
|
|
49
|
+
# Skip lines that don't have a colon
|
|
50
|
+
pass
|
|
51
|
+
return config
|
|
52
|
+
else:
|
|
53
|
+
# YAML format for more complex configuration
|
|
54
|
+
return yaml.safe_load(content) or {}
|
|
55
|
+
except Exception as e:
|
|
56
|
+
raise ConfigurationError(f"Failed to parse configuration file: {e}")
|
|
57
|
+
|
|
58
|
+
def create_default_config(url: str, key: str, config_file: str = DEFAULT_CONFIG_FILE) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Create a default configuration file.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
url: API base URL
|
|
64
|
+
key: API key
|
|
65
|
+
config_file: Path to configuration file
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
ConfigurationError: If the configuration file can't be created
|
|
69
|
+
"""
|
|
70
|
+
config_path = Path(os.path.expanduser(config_file))
|
|
71
|
+
|
|
72
|
+
# Ensure directory exists
|
|
73
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
with open(config_path, 'w') as f:
|
|
77
|
+
f.write(f"url: {url}\n")
|
|
78
|
+
f.write(f"key: {key}\n")
|
|
79
|
+
f.write("# Configuration file for Terrakio API\n")
|
|
80
|
+
except Exception as e:
|
|
81
|
+
raise ConfigurationError(f"Failed to create configuration file: {e}")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class APIError(Exception):
|
|
2
|
+
"""Exception raised for errors in the API responses."""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConfigurationError(Exception):
|
|
7
|
+
"""Exception raised for errors in the configuration."""
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DownloadError(Exception):
|
|
12
|
+
"""Exception raised for errors during data download."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ValidationError(Exception):
|
|
17
|
+
"""Exception raised for invalid request parameters."""
|
|
18
|
+
pass
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Union, BinaryIO
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from ..exceptions import DownloadError
|
|
6
|
+
|
|
7
|
+
def download_file(url: str, target: Union[str, Path, BinaryIO],
|
|
8
|
+
session: requests.Session = None, chunk_size: int = 8192,
|
|
9
|
+
timeout: int = 60, verify: bool = True) -> None:
|
|
10
|
+
"""
|
|
11
|
+
Download a file from a URL.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
url: URL to download from
|
|
15
|
+
target: Path or file-like object to write to
|
|
16
|
+
session: Optional requests session to use
|
|
17
|
+
chunk_size: Size of chunks to download
|
|
18
|
+
timeout: Request timeout
|
|
19
|
+
verify: Verify SSL certificates
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
DownloadError: If download fails
|
|
23
|
+
"""
|
|
24
|
+
try:
|
|
25
|
+
# Use provided session or create a new one
|
|
26
|
+
if session is None:
|
|
27
|
+
_session = requests.Session()
|
|
28
|
+
else:
|
|
29
|
+
_session = session
|
|
30
|
+
|
|
31
|
+
# Stream the download
|
|
32
|
+
with _session.get(url, stream=True, timeout=timeout, verify=verify) as response:
|
|
33
|
+
if not response.ok:
|
|
34
|
+
raise DownloadError(f"Download failed: {response.status_code} {response.reason}")
|
|
35
|
+
|
|
36
|
+
# Handle different target types
|
|
37
|
+
if isinstance(target, (str, Path)):
|
|
38
|
+
with open(target, 'wb') as f:
|
|
39
|
+
for chunk in response.iter_content(chunk_size=chunk_size):
|
|
40
|
+
f.write(chunk)
|
|
41
|
+
else:
|
|
42
|
+
# Assume file-like object
|
|
43
|
+
for chunk in response.iter_content(chunk_size=chunk_size):
|
|
44
|
+
target.write(chunk)
|
|
45
|
+
|
|
46
|
+
except requests.RequestException as e:
|
|
47
|
+
raise DownloadError(f"Download failed: {str(e)}")
|
|
48
|
+
finally:
|
|
49
|
+
# Close the session if we created it
|
|
50
|
+
if session is None and '_session' in locals():
|
|
51
|
+
_session.close()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from typing import Dict, Any, List
|
|
2
|
+
from ..exceptions import ValidationError
|
|
3
|
+
|
|
4
|
+
def validate_feature(feature: Dict[str, Any]) -> None:
|
|
5
|
+
"""
|
|
6
|
+
Validate a GeoJSON Feature object.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
feature: GeoJSON Feature dictionary
|
|
10
|
+
|
|
11
|
+
Raises:
|
|
12
|
+
ValidationError: If the feature is invalid
|
|
13
|
+
"""
|
|
14
|
+
if not isinstance(feature, dict):
|
|
15
|
+
raise ValidationError("Feature must be a dictionary")
|
|
16
|
+
|
|
17
|
+
if feature.get('type') != 'Feature':
|
|
18
|
+
raise ValidationError("Feature must have 'type' property set to 'Feature'")
|
|
19
|
+
|
|
20
|
+
if 'geometry' not in feature:
|
|
21
|
+
raise ValidationError("Feature must have a 'geometry' property")
|
|
22
|
+
|
|
23
|
+
geometry = feature['geometry']
|
|
24
|
+
if not isinstance(geometry, dict):
|
|
25
|
+
raise ValidationError("Feature geometry must be a dictionary")
|
|
26
|
+
|
|
27
|
+
if 'type' not in geometry:
|
|
28
|
+
raise ValidationError("Geometry must have a 'type' property")
|
|
29
|
+
|
|
30
|
+
if 'coordinates' not in geometry:
|
|
31
|
+
raise ValidationError("Geometry must have a 'coordinates' property")
|
|
32
|
+
|
|
33
|
+
# Add more specific validation based on geometry type if needed
|
|
34
|
+
|
|
35
|
+
def create_point_feature(lon: float, lat: float, properties: Dict[str, Any] = None) -> Dict[str, Any]:
|
|
36
|
+
"""
|
|
37
|
+
Create a GeoJSON Point Feature object.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
lon: Longitude coordinate
|
|
41
|
+
lat: Latitude coordinate
|
|
42
|
+
properties: Optional properties dictionary (default: empty dict)
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Dict: GeoJSON Feature object
|
|
46
|
+
"""
|
|
47
|
+
if properties is None:
|
|
48
|
+
properties = {}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
"type": "Feature",
|
|
52
|
+
"properties": properties,
|
|
53
|
+
"geometry": {
|
|
54
|
+
"type": "Point",
|
|
55
|
+
"coordinates": [lon, lat]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def create_polygon_feature(coordinates: List[List[float]], properties: Dict[str, Any] = None) -> Dict[str, Any]:
|
|
61
|
+
"""
|
|
62
|
+
Create a GeoJSON Polygon Feature object.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
coordinates: List of [lon, lat] coordinate pairs forming a polygon (first and last must be identical)
|
|
66
|
+
properties: Optional properties dictionary (default: empty dict)
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Dict: GeoJSON Feature object
|
|
70
|
+
"""
|
|
71
|
+
if properties is None:
|
|
72
|
+
properties = {}
|
|
73
|
+
|
|
74
|
+
# Check if polygon is closed
|
|
75
|
+
if coordinates[0] != coordinates[-1]:
|
|
76
|
+
coordinates.append(coordinates[0]) # Close the polygon
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
"type": "Feature",
|
|
80
|
+
"properties": properties,
|
|
81
|
+
"geometry": {
|
|
82
|
+
"type": "Polygon",
|
|
83
|
+
"coordinates": [coordinates]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: terrakio_api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A client library for Terrakio's WCS API service
|
|
5
|
+
Home-page: https://github.com/HaizeaAnalytics/terrakio-python-api
|
|
6
|
+
Author: Yupeng Chao
|
|
7
|
+
Author-email: yupeng@haizea.com.au
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering
|
|
18
|
+
Requires-Python: >=3.7
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: requests>=2.25.0
|
|
22
|
+
Requires-Dist: pyyaml>=5.1
|
|
23
|
+
Requires-Dist: xarray>=2023.1.0
|
|
24
|
+
Requires-Dist: netcdf4>=1.6.0
|
|
25
|
+
Requires-Dist: pandas>=1.5.0
|
|
26
|
+
Requires-Dist: numpy>=1.22.0
|
|
27
|
+
Requires-Dist: scipy>=1.8.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=6.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=2.10.0; extra == "dev"
|
|
31
|
+
Requires-Dist: black>=20.8b1; extra == "dev"
|
|
32
|
+
Requires-Dist: flake8>=3.8.0; extra == "dev"
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: author-email
|
|
35
|
+
Dynamic: classifier
|
|
36
|
+
Dynamic: description
|
|
37
|
+
Dynamic: description-content-type
|
|
38
|
+
Dynamic: home-page
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
Dynamic: provides-extra
|
|
41
|
+
Dynamic: requires-dist
|
|
42
|
+
Dynamic: requires-python
|
|
43
|
+
Dynamic: summary
|
|
44
|
+
|
|
45
|
+
# Terrakio API Client
|
|
46
|
+
|
|
47
|
+
A Python client for Terrakio's Web Coverage Service (WCS) API.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install terrakio-api
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
terrakio_api/__init__.py
|
|
6
|
+
terrakio_api/api.py
|
|
7
|
+
terrakio_api/client.py
|
|
8
|
+
terrakio_api/config.py
|
|
9
|
+
terrakio_api/exceptions.py
|
|
10
|
+
terrakio_api.egg-info/PKG-INFO
|
|
11
|
+
terrakio_api.egg-info/SOURCES.txt
|
|
12
|
+
terrakio_api.egg-info/dependency_links.txt
|
|
13
|
+
terrakio_api.egg-info/requires.txt
|
|
14
|
+
terrakio_api.egg-info/top_level.txt
|
|
15
|
+
terrakio_api/utils/__init__.py
|
|
16
|
+
terrakio_api/utils/download.py
|
|
17
|
+
terrakio_api/utils/validation.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
terrakio_api
|