3

I have this model

class Company(models.Model):
name = models.CharField(max_length = 50)
description = models.TextField()
latitude = models.FloatField()
longitude = models.FloatField()
owner = models.ForeignKey(User, on_delete = models.CASCADE, related_name = "company_owner")
category = models.ForeignKey(Category, on_delete = models.CASCADE)
def __str__(self):
    return self.name
class Meta:
    verbose_name_plural = "Companies"

def get_absolute_url(self):
    return reverse('category_list')
    #if want to redirect to its detail page then
    # return reverse('company_detail' ,kwargs = {'pk' : self.pk})

def get_distance(self):
    ip = get('https://api.ipify.org').text
    reader = geoip2.database.Reader('categories/GeoLite2-City.mmdb')
    response = reader.city(ip)
    current_lat = response.location.latitude
    current_lon = response.location.longitude
    comp_lat = self.latitude
    comp_lon = self.longitude
    R = 6373.0

    lat1 = radians(current_lat)
    lon1 = radians(current_lon)
    lat2 = radians(comp_lat)
    lon2 = radians(comp_lon)

    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
    c = 2 * atan2(sqrt(a), sqrt(1 - a))

    distance = R * c
    return(distance)

I have got the distance between the user location and company location from get_distance() function. But how do I sort the distance in ascending order? Since the distance differs from various location of the user I can't store the distance in database. I want to print the objects sorted in ascending order by distance

  • Have you considered using [GeoDjango](https://docs.djangoproject.com/en/2.1/ref/contrib/gis/tutorial/#introduction)? You could then just use [distance to sort](https://stackoverflow.com/questions/19703975/django-sort-by-distance) – mfrackowiak Jan 29 '19 at 12:46

2 Answers2

0

The best solution would be to look into using GeoDjango which enables you to do spatial queries.

Besides some other things you can do distance lookups which is mainly the thing you are looking for. All the query functionality then resides inside the database using using appropriate database extensions (eg. PostGIS for postgres). If you cannot use GeoDjango look into doing a raw SQL query, for example see this question.

Bernhard Vallant
  • 49,468
  • 20
  • 120
  • 148
0

Since this question has still no accepted answer and a few dozen fellows have seen it, I decided to give it a swing.

First of all, I think BernhardVallant and mrfackowiak already pointed out to the right solution. I will just illustrate what the code for it might look like.

Add Geodjango to your project

From the the official docs "GeoDjango intends to be a world-class geographic Web framework". Basically, it enables you to manipulate geographic data (coordinates, raster, projects) in all kinds of ways (calculate distance, filter objects based on geographic shapes, etc).

Setting it up requires a few steps that have been thorowly explained by many people already. You can start with the official documentation's tutorial.

Update your model

First, Import GeoDjango's model and special objects for geographic data. Then, update your model with the following changes.

# models.py
from django.contrib.gis.db import models
from django.contrib.gis.geos import GEOSGeometry, fromstr

# Company model inherits from GeoDjango's model
class Company(models.Model): 

    ...  # your other fields go here
    latitude = models.FloatField()
    longitude = models.FloatField()
    geo_location = models.PointField(null=True) # New field

    # New method to generate geo_location from lat, lng
    def create_geo_location(self):
        self.geo_location = fromstr(f'POINT({self.lng} {self.lat})', srid=4326)

    # Overwrite save() to use create_geo_location()
    def save(self, **kwargs):
        """ Creates a geo_location value (Point), if no prior-value exist"""
        if not self.geo_location:
            self.create_geo_location()

You won't be using your get_distance() method here, instead you will move the logic to your view.

Update your views.py

This is what your view should look like:

# views.py
from <your-app>.models import Company
from decimal import Decimal
from django.contrib.gis.geos import fromstr
from django.contrib.gis.db.models.functions import Distance 
from django.contrib.gis.measure import D 

class CompanyListView(ListView):
    context_object_name = 'companies'

    # Get user lat and lng using the logic in your get_distance() method and 
    # .. transom the values to Decimal 
    # ex. user_lat, user_lng = Decimal(45.5260525), Decimal(-73.5596788) 

    # user location is a geographic point value with a specific projection (srid)
    user_location = fromstr(f'POINT({user_lng} {user_lat})', srid=4326)

    queryset = Company.objects.filter(geo_location__dwithin=(user_location, D(m=2000)))  
       .annotate(distance_to_user = Distance("geo_location", user_location)) 
       .order_by("distance_to_user") 

The query will get all the Company instances that are within 2km distance from the user. It will also create a new variable, with annotate, called distance_to_user where it will store the distance (in meters). And finally, it will sort the result.

There are a few details about geographic data and queries that I didn't explain, but you will be better of learning a little about them if you are going to use GeoDjango. I hope this helps.

Dukanis
  • 1
  • 1