5

Is there a generic way to flip/invert/reverse the order of the coordinates of a shapely geometry?

Here are a couple of examples of what I mean.

Example 1

Input: LINESTRING (0 0, 1 1, 3 1, -3 3)

Output: LINESTRING (-3 3, 3 1, 1 1, 0 0)

Example 2

Input: MULTILINESTRING ((120 105, 140 98),(160 103, 140 98),(100 100, 120 105))

Output: MULTILINESTRING ((120 105, 100 100), (140 98, 160 103), (140 98, 120 105))

My implementation (small reproducible example)

I was able to put together a small implementation for LineStrings and MultiLineStrings. Note that it is not efficient at all and it doesn't account for the vast amount of shape types (polygons, multipolygons, etc).

import shapely

Shape inverter

def invert_coords(input_geom): if input_geom.type.lower() == 'linestring': coords = [tuple(coord) for coord in list(input_geom.coords)][::-1] out_geom = shapely.geometry.LineString(coords) elif input_geom.type.lower() == 'multilinestring': coords = [list(this_geom.coords)[::-1] for this_geom in input_geom.geoms][::-1] out_geom = shapely.geometry.MultiLineString(coords) return out_geom

Write the WKTs

ls_wkt = 'LINESTRING (0 0, 1 1, 3 1, -3 3)' mls_wkt = 'MULTILINESTRING ((120 105, 140 98),(160 103, 140 98),(100 100, 120 105))'

Generate the shapely geometries

ls = shapely.wkt.loads(ls_wkt) mls = shapely.wkt.loads(mls_wkt)

Inverting shapes

ls_inv = invert_coords(ls) mls_inv = invert_coords(mls)

print(ls_inv.wkt)

LINESTRING (-3 3, 3 1, 1 1, 0 0)

print(mls_inv.wkt)

MULTILINESTRING ((120 105, 100 100), (140 98, 160 103), (140 98, 120 105))

Back to the main question

Is there a generic yet straightforward way of inverting the order of the coordinates for any shapely geometry?

Felipe D.
  • 2,509
  • 2
  • 14
  • 32

4 Answers4

6

Maybe explore using shapely.ops.transform():

Applies func to all coordinates of geom and returns a new geometry of the same type from the transformed coordinates.

It does the work of parsing out the coordinate sequences from the given geometry type, and lets you operate on each axis as an iterable.

import shapely.wkt
import shapely.ops

def reverse_geom(geom): def _reverse(x, y, z=None): if z: return x[::-1], y[::-1], z[::-1] return x[::-1], y[::-1]

return shapely.ops.transform(_reverse, geom)


ls_wkt = 'LINESTRING (0 0, 1 1, 3 1, -3 3)' lsz_wkt = 'LINESTRING (0 0 0, 1 1 1, 3 1 1, -3 3 2)' mls_wkt = 'MULTILINESTRING ((120 105, 140 98),(160 103, 140 98),(100 100, 120 105))'

ls = shapely.wkt.loads(ls_wkt) lsz = shapely.wkt.loads(lsz_wkt) mls = shapely.wkt.loads(mls_wkt)

print(ls) print(reverse_geom(ls))

print(lsz) print(reverse_geom(lsz))

print(mls) print(reverse_geom(mls))

There's also shapely.geometry.polygon.orient() which will let you obtain a specific polygon ring ordering

mikewatt
  • 5,083
  • 9
  • 21
4

This is now very easy with the .reverse method:

import shapely

line = shapely.wkt.loads("LINESTRING (0 0, 1 1, 3 1, -3 3)") multiline = shapely.wkt.loads("MULTILINESTRING ((120 105, 140 98),(160 103, 140 98),(100 100, 120 105))")

print(line.reverse().wkt) #'LINESTRING (-3 3, 3 1, 1 1, 0 0)'

print(multiline.reverse().wkt) #MULTILINESTRING ((140 98, 120 105), (140 98, 160 103), (120 105, 100 100))

BERA
  • 72,339
  • 13
  • 72
  • 161
2

There is an open issue about reversing geometries https://github.com/geopandas/geopandas/issues/1682. There does not seem to be a native Shapely implementation yet but for example pygeos has such function https://pygeos.readthedocs.io/en/latest/constructive.html#pygeos.constructive.reverse

user30184
  • 65,331
  • 4
  • 65
  • 118
  • Awesome, thanks for the tip! Shifting back and forth between shapely and pygeos geometries can be a little cumbersome, but it gets the job done! shapely.wkt.loads(pygeos.to_wkt(pygeos.reverse(pygeos.Geometry(shapely_geom.wkt)))) Thanks again for the tip! – Felipe D. Nov 08 '21 at 19:45
  • Since Shapely and PyGEOS are merging into Shapely 2.0, shifting back and forth will hopefully not be needed for much longer. – Erik May 19 '22 at 09:36
2

The substring operation in Shapely can be used for this.

From the substring docs:

If the start distance is actually past the end distance, then the reversed substring is returned such that the start distance is at the first coordinate.

Thus we can do:

from shapely.ops import substring
ls = LineString([(0,0), (1,0), (2,0)])

reversed_ls = substring(ls, 1, 0, normalized=True)

or

reversed_ls = substring(ls, ls.length, 0)

substring only works on LineStrings though, not MultiLineStrings.

If you want a convenience function reverse, you can wrap substring using functools.partial:

from functools import partial
reverse = partial(substring, start_dist=1, end_dist=0, normalized=True)
reversed_ls = reverse(ls)
Erik
  • 261
  • 1
  • 6
  • 1
    Awesome! Thanks for the suggestion!!! You can also avoid using ls.length by instead setting the normalized parameter to True. Here's the full command: substring(ls, 1, 0, normalized=True). – Felipe D. May 19 '22 at 15:13
  • 1
    Thanks, that's nicer! Edited my answer. – Erik May 20 '22 at 09:55