This file is indexed.

/usr/lib/python3/dist-packages/shapely/ops.py is in python3-shapely 1.6.4-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
"""Support for various GEOS geometry operations
"""

import sys

if sys.version_info[0] < 3:
    from itertools import izip
else:
    izip = zip

from ctypes import byref, c_void_p, c_double

from shapely.geos import lgeos
from shapely.geometry.base import geom_factory, BaseGeometry
from shapely.geometry import asShape, asLineString, asMultiLineString, Point, MultiPoint,\
                             LineString, MultiLineString, Polygon, GeometryCollection

__all__ = ['cascaded_union', 'linemerge', 'operator', 'polygonize',
           'polygonize_full', 'transform', 'unary_union', 'triangulate', 'split']


class CollectionOperator(object):

    def shapeup(self, ob):
        if isinstance(ob, BaseGeometry):
            return ob
        else:
            try:
                return asShape(ob)
            except ValueError:
                return asLineString(ob)

    def polygonize(self, lines):
        """Creates polygons from a source of lines

        The source may be a MultiLineString, a sequence of LineString objects,
        or a sequence of objects than can be adapted to LineStrings.
        """
        source = getattr(lines, 'geoms', None) or lines
        try:
            source = iter(source)
        except TypeError:
            source = [source]
        finally:
            obs = [self.shapeup(l) for l in source]
        geom_array_type = c_void_p * len(obs)
        geom_array = geom_array_type()
        for i, line in enumerate(obs):
            geom_array[i] = line._geom
        product = lgeos.GEOSPolygonize(byref(geom_array), len(obs))
        collection = geom_factory(product)
        for g in collection.geoms:
            clone = lgeos.GEOSGeom_clone(g._geom)
            g = geom_factory(clone)
            g._other_owned = False
            yield g

    def polygonize_full(self, lines):
        """Creates polygons from a source of lines, returning the polygons
        and leftover geometries.

        The source may be a MultiLineString, a sequence of LineString objects,
        or a sequence of objects than can be adapted to LineStrings.

        Returns a tuple of objects: (polygons, dangles, cut edges, invalid ring
        lines). Each are a geometry collection.

        Dangles are edges which have one or both ends which are not incident on
        another edge endpoint. Cut edges are connected at both ends but do not
        form part of polygon. Invalid ring lines form rings which are invalid
        (bowties, etc).
        """
        source = getattr(lines, 'geoms', None) or lines
        try:
            source = iter(source)
        except TypeError:
            source = [source]
        finally:
            obs = [self.shapeup(l) for l in source]
        L = len(obs)
        subs = (c_void_p * L)()
        for i, g in enumerate(obs):
            subs[i] = g._geom
        collection = lgeos.GEOSGeom_createCollection(5, subs, L)
        dangles = c_void_p()
        cuts = c_void_p()
        invalids = c_void_p()
        product = lgeos.GEOSPolygonize_full(
            collection, byref(dangles), byref(cuts), byref(invalids))
        return (
            geom_factory(product),
            geom_factory(dangles),
            geom_factory(cuts),
            geom_factory(invalids)
            )

    def linemerge(self, lines):
        """Merges all connected lines from a source

        The source may be a MultiLineString, a sequence of LineString objects,
        or a sequence of objects than can be adapted to LineStrings.  Returns a
        LineString or MultiLineString when lines are not contiguous.
        """
        source = None
        if hasattr(lines, 'type') and lines.type == 'MultiLineString':
            source = lines
        elif hasattr(lines, '__iter__'):
            try:
                source = asMultiLineString([ls.coords for ls in lines])
            except AttributeError:
                source = asMultiLineString(lines)
        if source is None:
            raise ValueError("Cannot linemerge %s" % lines)
        result = lgeos.GEOSLineMerge(source._geom)
        return geom_factory(result)

    def cascaded_union(self, geoms):
        """Returns the union of a sequence of geometries

        This is the most efficient method of dissolving many polygons.
        """
        try:
            L = len(geoms)
        except TypeError:
            geoms = [geoms]
            L = 1
        subs = (c_void_p * L)()
        for i, g in enumerate(geoms):
            subs[i] = g._geom
        collection = lgeos.GEOSGeom_createCollection(6, subs, L)
        return geom_factory(lgeos.methods['cascaded_union'](collection))

    def unary_union(self, geoms):
        """Returns the union of a sequence of geometries

        This method replaces :meth:`cascaded_union` as the
        prefered method for dissolving many polygons.

        """
        try:
            L = len(geoms)
        except TypeError:
            geoms = [geoms]
            L = 1
        subs = (c_void_p * L)()
        for i, g in enumerate(geoms):
            subs[i] = g._geom
        collection = lgeos.GEOSGeom_createCollection(6, subs, L)
        return geom_factory(lgeos.methods['unary_union'](collection))

