Masarykova univerzita Fakulta informatiky Překryvy map Diplomová práce Dominik Janků Brno, jaro 2014 Prohlášení Prohlašuji, že tato diplomová práce je mým původním autorským dílem, které jsem vypracoval samostatně. Všechny zdroje, prameny a literaturu, které jsem při vypracování používal nebo z nich čerpal, v práci řádně cituji s uvedením úplného odkazu na příslušný zdroj. Vedoucí práce: doc. RNDr. Martin Čadek, CSc. i Poděkování Mé velké poděkování patří doc. RNDr. Martinu Čadkovi, CSc, za ochotu, trpělivost a cenné poznámky během vedení této diplomové práce. Chtěl bych také poděkovat své přítelkyni za trpělivost, oporu a věcné poznámky k jazykové části textu. iii Shrnutí Diplomová práce představuje dva rovinné geometrické algoritmy, jmenovitě Bentlyův-Ottmanův algoritmus pro výpočet průsečíků úseček a algoritmus pro výpočet překryvu map. Kromě teoretického výkladu nabízí kompletní pseudokódy, které mohou být využity při jejich implementaci. Ta je ostatně součástí práce v podobě ukázkových programů s otevřeným zdrojovým kódem. v Klíčová slova průsečíky úseček, algoritmus zametači přímky, Bentley-Ottman, pře-kryvy map, rovinná podrozdělení, pseudokód, Python vii Obsah Úvod................................ 3 1 Průsečíky úseček......................... 5 1.1 Výpočet průsečíku dvou úseček.............. 5 1.2 Triviální algoritmus..................... 6 1.3 Bentleyův-Ottmanův algoritmus............. 7 1.4 Lexikografické řazení bodů ................ 7 1.5 Použité datové struktury.................. 7 1.5.1 Prioritní fronta Q.................. 8 1.5.2 Vyvážený binární strom T............. 8 1.6 Množiny U (p), C (p) a L (p)................. 10 1.7 Inicializace a běh algoritmu................ 10 1.8 Zpracování události..................... 11 1.9 Hledání nových průsečíků................. 13 1.10 Výstup............................ 14 2 Překry vy map........................... 15 2.1 Základní pojmy....................... 16 2.2 Dvojitě souvislý seznam.................. 17 2.2.1 Struktura dvojitě souvislého seznamu...... 18 2.2.2 Příklad zápisu dat................. 19 2.3 Algoritmus.......................... 19 2.3.1 Spojení seznamů.................. 20 2.3.2 Výpočet průsečíků................. 23 Obsluha události.................. 25 Obsluha události - eliminace úseček C(p) ... 25 Obsluha události - propojení hran........ 28 2.3.3 Zpracování oblastí................. 31 Hranice oblasti................... 31 Určení vnější a vnitřní hranice.......... 32 Zjištění cyklů.................... 33 Komponenty souvislosti ............. 33 1 Algoritmické vytvoření komponent souvislosti 35 Určení původních názvů oblastí......... 37 Vytvoření záznamů ve faces(D)......... 40 2.4 Úplný algoritmus...................... 41 2.5 Časová složitost překryvu map.............. 41 2.6 Booleovy operace nad polygony ............. 41 3 Ukázkové programy......... .............. 43 3.1 Použité technologie..................... 43 3.1.1 Python........................ 43 3.1.2 Blist.......................... 44 Instalace....................... 44 3.1.3 Tkinter........................ 44 3.1.4 Pomocné třídy ................... 45 3.2 Program Sweepline..................... 45 3.2.1 Ovládání programu ................ 46 3.2.2 Významy barev bodů ............... 46 3.3 Program MapOverlay ................... 46 3.3.1 Ovládání programu ................ 47 3.3.2 Struktura souboru s mapou............ 47 Závěr................................ 49 A Obsah přiloženého CD ..................... 55 2 Úvod Geometrické algoritmy se vždy řadily k nejpřitažlivějším částem informatiky Snad proto, že se s jejich aplikací setkáváme takřka na každém kroku. Není proto příliš velkým překvapením, že jsem si jednu z jejich částí vybral jako téma této diplomové práce. Problematika výpočtu průsečíků a překryvu map patří k těm nej-základnějším. Setkáváme se s nimi například v grafických, geografických, nebo konstruktérských aplikacích, které by bez efektivních algoritmů nemohly pracovat. Přestože v dnešní době disponujeme řádově většími výpočetními výkony, než tomu bylo v minulosti, pořád zde existuje poptávka po algoritmech, které řeší daný problém v co nejmenší časové složitosti. Algoritmy prezentované v této práci patří mezi ně. První z nich, Bentleyův-Ottmanův algoritmus [2] slouží k výpočtu průsečíků úseček, přičemž zohledňuje jejich počet ve své časové složitosti. Totéž činí i algoritmus překryvu map [3], který je popsán následovně. Konečným cílem této práce je naprogramování aplikací, které by oba výše uvedené algoritmy implementovaly a umožnily uživateli jejich názornou ukázku. Způsob, jakým algoritmy pracují, je popsán zvlášť v jednotlivých kapitolách. Teoretický výklad je navíc doplněn o pseudokódy, které čtenáři mohou pomoci s pochopením a případnou implementací algoritmů. Členění kapitol je následující. První kapitola otevírá problematiku průsečíků úseček a to rekapitulací základních poznatků z oboru analytické geometrie. Ihned poté následuje představení Bentley-Ottmanova algoritmu, včetně příslušných pseudokódů. Druhá kapitola obsahuje stěžejní část této práce - průniky map. Úvodem čtenáře seznamuje se základními pojmy a pokračuje představením dvojitě souvislého seznamu, jakožto vhodné datové struktury pro uchování map. Jednotlivé kroky algoritmu jsou podrobně 3 popsány a stejně jako v kapitole první, ani zde nechybí doprovodné pseudokódy Třetí kapitola představuje naprogramované aplikace pro demonstraci algoritmů. Uvedeny jsou použité technologie a způsob ovládání aplikací, které zároveň slouží jako uživatelská příručka. Poslední kapitola přináší rekapitulaci dosažených výsledků. 4 Kapitola 1 Průsečíky úseček Pro řešení úlohy překryvu map potřebujeme zvládnout vypočet průsečíků úseček. V této kapitole budeme pracovat s množinou úseček {si, s2, ■ ■ ■, sn}, kde jednotlivé úsečky s« jsou určeny koncovými body vť. 1.1 Výpočet průsečíku dvou úseček Máme dány dvě úsečky: úsečku s, určenou body (px,py), (qx,qy) a úsečku s' určenou body (p'x,p'y), (q'x, q'y)-Našim úkolem je nalézt jejich průsečík. Pro tento účel využijeme parametrické vyjádření přímky s v rovině: x = Px + tU1} u= (qx - px,qy - py), (1.1) y = py + tu2, kde t je tzv. parametr. Pokud platí, zet e [0; 1], pak bod (x, y) leží na úsečce s. K nalezení průsečíku úseček s, s' odvodíme soustavu rovnic, kterou získáme z parametrického vyjádření (1.1): Px + tiu1=p'x + t2v1, u = [qx — px, qy — py), Py + t1u2=Py + t2V2, v = (qx-px,qy-py). Ze soustavy rovnic (1.2) vypočteme parametr íx a t2 následovně: . _ VÁPy ~Py) +MPx -Px) , _ MPy ~ Py) + MPx ~ Px) íl — -, í2 — -;-• UiV2 - U2Vi UiV2 + U2Vi Pokud nám vyjde nulový jmenovatel, znamená to, že jsou úsečky navzájem rovnoběžné (mají stejný směrový vektor) a průsečík buď neexistuje, nebo jich existuje nekonečně mnoho. 5 1. Průsečíky úseček Je-li jmenovatel různý od nuly, nejsou úsečky rovnoběžné a průsečík existuje, právě když í1; t2 G [0,1]. V tomto případě má průsečík souřadnice Obecně si však nevystačíme s počítáním průsečíku pouze dvou úseček. Ve většině případů potřebujeme spočítat průsečíky n úseček, kde n může být libovolně velké (konečné) číslo. Z tohoto důvodu vznikly nejrůznější algoritmy, které tuto úlohu řeší [2, 4]. 1.2 Triviální algoritmus Průsečíky hledáme tak, že vybereme libovolnou úsečku s a testujeme ji na průsečík s ostatními úsečkami. Otestované dvojice úseček si označíme a pokračujeme dále. Celkem tedy potřebujeme (™) testů, což odpovídá časové složitosti 0(n2). Zde je třeba říct, že lepší algoritmus obecně neexistuje, neboť vždy může existovat až (™) průsečíků a ty musíme označit. V praxi se ale nestává příliš často, že bychom jich měli tak mnoho. Z tohoto důvodu se hledala řešení, která by počet průsečíků zohlednila při výpočtu. Jedním z nich je Bentleyův-Ottmanův algoritmus, o kterém pojednává následující část textu. Obrázek 1.1: Průsečíky úseček 6 1. Průsečíky úseček 1.3 Bentleyův-Ottmanův algoritmus Bentleyův-Ottmanův algoritmus patří do rodiny algoritmů pracujících s ideou tzv. zametači přímky [2] - myšlené horizontální přímky, která se zastavuje v tzv. událostech. Tyto události jsou procházeny odshora dolů, zleva doprava a vše co je nad nimi (a vlevo na přímce od události) je pokládáno za vyřešené. Algoritmus dosahuje časové složitosti 0((n + k)\ogn), kde k je počet průsečíků a n je počet úseček (viz [3, 9] pro důkaz). 1.4 Lexikografické řazení bodů Definujeme následující lexikografické uspořádání bodů v rovině: Jak už je v počítačové grafice zvykem, y-souřadnice roste na zobrazovacím zařízení odshora dolů a my se této konvence budeme držet. Pokaždé, když budeme hovořit o horním nebo dolním bodu úsečky, máme tím na mysli lexikograficky menší nebo větší bod. 1.5 Použité datové struktury Algoritmus využívá ke své činnosti dvě datové struktury: prioritní frontu událostí Q a vyvážený binární strom T [3]. Obě si rozebereme podrobněji. (1.3) 7 1. Průsečíky úseček 1.5.1 Prioritní fronta Q Do prioritní fronty [8] ukládáme doposud nezpracované události, což jsou význačné body, ve kterých se algoritmus zastavuje. Události jsou ve frontě řazeny lexikograficky (proto prioritní) dle uspořádání 1.3. Na začátku fronty se proto nachází nejmenší událost. Vložení nové události do fronty musí splňovat časovou složitost O (log n). Ve stejné složitosti probíhá také ověření, zda-li se již daná událost ve frontě nenachází. Logaritmické složitosti můžeme dosáhnout využitím binárního vyhledávání (půlení intervalu). Výběr nejmenší události ke zpracování musí probíhat v čase 0(1). 1.5.2 Vyvážený binární strom T Vyvážený binární strom [8] T uchovává ve svých listech aktuální pořadí úseček, které protíná zametači přímka í. Algoritmus tuto informaci využívá při testování průsečíků, což bude podrobněji rozebráno v sekci 1.9. Ve vnitřních uzlech stromu se pak nachází hodnota nejpravějšího listu levého podstromu. Ukázku stromu můžeme pozorovat na obrázku 1.3. Obrázek 1.3: Úsečky protnuté í a odpovídající strom T Může být trochu nezvyklé uchovávat ve stromě úsečky namísto čísel. Pokud ale dokážeme určit, která z úseček je „menší" (myšleno více vlevo) a která „větší" (myšleno více vpravo) je možné použít i takové uspořádání. Abychom toho byli schopni, předpokládejme, že bod p je právě zpracovávaná událost. Zametači přímka l splňuje rovnici y = py (prochází bodem p). Dále předpokládejme existenci libovolné úsečky 8 1. Průsečíky úseček s, která je určena body {xi,yi) a (x2, y2), přičemž platí, že < P < (x2,y2) dle lexikografického uspořádání 1.3. Definujeme funkci (px jinak, která vrací rr-souřadnici průsečíku úsečky s se zametači přímkou í. Pokud je úsečka horizontálně orientovaná, použije se rr-souřadnice události p. Dále definujeme funkci (1.4) V?(s) X2 — X\ (1.5) ^2 - xx)2 + (y2 - í/i)2 která je rovna kosinu úhlu úsečky s svíraného s osou x. Ten budeme potřebovat v situacích, kdy prochází bodem p více úseček zároveň (viz obrázek č. 1.4). Obdržíme totiž více stejných hodnot což ke korektnímu rozhodnutí o pořadí nebude stačit. Porovnání úhlů nám tuto možnost nabízí. P Sl \s2 /s4 S3 Obrázek 1.4: Více úseček prochází událostí p Nyní již známe vše potřebné k tomu, abychom mohli definovat ostré uspořádání nad libovolnými úsečkami s, t ve stromě T: s 0 do Vybereme z fronty Q událost p. HandleEventPoint(p) end Poté následuje průchod jednotlivých úseček ze vstupní množiny (ř. 3-8), při kterém provádíme následující úkony: 1. konstruujeme množiny úseček U (p), L (p) pro pozdější použití, 2. přidáváme koncové body úseček do fronty událostí Q. Posledním krokem je výběr nezpracované události ze začátku fronty Q (i. 9-12) a její zpracování algoritmem č. 2. Tak činíme do doby, než bude fronta událostí prázdná. 1.8 Zpracování události Stěžejní částí Bentley-Ottmanova algoritmu je zpracování události p algoritmem č. 2 (vychází z [3]). Na jeho počátku zpřístupníme množiny U(p), C(p) a L(p). V případě, že sjednocení množin obsahuje více než jednu úsečku, označíme bod p jako jejich průsečík (ř. 4-6). 11 1. Průsečíky úseček Algoritmus 2: HandleEventPoint(p) Input: Událost p. U(p) «— úsečky jejichž horní bod je p L(p) «— úsečky jejichž dolní bod je p C(p) úsečky jejichž vnitřním bodem je p if | U(p) U C(p) U L(p) | > 2 then | Označíme bod p jako průsečík úseček U(p) U C(p) U L(p). end Odebereme úsečky L(p) U C(p) ze stromu T. Vložíme úsečky U(p) U C (p) do stromu T. if U(p) U C{p) = 0 then si ^— úsečka nalevo od bodu p ve stromě T sr ^— úsečka napravo od bodu p ve stromě T FlNDNEWEVENT(sř, sr/p) else s' ^— nejlevější úsečka zU(p)VJ C(p) si ^— levý soused úsečky s' ve stromě T FINDNEWEVENT(s', Syp) s" ^— nejpravější úsečka zU(p)VJ C(p) sr ^— pravý soused úsečky s" ve stromě T FindNewEvent(s", sr/p) end Práce se stromem T Je velice důležité, abychom při manipulaci se stromem T (ř. 7-8) dodržovali následující pravidla: 1. při odebírání úseček se řídíme uspořádáním 1.6, které je platné pro předchozí událost, 2. naopak při vkládání je platné uspořádání podle zpracovávané události p. Činíme tak proto, že úsečky C (p) mění v bodě p své pořadí. Strom ale o této změně nemůže „vědět" a tak v něm zůstává pořadí úseček platné z předchozí události. Abychom zajistili jejich správné prohození, odstraníme je a znovu vložíme (podle zmíněných pravidel). 12 1. Průsečíky úseček Tímto způsobem udržujeme platné pořadí úseček, které je nezbytné pro nalezení nových průsečíků. 1.9 Hledání nových průsečíků Posledním krokem algoritmu je hledání nových průsečíků. To probíhá v závislosti na tom, jestli je sjednocení U(p) U C (p) prázdné či nikoliv (alg. č. 2, ř. 9-20) [3]. Na obrázku 1.7 vidíme ilustraci obou situací. Je zřejmé, že pokud některá z úseček si, sr, s', s" neexistuje, nemá smysl hledat její průsečíky. (a) U(p) U C (p) = 0 (b) U (p) U C (p) ^ 0 Obrázek 1.7: Hledání nových průsečíků Algoritmus 3: FindNewEvent(s;, sr, p) Input: si, sr - testované úsečky, p - bod události if 3 průsečík q úseček sř a sr A q > p then if q je vnitřním bodem si then | Přidáme úsečku si do množiny C (q). if q je vnitřním bodem sr then | Přidáme úsečku sr do množiny C (q). if q Q then | Přidáme bod q do fronty Q. end Algoritmus č. 3 řeší testování na průsečík dvou úseček podle soustavy rovnic 1.2. Pokud je průsečík nalezen a leží pod bodem událostí p, pak se přistoupí k dalšímu zpracováni. Průsečíky nad událostí nás 13 1. Průsečíky úseček již nezajímají (byly vypočítány a zpracovány v předchozím průběhu algoritmu). 1.10 Výstup Výstupem algoritmu je seznam všech průsečíků včetně úseček, které jimi prochází. Jak uvidíme v následující kapitole, může být výstup obohacen o další informace, které potřebujeme pro vyřešení specifického typu úlohy. 14 Kapitola 2 Překryvy map Stěžejní částí této práce jsou překryvy map. Problém překryvu si můžeme jednoduše představit jako nalezení průsečíků a překrývajících se oblastí dvou zadaných map [3]. Příklad jednoduchého překryvu můžeme vidět na obrázku 2.1, oblasti jsou značeny písmenem /. Obrázek 2.1: Překryv dvou jednoduchých map Překryvy map se uplatňují v široké škále aplikací. Nejčastěji pak v geografických výpočtech, územním projektování atp. Když se například rozhodne o stavbě nové pozemní komunikace, je potřeba učinit řadu nezbytných kroků. Jedním z nich je zjistit, přes které pozemky projektovaná stavba povede. Na základě těchto informací pak dojde k rozdělení pozemků v katastru nemovitostí a jsou zahájeny jejich výkupy. Jedná se o ukázkový příklad, u kterého je vhodné použít překryvy map - jedna mapa bude projektované území silnice a druhá mapa bude oblast, do které je silnice zasazena. 15 2. PŘEKRYVY map 2.1 Základní pojmy V této části si osvětlíme některé základní pojmy, které budou nezbytné pro správné pochopení algoritmu. Začněme ústředním pojmem celé kapitoly - mapou. Mapa není nic jiného, než rovinné podrozdělení. Tedy rovina, kterou rozdělují uzavřené lomené čáry složené z úseček na oblasti. Na obrázku 2.2 můžeme vidět jednoduché rovinné podrozdělení, skládající se ze 4 oblastí, 10 vrcholů a 22 hran (hranu chápeme jako orientovanou úsečku). Rovinné podrozdělení značíme písmenem S. V textu se dále setkáme s následujícími pojmy: Vrchol je koncový bod hrany, budeme jej označovat jako v{ (vertex). Hrana je orientovaná úsečka vycházející z vrcholu v i a končící ve vrcholu Vj. Značíme jako e^- (edge). Výchozí vrchol hrany v;t označujeme jako origin(eij). Oblast je plocha vymezená právě jednou vnější hranicí a libovolným počtem hranic vnitřních. Označujeme f í (face). Přilehlá oblast je oblast umístěna nalevo od hrany. Obrázek 2.2: Příklad rovinného podrozdělení 16 2. PŘEKRYVY MAP Dvojče hrany e, j je hrana s opačnou orientací od hrany e^. Dvojče hrany označujeme twin(ei:j). Následník hrany ei:j je hrana e^k vycházející z koncového vrcholu hrany ei:j, která má stejnou přilehlou oblast jako ei:j. Označujeme next(e,ij). Předchůdce hrany e,^ je hrana e,hj, která má svůj koncový vrchol totožný s počátečním vrcholem hrany e^k a navíc mají obě hrany stejnou přilehlou oblast. Označujeme prev{e^k). 2.2 Dvojitě souvislý seznam Vhodnou datovou strukturou pro reprezentaci mapy je dvojitě souvislý seznam1. Tvoří jej tři tabulky záznamů (relace), z nichž každá zvlášť uchovává informace o vrcholech, hranách a oblastech [3]. K základním atributům jednotlivých tabulek (viz podsekce 2.2.1) můžeme libovolně přidávat další - doplňující informace. Když například sestavujeme mapu znečištění ovzduší, bude u každé oblasti uvedena také hodnota naměřené veličiny, atp. Dvojitě souvislým je seznam nazýván proto, jelikož propojuje vrcholy s hranami a hrany s oblastmi. Vrcholy Hrany Oblasti V dalším textu budeme dvojitě souvislý seznam označovat písmenem D. 1. V anglické literatuře se setkáme s názvem doubly-connected edge list (DCEL). 17 2. překryvy MAP 2.2.1 Struktura dvojitě souvislého seznamu 1. vrcholy (vertices) • název vrcholu2 • souřadnice (IR x IR) • ukazatel3 na libovolnou hranu vycházející z vrcholu 2. hrany (edges) • název hrany • ukazatel na vrchol, ze kterého hrana vychází • ukazatel na dvojče hrany • ukazatel na následníka hrany se stejnou přilehlou oblastí • ukazatel na předchůdce hrany • ukazatel na přilehlou oblast (oblast vlevo dle směru orientace) 3. oblasti (faces) • název oblasti • ukazatel na jednu hranu přilehlou z vnější hranice • ukazatel na jednu hranu přilehlou pro každou komponentu vnitřní hranice Názvy vrcholů, hran a oblastí mohou být libovolné4 - jsou určeny čistě pro naše potřeby a nemají vliv na strukturu dat. Tím rozhodujícím jsou totiž ukazatelé. Ukazatel si můžeme představit jako adresu (nebo index), na které se poukazovaný záznam nachází. Nejedná se tedy o data samotná, ale jen o informaci, kde je nalezneme. Přístup k poukazovanému záznamu probíhá v konstantním čase 0(1). 2. Pro naše účely budeme vrcholy pojmenovávat podle jejich oznčení, tedy ví. 3. Ukazatel chápeme jako adresu konkrétní poukazované položky v seznamu, která je dostupná v čase 0(1). V programovacích jazycích jako je C, C++ se často implementuje pomocí operátoru *. 4. Názvy se dokonce mohou i shodovat, nejedná se tedy o tzv. primární klíče 18 2. PŘEKRYVY MAP Pokud budeme chtít získat ostatní informace (jako například všechny hrany vedoucí z/do vrcholu), můžeme tak zpravidla učinit v lineárním čase 0(n) ku celkovému množství požadovaných dat. 2.2.2 Příklad zápisu dat Na obrázku 2.3 máme uvedeno jednoduché rovinné podrozdělení. Pro názornost si jej převedeme do dvojitě souvislého seznamu. Pokud je v názvu sloupce tabulky uveden znak * znamená to, že se jedná o ukazatel na data, nikoliv data samotná. Výsledek převodu vidíme v tabulkách 2.1, 2.2 a 2.3. v5 e5i4 v4 v1 e2,i v2 Obrázek 2.3: Triviální rovinné podrozdělení Název Souřadnice * Vycházející hrana Vl (0; 10) ei,2 V2 (10; 10) e2,3 v3 (15; 5) e3,4 v4 (9;0) e3,4 v5 (i;0) e4,i Tabulka 2.1: Vrcholy z obrázku 2.3 2.3 Algoritmus Nyní již známe všechny potřebné informace k tomu, abychom si mohli rozebrat princip algoritmu překryvu map. Ten bude na svém 19 2. překryvy MAP Název *Vrchol *Dvojče *Násled. *Předchůd. *Oblast ei,2 ^i e2,i e2,4 e5,i fi e2,i ^2 ei,2 ei,5 e3,2 foo e2,3 ^2 e3,2 e3,4 e4,2 Í2 e3,2 ^3 e2,3 e2,i e4,3 foo e3,4 ^3 e4,3 e4,2 e2,3 Í2 e4,3 V4 e3,4 e3,2 e5,4 foo e4,2 V4 e2,4 e2,3 e3,4 h e2,4 V2 e4,2 e4,5 ei,2 fl e4,5 V4 e5,4 e5,i e2,4 fl e5,4 v5 e4,5 e4,3 ei,5 foo e5,i ei,5 ei,2 e4,5 fl ei,5 Vl e5,i e5,4 e2,i foo Tabulka 2.2: Hrany z obrázku 2.3 Název *Hrana vnější hranice * Hrany vnitřních hranic foo — {el,5> fl ei,2 0 h e2,3 0 Tabulka 2.3: Oblasti z obrázku 2.3 vstupu přijímat dvojitě souvislé seznamy Dlr D2, reprezentující rovinná podrozdělení S\ a S2. Jejich překryv budeme chtít vypočítat. Výsledkem výpočtu je nové rovinné podrozdělení S, reprezentované dvojitě souvislým seznamem D. 2.3.1 Spojení seznamů Prvním krokem algoritmu je spojení seznamů Di a D2 do jednoho seznamu D. Tuto operaci provádí algoritmus 4, DCELMerge(Di , D2). Princip spočívá v postupném slučování hran a vrcholů do jediného seznamu D. Oblasti slučovat nebudeme, neboť je ještě nemáme vypočteny. Označením všech vrcholů jako doposud nepřidaných (ř. 2) se vy- 20 2. PŘEKRYVY map hneme situaci, kdy bychom přidávali do vertices(D) víc shodných vrcholů najednou. Poté procházíme jednotlivé hrany prvního podrozdělení a postupně je přidáváme do edges(D). Na tomto místě je potřeba zmínit, že vzájemná propojenost ukazatelů v záznamu musí být zachována. Toho docílíme například tak, že nebudeme přidávat jednotlivé záznamy hran a vrcholů, ale jen ukazatele na ně. Nové vrcholy a hrany budeme přidávat až ve chvíli, kdy budeme počítat nové průsečíky (viz další části). Spolu s hranami přidáváme i jejich výchozí vrcholy za podmínky, že se již v D nenachází (ř. 7-10, 18-20). Navíc si u hran poznamenáme číslo mapy, ze které pochází (ř. 4,13). Tuto informaci použijeme v dalších krocích algoritmu. Obdobně postupujeme se záznamy původem z D2. Jediný rozdíl bude spočívat v tom, že ošetříme sitauce, kdy se již vrchol se shodnými souřadnicemi ve vertices(D) nachází. V takovém případě nastavíme u zpracovávané hrany ukazatel origin(e) na existující vrchol ve vertices(D) (ř 16-17). Obrázek 2.4: Řešení shodných souřadnic u dvou vrcholů Zmíněnou situaci ilustruje obrázek 2.4. Vrchol vi z vertices(Di) je souřadnicově shodný s vrcholem v2 z vertices(D2). Situaci rozhodneme ve prospěch vrcholu z prvního podrozdělení. Abychom při kontrole existence vrcholu, prováděné na řádku 16, zachovali časovou složitost 0(n logn), můžeme si vytvořit pomocný binární vyhledávací strom. Uzly stromu uspořádáme dle souřadnic vrcholů. Vytvoření stromu má časovou složitost 0(n\ogn), vyhledání vrcholu v něm pak O(logn). Tímto způsobem můžeme zjistit, zda-li se již nějaký vrchol ve vertices(D) nenachází. 21 2. překryvy MAP Algoritmus 4: DCELMerge (Dlř D2) Input: Dvojitě souvislé seznamy Dlf D2 Output: Datově spojený dvojitě souvislý seznam D. D prázdný dvojitě souvislý seznam. Označíme všechny vrcholy v D\ a D2 jako nepřidané, for e G edges(Di) do source_map{e) 1 edges(D) edges(D) U {e} f ace_label{e) {label(/ace(e))} ■u origin(e) if ->is_added(v) then vertices(D) vertices(D) U {-u} is_added(v) Trae end end for e G edges(D2) do source_map{e) 2 edges(D) edges(D) U {e} f ace_label{e) {/a6e/(/ace(e))} ■u origin(e) ií3 w E vertices (D) .coords (w) = coords{v) then | origin(e) -u; else if ~^is_added{v) then vertices(D) vertices(D) U {-u} is_added{v) Trae end end 22 2. překryvy map 2.3.2 Výpočet průsečíků Výpočet průsečíků z dvou různých podrozdělení je dalším krokem algoritmu. Za tímto účelem využijeme nám známý algoritmus zametači přímky, který pro naše účely upravíme. Výsledek je uveden v algoritmu 5 - OverlaySweepLine (D). Algoritmus začíná převodem hran na úsečky, se kterými se nám bude lépe pracovat. Postup převodu je uveden na řádcích 1-10. Začneme tím, že označíme všechny hrany z edges(D) jako nezpracované. Poté převádíme jednu (nezpracovanou) hranu po druhé a vytváříme z nich úsečky. Pamatujeme si přitom, ze které hrany úsečka vznikla a to díky ukazateli na ní. Abychom se však vyhnuli vytváření identických úseček, označíme rovněž její dvojče za zpracované. Nakonec získáváme množinu úseček S, se kterou můžeme dále pracovat5. Následuje procházení jednotlivých úseček s e S, které se od původní verze příliš neliší. Jediným krokem navíc je uchovávání počtu úseček patřící k té, či oné mapě. Tuto informaci si budeme pamatovat u jednotlivých množin U (p) a L (p). Za tímto účelem definujme funkci (Tí(S), kde i je označení mapy a S je množina úseček. Pak a,i(S) vrací počet úseček v S s původem v mapě i. Tak například ai(U(p)) vrací počet úseček v U(p) s původem v mapě 1. Tuto informaci bude užitečné znát při zpracování události algoritmem 6, jak ostatně uvidíme dále. 5. Při implementaci převodní procedury bychom mohli navíc uspořit výpočetní čas tím, že bychom konverzi prováděli přímo v cyklu na řádcích 13-19, nicméně asymptotická složitost by zůstala nezměněna - 0(n). I [ 23 2. překryvy map Algoritmus 5: OverlaySweepLine (D) Input: Dvojitě souvislý seznam D. Output: D s vypočtenými průsečíky Označíme všechny hrany edges(D) jako nezpracované. for e G edges(D) do if hrana e nebyla dosud zpracována then s segment(e) edgejpointer(s) Odo Vybereme z fronty Q událost p. OverlayHandleEventPoint(p) end 24 2. překryvy map Obsluha události Obsluha události p doznala výraznějších změn oproti své původní verzi uvedené v algoritmu 2. Její podobu můžeme vidět v algoritmu 6 - OverlayHandleEventPoin(p). Při obsluze události vycházíme z faktu, že nehledáme průsečíky úseček, které mají původ v téže mapě - ty již známe (jinak by mapa nebyla korektní). Hledáme pouze takové průsečíky, které mají původ v obou mapách současně. Když je takový průsečík nalezen, provedeme potřebné úpravy v D tak, aby oblast nad p byla zpracovaná. Pro přehlednost si postup rozčleníme do tří částí: 1. eliminace úseček C (p) rozdělením na horní a dolní část, 2. propojení (nastavení předchůdců a následníků) hran směřujících z/do p, 3. hledání nových průsečíků pod p. Obsluha události - eliminace úseček C (p) Při obsluze události p je nezbytné provést rozdělení všech úseček v množině C (p). Tento krok činíme proto, abychom vytvořili korektní dvojitě souvislý seznam. Každý vrchol v něm může být pouze začátkem nebo koncem hrany, nemůže ležet uvnitř ní. s s s' Obrázek 2.5: Princip algoritmu SplitCpSegment(s, p) 25 2. překryvy map Algoritmus 6: OverlayHandleEventPoint(p) Input: Událost p. U (p) «— úsečky jejichž horní bod je p L (p) «— úsečky jejichž dolní bod je p C (p) úsečky jejichž vnitřním bodem je p for s e C (p) do s' <- SplitCpSegment(s, p) L(p) 0 A o-i(L(p) U U(p)) > 0 A a2(L(p) U U(p)) > 0 then : si «— nejlevější úsečka z L(p) -. sr «— nejpravější úsečka z L(p) while s\ ý sr do : s'r «— pravý soused úsečky s\ ve stromě T ■. DoClockwiseConnect(s;, s'r, p) s\ 0 A a2(L(p) U U (p)) > 0. Tedy, že existuje alespoň jedna úsečka z každé mapy. Tato podmínka je obsažena jak v algoritmu 8, tak v algoritmu 9. Oba navíc přidávají podmínku na neprázdnost zpracovávaných úseček. Tímto způsobem se můžeme vyhnout zbytečnému propojování hran původem z téže mapy - je již hotové. 29 2. překryvy map Algoritmus 9: ClockwiseConnectLower(p) Input: Událost p a úsečky L(p),U(p). Result: Propojení hran úseček z množiny U (p). i: Vložíme úsečky U (p) do stromu T. 2: if I U (p) I > 0 A oi(L(p) U U (p)) > 0 A (T2{L[p) U f/(p)) > 0 then 3: ti «— nejlevější úsečeka z f/(p) 4: tr «— nejpravější úsečeka z U (p) 5: íj. tr 6: while íj. 7^ ti do 7: ŕ{ «— levý soused úsečky ťr ve stromě T 8: DoClockwiseConnect(4, í;, p) 9: t; <- t; ío: end íi: return í;, ír 12: end Algoritmus 10: DoClockwiseConnect(s, t, p) Input: Událost p, která je společným koncovým bodem úseček s, t. Result: Nastaví předchůdce a následníka hran úseček s, t při události p. i: ei hrana úsečky s taková, že origin(ei) ^ p 2: e2 hrana úsečky t taková, že origin(e2) = p 3: next(ei) e2 4: prev(e2) Všechna data se nachází uvnitř párové značky . Nalezneme zde tři seznamy, které kopírují strukturu dvojitě souvislého seznamu: data o vrcholech , hranách a oblastech . Atribut id je unikátním identifikačním pojmenováním objektu a nesmí se tedy stát, že by existovaly dva objekty se stejným iden- 47 3. Ukázkové programy tifikátorem. Taková situace bude programem považována za chybu na vstupu. Ostatní atributy vrcholů, hran nebo oblastí se řídí konvenčním pojmenováním, které v této práci běžně užíváme. U záznamů oblastí rozlišujeme vnější a vnitřní hranici atributem type u značky . Více než jedna vnější hranice u oblasti znamená chybu na vstupu. Předdefinované mapy jsou k nalezení v adresáři map s na přiloženém CD a lze je použít jako vstup programu. 48 Zaver Závěrem lze konstatovat, že cíle stanovené v úvodu této práce byly splněny Přínosem v problematice výpočtu průsečíků úseček je definování způsobu práce s binárním stromem T. V literatuře [2, 3, 9] totiž není explicitně uvedeno, jakým způsobem se rozhodovat při procházení stromu vůči zadanému bodu. Jeden z možných způsobů je popsán v podsekci 1.5.2. Největší objem původní práce však představuje kapitola druhá: prakticky všechny uvedené pseudokódy musely být nově vymyšleny. Mnohé postupy byly navíc oproti těm z [3] vylepšeny a zjednodušeny. Naprogramování dvou ukázkových aplikací je neméně významným přínosem. Vzhledem k tomu, že programy prakticky nejsou závislé na zvoleném prostředí, může se s touto problematikou seznámit velké množství zájemců. Možnosti k dalšímu rozvoji vidím v ošetření některých speciálních situací na vstupu, jako jsou dvě navzájem se překrývající úsečky. Dále bude možné rozšiřovat funkcionalitu ukázkových programů dle přání uživatelů. 49 Seznam algoritmů 1 SweepLine (S)........................ 11 2 HandleEventPoint(p).................. 12 3 FindNewEvent(s;/ sr/p).................. 13 4 DCELMERGE (Dlr D2).................... 22 5 OverlaySweepLine (D).................. 24 6 OverlayHandleEventPoint(p)............. 26 7 SplitCpSegment (s,p)................... 27 8 ClockwiseConnectUpper(p) .............. 29 9 ClockwiseConnectLower(p).............. 30 10 DoClockwiseConnect(s, t, p).............. 30 11 inner (c) ........................... 32 12 outer (c)........................... 32 13 OverlayCycles (D) .................... 34 14 OverlayComponents (C)................. 36 15 FindFaceLabels (G).................... 39 16 OverlayFaces (G) ..................... 40 17 MapOverlay (Dlr D2) ................... 41 51 Literatúra [1] Python 2.7.7 documentation. 18.5.2014, [online]. URL [https://docs.python.0rg/2/] [2] Bentley, J. L.; Ottmann, T. A.: Algorithms for reporting and counting geometric intersections. IEEE Trans. Comput, 1979. [3] Berk, M.; Kreveld, M.; Cheong, O.; ay. Computational Geometry. Springer, 2008, ISBN 978-3-540-77973-5. [4] Chazelle, B.; Edelsbrunner, H.: An optimal algorithm for intersecting line segments in the plane. Journal of the ACM 39, 1992: s. 1-54. [5] Lundh, E: An Introduction to Tkinter. 2005, [online]. URL [http://elfbot.org/tkinterbook/] [6] Stutzbach, D.: Blist Documentation. 2010, [online]. URL [http://stutzbachenterprises.com/blist/] [7] Summerfield, M.: Python 3. Computer press, 2010, ISBN 978-8-025-12737-7. [8] Wróblewski, P.: Algoritmy. Computer press, 2004, ISBN 80-251-0343-9. [9] Čadek, M.: Geometrické algoritmy, kap. 2. 2013, [online]. URL [http://tinyurl.com/qzzk5xb] 53 Příloha A Obsah přiloženého CD Přiložený kompaktní disk je nedílnou součástí této práce. Obsah kořenového adresáře je následující: maps Adresář s předdefinovanými mapami. geotools.py Modul usnaďňující práci s geometrickými objekty. dcel.py Modul pro práci s dvojitě souvislým seznamem. sweepline.py Program Sweepline. mapoverlay.py Program MapOverlay. 55