GeoDjango: Determine the area of a polygon - django

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?

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/

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

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!

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.

Related

GeoDjango: How to create a circle anywhere on earth based on point and radius?

I have a similar question to this one. Using geodjango, I want to draw a circle on a map with a certain radius in km. However, the suggested solution
a) does not use km but instead degrees, and
b) becomes an oval further north or south.
Here is what I do:
from django.contrib.gis import geos
lat = 49.17
lng = -123.96
center = geos.Point(lng, lat)
radius = 0.01
circle = center.buffer(radius)
# And I then use folium to show a map on-screen:
map = folium.Map(
location=[lat,lng],
zoom_start=14,
attr="Mapbox"
)
folium.GeoJson(
circle.geojson,
name="geojson",
).add_to(map)
The result is this:
How can I
a) draw a circle that is always 3 km in radius, independent from the position on the globe, and
b) ensure this is a circle and not an oval at all latitudes?
Here is the Code
from django.contrib.gis import geos
import folium
lat = 49.17
lng = -123.96
center = geos.Point(x=lng, y=lat, srid=4326)
center.transform(3857) # Transform Projection to Web Mercator
radius = 3000 # now you can use meters
circle = center.buffer(radius)
circle.transform(4326) # Transform back to WGS84 to create geojson
# And I then use folium to show a map on-screen:
map = folium.Map(
location=[lat,lng],
zoom_start=14,
attr="Mapbox"
)
geojson = folium.GeoJson(
circle.geojson,
name="geojson",
)
geojson.add_to(map)
Explanation
This problem occurs due to Map Projections.
Lat/Long Coordinates are represented by the Map Projection WGS84. The Values are in degrees.
The map you see in folium has another map projection (Web Mercator). It tries to represent the world as a plane, which produces distortions to the north and south. The coordinate values are in meters.
On a globe your created circle would look completely round, but because folium uses another projection it gets distorted.
It is also important to know that every projection is represented by a number (EPSG Code). With this epsg codes, you can transform your coordinates from one projection into another.
Web Mercator -> EPSG 3857
WGS84 -> EPSG 4326
With my Code you now get a round circle in folium for Web Mercator, but be aware that it would look oval and distorted, when looking at it on a globe.
This is just a very easy explanation. You might have a look at Map Projections to better understand the problem.
This guide gives a good overview:
Map Projections
try this
folium.Circle(
radius=3000,
location=[lat,lng],
popup="Whatever name",
color="#3186cc",
fill=True,
fill_color="#3186cc",
).add_to(m)

How to do location based query with simple Django filter?

