ใช้ geohash สำหรับการค้นหาแบบใกล้ชิด?


30

ฉันกำลังมองหาการเพิ่มประสิทธิภาพการค้นหาจุดที่ใกล้เคียงกับจุดทางภูมิศาสตร์

ข้อมูลที่ฉันป้อนเป็น lat, lng point และฉันกำลังค้นหาสถานที่ที่กำหนดไว้ล่วงหน้าเป็นจุดที่ใกล้ที่สุด

ฉันไม่สนใจว่าจะใช้เวลา / พื้นที่ในการสร้างดัชนีสถานที่ที่คำนวณไว้ล่วงหน้าเท่าใด แต่ฉันสนใจว่าคำสั่งจะเร็วมาก

ฉันกำลังคิดเกี่ยวกับการใช้ geohash เป็นคีย์ค้นหาซึ่งฉันจะตรวจสอบก่อนว่าฉันได้รับผลลัพธ์สำหรับ X chars ของคีย์หรือไม่จากนั้นทำการตัดทอนตัวอักษรต่อจากจุดสิ้นสุดของแป้นจนกว่าฉันจะเห็นผลลัพธ์

สำหรับความเข้าใจของฉัน (กระจัดกระจายมาก) ของเทคนิคดัชนีทางภูมิศาสตร์วิธีการนี้ควรจะสามารถให้ผลลัพธ์ที่เร็วที่สุด (ในแง่ของเวลาแบบสอบถาม) เปรียบเทียบกับการนำไปใช้งานอื่น ๆ ที่รู้จักทั้งหมด (เช่น R Tree และ co.)


มีความแตกต่างที่สำคัญระหว่างการใช้ geohash และการจัดเก็บ lat / long ของคุณใน eastings / northings (ตัวอย่าง) หรือไม่? คุณสามารถเปลี่ยนความแม่นยำในการค้นหาของคุณได้ด้วยการตัดแต่งอักขระ / หลัก (นี่เป็นคำถามหมดจดจากความอยากรู้ - ฉันไม่คุ้นเคยกับหัวข้อนี้)
djq

ประเด็นเหล่านี้ถูกเก็บไว้ในฐานข้อมูลหรือในหน่วยความจำหรือ?
Marc Pfister

@MarcPfister ปัญหานี้มีอายุ 2 ปี (สำหรับกรณีการใช้งานของฉัน) แต่มันก็มีความเกี่ยวข้องกับชุมชนเสมอดังนั้นฉันจะดำเนินการอภิปรายต่อไป ข้อมูลที่ถูกกล่าวถึงนั้นถูกจัดเก็บในฐานข้อมูล nosql
Maxim Veksler

นอกจากนี้ฉันเชื่อว่านับจากเวลาที่คำถามนี้ได้รับการตอบ MongoDB ได้ดำเนินการจัดทำดัชนีและค้นหา geohash ซึ่งพิสูจน์จุดนี้ ฉันยังไม่เห็นกระดาษสีขาวของการใช้งาน แต่รหัสนั้นเปิดให้บริการและมีให้สำหรับทุกฝ่ายที่สนใจ
Maxim Veksler

อาโอเค. CouchDB ยังมีการสร้างดัชนีอวกาศตอนนี้อาจใช้ geohash ด้วย
Marc Pfister

คำตอบ:


25

คุณสามารถทำได้อย่างแน่นอน และสามารถทำได้ค่อนข้างเร็ว (บิตการคำนวณแบบเข้มข้นสามารถกระจายได้อีกด้วย)

มีหลายวิธี แต่วิธีหนึ่งที่ฉันใช้คือการใช้รายการสั่งซื้อของgeohashes ตามจำนวนเต็มและหาช่วง geohash เพื่อนบ้านที่ใกล้ที่สุดทั้งหมดสำหรับการแก้ปัญหา geohash เฉพาะ (ความละเอียดใกล้เคียงกับdistanceเกณฑ์ของคุณ) แล้ว สอบถามช่วง geohash เหล่านั้นเพื่อรับรายการของจุดที่ใกล้เคียง ฉันใช้ redis และ nodejs (เช่น. javascript) สำหรับสิ่งนี้ Redis เร็วสุดและสามารถดึงข้อมูลช่วงที่สั่งซื้อได้อย่างรวดเร็ว แต่มันไม่สามารถทำสิ่งต่างๆมากมายสำหรับการจัดการคิวรี่ดัชนีที่ฐานข้อมูล SQL สามารถทำได้

