sanfilippopablo
sanfilippopablo

Reputation: 1439

It is necessary to use GeoDjango to query distances in Django?

In the site i'm building, i have stored events with a Foreign Key to Cities. Like this:

class Event(models.Model):
    name = models.CharField(max_length=255)
    ...
    ciudad = models.ForeignKey(City)

class City(models.Model):
    name = models.CharField(max_length=500)
    ...
    lat = models.FloatField()
    lon = models.FloatField()

I want to query events at some kms of some city. What i actually do is this:

# isInRange takes two cities and a distance in kms and calculates
# if the distance between the cities (by the fields lat and lon and
# one nice formula) is minor than the given distance.
results = []
for event in Event.objects.all():
    if isInRange(city, event.city, kms):
        results.append(event)

I know, is very inefficient. I know that it is posible to do this in GeoDjango, but this is the only "geo-thing" that i have to do in my entire project. I have to use that "complex" solution without excuse or there is a way to do this in a more efficient manner?

Upvotes: 0

Views: 1544

Answers (2)

rphlo
rphlo

Reputation: 711

A better solution using a custom manager as I describe in this post Django sort by distance

Upvotes: 1

sid
sid

Reputation: 1042

If you don't need to be very exact in your range, you can use an approximation to calculate the latitude and longitude ranges. Concept explained here:

Using the city location and distance, find the change in latitude (this remains the same no matter where), and approximate change in longitude (varies based on latitude). Then calculate a bounding box.

import math

# earth_radius = 3960.0  # for miles
earth_radius = 6371.0  # for kms
degrees_to_radians = math.pi/180.0
radians_to_degrees = 180.0/math.pi

def change_in_latitude(distance):
    "Given a distance north, return the change in latitude."
    return (distance/earth_radius)*radians_to_degrees

def change_in_longitude(latitude, distance):
    "Given a latitude and a distance west, return the change in longitude."
    # Find the radius of a circle around the earth at given latitude.
    r = earth_radius*math.cos(latitude*degrees_to_radians)
    return (distance/r)*radians_to_degrees

def bounding_box(latitude, longitude, distance):
    lat_change = change_in_latitude(distance)
    lat_max = latitude + lat_change
    lat_min = latitude - lat_change
    lon_change = change_in_longitude(latitude, distance)
    lon_max = longitude + lon_change
    lon_min = longitude - lon_change
    return (lon_max, lon_min, lat_max, lat_min)

To calculate events within distance kms of city:

lon_max, lon_min, lat_max, lat_min = bounding_box(city.lat, city.lon, kms)
events = Event.objects.filter(
    city__lat__lte=lat_max,
    city__lat__gte=lat_min,
    city__lon__lte=lon_max,
    city__lon__gte=lon_min
)

Keep in mind that the error becomes larger with large distances, and the closer your are to the poles. There is also an issue with places near the anti-meridian (international date line), but that's easy to check for (check if longitude is > 180 or < -180).

If you want more acurate results, you can use this method as a first pass and then use your function, so you don't have to go through every event individually.

Upvotes: 10

Related Questions