Trong loạt bài viết này, tôi sẽ mô tả một số kỹ thuật cần thiết để truy vấn dữ liệu không gian địa lý trong MongoDB, điều này có thể hữu ích nếu bạn muốn ứng dụng hoặc API của mình cung cấp quyền truy cập vào thông tin được sắp xếp dựa trên khoảng cách từ một số vị trí cụ thể. Ví dụ
- kinh doanh [nhà hàng, cửa hàng vv. ] hoặc các địa điểm yêu thích khác,
- những người dùng khác của ứng dụng [như trong trường hợp ứng dụng hẹn hò]
Các kỹ thuật này sẽ cho phép dịch vụ của bạn mở rộng quy mô và duy trì hiệu quả vì chúng cho phép truy cập dữ liệu theo thời gian liên tục [bất kể lượng dữ liệu trong cơ sở dữ liệu của bạn là bao nhiêu] và giảm thiểu yêu cầu bộ nhớ đệm ở phía máy khách
Bạn sẽ học được gìTrong các phần sau tôi sẽ chỉ cho bạn cách
- lưu trữ dữ liệu vị trí [cặp kinh độ, vĩ độ] trong tài liệu MongoDB
- truy vấn các tài liệu đó bằng cách sử dụng dữ liệu vị trí, với các kết quả được sắp xếp theo khoảng cách từ một điểm cụ thể [từ gần nhất đến xa nhất]
- trang hiệu quả thông qua kết quả của các truy vấn như vậy
Bạn sẽ có thể làm theo hướng dẫn này ngay cả khi bạn chưa từng sử dụng MongoDB trước đây. Mặt khác, nếu 2 điểm đầu tiên nghe có vẻ quen thuộc, bạn có thể chuyển thẳng sang phần 3
Chúng tôi sẽ sử dụng Node. js và trình điều khiển Javascript MongoDB chính thức. Các đoạn mã sẽ có trong Coffeescript 2
Nếu bạn muốn chạy các ví dụ mã cục bộ, hãy sao chép repo đi kèm và làm theo hướng dẫn trong README.md
Đối với các cặp kinh độ, vĩ độ, chúng ta cần sử dụng định dạng đối tượng
doc
ở trên là những gì bạn muốn chèn vào bộ sưu tập mongo, nó có một trường có tên là location
có giá trị là một đối tượng Điểm GeoJSON. Tên của trường là không quan trọng, chúng tôi có thể đã sử dụng bất kỳ tên khóa hợp lệ nào khác thay vì location
. Chúng tôi cũng có thể lồng GeoJSON sâu hơn vào cấu trúc đối tượng, tuy nhiên, đặt type
và coordinates
ở cấp cao nhất của doc
sẽ không hoạt động. Ví dụ, cũng có thể lưu trữ nhiều đối tượng GeoJSON trong một tài liệu
Lưu ý rằng
- kinh độ đến trước [điều này ngược lại với những gì bạn có thể được sử dụng, ví dụ: truy vấn bản đồ google]
- giá trị kinh độ cần nằm trong khoảng từ -180 đến 180 [bao gồm cả hai]
- giá trị vĩ độ cần nằm trong khoảng từ -90 đến 90 [bao gồm cả hai]
Bây giờ bạn đã biết những điều cơ bản, hãy tạo một số dữ liệu để làm việc với
Chúng tôi đã tạo 6 tài liệu, với các vị trí bắt đầu từ đường xích đạo, tăng vĩ độ theo các khoảng thời gian bằng nhau, trong khi vẫn giữ cố định kinh độ ở 0. Dưới đây là các điểm được vẽ trên một hình cầu
Lưu ý rằng trong các ví dụ mã sau đây, chúng tôi sẽ bỏ qua bản soạn sẵn cần thiết để lấy đối tượng bộ sưu tập mongodb và chèn tài liệu vào đó. Trong repo đi kèm, bản tóm tắt này đã được chứa trong các hàm trợ giúp with_collection
và with_collection_with_points
Để truy vấn các tài liệu dựa trên khoảng cách của chúng từ một điểm cụ thể, chúng ta sẽ sử dụng toán tử truy vấn $near
. Nhưng trước khi làm điều này, chúng ta cần tạo một chỉ mục 2dsphere trên trường chứa các đối tượng Điểm GeoJSON
Đặt tùy chọn nền là rất quan trọng khi tạo chỉ mục trên cơ sở dữ liệu trực tiếp, vì theo mặc định sẽ chặn tất cả các hoạt động khác trên cơ sở dữ liệu trong khi chỉ mục đang được tạo [có thể mất một lúc nếu bộ sưu tập lớn]
Bây giờ, truy vấn cơ bản, trả về tất cả tài liệu được sắp xếp theo khoảng cách [ chính xác là khoảng cách vòng tròn lớn ] từ điểm doc
0, dựa trên dữ liệu tại trường location
trong tài liệu
Trừ khi ý định của bạn là xử lý tất cả các tài liệu trong bộ sưu tập [ trong trường hợp đó, bạn có thể gọi là ____1_______2 thay vì ____1_______3 ], bạn sẽ muốn giới hạn số lượng tài liệu được trả lại. Đây là cách nó được thực hiện
ví dụ có thể chạy được trên githubTrong hình minh họa bên dưới, điểm màu trắng đánh dấu vị trí được sử dụng trong truy vấn trên [ doc
0 ] và các điểm được khoanh tròn màu xanh lá cây biểu thị kết quả của truy vấn với doc
5 được đặt thành doc
6
xem mã được sử dụng để tạo mã này trên Phân trang JSFiddle thông qua các kết quả, từ gần nhất đến xa nhất [chuyển tiếp]
Ghi chú. kỹ thuật được thảo luận trong phần này đã được mô tả trước đây bởi A. Jesse Jiryu Davis, người đã triển khai tính năng MondoDB giúp cho kỹ thuật này trở nên khả thi. Bài viết của anh ấy đi vào chi tiết về lý do tại sao phương pháp này hoạt động hiệu quả, vì vậy nó đáng để đọc, tuy nhiên các ví dụ về mã là trong python, do đó, chúng tôi sẽ thực hiện từng bước tại đây vì lợi ích của Node. cộng đồng js
Dựa trên truy vấn mà chúng tôi đã xác định trong phần trước, cách đơn giản nhất để triển khai phân trang là sử dụng doc
5 để kiểm soát kích thước trang/lô và phương pháp doc
8 để đặt độ lệch trang mong muốn. Ví dụ
Truy vấn trên sẽ trả về trang kết quả thứ 2 khi truy vấn dữ liệu thử nghiệm của chúng tôi, như minh họa bên dưới
xem mã được sử dụng để tạo mã này trên JSFiddle
Tuy nhiên, hiệu suất của doc
9 giảm tuyến tính khi phần bù tăng [ như đã trình bày trong bài viết đã đề cập ở trên ], do máy chủ MongoDB cần quét qua tất cả các kết quả truy vấn ngay từ đầu cho đến khi đạt được phần bù
Một giải pháp thay thế thời gian không đổi liên quan đến việc sử dụng toán tử truy vấn location
0, để loại trừ các kết quả nằm trong bán kính nhất định khỏi điểm truy vấn
Giả sử rằng chúng ta biết khoảng cách giữa điểm truy vấn và tài liệu xa nhất từ một trang kết quả nhất định, chúng ta có thể truy vấn trang tiếp theo như sau
Nhưng chúng ta lấy khoảng cách từ đâu? . Hoặc sử dụng một cái gì đó như location
1. Tuy nhiên, chúng tôi phải đảm bảo rằng triển khai đã chọn của chúng tôi phù hợp với triển khai được sử dụng bởi MongoDB. May mắn thay, chúng tôi không phải trải qua tất cả những rắc rối đó vì chúng tôi có thể yêu cầu MongoDB đính kèm khoảng cách được tính toán động cho từng tài liệu trong kết quả truy vấn. Chúng tôi sẽ chỉ phải chuyển đổi truy vấn location
2 của mình thành một quy trình tổng hợp tương đương, sử dụng giai đoạn $geoNear với tùy chọn location
3 của nó
Bây giờ chúng ta có thể sử dụng thuộc tính location
4 của tài liệu cuối cùng từ kết quả truy vấn [vì chúng được sắp xếp theo khoảng cách, theo thứ tự tăng dần], để tìm nạp trang tiếp theo. Hãy sử dụng chức năng trên để tìm nạp hai trang đầu tiên
Và đây là hình ảnh trực quan về kết quả của lệnh gọi thứ hai tới location
5, với vòng tròn màu vàng bao quanh khu vực bị loại trừ khỏi truy vấn bằng cách sử dụng location
6
xem mã được sử dụng để tạo mã này trên JSFiddle
Tuy nhiên, đây không phải là chính xác những gì chúng tôi muốn. tài liệu cuối cùng của trang trước được đưa vào trang tiếp theo, vì location
6 chỉ loại trừ tài liệu, khoảng cách nào nhỏ hơn giá trị đã cho. Để ngăn chặn điều này, chúng tôi cần thêm truy vấn sau vào giai đoạn tổng hợp location
8 của chúng tôi
Truy vấn sử dụng toán tử location
9 để bỏ qua các tài liệu với các ____2_______0 đã cho khi thu thập kết quả. Chúng ta xong chưa? . Xét tập hợp các điểm sau
Lưu ý rằng điểm thứ 2 và thứ 3 nằm ở cùng một khoảng cách chính xác từ điểm doc
0. Bây giờ, nếu chúng ta đặt location
2 thành doc
0 và location
4 thành location
5, thì kết quả tìm nạp trang thứ hai sẽ như thế nào?
xem mã được sử dụng để tạo mã này trên JSFiddle
Điều này là do cả location
6 và location
8 đều không loại trừ tài liệu có tọa độ location
9. Do đó, thay vì chỉ sử dụng location
0 của tài liệu cuối cùng, chúng ta cần thu thập location
0 của tất cả các tài liệu có khoảng cách bằng khoảng cách của type
2. Để tất cả chúng cùng nhau
Đưa ra type
3, đây là cách bạn tìm nạp cái tiếp theo
Lưu ý rằng logic trong type
4 và type
5 rất có thể sẽ được thực thi trên máy khách, do đó nó không được đưa vào hàm location
5, hàm dự kiến _______4_______7 và type
8 được tính toán trước sẽ được chuyển vào. Điều này là để giảm thiểu lượng dữ liệu được gửi qua dây. Ngoài ra, máy chủ có thể bao gồm Tiêu đề liên kết HTTP với tất cả thông tin cần thiết để tìm nạp trang tiếp theo, trong trường hợp đó, tất cả mã trên sẽ được thực thi trên máy chủ
Cuối cùng, chúng ta cần xử lý trường hợp có quá nhiều tài liệu có cùng khoảng cách, đến nỗi tài liệu cuối cùng trong một trang có cùng khoảng cách với type
8 được sử dụng để tìm nạp trang đó. Ví dụ: khi tìm nạp trang thứ 3 từ tập hợp các điểm sau [ với coordinates
0 và coordinates
1 ]
Sử dụng triển khai ở trên, chúng tôi sẽ nhận được điểm coordinates
2 thay vì coordinates
3 mong muốn, vì location
0 của điểm cuối cùng từ trang 1 [ location
6 ], không bị loại trừ mặc dù khoảng cách của nó giống như location
6. Do đó, trong những trường hợp như vậy, chúng ta cần chuyển type
7 từ truy vấn trước sang truy vấn tiếp theo
Điều đáng chú ý là trong trường hợp cực đoan, tất cả các tài liệu có cùng khoảng cách, kích thước của mảng type
7, và do đó, lượng dữ liệu được gửi qua dây theo từng yêu cầu, sẽ tăng tuyến tính khi chúng tôi chuyển qua các trang. Trên hết, bản thân hiệu suất truy vấn sẽ giảm theo cách tương tự [và tôi cá là nó sẽ tệ hơn khi triển khai ngây thơ chỉ dựa vào doc
9]. Để giảm thiểu điều này, thay vì tích lũy type
7, chúng tôi có thể giữ số lượng tài liệu để bỏ qua [và sử dụng nó cùng với location
6]. Điều này sẽ giữ cho kích thước của yêu cầu không đổi và hiệu suất truy vấn nằm trong giới hạn của giải pháp doc
9 đơn giản. Tuy nhiên, việc theo dõi các location
0 để loại trừ có một số lợi thế so với việc sử dụng bỏ qua trong các trường hợp dữ liệu được truy vấn có tính động cao [ khi các thay đổi dự kiến sẽ xảy ra giữa các lần tìm nạp], mà chúng ta có thể thảo luận trong các bài viết sau
Cuối cùng, tôi muốn bạn lưu ý rằng phương pháp trên không hỗ trợ chuyển trực tiếp đến một trang cụ thể [ mà không tìm nạp tất cả các trang ở giữa ]. Tuy nhiên, điều này có thể đạt được bằng cách thêm doc
9 [ bằng doc
5 ] thích hợp vào truy vấn. Việc sử dụng doc
9 rõ ràng sẽ bị phạt về hiệu suất tỷ lệ thuận với số lượng tài liệu bị bỏ qua, nhưng thực sự không có cách nào khác [trừ khi thay vì chỉ định số lượng trang/tài liệu cần bỏ qua, trường hợp sử dụng của bạn có thể thực hiện bằng cách sử dụng location
6 để bỏ qua một mục không xác định . May mắn thay, chúng tôi chỉ cần trả mức giá này một lần cho mỗi lần nhảy trang, vì một khi trang mong muốn được tìm nạp bằng cách sử dụng doc
9, trang tiếp theo có thể được truy vấn dựa trên chỉ bằng cách sử dụng location
6 và type
7
Chúng tôi đã trình bày cách lưu trữ và truy vấn dữ liệu không gian địa lý trong mongodb và thảo luận cách chuyển trang hiệu quả qua lượng lớn dữ liệu đó [từ vị trí gần nhất đến vị trí xa nhất]. Trong Phần 2 của loạt bài này, bạn sẽ học cách sử dụng một thủ thuật tiện lợi để lật trang qua các vị trí theo thứ tự ngược lại
Đừng quên sao chép repo đi kèm và chơi với các ví dụ mã có thể dễ dàng chạy từ dòng lệnh