قبل ذلك ، أثناء عملية البحث في المحتوى المستندة إلى Lucene ، علمت أنه يمكن لوكين استرداد معلومات النص والمعلومات العددية ، ويبدو أن المسافة المكانية يتم تنفيذها في الكود المصدري. في الأشهر الستة الماضية ، تلامس SOLR ، الذي يحتوي على بحث عن المسافة المكانية (خط العرض وخط الطول). لقد تعلمت مؤخرًا عن التنفيذ وتعلمت أن هناك تقنية شائعة نسبيًا لتحقيق البحث عن المسافة المكانية - Geohash. اسمحوا لي أن أقدم geohash أدناه.
ميزات geohash
لذلك ، عندما نقوم بالبحث عن المسافة ، نحتاج فقط إلى بادئة مطابقة Geohash. يتم تقديم الأسباب المحددة لاحقًا.
مبدأ Geohash
إن أبسط تفسير لـ GeoHash هو تحويل معلومات الموقع إلى ترميز سلسلة قابل للفرز وقابل للمقارنة. تم وصف عملية التنفيذ التالية بالتفصيل أدناه:
أولاً ، نقسم خط العرض (-90 ، 90) إلى فترتين (-90 ، 0) و (0 ، 90). إذا كانت قيمة خط العرض لموضع الإحداثيات في الفاصل الأول ، فإن الترميز هو 0 ، وإلا فإن الترميز هو 1. نستخدم 40.222012 كمثال. نظرًا لأن 40.222012 ينتمي إلى (0 ، 90) ، فإن الترميز هو 1. ثم نستمر في تقسيم (0 ، 90) إلى فترتين (0 ، 45) و (45 ، 90) ، بينما يقع 40.222012 في (0 ، 45) ، وبالتالي فإن الترميز هو 0 ، وما إلى ذلك. قمنا بتقسيم 20 مرة ، وأخيراً نحسب أن ترميز 40.222012 هو 10111001001101000110.
يتم استخدام نفس الطريقة من أجل خط الطول ، ويتم الحصول على ترميز 116.248283 كـ 11010010101010101010101010101010101010101010101.
بعد ذلك ، ندمج ترميزات خط العرض وخط الطول. الرقم الفردي هو خط العرض وحتى العدد هو خط الطول. الترميز الناتج هو 111001110100100110011111111111100110011110011001110110110 (إنه يحتاج إلى اهتمام خاص هنا ، والرقم الفردي والرقم المذكور هنا هو الاشتراكات في مجموعة القيمة ، بدءًا من 0) ؛
أخيرًا ، يتم تشفير BASE32. العشرية المقابلة للسلسلة الثنائية هي 28 ، 29 ، 4 ، 24 ، 27 ، 6 ، 1 ، 22 ، على التوالي. التحويل إلى BASE32 هو WX4SV61Q ، لذلك (40.222012 ، 116.248283) يتم تشفيرها على أنها WX4SV61Q. (الشكل التالي يقدم مراسلات BASE32)
الموقع المقابل للرمز WX4SV61Q على الخريطة هو كما يلي:
هنا ، طول ترميز Geohash لدينا هو 8 ، والدقة 19 متر. يسرد الجدول التالي الدقة المقابلة لأطوال الترميز المختلفة:
من الدقة أعلاه ، يمكننا أن نرى أنه إذا كنت ترغب في تحديد عنصر على بعد كيلومتر واحد (40.222012 ، 116.248283) ، نحتاج فقط إلى العثور على الجيوهاش المقابل لإحداثيات العنصر مع WX4SV كبادئة.
امتداد Geohash
حتى الآن لدينا فهم معين للمؤشرات المكانية ، لكن المقدمة أعلاه لا يمكنها تحقيق أحد المواقف التالية:
يمكننا أن نرى من الشكل أن النقطة الحمراء أقرب إلى النقطة الخضراء أعلاه وأبعد من النقطة الخضراء أدناه ، ولكن النقطة الحمراء هي نفس السلسلة المشفرة للنقطة الخضراء أدناه ، وكلاهما WX4G0. فكرة حل مشاكل الحدود مثل geohash بسيطة للغاية. عندما نبحث أو نستفسر ، فإننا نطابق المناطق الثمانية المحيطة ، والتي يمكن أن تحل مشكلة الحدود بشكل جيد. بعد ذلك ، سنقوم بتنفيذ Geohash في Java.
تنفيذ جافا
قبل التنفيذ ، نقوم أولاً بتحديد موقع الموقع ونستخدمه لتمثيل معلومات الطول والعرض:
/ ***@الوصف: تخزين معلومات الطول والعرض*/ package com.lulei.geo.bean ؛ الطبقة العامة locationbean {public static final double minlat = -90 ؛ Maxlat النهائي الثابت العام = 90 ؛ استاتيكي عام مزدوج minlng = -180 ؛ Maxlng النهائي الثابت العام = 180 ؛ private double lat ؛ // latitude [-90،90] private double lng ؛ // loncitude [-180،180] public locationbean (double lat ، double lng) {this.lat = lat ؛ this.lng = lng ؛ } public double getlat () {return lat ؛ } public void setlat (double lat) {this.lat = lat ؛ } public double getlng () {return lng ؛ } public void setlng (double lng) {this.lng = lng ؛ }} ثم نكتب فصلًا لتنفيذ Geohash. في عملية تنفيذ geohash ، نحتاج إلى تحديد بعض الثوابت ومعلومات خطوط الطول والطول ، على النحو التالي:
الطبقة العامة geohash {private locationbean موقع ؛ /** * 1 2500km ؛ 2 630km ؛ 3 78km ؛ 4 30km * 5 2.4km ؛ 6 610 م ؛ 7 76 م ؛ 8 19m */ private int henglength = 8 ؛ // يتم تحويل خطوط الطول والعرض إلى Geohash طول private intlength = 20 ؛ // يتم تحويل خطوط الطول والطول إلى طول ثنائي int lnglength = 20 ؛ // يتم تحويل خط الطول والخط الطول إلى minlat ذات الطول الثنائي الخاص ؛ // حجم الوحدة لكل خطوط خطوط عرضية مزدوجة من الشبكة ؛ // يتم انهيار كل من خط الطول ، char char [] char [] {0 '، 1' ، '،' 2 ، '2 ،' ، '، "،" ، "،" ، "،" ، "،" ، "،" ، ". 'f' ، 'g' ، 'h' ، 'j' ، 'k' ، 'm' ، 'n' ، 'p' ، 'q' ، 'r' ، 's' ، 't' ، 'u' ، 'v' ، 'w' ، 'x' ، y '،' z '} ؛ } عند إنشاء إنشاء geohash ، نحتاج إلى تعيين بعض الخصائص:
geohash العامة (double lat ، double lng) {location = new locationbean (lat ، lng) ؛ setMinlatlng () ؛ } public int gethashlength () {return hashlength ؛ } / *** author: lulei* description: قم بتعيين الحد الأدنى من Latitude and longitude* / private void setminlatlng () {minlat = locationbean.maxlat - locationbean.minlat ؛ لـ (int i = 0 ؛ i <latlength ؛ i ++) {minlat /= 2.0 ؛ } minlng = locationbean.maxlng - locationbean.minlng ؛ لـ (int i = 0 ؛ i <lnglength ؛ i ++) {minlng /= 2.0 ؛ }} عندما نستخدم geohash ، نحتاج إلى ضبط طول الترميز النهائي ، لذلك نكتب طريقة لتعيين طول geohash
sethashlength boolean public (طول int) {if (length <1) {return false ؛ } henglength = الطول ؛ latlength = (الطول * 5) / 2 ؛ if (length ٪ 2 == 0) {lnglength = latlength ؛ } آخر {lnglength = latlength + 1 ؛ } setMinLatlng () ؛ العودة صحيح. } مع هذه الإعدادات ، نحتاج إلى تحويل خط الطول والعرض إلى الترميزات الثنائية المقابلة
boolean private [] gethasharray (قيمة مزدوجة ، مزدوجة الدقيقة ، أقصى مزدوج ، طول int) {if (value <min || value> max) {return null ؛ } if (length <1) {return null ؛ } boolean [] النتيجة = New Boolean [length] ؛ لـ (int i = 0 ؛ i <length ؛ i ++) {double mid = (min+max) / 2.0 ؛ if (value> mid) {result [i] = true ؛ دقيقة = منتصف ؛ } آخر {result [i] = false ؛ الحد الأقصى = منتصف ؛ }} نتيجة الإرجاع ؛ } بعد الحصول على الترميز الثنائي لخطوط الطول والعرض على التوالي ، نحتاج إلى دمج سلاسل ثنائية في واحدة
BOOLEAN الخاص [] دمج (BOOLEAN [] LATARRAY ، BOOLEAN [] LNGARRAY) {if (latarray == null || lngarray == null) {return null ؛ } boolean [] result = new boolean [lngarray.length + latarray.length] ؛ المصفوفات. لـ (int i = 0 ؛ i <lngarray.length ؛ i ++) {result [2 * i] = lngarray [i] ؛ } لـ (int i = 0 ؛ i <latarray.length ؛ i ++) {result [2 * i+1] = latarray [i] ؛ } نتيجة الإرجاع ؛ } أخيرًا ، نحتاج إلى تحديد تحويل 32 من التحويل الثنائي الذي تم الحصول عليه
/ ** * param lat * param lng * regurn * author: lulei * description: احصل على سلسلة base32 من خط العرض وخط الطول */ سلسلة خاصة getGeohashBase32 (double lat ، double lng) {boolean [] bools = getGeobinary (lng ، lng) ؛ if (bools == null) {return null ؛ } StringBuffer SB = new StringBuffer () ؛ لـ (int i = 0 ؛ i <bools.length ؛ i = i + 5) {boolean [] base32 = new boolean [5] ؛ لـ (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: تحويل binary بخمسة بت إلى base32 * / private char getBase32Char (boolean [] base32) {if (base32 == null || base32.length! = 5) {return '' ؛ } int num = 0 ؛ لـ (Boolean Bool: base32) {num << = 1 ؛ if (bool) {num += 1 ؛ }} chars الإرجاع [num ٪ chars.length] ؛ } للحصول على مسألة كيفية الحصول على قيمة Geohash للمناطق الثمانية المحيطة ، يمكننا القيام بالتحول التالي. نحن نعرف بالفعل خطوط الطول والطول للنقطة الحالية ، ونعلم أيضًا عرض خط الطول والعرض في كل منطقة. إذا تمت إضافة خط الطول أو طرحه ، فيمكننا أن نكون موجودين على طول الطول اليسرى واليمين من المنطقة. إذا تمت إضافة خط العرض أو طرحه ، فيمكننا الحصول على خط عرض الأجزاء العلوية والسفلية من المنطقة ، حتى نتمكن من الحصول على إحداثيات نقطة في المناطق الثمانية المحيطة بالمنطقة. نقوم بحساب إحداثيات هذه النقاط الثمانية ، وهي رمز Geohash المقابل للمناطق الثمانية.
القائمة العامة <string> getGeoHashBase32For9 () {double leftlat = location.getlat () - minlat ؛ double rightlat = location.getlat () + minlat ؛ double uplng = location.getlng () - minlng ؛ Double Downlng = location.getlng () + minlng ؛ قائمة <Tring> base32for9 = new ArrayList <String> () ؛ . if (! (LAFTUP == NULL || "" .equals (Leftup))) {base32for9 } String LeftMid = getGeoHashBase32 (LeftLat ، location.getlng ()) ؛ if (! (LeftMid == null || "" .equals (LeftMid))) {base32for9.add (LeftMid) ؛ } string leftdown = getGeoHashBase32 (LeftLat ، downlng) ؛ if (! (LeftDown == null || "" .equals (LeftDown))) {base32for9 } // الأوتار الثلاثة في الوسط من أعلى إلى أسفل منتصف = getGeoHashBase32 (location.getlat () ، uplng) ؛ if (! (midup == null || "" .equals (midup))) {base32for9 } string midmid = getGeoHashBase32 (location.getlat () ، location.getlng ()) ؛ if (! (midmid == null || "" .equals (midmid))) {base32for9.add (midmid) ؛ } string middown = getGeoHashBase32 (location.getlat () ، downlng) ؛ if (! (middown == null || "" .equals (middown))) {base32for9 } // 3 سلاسل على اليمين من أعلى إلى أسفل اليمين = 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 ، location.getlng ()) ؛ if (! (rightmid == null || "" .equals (rightmid))) {base32for9.add (rightMid) ؛ } string levelddown = getGeoHashBase32 (rightlat ، downlng) ؛ if (! (levelddown == null || "" .equals (rightdown))) {base32for9 } return base32for9 ؛ } نتائج التشغيل
رمز كامل
يوجد بالفعل رمز LoacationBean الكامل في المدونة أعلاه ، لذلك لن أكتبه هنا.
/ ***@الوصف: geohash يدرك تحويل خط العرض وخط الطول*/ package com.lulei.geo ؛ استيراد java.util.arraylist ؛ استيراد java.util.arrays ؛ استيراد java.util.list ؛ استيراد com.lulei.geo.bean.locationBean ؛ استيراد com.lulei.util.jsonutil ؛ الطبقة العامة geohash {private locationbean موقع ؛ /** * 1 2500km ؛ 2 630km ؛ 3 78km ؛ 4 30km * 5 2.4km ؛ 6 610m ؛ 7 76 م ؛ 8 19m */ private int henglength = 8 ؛ // يتم تحويل خطوط الطول والعرض إلى Geohash طول private intlength = 20 ؛ // يتم تحويل خطوط الطول والطول إلى طول ثنائي int lnglength = 20 ؛ // يتم تحويل خطوط الطول والطول إلى مينلات مزدوجة ذات طول ثنائي ؛ // حجم وحدة كل خطوط عرضية مزدوجة minlng ؛ // سقطت من كل خط الطول الخاص الثابتة الثابتة char [] chars = {'0' ، '1' ، '2' ، '3' ، '4' ، '5 ،' 6 '،' 7 '،' 8 '،' 9 '،' b '،' c '،' q '،' q '،' q '،' q '، "،" ، 's' ، 't' ، 'u' ، 'v' ، 'w' ، 'x' ، 'y' ، 'z'} ؛ geohash العامة (double lat ، double lng) {location = new locationbean (lat ، lng) ؛ setMinlatlng () ؛ } public int gethashlength () {return hashlength ؛ } / *** author: lulei* description: قم بتعيين الحد الأدنى من Latitude and longitude* / private void setminlatlng () {minlat = locationbean.maxlat - locationbean.minlat ؛ لـ (int i = 0 ؛ i <latlength ؛ i ++) {minlat /= 2.0 ؛ } minlng = locationbean.maxlng - locationbean.minlng ؛ لـ (int i = 0 ؛ i <lnglength ؛ i ++) {minlng /= 2.0 ؛ }} / ** * return * @Author: lulei * description: ابحث عن نقطة الإحداثيات التسعة والنقاط المحيطة * / القائمة العامة <string> getGeohashBase32For9 () {double layslat = location.getlat () - minlat ؛ double rightlat = location.getlat () + minlat ؛ double uplng = location.getlng () - minlng ؛ Double Downlng = location.getlng () + minlng ؛ قائمة <Tring> base32for9 = new ArrayList <String> () ؛ . if (! (LAFTUP == NULL || "" .equals (Leftup))) {base32for9 } String LeftMid = getGeoHashBase32 (LeftLat ، location.getlng ()) ؛ if (! (LeftMid == null || "" .equals (LeftMid))) {base32for9.add (LeftMid) ؛ } string leftdown = getGeoHashBase32 (LeftLat ، downlng) ؛ if (! (LeftDown == null || "" .equals (LeftDown))) {base32for9 } // السلاسل الثلاثة من الأعلى إلى الأسفل في منتصف منتصف = getGeoHashBase32 (location.getlat () ، uplng) ؛ if (! (midup == null || "" .equals (midup))) {base32for9 } string midmid = getGeoHashBase32 (location.getlat () ، location.getlng ()) ؛ if (! (midmid == null || "" .equals (midmid))) {base32for9.add (midmid) ؛ } string middown = getGeoHashBase32 (location.getlat () ، downlng) ؛ if (! (middown == null || "" .equals (middown))) {base32for9 } // 3 سلاسل من أعلى إلى أسفل على الجانب الأيمن الأيمن = getGeoHashBase32 (rightlat ، uplng) ؛ if (! (rightup == null || "" .equals (rightup))) {base32for9.add (rightup) ؛ } string rightmid = getGeoHashBase32 (rightlat ، location.getlng ()) ؛ if (! (rightmid == null || "" .equals (rightmid))) {base32for9.add (rightMid) ؛ } string levelddown = getGeoHashBase32 (rightlat ، downlng) ؛ if (! (levelddown == null || "" .equals (rightdown))) {base32for9 } return base32for9 ؛ } / ** * @param length * return * author: lulei * description: اضبط خط العرض والطول على طول geohash * / sethashlength public (طول int) {if (length <1) {return false ؛ } henglength = الطول ؛ latlength = (الطول * 5) / 2 ؛ if (length ٪ 2 == 0) {lnglength = latlength ؛ } آخر {lnglength = latlength + 1 ؛ } setMinLatlng () ؛ العودة صحيح. } / ** * return * author: lulei * description: احصل على سلسلة Base32 من خط العرض والطول * / السلسلة العامة getGeohashBase32 () {return getGeoHashBase32 (location.getlat () ، location.getlng ()) ؛ } / ** * param lat * param lng * return * @author: lulei * description: احصل على سلسلة من خطوط الطول والعرض * / سلسلة خاصة getGeoHashBase32 (double lat ، double lng) {boolean [] bools = getGeobinary (lng) ؛ if (bools == null) {return null ؛ } StringBuffer SB = new StringBuffer () ؛ لـ (int i = 0 ؛ i <bools.length ؛ i = i + 5) {boolean [] base32 = new boolean [5] ؛ لـ (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: تحويل binary بخمسة بت إلى base32 * / private char getBase32Char (boolean [] base32) {if (base32 == null || base32.length! = 5) {return '' ؛ } int num = 0 ؛ لـ (Boolean Bool: base32) {num << = 1 ؛ if (bool) {num += 1 ؛ }} chars الإرجاع [num ٪ chars.length] ؛ } / ** * param lat * param lng * regurn * author: lulei * description: احصل على السلسلة الثنائية الجغرافية من الإحداثيات * / private boolean [] getGeObinary (double lat ، double lng) {boolean [] latarray = gethasharray (latbean.minlat ، littlater ، Boolean [] lngarray = gethasharray (lng ، locationbean.minlng ، locationbean.maxlng ، lnglength) ؛ Return Merge (Latarray ، Lngarray) ؛ } / ** * param latarray * param lngarray * regurn * author: lulei * description: دمج خط العرض والطول الثنائي * / boolean الخاص [] boolean [] latarray ، boolean [] lngarray) {if (latarray == null || } boolean [] result = new boolean [lngarray.length + latarray.length] ؛ المصفوفات. لـ (int i = 0 ؛ i <lngarray.length ؛ i ++) {result [2 * i] = lngarray [i] ؛ } لـ (int i = 0 ؛ i <latarray.length ؛ i ++) {result [2 * i+1] = latarray [i] ؛ } نتيجة الإرجاع ؛ } / ** * param value * param min * param max * return * author: lulei * description: تحويل الأرقام إلى سلسلة geohash الثنائية * / private boolean [] gethasharray (قيمة مزدوجة ، min double ، double max ، int) {if (value <min || value> max) {return null ؛ } if (length <1) {return null ؛ } boolean [] النتيجة = New Boolean [length] ؛ لـ (int i = 0 ؛ i <length ؛ i ++) {double mid = (min+max) / 2.0 ؛ if (value> mid) {result [i] = true ؛ دقيقة = منتصف ؛ } آخر {result [i] = false ؛ الحد الأقصى = منتصف ؛ }} نتيجة الإرجاع ؛ } main static void main (string [] args) {// todo method method method tuto geohash g = new geohash (40.222012 ، 116.248283) ؛ system.out.println (g.getgeohashbase32 ()) ؛ System.out.println (jsonutil.parsejson (g.getgeohashbase32for9 ())) ؛ }}ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.