Mat
Mat

Reputation: 86474

GeoDjango inteprets MultiPolygon as LinearRing

I'm importing some OSM data from Trimble into a PostGIS database to process it as part of a Django app. This works fine for points and lines but I'm struggling with the polygons.

The import appears to work fine:

shp2pgsql -d -I aeroway_polygon_polygon.shp aeroway_polygon | psql

Django InspectDB interprets the data in a sensible manner:

./manage.py inspectdb > models.py

models.py contents:

class AerowayPolygon(models.Model):
    gid = models.AutoField(primary_key=True)
    id = models.FloatField(blank=True, null=True)
    osm_id = models.DecimalField(max_digits=65535, decimal_places=65535, blank=True, null=True)
    z_order = models.FloatField(blank=True, null=True)
    aeroway = models.CharField(max_length=80, blank=True, null=True)
    name = models.CharField(max_length=80, blank=True, null=True)
    name_en = models.CharField(db_column='name:en', max_length=80, blank=True, null=True)  # Field renamed to remove unsuitable characters.
    operator = models.CharField(max_length=80, blank=True, null=True)
    ref = models.CharField(max_length=80, blank=True, null=True)
    faa = models.CharField(max_length=80, blank=True, null=True)
    iata = models.CharField(max_length=80, blank=True, null=True)
    icao = models.CharField(max_length=80, blank=True, null=True)
    website = models.CharField(max_length=80, blank=True, null=True)
    contact_we = models.CharField(db_column='contact:we', max_length=80, blank=True, null=True)  # Field renamed to remove unsuitable characters.
    phone = models.CharField(max_length=80, blank=True, null=True)
    contact_ph = models.CharField(db_column='contact:ph', max_length=80, blank=True, null=True)  # Field renamed to remove unsuitable characters.
    ele = models.CharField(max_length=80, blank=True, null=True)
    tower_type = models.CharField(db_column='tower:type', max_length=80, blank=True, null=True)  # Field renamed to remove unsuitable characters.
    geom = models.MultiPolygonField(srid=0, dim=4, blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'aeroway_polygon'

Any attempt to access objects from the database cause GEOS to complain about a LinearRing.

>>> from data.models import AerowayPolygon
>>> AerowayPolygon.objects.all()[0]
GEOS_ERROR: IllegalArgumentException: Points of LinearRing do not form a closed linestring

The error is not wrong, the points don't close the LineString. But I'm confused as I think the type should be a MultiPolygon and should, therefore, work fine. What gives?


I dug a little deeper by manually trying to take geometries from PostGIS.

As a well-known binary hex string, I get the same behaviour:

>>> from django.contrib.gis.geos import GEOSGeometry
>>> wkb ='01060000C00100000001030000C0020000008E0000000064931E4F47DDBF4020B11AB5BC49400000000000000000FFFFFFFFFFFFEFFF006493B23347DDBF442075F9B7BC494 ...     003C9368871FDDBF4020B193B4BC49400000000000000000FFFFFFFFFFFFEFFF'
>>> GEOSGeometry(wkb)
GEOS_ERROR: IllegalArgumentException: Points of LinearRing do not form a closed linestring

However, if I pre-convert to well known text using ST_AsEWKT, all appears well:

>>> wkt = 'MULTIPOLYGON(((-0.45747735963738 51.4742768635629 0 -1.79769313486232e+308,-0.457470821752906 51.474364454451 0 -1.79769313486232e+308, ... ,-0.455049373745112 51.4742607703088 0 -1.79769313486232e+308)))'
>>> GEOSGeometry(wkt)
<MultiPolygon object at 0x7f0948769098>

Upvotes: 3

Views: 1512

Answers (2)

John Moutafis
John Moutafis

Reputation: 23134

Polygons by definition are closed geometries, which means that the first and last Point must be the same exact Point.
For Multipolygons the same principle exists, hence every Polygon of a Multipolygon must be closed and there must not be "free" edges inside the geometry.

If you check your dataset you will find that some of those LineStrings are not closing.

@AntoinePinsard gives a good PostGIS solution.
In GeoDjango version >= 1.10, MakeValid exists as a database function and we can use it on queries:

AerowayPolygon.objects.all().update(geom=MakeValid('geom'))

If your Django version < 1.10, I have a set of answers on how to create a custom database function and use it as in the example above:

from django.contrib.gis.db.models.functions import GeoFunc

class MyMakeValid(GeoFunc):
      function='ST_MakeValid'

AerowayPolygon.objects.all().update(geom=MyMakeValid('geom'))

Upvotes: 1

Antoine Pinsard
Antoine Pinsard

Reputation: 34942

Each polygon of a MultiPolygon should still form a closed linestring. Your data is probably malformed or corrupted.

You can try to fix this by using ST_MakeValid.

UPDATE aeroway_polygon
SET geom = ST_Multi(ST_CollectionExtract(ST_Makevalid(geom), 3))
WHERE ST_IsValid(geom) = false;

Note that I didn't test this query, I found it here on gis.stackexchange.

Upvotes: 2

Related Questions