togo 0.1.0__tar.gz → 0.1.3__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.
Potentially problematic release.
This version of togo might be problematic. Click here for more details.
- togo-0.1.3/PKG-INFO +251 -0
- togo-0.1.3/README.md +231 -0
- togo-0.1.3/pyproject.toml +35 -0
- {togo-0.1.0 → togo-0.1.3}/setup.py +21 -16
- {togo-0.1.0 → togo-0.1.3}/tests/test_geometry.py +37 -0
- {togo-0.1.0 → togo-0.1.3}/tests/test_line.py +15 -0
- togo-0.1.3/tests/test_segment.py +17 -0
- {togo-0.1.0 → togo-0.1.3}/togo.c +15463 -8598
- togo-0.1.3/togo.egg-info/PKG-INFO +251 -0
- {togo-0.1.0 → togo-0.1.3}/togo.egg-info/SOURCES.txt +1 -0
- {togo-0.1.0 → togo-0.1.3}/togo.pyx +343 -41
- togo-0.1.0/PKG-INFO +0 -141
- togo-0.1.0/README.md +0 -126
- togo-0.1.0/pyproject.toml +0 -12
- togo-0.1.0/togo.egg-info/PKG-INFO +0 -141
- {togo-0.1.0 → togo-0.1.3}/LICENSE +0 -0
- {togo-0.1.0 → togo-0.1.3}/MANIFEST.in +0 -0
- {togo-0.1.0 → togo-0.1.3}/setup.cfg +0 -0
- {togo-0.1.0 → togo-0.1.3}/tests/test_factories.py +0 -0
- {togo-0.1.0 → togo-0.1.3}/tests/test_geometries.py +0 -0
- {togo-0.1.0 → togo-0.1.3}/tests/test_point.py +0 -0
- {togo-0.1.0 → togo-0.1.3}/tests/test_poly.py +0 -0
- {togo-0.1.0 → togo-0.1.3}/tests/test_rect.py +0 -0
- {togo-0.1.0 → togo-0.1.3}/tests/test_ring.py +0 -0
- {togo-0.1.0 → togo-0.1.3}/tg.c +0 -0
- {togo-0.1.0 → togo-0.1.3}/tg.h +0 -0
- {togo-0.1.0 → togo-0.1.3}/togo.egg-info/dependency_links.txt +0 -0
- {togo-0.1.0 → togo-0.1.3}/togo.egg-info/top_level.txt +0 -0
togo-0.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: togo
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Lightweight Python bindings for the TG geometry library
|
|
5
|
+
Author-email: Giorgio Salluzzo <giorgio.salluzzo@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mindflayer/togo
|
|
8
|
+
Project-URL: Source, https://github.com/mindflayer/togo
|
|
9
|
+
Project-URL: Tracker, https://github.com/mindflayer/togo/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: C
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: license
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# ToGo
|
|
22
|
+
Python bindings for [TG](https://github.com/tidwall/tg)
|
|
23
|
+
(Geometry library for C - Fast point-in-polygon)
|
|
24
|
+
|
|
25
|
+
ToGo is a high-performance Python library for computational geometry, providing a Cython wrapper around the above-mentioned C library.
|
|
26
|
+
|
|
27
|
+
The main goal is to offer a Pythonic, object-oriented, fast and memory-efficient library for geometric operations, including spatial predicates, format conversions, and spatial indexing.
|
|
28
|
+
|
|
29
|
+
While ToGo's API interfaces are still a work in progress, the underling C library is stable and well-tested.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install togo
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- Fast and efficient geometric operations
|
|
40
|
+
- Support for standard geometry types: Point, Line, Ring, Polygon, and their multi-variants
|
|
41
|
+
- Geometric predicates: contains, intersects, covers, touches, etc.
|
|
42
|
+
- Format conversion between WKT, GeoJSON, WKB, and HEX
|
|
43
|
+
- Spatial indexing for accelerated queries
|
|
44
|
+
- Memory-efficient C implementation with Python-friendly interface
|
|
45
|
+
|
|
46
|
+
## Basic Usage
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from togo import Geometry, Point, Ring, Poly
|
|
50
|
+
|
|
51
|
+
# Create a geometry from GeoJSON
|
|
52
|
+
geom = Geometry('{"type":"Point","coordinates":[1.0,2.0]}')
|
|
53
|
+
|
|
54
|
+
# Create a point
|
|
55
|
+
point = Point(1.0, 2.0)
|
|
56
|
+
|
|
57
|
+
# Create a polygon
|
|
58
|
+
ring = Ring([(0,0), (10,0), (10,10), (0,10), (0,0)])
|
|
59
|
+
polygon = Poly(ring)
|
|
60
|
+
|
|
61
|
+
# Convert to various formats
|
|
62
|
+
wkt = polygon.as_geometry().to_wkt()
|
|
63
|
+
geojson = polygon.as_geometry().to_geojson()
|
|
64
|
+
|
|
65
|
+
# Perform spatial predicates
|
|
66
|
+
point_geom = point.as_geometry()
|
|
67
|
+
contains = polygon.as_geometry().contains(point_geom)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Core Classes
|
|
71
|
+
|
|
72
|
+
### Geometry
|
|
73
|
+
|
|
74
|
+
The base class that wraps tg_geom structures and provides core operations:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# Create from various formats
|
|
78
|
+
g1 = Geometry('POINT(1 2)', fmt='wkt')
|
|
79
|
+
g2 = Geometry('{"type":"Point","coordinates":[1,2]}', fmt='geojson')
|
|
80
|
+
|
|
81
|
+
# Geometric predicates
|
|
82
|
+
g1.intersects(g2)
|
|
83
|
+
g1.contains(g2)
|
|
84
|
+
g1.within(g2)
|
|
85
|
+
|
|
86
|
+
# Format conversion
|
|
87
|
+
g1.to_wkt()
|
|
88
|
+
g1.to_geojson()
|
|
89
|
+
|
|
90
|
+
# Access sub-geometries by index (e.g., for GeometryCollection, MultiPoint, etc.)
|
|
91
|
+
gc = Geometry('GEOMETRYCOLLECTION(POINT(1 2),POINT(3 4))', fmt='wkt')
|
|
92
|
+
first = gc[0] # Bound to TG function call tg_geom_geometry_at(idx)
|
|
93
|
+
second = gc[1]
|
|
94
|
+
print(first.type_string()) # 'Point'
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Point
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from togo import Point
|
|
101
|
+
|
|
102
|
+
# Create a point
|
|
103
|
+
p = Point(1.0, 2.0)
|
|
104
|
+
|
|
105
|
+
# Access coordinates
|
|
106
|
+
print(f"X: {p.x}, Y: {p.y}")
|
|
107
|
+
|
|
108
|
+
# Get as a tuple
|
|
109
|
+
print(p.as_tuple())
|
|
110
|
+
|
|
111
|
+
# Convert to a Geometry object
|
|
112
|
+
geom = p.as_geometry()
|
|
113
|
+
print(geom.type_string())
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Segment
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from togo import Segment, Point
|
|
120
|
+
|
|
121
|
+
# Create a segment from two points (or tuples)
|
|
122
|
+
seg = Segment(Point(0, 0), Point(1, 1))
|
|
123
|
+
# Or using tuples
|
|
124
|
+
tuple_seg = Segment((0, 0), (1, 1))
|
|
125
|
+
|
|
126
|
+
# Access endpoints
|
|
127
|
+
print(seg.a) # Point(0, 0)
|
|
128
|
+
print(seg.b) # Point(1, 1)
|
|
129
|
+
|
|
130
|
+
# Get the bounding rectangle
|
|
131
|
+
rect = seg.rect()
|
|
132
|
+
print(rect) # ((0.0, 0.0), (1.0, 1.0))
|
|
133
|
+
|
|
134
|
+
# Check intersection with another segment
|
|
135
|
+
other = Segment((1, 1), (2, 2))
|
|
136
|
+
print(seg.intersects(other)) # True or False
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Line
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from togo import Line
|
|
143
|
+
|
|
144
|
+
# Create a line from a list of tuples
|
|
145
|
+
line = Line([(0,0), (1,1), (2,0)])
|
|
146
|
+
|
|
147
|
+
# Get number of points
|
|
148
|
+
print(f"Number of points: {line.num_points()}")
|
|
149
|
+
|
|
150
|
+
# Get all points as a list of tuples
|
|
151
|
+
print(f"Points: {line.points()}")
|
|
152
|
+
|
|
153
|
+
# Get the length of the line
|
|
154
|
+
print(f"Length: {line.length()}")
|
|
155
|
+
|
|
156
|
+
# Get the bounding box
|
|
157
|
+
print(f"Bounding box: {line.rect()}")
|
|
158
|
+
|
|
159
|
+
# Get a point by index
|
|
160
|
+
print(f"First point: {line[0].as_tuple()}")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Ring
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from togo import Ring
|
|
167
|
+
|
|
168
|
+
# Create a ring (must be closed)
|
|
169
|
+
ring = Ring([(0,0), (10,0), (10,10), (0,10), (0,0)])
|
|
170
|
+
|
|
171
|
+
# Get area and perimeter
|
|
172
|
+
print(f"Area: {ring.area()}")
|
|
173
|
+
print(f"Perimeter: {ring.perimeter()}")
|
|
174
|
+
|
|
175
|
+
# Check if it's convex or clockwise
|
|
176
|
+
print(f"Is convex: {ring.is_convex()}")
|
|
177
|
+
print(f"Is clockwise: {ring.is_clockwise()}")
|
|
178
|
+
|
|
179
|
+
# Get bounding box
|
|
180
|
+
min_pt, max_pt = ring.rect().min, ring.rect().max
|
|
181
|
+
print(f"Bounding box: {min_pt.as_tuple()}, {max_pt.as_tuple()}")
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Poly
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from togo import Poly, Ring, Point
|
|
188
|
+
|
|
189
|
+
# Create a polygon with one exterior ring and one interior hole
|
|
190
|
+
exterior = Ring([(0,0), (10,0), (10,10), (0,10), (0,0)])
|
|
191
|
+
hole1 = Ring([(1,1), (2,1), (2,2), (1,2), (1,1)])
|
|
192
|
+
poly = Poly(exterior, holes=[hole1])
|
|
193
|
+
|
|
194
|
+
# Get the exterior ring
|
|
195
|
+
ext_ring = poly.exterior()
|
|
196
|
+
print(f"Exterior has {ext_ring.num_points()} points")
|
|
197
|
+
|
|
198
|
+
# Get number of holes
|
|
199
|
+
print(f"Number of holes: {poly.num_holes()}")
|
|
200
|
+
|
|
201
|
+
# Get a hole by index
|
|
202
|
+
h = poly.hole(0)
|
|
203
|
+
print(f"Hole area: {h.area()}")
|
|
204
|
+
|
|
205
|
+
# A polygon is a geometry, so you can use geometry methods
|
|
206
|
+
geom = poly.as_geometry()
|
|
207
|
+
print(f"Contains point (5,5): {geom.contains(Point(5,5).as_geometry())}")
|
|
208
|
+
# Point is inside the hole, so it is not contained by the polygon
|
|
209
|
+
print(f"Contains point (1.5,1.5): {geom.contains(Point(1.5,1.5).as_geometry())}")
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### MultiGeometries
|
|
213
|
+
|
|
214
|
+
Creating collections of geometries:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from togo import Geometry, Point, Line, Poly, Ring
|
|
218
|
+
|
|
219
|
+
# Create a MultiPoint
|
|
220
|
+
multi_point = Geometry.from_multipoint([(0,0), (1,1), Point(2,2)])
|
|
221
|
+
|
|
222
|
+
# Create a MultiLineString
|
|
223
|
+
multi_line = Geometry.from_multilinestring([
|
|
224
|
+
[(0,0), (1,1)],
|
|
225
|
+
Line([(2,2), (3,3)])
|
|
226
|
+
])
|
|
227
|
+
|
|
228
|
+
# Create a MultiPolygon
|
|
229
|
+
poly1 = Poly(Ring([(0,0), (1,0), (1,1), (0,1), (0,0)]))
|
|
230
|
+
poly2 = Poly(Ring([(2,2), (3,2), (3,3), (2,3), (2,2)]))
|
|
231
|
+
multi_poly = Geometry.from_multipolygon([poly1, poly2])
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Polygon Indexing
|
|
235
|
+
|
|
236
|
+
Togo supports different polygon indexing strategies for optimized spatial operations:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
from togo import TGIndex, set_polygon_indexing_mode
|
|
240
|
+
|
|
241
|
+
# Set the indexing mode
|
|
242
|
+
set_polygon_indexing_mode(TGIndex.NATURAL) # or NONE, YSTRIPES
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Performance Considerations
|
|
246
|
+
|
|
247
|
+
- Togo is optimized for speed and memory efficiency
|
|
248
|
+
- For large datasets, proper indexing can significantly improve performance
|
|
249
|
+
- Creating geometries with the appropriate format avoids unnecessary conversions
|
|
250
|
+
|
|
251
|
+
Soon there will be a full API documentation, for now please refer to the test suite for more usage examples.
|
togo-0.1.3/README.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# ToGo
|
|
2
|
+
Python bindings for [TG](https://github.com/tidwall/tg)
|
|
3
|
+
(Geometry library for C - Fast point-in-polygon)
|
|
4
|
+
|
|
5
|
+
ToGo is a high-performance Python library for computational geometry, providing a Cython wrapper around the above-mentioned C library.
|
|
6
|
+
|
|
7
|
+
The main goal is to offer a Pythonic, object-oriented, fast and memory-efficient library for geometric operations, including spatial predicates, format conversions, and spatial indexing.
|
|
8
|
+
|
|
9
|
+
While ToGo's API interfaces are still a work in progress, the underling C library is stable and well-tested.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install togo
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- Fast and efficient geometric operations
|
|
20
|
+
- Support for standard geometry types: Point, Line, Ring, Polygon, and their multi-variants
|
|
21
|
+
- Geometric predicates: contains, intersects, covers, touches, etc.
|
|
22
|
+
- Format conversion between WKT, GeoJSON, WKB, and HEX
|
|
23
|
+
- Spatial indexing for accelerated queries
|
|
24
|
+
- Memory-efficient C implementation with Python-friendly interface
|
|
25
|
+
|
|
26
|
+
## Basic Usage
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from togo import Geometry, Point, Ring, Poly
|
|
30
|
+
|
|
31
|
+
# Create a geometry from GeoJSON
|
|
32
|
+
geom = Geometry('{"type":"Point","coordinates":[1.0,2.0]}')
|
|
33
|
+
|
|
34
|
+
# Create a point
|
|
35
|
+
point = Point(1.0, 2.0)
|
|
36
|
+
|
|
37
|
+
# Create a polygon
|
|
38
|
+
ring = Ring([(0,0), (10,0), (10,10), (0,10), (0,0)])
|
|
39
|
+
polygon = Poly(ring)
|
|
40
|
+
|
|
41
|
+
# Convert to various formats
|
|
42
|
+
wkt = polygon.as_geometry().to_wkt()
|
|
43
|
+
geojson = polygon.as_geometry().to_geojson()
|
|
44
|
+
|
|
45
|
+
# Perform spatial predicates
|
|
46
|
+
point_geom = point.as_geometry()
|
|
47
|
+
contains = polygon.as_geometry().contains(point_geom)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Core Classes
|
|
51
|
+
|
|
52
|
+
### Geometry
|
|
53
|
+
|
|
54
|
+
The base class that wraps tg_geom structures and provides core operations:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# Create from various formats
|
|
58
|
+
g1 = Geometry('POINT(1 2)', fmt='wkt')
|
|
59
|
+
g2 = Geometry('{"type":"Point","coordinates":[1,2]}', fmt='geojson')
|
|
60
|
+
|
|
61
|
+
# Geometric predicates
|
|
62
|
+
g1.intersects(g2)
|
|
63
|
+
g1.contains(g2)
|
|
64
|
+
g1.within(g2)
|
|
65
|
+
|
|
66
|
+
# Format conversion
|
|
67
|
+
g1.to_wkt()
|
|
68
|
+
g1.to_geojson()
|
|
69
|
+
|
|
70
|
+
# Access sub-geometries by index (e.g., for GeometryCollection, MultiPoint, etc.)
|
|
71
|
+
gc = Geometry('GEOMETRYCOLLECTION(POINT(1 2),POINT(3 4))', fmt='wkt')
|
|
72
|
+
first = gc[0] # Bound to TG function call tg_geom_geometry_at(idx)
|
|
73
|
+
second = gc[1]
|
|
74
|
+
print(first.type_string()) # 'Point'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Point
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from togo import Point
|
|
81
|
+
|
|
82
|
+
# Create a point
|
|
83
|
+
p = Point(1.0, 2.0)
|
|
84
|
+
|
|
85
|
+
# Access coordinates
|
|
86
|
+
print(f"X: {p.x}, Y: {p.y}")
|
|
87
|
+
|
|
88
|
+
# Get as a tuple
|
|
89
|
+
print(p.as_tuple())
|
|
90
|
+
|
|
91
|
+
# Convert to a Geometry object
|
|
92
|
+
geom = p.as_geometry()
|
|
93
|
+
print(geom.type_string())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Segment
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from togo import Segment, Point
|
|
100
|
+
|
|
101
|
+
# Create a segment from two points (or tuples)
|
|
102
|
+
seg = Segment(Point(0, 0), Point(1, 1))
|
|
103
|
+
# Or using tuples
|
|
104
|
+
tuple_seg = Segment((0, 0), (1, 1))
|
|
105
|
+
|
|
106
|
+
# Access endpoints
|
|
107
|
+
print(seg.a) # Point(0, 0)
|
|
108
|
+
print(seg.b) # Point(1, 1)
|
|
109
|
+
|
|
110
|
+
# Get the bounding rectangle
|
|
111
|
+
rect = seg.rect()
|
|
112
|
+
print(rect) # ((0.0, 0.0), (1.0, 1.0))
|
|
113
|
+
|
|
114
|
+
# Check intersection with another segment
|
|
115
|
+
other = Segment((1, 1), (2, 2))
|
|
116
|
+
print(seg.intersects(other)) # True or False
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Line
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from togo import Line
|
|
123
|
+
|
|
124
|
+
# Create a line from a list of tuples
|
|
125
|
+
line = Line([(0,0), (1,1), (2,0)])
|
|
126
|
+
|
|
127
|
+
# Get number of points
|
|
128
|
+
print(f"Number of points: {line.num_points()}")
|
|
129
|
+
|
|
130
|
+
# Get all points as a list of tuples
|
|
131
|
+
print(f"Points: {line.points()}")
|
|
132
|
+
|
|
133
|
+
# Get the length of the line
|
|
134
|
+
print(f"Length: {line.length()}")
|
|
135
|
+
|
|
136
|
+
# Get the bounding box
|
|
137
|
+
print(f"Bounding box: {line.rect()}")
|
|
138
|
+
|
|
139
|
+
# Get a point by index
|
|
140
|
+
print(f"First point: {line[0].as_tuple()}")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Ring
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from togo import Ring
|
|
147
|
+
|
|
148
|
+
# Create a ring (must be closed)
|
|
149
|
+
ring = Ring([(0,0), (10,0), (10,10), (0,10), (0,0)])
|
|
150
|
+
|
|
151
|
+
# Get area and perimeter
|
|
152
|
+
print(f"Area: {ring.area()}")
|
|
153
|
+
print(f"Perimeter: {ring.perimeter()}")
|
|
154
|
+
|
|
155
|
+
# Check if it's convex or clockwise
|
|
156
|
+
print(f"Is convex: {ring.is_convex()}")
|
|
157
|
+
print(f"Is clockwise: {ring.is_clockwise()}")
|
|
158
|
+
|
|
159
|
+
# Get bounding box
|
|
160
|
+
min_pt, max_pt = ring.rect().min, ring.rect().max
|
|
161
|
+
print(f"Bounding box: {min_pt.as_tuple()}, {max_pt.as_tuple()}")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Poly
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from togo import Poly, Ring, Point
|
|
168
|
+
|
|
169
|
+
# Create a polygon with one exterior ring and one interior hole
|
|
170
|
+
exterior = Ring([(0,0), (10,0), (10,10), (0,10), (0,0)])
|
|
171
|
+
hole1 = Ring([(1,1), (2,1), (2,2), (1,2), (1,1)])
|
|
172
|
+
poly = Poly(exterior, holes=[hole1])
|
|
173
|
+
|
|
174
|
+
# Get the exterior ring
|
|
175
|
+
ext_ring = poly.exterior()
|
|
176
|
+
print(f"Exterior has {ext_ring.num_points()} points")
|
|
177
|
+
|
|
178
|
+
# Get number of holes
|
|
179
|
+
print(f"Number of holes: {poly.num_holes()}")
|
|
180
|
+
|
|
181
|
+
# Get a hole by index
|
|
182
|
+
h = poly.hole(0)
|
|
183
|
+
print(f"Hole area: {h.area()}")
|
|
184
|
+
|
|
185
|
+
# A polygon is a geometry, so you can use geometry methods
|
|
186
|
+
geom = poly.as_geometry()
|
|
187
|
+
print(f"Contains point (5,5): {geom.contains(Point(5,5).as_geometry())}")
|
|
188
|
+
# Point is inside the hole, so it is not contained by the polygon
|
|
189
|
+
print(f"Contains point (1.5,1.5): {geom.contains(Point(1.5,1.5).as_geometry())}")
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### MultiGeometries
|
|
193
|
+
|
|
194
|
+
Creating collections of geometries:
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from togo import Geometry, Point, Line, Poly, Ring
|
|
198
|
+
|
|
199
|
+
# Create a MultiPoint
|
|
200
|
+
multi_point = Geometry.from_multipoint([(0,0), (1,1), Point(2,2)])
|
|
201
|
+
|
|
202
|
+
# Create a MultiLineString
|
|
203
|
+
multi_line = Geometry.from_multilinestring([
|
|
204
|
+
[(0,0), (1,1)],
|
|
205
|
+
Line([(2,2), (3,3)])
|
|
206
|
+
])
|
|
207
|
+
|
|
208
|
+
# Create a MultiPolygon
|
|
209
|
+
poly1 = Poly(Ring([(0,0), (1,0), (1,1), (0,1), (0,0)]))
|
|
210
|
+
poly2 = Poly(Ring([(2,2), (3,2), (3,3), (2,3), (2,2)]))
|
|
211
|
+
multi_poly = Geometry.from_multipolygon([poly1, poly2])
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Polygon Indexing
|
|
215
|
+
|
|
216
|
+
Togo supports different polygon indexing strategies for optimized spatial operations:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from togo import TGIndex, set_polygon_indexing_mode
|
|
220
|
+
|
|
221
|
+
# Set the indexing mode
|
|
222
|
+
set_polygon_indexing_mode(TGIndex.NATURAL) # or NONE, YSTRIPES
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Performance Considerations
|
|
226
|
+
|
|
227
|
+
- Togo is optimized for speed and memory efficiency
|
|
228
|
+
- For large datasets, proper indexing can significantly improve performance
|
|
229
|
+
- Creating geometries with the appropriate format avoids unnecessary conversions
|
|
230
|
+
|
|
231
|
+
Soon there will be a full API documentation, for now please refer to the test suite for more usage examples.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"setuptools>=61",
|
|
4
|
+
"wheel",
|
|
5
|
+
"Cython>=3.0.0",
|
|
6
|
+
]
|
|
7
|
+
build-backend = "setuptools.build_meta"
|
|
8
|
+
|
|
9
|
+
[project]
|
|
10
|
+
name = "togo"
|
|
11
|
+
version = "0.1.3"
|
|
12
|
+
description = "Lightweight Python bindings for the TG geometry library"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
authors = [
|
|
15
|
+
{name = "Giorgio Salluzzo", email = "giorgio.salluzzo@gmail.com"}
|
|
16
|
+
]
|
|
17
|
+
requires-python = ">=3.8"
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: C",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Topic :: Scientific/Engineering :: GIS"
|
|
24
|
+
]
|
|
25
|
+
dynamic = ["license"]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/mindflayer/togo"
|
|
29
|
+
Source = "https://github.com/mindflayer/togo"
|
|
30
|
+
Tracker = "https://github.com/mindflayer/togo/issues"
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
addopts = "-v -x -q"
|
|
34
|
+
testpaths = ["tests"]
|
|
35
|
+
pythonpath = ["."]
|
|
@@ -17,31 +17,36 @@ TG_HEADER_FILENAME = os.path.basename(urlparse(TG_HEADER_URL).path)
|
|
|
17
17
|
if not os.path.exists(TG_HEADER_FILENAME):
|
|
18
18
|
urllib.request.urlretrieve(TG_HEADER_URL, TG_HEADER_FILENAME)
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
# Enable optional AddressSanitizer build via env var ASAN=1
|
|
21
|
+
asan_enabled = os.environ.get("ASAN") == "1"
|
|
22
|
+
extra_compile_args = []
|
|
23
|
+
extra_link_args = []
|
|
24
|
+
if asan_enabled:
|
|
25
|
+
# Favor debuggability over speed
|
|
26
|
+
extra_compile_args += [
|
|
27
|
+
"-O1",
|
|
28
|
+
"-g",
|
|
29
|
+
"-fno-omit-frame-pointer",
|
|
30
|
+
"-fsanitize=address",
|
|
31
|
+
]
|
|
32
|
+
extra_link_args += [
|
|
33
|
+
"-fsanitize=address",
|
|
34
|
+
]
|
|
29
35
|
|
|
30
36
|
setup(
|
|
31
|
-
name=NAME,
|
|
32
|
-
version=VERSION,
|
|
33
|
-
description=DESCRIPTION,
|
|
34
|
-
long_description=LONG_DESCRIPTION,
|
|
35
|
-
long_description_content_type=LONG_DESCRIPTION_CONTENT_TYPE,
|
|
36
|
-
license="MIT",
|
|
37
37
|
ext_modules=cythonize(
|
|
38
38
|
[
|
|
39
39
|
Extension(
|
|
40
40
|
"togo",
|
|
41
41
|
sources=["togo.pyx", TG_SOURCE_FILENAME],
|
|
42
42
|
include_dirs=["."],
|
|
43
|
+
extra_compile_args=extra_compile_args,
|
|
44
|
+
extra_link_args=extra_link_args,
|
|
43
45
|
)
|
|
44
46
|
]
|
|
45
47
|
),
|
|
46
|
-
|
|
48
|
+
# Explicitly disable auto-discovery in flat layout
|
|
49
|
+
packages=[],
|
|
50
|
+
py_modules=[],
|
|
51
|
+
license="MIT",
|
|
47
52
|
)
|
|
@@ -94,6 +94,9 @@ def test_geometry_num_lines_polys_geometries():
|
|
|
94
94
|
"GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(0 0,1 1))", fmt="wkt"
|
|
95
95
|
)
|
|
96
96
|
assert g_collection.num_geometries() == 2
|
|
97
|
+
# Use Geometry.__getitem__ to access sub-geometries of MultiLineString
|
|
98
|
+
first_line = g_lines[0]
|
|
99
|
+
assert first_line.type_string() == "LineString"
|
|
97
100
|
|
|
98
101
|
|
|
99
102
|
def test_geometry_z_m():
|
|
@@ -156,3 +159,37 @@ def test_geometry_constructor_invalid():
|
|
|
156
159
|
Geometry("not a geometry", fmt="geojson")
|
|
157
160
|
with pytest.raises(ValueError):
|
|
158
161
|
Geometry("not a geometry", fmt="hex")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_geometry_point_accessor():
|
|
165
|
+
g = Geometry("POINT(3 4)", fmt="wkt")
|
|
166
|
+
pt = g.point()
|
|
167
|
+
assert pt.x == 3.0 and pt.y == 4.0
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_geometry_line_accessor_and_from_linestring():
|
|
171
|
+
points = [(0, 0), (1, 1), (2, 2)]
|
|
172
|
+
g = Geometry.from_linestring(points)
|
|
173
|
+
line = g.line()
|
|
174
|
+
pts = line.points()
|
|
175
|
+
assert pts == points
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_geometry_poly_accessor():
|
|
179
|
+
# Polygon with one ring
|
|
180
|
+
wkt = "POLYGON((0 0,1 0,1 1,0 1,0 0))"
|
|
181
|
+
g = Geometry(wkt, fmt="wkt")
|
|
182
|
+
poly = g.poly()
|
|
183
|
+
ext = poly.exterior()
|
|
184
|
+
assert ext.num_points() == 5
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_geometry_geom_at():
|
|
188
|
+
# GeometryCollection with two points
|
|
189
|
+
g = Geometry("GEOMETRYCOLLECTION(POINT(1 2),POINT(3 4))", fmt="wkt")
|
|
190
|
+
g0 = g[0]
|
|
191
|
+
g1 = g[1]
|
|
192
|
+
assert g0.type_string() == "Point"
|
|
193
|
+
assert g1.type_string() == "Point"
|
|
194
|
+
assert g0.point().x == 1.0 and g0.point().y == 2.0
|
|
195
|
+
assert g1.point().x == 3.0 and g1.point().y == 4.0
|
|
@@ -48,3 +48,18 @@ def test_line_as_geometry():
|
|
|
48
48
|
assert g.type_string() == "LineString"
|
|
49
49
|
rect = g.rect()
|
|
50
50
|
assert rect == ((0.0, 0.0), (2.0, 2.0))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_line_getitem():
|
|
54
|
+
points = [(10, 20), (30, 40), (50, 60)]
|
|
55
|
+
line = Line(points)
|
|
56
|
+
pt0 = line[0]
|
|
57
|
+
pt1 = line[1]
|
|
58
|
+
pt2 = line[2]
|
|
59
|
+
assert pt0.x == 10 and pt0.y == 20
|
|
60
|
+
assert pt1.x == 30 and pt1.y == 40
|
|
61
|
+
assert pt2.x == 50 and pt2.y == 60
|
|
62
|
+
with pytest.raises(IndexError):
|
|
63
|
+
_ = line[3]
|
|
64
|
+
with pytest.raises(IndexError):
|
|
65
|
+
_ = line[-1]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from togo import Point, Segment
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_segment_creation_and_rect():
|
|
5
|
+
a = Point(0, 0)
|
|
6
|
+
b = Point(1, 1)
|
|
7
|
+
seg = Segment(a, b)
|
|
8
|
+
rect = seg.rect()
|
|
9
|
+
assert rect == ((0, 0), (1, 1))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_segment_intersects():
|
|
13
|
+
seg1 = Segment(Point(0, 0), Point(1, 1))
|
|
14
|
+
seg2 = Segment(Point(0, 1), Point(1, 0))
|
|
15
|
+
seg3 = Segment(Point(2, 2), Point(3, 3))
|
|
16
|
+
assert seg1.intersects(seg2)
|
|
17
|
+
assert not seg1.intersects(seg3)
|