shapely 1.3.3__cp34-none-win_amd64.whl

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.
Files changed (95) hide show
  1. Shapely-1.3.3.data/data/shapely/_geos.pxi +52 -0
  2. Shapely-1.3.3.dist-info/DESCRIPTION.rst +505 -0
  3. Shapely-1.3.3.dist-info/METADATA +527 -0
  4. Shapely-1.3.3.dist-info/RECORD +95 -0
  5. Shapely-1.3.3.dist-info/WHEEL +5 -0
  6. Shapely-1.3.3.dist-info/metadata.json +1 -0
  7. Shapely-1.3.3.dist-info/top_level.txt +1 -0
  8. shapely/DLLs/geos.dll +0 -0
  9. shapely/DLLs/geos_c.dll +0 -0
  10. shapely/__init__.py +1 -0
  11. shapely/_geos.pxi +52 -0
  12. shapely/affinity.py +255 -0
  13. shapely/algorithms/__init__.py +0 -0
  14. shapely/algorithms/cga.py +16 -0
  15. shapely/coords.py +206 -0
  16. shapely/ctypes_declarations.py +455 -0
  17. shapely/examples/__init__.py +1 -0
  18. shapely/examples/dissolve.py +53 -0
  19. shapely/examples/geoms.py +51 -0
  20. shapely/examples/intersect.py +81 -0
  21. shapely/examples/world.py +21 -0
  22. shapely/ftools.py +78 -0
  23. shapely/geometry/__init__.py +20 -0
  24. shapely/geometry/base.py +797 -0
  25. shapely/geometry/collection.py +43 -0
  26. shapely/geometry/geo.py +75 -0
  27. shapely/geometry/linestring.py +298 -0
  28. shapely/geometry/multilinestring.py +156 -0
  29. shapely/geometry/multipoint.py +213 -0
  30. shapely/geometry/multipolygon.py +186 -0
  31. shapely/geometry/point.py +269 -0
  32. shapely/geometry/polygon.py +504 -0
  33. shapely/geometry/proxy.py +51 -0
  34. shapely/geos.py +732 -0
  35. shapely/impl.py +126 -0
  36. shapely/iterops.py +55 -0
  37. shapely/linref.py +26 -0
  38. shapely/ops.py +234 -0
  39. shapely/predicates.py +23 -0
  40. shapely/prepared.py +61 -0
  41. shapely/speedups/__init__.py +43 -0
  42. shapely/speedups/_speedups.c +5674 -0
  43. shapely/speedups/_speedups.pyd +0 -0
  44. shapely/tests/__init__.py +81 -0
  45. shapely/tests/binascii_hex.txt +47 -0
  46. shapely/tests/test_affinity.py +255 -0
  47. shapely/tests/test_box.py +24 -0
  48. shapely/tests/test_cga.py +37 -0
  49. shapely/tests/test_collection.py +29 -0
  50. shapely/tests/test_delegated.py +34 -0
  51. shapely/tests/test_dlls.py +20 -0
  52. shapely/tests/test_doctests.py +36 -0
  53. shapely/tests/test_emptiness.py +53 -0
  54. shapely/tests/test_equality.py +29 -0
  55. shapely/tests/test_geointerface.py +74 -0
  56. shapely/tests/test_geomseq.py +13 -0
  57. shapely/tests/test_getitem.py +125 -0
  58. shapely/tests/test_invalid_geometries.py +30 -0
  59. shapely/tests/test_iterops.py +47 -0
  60. shapely/tests/test_linear_referencing.py +72 -0
  61. shapely/tests/test_linemerge.py +52 -0
  62. shapely/tests/test_linestring.py +111 -0
  63. shapely/tests/test_locale.py +56 -0
  64. shapely/tests/test_mapping.py +13 -0
  65. shapely/tests/test_multilinestring.py +61 -0
  66. shapely/tests/test_multipoint.py +73 -0
  67. shapely/tests/test_multipolygon.py +72 -0
  68. shapely/tests/test_ndarrays.py +54 -0
  69. shapely/tests/test_operations.py +70 -0
  70. shapely/tests/test_operators.py +18 -0
  71. shapely/tests/test_persist.py +34 -0
  72. shapely/tests/test_pickle.py +22 -0
  73. shapely/tests/test_point.py +139 -0
  74. shapely/tests/test_polygon.py +186 -0
  75. shapely/tests/test_polygonize.py +49 -0
  76. shapely/tests/test_predicates.py +34 -0
  77. shapely/tests/test_prepared.py +31 -0
  78. shapely/tests/test_products_z.py +17 -0
  79. shapely/tests/test_singularity.py +19 -0
  80. shapely/tests/test_speedups.py +39 -0
  81. shapely/tests/test_styles.py +20 -0
  82. shapely/tests/test_transform.py +84 -0
  83. shapely/tests/test_union.py +72 -0
  84. shapely/tests/test_validation.py +12 -0
  85. shapely/tests/test_vectorized.py +107 -0
  86. shapely/tests/test_xy.py +17 -0
  87. shapely/tests/threading_test.py +40 -0
  88. shapely/topology.py +60 -0
  89. shapely/validation.py +8 -0
  90. shapely/vectorized/__init__.py +3 -0
  91. shapely/vectorized/_vectorized.c +20816 -0
  92. shapely/vectorized/_vectorized.pyd +0 -0
  93. shapely/vectorized/_vectorized.pyx +120 -0
  94. shapely/wkb.py +37 -0
  95. shapely/wkt.py +26 -0
