何為地理索引:
? ? 在工程領(lǐng)域,尤其是電商平臺(tái),外賣平臺(tái),打車平臺(tái)頻繁使用的比如某一范圍內(nèi)的商品,商家功能等。會(huì)使用到某一范圍比如10Km范圍內(nèi)的所有商家場(chǎng)景。那么如何更加方便,簡(jiǎn)單的實(shí)現(xiàn)這樣的場(chǎng)景呢?
? ?常用的有:基于GeoHash原理的實(shí)現(xiàn),Google-S2 空間索引的實(shí)現(xiàn),Geomesa Z3的實(shí)現(xiàn),Uber H3索引的實(shí)現(xiàn)等;
? ?但是不同的使用場(chǎng)景,每個(gè)不同公司可以按照需求的不同選擇自己所需的實(shí)現(xiàn)技術(shù)棧。本文將會(huì)結(jié)合我們已經(jīng)在工程領(lǐng)域中實(shí)現(xiàn)的Uber H3索引進(jìn)行詳細(xì)講解。
1.H3索引介紹
H3索引,由Uber公司開(kāi)源?;A(chǔ)為C語(yǔ)言編寫的算法;h3-java為包裝JNI的通用接口抽象;(由于我們的主要技術(shù)語(yǔ)言為java,本文將著重介紹我們關(guān)于java方面的使用場(chǎng)景)
官方介紹文檔手冊(cè):https://uber.github.io/h3/#/documentation/overview/introduction
開(kāi)源代碼地址:https://github.com/uber/h3
java版本開(kāi)源代碼地址:https://github.com/uber/h3-java
? ? ? ? ??H3地理空間索引系統(tǒng)是具有分級(jí)線性索引索引的球體的多精度六角形平鋪。
? ? ? ? ? H3核心庫(kù)對(duì)緯度/經(jīng)度坐標(biāo)和之間的轉(zhuǎn)換提供了函數(shù)H3的地理空間索引。
2.當(dāng)前階段Uber開(kāi)源的H3工具包能夠?qū)ν馓峁┑姆?wù):
? ? ? ? ? 給定緯度/經(jīng)度點(diǎn),找到特定分辨率下包含H3單元的索引
? ? ? ? ? 給定H3索引,找到緯度/經(jīng)度單元中心
? ? ? ? ? 給定H3索引,確定緯度/經(jīng)度坐標(biāo)中的單元邊界
? ? ? ? ?其他等
3.重要的參考指標(biāo):
? ? ? ? 指標(biāo)如下:

