How to find near objects in django?

I have a service provider model . it has two fields named : lat & lon

class Provider(models.Model):
    name = models.CharField(max_length=100)
    lat = models.CharField(max_length=20)
    lon = models.CharField(max_length=20)
    address = models.TextField(null=True , blank=True)

    def save(self, *args, **kwargs):
        try:
            data = getaddress(self.lat , self.lon)
            self.address = data['formatted_address']
        except:
            pass
        super(Provider, self).save(*args, **kwargs)

How can i get a location from user and find some service providers that are near to my user? and it must be from the lat and lon field beacuse it is possible to service provider is located on another city ! also the speed of proccesse is important!

Alireza! I have a working idea of how to solve your problem. So, I see a solution via geoip2 library.

Here you can find information about using this library with django. It`s from django documantation.

And here one intresting article about related problem. It may be helpful. And I also found a couple of similar problems that have already been discussed. It may be useful to you.

So. If short, the solution is as follows:

# Let's assume that you have already created a django project and an application

from django.contrib.gis.geoip2 import GeoIP2

g = GeoIP2()


# You`ll need to user IP. For example it possible to get this via some way like that:

def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

# After that put response of that function to fallowing:

g.city("72.14.207.99")

{'accuracy_radius': 1000,
 'city': 'Mountain View',
 'continent_code': 'NA',
 'continent_name': 'North America',
 'country_code': 'US',
 'country_name': 'United States',
 'is_in_european_union': False,
 'latitude': 37.419200897216797,
 'longitude': -122.05740356445312,
 'metro_code': 807,
 'postal_code': '94043',
 'region_code': 'CA',
 'region_name': 'California',
 'time_zone': 'America/Los_Angeles',
 'dma_code': 807,
 'region': 'CA'}

You can see that there are latitude and longitude in response. Next, you will only need to compare the received width and longitude with the width and longitude of the provider. For example, you can connect the django-filter module and make a request with filtering by width and longitude fields.

I hope that my answer helped you a little.

Adding to the answer:

import geopy.distance

def find_nearest(user_lat, user_lon):

    # We get all the providers' objects and calculate the distance to them
    return Provider.objects.filter(
        # you can add field city or country and use here to immediately reduce the number of objects being checked
    ).annotate(
        distance=Coalesce(# you can use Coalesce or not, the main thing here is the essence of the idea
            geopy.distance.geodesic((lat, lon), (user_lat, user_lon)).km,
            Value(0), output_field=FloatField()
        )
    ).order_by('-distance')[0]

That depends on a number of factors. To do this fast you need your database to be able to index over your latitude/longitude coordinates. This means your database needs to be able to understand your coordinate system. So forget about Django for a second. You need to know which database backend (DBMS) you are using. Since you haven't mentioned one, I'm just going to refer to PostgreSQL as a reference for the rest of the answer. Some other databases also have a Geographic Implementation System (GIS), so the rest of answer generally holds true for these DBMSs. However, they tend not to be installed by default.

With PostgreSQL, you can install an extension called postgis. Postgis understands coordinate systems. It can accurately calculate the distance between any two points on the Earth (ie. taking into account that the Earth is a spheroid and not a perfect sphere). It can also provide indexes for your points, so that you can quickly find points that are close to each other. Example (using psql -- a command line client for PostgreSQL):

user=# CREATE EXTENSION postgis;
CREATE EXTENSION
user=# SELECT st_distance(
    --                  E/W     N/S
    'SRID=4326;POINT(-118.4079 33.9434)'::GEOGRAPHY, -- Los Angeles (LAX)
    'SRID=4326;POINT(   2.5559 49.0083)'::GEOGRAPHY  -- Paris (CDG)
    ) as "distance (meters)";
 distance (meters)
-------------------
  9124665.27317673

Here, SRID=4326 tells postgres that the coordinates are from a specific coordinate system (WGS 84 -- standard for GPS).

And using an index (to find the closest airport in the table to London Heathrow):

