Jiří Činčura Prostorová data v MS SQL Server 2008 Veškerá práva vyhrazena Vydáno dne: 12. října 2009 Vydání: první ID/Revize: PDF0001/01 Vydavatel: Avre Publishing, spol. s r.o., Databázový svět (http://www.dbsvet.cz) Úvod Microsoft SQL Server ve své poslední verzi 2008 přináší podporu pro práci s prostorovými daty. Jak je tato podpora implementována? Jaké má možnosti? Co můžeme očekávat? Na všechny tyto otázky a samozřejmě nejen ty, se pokusíme odpovědět v následujících stránkách. Prostorová data jsou v MS SQL 2008 implementována jako CLR rozšíření. Nejsou součástí serveru jako například typ integer. Na druhou stranu jsou implicitní součástí, takže není třeba řešit instalaci apod. Stejně tak server podporuje indexaci těchto dat, pro zrychlení dotazů, takže ani zde není vývojář ochuzen. Vlastní datové typy, které můžeme využít, jsou dva geometry a geography. První jmenovaný slouží k reprezentaci dat, jako např. bodů, čar (linií), polygonů v rovině s omezením souřadnic (konečný prostor). Naproti tomu datový typ geography slouží k reprezentaci stejných dat, avšak na povrchu zeměkoule. Oba typy pracují ve dvou dimenzích (např. na zeměkouli se nebere v potaz výška). Jak jsme se již zmínili, podporovány jsou indexy nad prostorovými daty. Index je tvořen standardním B stromem. Dekompozice je prováděna ve čtyřech úrovních pomocí mřížky a při tvorbě indexu je možné specifikovat, jak hustá mřížka bude na každé úrovni: LOW = 4 4 MEDIUM = 8 8 HIGH = 16 16 Jakmile je prostor rozdělen do mřížky, je třeba přečíst jednotlivé řádky a oindexovat je. Tento proces (tzv. teselace 1 ) prochází jednotlivé řádky a přiřazuje jim buňky, ve kterých objekt leží (nebo se jich dotýká) tzv. touched cells. Procházení se provádí procházením do šířky v jednotlivých úrovních. Aby se počet těchto buněk držel na rozumné hodnotě, existuje několik pravidel, která snižují tento počet. Protože popis jednotlivých pravidel překračuje hranice článku, uvedeme je bez detailního vysvětlení: covering rule cells-per-object rule deepest-cell rule A laskavého čtenáře odkážeme na oblíbený vyhledávač, který zajisté poskytne dostatek výsledků. Zatím jsme se bavili o datových typech geometry a geography jako by se jednalo o téměř stejné typy. Ale datový typ geography přidává o něco málo na komplexnosti. Data jsou totiž reprezentována na 1 Existuje více způsobů jak tento proces provést. MS SQL Server 2008 podporuje ke každému typu jen jeden.
geodetickém elipsoidu a je tedy třeba s nimi zacházet opatrněji. Plocha geodetického elipsoidu musí projít jistou konkrétní projekcí do roviny 2. MS SQL 2008 promítne každou polokouli na stěny čtyřbokého jehlanu (např. pyramida). Tyto dva jehlany poté zploští (vrchol se stane součástí podstavy) a tyto rovinné útvary spojí do jedné roviny. Proces i výsledek můžete vidět na obrázku. Projekce geodetického elipsoidu do roviny v MS SQL 2008 (zdroj: microsoft.com) Vlastní index se vytváří příkazem CREATE SPATIAL INDEX a samozřejmě má smysl jej použít pouze na sloupce s typem geography nebo geometry. Jakmile je index vytvořen, může jej optimalizátor pro některé typy dotazů použít a tím dramaticky zrychlit jejich provádění. Základní práce s prostorovými daty Ačkoli prostorová data obecně nabízejí mnoho objektů, se kterými je možné přímo pracovat, v tomto dokumentu se omezíme na základní bod, linie a polygon. Vytvořme jednoduchou tabulku pro testování: create table test (prostorova_data geography); Pozn.: Pro další účely budeme vždy pracovat s typem geography. V případě odlišností od geometry bude rozdíl uveden. Aby bylo možné zapsat objekty v SQL apod., existuje specifický formát nazvaný jako WKT (Well Known Text). Jedná se o textový zápis různých prostorových objektů (podobně jako WKB je binární reprezentace), který je podporován mnoha systémy, MS SQL Server 2008 nevyjímaje. Bod Bod reprezentuje tečku v prostoru. Je určen svými souřadnicemi. Zápis je velmi jednoduchý: POINT(1 2) Případně zápis speciálního prázdného bodu: POINT EMPTY 2 Není těžké nahlédnout, že projekce vždy některé parametry deformuje.
Všimněte si, že mezi souřadnicemi není čárka nebo středník, odděleny jsou mezerou. Abychom jej mohli na straně databáze použít, je třeba vytvořit z tohoto textu instanci objektu geography. K tomu slouží metoda STGeomFromText. declare @bod geography; set @bod = geography::stgeomfromtext('point(1 2)', 4326); select @bod.tostring(); insert into test values (@bod); Pozn.: Metoda STGeomFromText vyžaduje druhý parametr tzv. spatial reference ID. Prozatím stačí, když vždy použijete 4326 odpovídající systému GPS (WGS84). Podrobnější rozbor bude v závěrečném dílu. Linie Linii je možné si představit jako lomenou (rovnou) čáru. V GIS systémech může typicky reprezentovat vedení potrubí apod. Ve WKT formátu je označena LINESTRING. Jedná se o sekvenci bodů, které danou sérii úseček vymezují. Např.: LINESTRING(1 2, 3 4, 7 8) Pro práci na straně serveru: declare @cara geography; set @cara = geography::stgeomfromtext('linestring(1 2, 3 4, 7 8)', 4326); select @cara.tostring(); insert into test values (@cara); Linie může být složena z libovolného množství úseček. Jednotlivé body jsou virtuálně spojeny a vznikne jedna lomená čára. Polygon Polygon, jak již název napovídá, si můžeme představit jako mnohoúhelník. Avšak s několika vylepšeními. Bystré čtenáře jistě napadlo, proč polygony neřešit jako uzavřenou lomenou čáru? Ve skutečnosti je polygon zapsán jako série bodů. Ale právě informace, že se jedná o polygon a nikoli o náhodou spojenou čáru přidává na možnostech. A mimo jiné u polygonů jsou povoleny díry (Ty by také bylo možné reprezentovat linií, ale již vidíme, že vhodné obalení do vlastního objektu se nám rýsuje.). Zápis je opět velmi přímočarý: POLYGON((1 1, 5 1, 1 5, 1 1)) Případně polygon s dírou: POLYGON((1 1, 5 1, 1 5, 1 1), (2 2, 2 3, 3 3, 3 2, 2 2)) Je důležité poznamenat, že orientace polygonu (jak jdou body za sebou) má velký význam. MS SQL Server 2008 vyžaduje, aby vnější polygon byl orientován protisměru hodinových ručiček a díry po
směru. Zde trochu zjednodušuji, neboť se stačí podívat na polygon zespoda a orientace je opačná. Nicméně budeme-li polygon procházet, musíme mít obsah na levé straně. Pro práci na straně serveru: declare @polygon geography; set @polygon = geography::stgeomfromtext('polygon((1 1, 5 1, 1 5, 1 1),(2 2, 2 3, 3 3, 3 2, 2 2))', 4326); select @polygon.tostring(); insert into test values (@polygon); Polygony dávají obrovské možnosti co s nimi vytvořit. Na internetu můžete nalézt polygony reprezentující velká světová města apod. A díky dírám a orientaci, která je ještě k tomu mezi různými systémy různá (např. ESRI má orientaci opačnou), můžete s polygony zažít zajímavé chvilky. Vzdálenost dvou bodů První metodou je STDistance. Jak již název napovídá, tato metoda vrátí (nejkratší samozřejmě) vzdálenost mezi dvěma body (elementy). Pokud tedy zavoláme: declare @g1 geography; declare @g2 geography; set @g1 = geography::stgeomfromtext('point(51 15)', 4326); set @g2 = geography::stgeomfromtext('point(50 14)', 4326); select @g1.stdistance(@g2); Zjistíme, že vzdálenost těchto dvou bodů je přibližně 154,5 km, vzdušnou čarou. Operace je samozřejmě reflexivní vzdálenost A-B je stejná jako B-A. Obal/Obalová křivka Často je potřeba již existující element obalit a vytvořit jej tak tlustší. Např. pro zjištění, jestli všechny domy mají předepsaný odstup od vedení inženýrské sítě. Pro tento účel slouží metoda STBuffer (případně rozšířená BufferWithTolerance). Nejjednodušší je vytvoření obalu kolem bodu: declare @g geography; set @g = geography::stgeomfromtext('point(49.414413 14.657804)', 4326); select @g.stbuffer(2000), @g.stbuffer(2000).tostring(); Výsledkem obalu kolem bodu je samozřejmě polygon připomínající kruh (doporučuji prohlédnout výsledek). V tomto konkrétním případě 2km kruh kolem města Tábor.
V případě aplikace na linii je výsledkem tlustá čára se zakulacenými konci: declare @g geography; set @g = geography::stgeomfromtext('linestring(2 1, 3 1)', 4326); select @g.stbuffer(1000), @g.stbuffer(1000).tostring(); Průnik Velmi častou úlohou je zjištění, zdali bod leží v daném obdélníku. Typicky při zobrazení výřezu mapy. Pro tento účel existuje metoda STIntersects, která vrátí 1, pokud první element proniká druhý, jinak 0. Zjištění všech bodů, které leží v daném polygonu (čtverec): declare @g geography; set @g = geography::stgeomfromtext('polygon((2 1, 2 2, 1))', 4326); 1 2, 1 1, 2 select prostorova_dataa from test where @g.stintersects(prostorova_data) = 1; Průnik, rozdíl, sjednocení Protože jsme v předchozím odstavci tak trochu nakousli množinové operace, zmíníme nakonec tři základní množinové operace průnik, rozdíl a sjednocení. Průnik je reprezentován metodou STIntersection, rozdíl STDifference a sjednocení STUnion. Všechny metody vrátí jako výsledek element, který odpovídá provedené operaci. Plocha Poslední metoda, na kterou se dnes podíváme, je metoda pro zjištění plochy elementu (tedy polygonu). Metoda se příznačně jmenuje STArea a neočekává žádný parametr.
Plochu čtverce použitého výše u metody STIntersects spočítáme: declare @g geography; set @g = geography::stgeomfromtext('polygon((2 1, 2 2, 1 2, 1 1, 2 1))', 4326); select @g.starea(); Zjistíme, že je to přibližně 12 tis. km 2 (pozor pracujete na zeměkouli, nikoli v rovině). Ačkoli MS SQL Server 2008 poskytuje více funkcí nad prostorovými daty, i s několika představenými základními můžete řešit složité úlohy. Webové aplikace Velké množství aplikací dneška jsou aplikace webové ať už klasické dynamicky generované (X)HTML stránky, nebo bohaté aplikace typu Silverlight nebo Flash. A protože možnost využívat Google mapy nebo Live mapy se přímo nabízí, zaměříme se na ně. První co jistě potřebujeme, je zobrazení našich objektů v definovaném obdélníku, tj. obdélníku, který je zrovna vidět. Ten bude identifikován levým horním a pravým dolním vrcholem. Potřebujeme tedy pomocnou funkci, která nám z těchto bodů vytvoří obdélník, polygon. A protože se většinou v těchto mapách pracuje v LatLong souřadnicích, budeme i my ctít toto předávání. Funkce by mohla vypadat jako například: create function GetBoundingRectangle ( @TopLeftLat float, @TopLeftLong float, @BottomRightLat float, @BottomRightLong float ) returns geography as declare @polygon varchar(max); set @polygon = -- BR cast(@bottomrightlong as varchar(max)) + ' ' + cast(@bottomrightlat as varchar(max)) + ', ' + -- TR cast(@bottomrightlong as varchar(max)) + ' ' + cast(@topleftlat as varchar(max)) + ', ' + -- TL cast(@topleftlong as varchar(max)) + ' ' + cast(@topleftlat as varchar(max)) + ', ' + -- BL cast(@topleftlong as varchar(max)) + ' ' + cast(@bottomrightlat as varchar(max)) + ', ' + -- closing point cast(@bottomrightlong as varchar(max)) + ' ' + cast(@bottomrightlat as varchar(max));
return geography::stgeomfromtext('polygon((' + @polygon + '))', 4326); Pokud chceme body v daném obdélníku, nejjednodušší je vzít jednotlivé body a vyzkoušet, zdali s ním mají průnik. Výsledná procedura: create procedure ObjectsInRectangle ( @TopLeftLat float, @TopLeftLong float, @BottomRightLat float, @BottomRightLong float ) as declare @rectangle geography; set @rectangle = dbo.getboundingrectangle(@topleftlat, @TopLeftLong, @BottomRightLat, @BottomRightLong); select o.* from Objects o where o.location.stintersects(@rectangle) = 1 ; Tímto nenáročným způsobem se nám podařilo velmi jednoduše vybrat odpovídající body, které mohou být následně aplikací zobrazeny na mapě. Druhým častým požadavkem je zobrazení všech objektů, které leží v určité vzdálenosti od vybraného. Např. vyhledání pneuservisu v okruhu 10 km. S poznatky z minulého dílu je tento úkol také otázka pár řádků: create procedure ObjectsInDistanceFromPoint ( @Lat float, @Long float, @MaxDistance float ) as declare @point geography; set @point = geography::stgeomfromtext('point('+ cast(@long as varchar(max)) +' '+ cast(@lat as varchar(max)) +')', 4326); select o.* from Objects o where o.location.stdistance(@point) < @MaxDistance ;
Samozřejmě je možné použít i přístup podobný minulému udělat kolem bodu kruh a následně průnik. Nakonec jedna komplexnější úloha. Představme si, že máme čáru(y) reprezentující cesty resp. silnice. Silnice nesmí procházet žádným pozemkem samozřejmě, a v okruhu 20 m od silnice nesmí být žádný pozemek bez domku resp. s domkem s plochou menší nebo rovnou jak 80 m 2 a 50 m od silnice nesmí být žádný pozemek s domkem s plochou vetší jak 80 m 2. create function TestRoad(@road geography) returns bit as declare @result bit; declare @roadwith20area geography; declare @roadwith50area geography; set @roadwith20area = @road.stbuffer(20); set @roadwith50area = @road.stbuffer(50); if ( exists(select 1 from Lands l left outer join Buildings b on (l.id = b.id_land) where (b.id_land is null or b.location.starea() <= 80) and l.location.stintersects(@roadwith20area) = 1) or exists(select 1 from Lands l inner join Buildings b on (l.id = b.id_land) where b.location.starea() > 80 and l.location.stintersects(@roadwith50area) = 1) ) set @result = 0; else set @result = 1; return @result; Pozn.: Funkce je záměrně napsána trochu neohrabaně, především pro lepší popisnost. Ačkoli OGC funkce (a nejen ty), které můžete využít, nabízí bohaté možnosti, pro složitější úkoly je vhodné umět si problém představit nebo nakreslit a vhodně aplikovat jednotlivé z tohoto pohledu transformace k dosažení kýženého výsledku. SRID SRID je zkratka pro Spatial Reference IDentifier. Pokud pracujete s datovým typem geography, je každá instance tohoto typu opatřena touto informací. A o co že se vlastně jedná? Jak již víme, země není přesně kulatá a je třeba její tvar aproximovat. Těchto postupů samozřejmě existuje celá řada, a data v jedné projekci na specifickém elipsoidu nejsou přímo kompatibilní s jinou (je ale možné
provádět přepočty). SQL Server 2008 podporuje SRID podle EPSG (European Petroleum Survey Group) standardu. Všechny podporované identifikátory naleznete dotazem do sys.spatial_reference_systems. A protože dnes, především mezi ne-kartografy, je hojně rozšířen systém GPS používali jsme SRID 4326, tj. WGS 84. Na druhou stranu, v našich krajích se na úřadech používá většinou S-JTSK. Prostorové indexy Poslední o čem se zmíníme, je několik parametrů prostorového indexu. Za normálních okolností jej můžete vytvořit jako každý jiný a používat. Nicméně oproti běžným indexům, mají prostorové indexy jisté specifické možnosti. Definice se liší pro datový typ geometry a geography, což vychází z odlišných dat, které tyto datové typy představují. Nejprve se ale podívejme na omezení. Prostorový index může být definován pouze na tabulce, která má specifikován primární klíč. Což není tak velké omezení, neboť asi každá rozumná tabulka v databázi má primární klíč definován. Dalším, vlastně souvisejícím, omezením je nemožnost indexovat pohledy. Zdali je to pro vás omezení posuďte sami. A konečně maximální počet indexů nad jednou tabulkou je 249. Nyní se podívejme, jaké hlavní možnosti můžeme u geometry prostorového indexu specifikovat. Jeden již známe, jde o hustotu mřížky, která je použita pro vytváření indexu. Specifikovat je možné všechny čtyři úrovně. Významným parametrem je omezující obdélník, ve kterém se naše objekty nachází. Pokud specifikujete příliš velký obdélník, zbytečně plýtváte zdroje na plochu, kde nikdy nic nebude. Na druhou stranu příliš malý obdélník může být později omezující. Souřadnice omezujícího obdélníku jsou přirozeně povinné. Naštěstí je možné index vytvořit znovu, případně vytvořit jiný. Stejně jako pro geometry, i pro geography je možné specifikovat hustotu mřížky, kterou je zeměkoule, či lépe elipsoid, rozřezán. Nicméně právě na zeměkouli nabízí, podle mě, mnohem větší možnosti pro hraní. Samozřejmě neexistuje univerzální správné pravidlo. Záleží, jaké objekty ukládáte (např. hranice hlavních měst států vs. souřadnice křížení potrubí vodovodní sítě) a také jaké dotazy nad nimi provádíte. Je rozdíl v dotazu na města v daném obdélníku v měřítku kontinentů a hledání nejbližších objektů v pohledu vesnic. Avšak díky možnosti vytvořit více indexů, s různou hustotou mřížky v různých úrovních, není problém dobře postihnout většinu typů dotazů, které musí databázový stroj řešit. Mimo těchto několika základních parametrů můžete použít ještě několik další parametrů, některé jsou jako u normálních indexů jiné specifické. Pro kompletní popis doporučujeme navštívit dokumentaci.