🔺 TIN და Delaunay-ს ტრიანგულაცია¶
რელიეფი შეიძლება ორი გზით წარმოვადგინოთ:
- რასტრი (DEM): სიმაღლეები რეგულარულ ბადეში. მარტივია, მაგრამ ყველგან ერთნაირი სიზუსტეა.
- TIN (Triangulated Irregular Network): სიმაღლის წერტილები სამკუთხედებად არის გაერთიანებული. სამკუთხედები ბრტყელია და მათი ზედაპირი რელიეფს მიახლოებით აღწერს. რთულ უბნებზე წერტილი მეტია, ბრტყელზე ნაკლები, ამიტომ მეხსიერებას ეკონომიურად ვხარჯავთ.
ამ გვერდზე ვიგებთ, როგორ აგებენ TIN-ს (Delaunay-ს ტრიანგულაცია) და როგორ ვიყენებთ მას სიმაღლის ინტერპოლაციისთვის.
💡 რატომ Delaunay?¶
ერთი და იგივე წერტილების გაერთიანება სამკუთხედებად ბევრი გზით შეიძლება. Delaunay-ს ტრიანგულაცია ერთადერთია, რომელიც ამ თვისებას აკმაყოფილებს:
ყოველი სამკუთხედის შემოხაზულ წრეში სხვა წერტილი არ ხვდება.
შედეგად ის მაქსიმალურად გაურბის ძალიან ვიწრო, „ნემსივით" სამკუთხედებს, რადგან ისინი ინტერპოლაციას აუარესებენ. ეს თვისება Voronoi-ს დიაგრამასთანაც ორმაგად (dual) არის დაკავშირებული: Voronoi-ს უჯრები ის არეებია, რომლებიც თითოეულ წერტილთან უფრო ახლოსაა, ვიდრე სხვასთან, ხოლო Delaunay-ს წიბო ორ წერტილს აერთებს, თუ მათი უჯრები ერთმანეთს ეხება.
🎮 სცადეთ თქვენც¶
დააჭირეთ რუკაზე წერტილების დასამატებლად: ტრიანგულაცია ყოველ დამატებაზე თავიდან აიგება. გადაატარეთ მაუსი სამკუთხედზე: ჩანს მისი შემოხაზული წრე (სხვა წერტილი მასში არ ხვდება), ინტერპოლირებული სიმაღლე ნამდვილ სიმაღლესთან შედარებით, ასევე სამკუთხედის დახრილობა და ექსპოზიცია.
დაკვირვება
- სამკუთხედის ფერი მისი წვეროების საშუალო სიმაღლეა, ზუსტი მნიშვნელობა კი მხოლოდ წერტილებზე გვაქვს. სამკუთხედის შიგნით სიმაღლე წრფივად იცვლება, ამიტომ ინტერპოლირებული და ნამდვილი მნიშვნელობა ოდნავ განსხვავდება.
- წერტილების დამატებით განსხვავება მცირდება.
- დახრილობა ამ დემოში პირობითია (პიქსელები და სიმაღლის ერთეულები სხვადასხვაა), ამიტომ რიცხვი უფრო ტენდენციას აჩვენებს.
⚙️ ალგორითმი: Bowyer–Watson¶
წერტილებს სათითაოდ ვამატებთ:
- დასაწყისში ავიღოთ სუპერ-სამკუთხედი, რომელიც ყველა წერტილს შეიცავს.
- ახალი წერტილი
p-სთვის ვიპოვოთ ყველა სამკუთხედი, რომლის შემოხაზულ წრეშიცpხვდება. ეს „ცუდი" სამკუთხედებია. - „ცუდი" სამკუთხედები ერთად ქმნიან ხვრელს. მისი საზღვარი ის წიბოებია, რომლებიც მხოლოდ ერთ ცუდ სამკუთხედს ეკუთვნის.
- ცუდ სამკუთხედებს ვშლით და ხვრელის თითოეულ წიბოს
p-სთან ვაერთებთ: ახალი სამკუთხედები მიიღება. - ბოლოს იმ სამკუთხედებს ვშლით, რომლებიც სუპერ-სამკუთხედის წვეროს შეიცავს.
სირთულე
ეს მარტივი ვერსია O(n²)-ია (ყოველ წერტილზე ყველა სამკუთხედს ვამოწმებთ). სივრცითი ინდექსით (Quadtree, R-tree) ის საშუალოდ O(n log n)-მდე იაფდება. პროფესიონალური ბიბლიოთეკები (Qhull, CGAL, GEOS) ასეთ ოპტიმიზებულ ალგორითმებს იყენებენ.
🐍 რეალიზაცია სუფთა Python-ით¶
გამოყენება და შემოწმება¶
import random
random.seed(7)
pts = [(random.random() * 100, random.random() * 100) for _ in range(300)]
triangles = delaunay(pts)
print(len(pts), 'წერტილი ->', len(triangles), 'სამკუთხედი')
# სიმაღლეები: სიბრტყე z = 0.5x + 0.2y, ამიტომ ინტერპოლაცია ზუსტი უნდა იყოს
z = [0.5 * x + 0.2 * y for x, y in pts]
print(interpolate((50, 50), pts, z, triangles)) # 35.0 (+ დამრგვალება)
print(slope_aspect((0, 0), (1, 0), (0, 1), 0, 0, 1)) # (45.0, 180.0)
სამკუთხედების რაოდენობა
n წერტილისა და h ამოზნექილი გარსის წვეროსთვის Delaunay-ს ტრიანგულაცია ზუსტად 2n - 2 - h სამკუთხედს შეიცავს. ეს ფორმულა კარგი შემოწმებაა: ჩვენი კოდი 20 შემთხვევით ნაკრებზე ყოველთვის ამ რიცხვს იძლევა.
🧰 იგივე ბიბლიოთეკებით¶
📝 დავალებები¶
დავალება 1
დაწერეთ ფუნქცია, რომელიც ამოწმებს, რომ delaunay()-ის შედეგი ნამდვილად აკმაყოფილებს Delaunay-ს თვისებას: არც ერთი წერტილი არც ერთი სამკუთხედის შემოხაზულ წრეში არ ხვდება. შეამოწმეთ ის 10 შემთხვევით ნაკრებზე.
ამოხსნა
def is_delaunay(pts, triangles, eps=1e-9):
for t in triangles:
cx, cy, r2 = circumcircle(*(pts[i] for i in t))
for j, p in enumerate(pts):
if j not in t and (p[0] - cx) ** 2 + (p[1] - cy) ** 2 < r2 - eps:
return False
return True
for seed in range(10):
random.seed(seed)
P = [(random.random() * 100, random.random() * 100) for _ in range(150)]
print(seed, is_delaunay(P, delaunay(P)))
დავალება 2
ააგეთ 50×50 უჯრიანი რასტრი TIN-იდან (ინტერპოლაციით) რელიეფზე z = 50 + 20·sin(x/15) + 15·cos(y/12) და გამოთვალეთ ნამდვილთან საშუალო კვადრატული გადახრა (RMSE) 30, 100 და 400 წერტილის შემთხვევაში.
ამოხსნა
import math
def surface(x, y):
return 50 + 20 * math.sin(x / 15) + 15 * math.cos(y / 12)
def rmse(n_points):
random.seed(1)
P = [(random.random() * 100, random.random() * 100) for _ in range(n_points)]
Z = [surface(*p) for p in P]
T = delaunay(P)
errs = []
for i in range(50):
for j in range(50):
q = (10 + i * 80 / 49, 10 + j * 80 / 49) # ცენტრალური ნაწილი
v = interpolate(q, P, Z, T)
if v is not None:
errs.append((v - surface(*q)) ** 2)
return math.sqrt(sum(errs) / len(errs))
for n in (30, 100, 400):
print(n, round(rmse(n), 3))
დავალება 3
სიბრტყისთვის z = 0.5x + 0.2y გამოთვალეთ ყველა სამკუთხედის დახრილობა და ექსპოზიცია. რა მნიშვნელობა უნდა მიიღოთ და რატომ ერთნაირია ყველა სამკუთხედზე?
ამოხსნა
random.seed(2)
P = [(random.random() * 100, random.random() * 100) for _ in range(100)]
Z = [0.5 * x + 0.2 * y for x, y in P]
results = {(round(s, 3), round(a, 3))
for i, j, k in delaunay(P)
for s, a in [slope_aspect(P[i], P[j], P[k], Z[i], Z[j], Z[k])]}
print(results)
print(math.degrees(math.atan(math.hypot(0.5, 0.2))))
atan(√(0.5²+0.2²)) ≈ 28.3°-ია, ექსპოზიცია კი ≈ 248° (სამხრეთ-დასავლეთი, ანუ დაქანება +x, +y გრადიენტის საპირისპირო მხარეს არის).