Reputation: 754
I am using
First question
I need to use GeoDjango to calculate distance between two points. When I checked the documentation it says that GeoQuerySet.distance() is deprecated and instead use Distance() from django.contrib.gis.db.models.functions.
The following code works OK:
from django.contrib.gis.db.models.functions import Distance
p1 = Instrument.objects.get(pk=151071000).coordinates
p2 = Instrument.objects.filter(pk=151071008)
for i in p2.annotate(distance=Distance('coordinates', p1)):
print i.distance
print i.distance.__class__
Output:
461.10913945 m
<class 'django.contrib.gis.measure.Distance'>
My model:
class Instrument(models.Model):
...
coordinates = gis_models.PointField(null=True, blank=True, dim=3)
But I have only two points so when I try to use Distance() without annotate() it returns instance of class django.contrib.gis.db.models.functions.Distance() rathen than django.contrib.gis.measure.Distance():
p1 = Instrument.objects.get(pk=151071000).coordinates
p2 = Instrument.objects.get(pk=151071008).coordinates
print Distance(p1, p2)
Output:
Distance(Value(SRID=4326;POINT Z (-76.48623600000001 44.260223 0)), GeomValue(SRID=4326;POINT Z (-76.490923 44.262658 0)))
How do I receive the same result as with using annotate()?
Second question
I have to calculate 3d distance that is taking into account depth/elevation. But when I try to do it I receive the same result as with 2d. Below I changed elevation to 200 in the first object:
p1 = Instrument.objects.get(pk=151071000)
p1.coordinates = 'SRID=4326;POINT Z (-76.48623600000001 44.260223 200)'
p2 = Instrument.objects.filter(pk=151071008)
for i in p2.annotate(distance=Distance('coordinates', p1.coordinates)):
print i.distance
Output:
461.10913945 m
Upvotes: 3
Views: 1685
Reputation: 196
** python 3.7, Django 2.2.10
some useful functions ive written to help me with lat/lon coordinates in applications
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import Distance
def get_point(lat, lon):
try:
lat = float(lat)
lon = float(lon)
if lat and lon:
return Point(x=float(round(lon, 6)), y=float(round(lat, 6)), srid=4326)
else:
return None
except Exception:
return None
def get_point_to_point_distance(point_one, point_two, measurement_unit="mile"):
return Distance(**{measurement_unit: point_one.distance(point_two)})
where measurement_unit can be any of the following:
STANDARD_UNIT = "m"
UNITS = {
'chain': 20.1168,
'chain_benoit': 20.116782,
'chain_sears': 20.1167645,
'british_chain_benoit': 20.1167824944,
'british_chain_sears': 20.1167651216,
'british_chain_sears_truncated': 20.116756,
'cm': 0.01,
'british_ft': 0.304799471539,
'british_yd': 0.914398414616,
'clarke_ft': 0.3047972654,
'clarke_link': 0.201166195164,
'fathom': 1.8288,
'ft': 0.3048,
'german_m': 1.0000135965,
'gold_coast_ft': 0.304799710181508,
'indian_yd': 0.914398530744,
'inch': 0.0254,
'km': 1000.0,
'link': 0.201168,
'link_benoit': 0.20116782,
'link_sears': 0.20116765,
'm': 1.0,
'mi': 1609.344,
'mm': 0.001,
'nm': 1852.0,
'nm_uk': 1853.184,
'rod': 5.0292,
'sears_yd': 0.91439841,
'survey_ft': 0.304800609601,
'um': 0.000001,
'yd': 0.9144,
}
# Unit aliases for `UNIT` terms encountered in Spatial Reference WKT.
ALIAS = {
'centimeter': 'cm',
'foot': 'ft',
'inches': 'inch',
'kilometer': 'km',
'kilometre': 'km',
'meter': 'm',
'metre': 'm',
'micrometer': 'um',
'micrometre': 'um',
'millimeter': 'mm',
'millimetre': 'mm',
'mile': 'mi',
'yard': 'yd',
'British chain (Benoit 1895 B)': 'british_chain_benoit',
'British chain (Sears 1922)': 'british_chain_sears',
'British chain (Sears 1922 truncated)': 'british_chain_sears_truncated',
'British foot (Sears 1922)': 'british_ft',
'British foot': 'british_ft',
'British yard (Sears 1922)': 'british_yd',
'British yard': 'british_yd',
"Clarke's Foot": 'clarke_ft',
"Clarke's link": 'clarke_link',
'Chain (Benoit)': 'chain_benoit',
'Chain (Sears)': 'chain_sears',
'Foot (International)': 'ft',
'German legal metre': 'german_m',
'Gold Coast foot': 'gold_coast_ft',
'Indian yard': 'indian_yd',
'Link (Benoit)': 'link_benoit',
'Link (Sears)': 'link_sears',
'Nautical Mile': 'nm',
'Nautical Mile (UK)': 'nm_uk',
'US survey foot': 'survey_ft',
'U.S. Foot': 'survey_ft',
'Yard (Indian)': 'indian_yd',
'Yard (Sears)': 'sears_yd'
}
Upvotes: 0
Reputation: 23134
Let's break the problem down:
In the Distance class documentation, we can read the following:
Accepts two geographic fields or expressions and returns the distance between them, as a Distance object.
So the Distance(p1, p2)
returns a Distance
object.
If you do:
p1 = Instrument.objects.get(pk=151071000).coordinates
p2 = Instrument.objects.get(pk=151071008).coordinates
d = Distance(m=p1.distance(p2))
print d.m
You will get the measurement in meters.
I would stick with the annotate
solution, which seems more solid! (opinionated response)
Distance
calculates the 2D distance between two points. In order to get a 3D calculation, you need to create one yourself.
You can have a look at my method from this question: Calculating distance between two points using latitude longitude and altitude (elevation)
EDIT 2019: Since the initial answer I have composed a Q&A style example here: How to calculate 3D distance (including altitude) between two points in GeoDjango that uses a far better (and less calculation error-prone) distance calculation between 2 points with altitude.
In sort:
We need to calculate the 2D great-circle distance between 2 points using either the Haversine formula or the Vicenty formula and then we can combine it with the difference (delta) in altitude between the 2 points to calculate the Euclidean distance between them as follows:
dist = sqrt(great_circle((lat_1, lon_1), (lat-2, lon_2).m**2, (alt_1 - alt_2)**2)
The solution assumes that the altitude is in meters and thus converts the great_circle
's result into meters as well.
Leaving this here for comment continuation purposes.
2. Distance
calculates the 2D distance between two points. In order to get a 3D calculation, you need to create one yourself.
You can have a look at my method from this question: Calculating distance between two points using latitude longitude and altitude (elevation)
Let polar_point_1 = (long_1, lat_1, alt_1) and polar_point_2 = (long_2, lat_2, alt_2)
Translate each point to it's Cartesian equivalent by utilizing this formula:
x = alt * cos(lat) * sin(long) y = alt * sin(lat) z = alt * cos(lat) * cos(long)
and you will have p_1 = (x_1, y_1, z_1) and p_2 = (x_2, y_2, z_2) points respectively.
Finally use the Euclidean formula:
dist = sqrt((x_2-x_1)**2 + (y_2-y_1)**2 + (z_2-z_1)**2)
Upvotes: 0