ucrs 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.
- ucrs-0.1.0/LICENSE +21 -0
- ucrs-0.1.0/PKG-INFO +100 -0
- ucrs-0.1.0/README.md +68 -0
- ucrs-0.1.0/pyproject.toml +75 -0
- ucrs-0.1.0/ucrs.py +162 -0
ucrs-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Panos Mavrogiorgos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ucrs-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ucrs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Panos Mavrogiorgos
|
|
6
|
+
Author-email: pmav99@gmail.com
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Provides-Extra: cartopy
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Provides-Extra: full
|
|
16
|
+
Provides-Extra: gdal
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: cartopy ; extra == "cartopy"
|
|
19
|
+
Requires-Dist: cartopy ; extra == "full"
|
|
20
|
+
Requires-Dist: gdal ; extra == "full"
|
|
21
|
+
Requires-Dist: gdal ; extra == "gdal"
|
|
22
|
+
Requires-Dist: pyproj
|
|
23
|
+
Requires-Dist: pyright (>=1.1.0) ; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest (>=8.0.0) ; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest (>=8.0.0) ; extra == "test"
|
|
26
|
+
Requires-Dist: pytest-cov (>=4.1.0) ; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-cov (>=4.1.0) ; extra == "test"
|
|
28
|
+
Requires-Dist: pytest-xdist (>=3.5.0) ; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-xdist (>=3.5.0) ; extra == "test"
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# UCRS - Unified CRS
|
|
33
|
+
|
|
34
|
+
Working with geospatial data in Python often means juggling multiple libraries, each with its own CRS representation:
|
|
35
|
+
- **pyproj** uses `pyproj.CRS`
|
|
36
|
+
- **cartopy** uses `cartopy.crs.CRS` and `cartopy.crs.Projection`
|
|
37
|
+
- **GDAL/osgeo** uses `osgeo.osr.SpatialReference`
|
|
38
|
+
|
|
39
|
+
Converting between these formats is well
|
|
40
|
+
[documented](https://pyproj4.github.io/pyproj/stable/crs_compatibility.html),
|
|
41
|
+
but a bit tedious.
|
|
42
|
+
|
|
43
|
+
UCRS solves this by providing a single wrapper class that accepts any CRS input
|
|
44
|
+
and lazily converts to an instance of any library you need.
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from ucrs import UCRS
|
|
50
|
+
|
|
51
|
+
# Create UCRS from any CRS representation
|
|
52
|
+
ucrs = UCRS(4326) # EPSG code
|
|
53
|
+
ucrs = UCRS("EPSG:4326") # EPSG string
|
|
54
|
+
ucrs = UCRS(pyproj.CRS.from_epsg(4326)) # pyproj.CRS
|
|
55
|
+
ucrs = UCRS(cartopy.crs.PlateCarree()) # cartopy CRS
|
|
56
|
+
ucrs = UCRS(srs) # osgeo.osr.SpatialReference
|
|
57
|
+
|
|
58
|
+
# Access different representations
|
|
59
|
+
ucrs.proj # Always available
|
|
60
|
+
ucrs.cartopy # Requires cartopy
|
|
61
|
+
ucrs.osgeo # Requires GDAL
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## How It Works
|
|
65
|
+
|
|
66
|
+
UCRS uses `pyproj.CRS` as its internal canonical representation. All conversions follow this pattern:
|
|
67
|
+
|
|
68
|
+
1. **Input** → `pyproj.CRS` (during initialization)
|
|
69
|
+
2. `pyproj.CRS` → **Output format** (via cached properties)
|
|
70
|
+
|
|
71
|
+
Conversions are lazy and cached, so accessing `.proj`, `.cartopy`, or `.osgeo` multiple times returns the same object without re-conversion.
|
|
72
|
+
|
|
73
|
+
## Requirements
|
|
74
|
+
|
|
75
|
+
- Python 3.10+
|
|
76
|
+
- pyproj (required)
|
|
77
|
+
- cartopy (optional)
|
|
78
|
+
- GDAL (optional)
|
|
79
|
+
|
|
80
|
+
## Installation
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Minimal installation (pyproj only)
|
|
84
|
+
pip install ucrs
|
|
85
|
+
|
|
86
|
+
# With cartopy support
|
|
87
|
+
pip install ucrs[cartopy]
|
|
88
|
+
|
|
89
|
+
# With GDAL support
|
|
90
|
+
pip install ucrs[gdal]
|
|
91
|
+
|
|
92
|
+
# With all optional dependencies
|
|
93
|
+
pip install ucrs[full]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT License - see LICENSE file for details.
|
|
100
|
+
|
ucrs-0.1.0/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# UCRS - Unified CRS
|
|
2
|
+
|
|
3
|
+
Working with geospatial data in Python often means juggling multiple libraries, each with its own CRS representation:
|
|
4
|
+
- **pyproj** uses `pyproj.CRS`
|
|
5
|
+
- **cartopy** uses `cartopy.crs.CRS` and `cartopy.crs.Projection`
|
|
6
|
+
- **GDAL/osgeo** uses `osgeo.osr.SpatialReference`
|
|
7
|
+
|
|
8
|
+
Converting between these formats is well
|
|
9
|
+
[documented](https://pyproj4.github.io/pyproj/stable/crs_compatibility.html),
|
|
10
|
+
but a bit tedious.
|
|
11
|
+
|
|
12
|
+
UCRS solves this by providing a single wrapper class that accepts any CRS input
|
|
13
|
+
and lazily converts to an instance of any library you need.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from ucrs import UCRS
|
|
19
|
+
|
|
20
|
+
# Create UCRS from any CRS representation
|
|
21
|
+
ucrs = UCRS(4326) # EPSG code
|
|
22
|
+
ucrs = UCRS("EPSG:4326") # EPSG string
|
|
23
|
+
ucrs = UCRS(pyproj.CRS.from_epsg(4326)) # pyproj.CRS
|
|
24
|
+
ucrs = UCRS(cartopy.crs.PlateCarree()) # cartopy CRS
|
|
25
|
+
ucrs = UCRS(srs) # osgeo.osr.SpatialReference
|
|
26
|
+
|
|
27
|
+
# Access different representations
|
|
28
|
+
ucrs.proj # Always available
|
|
29
|
+
ucrs.cartopy # Requires cartopy
|
|
30
|
+
ucrs.osgeo # Requires GDAL
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## How It Works
|
|
34
|
+
|
|
35
|
+
UCRS uses `pyproj.CRS` as its internal canonical representation. All conversions follow this pattern:
|
|
36
|
+
|
|
37
|
+
1. **Input** → `pyproj.CRS` (during initialization)
|
|
38
|
+
2. `pyproj.CRS` → **Output format** (via cached properties)
|
|
39
|
+
|
|
40
|
+
Conversions are lazy and cached, so accessing `.proj`, `.cartopy`, or `.osgeo` multiple times returns the same object without re-conversion.
|
|
41
|
+
|
|
42
|
+
## Requirements
|
|
43
|
+
|
|
44
|
+
- Python 3.10+
|
|
45
|
+
- pyproj (required)
|
|
46
|
+
- cartopy (optional)
|
|
47
|
+
- GDAL (optional)
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Minimal installation (pyproj only)
|
|
53
|
+
pip install ucrs
|
|
54
|
+
|
|
55
|
+
# With cartopy support
|
|
56
|
+
pip install ucrs[cartopy]
|
|
57
|
+
|
|
58
|
+
# With GDAL support
|
|
59
|
+
pip install ucrs[gdal]
|
|
60
|
+
|
|
61
|
+
# With all optional dependencies
|
|
62
|
+
pip install ucrs[full]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT License - see LICENSE file for details.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ucrs"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Panos Mavrogiorgos",email = "pmav99@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"pyproj"
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.optional-dependencies]
|
|
15
|
+
cartopy = ["cartopy"]
|
|
16
|
+
gdal = ["gdal"]
|
|
17
|
+
full = ["cartopy", "gdal"]
|
|
18
|
+
test = [
|
|
19
|
+
"pytest>=8.0.0",
|
|
20
|
+
"pytest-cov>=4.1.0",
|
|
21
|
+
"pytest-xdist>=3.5.0",
|
|
22
|
+
]
|
|
23
|
+
dev = [
|
|
24
|
+
"pytest>=8.0.0",
|
|
25
|
+
"pytest-cov>=4.1.0",
|
|
26
|
+
"pytest-xdist>=3.5.0",
|
|
27
|
+
"pyright>=1.1.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
32
|
+
build-backend = "poetry.core.masonry.api"
|
|
33
|
+
|
|
34
|
+
[tool.poetry.group.dev.dependencies]
|
|
35
|
+
pytest = "*"
|
|
36
|
+
pytest-cov = "*"
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
minversion = "8.0"
|
|
40
|
+
addopts = [
|
|
41
|
+
"-ra",
|
|
42
|
+
"--strict-markers",
|
|
43
|
+
"--strict-config",
|
|
44
|
+
"--showlocals",
|
|
45
|
+
]
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
pythonpath = ["."]
|
|
48
|
+
markers = [
|
|
49
|
+
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
|
50
|
+
"requires_cartopy: marks tests that require cartopy to be installed",
|
|
51
|
+
"requires_osgeo: marks tests that require osgeo/GDAL to be installed",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[tool.coverage.run]
|
|
55
|
+
source = ["ucrs"]
|
|
56
|
+
branch = true
|
|
57
|
+
parallel = true
|
|
58
|
+
|
|
59
|
+
[tool.coverage.report]
|
|
60
|
+
precision = 2
|
|
61
|
+
show_missing = true
|
|
62
|
+
skip_covered = false
|
|
63
|
+
exclude_lines = [
|
|
64
|
+
"pragma: no cover",
|
|
65
|
+
"def __repr__",
|
|
66
|
+
"raise AssertionError",
|
|
67
|
+
"raise NotImplementedError",
|
|
68
|
+
"if __name__ == .__main__.:",
|
|
69
|
+
"if TYPE_CHECKING:",
|
|
70
|
+
"@abstractmethod",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.coverage.paths]
|
|
74
|
+
source = ["ucrs.py"]
|
|
75
|
+
|
ucrs-0.1.0/ucrs.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
|
|
2
|
+
"""Unified CRS (UCRS) class for seamless conversion between pyproj, cartopy, and osgeo."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from typing import cast
|
|
8
|
+
from typing import TypeAlias
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
import pyproj
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import cartopy.crs as ccrs
|
|
15
|
+
CARTOPY_AVAILABLE = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
CARTOPY_AVAILABLE = False # pyright: ignore[reportConstantRedefinition]
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import osgeo
|
|
21
|
+
from osgeo.osr import SpatialReference
|
|
22
|
+
OSGEO_AVAILABLE = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
OSGEO_AVAILABLE = False # pyright: ignore[reportConstantRedefinition]
|
|
25
|
+
|
|
26
|
+
# Type alias for accepted input types
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
import cartopy.crs as ccrs
|
|
29
|
+
from osgeo.osr import SpatialReference
|
|
30
|
+
|
|
31
|
+
CRSInput: TypeAlias = (
|
|
32
|
+
pyproj.CRS
|
|
33
|
+
| ccrs.CRS
|
|
34
|
+
| ccrs.Projection
|
|
35
|
+
| SpatialReference
|
|
36
|
+
| str
|
|
37
|
+
| int
|
|
38
|
+
| dict[str, str]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class UCRS:
|
|
43
|
+
"""Unified CRS for seamless conversion between pyproj, cartopy, and osgeo.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
obj : CRSInput
|
|
48
|
+
Can be anything that pyproj.crs.CRS.from_user_input() accepts, or
|
|
49
|
+
instances of cartopy.crs.CRS, cartopy.crs.Projection, pyproj.CRS,
|
|
50
|
+
or osgeo.osr.SpatialReference.
|
|
51
|
+
|
|
52
|
+
Examples
|
|
53
|
+
--------
|
|
54
|
+
>>> ucrs = UCRS(4326)
|
|
55
|
+
>>> ucrs = UCRS("EPSG:4326")
|
|
56
|
+
>>> ucrs = UCRS(pyproj.CRS.from_epsg(4326))
|
|
57
|
+
|
|
58
|
+
Access different CRS representations:
|
|
59
|
+
>>> proj_crs = ucrs.proj
|
|
60
|
+
>>> cart_crs = ucrs.cartopy # if cartopy available
|
|
61
|
+
>>> osgeo_crs = ucrs.osgeo # if osgeo available
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, obj: CRSInput) -> None:
|
|
65
|
+
"""Initialize UCRS from various CRS representations."""
|
|
66
|
+
self._original: CRSInput = obj
|
|
67
|
+
|
|
68
|
+
# Convert input to pyproj.CRS
|
|
69
|
+
match obj:
|
|
70
|
+
case pyproj.CRS():
|
|
71
|
+
self._pyproj_crs: pyproj.CRS = obj
|
|
72
|
+
|
|
73
|
+
case _ if OSGEO_AVAILABLE and isinstance(obj, SpatialReference):
|
|
74
|
+
# Convert from osgeo to pyproj
|
|
75
|
+
wkt: str
|
|
76
|
+
if osgeo.version_info.major < 3:
|
|
77
|
+
wkt = cast(str, obj.ExportToWkt())
|
|
78
|
+
else:
|
|
79
|
+
wkt = cast(str, obj.ExportToWkt(["FORMAT=WKT2_2018"]))
|
|
80
|
+
self._pyproj_crs = pyproj.CRS.from_wkt(wkt)
|
|
81
|
+
|
|
82
|
+
case _ if CARTOPY_AVAILABLE and isinstance(obj, (ccrs.CRS, ccrs.Projection)):
|
|
83
|
+
# cartopy.crs.CRS inherits from pyproj.CRS, so use from_user_input
|
|
84
|
+
self._pyproj_crs = pyproj.CRS.from_user_input(obj)
|
|
85
|
+
|
|
86
|
+
case _:
|
|
87
|
+
# Handle all other inputs via from_user_input (str, int, dict, etc.)
|
|
88
|
+
self._pyproj_crs = pyproj.CRS.from_user_input(obj)
|
|
89
|
+
|
|
90
|
+
@cached_property
|
|
91
|
+
def proj(self) -> pyproj.CRS:
|
|
92
|
+
"""Return pyproj.CRS representation."""
|
|
93
|
+
return self._pyproj_crs
|
|
94
|
+
|
|
95
|
+
@cached_property
|
|
96
|
+
def cartopy(self) -> ccrs.CRS | ccrs.Projection:
|
|
97
|
+
"""Return cartopy.crs.CRS or cartopy.crs.Projection representation.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
ccrs.CRS or ccrs.Projection
|
|
102
|
+
Geographic CRS returns ccrs.CRS, projected CRS returns ccrs.Projection.
|
|
103
|
+
|
|
104
|
+
Raises
|
|
105
|
+
------
|
|
106
|
+
ImportError
|
|
107
|
+
If cartopy is not installed.
|
|
108
|
+
RuntimeError
|
|
109
|
+
If the CRS cannot be converted (e.g., WKT2 not available).
|
|
110
|
+
"""
|
|
111
|
+
if not CARTOPY_AVAILABLE:
|
|
112
|
+
raise ImportError(
|
|
113
|
+
"cartopy is not installed. Install it with: pip install cartopy"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
proj_crs = self.proj
|
|
117
|
+
|
|
118
|
+
# Check if geographic or projected
|
|
119
|
+
if proj_crs.is_geographic:
|
|
120
|
+
return ccrs.CRS(proj_crs)
|
|
121
|
+
else:
|
|
122
|
+
return ccrs.Projection(proj_crs)
|
|
123
|
+
|
|
124
|
+
@cached_property
|
|
125
|
+
def osgeo(self) -> SpatialReference:
|
|
126
|
+
"""Return osgeo.osr.SpatialReference representation.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
SpatialReference
|
|
131
|
+
GDAL/OGR Spatial Reference object.
|
|
132
|
+
|
|
133
|
+
Raises
|
|
134
|
+
------
|
|
135
|
+
ImportError
|
|
136
|
+
If osgeo is not installed.
|
|
137
|
+
"""
|
|
138
|
+
if not OSGEO_AVAILABLE:
|
|
139
|
+
raise ImportError(
|
|
140
|
+
"osgeo (GDAL) is not installed. Install it with: pip install gdal"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
from pyproj.enums import WktVersion
|
|
144
|
+
|
|
145
|
+
proj_crs = self.proj
|
|
146
|
+
osr_crs = SpatialReference()
|
|
147
|
+
|
|
148
|
+
# Convert from pyproj to osgeo
|
|
149
|
+
if osgeo.version_info.major < 3:
|
|
150
|
+
osr_crs.ImportFromWkt(proj_crs.to_wkt(WktVersion.WKT1_GDAL))
|
|
151
|
+
else:
|
|
152
|
+
osr_crs.ImportFromWkt(proj_crs.to_wkt())
|
|
153
|
+
|
|
154
|
+
return osr_crs
|
|
155
|
+
|
|
156
|
+
def __repr__(self) -> str: # pyright: ignore[reportImplicitOverride]
|
|
157
|
+
"""Return string representation."""
|
|
158
|
+
return f"UCRS({self.proj.name})"
|
|
159
|
+
|
|
160
|
+
def __str__(self) -> str: # pyright: ignore[reportImplicitOverride]
|
|
161
|
+
"""Return string representation."""
|
|
162
|
+
return str(self.proj)
|