2009-12-16 1 views
10

Ich versuche, Nähe Suche nach einem grundlegenden Laden Locater in Django zu behandeln. Anstatt PostGIS mit meiner App zu übertragen, nur um den Distanzfilter von GeoDjango verwenden zu können, möchte ich in einer Modellabfrage die Entfernungsformel Sphärisches Gesetz der Kosinus verwenden. Ich möchte, dass alle Berechnungen in der Datenbank in einer einzigen Abfrage durchgeführt werden, um die Effizienz zu erhöhen.Filter Postleitzahlen von Nähe in Django mit dem Sphärischen Gesetz der Kosinus

Ein Beispiel MySQL-Abfrage aus dem Internet das Sphärische Kosinussatz wie diese Umsetzung:

SELECT id, ( 
    3959 * acos(cos(radians(37)) * cos(radians(lat)) * 
    cos(radians(lng) - radians(-122)) + sin(radians(37)) * 
    sin(radians(lat))) 
) 
AS distance FROM stores HAVING distance < 25 ORDER BY distance LIMIT 0 , 20; 

Die Abfrage muss die Postleitzahl ForeignKey für jedes Geschäft der lat/lng Werte verweisen. Wie kann ich all dies in einer Django-Modellabfrage durchführen?

+0

Können Sie Ihre Modelle posten? – cethegeek

+0

(a) Das ist NICHT die 'hairsine' Formel; es ist die Formel des "sphärischen Gesetzes der Kosinus"; sehen Sie zum Beispiel "http: // www.movable-type.co.uk/scripts/ latlong.html" und schauen Sie sich die entsprechenden Wikipedia-Artikel an. (b) Ich vertraue darauf, dass Sie die hartkodierten Benutzerkoordinaten durch Variablen ersetzen :-) (c) Der geneigte Leser sollte gewarnt werden, dass Ihre Distanzeinheit etwas archaisch ist (etwas, was mit 1000 * zu tun hat) das Standardtempo der Legion), glaube ich) :-) –

+0

Jeez, was ist so schwer an GeoDjango? Installiere es einfach :) –

Antwort

8

Es ist möglich, die raw SQL queries in Django auszuführen.

Mein Vorschlag ist, schreibe die Abfrage, um eine Liste von IDs zu ziehen (wie es jetzt aussieht), dann verwende die IDs, um die zugehörigen Modelle zu ziehen (in einer regulären Django-Abfrage ohne Raw-SQL)). Versuchen Sie, Ihr SQL so dialektunabhängig wie möglich zu halten, so dass Sie sich keine Sorgen mehr machen müssen, wenn Sie Datenbanken wechseln müssen.

Um, hier ist ein Beispiel dafür, zu klären, wie es geht:

def get_models_within_25 (self): 
    from django.db import connection, transaction 
    cursor = connection.cursor() 

    cursor.execute("""SELECT id, ( 
     3959 * acos(cos(radians(37)) * cos(radians(lat)) * 
     cos(radians(lng) - radians(-122)) + sin(radians(37)) * 
     sin(radians(lat)))) 
     AS distance FROM stores HAVING distance < 25 
     ORDER BY distance LIMIT 0 , 20;""") 
    ids = [row[0] for row in cursor.fetchall()] 

    return MyModel.filter(id__in=ids) 

Als wichtigen Hinweis, ich kann nicht für diesen Code bürgen, da es ein paar Monate her, seit ich jede Django geschrieben habe, aber es sollte in die richtige Richtung gehen.

+0

Es funktioniert gut, braucht nur Triple-Quoting (oder in eine einzige Zeichenfolge umgewandelt werden). – Tom

+0

Nur eine Folge dazu. Die ursprüngliche Abfrage gibt ein Feld "Abstand" zurück (das den Abstand zwischen den zwei Gruppen von log/lat anzeigt). Wie würde ich den zweiten Teil ausführen, aber mit diesem zusätzlichen "Abstand" -Feld? – dotty

+0

Was ist "der zweite Teil"? – jakeboxer

4

einfach auf jboxer Antwort folgen zu lassen, hier ist das Ganze als Teil einer benutzerdefinierten Manager mit einigen der Sachen hartcodiert verwandelte sich in Variablen:

class LocationManager(models.Manager): 
    def nearby_locations(self, latitude, longitude, radius, max_results=100, use_miles=True): 
     if use_miles: 
      distance_unit = 3959 
     else: 
      distance_unit = 6371 

     from django.db import connection, transaction 
     cursor = connection.cursor() 

     sql = """SELECT id, (%f * acos(cos(radians(%f)) * cos(radians(latitude)) * 
     cos(radians(longitude) - radians(%f)) + sin(radians(%f)) * sin(radians(latitude)))) 
     AS distance FROM locations_location HAVING distance < %d 
     ORDER BY distance LIMIT 0 , %d;""" % (distance_unit, latitude, longitude, latitude, int(radius), max_results) 
     cursor.execute(sql) 
     ids = [row[0] for row in cursor.fetchall()] 

     return self.filter(id__in=ids) 
1

jboxer Antwort Nach

def find_cars_within_miles_from_postcode(request, miles, postcode=0): 

    # create cursor for RAW query 
    cursor = connection.cursor() 

    # Get lat and lon from google 
    lat, lon = getLonLatFromPostcode(postcode) 

    # Gen query 
    query = "SELECT id, ((ACOS(SIN("+lat+" * PI()/180) * SIN(lat * PI()/180) + COS("+lat+" * PI()/180) * COS(lat * PI()/180) * COS(("+lon+" - lon) * PI()/180)) * 180/PI()) * 60 * 1.1515) AS distance FROM app_car HAVING distance<='"+miles+"' ORDER BY distance ASC" 

    # execute the query 
    cursor.execute(query) 

    # grab all the IDS form the sql result 
    ids = [row[0] for row in cursor.fetchall()] 

    # find cars from ids 
    cars = Car.objects.filter(id__in=ids) 

    # return the Cars with these IDS 
    return HttpResponse(cars) 

