php實現的geohash,php !

本文目錄一覽:

PHP使用中,geohash用一個字元串表示經度和緯度兩個坐標。某些情況下無法在兩列上同時應用索引

不太清楚啊,樓主還是自己去後盾人那裡看看吧,那裡很多教學視頻

redis常用數據結構介紹和業務應用場景分析

redis內置了很多常用數據結構,了解這些數據結構的功能和應用場景能夠讓我們在需求開發時靈活運用來解決實際問題。

String是redis中最基礎的數據結構,你可以把它用作緩存最基礎的kv(key-value)類型的緩存(value最大為512MB),只需要把需要緩存的對象進行string的編解碼即可。另外String也可以保存數值類型的數據,就可以來實現計數功能(redi提供了incr等原子操作)

常見應用場景

List列表更多的時候是把它當成隊列使用(最大2^32 – 1個元素),使用入隊出隊功能,如果來使用它作為各種列表的話,很多時候不具備防重功能在使用的時候不是很方便。

常見應用場景

Set是一種無序不重複的集合,添加刪除檢查是否存在都是O(1)的時間複雜度。

常見應用場景

hash是一個map結構,可以像存儲對象的多個欄位一樣存儲一個key的多類數據。

常見應用場景

redis中的pub/sub可以實現廣播功能,類似rocketmq中的broadcast

常見應用場景

除了上述最基本的數據結構外,redis還提供了一些其他的數據結構,有的是需要安裝相關redis stack來使用的。

bitmap本質上還是使用的string字元串,不過可以通過bit來進行操作,把這個key的value值想像成bit組成的數組。

常見應用場景

bloomfilter(也叫布隆過濾器)可以理解成一種特殊的set集合,它可以用來判斷一個值是否在這個集合中,不過不同於普通的set,它的判斷存在一定誤判的可能(假陽性),如果bloomfilter判斷一個值不在這個集合中,那麼一定不在,但是如果判斷在,那麼有可能不在。

常見應用場景

hyperloglog是一種概率性的去重計數數據結構,可以實現一定精度的去重計數

常見應用場景

geohash可以實現距離計算、距離查詢等地理位置相關的功能

常見應用場景

[image]10 PHP使用中,geohash用一個字元串表示經度和緯度兩個坐標。某些情況下無法

