9

I am developing a GeoDjango application where users can upload map files and do some basic mapping operations like querying features inside polygons.

I recognized that users happen to upload "MultiLineString"s instead of "Polygon"s sometimes. This causes the queries expecting closed geometries to fail.

What is the best way to convert a MultiLineString object to a Polygon in Python?

Georgy
  • 6,348
  • 7
  • 46
  • 58
onurmatik
  • 4,365
  • 6
  • 38
  • 64

3 Answers3

9

Hehe, at first I wrote this:

def close_geometry(self, geometry):
   if geometry.empty or geometry[0].empty:
       return geometry # empty

   if(geometry[-1][-1] == geometry[0][0]):
       return geometry  # already closed

   result = None
   for linestring in geom:
      if result is None:
          resultstring = linestring.clone()
      else:
          resultstring.extend(linestring.coords)

   geom = Polygon(resultstring)

   return geom

but then I discovered that there is a nifty little method called convex_hull that does the polygon conversion for you automatically.

>>> s1 = LineString((0, 0), (1, 1), (1, 2), (0, 1))
>>> s1.convex_hull
<Polygon object at ...>
>>> s1.convex_hull.coords
(((0.0, 0.0), (0.0, 1.0), (1.0, 2.0), (1.0, 1.0), (0.0, 0.0)),)

>>> m1=MultiLineString(s1)
>>> m1.convex_hull
<Polygon object at...>
>>> m1.convex_hull.coords
(((0.0, 0.0), (0.0, 1.0), (1.0, 2.0), (1.0, 1.0), (0.0, 0.0)),)
Andriy Drozdyuk
  • 50,346
  • 44
  • 149
  • 256
  • such an obscure name for a method that saves the day. thanks. if the MultiLineString has more than 1 LineString, convex_hull returns 1 Polygon that contains them all. if you want to have each LineString as a separate Polygon, you still have to loop in the MultiLineString and apply convex_hull to each LineString. – onurmatik Jun 04 '10 at 16:00
  • True, you just never mentioned that you wanted a separate polygon for each line. :-) – Andriy Drozdyuk Jun 04 '10 at 19:27
  • 13
    The Convex Hull of the multiline may not be what you need. A Convex Hull defines the minimal polygon that contains the shape, and may not be as accurate as you need as it will not include any points on the shape's boundary that are "inside". i.e. if you have a shape with a chunk taken out of it, you may not see that the shape has a chunk out of its border. – ianmjones Jun 17 '10 at 09:04
  • 2
    You can use shapely.geometry.Polygon to simply convert to line string to a polygon. It will connect the first and last coordinates. Try Polygon([(0, 0), (1, 1), (1, 2), (0, 1)]) or Polygon(s1) to produce POLYGON ((0 0, 1 1, 1 2, 0 1, 0 0)). – Matt Oct 09 '19 at 03:39
1

This small code can save a lot of time, maybe later a shorter form in geopandas will be incorporated.

import geopandas as gpd
from shapely.geometry import Polygon, mapping

def linestring_to_polygon(fili_shps):
    gdf = gpd.read_file(fili_shps) #LINESTRING
    geom = [x for x in gdf.geometry]
    all_coords = mapping(geom[0])['coordinates']
    lats = [x[1] for x in all_coords]
    lons = [x[0] for x in all_coords]
    polyg = Polygon(zip(lons, lats))
    return gpd.GeoDataFrame(index=[0], crs=gdf.crs, geometry=[polyg])
1

Here is a modification of Carlos answer, it is simpler and returns not just one element, but all the rows in the source file

import geopandas as gpd
from shapely.geometry import Polygon, mapping

def linestring_to_polygon(fili_shps):
    gdf = gpd.read_file(fili_shps) #LINESTRING
    gdf['geometry'] = [Polygon(mapping(x)['coordinates']) for x in gdf.geometry]
    return gdf
Nikita Pestrov
  • 5,576
  • 4
  • 28
  • 63