วิธีการดังกล่าวแสดงไว้ที่นี่: https://github.com/yinqiwen/ardb/wiki/Spatial-Index

แต่ส่วนสำคัญของมันคือ (การถอดความลิงก์):

  1. คุณเก็บคะแนน geohashed ทั้งหมดของคุณในความละเอียดที่ดีที่สุดที่คุณต้องการ (โดยปกติจะเป็นจำนวนเต็ม 64 บิตสูงสุดหากเข้าถึงได้หรือในกรณีของจาวาสคริปต์ 52 บิต) ในชุดที่สั่ง (เช่น zset เป็นสีแดง) ห้องสมุด Geohash ส่วนใหญ่ในวันนี้มีฟังก์ชั่นจำนวนเต็ม geohash และคุณจะต้องใช้สิ่งเหล่านี้แทนที่จะใช้ geohash base32 ทั่วไป
  2. จากรัศมีที่คุณต้องการค้นหาภายในคุณจะต้องค้นหาความลึก / ความละเอียดเล็กน้อยที่จะตรงกับพื้นที่การค้นหาของคุณและจะต้องน้อยกว่าหรือเท่ากับความลึกบิต geohash ที่คุณจัดเก็บ ไซต์ที่เชื่อมโยงนั้นมีตารางที่สัมพันธ์กับความลึกบิตของ geohash กับพื้นที่กล่องที่มีขอบเขตเป็นเมตร
  3. จากนั้นให้คุณปรับแต่งพิกัดเดิมของคุณด้วยความละเอียดที่ต่ำกว่านี้
  4. ที่ความละเอียดต่ำกว่านั้นยังพบว่าพื้นที่ geohash ของเพื่อนบ้านทั้งแปด (n, ne, e, se, s, sw, w, nw) เหตุผลที่คุณต้องทำวิธีเพื่อนบ้านเนื่องจากพิกัดสองอันที่อยู่ติดกันอาจมี geohash ที่แตกต่างกันโดยสิ้นเชิงดังนั้นคุณต้องทำการหาค่าเฉลี่ยของพื้นที่ที่ครอบคลุมโดยการค้นหา
  5. เมื่อคุณได้รับ geohashes เพื่อนบ้านทั้งหมดที่ความละเอียดต่ำกว่านี้ให้เพิ่มรายการ geohash ของพิกัดของคุณจากขั้นตอนที่ 3
  6. จากนั้นคุณต้องสร้างช่วงของค่า geohash เพื่อค้นหาภายในซึ่งครอบคลุม 9 พื้นที่เหล่านี้ ค่าจากขั้นตอนที่ 5 คือขีด จำกัด ช่วงล่างของคุณและหากคุณเพิ่ม 1 ลงในแต่ละค่าคุณจะได้รับขีด จำกัด ช่วงบนของคุณ ดังนั้นคุณควรมีอาร์เรย์ 9 ช่วงซึ่งแต่ละช่วงมีขีด จำกัด ล่างและขีด จำกัด บนของ geohash (รวม 18 geohash) Geohash เหล่านี้ยังอยู่ในความละเอียดที่ต่ำกว่าจากขั้นตอนที่ 2
  7. จากนั้นคุณแปลง geohashes ทั้ง 18 ตัวนี้ให้เป็นความลึก / ความละเอียดใด ๆ ก็ตามที่คุณเก็บ geohash ทั้งหมดของคุณไว้ในฐานข้อมูลของคุณโดยทั่วไปแล้วคุณทำสิ่งนี้โดย bithifting เป็นความลึกบิตที่ต้องการ
  8. ตอนนี้คุณสามารถทำแบบสอบถามแบบหาจุดภายในช่วงเหล่านี้และคุณจะได้รับคะแนนทั้งหมดประมาณภายในระยะทางจากจุดเดิมของคุณ จะไม่มีการทับซ้อนกันดังนั้นคุณไม่จำเป็นต้องทำการแยกใด ๆ เพียงแค่ค้นหาช่วงบริสุทธิ์อย่างรวดเร็ว (เช่นในสีแดง: ZRANGEBYSCORE zsetname lowerLimit upperLimit ตลอด 9 ช่วงที่ผลิตในขั้นตอนนี้)

