言成言成啊 | Kit Chen's Blog

GIS基础-S2Geometry算法

发布于2023-01-27 15:35:05,更新于2024-11-03 13:35:25,标签:gis  文章会持续修订,转载请注明来源地址:https://meethigher.top/blog

S2Geometry算法,可以将二维地理坐标,转换为单一字符串,作为空间索引。并且根据层级,将整个地球进行分块,每块都有一个索引。如图。

利用该工具,可以实现简单的需求,显示出自己附近一个范围内可用的出租车或者共享单车。假设地图显示以自己为圆心,5公里为半径,这个范围内的车。

一、在线工具

S2可视化工具-OpenStreetMap

S2可视化在线绘制工具-GooleMap

DataV.GeoAtlas地理小工具系列-获取地理边界数据

空间索引 S2 学习指南及Java工具类实践 - 且炼时光

高效的多维空间点索引算法 — Geohash 和 Google S2

Google S2 是如何解决空间覆盖最优解问题的?

二、S2工具类

添加依赖

1
2
3
4
5
6
<!-- google s2 -->
<dependency>
<groupId>io.sgr</groupId>
<artifactId>s2-geometry-library-java</artifactId>
<version>1.0.0</version>
</dependency>

创建工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import com.google.common.geometry.*;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

/**
* s2工具类
*
* @author chenchuancheng github.com/meethigher
* @since 2023/1/27 15:59
*/
public class S2Utils {

private final S2RegionCoverer coverer;

private final int minLevel;

private final int maxLevel;

private final int maxCells;

/**
* @param minLevel 最小层级
* @param maxLevel 最大层级
* @param maxCells 最大格子数 在拆分网格时,优先使用level,如果根据level区间分出的最小数量,已经大于maxCell,则maxCell失效
*/
public S2Utils(int minLevel, int maxLevel, int maxCells) {
this.minLevel = minLevel;
this.maxCells = maxCells;
this.maxLevel = maxLevel;
coverer = new S2RegionCoverer();
coverer.setMinLevel(minLevel);
coverer.setMaxCells(maxCells);
coverer.setMaxLevel(maxLevel);
}


/**
* 经纬度转为指定层级的cellId
*/
public S2CellId toCellId(double lon, double lat, int level) {
return S2CellId.fromLatLng(S2LatLng.fromDegrees(lat, lon)).parent(level);
}

/**
* 经纬度转为最高层级(30级)的cellId
*/
public S2CellId toCellId(double lon, double lat) {
return S2CellId.fromLatLng(S2LatLng.fromDegrees(lat, lon)).parent(maxLevel);
}

/**
* token转换cellId
*/
public S2CellId tokenToCellId(String token) {
return S2CellId.fromToken(token);
}

/**
* cellId转换经纬度
*/
public Point cellIdToPoint(S2CellId cellId) {
S2LatLng s2LatLng = new S2CellId(cellId.id()).toLatLng();
return new Point(s2LatLng.lngDegrees(), s2LatLng.latDegrees());
}


/**
* 获取s2区域下的s2cellId列表
* 不允许出现比minLevel更小层级、比maxLevel更大层级的区域
*
* @param region S2区域
* @return S2CellId列表
*/
public List<S2CellId> getCellIdInRegion(S2Region region) {
ArrayList<S2CellId> s2CellIds = coverer.getCovering(region).cellIds();
return s2CellIds.stream().flatMap(s2CellId -> childrenCellId(s2CellId, minLevel).stream()).collect(Collectors.toList());
}

/**
* 将单个cellId转换为多个指定level的cellId
*
* @param s2CellId
* @param desLevel 允许的最小层级level
* @return
*/
public static List<S2CellId> childrenCellId(S2CellId s2CellId, Integer desLevel) {
return childrenCellId(s2CellId, s2CellId.level(), desLevel);
}

/**
* 递归获取
*/
private static List<S2CellId> childrenCellId(S2CellId s2CellId, Integer curLevel, Integer desLevel) {
List<S2CellId> s2CellIds = new LinkedList<>();
if (curLevel < desLevel) {
long interval = (s2CellId.childEnd().id() - s2CellId.childBegin().id()) / 4;
for (int i = 0; i < 4; i++) {
long id = s2CellId.childBegin().id() + interval * i;
s2CellIds.addAll(childrenCellId(new S2CellId(id), curLevel + 1, desLevel));
}
return s2CellIds;
} else {
s2CellIds.add(s2CellId);
return s2CellIds;
}
}


/**
* 获取S2Region
*
* @param lat 纬度
* @param lon 经度
* @param radius 单位为m
* @return 区域
*/
public S2Region createCircleRegion(double lon, double lat, double radius) {
double capHeight = (2 * S2.M_PI) * (radius / 40075017);
return S2Cap.fromAxisHeight(S2LatLng.fromDegrees(lat, lon).toPoint(), capHeight * capHeight / 2);
}

public static class Point {
private double lon;

private double lat;

public Point(double lon, double lat) {
this.lon = lon;
this.lat = lat;
}

public Point() {
}

public double getLon() {
return lon;
}

public void setLon(double lon) {
this.lon = lon;
}

public double getLat() {
return lat;
}

public void setLat(double lat) {
this.lat = lat;
}
}
}
发布:2023-01-27 15:35:05
修改:2024-11-03 13:35:25
链接:https://meethigher.top/blog/2023/gis-s2/
标签:gis 
付款码 打赏 分享
Shift+Ctrl+1 可控制工具栏