1. 引言
本文将深入探讨 Elasticsearch 的地理空间功能。我们不会涉及 Elasticsearch 实例和 Java 客户端的搭建,而是聚焦于如何存储地理数据以及通过地理查询检索数据。
接下来,我们直接了解可用的地理数据类型。
2. 地理数据类型
Elasticsearch 支持两种主要地理数据类型:**geo_point
(由经纬度坐标组成)和 geo_shape
(可表示矩形、线条、多边形等复杂形状)**。使用地理查询前,必须手动创建索引映射并显式设置字段映射。⚠️ 动态映射不适用于地理类型字段。
下面我们详细解析每种数据类型。
2.1. Geo Point 数据类型
geo_point
是最基础的类型,表示地图上的经纬度点。它可用于多种场景:
- 判断点是否在矩形区域内
- 按距离范围搜索对象
- 在复杂
geo_shape
查询中搜索索引点
简单来说,geo_point
就像地图上的一个图钉。此外,它还支持按位置分组文档(如特定区域或距离某点的范围),并按距离排序(从近到远)。
首先需要创建索引映射:
PUT /index_name
{
"mappings": {
"TYPE_NAME": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
}
现在可以开始存储地理点数据了。
2.2. Geo Shape 数据类型
与 geo_point
不同,geo_shape
支持存储和搜索多边形、矩形等复杂形状。要查询包含非地理点的形状文档,必须使用 geo_shape
类型。
同样,我们先创建索引映射:
PUT /index_name
{
"mappings": {
"TYPE_NAME": {
"properties": {
"location": {
"type": "geo_shape"
}
}
}
}
}
Elasticsearch 将 geo_shape
表示为三角网格,以实现高空间分辨率。
接下来我们看看如何存储数据。
3. 存储 Geo Point 数据的多种方式
假设我们已将 location
字段映射为 geo_point
类型。
3.1. 经纬度对象
显式定义经纬度键值对:
PUT index_name/_doc
{
"location": {
"lat": 23.02,
"lon": 72.57
}
}
✅ 最清晰无歧义的方式。
3.2. 经纬度字符串
用字符串简化表示:
{
"location": "23.02,72.57"
}
⚠️ 注意顺序:字符串格式为 lat,lon
,而数组格式、GeoJSON 和 WKT 格式为 lon,lat
。
3.3. 经纬度数组
使用数组表示:
{
"location": [72.57, 23.02]
}
⚠️ 顺序反转:数组格式中经度在前,纬度在后(为兼容 GeoJSON 标准)。
3.4. Geo Hash
使用地理哈希值代替坐标:
{
"location": "tsj4bys"
}
虽然哈希值简洁且适合邻近搜索,但可读性差。可使用 在线工具 转换经纬度到哈希值。
4. 存储 Geo Shape 数据的多种方式
假设我们已将 region
字段映射为 geo_shape
类型。
4.1. 点(Point)
创建最简单的形状——点:
POST /index/_doc
{
"region" : {
"type" : "point",
"coordinates" : [72.57, 23.02]
}
}
region
字段包含 type
和 coordinates
两个子字段,用于标识数据类型。
4.2. 线(LineString)
插入线段:
POST /index/_doc
{
"region" : {
"type" : "linestring",
"coordinates" : [[77.57, 23.02], [77.59, 23.05]]
}
}
线段由起点和终点两个坐标定义。聚合多条 LineString
可用于构建导航系统。
4.3. 多边形(Polygon)
插入多边形:
POST /index/_doc
{
"region" : {
"type" : "polygon",
"coordinates" : [
[ [10.0, 0.0], [11.0, 0.0], [11.0, 1.0], [10.0, 1.0], [10.0, 0.0] ]
]
}
}
⚠️ 首尾坐标必须相同以形成闭合多边形。
4.4. 其他 GeoJSON/WKT 格式
Elasticsearch 支持丰富的 GeoJSON/WKT 结构:
MultiPoint
MultiLineString
MultiPolygon
GeometryCollection
Envelope
(非标准 GeoJSON,但 ES 和 WKT 支持)
完整列表见 官方文档。
关键点:必须提供
type
和coordinates
字段才能正确索引。由于结构复杂,Elasticsearch 不支持对geo_shape
字段排序或直接检索,只能通过_source
字段获取。
5. 在 Elasticsearch 中插入地理数据
现在插入文档并学习地理查询。首先添加 Java 客户端依赖:
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.9.0</version>
</dependency>
5.1. 创建显式映射的索引
插入数据前需定义索引映射:
client.indices().create(builder -> builder.index(WONDERS_OF_WORLD)
.mappings(bl -> bl
.properties("region", region -> region.geoShape(gs -> gs))
.properties("location", location -> location.geoPoint(gp -> gp))
)
);
这里创建了两个字段:geo_shape
类型的 region
和 geo_point
类型的 location
。
5.2. 插入 geo_point 文档
创建 Java 类表示 geo_point
数据:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Location {
private String name;
private List<Double> location;
}
name
表示位置名称,location
是包含经纬度的列表(使用 Lombok 简化代码)。
使用 index()
方法插入文档:
Location pyramidsOfGiza = new Location("Pyramids of Giza", List.of(31.1328, 29.9761));
IndexResponse response = client.index(builder -> builder
.index(WONDERS_OF_WORLD)
.document(pyramidsOfGiza));
也可直接使用 JSON 字符串:
String jsonObject = """
{
"name":"Lighthouse of alexandria",
"location":{ "lat": 31.2139, "lon": 29.8856 }
}
""";
IndexResponse response = client.index(idx -> idx
.index(WONDERS_OF_WORLD)
.withJson(new StringReader(jsonObject)));
5.3. 插入 geo_shape 文档
直接使用 JSON 字符串插入 geo_shape
数据:
String jsonObject = """
{
"name":"Agra",
"region":{
"type":"envelope",
"coordinates":[[75,30.2],[80.1,25]]
}
}
""";
IndexResponse response = client.index(idx -> idx
.index(WONDERS_OF_WORLD)
.withJson(new StringReader(jsonObject)));
现在可以开始地理查询了。
6. 在 Elasticsearch 中查询地理数据
6.1. 地理边界框查询(Geo Bounding Box)
假设要在地图矩形区域内查找所有点,使用边界框查询:
{
"query":{
"geo_bounding_box":{
"location":{
"top_left":[30.0,31.0],
"bottom_right":[32.0,28.0]
}
}
}
}
Java 实现示例:
SearchRequest.Builder builder = new SearchRequest.Builder().index(WONDERS_OF_WORLD);
builder.query(query -> query
.geoBoundingBox(geoBoundingBoxQuery ->
geoBoundingBoxQuery.field("location")
.boundingBox(geoBounds -> geoBounds.tlbr(bl4 -> bl4
.topLeft(geoLocation -> geoLocation.coords(List.of(30.0, 31.0)))
.bottomRight(geoLocation -> geoLocation.coords(List.of(32.0, 28.0))))
)
)
);
边界框查询支持与
geo_point
相同的多种格式,详见 官方文档。
执行查询:
SearchResponse<Location> searchResponse = client.search(build, Location.class);
log.info("Search response: {}", searchResponse);
6.2. 地理形状查询(Geo Shape)
查询 geo_shape
文档必须使用 GeoJSON。例如查找特定坐标内的所有文档:
{
"query":{
"bool":{
"filter":[
{
"geo_shape":{
"region":{
"shape":{
"type":"envelope",
"coordinates":[[74.0,31.2],[81.1,24.0]]
},
"relation":"within"
}
}
}
]
}
}
}
relation
字段定义空间关系操作符:
INTERSECTS
(默认):返回与查询几何体相交的文档DISJOINT
:返回与查询几何体无重叠的文档WITHIN
:返回完全在查询几何体内的文档CONTAINS
:返回包含查询几何体的文档
Java 实现示例:
StringReader jsonData = new StringReader("""
{
"type":"envelope",
"coordinates": [[74.0, 31.2], [81.1, 24.0 ] ]
}
""");
SearchRequest searchRequest = new SearchRequest.Builder()
.query(query -> query.bool(boolQuery -> boolQuery
.filter(query1 -> query1
.geoShape(geoShapeQuery -> geoShapeQuery.field("region")
.shape(
geoShapeFieldQuery -> geoShapeFieldQuery.relation(GeoShapeRelation.Within)
.shape(JsonData.from(jsonData))
))))).build();
执行查询:
SearchResponse<Object> search = client.search(searchRequest, Object.class);
log.info("Search response: {}", search);
由于
geo_shape
结构多变,返回结果映射为泛型Object
。
6.3. 地理距离查询(Geo Distance)
查找指定点距离范围内的所有文档:
{
"query":{
"geo_distance":{
"location":{
"lat":29.976,
"lon":31.131
},
"distance":"10 miles"
}
}
}
Java 实现示例:
SearchRequest searchRequest = new SearchRequest.Builder().index(WONDERS_OF_WORLD)
.query(query -> query
.geoDistance(geoDistanceQuery -> geoDistanceQuery
.field("location").distance("10 miles")
.location(geoLocation -> geoLocation
.latlon(latLonGeoLocation -> latLonGeoLocation
.lon(29.88).lat(31.21)))
)
).build();
距离查询支持 多种坐标格式。
6.4. 地理多边形查询(Geo Polygon)
在多边形区域内查找所有点:
{
"query":{
"bool":{
"filter":[
{
"geo_shape":{
"location":{
"shape":{
"type":"polygon",
"coordinates":[[[68.859, 22.733],[68.859, 24.733],[70.859, 23]]]
},
"relation":"within"
}
}
}
]
}
}
}
Java 实现示例:
JsonData jsonData = JsonData.fromJson("""
{
"type":"polygon",
"coordinates":[[[68.859,22.733],[68.859,24.733],[70.859,23]]]
}
""");
SearchRequest build = new SearchRequest.Builder()
.query(query -> query.bool(
boolQuery -> boolQuery.filter(
query1 -> query1.geoShape(geoShapeQuery -> geoShapeQuery.field("location")
.shape(
geoShapeFieldQuery -> geoShapeFieldQuery.relation(GeoShapeRelation.Within)
.shape(jsonData)))))
).build();
⚠️ 此查询仅支持 geo_point
数据类型。
7. 总结
本文详细介绍了 Elasticsearch 地理数据的索引映射选项(geo_point
和 geo_shape
),展示了多种存储地理数据的方式,并通过地理查询和 Java API 演示了如何过滤结果。
完整代码见 GitHub 仓库。