คุณสามารถเพิ่มประสิทธิภาพ (ความเร็วฉลาด) นี้โดย:

  1. รับช่วง 9 จากขั้นตอนที่ 6 และค้นหาที่พวกเขานำไปสู่ซึ่งกันและกัน โดยปกติคุณสามารถลด 9 ช่วงแยกเป็นประมาณ 4 หรือ 5 ขึ้นอยู่กับตำแหน่งของพิกัด สิ่งนี้สามารถลดเวลาการสืบค้นของคุณลงครึ่งหนึ่ง
  2. เมื่อคุณมีช่วงสุดท้ายของคุณคุณควรถือพวกเขาเพื่อนำมาใช้ใหม่ การคำนวณช่วงเหล่านี้อาจใช้เวลาส่วนใหญ่ในการประมวลผลดังนั้นหากพิกัดเดิมของคุณไม่เปลี่ยนแปลงมากนัก แต่คุณต้องสร้างแบบสอบถามระยะทางเดียวกันซ้ำอีกครั้งคุณควรรักษาความพร้อมไว้แทนที่จะคำนวณทุกครั้ง
  3. หากคุณกำลังใช้ Redis ให้ลองรวมแบบสอบถามเข้าไปใน MULTI / EXEC เพื่อให้ระบบส่งไปป์ไลน์เพื่อประสิทธิภาพที่ดีขึ้นเล็กน้อย
  4. ส่วนที่ดีที่สุด: คุณสามารถแจกจ่ายขั้นตอนที่ 2-7 ให้กับลูกค้าได้แทนที่จะทำการคำนวณทั้งหมดในที่เดียว สิ่งนี้จะช่วยลดภาระของ CPU ในสถานการณ์ที่มีคำขอนับล้านเข้ามา

คุณสามารถปรับปรุงความแม่นยำเพิ่มเติมได้โดยใช้ฟังก์ชันระยะทางวงกลม / ประเภทแฮเวอรีนในผลลัพธ์ที่ส่งคืนถ้าคุณสนใจความแม่นยำมาก

นี่เป็นเทคนิคที่คล้ายกันโดยใช้ geohashes base32 สามัญและแบบสอบถาม SQL แทน redis: https://github.com/davetroy/geohash-js

ฉันไม่ได้ตั้งใจจะเสียบสิ่งของของตัวเอง แต่ฉันได้เขียนโมดูลสำหรับ nodejs & redis ซึ่งทำให้ง่ายต่อการใช้งาน ดูรหัสหากคุณต้องการ: https://github.com/arjunmehta/node-georedis