這問題我記得不太清楚了,只有一點點印象,當時我在後盾人學會的,~( ̄▽ ̄~)~最近不太用了,我給忘了(~_~;)放心吧,你在後盾人絕對可以學會的(´-ω-`)

有什麼方式可以取得到周邊有多少商戶

php可以獲取附近的商家。

操作方法如下:Shop表存儲欄位 Lat, Lng 現在使用方案為 通過 sql 語句進行距離的計算 之後 order by limit 進行分頁 但在SQL內進行計算,導致慢查詢. 目前 有兩種方案

A方案 : 獲取用戶當前的經緯度 通過演算法找到每條記錄所在點的經緯度周圍的一個大概範圍,比方說正方形的四個點,然後使用mysql的空間計算

B方案 :通過 Geohash 演算法 算出附近的商家 前端通過介面獲取數據進行分頁,採用以上兩種方案時,均為一次性拉取出附近商戶的數據,之後進行距離的計算,根據距離的排序生成最終數組,此時數據分頁 應該採用 根據數組的索引 計算偏移量進行分頁的操作。

補充 同時要求 能夠根據城市 和 區域 進行搜索 用關係型資料庫的話,給經緯度加上索引。附近的演算法可以從經緯度入手,以用戶的經緯度(x,y)為基準,查詢的範圍為((x+/-),y(+/-)), 擴大搜索範圍就是對x y的範圍的加大。 使用ElasticSearch 或者 Solr之類支持空間的搜索引擎。 之前寫過相關的Demo: Django ElasticSearch Ionic 打造 GIS 移動應用 —— 架構設計

在java中,geohash怎麼計算周圍8點的字元串?如何獲得周圍8點的經緯度坐標?

Geohash的最簡單的解釋就是:將一個經緯度信息,轉換成一個可以排序,可以比較的字元串編碼

import java.io.File;

import java.io.FileInputStream;

import java.util.BitSet;

import java.util.HashMap;

public class Geohash {

private static int numbits = 6 * 5;

final static char[] digits = { ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’,

‘9’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘j’, ‘k’, ‘m’, ‘n’, ‘p’,

‘q’, ‘r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’ };

final static HashMapCharacter, Integer lookup = new HashMapCharacter, Integer();

static {

int i = 0;

for (char c : digits)

lookup.put(c, i++);

}

public static void main(String[] args) throws Exception{

System.out.println(new Geohash().encode(45, 125));

}

public double[] decode(String geohash) {

StringBuilder buffer = new StringBuilder();

for (char c : geohash.toCharArray()) {

int i = lookup.get(c) + 32;

buffer.append( Integer.toString(i, 2).substring(1) );

}

BitSet lonset = new BitSet();

BitSet latset = new BitSet();

//even bits

int j =0;

for (int i=0; i numbits*2;i+=2) {

boolean isSet = false;

if ( i buffer.length() )

isSet = buffer.charAt(i) == ‘1’;

lonset.set(j++, isSet);

}

//odd bits

j=0;

for (int i=1; i numbits*2;i+=2) {

boolean isSet = false;

if ( i buffer.length() )

isSet = buffer.charAt(i) == ‘1’;

latset.set(j++, isSet);

}

double lon = decode(lonset, -180, 180);

double lat = decode(latset, -90, 90);

return new double[] {lat, lon};

}

private double decode(BitSet bs, double floor, double ceiling) {

double mid = 0;

for (int i=0; ibs.length(); i++) {

mid = (floor + ceiling) / 2;

if (bs.get(i))

floor = mid;

else

ceiling = mid;

}

return mid;

}

public String encode(double lat, double lon) {

BitSet latbits = getBits(lat, -90, 90);

BitSet lonbits = getBits(lon, -180, 180);

StringBuilder buffer = new StringBuilder();

for (int i = 0; i numbits; i++) {

buffer.append( (lonbits.get(i))?’1′:’0′);

buffer.append( (latbits.get(i))?’1′:’0′);

}

return base32(Long.parseLong(buffer.toString(), 2));

}

private BitSet getBits(double lat, double floor, double ceiling) {

BitSet buffer = new BitSet(numbits);

for (int i = 0; i numbits; i++) {

double mid = (floor + ceiling) / 2;

if (lat = mid) {

buffer.set(i);

floor = mid;

} else {

ceiling = mid;

}

}

return buffer;

}

public static String base32(long i) {

char[] buf = new char[65];

int charPos = 64;

boolean negative = (i 0);

if (!negative)

i = -i;

while (i = -32) {

buf[charPos–] = digits[(int) (-(i % 32))];

i /= 32;

}

buf[charPos] = digits[(int) (-i)];

if (negative)

buf[–charPos] = ‘-‘;

return new String(buf, charPos, (65 – charPos));

}

}

import java.io.File;

import java.io.FileInputStream;

import java.util.BitSet;

import java.util.HashMap;

public class Geohash {

private static int numbits = 6 * 5;

final static char[] digits = { ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’,

‘9’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘j’, ‘k’, ‘m’, ‘n’, ‘p’,

‘q’, ‘r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’ };

final static HashMapCharacter, Integer lookup = new HashMapCharacter, Integer();

static {

int i = 0;

for (char c : digits)

lookup.put(c, i++);

}

public static void main(String[] args) throws Exception{

System.out.println(new Geohash().encode(45, 125));

}

public double[] decode(String geohash) {

StringBuilder buffer = new StringBuilder();

for (char c : geohash.toCharArray()) {

int i = lookup.get(c) + 32;

buffer.append( Integer.toString(i, 2).substring(1) );

}

BitSet lonset = new BitSet();

BitSet latset = new BitSet();

//even bits

int j =0;

for (int i=0; i numbits*2;i+=2) {

boolean isSet = false;

if ( i buffer.length() )

isSet = buffer.charAt(i) == ‘1’;

lonset.set(j++, isSet);

}

//odd bits

j=0;

for (int i=1; i numbits*2;i+=2) {

boolean isSet = false;

if ( i buffer.length() )

isSet = buffer.charAt(i) == ‘1’;

latset.set(j++, isSet);

}

double lon = decode(lonset, -180, 180);

double lat = decode(latset, -90, 90);

return new double[] {lat, lon};

}

private double decode(BitSet bs, double floor, double ceiling) {

double mid = 0;

for (int i=0; ibs.length(); i++) {

mid = (floor + ceiling) / 2;

if (bs.get(i))

floor = mid;

else

ceiling = mid;

}

return mid;

}

public String encode(double lat, double lon) {

BitSet latbits = getBits(lat, -90, 90);

BitSet lonbits = getBits(lon, -180, 180);

StringBuilder buffer = new StringBuilder();

for (int i = 0; i numbits; i++) {

buffer.append( (lonbits.get(i))?’1′:’0′);

buffer.append( (latbits.get(i))?’1′:’0′);

}

return base32(Long.parseLong(buffer.toString(), 2));

}

private BitSet getBits(double lat, double floor, double ceiling) {

BitSet buffer = new BitSet(numbits);

for (int i = 0; i numbits; i++) {

double mid = (floor + ceiling) / 2;

if (lat = mid) {

buffer.set(i);

floor = mid;

} else {

ceiling = mid;

}

}

return buffer;

}

public static String base32(long i) {

char[] buf = new char[65];

int charPos = 64;

boolean negative = (i 0);

if (!negative)

i = -i;

while (i = -32) {

buf[charPos–] = digits[(int) (-(i % 32))];

i /= 32;

}

buf[charPos] = digits[(int) (-i)];

if (negative)

buf[–charPos] = ‘-‘;

return new String(buf, charPos, (65 – charPos));

}

}

Geohash原理

        GeoHash本質上是空間索引的一種方式,其基本原理是將地球理解為一個二維平面,將平面遞歸分解成更小的子塊,每個子塊在一定經緯度範圍內擁有相同的編碼。以GeoHash方式建立空間索引,可以提高對空間poi數據進行經緯度檢索的效率。

        GeoHash將二維的經緯度轉換成字元串,比如下圖展示了北京9個區域的GeoHash字元串,分別是WX4ER,WX4G2、WX4G3等等,每一個字元串代表了某一矩形區域。也就是說,這個矩形區域內所有的點(經緯度坐標)都共享相同的GeoHash字元串,這樣既可以保護隱私(只表示大概區域位置而不是具體的點),又比較容易做緩存。

        Geohash編碼中,字元串相似的表示距離相近(特殊情況後文闡述),這樣可以利用字元串的前綴匹配來查詢附近的POI信息。如下兩個圖所示,一個在城區,一個在郊區,城區的GeoHash字元串之間比較相似,郊區的字元串之間也比較相似,而城區和郊區的GeoHash字元串相似程度要低些。此外,不同的編碼長度,表示不同的範圍區間,字元串越長,表示的範圍越精確。

        以經緯度值:(116.389550, 39.928167)進行演算法說明,對緯度39.928167進行逼近編碼 (地球緯度區間是[-90,90]

    a. 區間[-90,90]進行二分為[-90,0),[0,90],稱為左右區間,可以確定39.928167屬於右區間[0,90],給標記為1

    b. 接著將區間[0,90]進行二分為 [0,45),[45,90],可以確定39.928167屬於左區間 [0,45),給標記為0

    c. 遞歸上述過程39.928167總是屬於某個區間[a,b]。隨著每次迭代區間[a,b]總在縮小,並越來越逼近39.928167

    d. 如果給定的緯度x(39.928167)屬於左區間,則記錄0,如果屬於右區間則記錄1,序列的長度跟給定的區間劃分次數有關,如下圖

        e. 同理,地球經度區間是[-180,180],可以對經度116.389550進行編碼。通過上述計算, 緯度產生的編碼為1 1 0 1 0 0 1 0 1 1 0 0 0 1 0,經度產生的編碼為1 0 1 1 1 0 0 0 1 1 0 0 0  1 1

        f. 合併:偶數位放經度,奇數位放緯度,把2串編碼組合生成新串如下圖

        g. 首先將11100 11101 00100 01111 0000  01101轉成十進位,對應著28、29、4、15,0,13 十進位對應的base32編碼就是wx4g0e,如下圖.

         h. 同理,將編碼轉換成經緯度的解碼演算法與之相反

        Geohash其實就是將整個地圖或者某個分割所得的區域進行一次劃分,由於採用的是base32編碼方式,即Geohash中的每一個字母或者數字(如wx4g0e中的w)都是由5bits組成(2^5 = 32,base32),這5bits可以有32中不同的組合(0~31),這樣我們可以將整個地圖區域分為32個區域,通過00000 ~ 11111來標識這32個區域。第一次對地圖劃分後的情況如下圖所示(每個區域中的編號對應於該區域所對應的編碼)。

        Geohash的0、1串序列是經度0、1序列和緯度0、1序列中的數字交替進行排列的,偶數位對應的序列為經度序列,奇數位對應的序列為緯度序列,在進行第一次劃分時,Geohash0、1序列中的前5個bits(11100),那麼這5bits中有3bits是表示經度,2bits表示緯度,所以第一次劃分時,是將經度劃分成8個區段(2^3 = 8),將緯度劃分為4個區段(2^2 = 4),這樣就形成了32個區域。如下圖

        同理,可以按照第一次劃分所採用的方式對第一次劃分所得的32個區域各自再次劃分。

        上文講了GeoHash的計算步驟,僅僅說明是什麼而沒有說明為什麼?為什麼分別給經度和維度編碼?為什麼需要將經緯度兩串編碼交叉組合成一串編碼?本節試圖回答這一問題。

        如圖所示,我們將二進位編碼的結果填寫到空間中,當將空間劃分為四塊時候,編碼的順序分別是左下角00,左上角01,右下腳10,右上角11,也就是類似於Z的曲線,當我們遞歸的將各個塊分解成更小的子塊時,編碼的順序是自相似的(分形),每一個子快也形成Z曲線,這種類型的曲線被稱為Peano空間填充曲線。

        這種類型的空間填充曲線的優點是將二維空間轉換成一維曲線(事實上是分形維),對大部分而言,編碼相似的距離也相近,但Peano空間填充曲線最大的缺點就是突變性,有些編碼相鄰但距離卻相差很遠,比如0111與1000,編碼是相鄰的,但距離相差很大。

        除Peano空間填充曲線外,還有很多空間填充曲線,如圖所示,其中效果公認較好是Hilbert空間填充曲線,相較於Peano曲線而言,Hilbert曲線沒有較大的突變。但是由於Peano曲線實現更加簡單,在使用的時候配合一定的解決手段,可以很好的滿足大部分需求,因此TD內部Geohash演算法採用的是Peano空間填充曲線。

    a. 由於GeoHash是將區域劃分為一個個規則矩形,並對每個矩形進行編碼,這樣在查詢附近POI信息時會導致以下問題,比如紅色的點是我們的位置,綠色的兩個點分別是附近的兩個餐館,但是在查詢的時候會發現距離較遠餐館的GeoHash編碼與我們一樣(因為在同一個GeoHash區域塊上),而較近餐館的GeoHash編碼與我們不一致。這個問題往往產生在邊界處。

        解決的思路很簡單,我們查詢時,除了使用定位點的GeoHash編碼進行匹配外,還使用周圍8個區域的GeoHash編碼,這樣可以避免這個問題。

    b. 我們已經知道現有的GeoHash演算法使用的是Peano空間填充曲線,這種曲線會產生突變,造成了編碼雖然相似但距離可能相差很大的問題,因此在查詢附近餐館時候,首先篩選GeoHash編碼相似的POI點,然後進行實際距離計算。

    c. GeoHash Base32編碼長度與精度。可以看出,當geohash base32編碼長度為8時,精度在19米左右,而當編碼長度為9時,精度在2米左右,編碼長度需要根據數據情況進行選擇。

        理解了geohash演算法的基本原理之後,本節將介紹一個實際應用中常見的場景:計算圍欄範圍內所有的Geohash編碼。該場景封裝為函數可以表示如下:輸入組成圍欄的點經緯度坐標集合和指定的geohash長度,輸出一組geohash編碼。

        public static Set getHashByFence(List points, int length)

        具體演算法步驟如下:

1. 輸入圍欄點坐標集合List points和指定的geohash長度length

2. 計算圍欄的外包矩形的左上角和右下角坐標lat_min、lat_max、lng_min、lng_max

3. 根據lat_min、lat_max、lng_min、lng_max,計算外包矩形對角定點的距離d

4. 以外包矩形中心點為圓心,以d/2為半徑做一個圓,計算圓覆蓋範圍內的geohash

    4.1 獲取圓的外包矩形左上角和右下角定點坐標經緯度,存儲到double[] locs

    4.2 根據geohash字元長度計算該長度geohash編碼對應的經緯度間隔(latA,lngA)

    4.3 根據latA和lngA,計算出locs組成的矩形的左上角和右下角定點的經緯度,在geohash劃分的網格的索引(也就是第幾個),分別記為lat_min,lat_max,lng_min,lng_max

    4.4 計算lat_min,lat_max,lng_min,lng_max對應範圍內左右geohash的二進位編碼,然後將經緯度二進位編碼uncode為geohash字元編碼,保存為Set sets

5. 剔除sets中geohash編碼對應矩形的中心點不在points圍欄範圍內的geohash,得到最終的geohash結果集

原創文章,作者:ONJX,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/143028.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
ONJX的頭像ONJX
上一篇 2024-10-14 18:44
下一篇 2024-10-14 18:44

相關推薦

  • PHP和Python哪個好找工作?

    PHP和Python都是非常流行的編程語言,它們被廣泛應用於不同領域的開發中。但是,在考慮擇業方向的時候,很多人都會有一個問題:PHP和Python哪個好找工作?這篇文章將從多個方…

    編程 2025-04-29
  • PHP怎麼接幣

    想要在自己的網站或應用中接受比特幣等加密貨幣的支付,就需要對該加密貨幣擁有一定的了解,並使用對應的API進行開發。本文將從多個方面詳細闡述如何使用PHP接受加密貨幣的支付。 一、環…

    編程 2025-04-29
  • 使用PHP foreach遍歷有相同屬性的值

    本篇文章將介紹如何使用PHP foreach遍歷具有相同屬性的值,並給出相應的代碼示例。 一、基礎概念 在講解如何使用PHP foreach遍歷有相同屬性的值之前,我們需要先了解幾…

    編程 2025-04-28
  • PHP獲取301跳轉後的地址

    本文將為大家介紹如何使用PHP獲取301跳轉後的地址。301重定向是什麼呢?當我們訪問一個網頁A,但是它已經被遷移到了另一個地址B,此時若伺服器端做了301重定向,那麼你的瀏覽器在…

    編程 2025-04-27
  • PHP登錄頁面代碼實現

    本文將從多個方面詳細闡述如何使用PHP編寫一個簡單的登錄頁面。 1. PHP登錄頁面基本架構 在PHP登錄頁面中,需要包含HTML表單,用戶在表單中輸入賬號密碼等信息,提交表單後服…

    編程 2025-04-27
  • PHP與Python的比較

    本文將會對PHP與Python進行比較和對比分析,包括語法特性、優缺點等方面。幫助讀者更好地理解和使用這兩種語言。 一、語法特性 PHP語法特性: <?php // 簡單的P…

    編程 2025-04-27
  • PHP版本管理工具phpenv詳解

    在PHP項目開發過程中,我們可能需要用到不同版本的PHP環境來試驗不同的功能或避免不同版本的兼容性問題。或者我們需要在同一台伺服器上同時運行多個不同版本的PHP語言。但是每次手動安…

    編程 2025-04-24
  • PHP數組去重詳解

    一、array_unique函數 array_unique是php中常用的數組去重函數,它基於值來判斷元素是否重複,具體使用方法如下: $array = array(‘a’, ‘b…

    編程 2025-04-24
  • PHP導出Excel文件

    一、PHP導出Excel文件列寬調整 當我們使用PHP導出Excel文件時,有時需要調整單元格的列寬。可以使用PHPExcel類庫中的setWidth方法來設置單元格的列寬。下面是…

    編程 2025-04-24
  • php擴展庫初探

    一、什麼是php擴展庫? PHP擴展庫(PHP extension)是一些用C語言編寫的動態鏈接庫,用於擴展PHP的功能。PHP擴展庫使得PHP可以與各種資料庫系統相連、SMTP、…

    編程 2025-04-23

發表回復

登錄後才能評論