5

In my model I have a polygon field defined via

polygon = models.PolygonField(srid=4326, geography=True, null=True, blank=True)

When I want to determine the area of the polygon, I call

area_square_degrees = object.polygon.area

But how can I convert the result in square degrees into m2 with GeoDjango? This answer does not work, since area does not have a method sq_m. Is there any built-in conversion?

Community
  • 1
  • 1
neurix
  • 4,126
  • 6
  • 46
  • 71
  • Did you find a django native solution that can also save the area in the database upon the object creation? – Charalamm Jul 01 '21 at 14:59

4 Answers4

7

You need to transform your data to the correct spatial reference system.

area_square_local_units = object.polygon.transform(srid, clone=False).area

In the UK you might use the British National Grid SRID of 27700 which uses meters.

area_square_meters = object.polygon.transform(27700, clone=False).area

You may or may not want to clone the geometry depending on whether or not you need to do anything else with it in its untransformed state.

Docs are here https://docs.djangoproject.com/en/1.8/ref/contrib/gis/geos/

cchristelis
  • 1,985
  • 1
  • 13
  • 17
Alexander
  • 1,245
  • 2
  • 15
  • 22
  • Note that Guillaume's answer below is a better solution when using 4326 & geography type in your model geometry field. – Alexander Mar 25 '21 at 22:44
4

I have struggled a lot with this, since i could'nt find a clean solution. The trick is you have to use the postgis capabilities (and and thus its only working with postgis..):

from django.contrib.gis.db.models.functions import Area
loc_obj = Location.objects.annotate(area_=Area("poly")).get(pk=??)  
# put the primary key of the object

print(loc_obj.area_)  # distance object, result should be in meters, but you can change to the unit you want, e.g  .mi for miles etc..

The models.py:

class Location(models.Model):
    poly = gis_models.PolygonField(srid=4326, geography=True)

It's i think the best way to do it if you have to deal with geographic coordinates instead of projections. It does handle the curve calculation of the earth, and the result is precise even with big distance/area

Guillaume Lebreton
  • 2,586
  • 16
  • 25
  • Is there a way to make the area calculation upon the object creation or at least make the calculation for one row instead of the whole table? – Charalamm Jul 02 '21 at 06:36
  • I guess it something like `Location.objects.filter(pk=XXX) .annotate(area_=Area("poly"))`. Then calculations are made only on the subset of the table. – Guillaume Lebreton Jul 09 '21 at 15:30
1

I needed an application to get the area of poligons around the globe and if I used an invalid country/region projection I got the error OGRException: OGR failure

I ended using an OpenLayers implementation using the 4326 projection (is the default projection) to avoid concerning about every country/region specific projection. Here is my code:

import math

def getCoordsM2(coordinates):
    d2r = 0.017453292519943295  # Degrees to radiant
    area = 0.0
    for coord in range(0, len(coordinates)):
        point_1 = coordinates[coord]
        point_2 = coordinates[(coord + 1) % len(coordinates)]
        area += ((point_2[0] - point_1[0]) * d2r) *\
            (2 + math.sin(point_1[1] * d2r) + math.sin(point_2[1] * d2r))
    area = area * 6378137.0 * 6378137.0 / 2.0
    return math.fabs(area)

def getGeometryM2(geometry):
    area = 0.0
    if geometry.num_coords > 2:
        # Outer ring
        area += getCoordsM2(geometry.coords[0])
        # Inner rings
        for counter, coordinates in enumerate(geometry.coords):
            if counter > 0:
                area -= getCoordsM2(coordinates)
    return area

Simply pass your geometry to getGeometryM2 function and you are done! I use this function in my GeoDjango model as a property. Hope it helps!

castledom04
  • 207
  • 2
  • 13
-3

If Its earths surface area that you are talking about, 1 square degree has 12,365.1613 square km. So multiple your square degree and multiply by 10^6 to convert to meters.

Sri
  • 693
  • 1
  • 9
  • 22