Python — SQLAlchemy: фильтровать запрос по расстоянию большого круга?

#python #database #sqlite #sqlalchemy #pylons

#python #База данных #sqlite #sqlalchemy #пилоны

Вопрос:

Я использую 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
  

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

Комментарии:

1. Это не формула хаверсина, это формула сферического закона косинусов. Смотрите (например) movable-type.co.uk/scripts/latlong.html

2. можем ли мы увидеть более полный пример вашего Location класса? В частности, как возникает каждое из этих свойств. Являются ли они атрибутами класса?

3. Упс, ты прав. Не haversine, просто расстояние большого круга.

Ответ №1:

На самом деле вы не можете использовать 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.* Для всего такого рода хитроумия у вас будет combine вместо in в @great_circle_distance.expression методе. К сожалению, вы также не можете этого сделать с помощью sqlite; он не предоставляет тригонометрических функций
Вы могли бы использовать PostgreSQL, который работает, или вы можете попробовать добавить эти функции в sqlite самостоятельно:

РЕДАКТИРОВАТЬ На самом деле не сложно добавить функции в 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
  

Комментарии:

1. Спасибо! У меня было ощущение, что это связано с этим. Я попробую это позже вечером и дам вам знать!

2. Кроме того, еще одна вещь. Я заставил его работать, когда база данных sqlite находится в памяти, но для уже созданной я не могу добавить функции так, как вы описали. Как я могу добавить функции в базы данных sqlite, которые уже созданы?

3. Проблема не в том, что база данных уже создана, а в том, что функции доступны для каждого соединения; и sqlite создает совершенно новое соединение для баз данных на диске, чтобы избежать проблем потокобезопасности; Лучшее решение может зависеть от ваших потребностей в потоковой обработке; и поэтому вам следует посмотреть на параметры драйвера sqliteв sqlalchemy

4. Отлично. Спасибо за совет. В итоге я подключил прослушиватель событий к пулу, который создал пользовательские тригонометрические функции для каждого соединения: sqlalchemy.org/docs/core /…

Ответ №2:

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

Это потому, что вы используете «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» того же объекта.

Комментарии:

1. Это не учитывает, как работает «гибридный» декоратор SQLAlchemy.

Ответ №3:

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

Комментарии:

1. Я импортировал с помощью этого: «из sqlalchemy.ext.hybrid импорт hybrid_property, hybrid_method»