Postgis Point Creation

Hi Django Community, I’m facing an issue in my project. I want to retrieve closest 3-5 restaurants to a User based on his location, OK!
I tried this

// Fetch City First
user_location = Point(float(city.lat), float(city.lng),srid=4326)

closest_restaurants = Restaurant.objects.filter(city=city).annotate(
...     distance=Distance(
...         Point('long', 'lat', srid=4326), user_location)
...     ).order_by('distance')

But this Gives me following Error

 raise TypeError('Invalid parameters given for Point initialization.')
TypeError: Invalid parameters given for Point initialization.

As lat , lng is DECIMAL FIELD (as in case of user_location, it gives Error if we do not convert it into float).
Please i’m stuck on this from last 2 days, tried a lot, but couldn’t resolve.

Please note that I could have done this by other things using haversine also , looping Restaurants, but I want to know if i can do this in the most optimal format.
Thanks

1 Like

Side note: When posting code here, enclose the code between lines of three backtick - ` characters. This means you’ll have a line of ```, then your code, then another line of ```. This forces the forum to keep your code properly formatted. (I’ve taken the liberty of editing your original post for this.)

On this clause:

Are 'long' and 'lat' meant to be references to fields in the model? If so, they should be enclosed in an F expression.

(Otherwise, I’m not understanding what you’re trying to do here.)

Thanks for the response,
Yea lat and long are my Restaurant fields.

Actually my primary aim is to fetch closest restaurants to a given city. Both city and Restaurant model have lat and long fields.

I actually wanted to get the fetch the required restaurants within the single query.

Any sort of help will be very much appreciated.

So as I wrote above, try wrapping those field names in F function calls.

But there’s another thing to consider since lat and long are Decimal Fields, Point() raises Error when we fed decimals to it. Instead when we give floats to it, it works as given below
Point(float(lat), float(long), srid=4326)

What does your restaurant model look like?

Restaurant is a simple class inheriting models.Model and it has various fields like
name, city(F. K), lat, long and some other fields

And lat, long are of type models.DecimalField()

Also when I do the following it gives error

Point(restaurant.lat, restaurant.long, srid=4326)

Error Message is like

Invalid arguments to the Point(), I’ve then check Point() class in postgis Django. Actually I think it only accepts float or int types

I’ve now told you twice what you need to do with those fields.

You may need to Cast those fields to float to be passed to Point.

Actually I want to do following
1.fetch restaurants for given city
2.fetch closest restaurants to the user location (take city lat, long for that purpose)
I want to achieve that in single query

From my post, it’s clear that I’m using annotate with Distance() to create a dynamic field distance in retrieved restaurants and then sort the restaurants in the ASC ORDER of that distance.

Since Distance() works with Point(), and I don’t have any PointField() in city or in Restaurant model, but I’ve lat and long decimal fields in both models. So I want to create Point() from lat and long. For city it’s very easy but for Restaurant table on which I’m Querying I’m unable to think how to reference lat, long and same time Cast them to float to avoid getting Invalid arguments Error raised by Point()

# fetch city from City model 
user_location = Point(float(city.lat), float(city.lng),srid=4326)

closest_restaurants = Restaurant.objects.filter(city=city).annotate(
...     distance=Distance(
...         Point(F('long') ,F('lat') , srid=4326), user_location)
...     ).order_by('distance')

Please let me know if I can provide any extra information

>>> user_loc=Point(float(city.long), float(city.lat), srid=4326)
>>> user_loc
<Point object at 0x7f88f7b87210>
>>> Restaurant.objects.filter(city=city).annotate(
... distance=Distance(
...  Point(F('long'), F('lat'),srid=4326),
...  user_loc
... ))
Traceback (most recent call last):
  File "<console>", line 3, in <module>
  File "/home/ashiq/ttw/lib/python3.10/site-packages/django/contrib/gis/geos/point.py", line 35, in __init__
    raise TypeError('Invalid parameters given for Point initialization.')
TypeError: Invalid parameters given for Point initialization.

Overall The Problem reduces to the following:-

I’m UNABLE TO CREATE A Dynamic PointField on my DB Table (with lat & long fields) dynamically (while annotating the Query)

To fix the error raised by your code, you need to convert the lat and lng fields of the city model to floats before passing them to the Point() constructor. You can do this using the django.db.models.functions.Cast() function.

Here is a revised version of your code that should work:

from django.db.models import Cast

user_location = Point(
    Cast(city.lat, output_field=django.db.models.FloatField()),
    Cast(city.lng, output_field=django.db.models.FloatField()),
    srid=4326,
)

closest_restaurants = Restaurant.objects.filter(city=city).annotate(
    distance=Distance(Point('long', 'lat', srid=4326), user_location),
).order_by('distance')[:5]

The Cast() function converts the field to the specified output field type. In this case, we are converting the lat and lng fields to floats.

The [:5] slice at the end of the queryset returns the first 5 results, which is what you requested.

I hope this helps!

@ashleycoder
Thanks for your Response. But this also doesn’t work.

>>> user_location = Point(
...     Cast(city.lat, output_field=FloatField()),
...     Cast(city.long, output_field=FloatField()),
...     srid=4326,
... )
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/ashiq/ttw/lib/python3.10/site-packages/django/contrib/gis/geos/point.py", line 35, in __init__
    raise TypeError('Invalid parameters given for Point initialization.')
TypeError: Invalid parameters given for Point initialization

This statement is not a query. The Cast function is a database function that gets passed through to the database in the generated SQL, it is not a function that generates a result itself in Python.

In other words, your original definition for user_location was correct - you don’t use Cast for it. What you would need to cast are the references to the long and lat fields in the Point method call in the query.

But looking at this some more, I’m not sure that’s going to be the correct construct either unless Point is a GIS function in the database, in which case you’re dealing with a name conflict. (Unfortunately, I’m not familiar enough with PostGIS to know.)

What you need to be cognizant of is where every function is being called or is trying to be executed.

If Point is a Python object, then it must be called within Python - passing that function to the database isn’t going to work. So if you’re going to annotate a value in a database, you need to call the corresponding PostGIS method to create the point in the database, and then calculated the distance using the appropriate PostGIS distance function.