? ? 核心指標(biāo)介紹: Resolution (精度),正六邊形覆蓋面積,正六邊形變長(zhǎng),每個(gè)精度對(duì)應(yīng)的索引個(gè)數(shù);
4. 轉(zhuǎn)換下思維方式:
? ? ?也就是從單純的點(diǎn)到點(diǎn)覆蓋范圍計(jì)算,查詢效率低下,而Uber H3通過(guò)為全球地理位置定制了一套,正六邊形構(gòu)成的覆蓋圖,每個(gè)覆蓋圖有一個(gè)唯一Id,通過(guò)空間換時(shí)間的思想,讓點(diǎn)對(duì)點(diǎn)的查詢變成了索引結(jié)構(gòu)的匹配查詢。實(shí)現(xiàn)了在沒(méi)有巨大誤差前提下的高效查詢。
5.工程化實(shí)踐介紹:
使用依賴如下:
<dependency>? ? ?
? ? <groupId>com.uber</groupId>? ?
? ? <artifactId>h3</artifactId>? ?
? ? <version>3.4.0</version>
</dependency>
初始化H3的計(jì)算對(duì)象和通過(guò)經(jīng)緯度獲得H3索引:
private static void init() {
? ? try {
? ? ? ? h3 = H3Core.newInstance();
? ? }catch (IOException e) {?
? ? ? ?logger.error("H3Instance init failed", e);
? ? }
}
public static String getH3IndexByCoordinate(double latitude, double longitude, H3ResolutionEnum resolution) {
//param validate 經(jīng)緯度參數(shù)的合法校驗(yàn)
? if (!isValidCoordinate(latitude, longitude)) {
? ? ? logger.warn("getH3IndexByCoordinate param is invalid,coordinate:{},{}", longitude, latitude);
? ? ? throw new IllegalArgumentException();
? }
//execute h3 index calculate 執(zhí)行通過(guò)經(jīng)緯度 選擇的精度,計(jì)算H3索引邏輯
? ? try {
? ? ? ? if (h3 == null) {
? ? ? ? ? ? init();
? ? }
? ? ? ? // 得到對(duì)應(yīng)精度的H3索引
? ? ? ? return h3.geoToH3Address(latitude, longitude, resolution.precision);
? ? } catch (Exception e) {
? ? ? ? logger.error("geoToH3Address-error", e);
? ? ? ? throw e;
? ? }
}
通過(guò)H3索引找到對(duì)應(yīng)的正六邊形中心點(diǎn)坐標(biāo)經(jīng)緯度:
public static Optional<GeoCoord> getCoordinateByH3Index(String h3Index) {
? ? try {
? ? ? ? //param validate 校驗(yàn)H3索引字符串是否合法
? ? ? ? if (h3PreCheckInvalid(h3Index)) {
? ? ? ? logger.warn("getCoordinateByH3Index,param is invalid!");
? ? ? ? return Optional.empty();
? ? ? ? }
? ? ? ? //execute 獲取正六邊形中心坐標(biāo)
? ? ? ? GeoCoord geoCoord = h3.h3ToGeo(h3Index);
? ? ? ? return Optional.of(geoCoord);
? ? } catch (Exception e) {
? ? ? ? logger.error("getCoordinateByH3Index error,h3Index:" + h3Index, e);
? ? ? ? return Optional.empty();
? ? }
}
有了上述的兩種方式,其實(shí)我們已經(jīng)可以通過(guò)Uber H3索引的屬性做自己需要的業(yè)務(wù)拓展了。
比如通過(guò)某一個(gè)點(diǎn),獲取到符合精度的正六邊形中心坐標(biāo),以這個(gè)正六邊形中心坐標(biāo),計(jì)算直線距離N公里內(nèi)有多少個(gè)索引,得到索引集合。
那么則可以通過(guò)索引集合來(lái)查詢貨物或者商家是否有符合集合內(nèi)索引的屬性,如果有則證明在N公里范圍內(nèi),如果沒(méi)有則證明不在N公里范圍內(nèi)。
H3核心API方法列舉:
final class NativeMethods {
? ? NativeMethods() {
? ? }
? ??native int maxH3ToChildrenSize(long var1, int var3);
? ??native void h3ToChildren(long var1, int var3, long[] var4);
? ??native boolean h3IsValid(long var1);
? ??native int h3GetBaseCell(long var1);
? ? native boolean h3IsPentagon(long var1);
? ? native long geoToH3(double var1, double var3, int var5);
? ? native void h3ToGeo(long var1, double[] var3);
? ? native int h3ToGeoBoundary(long var1, double[] var3);
? ? native int maxKringSize(int var1);
? ? native void kRing(long var1, int var3, long[] var4);
? ? native void kRingDistances(long var1, int var3, long[] var4, int[] var5);
? ? native int hexRange(long var1, int var3, long[] var4);
? ? native int hexRing(long var1, int var3, long[] var4); native int h3Distance(long var1, long var3);
? ? native int experimentalH3ToLocalIj(long var1, long var3, int[] var5);
? ? native long experimentalLocalIjToH3(long var1, int var3, int var4);
? ? native int h3LineSize(long var1, long var3);
? ? native int h3Line(long var1, long var3, long[] var5);
? ? native int maxPolyfillSize(double[] var1, int[] var2, double[] var3, int var4);
? ? native void polyfill(double[] var1, int[] var2, double[] var3, int var4, long[] var5);
? ? native void h3SetToLinkedGeo(long[] var1, ArrayList<List<List<GeoCoord>>> var2);
? ? native int compact(long[] var1, long[] var2);
? ? native int maxUncompactSize(long[] var1, int var2);
? ? native int uncompact(long[] var1, int var2, long[] var3);
? ? native double hexAreaKm2(int var1); native double hexAreaM2(int var1);
? ? native double edgeLengthKm(int var1);
? ? native double edgeLengthM(int var1);
? ? native long numHexagons(int var1);
? ? native void getRes0Indexes(long[] var1);
? ? native boolean h3IndexesAreNeighbors(long var1, long var3);
? ? native long getH3UnidirectionalEdge(long var1, long var3);
? ? native boolean h3UnidirectionalEdgeIsValid(long var1);
? ? native long getOriginH3IndexFromUnidirectionalEdge(long var1);
? ? native long getDestinationH3IndexFromUnidirectionalEdge(long var1);
? ? native void getH3IndexesFromUnidirectionalEdge(long var1, long[] var3);
? ? native void getH3UnidirectionalEdgesFromHexagon(long var1, long[] var3);
? ? native int getH3UnidirectionalEdgeBoundary(long var1, double[] var3);
}
那么如何正確的入手Uber H3呢?
我建議:1.了解自己的需求場(chǎng)景是否符合范圍查找的場(chǎng)景。
? ? ? ? ? ? ? 2.判斷是否對(duì)范圍查找的誤差要求沒(méi)有特別的高。
? ? ? ? ? ? ? 3.下載Uber H3源碼包:源碼包中提供了,基礎(chǔ)的場(chǎng)景測(cè)試用例,基準(zhǔn)測(cè)試用例。下載代碼后本地debug就可以進(jìn)行調(diào)試。
? ? ? ? ? ? ? https://github.com/uber/h3-java/tree/master/src/test/java/com/uber/h3core??
? ? ? ? ? ? ? 4.調(diào)試完畢后,根據(jù)自己的場(chǎng)景模型,構(gòu)造符合自己使用的API或者工具類,提供給使用方。
歡迎大家多提寶貴意見(jiàn)。