Antes, durante el proceso de búsqueda de contenido basado en Lucene, aprendí que Lucene puede recuperar información de texto e información numérica, y la distancia espacial parece implementarse en el código fuente. En los últimos seis meses, he entrado en contacto con Solr, que tiene una búsqueda de distancia espacial (latitud y longitud). Recientemente, aprendí sobre la implementación y aprendí que existe una tecnología relativamente común para realizar la búsqueda de distancia espacial: Geohash. Déjame presentar a Geohash a continuación.
Características de Geohash
Por lo tanto, cuando hacemos una búsqueda de distancia, solo necesitamos que el prefijo coincida con Geohash. Las razones específicas se introducen más adelante.
Principio de geohash
La explicación más simple de Geohash es convertir una información de ubicación en una codificación de cadenas ordenable y comparable. El siguiente proceso de implementación se describe en detalle a continuación:
Primero, dividimos la latitud (-90, 90) en dos intervalos (-90, 0) y (0, 90). Si el valor de latitud de la posición de coordenada está en el primer intervalo, la codificación es 0, de lo contrario, la codificación es 1. Usamos 40.222012 como ejemplo. Dado que 40.222012 pertenece a (0, 90), la codificación es 1. Luego continuamos dividiéndonos (0, 90) en dos intervalos (0, 45) y (45, 90), mientras que 40.222012 se encuentra en (0, 45), por lo que la codificación es 0, y así sucesivamente. Dividimos 20 veces y finalmente calculamos que la codificación de 40.222012 es 10111001001101000110.
El mismo método se utiliza para la longitud, y la codificación de 116.248283 se obtiene como 1101001010101010101010101010101010101010101010101.
A continuación, fusionamos los codificaciones de latitud y longitud. El número impar es la latitud y el número par es longitud. La codificación resultante es 11100111010010011000110110011011001100110110011000000110110 (necesita atención especial aquí, el número impar y el número pares mencionado aquí son los subíndices de la matriz de valor, a partir de 0);
Finalmente, Base32 está codificado. El decimal correspondiente a la cadena binaria es 28, 29, 4, 24, 27, 6, 1, 22, respectivamente. La conversión a Base32 es WX4SV61Q, por lo que (40.222012, 116.248283) está codificado como WX4SV61Q. (La siguiente figura introduce la correspondencia de Base32)
La ubicación correspondiente del código WX4SV61Q en el mapa es la siguiente:
Aquí, la longitud de codificación de nuestro Geohash es 8, y la precisión es de 19 metros. La siguiente tabla enumera la precisión correspondiente a diferentes longitudes de codificación:
De la precisión anterior, podemos ver que si desea seleccionar un elemento dentro de los 2 km de ME (40.222012, 116.248283), solo necesitamos encontrar el Geohash correspondiente a las coordenadas del artículo con WX4SV como prefijo.
Extensión de Geohash
Hasta ahora tenemos una cierta comprensión de los índices espaciales, pero la introducción anterior no puede lograr una de las siguientes situaciones:
Podemos ver en la figura que el punto rojo está más cerca del punto verde arriba y más lejos del punto verde a continuación, pero el punto rojo es la misma que la cadena codificada del punto verde a continuación, y es ambos wx4g0. La idea de resolver problemas límite como Geohash es muy simple. Cuando buscamos o consultamos, combinamos las ocho áreas circundantes, lo que puede resolver bien el problema de límite. A continuación, implementaremos Geohash en Java.
Implementación de Java
Antes de la implementación, primero definimos una ubicación y la usamos para representar la información de latitud y longitud:
/ ***@Descripción: almacenar información de latitud y longitud*/ paquete com.lulei.geo.bean; Public Class UbicatBean {public static final Double minlat = -90; Public estática final Double Maxlat = 90; Public estático final Doble minlng = -180; Público estático final Doble Maxlng = 180; Private Double Lat; // Latitud [-90,90] Private Double Lng; // Longitud [-180,180] Public UbicationBean (doble lat, doble lng) {this.lat = lat; this.lng = lng; } public doble getLat () {return lat; } public void setlat (doble lat) {this.lat = lat; } public doble getlng () {return lng; } public void setlng (doble lng) {this.lng = lng; }} Luego escribimos una clase para implementar Geohash. En el proceso de implementación de Geohash, necesitamos definir algunas constantes e información de latitud y longitud, de la siguiente manera:
clase pública Geohash {ubicación privada ubicación de BeBean; /** * 1 2500km; 2 630km; 3 78km; 4 30km * 5 2.4km; 6 610m; 7 76m; 8 19m */ private int hashlength = 8; // Latitud y longitud se convierten en longitud de geohash private int latlength = 20; // Latitud y longitud se convierten en longitud binaria privada int lnglength = 20; // Longitud y longitud se convierten en longitud binaria Minlat doble privada; // tamaño de unidad de cada latitud de cuadrícula Minlng doble privado; // caída de cada longitud se colapsan private estatic final char [] chars = {'0', '1', '2', '3', '4', '5', 6 ',' 7 ',' 8 ',' 9 ',' b ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ','. 'F', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; } Al instancias de Geohash, necesitamos asignar algunas propiedades:
public Geohash (doble lat, doble lng) {ubicación = nueva ubicaciónbean (lat, lng); setminlatlng (); } public int gethashLength () {return HashLength; } / *** @author: lulei* @description: establezca la unidad mínima de latitud y longitud* / private void setMinlatlng () {minlat = ubicación.maxlat - ubicación.minlat; para (int i = 0; i <latlength; i ++) {minlat /= 2.0; } minlng = ubicaciónbean.maxlng - ubicaciónbean.minlng; for (int i = 0; i <lnglength; i ++) {minlng /= 2.0; }} Cuando usamos Geohash, necesitamos establecer la longitud de codificación final, por lo que escribimos un método para establecer la longitud de Geohash
public boolean sethashlength (int long) {if (longitud <1) {return false; } hashlength = longitud; latlength = (longitud * 5) / 2; if (longitud % 2 == 0) {lnglength = latlength; } else {lnglength = latlength + 1; } setMinlatlng (); devolver verdadero; } Con estas configuraciones, necesitamos convertir la longitud y la latitud en las codificaciones binarias correspondientes
booleano privado [] gethasharray (valor doble, doble min, doble max, int longitud) {if (valor <min || valor> max) {return null; } if (longitud <1) {return null; } boolean [] resultado = new Boolean [longitud]; for (int i = 0; i <longitud; i ++) {double mid = (min+max) / 2.0; if (valor> mid) {resultado [i] = true; min = medio; } else {resultado [i] = false; max = Mid; }} Resultado de retorno; } Después de obtener la codificación binaria de latitud y longitud respectivamente, necesitamos fusionar dos cuerdas binarias en una
privado booleano [] fusion (boolean [] latArray, boolean [] lngarray) {if (latArray == null || lnGarray == null) {return null; } boolean [] resultado = new Boolean [lnGarray.length + latArray.length]; Arrays.fill (resultado, falso); para (int i = 0; i <lngarray.length; i ++) {resultado [2 * i] = lnGarray [i]; } para (int i = 0; i <LatRAray.length; i ++) {resultado [2 * i+1] = LatRAY [i]; } resultado de retorno; } Finalmente, necesitamos basar 32 la conversión de la conversión binaria obtenida
/ ** * @param lat * @param lng * @return * @author: lulei * @description: obtenga la cadena base32 de latitud y longitud */ cadena privada getGeoHashBase32 (doble lat, doble lng) {boolean [] bools = getGeObinary (lat, lng); if (bools == null) {return null; } StringBuffer sb = new StringBuffer (); para (int i = 0; i <bools.length; i = i + 5) {boolean [] base32 = nuevo booleano [5]; para (int j = 0; j <5; j ++) {base32 [j] = bools [i+j]; } char cha = getbase32char (base32); if ('' == cha) {return null; } sb.append (cha); } return sb.ToString (); } / ** * @param base32 * @return * @author: lulei * @description: convierte binario de cinco bits a base32 * / private char getbase32char (boolean [] base32) {if (base32 == null || base32.length! = 5) {return '; } int num = 0; para (boolean bool: base32) {num << = 1; if (bool) {num += 1; }} devuelve chars [num % chars longitud]; } Para la cuestión de cómo obtener el valor de Geohash de las ocho áreas circundantes, podemos hacer la siguiente transformación. Ya conocemos la latitud y la longitud del punto actual, y también conocemos el ancho de longitud y latitud en cada área. Si la longitud se agrega o se resta, podemos ubicarnos en la longitud de las áreas izquierda y derecha del área. Si la latitud se agrega o se resta, podemos obtener la latitud de las partes superior e inferior del área, para que podamos obtener las coordenadas de un punto en las ocho áreas alrededor del área. Calculamos las coordenadas de estos ocho puntos, que es el código Geohash correspondiente a las ocho áreas.
Lista pública <String> getGeoHashBase32for9 () {double leftlat = ubicación.getLat () - minlat; double rightlat = ubicación.getLat () + minlat; doble uplng = ubicación.getlng () - minlng; doble downlng = ubicación.getlng () + minlng; Lista <String> base32FOR9 = new ArrayList <String> (); // Las 3 cadenas en la izquierda = getGeoHashBase32 (LeftLat, Uplng); if (! (izquierda == null || "" .equals (izquierda))) {base32for9.Add (izquierda); } String LeftMid = getGeoHashBase32 (LeftLat, ubicación.getLng ()); if (! (LeftMid == NULL || "" .equals (LeftMid))) {Base32For9.Add (LeftMid); } String Leftdown = getGeoHashBase32 (LeftLat, Downlng); if (! (Leftdown == null || "" .equals (izquierda))) {base32FOR9.Add (Leftdown); } // Las 3 cadenas en el medio de la parte superior a la parte inferior midUp = getGeoHashBase32 (ubicación.getLat (), uplng); if (! (midUp == null || "" .equals (midUp))) {base32for9.Add (midUp); } String midmid = getGeoHashBase32 (ubicación.getLat (), ubicación.getLng ()); if (! (midmid == null || "" .equals (midmid))) {base32for9.add (midmid); } String Minddown = getGeoHashBase32 (ubicación.getLat (), downlng); if (! (mediado == null || "" .equals (mediado en la mitad)) {base32for9.Add (mediano down); } // 3 cadenas a la derecha de arriba a abajo a la derecha = getGeoHashBase32 (rightlat, uplng); if (! (rightUp == null || "" .equals (rightUp))) {base32for9.Add (rightUp); } String RightMid = getGeoHashBase32 (rightlat, uplng); if (! (rightUp == null || "" .equals (rightUp))) {base32for9.Add (rightUp); } String RightMid = getGeoHashBase32 (rightlat, ubicación.getlng ()); if (! (rightMid == null || "" .equals (rightmid))) {base32for9.add (rightmid); } String Rightdown = getGeoHashBase32 (rightlat, downlng); if (! (right down == null || "" .equals (right down)) {base32for9.Add (right down); } Base de retorno32for9; } Resultados de ejecución
Código completo
Ya hay un código completo de LoacationBean en el blog anterior, por lo que no lo escribiré aquí.
/ ***@Descripción: Geohash se da cuenta de la conversión de latitud y longitud*/ paquete com.lulei.geo; import java.util.arrayList; importar java.util.arrays; import java.util.list; import com.lulei.geo.bean.locationbean; import com.lulei.util.jsonutil; clase pública Geohash {ubicación privada ubicación de BeBean; /** * 1 2500km; 2 630km; 3 78km; 4 30km * 5 2.4km; 6 610m; 7 76m; 8 19m */ private int hashlength = 8; // Latitud y longitud se convierten en longitud de geohash private int latlength = 20; // Latitud y longitud se convierten en longitud binaria privada int lnglength = 20; // Latitud y longitud se convierten en longitud binaria Minlat privado privado; // tamaño de unidad de cada minlng doble privado de latitud; // caída de cada longitud privada estática final char [] chars = {'0', '1' ',' 2 ',' 3 ',' 4 ',' 5 ',' 6 ',' 7 ',' 8 ',' 9 ',' b ',' c ',' d ',' e ',' f ',' g ',' h ',' j ',' k ',' m ',' n ',' p ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', ',', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; public Geohash (doble lat, doble lng) {ubicación = nueva ubicaciónbean (lat, lng); setminlatlng (); } public int gethashLength () {return HashLength; } / *** @author: lulei* @description: establezca la unidad mínima de latitud y longitud* / private void setMinlatlng () {minlat = ubicación.maxlat - ubicación.minlat; para (int i = 0; i <latlength; i ++) {minlat /= 2.0; } minlng = ubicaciónbean.maxlng - ubicaciónbean.minlng; for (int i = 0; i <lnglength; i ++) {minlng /= 2.0; }} / ** * @return * @author: lulei * @Description: Encuentre el punto de coordenada nueve y los puntos circundantes * / public List <String> getGeoHashBase32for9 () {double LeftLat = Ubuse.getLat () - Minlat; double rightlat = ubicación.getLat () + minlat; doble uplng = ubicación.getlng () - minlng; doble downlng = ubicación.getlng () + minlng; Lista <String> base32FOR9 = new ArrayList <String> (); // Las 3 cadenas en la izquierda = getGeoHashBase32 (LeftLat, Uplng); if (! (izquierda == null || "" .equals (izquierda))) {base32for9.Add (izquierda); } String LeftMid = getGeoHashBase32 (LeftLat, ubicación.getLng ()); if (! (LeftMid == NULL || "" .equals (LeftMid))) {Base32For9.Add (LeftMid); } String Leftdown = getGeoHashBase32 (LeftLat, Downlng); if (! (Leftdown == null || "" .equals (izquierda))) {base32FOR9.Add (Leftdown); } // Las 3 cadenas de arriba a abajo en el medio midup = getGeoHashBase32 (ubicación.getLat (), uplng); if (! (midUp == null || "" .equals (midUp))) {base32for9.Add (midUp); } String midmid = getGeoHashBase32 (ubicación.getLat (), ubicación.getLng ()); if (! (midmid == null || "" .equals (midmid))) {base32for9.add (midmid); } String Minddown = getGeoHashBase32 (ubicación.getLat (), downlng); if (! (mediado == null || "" .equals (mediado en la mitad)) {base32for9.Add (mediano down); } // 3 cadenas de arriba a abajo en el lado derecho derecho = getGeoHashBase32 (rightlat, uplng); if (! (rightUp == null || "" .equals (rightUp))) {base32for9.Add (rightUp); } String RightMid = getGeoHashBase32 (rightlat, ubicación.getlng ()); if (! (rightMid == null || "" .equals (rightmid))) {base32for9.add (rightmid); } String Rightdown = getGeoHashBase32 (rightlat, downlng); if (! (right down == null || "" .equals (right down)) {base32for9.Add (right down); } Base de retorno32for9; } / ** * @param longitud * @return * @author: lulei * @Description: Establezca la latitud y la longitud en la longitud de Geohash * / public boolean sethashLength (int longitud) {if (longitud <1) {return false; } hashlength = longitud; latlength = (longitud * 5) / 2; if (longitud % 2 == 0) {lnglength = latlength; } else {lnglength = latlength + 1; } setMinlatlng (); devolver verdadero; } / ** * @return * @author: lulei * @description: obtenga la cadena base32 de latitud y longitud * / public string getGeoHashBase32 () {return getGeoHashBase32 (ubicación.getLat (), ubicación.getlng ()); } / ** * @param lat * @param lng * @return * @author: lulei * @description: obtenga la cadena base32 de latitud y longitud * / cadena privada getGeoHashBase32 (doble lat, doble lng) {boolean [] bools = getGeobinary (lat, lng); if (bools == null) {return null; } StringBuffer sb = new StringBuffer (); para (int i = 0; i <bools.length; i = i + 5) {boolean [] base32 = nuevo booleano [5]; para (int j = 0; j <5; j ++) {base32 [j] = bools [i+j]; } char cha = getbase32char (base32); if ('' == cha) {return null; } sb.append (cha); } return sb.ToString (); } / ** * @param base32 * @return * @author: lulei * @description: convierte binario de cinco bits a base32 * / private char getbase32char (boolean [] base32) {if (base32 == null || base32.length! = 5) {return '; } int num = 0; para (boolean bool: base32) {num << = 1; if (bool) {num += 1; }} devuelve chars [num % chars longitud]; } / ** * @param lat * @param lng * @return * @author: lulei * @Description: obtenga la cadena geo binaria de coordenadas * / private boolean [] getGeObinary (doble lat, doble lng) {boolean [] latArray = getHaShArray (lat, ubicación bean.minlat, ubicación.maxlat, lAteLean, lAteRAY, LATRAY boolean [] lngarray = gethasharray (lng, ubicaciónbean.minlng, ubicaciónbean.maxlng, lnglength); Regreso de fusión (Latray, Lngarray); } / ** * @param latArray * @param lngarray * @return * @author: lulei * @Description: fusionar latitud y longitud binary * / private boolean [] merge (boolean [] latArray, boolean [] lnGarray) {si (Lattary == null | } boolean [] resultado = new Boolean [lnGarray.length + latArray.length]; Arrays.fill (resultado, falso); para (int i = 0; i <lngarray.length; i ++) {resultado [2 * i] = lnGarray [i]; } para (int i = 0; i <LatRAray.length; i ++) {resultado [2 * i+1] = LatRAY [i]; } resultado de retorno; } / ** * @param value * @param min * @param max * @return * @author: lulei * @Description: Convertir números en geohash binary string * / private boolean [] gethasharray (valor doble, doble min, doble max, int long) {if (valor <min || valor> max) {return null; } if (longitud <1) {return null; } boolean [] resultado = new Boolean [longitud]; for (int i = 0; i <longitud; i ++) {double mid = (min+max) / 2.0; if (valor> mid) {resultado [i] = true; min = medio; } else {resultado [i] = false; max = Mid; }} Resultado de retorno; } public static void main (string [] args) {// TODO Método generado por auto Geohash g = nuevo Geohash (40.222012, 116.248283); System.out.println (g.getGeoHashBase32 ()); System.out.println (jsonutil.parsejson (g.getGeohashbase32for9 ())); }}Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.