Neighboring indexes in all directions, ordered by distance from the origin index.
前言
H3是一个针对地球的空间划分和空间索引系统。
H3地理空间索引系统是一个离散的全局网格系统,该系统由具有层次结构索引的球形多精度六边形拼贴组成。在球形外接二十面体的平面上创建六边形网格系统,然后使用反面为中心的多面体结构投影将网格单元投影到球体的表面。
从单纯的点到点覆盖范围计算,查询效率低下,而Uber H3通过为全球地理位置定制了一套,正六边形构成的覆盖图,每个覆盖图有一个唯一Id,通过空间换时间的思想,让点对点的查询变成了索引结构的匹配查询。实现了在没有巨大误差前提下的高效查询。
参考:
Uber h3算法_小柯行色匆匆的博客-CSDN博客_uber h3
一、h3是什么?
H3是一个地理空间索引系统,它将世界划分为六边形单元。H3在Apache 2许可下是开源的。
二、使用步骤
1.引入库
<dependency>
<groupId>com.uber</groupId>
<artifactId>h3</artifactId>
<version>3.7.1</version>
</dependency>
2.完整代码
h3工具类
package com.tqy.util;
import com.uber.h3core.H3Core;
import com.uber.h3core.util.GeoCoord;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public final class H3Util {
private H3Util() {
}
private static H3Core instance = null;
/**
* description
* 获取H3实例
* @author tengqingya
* @date 2022/9/3 16:44
* @return com.uber.h3core.H3Core
*/
public static H3Core getInstance() {
if (instance == null) {
instance = getH3Core();
}
return instance;
}
/**
* description
* H3单例模式
* @author tengqingya
* @date 2022/9/3 16:44
* @return com.uber.h3core.H3Core
*/
private static H3Core getH3Core() {
H3Core h3Core = null;
try {
h3Core = H3Core.newInstance();
} catch (IOException e) {
h3Core = H3Core.newSystemInstance();
}
return h3Core;
}
/**
* description
* 获取当前位置N圈的六边形索引
* @param latitude latitude
* @param longitude longitude
* @param kRing kRing
* @author tengqingya
* @date 2022/9/3 16:45
* @return java.util.List<java.lang.String>
*/
public static List<String> kRingDistances(Double latitude, Double longitude, int kRing) {
H3Core h3Core = getInstance();
if (Objects.isNull(h3Core)) {
return new ArrayList<>();
}
String h3Address = h3Core.geoToH3Address(latitude, longitude, ConstantsEnum.H3_REVOLUTION7);
List<List<String>> kRingDistances = h3Core.kRingDistances(h3Address, kRing);
if (CollectionUtils.isEmpty(kRingDistances)) {
return Collections.emptyList();
}
return kRingDistances.stream().flatMap(Collection::stream).collect(Collectors.toList());
}
/**
* description
* 获取当前位置N圈的六边形索引,按圈数归并
* @param latitude latitude
* @param longitude longitude
* @param kRing kRing
* @author tengqingya
* @date 2022/9/3 16:45
* @return java.util.List<java.util.List < java.lang.String>>
*/
public static List<List<String>> kRingDistancesList(Double latitude, Double longitude, int kRing) {
H3Core h3Core = getInstance();
if (Objects.isNull(h3Core)) {
return new ArrayList<>();
}
String h3Address = h3Core.geoToH3Address(latitude, longitude, ConstantsEnum.H3_REVOLUTION7);
//h3Address is like 87309154affffff
List<List<String>> kRingDistances = h3Core.kRingDistances(h3Address, kRing);
if (CollectionUtils.isEmpty(kRingDistances)) {
return Collections.emptyList();
}
return kRingDistances;
}
/**
* description
* 根据半径获取精确度值
* @param radius radius
* @author tengqingya
* @date 2022/8/19 18:52
* @return int
*/
public static int getResolution(Integer radius) {
if (null == radius || radius > ConstantsEnum.INT_4000) {
radius = ConstantsEnum.INT_4000;
}
return radius > ConstantsEnum.INT_2000 ? ConstantsEnum.INT_2 : 1;
}
/**
* H3索引转网格角
* @param h3Address H3索引
* @return 网格角
*/
public static List<GeoCoord> h3ToGeoBoundary(String h3Address) {
return getInstance().h3ToGeoBoundary(h3Address);
}
}
枚举类
package com.tqy.util;
public final class ConstantsEnum {
private ConstantsEnum() {
}
public static final Integer H3_REVOLUTION7 = 7;
/**
* 地球半径,单位:米
*/
public static final double ERATH_RADIUS_IN_METER = 6378137d;
public static final Integer INT_2 = 2;
public static final int INT_2000 = 2000;
public static final int INT_4000 = 4000;
}
使用
package com.tqy;
import com.alibaba.fastjson.JSON;
import com.meizu.tqy.util.H3Util;
import java.util.List;
import static java.lang.System.out;
/**
* @author tengqy
* @create 2023-01-16 19:59
*/
public class h3 {
public static void main(String[] args){
List<List<String>> h3AddressListAll = H3Util.kRingDistancesList(31.932001156316513d, 118.81938553759494d, 3);
out.println(JSON.toJSONString(h3AddressListAll));
}
}
输出结果
[["87309154affffff"],["87309155dffffff","873091559ffffff","873091464ffffff","87309154bffffff","873091548ffffff","87309154effffff"],["873091543ffffff","87309155cffffff","873091558ffffff","87309155bffffff","873091466ffffff","873091460ffffff","873091465ffffff","873091096ffffff","873091549ffffff","87309154dffffff","87309154cffffff","873091541ffffff"],["873091540ffffff","873091542ffffff","873091551ffffff","87309155effffff","87309155affffff","873091474ffffff","873091475ffffff","873091462ffffff","873091463ffffff","873091461ffffff","873091092ffffff","873091090ffffff","873091094ffffff","8730910b2ffffff","8730910b6ffffff","87309156bffffff","87309156affffff","873091545ffffff"]]
格式化后如下所示
[
[
"87309154affffff"
],
[
"87309155dffffff",
"873091559ffffff",
"873091464ffffff",
"87309154bffffff",
"873091548ffffff",
"87309154effffff"
],
[
"873091543ffffff",
"87309155cffffff",
"873091558ffffff",
"87309155bffffff",
"873091466ffffff",
"873091460ffffff",
"873091465ffffff",
"873091096ffffff",
"873091549ffffff",
"87309154dffffff",
"87309154cffffff",
"873091541ffffff"
],
[
"873091540ffffff",
"873091542ffffff",
"873091551ffffff",
"87309155effffff",
"87309155affffff",
"873091474ffffff",
"873091475ffffff",
"873091462ffffff",
"873091463ffffff",
"873091461ffffff",
"873091092ffffff",
"873091090ffffff",
"873091094ffffff",
"8730910b2ffffff",
"8730910b6ffffff",
"87309156bffffff",
"87309156affffff",
"873091545ffffff"
]
]
由于入参传的参数是
H3Util.kRingDistancesList(31.932001156316513d, 118.81938553759494d, 3)
其中3表示需要中心索引周围3圈的数据,所以返回的结果有周围的3层数据,每一层相较于上一层多6个(六边形)
总结
通过使用h3编码,可以将地理位置的范围查询,变为基于六边形的索引的等值或者in值查询,大大降低了查询的复杂度