第五阶段 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_distance≈ST_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. 坑与最佳实践
- 经纬度顺序易错:GeoJSON 数组是
[lon, lat],对象是{lat, lon}——别写反。 geo_distance放filter:不需要打分,放 filter 更快、可缓存。- 距离排序有成本:每命中文档都算距离,大结果集配合先用
geo_distance过滤缩小范围。 - 精度 vs 性能:
geo_distance有arc(精确)/plane(快、近距离够用)两种算法。 - 复杂形状用
geo_shape:点用geo_point即可,多边形/围栏才上geo_shape。