operator = CollectionOperator()
polygonize = operator.polygonize
polygonize_full = operator.polygonize_full
linemerge = operator.linemerge
cascaded_union = operator.cascaded_union
unary_union = operator.unary_union


def triangulate(geom, tolerance=0.0, edges=False):
    """Creates the Delaunay triangulation and returns a list of geometries

    The source may be any geometry type. All vertices of the geometry will be
    used as the points of the triangulation.

    From the GEOS documentation:
    tolerance is the snapping tolerance used to improve the robustness of
    the triangulation computation. A tolerance of 0.0 specifies that no
    snapping will take place.

    If edges is False, a list of Polygons (triangles) will be returned.
    Otherwise the list of LineString edges is returned.

    """
    func = lgeos.methods['delaunay_triangulation']
    gc = geom_factory(func(geom._geom, tolerance, int(edges)))
    return [g for g in gc.geoms]

class ValidateOp(object):
    def __call__(self, this):
        return lgeos.GEOSisValidReason(this._geom)

validate = ValidateOp()


def transform(func, geom):
    """Applies `func` to all coordinates of `geom` and returns a new
    geometry of the same type from the transformed coordinates.

    `func` maps x, y, and optionally z to output xp, yp, zp. The input
    parameters may iterable types like lists or arrays or single values.
    The output shall be of the same type. Scalars in, scalars out.
    Lists in, lists out.

    For example, here is an identity function applicable to both types
    of input.

      def id_func(x, y, z=None):
          return tuple(filter(None, [x, y, z]))

      g2 = transform(id_func, g1)

    A partially applied transform function from pyproj satisfies the
    requirements for `func`.

      from functools import partial
      import pyproj

      project = partial(
          pyproj.transform,
          pyproj.Proj(init='epsg:4326'),
          pyproj.Proj(init='epsg:26913'))

      g2 = transform(project, g1)

    Lambda expressions such as the one in

      g2 = transform(lambda x, y, z=None: (x+1.0, y+1.0), g1)

    also satisfy the requirements for `func`.
    """
    if geom.is_empty:
        return geom
    if geom.type in ('Point', 'LineString', 'LinearRing', 'Polygon'):

        # First we try to apply func to x, y, z sequences. When func is
        # optimized for sequences, this is the fastest, though zipping
        # the results up to go back into the geometry constructors adds
        # extra cost.
        try:
            if geom.type in ('Point', 'LineString', 'LinearRing'):
                return type(geom)(zip(*func(*izip(*geom.coords))))
            elif geom.type == 'Polygon':
                shell = type(geom.exterior)(
                    zip(*func(*izip(*geom.exterior.coords))))
                holes = list(type(ring)(zip(*func(*izip(*ring.coords))))
                             for ring in geom.interiors)
                return type(geom)(shell, holes)

        # A func that assumes x, y, z are single values will likely raise a
        # TypeError, in which case we'll try again.
        except TypeError:
            if geom.type in ('Point', 'LineString', 'LinearRing'):
                return type(geom)([func(*c) for c in geom.coords])
            elif geom.type == 'Polygon':
                shell = type(geom.exterior)(
                    [func(*c) for c in geom.exterior.coords])
                holes = list(type(ring)([func(*c) for c in ring.coords])
                             for ring in geom.interiors)
                return type(geom)(shell, holes)

    elif geom.type.startswith('Multi') or geom.type == 'GeometryCollection':
        return type(geom)([transform(func, part) for part in geom.geoms])
    else:
        raise ValueError('Type %r not recognized' % geom.type)