user=# CREATE TABLE airports (name TEXT, coords GEOGRAPHY);
CREATE TABLE
user=# INSERT INTO airports VALUES
    ('Los Angles (LAX)', 'POINT(-118.4079 33.9434)'),
    ('Iceland (KEF)',    'POINT( -22.6056 63.9850)'),
    ('Paris (CDG)',      'POINT(   2.5559 49.0083)');
    -- bulk insert these rows (except Paris) multiple times and you'll still 
    -- have a fast query thanks to index that is created. Around a couple ms
    -- when there are 100k+ rows in the table.
INSERT 0 3
user=# CREATE INDEX ON airports USING GIST (coords);
CREATE INDEX

user=# WITH input(coords) AS (
    SELECT 'POINT(-0.454295 51.470020)'::GEOGRAPHY -- London (LHR)
)
SELECT
    name,
    st_astext(airports.coords) AS coords,
    airports.coords <-> input.coords AS "distance (meters)"
FROM airports CROSS JOIN input
ORDER BY "distance (meters)"
LIMIT 1;
    name     |        coords         | distance (meters)
-------------+-----------------------+-------------------
 Paris (CDG) | POINT(2.5559 49.0083) | 347441.4699732649

Using Projected Coordinate Systems

AKA Faster, less accurate approximations.

Whilst treating the Earth like a sphere will yield accurate results for any two points on the planet, it can also be slow (because it requires extensive use of trigonometric functions). If you instead project lat/lon into a Cartesian space (a flat plane) and store these coordinates in your database, you can compute results much faster, but at the expense of accuracy over large distances. So within a city, using a projection will yield results that are nearly indistinguishable from the real values. For distances between cities in a state, results will still be accurate enough for most purposes. However, you should only compare points that are within your projection.

The below example shows that by using a projection, you lose only 11 meters over a distance of 33km -- less than 1/10th of a percent.

user=# CREATE TABLE nyc_airports (
    name TEXT,
    local_coords GEOMETRY,
    global_coords GEOGRAPHY
);
CREATE TABLE
users=# INSERT INTO nyc_airports (name, global_coords) VALUES
    ('JFK', 'POINT(-73.780968 40.641766)'), -- lat/lon pairs
    ('EWR', 'POINT(-74.174538 40.689491)');
INSERT 0 2
users=# UPDATE nyc_airports SET
    local_coords = st_transform(global_coords::GEOMETRY, 26918);
-- convert to NAD83, a projected strip about as wide New York State that stretches
-- from the southern tip of the USA up to just north of the last bit of Canada.
UPDATE 2

users=# SELECT
    DISTINCT ON (b.name)
    a.name AS "from", b.name AS "to",
    st_distance(a.global_coords, b.global_coords) AS "global distance",
    st_distance(a.local_coords, b.local_coords) AS "local distance"
FROM nyc_airports AS a CROSS JOIN nyc_airports AS b;
 from | to  | global distance |   local distance
------+-----+-----------------+--------------------
 JFK  | EWR |  33699.17253248 | 33688.828503315126
 JFK  | JFK |               0 |                  0

Getting back to Django

Django comes preinstalled with a library called geodjango that helps it work with spatial databases. You can see which databases it is compatible with here. You will also need to ensure all its required libraries have been installed. That is, it requires some C libraries in order to function. Once you have a compatible spatial database and geodjango's required libraries, using it becomes relatively easy. eg.

from django.contrib.gis.db import models

class Provider(models.Model):
    name = models.CharField(max_length=100)
    coord = models.PointField()  # defaults to lat/lon coordinate system

Creating a Provider

from django.contrib.gis.geos import Point

provider = Provider(name='LAX', coord=Point(-118.4079, 33.9434))

Finding the Nearest Provider

The distance computation is performed on the database server, where the server is able to leverage the index on coords (that was shown earlier) to minimise the number of rows it needs to examine.

from django.contrib.gis.db.models.functions import Distance
from django.contrib.gis.geos import Point

user_location = Point(-118.4079, 33.9434)
result = Provider.objects.annotate(
    distance=Distance('coord', user_location),
).order_by('distance').first()
Back to Top