การติดตามสองสามข้อ - คุณคำนวณเพื่อนบ้านอย่างไร การแฮชจำนวนเต็มอนุญาตให้ตัด (ฐาน z-32 ไม่ได้เช่น (7 อยู่ไกลจาก 8 ใน base32 geohash) วิธีการที่อธิบายไว้ใน geohash-js github.com/davetroy/geohash-js/blob/ master / matrix.txtคล้ายกันหรือไม่ในขณะที่อัลกอริทึมนี้ควรสร้างจุด geo-points geohash-js ทำการคำนวณ O (1) ของเซลล์เพื่อนบ้านเท่านั้น
Maxim Veksler

ว้าวนี่มันมีประโยชน์มาก ความเชี่ยวชาญมากในการตอบสนองนี้ งานที่ท้าทายค่อนข้างมาก
simon

9

สามารถอ่านคำถามได้หลายวิธี ฉันแปลมันเพื่อหมายความว่าคุณมีจุดจำนวนมากและคุณตั้งใจที่จะสอบสวนพวกเขาซ้ำ ๆ ด้วยคะแนนตามอำเภอใจให้เป็นคู่ประสานงานและต้องการที่จะได้รับคะแนนที่ใกล้ที่สุดไปยังโพรบโดยมีการแก้ไขก่อนล่วงหน้า (โดยหลักการแล้วถ้า n จะแตกต่างกันคุณสามารถตั้งค่าโครงสร้างข้อมูลสำหรับทุก ๆ n ที่เป็นไปได้และเลือกในเวลา O (1) กับโพรบแต่ละตัว: นี่อาจใช้เวลาในการตั้งค่านานมากและต้องใช้ RAM จำนวนมาก แต่เรา ได้รับคำสั่งให้เพิกเฉยต่อข้อกังวลดังกล่าว)

สร้างไดอะแกรม Voronoi เพื่อทุกจุด พาร์ติชั่นนี้แบ่งเป็นระนาบที่เชื่อมต่อกันซึ่งแต่ละอันมีเพื่อนบ้าน n คนเดียวกัน สิ่งนี้จะช่วยลดสถานการณ์ให้เป็นปัญหาจุดในรูปหลายเหลี่ยมซึ่งมีวิธีแก้ปัญหาที่มีประสิทธิภาพมากมาย

การใช้โครงสร้างข้อมูลเวกเตอร์สำหรับแผนภาพ Voronoi การค้นหาจุดในรูปหลายเหลี่ยมจะใช้เวลา O (บันทึก (n)) สำหรับจุดประสงค์ในทางปฏิบัติคุณสามารถสร้าง O (1) ด้วยค่าสัมประสิทธิ์โดยนัยที่น้อยมากเพียงแค่สร้างไดอะแกรมรุ่นแรสเตอร์ ค่าของเซลล์ในแรสเตอร์เป็น (i) ตัวชี้ไปยังรายการของจุดที่ใกล้ที่สุด n หรือ (ii) การบ่งชี้ว่าเซลล์นี้เลาะเลียบไปสองส่วนหรือมากกว่าในแผนภาพ การทดสอบสำหรับจุดใด ๆ ที่ (x, y) จะกลายเป็น:

Fetch the cell value for (x,y).
If the value is a list of points, return it.
Else apply a vector point-in-polygon algorithm to (x,y).

เพื่อให้ได้ประสิทธิภาพ O (1) ตาข่ายแรสเตอร์จะต้องดีพอที่จุดโพรบไม่กี่จุดจะตกอยู่ในเซลล์ที่ข้ามเขต Voronoi หลายแห่ง สิ่งนี้สามารถทำได้โดยมีค่าใช้จ่ายที่สูงมากในการจัดเก็บสำหรับกริด


3

ฉันใช้ geohashes สำหรับสิ่งนี้ เหตุผลที่ฉันเป็นเพราะฉันต้องการใช้การค้นหาใกล้เคียงโดยใช้ระบบข้อมูลสไตล์ปิรามิด .. ที่ geohash ที่มีความแม่นยำระดับที่ 8 คือ 'ฐาน' และสร้างผลรวมใหม่สำหรับ geohash ของความแม่นยำที่ 7 .. และอื่น ๆ เป็นต้น . ยอดรวมเหล่านี้เป็นพื้นที่ประเภทของกราวด์ ฯลฯ มันเป็นวิธีแฟนซีที่จะทำสิ่งที่แฟนซีมาก

ดังนั้น Geohash ระดับที่ 8 จะมีข้อมูลเช่น:

ประเภท: หญ้าเอเคอร์: 1.23

และวันที่ 7, 6 .. ฯลฯ จะมีข้อมูลเช่น:

grass_types: 123 ไร่: 6502

สิ่งนี้สร้างขึ้นจากความแม่นยำต่ำสุดเสมอ ทำให้ฉันสามารถทำสถิติสนุก ๆ ได้ทุกประเภทอย่างรวดเร็ว ฉันยังสามารถกำหนดการอ้างอิงรูปทรงเรขาคณิตให้กับการอ้างอิง Geohash แต่ละรายการโดยใช้ GeoJSON

ฉันสามารถเขียนหลายฟังก์ชั่นเพื่อค้นหา geohash ที่ใหญ่ที่สุดซึ่งประกอบเป็นวิวพอร์ตปัจจุบันของฉันและใช้ฟังก์ชันเหล่านั้นเพื่อค้นหา geohash ของความแม่นยำที่ใหญ่เป็นอันดับสองในวิวพอร์ต สิ่งนี้สามารถขยายได้อย่างง่ายดายไปยังข้อความค้นหาช่วงที่มีการจัดทำดัชนีซึ่งฉันจะค้นหาอย่างน้อย '86ssaaaa' และสูงสุดของ '86sszzzz' สำหรับความแม่นยำที่ฉันต้องการ

ฉันทำสิ่งนี้โดยใช้ MongoDB


3

การอัปเดตสำหรับปี 2018 และการระดมทุนทางคณิตศาสตร์หรือแหล่งที่มาทางประวัติศาสตร์ของ Geohash:

  • แรงบันดาลใจสำหรับ Geohash เป็นinterlave ที่เรียบง่ายของตัวเลขไบนารีอาจจะเพิ่มประสิทธิภาพของขั้นตอนวิธีไร้เดียงสาว่าบรรณนิทัศน์ตัวเลขทศนิยมเช่นของ C-สี่เหลี่ยม

  • การพัวพันแบบไบนารีทำให้เกิดกลยุทธ์ดัชนีZ-order-curveตามธรรมชาตินักประดิษฐ์ Geohash ไม่ได้เริ่มต้น "มองหาเส้นโค้งเศษส่วนที่ดีที่สุด" ... แต่น่าแปลกใจว่าการเพิ่มประสิทธิภาพการออกแบบนี้เป็นเส้นโค้งเศษส่วนที่ดีกว่า

ใช้ S2 Geometry Library

วิธี S2-geometry นั้นดีกว่า Geohash เพราะมันใช้รูปร่างทรงกลมของโลก (ลูกบาศก์) ใช้การฉายภาพเสริม(เซลล์ทั้งหมดมีรูปร่างใกล้เคียงกันและพื้นที่ใกล้เคียง) และเนื่องจากการทำดัชนีด้วยHilbert-curveนั้นดีกว่าZ- คำสั่งโค้ง :

... เราทำได้ดีกว่า ... ความไม่ต่อเนื่องในขณะที่เราไปจากบนขวาไปล่างซ้ายของรูปสี่เหลี่ยมผลในเราต้องแยกบางช่วงที่เราสามารถทำอย่างต่อเนื่อง (... ) เราสามารถกำจัดความไม่ต่อเนื่องใด ๆ (... )
blog.notdot.net/2009 ในการจัดทำดัชนีเชิงพื้นที่ด้วย Quadtrees และ Hilbert Curves

ตอนนี้มันเป็นห้องสมุดฟรีและมีประสิทธิภาพดูhttps://s2geometry.io

PS: นอกจากนี้ยังมี (ดี) รุ่นที่ไม่เป็นทางการอย่างเป็นทางการของ NodeJSs2-geometryและ "สนามเด็กเล่น", Add-ins และการสาธิตมากมายเช่นs2.sidewalklabs.com s2.sidewalklabs.com


2

ฉันอยากจะแนะนำให้ใช้แบบสอบถาม GEORADIUS ในสีแดง

พุชข้อมูลที่ถูกทำลายโดยระดับ geohash ที่เหมาะสมที่สุดโดยใช้การโทร GEOADD

ดูที่นี่ด้วย -> ProximityHash

ProximityHash สร้างชุดของ geohash ที่ครอบคลุมพื้นที่วงกลมกำหนดพิกัดกลางและรัศมี นอกจากนี้ยังมีตัวเลือกเพิ่มเติมในการใช้ GeoRaptor ที่สร้างการผสมผสานที่ดีที่สุดของ geohashes ในระดับต่าง ๆ เพื่อเป็นตัวแทนของวงกลมเริ่มต้นจากระดับสูงสุดและวนซ้ำจนกว่าการผสมผสานที่ดีที่สุดจะถูกชง ความแม่นยำของผลลัพธ์ยังคงอยู่ในระดับเดียวกับระดับ geohash เริ่มต้น แต่ขนาดข้อมูลลดลงอย่างมากจึงช่วยเพิ่มความเร็วและประสิทธิภาพ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.