def nearest_points(g1, g2):
    """Returns the calculated nearest points in the input geometries

    The points are returned in the same order as the input geometries.
    """
    seq = lgeos.methods['nearest_points'](g1._geom, g2._geom)
    if seq is None:
        if g1.is_empty:
            raise ValueError('The first input geometry is empty')
        else:
            raise ValueError('The second input geometry is empty')
    x1 = c_double()
    y1 = c_double()
    x2 = c_double()
    y2 = c_double()
    lgeos.GEOSCoordSeq_getX(seq, 0, byref(x1))
    lgeos.GEOSCoordSeq_getY(seq, 0, byref(y1))
    lgeos.GEOSCoordSeq_getX(seq, 1, byref(x2))
    lgeos.GEOSCoordSeq_getY(seq, 1, byref(y2))
    p1 = Point(x1.value, y1.value)
    p2 = Point(x2.value, y2.value)
    return (p1, p2)

def snap(g1, g2, tolerance):
    """Snap one geometry to another with a given tolerance

    Vertices of the first geometry are snapped to vertices of the second
    geometry. The resulting snapped geometry is returned. The input geometries
    are not modified.

    Parameters
    ----------
    g1 : geometry
        The first geometry
    g2 : geometry
        The second geometry
    tolerence : float
        The snapping tolerance

    Example
    -------
    >>> square = Polygon([(1,1), (2, 1), (2, 2), (1, 2), (1, 1)])
    >>> line = LineString([(0,0), (0.8, 0.8), (1.8, 0.95), (2.6, 0.5)])
    >>> result = snap(line, square, 0.5)
    >>> result.wkt
    'LINESTRING (0 0, 1 1, 2 1, 2.6 0.5)'
    """
    return(geom_factory(lgeos.methods['snap'](g1._geom, g2._geom, tolerance)))

def shared_paths(g1, g2):
    """Find paths shared between the two given lineal geometries

    Returns a GeometryCollection with two elements:
     - First element is a MultiLineString containing shared paths with the
       same direction for both inputs.
     - Second element is a MultiLineString containing shared paths with the
       opposite direction for the two inputs.

    Parameters
    ----------
    g1 : geometry
        The first geometry
    g2 : geometry
        The second geometry
    """
    if not isinstance(g1, LineString):
        raise TypeError("First geometry must be a LineString")
    if not isinstance(g2, LineString):
        raise TypeError("Second geometry must be a LineString")
    return(geom_factory(lgeos.methods['shared_paths'](g1._geom, g2._geom)))


