SqlAlchemy / Sqlite 距离计算

3 投票
3 回答
3127 浏览
提问于 2025-04-16 13:04

我正在使用sqlAlchemy这个工具,想要计算一个给定点到存储的点之间的距离,并把这个距离返回。

class Event(Base):

    __tablename__ = 'events'
    # Schema
    id = Column(Integer, primary_key=True)
    title = Column(String(150))
    description = Column(Text)
    url = Column(String(800))
    lat = Column(Float)
    lng = Column(Float)

……这是我的查询:

    nearest = """SELECT *, ((lat - '-41.288889') * (lat - '-41.288889')
 + (lng  - 174.777222) * (lng  - 174.777222)) AS distance FROM events 
ORDER BY distance ASC """

e = Event.query.from_statement(nearest)

看起来这个查询能正确返回对象的顺序,但我无法访问到距离这个属性。我该如何获取这个值呢?或者说,有什么好的方法来实现这个呢?

3 个回答

0

你可以在这里找到一些帮助:SQLAlchemy 自定义查询列

0

正如John Machin所指出的,你的距离计算方法有点奇怪,如果你的两个点之间的距离超过大约100公里,就会得到错误的结果。

你需要把距离转换成米(或者英尺等)才能得到合理的结果。可以查查Haversine或者Vincenty算法。更好的是,看看SQLAlchemy有没有什么对你有帮助的工具。

2

问题1:纬度和经度的度数长度是不一样的……在赤道上是一样的(假设地球是个球),但在极地时,经度的度数长度为零。

更新 为了说明这个问题的严重性:

向北或向南移动一度的纬度大约会覆盖111.2公里(假设地球是个球)。而向东或向西移动一度的经度在赤道上也是111.2公里。但在-41.3度的纬度下,移动一度的经度只会覆盖83.5公里。

从坐标(-41.3, 174.8)向正东移动到(-41.3, 174.92)是10.0公里。你的计算却把它当成了13.3公里——错误率高达33%

通过一个相对简单的近似方法,你可以把误差控制在4米以内:

from math import pi, sqrt, radians, cos
Earth_radius_km = 6371.009
km_per_deg_lat = 2 * pi * Earth_radius_km / 360.0

# what your SQL query is in effect doing
def approx_dist_1(lat1, lon1, lat2, lon2):
    return km_per_deg_lat * sqrt((lat1 - lat2) ** 2 + (lon1 - lon2) ** 2)

# better version    
def approx_dist_2(lat1, lon1, lat2, lon2):
    # calculate km_per_deg_lon for your central station in Python and 
    # embed it in your query
    km_per_deg_lon = km_per_deg_lat * cos(radians(lat1))
    return sqrt((km_per_deg_lat *(lat1 - lat2)) ** 2 + (km_per_deg_lon * (lon1 - lon2)) ** 2)

这个近似距离对于“寻找最近的披萨店”这样的应用来说已经足够好了,而且它的好处是可以在一些简单的环境中使用,比如SQLite,这些环境默认不支持正弦、余弦、正切及其反函数。

问题2:SQLite对错误比较宽容,但你不应该习惯性地使用引号,比如在(lat - '-41.288889')中。

问题3:我在SQLite层面上无法重现你的问题:

sqlite> create table foo (lat float, lon float);
sqlite> insert into foo values(99.9, -170.1);
sqlite> select * from foo;
99.9|-170.1
sqlite> SELECT *, ((lat - '-41.288889') * (lat - '-41.288889')
   ...>  + (lon  - 174.777222) * (lon  - 174.777222)) AS distance from foo;
99.9|-170.1|138874.600631492
sqlite>

也许你应该详细说明一下“这似乎返回了正确顺序的对象,但我无法访问距离属性”……检查一下e能告诉你什么?

撰写回答