Search code examples
pythondjangodjango-viewspostgis

show distance in km or m of each restaurant


I have implemented the feature of showing nearby restaurant from the given coordinates using postgis and geodjango. But I need to find the distance in km or m based on the distance it is nearby from user or given coordinates. I know question related to distance is asked in SO but this one is a bit different. I am showing the list of restaurant(list view) not a detail of restaurant that I will have specific restaurant location from the id. So i need an idea how should I now show the distance for each restaurant in the restaurant list view.

My idea is should I pass the lat and lng(that I am passing from the url) as context and use template filter for calculating the distance by doing

from django.contrib.gis.geos import GEOSGeometry
pnt = GEOSGeometry('SRID=4326;POINT(40.396764 -3.68042)')
pnt2 = GEOSGeometry('SRID=4326;POINT( 48.835797 2.329102  )')
pnt.distance(pnt2)*100

Here is the code in detail

def nearby_restaurant_finder(request, current_lat, current_long):
    from django.contrib.gis.geos import Point
    from django.contrib.gis.measure import D

    user_location = Point(float(current_long), float(current_lat))
    distance_from_point = {'km': 500}
    restaurants = Restaurant.gis.filter(
        location__distance_lte=(user_location, D(**distance_from_point)))
    restaurants = restaurants.distance(user_location).order_by('distance')
    context = {
        'restaurants': restaurants
    }
    return render(request, 'restaurant/nearby_restaurant.html', context)


 url(r'^nearby_restaurant/(?P<current_lat>-?\d*.\d*)/(?P<current_long>-?\d*.\d*)/$',
        views.nearby_restaurant_finder, name="nearby-restaurant"),


{% block page %}
    {% for restaurant in restaurants %}
        <h1>Nearby Restaurants are:</h1>
        <h3>{{ restaurant.name }}</h3>
        {% empty %}
        <h3>No Match Found</h3>
    {% endfor %}
{% endblock %}

Please share your idea on how should i do it


Solution

  • I think you're almost there; I would calculate the distance using python and then display them in the template instead of creating a filter.

    I would first update the context with a list of dictionaries or similar:

    def calculate_distance(restaurant_location, current_lat, current_long):
        # this function should return the distance of the restaurant from the user
        return distance_calculated
    
    def nearby_restaurant_finder(request, current_lat, current_long):
        from django.contrib.gis.geos import Point
        from django.contrib.gis.measure import D
    
        user_location = Point(float(current_long), float(current_lat))
        distance_from_point = {'km': 500}
        restaurants = Restaurant.gis.filter(location__distance_lte=(user_location, D(**distance_from_point)))
        restaurants = restaurants.distance(user_location).order_by('distance')
    
        # create a list of dictionaries with results to display
        ctx_restaurants = [
            {
                'name': restaurant.name, 
                'distance_from_user': calculate_distance(restaurant.location, current_lat, current_long)
            }
            for restaurant in restaurants
        ]
    
        # pass results into context
        context = {
            'restaurants': ctx_restaurants
        }
        return render(request, 'restaurant/nearby_restaurant.html', context)
    

    Then I would render this in the template in some sort of table

    {% block page %}
        <h1>Nearby Restaurants are:</h1>
        <table>
        {% for restaurant in restaurants %}
            <tr>
            <td>{{ restaurant.name }}</td>
            <td>{{ restaurant.distance_from_user}}</td>
            </tr>
        {% endfor %}
        </table>
    {% endblock %}
    

    Using TDD: Since the calculate_distance() is decoupled, I would test it by passing a bunch of known distances. Set up your tests according to the testing docs

    from django.test import TestCase
    from myapp.views import calculate_distance
    
    class DistanceTests(TestCase):
        def setUp(self):
            self.known_cases = [
                {'location': XX1, 'lat': XX1, 'long': XX1, 'expected': XX1},
                {'location': XX2, 'lat': XX2, 'long': XX2, 'expected': XX2},
            ]
    
        def test_calculate_distance(self):
            for case in self.known_cases:
                self.assertEquals(
                    calculate_distance(case['location'], case['lat'], case['long']),
                    case['expected']
                )