class SplitOp(object):

    @staticmethod
    def _split_polygon_with_line(poly, splitter):
        """Split a Polygon with a LineString"""

        assert(isinstance(poly, Polygon))
        assert(isinstance(splitter, LineString))

        union = poly.boundary.union(splitter)

        # some polygonized geometries may be holes, we do not want them
        # that's why we test if the original polygon (poly) contains
        # an inner point of polygonized geometry (pg)
        return [pg for pg in polygonize(union) if poly.contains(pg.representative_point())]

    @staticmethod
    def _split_line_with_line(line, splitter):
        """Split a LineString with another (Multi)LineString or (Multi)Polygon"""

        # if splitter is a polygon, pick it's boundary
        if splitter.type in ('Polygon', 'MultiPolygon'):
            splitter = splitter.boundary

        assert(isinstance(line, LineString))
        assert(isinstance(splitter, LineString) or isinstance(splitter, MultiLineString))

        if splitter.crosses(line):
            # The lines cross --> return multilinestring from the split
            return line.difference(splitter)
        elif splitter.relate_pattern(line, '1********'):
            # The lines overlap at some segment (linear intersection of interiors)
            raise ValueError('Input geometry segment overlaps with the splitter.')
        else:
            # The lines do not cross --> return collection with identity line
            return [line]

    @staticmethod
    def _split_line_with_point(line, splitter):
        """Split a LineString with a Point"""

        assert(isinstance(line, LineString))
        assert(isinstance(splitter, Point))

        # check if point is in the interior of the line
        if not line.relate_pattern(splitter, '0********'):
            # point not on line interior --> return collection with single identity line
            # (REASONING: Returning a list with the input line reference and creating a
            # GeometryCollection at the general split function prevents unnecessary copying
            # of linestrings in multipoint splitting function)
            return [line]
        elif line.coords[0] == splitter.coords[0]:
            # if line is a closed ring the previous test doesn't behave as desired
            return [line]

        # point is on line, get the distance from the first point on line
        distance_on_line = line.project(splitter)
        coords = list(line.coords)
        # split the line at the point and create two new lines
        # TODO: can optimize this by accumulating the computed point-to-point distances
        for i, p in enumerate(coords):
            pd = line.project(Point(p))
            if pd == distance_on_line:
                return [
                    LineString(coords[:i+1]),
                    LineString(coords[i:])
                ]
            elif distance_on_line < pd:
                # we must interpolate here because the line might use 3D points
                cp = line.interpolate(distance_on_line)
                ls1_coords = coords[:i]
                ls1_coords.append(cp.coords[0])
                ls2_coords = [cp.coords[0]]
                ls2_coords.extend(coords[i:])
                return [LineString(ls1_coords), LineString(ls2_coords)]

    @staticmethod
    def _split_line_with_multipoint(line, splitter):
        """Split a LineString with a MultiPoint"""

        assert(isinstance(line, LineString))
        assert(isinstance(splitter, MultiPoint))

        chunks = [line]
        for pt in splitter.geoms:
            new_chunks = []
            for chunk in filter(lambda x: not x.is_empty, chunks):
                # add the newly split 2 lines or the same line if not split
                new_chunks.extend(SplitOp._split_line_with_point(chunk, pt))
            chunks = new_chunks

        return chunks

    @staticmethod
    def split(geom, splitter):
        """
        Splits a geometry by another geometry and returns a collection of geometries. This function is the theoretical
        opposite of the union of the split geometry parts. If the splitter does not split the geometry, a collection
        with a single geometry equal to the input geometry is returned.
        The function supports:
          - Splitting a (Multi)LineString by a (Multi)Point or (Multi)LineString or (Multi)Polygon
          - Splitting a (Multi)Polygon by a LineString

        It may be convenient to snap the splitter with low tolerance to the geometry. For example in the case
        of splitting a line by a point, the point must be exactly on the line, for the line to be correctly split.
        When splitting a line by a polygon, the boundary of the polygon is used for the operation.
        When splitting a line by another line, a ValueError is raised if the two overlap at some segment.

        Parameters
        ----------
        geom : geometry
            The geometry to be split
        splitter : geometry
            The geometry that will split the input geom

        Example
        -------
        >>> pt = Point((1, 1))
        >>> line = LineString([(0,0), (2,2)])
        >>> result = split(line, pt)
        >>> result.wkt
        'GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), LINESTRING (1 1, 2 2))'
        """

        if geom.type in ('MultiLineString', 'MultiPolygon'):
             return GeometryCollection([i for part in geom.geoms for i in SplitOp.split(part, splitter).geoms])

        elif geom.type == 'LineString':
            if splitter.type in ('LineString', 'MultiLineString', 'Polygon', 'MultiPolygon'):
                split_func = SplitOp._split_line_with_line
            elif splitter.type in ('Point'):
                split_func = SplitOp._split_line_with_point
            elif splitter.type in ('MultiPoint'):
                split_func =  SplitOp._split_line_with_multipoint
            else:
                raise ValueError("Splitting a LineString with a %s is not supported" % splitter.type)

        elif geom.type == 'Polygon':
            if splitter.type == 'LineString':
                split_func = SplitOp._split_polygon_with_line
            else:
                raise ValueError("Splitting a Polygon with a %s is not supported" % splitter.type)

        else:
            raise ValueError("Splitting %s geometry is not supported" % geom.type)

        return GeometryCollection(split_func(geom, splitter))

split = SplitOp.split