如何查询给定坐标(以字符串类型的纬度和经度表示)中最近的记录?

13

我的项目中使用了GeoDjango和PostGIS。现在我遇到了问题,需要从我的PostgreSQL数据库表中获取给定坐标的最近记录。


请不要在标题中添加标签。 - ThiefMaster
1
我认为你的问题没有足够的信息,而且这是一个非常特殊的主题。试着将你的问题转化成一个类似的问题,因为那里可能会有更多的答案,知识渊博的人也更多。 - hochl
这是与度量相关的问题吗(例如,计算两个坐标之间的距离)?我不了解GeoDjango,但如果您需要计算方面的帮助,请随时提出 :-) - Joël
我需要用GeoDjango的方式来解决它。我会继续研究,然后更新这篇文章。 - eros
只想补充一下,我需要通过GeoDjango/PostgreSQL/PostGIS的方式来完成。 - eros
6个回答

19

这是使用GeoDjango和PostGIS的答案。

点坐标必须是一个GEOSGeometry对象。要使用它,请

from django.contrib.gis.geos import GEOSGeometry
point = GEOSGeometry('POINT(5 23)')

假设您有一个“餐厅”模型和点的坐标。那么,要找到最近的餐厅,只需使用:

Restaurants.objects.distance(point).order_by('distance')[0] 

[0] 是因为结果有序的第一条记录吗? -distance 是什么?它是模型几何类型的字段名吗? - eros
是的,[0] -- 排序结果的第一条记录。关于“-distance”:该方法以几何形状作为参数,并将包含到给定几何形状距离(作为距离对象)的每个模型附加到返回的查询集中的距离属性。 - Pavel Shvedov
3
+1 是正确答案,但“-distance”应该是“distance”。现在您正在选择最远的餐厅。 - RickyA
我尝试了这个,但是出现了“DataError: Coordinate values are out of range [-180 -90, 180 90] for GEOGRAPHY type”的错误。 - dannyroa

5

PostGIS 2.0及更高版本可以使用KNN最近邻搜索来获取最近的质心。例如:

SELECT ST_Distance(geom, 'SRID=26910;POINT(34.5 -23.2)'::geometry) AS d
FROM mypoints
ORDER BY geom <-> 'SRID=26910;POINT(34.5 -23.2)'::geometry
LIMIT 1;

3
我必须同意delawen的答案,但仅使用st_distance将非常缓慢。为了加快速度,您将需要利用GIST索引(请注意,大多数PostGIS函数都不使用索引,包括st_distance,请参见:postgis indexing recommendation)。
因此,您首先需要在该点周围创建一个缓冲区,然后使用“&&”检查其边界框(这是使用内置GIST索引的,因此性能要好得多),然后再使用“st_distance”检查距离。
例如,要从给定的坐标位置(例如X=1,Y=1)获取最近的“餐厅”,您可以编写以下内容:
select *,st_distance(the_geom_col,st_geomfromtext('POINT(1 1)',27700)) as distance 
from restaurants where st_buffer(st_geomfromtext('POINT(1 1)',27700),100) 
&& "the_geom_col"

相较于“st_distance”,这将非常快速,但结果可能包含距离给定位置超过100米的餐厅(特别是当几何形状保持在线或多边形格式时)。

为了获得更准确的结果,您可以在上述查询中添加以下内容,以获取完全位于100米范围内的餐厅:

and st_distance(the_geom_col,st_geomfromtext('POINTFROMTEXT(1 1)',27700)) <= 100

这将比仅使用 st_distance 更高效和更快。因为数据库只会对符合第一个条件的记录运行 st_distance
因此,作为一个经验法则,每当你必须执行昂贵的空间查找时,请尝试:
  • 通过使用特殊操作符(请参阅官方 PostGIS 文档中的特殊操作)尽可能过滤掉尽可能多的错误结果。
  • 然后编写实际的空间关系检查函数。
  • 在你的“geometry”列上始终有 GIST 索引。
  • 使用st_addbbox 将边界框添加到你的“几何图形”中。
  • 定期重新索引和清理/分析你的表。

注意:缓冲区大小或实际距离必须在你正在使用的投影系统中,即如果你正在使用 EPSG: 4326(纬度/经度),那么你必须以度数给出这些距离。例如,现实世界中的 1 米 = 0.00000899 度...而 100 米 = 做一下数学 :)


2

PostgreSQL/PostGIS用户应在ORDER BY子句中使用“<->”运算符来获取“K最近邻居”(KNN),正如Mike T此答案中所说。

为了从GeoDjango中的KNN-GiST性能提升中获益,您可以编写以下内容:

from django.contrib.gis.db.models.functions import GeomValue
from django.contrib.gis.geos import Point
from django.db.models.expressions import CombinedExpression, F

knn = 10
longitude = -96.876369
latitude = 29.905320
pnt = Point(longitude, latitude, srid=4326)
order_by_expression = CombinedExpression(F('geom'), '<->', GeomValue(pnt))
nearest_neighbors = Neighbor.objects.order_by(order_by_expression)[:knn]

2

我没有使用过GeoDjango,但在PostgreSQL/PostGIS中有st_distance(..)函数。因此,您可以按照 st_distance(geom_column, your_coordinates) asc 的顺序对结果进行排序,并查看最近的行。

如果您只有普通坐标(没有postgis几何图形),则可以使用geometryFromText函数将其转换为点。

这是您想要的吗?如果不是,请尽量详细说明。


你明白我在寻找什么。我已经成功尝试过了,但需要考虑性能。如果有其他方法,请指导我们。 - eros
你尝试在几何列上使用空间索引了吗? - María Arias de Reyna Domínguez

1
使用PostGIS和GeoDjango查找最近的邻居
考虑以下模型:
from django.contrib.gis.geos import Point
from django.contrib.gis.db import models

    class Store(models.Model):
        name = models.CharField(max_length=100)
        location = models.PointField(geography=True, srid=4326)
        longitude = models.FloatField()
        latitude = models.FloatField()
        objects = models.GeoManager()
    def save(self, **kwargs):
        self.location = Point(self.longitude, self.latitude)
        super(Store, self).save(**kwargs)

在一个视图中获取所有距离指定经度/纬度100英里半径内的兴趣点:

from django.contrib.gis.geos import Point
from django.contrib.gis.measure import D

point = Point(lng, lat)
points = Store.objects.filter(
    location__distance_lte=(point, D(mi=100))
).distance(point).order_by('distance')

在模板中显示结果:
<ul>
    {% for point in points %}
    <li>{{ point.name }} is {{ point.distance.mi|floatformat }} miles away</li>
    {% endfor %}
</ul>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接