Search code examples
pythondjangodjango-viewspostgisgeodjango

MakeValid not working for a single model object


I'm trying to use MakeValid to fix (validate) my geometry fields.
I can make it work by getting and updating in single line:

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

MyModel.objects.filter(id=<id>).update(polygon=MakeValid('polygon'))

but for some cases, I have to update polygon of a single model object already instantiated in a function (meaning I have already done .filter/.get) which gives me the following error:

// np is an object of MyModel which has a field 'polygon' which is `MultiPolygon` django model field
np.polygon = MakeValid(np.polygon)
// np.save()
TypeError: Cannot set MyModel SpatialProxy (MULTIPOLYGON) with value of type: <class 'django.contrib.gis.db.models.functions.MakeValid'>

Here, MakeValid(np.polygon) doesn't return a MultiPolygon object. Instead, it returns a django.contrib.gis.db.models.functions.MakeValid wrapper.

Can I get a Geometry object from MakeValid?


Solution

  • As stated in the linked post MakeValid is a database function, which means that it can be executed only during querying the database.

    It is 1-to-1 similar to the PostGIS usage of ST_MakeValid which cannot be executed outside of a table query (cannot exist autonomously).

    When you create the np object, and then you try to do:

    np.polygon = MakeValid(np.polygon)
    

    You are essentially trying to apply a database function to an instance of the 'MyModel' class which isn't supposed to work! (as it does not)


    What you can do:

    1. You can create a query to update a specific table row:

      np = MyModel.objects.filter(id=np.id).update(polygon=MakeValid('polygon'))
      

      Note: The object with id=np.id's polygon, will be updated in the database permanently with that method.

    2. You can utilize the GEOSGeometry.buffer():

      Using polygon.buffer(0) can tidy up most of the polygon irregularities (it can even solve some types of "bowtie"/self-intersecting polygons)

      np.polygon.valid          # False
      np.polygon.buffer(0)      # Make valid with buffer(0)
      np.polygon.valid          # True
      
    3. Finally, you can use Shapely and create a shapely polygon for your calculations which you can make valid with the same method as above (both Shapely's buffer and GeoDjango's buffer use GEOS library):

      from shapely.geometry import Polygon
      
      # Initialize the polygon object with one of the following ways:
      np_polygon = Polygon([np.polygon.coords])
      # or
      np_polygon = Polygon(np.polygon.wkt)
      
      np_polygon.is_valid                 # False
      np_polygon = np_polygon.buffer(0)   # Make valid with buffer(0)
      np_polygon.is_valid                 # True