I've saved user's coordinates in the User model. Post model has latitude, longitude and radius field. Only the users in that vicinity(of Post) will be able to see that post. I don't know how to use filter() here so I used the following approach:
post=Posts.objects.all()
for a in post:
distance= geopy.distance.geodesic((lat1,lng1), (a.latitude, a.longitude)).km
print(distance)
if distance < a.radius:
p.append(a)
else:
continue
Here, lat1 and lng1 are the coordinates of current User. Suggest if there is any better way as this seems very inefficient.
Depending on your requirements, you could use a square instead of a circle. Pre-calculate the x-max, x-min, y-max and y-min boundaries for your square and then do a simple User.filter(lat__gt=lat_min, user.lng__gt=lng_min, user.lat__lt=lat_max ... lookup in the database.
In a past project, I used this:
def get_latlng_bounderies(lat, lng, distance):
"""
Return min/max lat/lng values for a distance around a latlng.
:lat:, :lng: the center of the area.
:distance: in km, the "radius" around the center point.
:returns: Two corner points of a square that countains the circle,
lat_min, lng_min, lat_max, lng_max.
"""
gc = great_circle(kilometers=distance)
p0 = gc.destination((lat, lng), 0)
p90 = gc.destination((lat, lng), 90)
p180 = gc.destination((lat, lng), 180)
p270 = gc.destination((lat, lng), 270)
ret = p180[0], p270[1], p0[0], p90[1]
return ret
Its not a circle, so its not exact around the "corners" of the square, but its much faster, because its a simple float comparision in the database.

Calculating distance between two PointField (s) - Why is my result incorrect?

I am trying to calculate the distance between two locations in miles however the result that I am getting is incorrect.
The reason I think its incorrect is because I put locations (latitude and longitude) on this website and I get the distance in miles as 0.055. Here are the details from my code
PointField A : (-122.1772784, 47.7001663)
PointField B : (-122.1761632, 47.700408)
Distance : 0.001141091551967795
However, according to the website, the distance should be
Distance: 0.055 miles
Here is how I am calculating the distance.
This is my model
class modelEmp(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
location = models.PointField(srid=4326,max_length=40, blank=True, null=True)
objects = GeoManager()
and this is how I calculate the distance
result = modelEmpInstance.location.distance(PointFieldBLocation)
where result = 0.001141091551967795
Any suggestions on what I might be doing wrong here and why my result is different from the website?
Your calculation is not wrong but the result is in the EPSG:4326's units, which are degrees. In order to calculate the distance in the wanted unit, we need to do the following:
Transform the points into an EPSG with meter units.
If you don't care much about the accuracy of the calculations, you can use the EPSG:3857 (but the result will be 0.08104046068988752mi).
If you do care about the accuracy of your calculation though, you need to find an EPSG with meter units that are appropriate for your location. Since your points are located around the Seattle area, the appropriate EPSG is 32148.
Create a Distance object with the distance calculation in meters
Finally, convert it to miles:
from django.contrib.gis.measure import Distance
result = Distance(
m = modelEmpInstance.location.transform(
32148, clone=True
).distance(PointFieldBLocation.transform(32148, clone=True)
)
print(
'Raw calculation: {}\nRounded calculation: {}'
.format(result.mi, round(result.mi, 2)
)
This will print:
Raw calculation: 0.0546237743898667
Rounded calculation: 0.055

Django: How do i store a geo point in database

Needed correct datatype for geo points.
I will get and display it with google map API so format like
42.761819,11.104863
41.508577,-101.953125
Usecase:
user click on map
django save this point with additional data
on next visiting django display this points on map
So, no distances beetween points and etc hacks.
DB: postgres 8
Django: 1.4
Check out GeoDjango and see if it helps you. If your stack is configured to run GeoDjango, you can do this.
Your models will looks like this
from django.contrib.gis.db import models
class LocationPoint(models.Model):
point = models.PointField(srid=4326,dim=3)
accuracy = models.FloatField(default=0.0)
objects = models.GeoManager()
To save the point to the database all you will have to do is
from django.contrib.gis.geos import Point
point = Point(x=12.734534, y=77.2342, z=0, srid=4326)
location = LocationPoint()
location.point = point
location.save()
GeoDjango gives you extended abilities to do geospatial queries which you might be interested in the near future, like finding the distance between points, finding the nearest locations around a point etc.
Here's the link to GeoDjango
From django documentation about DecimalField:
DecimalField.max_digits
The maximum number of digits allowed in the
number. Note that this number must be greater than or equal to
decimal_places.
DecimalField.decimal_places
The number of decimal places to store with
the number.
which is refering to the Python Decimal
To make good choice about accurate data type and precission you should consider:
what is minimum possible value (latitude can be from 0 (down)up to (-)90 degrees) _ _.
what is maximum possible value (longitude can range from 0 (down)up to (-)180 degrees) _ _ _.
what is accuracy (decimal_places), you wish. Pleas notice that it has impact on zoom level on Google Maps.
By the way, for better understanding, it is good to know how the calculation is done (Python code):
def deg_to_dms(deg):
d = int(deg)
md = abs(deg - d) * 60
m = int(md)
sd = (md - m) * 60
return [d, m, sd]
def decimal(deg,min,sec):
if deg < 0:
dec= -1.0 * deg + 1.0 * min/60.0 + 1.0 * sec/3600.0
return -1.0 * dec
else:
dec=1.0 * deg + 1.0 * min/60.0 + 1.0 * sec/3600.0;
return dec
It looks like you're going to be storing a latitude and a longitude. I would go with a DecimalField for this, and store each number separately.
I use longitude and latitude in my django setup.
My model includes:
long_position = models.DecimalField (max_digits=8, decimal_places=3)
lat_position = models.DecimalField (max_digits=8, decimal_places=3)
For more precision you may want the decimal_places to be more.
When you want to display it in the Google Map API method you would reference your model and write a python code to output like this:
output = some_long_position + "," + some_lati_position
I'm using this in model
latitude = models.DecimalField(max_digits=11, decimal_places=7,null=True,blank=True)
longitude = models.DecimalField(max_digits=11, decimal_places=7,null=True,blank=True)

GeoDjango: How to create a circle based on point and radius

I have the following (simplified) Model:
class Zone(gismodels.Model):
name = gismodels.CharField()
poly = gismodels.PolygonField()
I want to create and save a polygon that represents a circle, based upon a given point and radius.
The only way I can figure out how to achieve this, is to call the postgis ST_Buffer function using raw SQL. I'm really hoping that there is another way.
Is it possible to access the GEOS buffer methods?
Yes, it is possible to use the geos buffer method:
>>> from django.contrib.gis import geos
>>> center = geos.Point(5, 5)
>>> radius = 2
>>> circle = center.buffer(radius)
>>> circle
<Polygon object at 0x1029d8370>
The radius here is in the same units as the coordinates of the points. This will work for some coordinate systems like UTM, but not as well for others.
Also, while this is appropriate for constructing a circular geometry, the PostGIS documentation notes that for doing radius searches ST_DWithin is more efficient.
I spent a ridiculous amount of time trying to get this working. Since this is the number one google search result, here's what worked for me:
radius_km = radius*1.609 # convert miles to km
point = target.geolocation # a django PointField using SRID 4326
# re-project point to a flat coordinate system
# so we can use meters instead of degrees below,
# AND get an actual circle instead of oval
point.transform(6347)
poly = point.buffer(radius_km*1000) # get a circular polygon from radius
poly.transform(4326)# re-project the resulting polygon back
Bonus: If you're doing this so you can get a circle on a google static map, grab polyline:
import polyline
import ast
geo = ast.literal_eval(poly.geojson) # turn the text into a dict
points = geo['coordinates'][0]
pl = polyline.encode(points, geojson=True, precision=5) # make a polyline out of the polygon for google
map_url += '&path=color:0x00000000%7Cfillcolor:0x0000AA33%7Cweight:1%7Cenc:' + pl