Một cách nhanh chóng và bẩn thỉu sử dụng một phân khu hình cầu đệ quy . Bắt đầu với một tam giác bề mặt trái đất, đệ quy chia từng tam giác từ một đỉnh ngang đến giữa cạnh dài nhất của nó. (Lý tưởng nhất là bạn sẽ chia hình tam giác thành hai phần có đường kính bằng nhau hoặc phần có diện tích bằng nhau, nhưng vì chúng liên quan đến một số tính toán khó hiểu, tôi chỉ chia hai bên chính xác một nửa: điều này khiến cho các hình tam giác khác nhau cuối cùng có kích thước khác nhau, nhưng điều đó dường như không quan trọng đối với ứng dụng này.)
Tất nhiên, bạn sẽ duy trì phân ngành này trong cấu trúc dữ liệu cho phép nhanh chóng xác định tam giác trong đó có bất kỳ điểm tùy ý nào. Cây nhị phân (dựa trên các lệnh gọi đệ quy) hoạt động độc đáo: mỗi lần chia tam giác, cây được chia tại nút của tam giác đó. Dữ liệu liên quan đến mặt phẳng phân tách được giữ lại, để bạn có thể nhanh chóng xác định phía nào của mặt phẳng bất kỳ điểm tùy ý nào: điều đó sẽ xác định việc di chuyển sang trái hay phải xuống cây.
(Tôi có nói là chia "mặt phẳng" không? Có - nếu mô hình bề mặt trái đất thành một hình cầu và sử dụng tọa độ địa tâm (x, y, z), thì hầu hết các tính toán của chúng tôi diễn ra theo ba chiều, trong đó các cạnh của hình tam giác là các mảnh của giao điểm của hình cầu với các mặt phẳng qua gốc của nó. Điều này làm cho việc tính toán nhanh chóng và dễ dàng.)
Tôi sẽ minh họa bằng cách hiển thị thủ tục trên một quãng tám của hình cầu; bảy quãng tám khác được xử lý theo cách tương tự. Một octant như vậy là một tam giác 90-90-90. Trong đồ họa của tôi, tôi sẽ vẽ các hình tam giác Euclide trải dài trên các góc giống nhau: chúng trông không đẹp cho đến khi chúng nhỏ lại, nhưng chúng có thể dễ dàng và nhanh chóng được vẽ. Đây là tam giác Euclide tương ứng với octant: đó là bắt đầu của thủ tục.
Vì tất cả các mặt có độ dài bằng nhau, một mặt được chọn ngẫu nhiên là "dài nhất" và được chia nhỏ:
Lặp lại điều này cho mỗi hình tam giác mới:
Sau n bước ta sẽ có 2 ^ n tam giác. Đây là tình huống sau 10 bước, hiển thị 1024 hình tam giác trong quãng tám (và 8192 trên tổng thể hình cầu):
Để minh họa thêm, tôi đã tạo một điểm ngẫu nhiên trong quãng tám này và đi trên cây phân khu cho đến khi cạnh dài nhất của tam giác đạt dưới 0,05 radian. Các tam giác (cartesian) được hiển thị với điểm thăm dò màu đỏ.
Ngẫu nhiên, để thu hẹp vị trí của một điểm đến một độ vĩ độ (xấp xỉ), bạn sẽ lưu ý rằng đây là khoảng 1/60 radian và do đó bao phủ khoảng (1/60) ^ 2 / (Pi / 2) = 1/6000 tổng bề mặt. Vì mỗi phân khu xấp xỉ một nửa kích thước tam giác, khoảng 13 đến 14 phân khu của octant sẽ thực hiện thủ thuật. Đó không phải là nhiều tính toán - như chúng ta sẽ thấy dưới đây - làm cho nó hiệu quả không phải là lưu trữ cây, mà là thực hiện phân chia một cách nhanh chóng. Lúc đầu, bạn sẽ lưu ý điểm nào nằm trong khoảng tám điểm - được xác định bởi các dấu của ba tọa độ của nó, có thể được ghi lại dưới dạng số nhị phân ba chữ số - và tại mỗi bước bạn muốn nhớ liệu điểm đó có nằm không ở bên trái (0) hoặc phải (1) của tam giác. Điều đó cho một số nhị phân 14 chữ số khác. Bạn có thể sử dụng các mã này để nhóm các điểm tùy ý.
(Nói chung, khi hai mã gần nhau là số nhị phân thực tế, các điểm tương ứng gần nhau; tuy nhiên, các điểm vẫn có thể gần nhau và có các mã khác nhau đáng kể. Ví dụ, hãy xem xét hai điểm cách nhau Xích đạo: mã của chúng phải khác nhau trước điểm nhị phân, bởi vì chúng ở các quãng tám khác nhau. Loại điều này là không thể tránh khỏi với bất kỳ phân vùng không gian cố định nào.)
Tôi đã sử dụng Mathicala 8 để thực hiện điều này: bạn có thể sử dụng nó như nguyên trạng hoặc mã giả để thực hiện trong môi trường lập trình yêu thích của bạn.
Xác định phía nào của mặt phẳng 0-ab điểm p nằm trên:
side[p_, {a_, b_}] := If[Det[{p, a, b}] >= 0, left, right];
Tinh chỉnh tam giác abc dựa trên điểm p.
refine[p_, {a_, b_, c_}] := Block[{sides, x, y, z, m},
sides = Norm /@ {b - c, c - a, a - b} // N;
{x, y, z} = RotateLeft[{a, b, c}, First[Position[sides, Max[sides]]] - 1];
m = Normalize[Mean[{y, z}]];
If[side[p, {x, m}] === right, {y, m, x}, {x, m, z}]
]
Hình cuối cùng được vẽ bằng cách hiển thị octant và trên hết, bằng cách hiển thị danh sách sau đây dưới dạng một tập hợp đa giác:
p = Normalize@RandomReal[NormalDistribution[0, 1], 3] (* Random point *)
{a, b, c} = IdentityMatrix[3] . DiagonalMatrix[Sign[p]] // N (* First octant *)
NestWhileList[refine[p, #] &, {a, b, c}, Norm[#[[1]] - #[[2]]] >= 0.05 &, 1, 16]
NestWhileList
liên tục áp dụng một thao tác ( refine
) trong khi một điều kiện liên quan (tam giác lớn) hoặc cho đến khi đạt được số lượng hoạt động tối đa (16).
Để hiển thị tam giác đầy đủ của octant, tôi bắt đầu với octant đầu tiên và lặp lại sàng lọc mười lần. Điều này bắt đầu với một sửa đổi nhỏ về refine
:
split[{a_, b_, c_}] := Module[{sides, x, y, z, m},
sides = Norm /@ {b - c, c - a, a - b} // N;
{x, y, z} = RotateLeft[{a, b, c}, First[Position[sides, Max[sides]]] - 1];
m = Normalize[Mean[{y, z}]];
{{y, m, x}, {x, m, z}}
]
Sự khác biệt là split
trả về cả hai nửa của tam giác đầu vào của nó chứ không phải là một điểm trong đó một điểm đã cho nằm. Phép tính tam giác đầy đủ thu được bằng cách lặp lại điều này:
triangles = NestList[Flatten[split /@ #, 1] &, {IdentityMatrix[3] // N}, 10];
Để kiểm tra, tôi đã tính toán kích thước của mọi tam giác và xem xét phạm vi. .
Through[{Min, Max}[Map[Round[Det[#], 0.00001] &, triangles[[10]] // N, {1}]]]
{0,00523, 0,00739}
Do đó, kích thước thay đổi tăng hoặc giảm khoảng 25% so với mức trung bình của họ: điều đó có vẻ hợp lý để đạt được một cách thống nhất cho các điểm nhóm.
Khi quét mã này, bạn sẽ không nhận thấy lượng giác : nơi duy nhất cần thiết, nếu có, sẽ là chuyển đổi qua lại giữa tọa độ hình cầu và cartesian. Mã này cũng không chiếu bề mặt trái đất lên bất kỳ bản đồ nào, do đó tránh được các biến dạng của người phục vụ. Mặt khác, nó chỉ sử dụng trung bình ( Mean
), Định lý Pythagore ( Norm
) và định thức 3 by 3 ( Det
) để thực hiện tất cả công việc. (Có một số lệnh thao tác danh sách đơn giản như RotateLeft
và Flatten
, cùng với việc tìm kiếm cạnh dài nhất của mỗi tam giác.)