@@ -0,0 +1,43 @@
1
+ """Multi-part collections of geometries
2
+ """
3
+
4
+ from shapely.geometry.base import BaseMultipartGeometry
5
+ from shapely.geometry.base import HeterogeneousGeometrySequence
6
+
7
+
8
+ class GeometryCollection(BaseMultipartGeometry):
9
+
10
+ """A heterogenous collection of geometries
11
+
12
+ Attributes
13
+ ----------
14
+ geoms : sequence
15
+ A sequence of Shapely geometry instances
16
+ """
17
+
18
+ def __init__(self):
19
+ BaseMultipartGeometry.__init__(self)
20
+
21
+ @property
22
+ def __geo_interface__(self):
23
+ geometries = []
24
+ for geom in self.geoms:
25
+ geometries.append(geom.__geo_interface__)
26
+ return dict(type='GeometryCollection', geometries=geometries)
27
+
28
+ @property
29
+ def geoms(self):
30
+ if self.is_empty:
31
+ return []
32
+ return HeterogeneousGeometrySequence(self)
33
+
34
+
35
+ # Test runner
36
+ def _test():
37
+ import doctest
38
+ doctest.testmod()
39
+
40
+
41
+ if __name__ == "__main__":
42
+ _test()
43
+
@@ -0,0 +1,75 @@
1
+ """
2
+ Geometry factories based on the geo interface
3
+ """
4
+
5
+ from .point import Point, asPoint
6
+ from .linestring import LineString, asLineString
7
+ from .polygon import Polygon, asPolygon
8
+ from .multipoint import MultiPoint, asMultiPoint
9
+ from .multilinestring import MultiLineString, asMultiLineString
10
+ from .multipolygon import MultiPolygon, MultiPolygonAdapter
11
+
12
+
13
+ def box(minx, miny, maxx, maxy, ccw=True):
14
+ """Returns a rectangular polygon with configurable normal vector"""
15
+ coords = [(maxx, miny), (maxx, maxy), (minx, maxy), (minx, miny)]
16
+ if not ccw:
17
+ coords = coords[::-1]
18
+ return Polygon(coords)
19
+
20
+ def shape(context):
21
+ """Returns a new, independent geometry with coordinates *copied* from the
22
+ context.
23
+ """
24
+ if hasattr(context, "__geo_interface__"):
25
+ ob = context.__geo_interface__
26
+ else:
27
+ ob = context
28
+ geom_type = ob.get("type").lower()
29
+ if geom_type == "point":
30
+ return Point(ob["coordinates"])
31
+ elif geom_type == "linestring":
32
+ return LineString(ob["coordinates"])
33
+ elif geom_type == "polygon":
34
+ return Polygon(ob["coordinates"][0], ob["coordinates"][1:])
35
+ elif geom_type == "multipoint":
36
+ return MultiPoint(ob["coordinates"])
37
+ elif geom_type == "multilinestring":
38
+ return MultiLineString(ob["coordinates"])
39
+ elif geom_type == "multipolygon":
40
+ return MultiPolygon(ob["coordinates"], context_type='geojson')
41
+ else:
42
+ raise ValueError("Unknown geometry type: %s" % geom_type)
43
+
44
+ def asShape(context):
45
+ """Adapts the context to a geometry interface. The coordinates remain
46
+ stored in the context.
47
+ """
48
+ if hasattr(context, "__geo_interface__"):
49
+ ob = context.__geo_interface__
50
+ else:
51
+ ob = context
52
+
53
+ try:
54
+ geom_type = ob.get("type").lower()
55
+ except AttributeError:
56
+ raise ValueError("Context does not provide geo interface")
57
+
58
+ if geom_type == "point":
59
+ return asPoint(ob["coordinates"])
60
+ elif geom_type == "linestring":
61
+ return asLineString(ob["coordinates"])
62
+ elif geom_type == "polygon":
63
+ return asPolygon(ob["coordinates"][0], ob["coordinates"][1:])
64
+ elif geom_type == "multipoint":
65
+ return asMultiPoint(ob["coordinates"])
66
+ elif geom_type == "multilinestring":
67
+ return asMultiLineString(ob["coordinates"])
68
+ elif geom_type == "multipolygon":
69
+ return MultiPolygonAdapter(ob["coordinates"], context_type='geojson')
70
+ else:
71
+ raise ValueError("Unknown geometry type: %s" % geom_type)
72
+
73
+ def mapping(ob):
74
+ """Returns a GeoJSON-like mapping"""
75
+ return ob.__geo_interface__
@@ -0,0 +1,298 @@
1
+ """Line strings and related utilities
2
+ """
3
+
4
+ import sys
5
+
6
+ if sys.version_info[0] < 3:
7
+ range = xrange
8
+
9
+ from ctypes import c_double, cast, POINTER
10
+
11
+ from shapely.coords import required
12
+ from shapely.geos import lgeos, TopologicalError
13
+ from shapely.geometry.base import BaseGeometry, geom_factory, JOIN_STYLE
14
+ from shapely.geometry.proxy import CachingGeometryProxy
15
+ from shapely.geometry.point import Point
16
+
17
+ __all__ = ['LineString', 'asLineString']
18
+
19
+
20
+ class LineString(BaseGeometry):
21
+ """
22
+ A one-dimensional figure comprising one or more line segments
23
+
24
+ A LineString has non-zero length and zero area. It may approximate a curve
25
+ and need not be straight. Unlike a LinearRing, a LineString is not closed.
26
+ """
27
+
28
+ def __init__(self, coordinates=None):
29
+ """
30
+ Parameters
31
+ ----------
32
+ coordinates : sequence
33
+ A sequence of (x, y [,z]) numeric coordinate pairs or triples or
34
+ an object that provides the numpy array interface, including
35
+ another instance of LineString.
36
+
37
+ Example
38
+ -------
39
+ Create a line with two segments
40
+
41
+ >>> a = LineString([[0, 0], [1, 0], [1, 1]])
42
+ >>> a.length
43
+ 2.0
44
+ """
45
+ BaseGeometry.__init__(self)
46
+ if coordinates is not None:
47
+ self._set_coords(coordinates)
48
+
49
+ @property
50
+ def __geo_interface__(self):
51
+ return {
52
+ 'type': 'LineString',
53
+ 'coordinates': tuple(self.coords)
54
+ }
55
+
56
+ def svg(self, scale_factor=1.):
57
+ """
58
+ SVG representation of the geometry. Scale factor is multiplied by
59
+ the size of the SVG symbol so it can be scaled consistently for a
60
+ consistent appearance based on the canvas size.
61
+ """
62
+ pnt_format = " ".join(["{0},{1}".format(*c) for c in self.coords])
63
+ return """<polyline
64
+ fill="none"
65
+ stroke="{2}"
66
+ stroke-width={1}
67
+ points="{0}"
68
+ opacity=".8"
69
+ />""".format(
70
+ pnt_format,
71
+ 2.*scale_factor,
72
+ "#66cc99" if self.is_valid else "#ff3333")
73
+
74
+ @property
75
+ def ctypes(self):
76
+ if not self._ctypes_data:
77
+ self._ctypes_data = self.coords.ctypes
78
+ return self._ctypes_data
79
+
80
+ def array_interface(self):
81
+ """Provide the Numpy array protocol."""
82
+ return self.coords.array_interface()
83
+
84
+ __array_interface__ = property(array_interface)
85
+
86
+ # Coordinate access
87
+ def _set_coords(self, coordinates):
88
+ self.empty()
89
+ self._geom, self._ndim = geos_linestring_from_py(coordinates)
90
+
91
+ coords = property(BaseGeometry._get_coords, _set_coords)
92
+
93
+ @property
94
+ def xy(self):
95
+ """Separate arrays of X and Y coordinate values
96
+
97
+ Example:
98
+
99
+ >>> x, y = LineString(((0, 0), (1, 1))).xy
100
+ >>> list(x)
101
+ [0.0, 1.0]
102
+ >>> list(y)
103
+ [0.0, 1.0]
104
+ """
105
+ return self.coords.xy
106
+
107
+ def parallel_offset(
108
+ self, distance, side,
109
+ resolution=16, join_style=JOIN_STYLE.round, mitre_limit=5.0):
110
+
111
+ """Returns a LineString or MultiLineString geometry at a distance from
112
+ the object on its right or its left side.
113
+
114
+ Distance must be a positive float value. The side parameter may be
115
+ 'left' or 'right'. The resolution of the buffer around each vertex of
116
+ the object increases by increasing the resolution keyword parameter or
117
+ third positional parameter.
118
+
119
+ The join style is for outside corners between line segments. Accepted
120
+ values are JOIN_STYLE.round (1), JOIN_STYLE.mitre (2), and
121
+ JOIN_STYLE.bevel (3).
122
+
123
+ The mitre ratio limit is used for very sharp corners. It is the ratio
124
+ of the distance from the corner to the end of the mitred offset corner.
125
+ When two line segments meet at a sharp angle, a miter join will extend
126
+ far beyond the original geometry. To prevent unreasonable geometry, the
127
+ mitre limit allows controlling the maximum length of the join corner.
128
+ Corners with a ratio which exceed the limit will be beveled."""
129
+ if mitre_limit == 0.0:
130
+ raise ValueError(
131
+ 'Cannot compute offset from zero-length line segment')
132
+ try:
133
+ return geom_factory(self.impl['parallel_offset'](
134
+ self, distance, resolution, join_style, mitre_limit,
135
+ bool(side == 'left')))
136
+ except OSError:
137
+ raise TopologicalError()
138
+
139
+
140
+ class LineStringAdapter(CachingGeometryProxy, LineString):
141
+
142
+ def __init__(self, context):
143
+ self.context = context
144
+ self.factory = geos_linestring_from_py
145
+
146
+ @property
147
+ def _ndim(self):
148
+ try:
149
+ # From array protocol
150
+ array = self.context.__array_interface__
151
+ n = array['shape'][1]
152
+ assert n == 2 or n == 3
153
+ return n
154
+ except AttributeError:
155
+ # Fall back on list
156
+ return len(self.context[0])
157
+
158
+ @property
159
+ def __array_interface__(self):
160
+ """Provide the Numpy array protocol."""
161
+ try:
162
+ return self.context.__array_interface__
163
+ except AttributeError:
164
+ return self.array_interface()
165
+
166
+ _get_coords = BaseGeometry._get_coords
167
+
168
+ def _set_coords(self, ob):
169
+ raise NotImplementedError(
170
+ "Adapters can not modify their coordinate sources")
171
+
172
+ coords = property(_get_coords)
173
+
174
+
175
+ def asLineString(context):
176
+ """Adapt an object the LineString interface"""
177
+ return LineStringAdapter(context)
178
+
179
+
180
+ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
181
+ # If numpy is present, we use numpy.require to ensure that we have a
182
+ # C-continguous array that owns its data. View data will be copied.
183
+ ob = required(ob)
184
+ try:
185
+ # From array protocol
186
+ array = ob.__array_interface__
187
+ assert len(array['shape']) == 2
188
+ m = array['shape'][0]
189
+ if m < 2:
190
+ raise ValueError(
191
+ "LineStrings must have at least 2 coordinate tuples")
192
+ try:
193
+ n = array['shape'][1]
194
+ except IndexError:
195
+ raise ValueError(
196
+ "Input %s is the wrong shape for a LineString" % str(ob))
197
+ assert n == 2 or n == 3
198
+
199
+ # Make pointer to the coordinate array
200
+ if isinstance(array['data'], tuple):
201
+ # numpy tuple (addr, read-only)
202
+ cp = cast(array['data'][0], POINTER(c_double))
203
+ else:
204
+ cp = array['data']
205
+
206
+ # Create a coordinate sequence
207
+ if update_geom is not None:
208
+ cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
209
+ if n != update_ndim:
210
+ raise ValueError(
211
+ "Wrong coordinate dimensions; this geometry has "
212
+ "dimensions: %d" % update_ndim)
213
+ else:
214
+ cs = lgeos.GEOSCoordSeq_create(m, n)
215
+
216
+ # add to coordinate sequence
217
+ for i in range(m):
218
+ dx = c_double(cp[n*i])
219
+ dy = c_double(cp[n*i+1])
220
+ dz = None
221
+ if n == 3:
222
+ try:
223
+ dz = c_double(cp[n*i+2])
224
+ except IndexError:
225
+ raise ValueError("Inconsistent coordinate dimensionality")
226
+
227
+ # Because of a bug in the GEOS C API,
228
+ # always set X before Y
229
+ lgeos.GEOSCoordSeq_setX(cs, i, dx)
230
+ lgeos.GEOSCoordSeq_setY(cs, i, dy)
231
+ if n == 3:
232
+ lgeos.GEOSCoordSeq_setZ(cs, i, dz)
233
+
234
+ except AttributeError:
235
+ # Fall back on list
236
+ try:
237
+ m = len(ob)
238
+ except TypeError: # Iterators, e.g. Python 3 zip
239
+ ob = list(ob)
240
+ m = len(ob)
241
+
242
+ if m < 2:
243
+ raise ValueError(
244
+ "LineStrings must have at least 2 coordinate tuples")
245
+
246
+ def _coords(o):
247
+ if isinstance(o, Point):
248
+ return o.coords[0]
249
+ else:
250
+ return o
251
+
252
+ try:
253
+ n = len(_coords(ob[0]))
254
+ except TypeError:
255
+ raise ValueError(
256
+ "Input %s is the wrong shape for a LineString" % str(ob))
257
+ assert n == 2 or n == 3
258
+
259
+ # Create a coordinate sequence
260
+ if update_geom is not None:
261
+ cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
262
+ if n != update_ndim:
263
+ raise ValueError(
264
+ "Wrong coordinate dimensions; this geometry has "
265
+ "dimensions: %d" % update_ndim)
266
+ else:
267
+ cs = lgeos.GEOSCoordSeq_create(m, n)
268
+
269
+ # add to coordinate sequence
270
+ for i in range(m):
271
+ coords = _coords(ob[i])
272
+ # Because of a bug in the GEOS C API,
273
+ # always set X before Y
274
+ lgeos.GEOSCoordSeq_setX(cs, i, coords[0])
275
+ lgeos.GEOSCoordSeq_setY(cs, i, coords[1])
276
+ if n == 3:
277
+ try:
278
+ lgeos.GEOSCoordSeq_setZ(cs, i, coords[2])
279
+ except IndexError:
280
+ raise ValueError("Inconsistent coordinate dimensionality")
281
+
282
+ if update_geom is not None:
283
+ return None
284
+ else:
285
+ return lgeos.GEOSGeom_createLineString(cs), n
286
+
287
+
288
+ def update_linestring_from_py(geom, ob):
289
+ geos_linestring_from_py(ob, geom._geom, geom._ndim)
290
+
291
+
292
+ # Test runner
293
+ def _test():
294
+ import doctest
295
+ doctest.testmod()
296
+
297
+ if __name__ == "__main__":
298
+ _test()
@@ -0,0 +1,156 @@
1
+ """Collections of linestrings and related utilities
2
+ """
3
+
4
+ import sys
5
+
6
+ if sys.version_info[0] < 3:
7
+ range = xrange
8
+
9
+ from ctypes import c_double, c_void_p, cast, POINTER
10
+
11
+ from shapely.geos import lgeos
12
+ from shapely.geometry.base import BaseMultipartGeometry
13
+ from shapely.geometry.linestring import LineString, geos_linestring_from_py
14
+ from shapely.geometry.proxy import CachingGeometryProxy
15
+
16
+ __all__ = ['MultiLineString', 'asMultiLineString']
17
+
18
+
19
+ class MultiLineString(BaseMultipartGeometry):
20
+ """
21
+ A collection of one or more line strings
22
+
23
+ A MultiLineString has non-zero length and zero area.
24
+
25
+ Attributes
26
+ ----------
27
+ geoms : sequence
28
+ A sequence of LineStrings
29
+ """
30
+
31
+ def __init__(self, lines=None):
32
+ """
33
+ Parameters
34
+ ----------
35
+ lines : sequence
36
+ A sequence of line-like coordinate sequences or objects that
37
+ provide the numpy array interface, including instances of
38
+ LineString.
39
+
40
+ Example
41
+ -------
42
+ Construct a collection containing one line string.
43
+
44
+ >>> lines = MultiLineString( [[[0.0, 0.0], [1.0, 2.0]]] )
45
+ """
46
+ super(MultiLineString, self).__init__()
47
+
48
+ if not lines:
49
+ # allow creation of empty multilinestrings, to support unpickling
50
+ pass
51
+ else:
52
+ self._geom, self._ndim = geos_multilinestring_from_py(lines)
53
+
54
+ def shape_factory(self, *args):
55
+ return LineString(*args)
56
+
57
+ @property
58
+ def __geo_interface__(self):
59
+ return {
60
+ 'type': 'MultiLineString',
61
+ 'coordinates': tuple(tuple(c for c in g.coords) for g in self.geoms)
62
+ }
63
+
64
+ def svg(self, scale_factor=1.):
65
+ """
66
+ SVG representation of the geometry. Scale factor is multiplied by
67
+ the size of the SVG symbol so it can be scaled consistently for a
68
+ consistent appearance based on the canvas size.
69
+ """
70
+ parts = []
71
+ for part in self.geoms:
72
+ pnt_format = " ".join(["{0},{1}".format(*c) for c in part.coords])
73
+ parts.append("""<polyline
74
+ fill="none"
75
+ stroke="{2}"
76
+ stroke-width={1}
77
+ points="{0}"
78
+ opacity=".8"
79
+ />""".format(
80
+ pnt_format,
81
+ 2.*scale_factor,
82
+ "#66cc99" if self.is_valid else "#ff3333"))
83
+ return "\n".join(parts)
84
+
85
+
86
+ class MultiLineStringAdapter(CachingGeometryProxy, MultiLineString):
87
+
88
+ context = None
89
+ _other_owned = False
90
+
91
+ def __init__(self, context):
92
+ self.context = context
93
+ self.factory = geos_multilinestring_from_py
94
+
95
+ @property
96
+ def _ndim(self):
97
+ try:
98
+ # From array protocol
99
+ array = self.context[0].__array_interface__
100
+ n = array['shape'][1]
101
+ assert n == 2 or n == 3
102
+ return n
103
+ except AttributeError:
104
+ # Fall back on list
105
+ return len(self.context[0][0])
106
+
107
+
108
+ def asMultiLineString(context):
109
+ """Adapts a sequence of objects to the MultiLineString interface"""
110
+ return MultiLineStringAdapter(context)
111
+
112
+
113
+ def geos_multilinestring_from_py(ob):
114
+ # ob must be either a sequence or array of sequences or arrays
115
+ try:
116
+ # From array protocol
117
+ array = ob.__array_interface__
118
+ assert len(array['shape']) == 1
119
+ L = array['shape'][0]
120
+ assert L >= 1
121
+
122
+ # Array of pointers to sub-geometries
123
+ subs = (c_void_p * L)()
124
+
125
+ for l in range(L):
126
+ geom, ndims = geos_linestring_from_py(array['data'][l])
127
+ subs[i] = cast(geom, c_void_p)
128
+ N = lgeos.GEOSGeom_getDimensions(subs[0])
129
+ except (NotImplementedError, AttributeError):
130
+ obs = getattr(ob, 'geoms', ob)
131
+ L = len(obs)
132
+ exemplar = obs[0]
133
+ try:
134
+ N = len(exemplar[0])
135
+ except TypeError:
136
+ N = exemplar._ndim
137
+ assert L >= 1
138
+ assert N == 2 or N == 3
139
+
140
+ # Array of pointers to point geometries
141
+ subs = (c_void_p * L)()
142
+
143
+ # add to coordinate sequence
144
+ for l in range(L):
145
+ geom, ndims = geos_linestring_from_py(obs[l])
146
+ subs[l] = cast(geom, c_void_p)
147
+
148
+ return (lgeos.GEOSGeom_createCollection(5, subs, L), N)
149
+
150
+ # Test runner
151
+ def _test():
152
+ import doctest
153
+ doctest.testmod()
154
+
155
+ if __name__ == "__main__":
156
+ _test()