Python - SqlAlchemy: отфильтровать запрос по расстоянию большого круга? - PullRequest
0 голосов
/ 29 сентября 2011

Я использую Python и Sqlalchemy для хранения значений широты и долготы в базе данных Sqlite. Я создал гибридный метод для моего объекта Location,

@hybrid_method
def great_circle_distance(self, other):
    """
    Tries to calculate the great circle distance between the two locations

    If it succeeds, it will return the great-circle distance
    multiplied by 3959, which calculates the distance in miles.

    If it cannot, it will return None.

    """
    return math.acos(  self.cos_rad_lat 
                     * other.cos_rad_lat 
                     * math.cos(self.rad_lng - other.rad_lng)
                     + self.sin_rad_lat
                     * other.sin_rad_lat
                     ) * 3959

Все значения, такие как cos_rad_lat и sin_rad_lat, являются значениями, которые я предварительно рассчитал для оптимизации расчета. Во всяком случае, когда я запускаю следующий запрос,

pq = Session.query(model.Location).filter(model.Location.great_circle_distance(loc) < 10)

Я получаю следующую ошибку,

line 809, in great_circle_distance
    * math.cos(self.rad_lng - other.rad_lng)
TypeError: a float is required

Когда я печатаю значения для self.rad_lng и other.rad_lng, я получаю, например,

self.rad_lng: Location.rad_lng 
other.rad_lng: -1.29154947064

Что я делаю не так?

Ответы [ 3 ]

5 голосов
/ 29 сентября 2011

Вы действительно не можете использовать модуль math таким образом:

>>> c = toyschema.Contact()
>>> c.lat = 10
>>> c.lat
10
>>> import math
>>> math.cos(c.lat)
-0.83907152907645244
>>> math.cos(toyschema.Contact.lat)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: a float is required

Вы будете комбинировать sqalchemy.func.* вместо math.* в методе @great_circle_distance.expression за все такого рода хитрости.К сожалению, вы не можете сделать это с помощью sqlite;он не предоставляет функции триггера Вы можете использовать PostgreSQL, который это делает, или вы можете попробовать добавить эти функции в sqlite самостоятельно:

EDIT На самом деле добавлять функции в sqlite несложно: проверено НЕ .

Необходимо добавить математические функции в sqlite:

engine = sqlalchemy.create_engine("sqlite:///:memory:/")
raw_con = engine.raw_connection()
raw_con.create_function("cos", 1, math.cos)
raw_con.create_function("acos", 1, math.acos)

class Location(...):
    ...
    @hybrid_method
    def great_circle_distance(self, other):
        """
        Tries to calculate the great circle distance between 
        the two locations by using the Haversine formula.

        If it succeeds, it will return the Haversine formula
        multiplied by 3959, which calculates the distance in miles.

        If it cannot, it will return None.

        """
        return math.acos(  self.cos_rad_lat 
                         * other.cos_rad_lat 
                         * math.cos(self.rad_lng - other.rad_lng)
                         + self.sin_rad_lat
                         * other.sin_rad_lat
                         ) * 3959

    @great_circle_distance.expression
    def great_circle_distance(cls, other):
        return sqlalchemy.func.acos(  cls.cos_rad_lat 
                         * other.cos_rad_lat 
                         * sqlalchemy.func.cos(cls.rad_lng - other.rad_lng)
                         + cls.sin_rad_lat
                         * other.sin_rad_lat
                         ) * 3959
0 голосов
/ 29 сентября 2011

Похоже, что вы все сделали правильно, но каким-то образом метод на самом деле не «гибридизуется». Не могли бы вы сделать что-нибудь глупое, например, не на самом деле не включить декоратор в ваш исходный код?

0 голосов
/ 29 сентября 2011

Очевидно, что вы не можете получить число с плавающей запятой из этой строки.

Это потому, что вы используете "self", которое в качестве первого параметра вызова указывает, что метод является частью объекта,а не какую-то переменную, которую вы можете передать.

Вы должны попробовать это:

def great_circle_distance(self, first, other):
    """
    Tries to calculate the great circle distance between 
    the two locations by using the Haversine formula.

    If it succeeds, it will return the Haversine formula
    multiplied by 3959, which calculates the distance in miles.

    If it cannot, it will return None.

    """
    return math.acos(  self.cos_rad_lat 
                     * other.cos_rad_lat 
                     * math.cos(first.rad_lng - other.rad_lng)
                     + self.sin_rad_lat
                     * other.sin_rad_lat
                     ) * 3959

Я предполагаю здесь выше, что глобальные переменные "self.cos_rad_lat" и "self.sin_rad_lat" инициированыс правильными значениями где-то еще в вашей программе, вероятно, в разделе «init» того же объекта.

...