第五阶段 50 · geo 地理查询(geo_point / geo_distance)

第五阶段 50 · geo 地理查询(geo_point / geo_distance)

50 · geo 地理查询(geo_point / geo_distance)

阶段:第五阶段 / 进阶(可选,业务涉及地理位置时)
ES:geo_point/geo_shape+geo_distance/geo_bounding_box| PostgreSQL:PostGISgeography/ST_DWithin


1. 概念

存经纬度并按「距离/范围」检索,就用地理类型:

类型用途
geo_point一个点(经纬度),最常用
geo_shape线/面/多边形等复杂形状

常见查询:geo_distance(某点 N 公里内)、geo_bounding_box(矩形框内)、
geo_distance聚合/排序(按距离排序、分环统计)。


2. PostgreSQL 对照

-- PostGIS:5km 内的门店SELECT*FROMstoresWHEREST_DWithin(geog,ST_MakePoint(:lon,:lat)::geography,5000);-- 按距离排序SELECT*,geog<->ST_MakePoint(:lon,:lat)::geographyASdistFROMstoresORDERBYdistLIMIT10;

ES 的geo_distanceST_DWithin;按距离排序 ≈<->排序。


3. ES DSL

3.1 mapping + 写入

PUT stores_idx { "mappings": { "properties": { "name": { "type": "keyword" }, "location": { "type": "geo_point" } } } } POST stores_idx/_doc { "name": "Store-1", "location": { "lat": 39.9042, "lon": 116.4074 } }

3.2 geo_distance(N 公里内) + 按距离排序

GET stores_idx/_search { "query": { "bool": { "filter": { "geo_distance": { "distance": "5km", "location": { "lat": 39.9, "lon": 116.4 } } } } }, "sort": [ { "_geo_distance": { "location": { "lat": 39.9, "lon": 116.4 }, "order": "asc", "unit": "km" } } ] }

3.3 geo_bounding_box(矩形框内)

"query": { "geo_bounding_box": { "location": { "top_left": { "lat": 40.1, "lon": 116.2 }, "bottom_right": { "lat": 39.7, "lon": 116.6 } } } }

4. Spring Boot 实现

@ComponentpublicclassDoc50GeoQuery{@AutowiredprivateElasticsearchClientelasticsearchClient;/** 某点 radiusKm 公里内的门店,按距离升序 */publicList<Map<String,Object>>nearby(StringindexName,doublelat,doublelon,doubleradiusKm)throwsIOException{SearchResponse<Map>resp=elasticsearchClient.search(s->s.index(indexName).query(q->q.bool(b->b.filter(f->f.geoDistance(g->g.field("location").distance(radiusKm+"km").location(l->l.latlon(ll->ll.lat(lat).lon(lon))))))).sort(so->so.geoDistance(gd->gd.field("location").location(l->l.latlon(ll->ll.lat(lat).lon(lon))).order(SortOrder.Asc).unit(DistanceUnit.Kilometers))),Map.class);returnresp.hits().hits().stream().map(Hit::source).filter(Objects::nonNull).collect(Collectors.toList());}}

import:co.elastic.clients.elasticsearch._types.SortOrder...DistanceUnit
每个 hit 的距离在hit.sort()(对应_geo_distance排序值)里可取出。


5. 坑与最佳实践

  1. 经纬度顺序易错:GeoJSON 数组是[lon, lat],对象是{lat, lon}——别写反。
  2. geo_distancefilter:不需要打分,放 filter 更快、可缓存。
  3. 距离排序有成本:每命中文档都算距离,大结果集配合先用geo_distance过滤缩小范围。
  4. 精度 vs 性能geo_distancearc(精确)/plane(快、近距离够用)两种算法。
  5. 复杂形状用geo_shape:点用geo_point即可,多边形/围栏才上geo_shape