Dies gibt meine Autos aus x Menge von Meilen zurück, das funktioniert gut. Die rohe Abfrage gab jedoch an, wie weit sie von einem bestimmten Ort entfernt war. Ich denke, der Feldname war 'distance'.

Wie kann ich dieses Feld "Entfernung" mit meinen Auto-Objekten zurückgeben?

8

Um Toms Antwort zu verfolgen, wird es in SQLite standardmäßig nicht funktionieren, da SQLite standardmäßig keine mathematischen Funktionen hat. Kein Problem, es ist ziemlich einfach hinzuzufügen:

class LocationManager(models.Manager): 
    def nearby_locations(self, latitude, longitude, radius, max_results=100, use_miles=True): 
     if use_miles: 
      distance_unit = 3959 
     else: 
      distance_unit = 6371 

     from django.db import connection, transaction 
     from mysite import settings 
     cursor = connection.cursor() 
     if settings.DATABASE_ENGINE == 'sqlite3': 
      connection.connection.create_function('acos', 1, math.acos) 
      connection.connection.create_function('cos', 1, math.cos) 
      connection.connection.create_function('radians', 1, math.radians) 
      connection.connection.create_function('sin', 1, math.sin) 

     sql = """SELECT id, (%f * acos(cos(radians(%f)) * cos(radians(latitude)) * 
     cos(radians(longitude) - radians(%f)) + sin(radians(%f)) * sin(radians(latitude)))) 
     AS distance FROM location_location WHERE distance < %d 
     ORDER BY distance LIMIT 0 , %d;""" % (distance_unit, latitude, longitude, latitude, int(radius), max_results) 
     cursor.execute(sql) 
     ids = [row[0] for row in cursor.fetchall()] 

     return self.filter(id__in=ids) 
+0

Scheint wie Magie, wie funktioniert das? Ich denke, es ist ziemlich ineffizient? – nisc

+0

Die 'von mysite Importeinstellungen' können allgemeiner gemacht werden, indem 'von django.conf Importeinstellungen' verwendet wird. –

+0

Wo ist der Referenzpunkt? – Gocht

5

auf Tom zu verfolgen, wenn Sie eine Abfrage haben wollen, die auch in postgresql funktioniert, können Sie nicht so verwenden, weil Sie einen Fehler werden sagen: ‚Abstand‘ ist nicht vorhanden.

Sie sollten das ganze sphärische Gesetz expresion in der WHERE-Klausel setzen, wie dies (Es funktioniert auch in mysql):

import math 
from django.db import connection, transaction 
from django.conf import settings 

from django .db import models 

class LocationManager(models.Manager): 
    def nearby_locations(self, latitude, longitude, radius, use_miles=False): 
     if use_miles: 
      distance_unit = 3959 
     else: 
      distance_unit = 6371 

     cursor = connection.cursor() 

     sql = """SELECT id, latitude, longitude FROM locations_location WHERE (%f * acos(cos(radians(%f)) * cos(radians(latitude)) * 
      cos(radians(longitude) - radians(%f)) + sin(radians(%f)) * sin(radians(latitude)))) < %d 
      """ % (distance_unit, latitude, longitude, latitude, int(radius)) 
     cursor.execute(sql) 
     ids = [row[0] for row in cursor.fetchall()] 

     return self.filter(id__in=ids) 

Bitte beachten Sie, dass die Breiten- und Längengrad wählen haben, sonst können Sie nicht verwende es in der WHERE-Klausel.

0

einige der vorgeschlagenen Antworten Mit oben, ich incosistent Ergebnisse war immer so ich die Gleichung wieder mit [diesen Link] http://www.movable-type.co.uk/scripts/latlong.html als Referenz zu überprüfen entschieden, ist die Gleichung d = acos(sin(lat1)*sin(lat2) + cos(lat1)*cos(lat2)*cos(lon2-lon1)) * 6371 wo d der Abstand ist, berechnet werden

lat1,lon1 ist die Koordinate des Basispunkts und lat2,lon2 ist die Koordinate der anderen Punkte, die in unserem Fall Punkte in der Datenbank sind.

Aus den obigen Antworten sucht die LocationManager Klasse wie diese

class LocationManager(models.Manager): 
def nearby_locations(self, latitude, longitude, radius, max_results=100, use_miles=True): 
    if use_miles: 
     distance_unit = 3959 
    else: 
     distance_unit = 6371 

    from django.db import connection, transaction 
    from mysite import settings 
    cursor = connection.cursor() 
    if settings.DATABASE_ENGINE == 'sqlite3': 
     connection.connection.create_function('acos', 1, math.acos) 
     connection.connection.create_function('cos', 1, math.cos) 
     connection.connection.create_function('radians', 1, math.radians) 
     connection.connection.create_function('sin', 1, math.sin) 

    sql = """SELECT id, (acos(sin(radians(%f)) * sin(radians(latitude)) + cos(radians(%f)) 
      * cos(radians(latitude)) * cos(radians(%f-longitude))) * %d) 
    AS distance FROM skills_coveragearea WHERE distance < %f 
    ORDER BY distance LIMIT 0 , %d;""" % (latitude, latitude, longitude,distance_unit, radius, max_results) 
    cursor.execute(sql) 
    ids = [row[0] for row in cursor.fetchall()] 

    return self.filter(id__in=ids) 

Verwenden der Website [link] http://www.movable-type.co.uk/scripts/latlong.html als Scheck, meine Ergebnisse wo konsistent.

Verwandte Themen