LINQ a zpracování záznamu trasy navigačního programu

Doc. Dr. Vladimír Homola, Ph.D.

Anotace

Článek popisuje jednu z možností, jak běžně dostupným programovým vybavením vyhodnotit záznam trasy, který je výstupem navigačního programu. Ukazuje současně některé matematické aspekty výpočtů na eliptických plochách, přístup k datům v sešitu (workbook) a v souborech XML, a zpracování dat v relačních databázích.  Používá k tomu jednak klasický VBA dostupný téměř ze všech aplikací, jednak produkt Visual Studio 2008 Express Edition uvolněný firmou Microsoft v prosinci 2007 k bezplatnému stažení. Tento produkt obsahuje dosti podstatné změny především v syntaxi jazyků Basic a C, které nyní mohou svými jazykovými konstrukcemi přistupovat přímo k datům ve čtyřech základních kategoriích: ADO, SQL, XML a obecná množina reprezentovaná rozšířením objektu dříve známého jako Kolekce. K tomu bylo zavedeno rozšíření programovacích jazyků označované jako LINQ (Language-Integrated Query).

Annotation

The article discusses one of possibilities of data logger's track-log evaluation. The VBA and the release of Microsoft Visual Studio 2008 Express Edition is used to data access in workbooks, XML files, and databases. There are fairly significant changes in Basic and C-language syntax to guarantee the direct data access via ADO, SQL, XML, and common objects - all of this through the own language syntax. The Language Integrated Query (LINQ) is the new tool to realize the mentioned data access.

Terminologie

Terminologie
Termín Termín anglicky Význam
Bod (trasy, GPS)  Trace point Časová a prostorová informace o jednom bodu prostoru dodaná navigačním systémem.
Trasa  Trace, Trace Log Časové a prostorové informace o místech výskytu sledovaného subjektu dodané navigačním systémem = posloupnost bodů v pořadí vzrůstajícího časového atributu.
Místo (silnice, cesty)  Point of road Prostorová, místní a další informace o jednom místě konkrétní silnice.
Silnice  Road Posloupnost míst na ní v pořadí vzrůstající kilometráže.
Cesta  Trip Časové, prostorové a místní informace s dalšími doprovodnými údaji generalizující absolvovanou trasu.

Popis problematiky

Civilizovaný člověk se pohybuje nějakým územím - zvláště v soukromém motorovém vozidle, byciklu a podobném - jen po komunikacích k tomu určených. Moderní civilizovaný člověk při takovém pohybu používá navigační systém, i když danou trasu absolvuje po sté. Navigační systémy kromě plánování před cestou a vlastní navigaci při cestě, umožňují o skutečně absolvované trase cesty informovat po jejím ukončení. A právě informace nikoliv o plánu, ale o skutečnosti je pro mnoho uživatelů tím daleko nejzajímavějším. Jde o to, aby v takovém případě dostal uživatel ty informace a v takové podobě, aby vyhovovaly právě jeho potřebám.

Způsob informování je dán konkrétním software konkrétního navigačního systému. Především ne každý navigační systém funguje jako data logger - zařízení s funkcí průběžného záznamu dat. Systémy, které tuto funkci mají, však nemusí mít ještě možnost exportu zaznamenané trasy - mohou záznam využívat jen pro prezentaci nebo konkrétní statistiky na tom zařízení, na kterém byly pořízeny. A konečně ty systémy, které mají možnost exportu, vytváří výstupní soubory v různých formátech s různou kvalitou informací. Společné mají to, že lze nastavit interval mezi jednotlivými záznamy - časový interval vždy (např. každých 5 vteřin), u některých i interval daný vzdáleností (např. každých 500 metrů).

Formát výstupu lze dnes vidět ve dvou základních podobách: výstup bázovaný XML a výstup jiný - přičemž ty první převládají. Jde např. o formát KML (Keyhole Markup Language), GML (Geography Markup Language), GeoXACML (Geospatial eXtensible Access Control Markup Language), GPX (GPS Exchange Format) a další. Zástupcem jiných formátů je např. NMEA, SiRF apod. V tomto případě jde spíše o protokoly přenosu dat z primárních zdrojů (GPS přijímače) do zpracovávajících systémů (navigačních a jiných). Byly často definovány výrobci nebo majoritními uživateli GPS technologií -  např. NMEA  (National Marine Electronics Association), Sirf (SiRF Technology Holdings, Inc.) a dalšími.

Ve všech shora zmíněných případech jde o formáty formalizované, definované a uložené většinou v elektronické podobě, u XML včetně schémat. Víceméně všechny rovněž mají možnost obsáhnout snad všechny druhy informací, které by koncového uživatele mohly potenciálně zajímat. Ovšem snad žádný navigační systém nemá možnost definovat strukturu exportovaných dat - ta je dána těmito systémy napevno a v naprosté většině obsahuje jen mírně transformovaná nebo předzpracovaná data přijímače GPS: datum, čas, zeměpisnou šířku a délku a případně nadmořskou výšku; na jejich základě lze odvodit rychlost mezi dvěma záznamy a směr pohybu.

Pro běžného uživatele data v takové kvalitě nejsou k ničemu. Běžný uživatel nepotřebuje vteřinu po vteřině vědět, že se vyskytoval na takových a takových souřadnicích. Potřebuje vědět, že tehdy a tehdy byl v Litomyšli, v tolik a tolik v Mejtě, že z Litomyšle to bylo 14,4 kilometrů které ujel za 6 min 48 sec tj. rychlostí 127 km/h. A právě řešení této situace je obsahem článku.

Poznámka: Dopřejme si jistého zevšeobecnění: v celém článku se pod označením GPS bude rozumět jakýkoliv funkční, pro navigaci použitelný družicový systém.

Použitá zařízení

Navigační systémy se běžně používají především jako jednoúčelové, jediným zařízením tvořené systémy pro navigaci v automobilech, na kolech i při pěší turistice. Jsou dostatečně známé a jimi se článek zabývá jen potud, pokud jejich software umožňuje provádět záznam ujeté trasy a její export v souborovém tvaru do počítačového prostředí. Článek se zabývá konkrétně sestavou tvořenou dvěma samostatnými, obecně použitelnými komponentami: PDA a přijímač signálu GPS.

PDA: Fujitsu-Siemens LOOX-718, procesor PXA272 520 MHz, interní paměť 128 MB, MM karta 8 GB, displej 640 x 480 High Color (16 bit), Bluetooth, WiFi, Infra, USB

GPS: i-Tec BT-339, 20 kanálů, 2D/3D, citlivost -130 dBm při s/š > 39 dB, přesnost polohy 5m (2D RMS s WAAS), přesnost času < 1ms, WGS-84, Bluetooth.

Vstupní data

Vstupní data popisované úlohy jsou dvojího druhu:

Data GPS - body trasy

Přijímač GPS komunikuje s PDA přes jeho virtuální COM port, na straně PDA přijímá informace z GPS program iGo My way 2006 Plus firmy Nav-N-Go z května 2007. Program iGo generuje záznam o trase do souboru ve formátu GPX (viz např. www.topografix.com/GPX/1/1, funkční ke dni 30.6.2009) ve velmi jednoduché struktuře:

 



Obr. 1: XML schéma GPX souboru

 

nebo v XML zápise - příklad:

<gpx version="1.0" creator="iGO 2006" ... >
  <trk>
    <trkseg>
      <trkpt lat="50.64061499" lon="13.82354665">
        <time>2007-11-02T10:30:15Z</time>
      </trkpt>
    </trkseg>
    <trkseg>
        ...
    </trkseg>
    ...
  </trk>
</gpx>

 

Program iGo je schopen paralelně generovat i záznam ve formátu NMEA (viz např. http://www.gpsinformation.org/dale/nmea.htm, funkční ke dni 30.6.2009). Ten obsahuje navíc informace o rychlosti, směru, nadmořské výšce a počtu družic. Protože však jde o textový soubor běžného typu comma delimited, není z hlediska zaměření tohoto článku zajímavý a nebude popisován.

Data XLS - místa silnic

Pro identifikaci míst (silnic, území) je použita tabulka relační databáze, primárně umístěná jako pojmenovaná oblast v sešitu Excelu tvořící seznam. Obsahuje informace o místech daného území (především o místech konkrétní silnice resp. silničního spoje, avšak není podmínkou). Struktura tabulky (již importovaná do databázového programu) je následující:

 

Struktura tabulky MÍSTA
Sloupec Typ Obsah
Sl TEXT (6) Identifikátor silnice
KM DOUBLE Kilometráž místa na dané silnici
ID - primární klíč TEXT (5) Jedinečný identifikátor - kód místa
JM TEXT (96) Textové označení místa
TP TEXT (2) Typ místa: křižovatka, čerpačka ...
RD BYTE Rychlost do místa z místa předchozího
EN INTEGER Exit number pro dálnice a rychlostní silnice
KR TEXT (5) Kód místa na křížící silnici
UZ TEXT (1) Indikace že místo je uzlem - znak "U"
SM TEXT (1) Směr k objektu na trase rostoucí kilometráže
PI TEXT (1) Point of interest - znak "x"
LA DOUBLE Latitude - zeměpisná šířka
LO DOUBLE Longitude - zeměpisná délka

 

V popisované aplikaci jsou použita data pouze z tučně modře označených sloupců: ID, JM, LA a LO. Ostatní slouží pro použití v jiných aplikacích autora. Příklad části takové tabulky je uveden níže; jde o výňatek dat použitých ve specifikaci cílů (viz následující odstavec).

 

Část tabulky MÍSTA - příklad
SI  KM  ID  JM  TP  RD  EN  KR  UZ  SM  PI  LA  LO
I17  27,667  VJCHR  Vjezd Chrudim  ob  80          x  49,953988  15,751418
I17  30,509  CHTRA  Rondel u Transporty  kr  40            49,952092  15,787743
I17  31,897  CHI37  Chrudim \ nahoru / I37  kr  30        P    49,941104  15,794650
I17  33,766  CHRKA  Rondel u Kauflandu  kr  40          x  49,946927  15,809012
I17  36,807  VJKOC  Vjezd Kočí  ob  80          x  49,949043  15,849568
I17  37,541  VYKOC  Výjezd Kočí  ob  50          x  49,951046  15,859152
I17  41,247  VJHTY  Vjezd Hrochův Týnec  ob  80          x  49,958627  15,907651
I17  41,982  VYHTY  Výjezd Hrochův Týnec  ob  50          x  49,961176  15,916942
I17  43,127  VJCAN  Vjezd Čankovice  ob  80            49,962363  15,932706
I17  43,846  VYCAN  Výjezd Čankovice  ob  50            49,965020  15,941221
I17  48,874  VJMES  Vjezd Městec  ob  80            49,970706  16,006408
I17  49,075  VYMES  Výjezd Městec  ob  50            49,971150  16,009067
I17  50,232  VJOST  Vjezd Ostrov  ob  80          x  49,971926  16,025218
I17  51,024  VYOST  Výjezd Ostrov  ob  50          x  49,971804  16,035982
I17  52,958  VJSTR  Vjezd Stradouň  ob  80            49,969215  16,062156
I17  53,442  VYSTR  Výjezd Stradouň  ob  50            49,969411  16,068736
I17  57,255  17X35  Zámrsk x R35  kr  80    35X17  U    x  49,984338  16,115406
R35  139,612  CHV35  Chvojenec  kr  30    CHVOJ        50,109642  15,936816
R35  144,822  THOAG  Holice Agip  ce  80        L    50,074302  15,981162
R35  159,407  35X17  R35 x I/17  kr  80    17X35  U  P    49,984343  16,115413
R35  162,703  VYSMK  Vysoké Mýto Karosa  kr  80          x  49,961743  16,145112
R35  164,807  VYSMP  Vysoké Mýto Plus  kr  35            49,946169  16,161385
R35  165,347  TSHVM  Shell Vysoké Mýto  ce  35        L x  49,942085  16,165461
R35  168,795  HRUVJ  Hrušová vjezd  ob  90         x  49,916734  16,193754
R35  169,736  HRUVY  Hrušová výjezd  ob  50         x  49,909948  16,198086
R35  171,546  OCERE  Exit / Cerekvice  kr  90         x  49,899460  16,224847
R35  177,328  LITVJ  Litomyšl vjezd  ob  90         x  49,882808  16,293059
R35  179,738  LITOS  Litomyšl světla centrum  kr  40         x  49,867365  16,312915
R35  180,754  LITVY  Litomyšl výjezd  ob  50            49,864188  16,324427
R35  189,683  SV366  Exit na Svitavy / II366  kr  80        P    49,813816  16,417881

 

Objem dat

Data dodaná navigačním systémem jsou [délka;šířka] systému WGS-84 a údaj [datum;čas]. Interval snímkování je nastavitelný. Předpokládejme trasovanou vzdálenost o velikosti šířky celé České republiky, tj. zhruba 500 km. Předpokládejme průměrnou rychlost kolem 50 km/hod. V tom případě jde o 10 hodin plynulého záznamu. Při intervalu snímkování 1 sec jde o 36 000 záznamů. Při struktuře (textový formát) dle předchozího odstavce jde zhruba o 90 znaků (= 90 bytů) na jeden záznam. Celkový objem dat pro 10-hodinový záznam je tedy nejvýš 3,5 MB, což při gigabytových kapacitách dnešních paměťových karet je zcela přijatelné. Jde přitom o data jednotlivých míst, časově vzdálených 1 sec.

Definice cíle

Definujme nyní, čeho chceme dosáhnout - a co tím chceme ukázat.

V úvodu kapitoly Popis problematiky jsou cíle zhruba naznačeny. Situace je následující:

 


Obr. 2: Schematická mapa části trasy

 

Jedu - úměrně dopravní situaci - po nějaké trase a navigační systém důsledně zaznamenává vždy po uplynutí stanoveného časového intervalu polohu a družicový čas. Mám připravený seznam míst na jednotlivých silnicích a po absolvování celé cesty mně zajímá skutečnost: místo od místa, kdy jsem je projel, jaká je vzdálenost jednoho od druhého, jakou rychlost lze dosáhnout mezi každými dvěma z nich. Tyto informace mně postačují např. v podobě tabulky relační databáze ať už v sešitu tabulkového procesoru nebo v klasické databázi (pozn.: vjezd a výjezd je orientován ve smyslu kilometráže dané silnice):

 

Tabulka SKUTEČNOST
SIL  KMS  KMC  IDM  JMM  TMC  DST  SPD  TMP
... ... ... ... ... ... ... ... ...
R35  179,738  251,100  LITOS  Litomyšl světla centrum  11:55:01  0,893  76,5  0,7
R35  177,328  253,520  LITVJ  Litomyšk vjezd  11:57:33  2,420  57,3  2,5
R35  171,546  258,990  OCERE  Exit ® Cerekvice  12:01:08  5,470  91,6  3,6
R35  169,736  261,211  HRUVY  Hrušová výjezd  12:02:24  2,221  105,2  1,3
R35  168,795  262,018  HRUVJ  Hrušová vjezd  12:03:09  0,807  64,6  0,8
R35  165,347  265,502  TSHVM  Shell Vysoké Mýto  12:05:35  3,484  85,9  2,4
R35  162,703  268,395  VYSMK  Vysoké Mýto Karosa  12:16:03  2,100  58,2  2,2
I17  57,255  271,690  17X35  Zámrsk x R35  12:18:45  3,295  73,2  2,7
I17  51,024  277,918  VYOST  Výjezd Ostrov  12:22:23  1,936  101,0  1,1
I17  50,232  278,704  VJOST  Vjezd Ostrov  12:23:10  0,786  60,2  0,8
I17  41,982  286,981  VYHTY  Výjezd Hrochův Týnec  12:29:33  1,150  94,1  0,7
I17  41,247  287,706  VJHTY  Vjezd Hrochův Týnec  12:30:24  0,725  51,2  0,8
I17  37,541  291,410  VYKOC  Výjezd Kočí  12:32:37  3,704  100,3  2,2
I17  36,807  292,159  VJKOC  Vjezd Kočí  12:33:22  0,749  59,9  0,7
I17  33,766  295,200  CHRKA  Chrudim rondel u Kauflandu  12:35:11  3,041  100,4  1,8
... ... ... ... ... ... ... ... ...

 

kde údaje v jednotlivých sloupcích znamenají

 

Struktura tabulky SKUTEČNOST
Sloupec Typ Obsah
SlL TEXT (6) Identifikátor silnice
KMS DOUBLE Kilometráž místa na dané silnici
KMC DOUBLE Ujeté kilometry od začátku cestu
IDM TEXT (5) Jedinečný identifikátor - kód místa
JMM TEXT (96) Textové označení místa
TMC DATE Okamžik průjezdu místem
DST DOUBLE Vzdálenost z předchozího místa
SPD DOUBLE Rychlost z předchozího místa
TMP DOUBLE Čas z předchozího místa

 

Cíle jsou tedy dány tabulkou ekvivalentní shora uvedeným příkladem SKUTEČNOST. Při směřování k tomuto cíli chce článek ukázat

Vzdálenost dvou míst

Vzdálenost dvou míst (dále A a B) zaznamenané trasy je důležitá nejen z hlediska přesného určení celkové délky trasy i aktuální rychlosti. Kardinální význam má při stanovení nejbližšího známého místa. Případná chyba při určení vzdálenosti dvou bezprostředně po sobě zaznamenaných míst se kumuluje do výpočtu celkové vzdálenosti.

Mezní případy

A. Rychlost je nula

Problém v tomto případě spočívá v tom, že přijímač GPS nedodává při konstantní poloze stále stejné souřadnice. Z technických důvodů se dvě následující informace o poloze mohou lišit o jistou elementární jednotku diference. U použitého přijímače i-Tec BT-339 to činí cca 0.006" jak pro šířku, tak pro délku. Pro méně příznivý případ (tj. zeměpisnou šířku) to činí cca 18 cm. Při době stání např. 10 minut to činí v nejnepříznivějším případě 100 metrů.

B. Rychlost je velmi malá

Ve vzorcích sférické trigonometrie se vyskytují goniometrické funkce úhlových rozdílů dvou poloh. Obvyklá hardwarová reprezentace necelých čísel je single / real / double precision floating point (číslo v pohyblivé řádové čárce v jednoduché / zvýšené / dvojnásobné přesnosti).

V jednoduché přesnosti (model o interní délce 4 byty) se lze spolehnout na 7 platných cifer. Nejbližší přesně zobrazené číslo k 1 je tedy 0.9999999, což je cos (92"). Je-li to ovšem rozdíl úhlů dvou míst o časovém rozdílu jedné vteřiny, pak to ve směru rovnoběžky představuje rychlost 9900 km/hod! Použití tohoto číselného modelu tedy nepřichází v úvahu.

Ve zvýšené přesnosti (model o interní délce 6 bytů) se lze spolehnout na 11 platných cifer. Analogicky předchozí úvaze představuje úhlový rozdíl za jednu časovou vteřinu rychlost asi 31 km/hod. Je zřejmé, že reálné rychlosti jsou nižší, proto ani tento model nelze obecně použít.

Ve dvojnásobné přesnosti (model o interní délce 8 bytů) se lze spolehnout na 17 platných cifer. Analogicky předchozím úvahám představuje úhlový rozdíl za jednu časovou vteřinu rychlost asi 0,99 km/hod. Naopak např. při rychlosti 1 m/sec = 3.6 km/hod tento rozdíl pro méně příznivý případ (pohyb podél rovnoběžky) činí cca 1/20" = 0.05". Hodnota cos(0.05") = 0.999 999 999 999 971, což lze v daném číselném modelu zobrazit přesně.

C. Rychlost je velmi velká

Problém by v tomto případě mohl být s trajektorií - viz následující obrázek zatáčky. Pokud by dva následující okamžiky vzorkování padly do začátku a konce zatáčky, pak je zaznamenaná vzdálenost asi o 11% menší než skutečná.

 


Obr. 3: Chyba trajektorie

 

Elipsoid, koule, rovina

Je zřejmé, že korektní postup výpočtu by vedl k určení vzdálenosti dvou míst na povrchu rotačního elipsoidu. Jimi a středem Země je dána rovina, jejíž průsečnice s povrchem elipsoidu je elipsa. Délka jejího eliptického oblouku vymezeného dvěma danými body je jejich hledaná vzdálenost. Tento postup ovšem vede k eliptickým integrálům, jejichž výpočet v množství rovném počtu zaznamenaných bodů trasy by enormně zpomaloval výpočet.

V geodézii je obecně přijímána v jistém - vzhledem k rozměrům elipsoidu malém - okolí daného bodu aproximace povrchu elipsoidu povrchem koule nebo dokonce rovinou. Jako ono "malé" okolí bývá přijímána hodnota 20 [km]. V popisované úloze jde o okolí nanejvýš 50 [m] - pro rychlost 180 [km/h] a interval vzorkování 1 [sec]. Aproximace kulovou plochou nebo rovinou je tedy zcela odůvodněná.

Elipsoid

Uvažujme nejprve skutečně referenční elipsoid. Pro jednoduchost uvažujme dvě místa A a B na stejném poledníku. Budeme zjišťovat délku poledníkového oblouku s = s(A,B) a celkově směřovat k její náhradě např. přímou vzdáleností u = u(A,B) - tedy tětivou elipsy, sférickou vzdáleností apod.

 



Obr. 4: Eliptický, kruhový, úsečkový úsek mezi dvěma body

 

Parametrické rovnice elipsy jsou

a protože pro délku s oblouku křivky obecně platí

tedy konkrétně pro elipsu o poloosách a resp. b

Je tedy

což pro substituci

(bez újmy na obecnosti můžeme položit a<b) dává

Jest ovšem

neúplný eliptický integrál druhého druhu, a pro hledanou délku oblouku s(A,B) je tedy

Pro neúplný eliptický integrál druhého druhu (po integraci rozvojem na řady) platí

 

                     (1)

kde

a

a dále bylo označeno

Diskutujme určení vzdálenosti tímto exaktním postupem. To bude probíhat nějakou počítačovou aplikací. Standardní, obecně použitelné datové modely s největší přesností odpovídají definici double precision floating point a to zaručuje přesnost na max. 17 platných cifer (viz např. Excel). Z tohoto ohledu však řada (1) nepříjemně rychle konverguje. Člen

v součtu (1) se - pro k odpovídající WGS-84 - neuplatní již pro n=4:

 

Několik členů řady (1)
n P=k^2n R=(-0,5) nad n P*R
1 4,5E-05 -5,0E-01 -2,2E-05
2 2,0E-09 -1,3E-01 -2,5E-10
3 9,0E-14 -6,3E-02 -5,6E-15
4 4,0E-18 -3,9E-02 -1,6E-19
5 1,8E-22 -2,7E-02 -4,9E-24
6 8,1E-27 -2,1E-02 -1,7E-28
7 3,6E-31 -1,6E-02 -5,8E-33
8 1,6E-35 -1,3E-02 -2,1E-37
9 7,3E-40 -1,1E-02 -8,0E-42
10 3,3E-44 -9,3E-03 -3,0E-46

 

a pro n=12 už vede k přetečení (resp. podtečení) při práci s číslem v pohyblivé řádové čárce. Pro představu: při výpočtu délky zemského kvadrantu je výsledek spočtený Excelem (resp. jinými aplikacemi užívajícími datový typ double) o 0.16% větší než skutečnost - na délku to činí 16.7 [km].

Existují některé algoritmy výpočtu neúplného eliptického integrálu druhého druhu (např. algoritmus aritmeticko - geometrického průměru) eliminující zmíněnou nevýhodu, ovšem za cenu náročnějšího výpočtu - příklad použití viz dále.

Kulová plocha

Při aproximaci kulovou plochou v blízkém okolí bodu P je vhodné zvolit takovou kulovou plochu, která má stejnou křivost - přesněji, která se v bodě P dotýká eliptické plochy a má poloměr rovný poloměru křivosti eliptické plochy v bodě dotyku. V bodě dotyku však má rotační elipsoid příčný poloměr křivosti (označovaný N) a meridiánový poloměr křivosti (označovaný M). Dále se často používá střední poloměr křivosti (označovaný R) jako geometrický průměr příčného a meridiánového poloměru:

R2 = M . N

Označíme-li a resp. b délku hlavní resp. vedlejší poloosy, e první excentricitu a W první geodetickou funkci, je

e2 = 1 - b2 / a2

W2 = 1 - e2 . sin2j

M = (1 - e2) . a / W

N = a / W

Jsou ovšem pro konkrétní elipsoid všechny poloměry křivosti i první geodetická funkce funkcí šířky:

M = M(j)

N = N(j)

R = R(j)

W = W(j)

Níže je uveden obrázek znázorňující situaci na elipsoidu, jsou rovněž uvedeny vzorce pro výpočet příčného poloměru křivosti N a převod na pravoúhlé souřadnice bodu ze souřadnic geografických [l;j].

 



Obr. 5: Bod na povrchu elipsoidu se souvislostmi

 

Srovnání výpočtem

Ověřme nyní na několika málo hodnotách přímým výpočtem jednotlivé možnosti. Víme, že jedna úhlová vteřina na poledníku je velmi zhruba 30 [m]. Rychlost 180 [km/h] je rovna 50 [m/sec]. Při intervalu vzorkování jedné časové vteřiny určitě neujedeme více než 60 metrů, což jsou zhruba dvě úhlové vteřiny. Porovnejme proto různé způsoby výpočtu vzdálenosti dvou míst A, B na stejné meridiánové elipse, jejichž rozdíl šířky jsou dvě úhlové vteřiny.

Označme u délku úsečky AB, s délku eliptického oblouku AB, P bod na elipse se šířkou rovnou průměru šířek bodů A a B. Označme dále Rs velikost průvodiče bodu Rs, N příčný poloměr křivosti v bodě P, C střed elipsy a O průsečík normály v bodě P s přímkou malé poloosy.

 



Obr. 6: Různé možnosti spojnice dvou bodů

 

Počítejme s dle matematických vztahů v odstavci Elipsoid. Počítejme s(AG) rovněž podle těchto vztahů, ale algoritmem aritmeticko - geometrického půměru. Počítejme u podle Pythagorovy věty. Počítejme s(N) jako délku oblouku kružnice s poloměrem N a středovým úhlem AOB (v barvě - modrá kružnice). Počítejme s(R) jako délku oblouku kružnice s poloměrem Rs a středovým úhlem ACB (v barvě - červená kružnice). Počítejme to vše pro šířku bodu B po řadě 10, 20 až 89 stupňů.

Výpočty byly prováděny v Excelu formulemi v jeho buňkách; pro výpočty eliptickými integrály se ovšem ve formulích volaly vlastní naprogramované funkce. Výsledek nejlépe znázorní graf:

 


Obr. 7: Graf délek dvouvteřinových oblouků počítaných různými metodami

 

Především křivka s dokumentuje evidentně chybějící další členy rozvoje dle vzorce (1) shora s jednoznačným závěrem: tento způsob prakticky použít nelze.

Další zajímavý fakt poskytuje skutečnost, že výpočet přímé vzdálenosti a obou sférických dávají prakticky totožný výsledek. Potvrzuje se tím, že v blízkém okolí bodu kulové plochy lze povrch koule považovat za rovinu?

Ovšem nejzajímavější je poslední křivka s(AG), tedy výpočet prováděný eliptickým integrálem metodou aritmeticko - geometrického průměru. Z grafu to zdaleka není patrné, ale uvažujme: dotyková koule na pólech má poloměr rovný malé poloose. Dotyková koule na rovníku má poloměr rovný velké poloose. Protože délka oblouku kružnice s poloměrem R a se středovým úhlem a je rovna R . a, měly by být poměry délek oblouků se stejným středovým úhlem na polární a rovníkové kouli ve stejném poměru jako poměr poloos. Pro WGS-84 je poměr a : b roven 1.003364. A pro křivku s(AG) je poměr délek oblouků na šířkách 90o a 00 roven 1.003326 - tedy vynikající shoda s předpokladem. Odtud je velmi blízko k tvrzení, že právě tato křivka nejvíce odpovídá skutečnosti.

Pro tři zbývající, téměř totožné křivky je tento poměr 1.0101. Porovnáme-li je s křivkou s(AG), jsou od ní rozptýleny v rozmezí ±19 [cm] na vzdálenosti 60 [m], což je  ±0.3%. Nejmenší odchylka je kolem 45o zeměpisné šířky. Pro oblast ČR je odchylka 0.06%, co je 3.5 [cm] na 60 [m] délky. Ještě jinak: kdybych prolétl rychlostí 200 [km/h] přímou vzdálenost z Aše k Shellce u Čendy, pak je při (časově) jednovteřinovém snímání polohy diference 252 [m].

Je však třeba si uvědomit, že tyto diference jsou maximální a v reálném provoze jsou nejméně poloviční. Se snižující se úhlovou vzdáleností dvou bodů A, B se totiž úsečka AB čím dál víc přibližuje plášti elipsoidu.

Rovina

Po tom, co bylo zjištěno v předchozím odstavci, je zcela na místě uvažovat o nahrazení povrchu elipsoidu v blízkém okolí daného bodu rovinou.

Při aproximaci rovinou v blízkém okolí bodu P je vhodné zvolit lokální tečnou rovinu (Local Tangent Plane) a zavést sekundární soustavu souřadnou celkem přirozeným způsobem: osa Z je totožná s normálou tečné roviny a tedy i elipsoidu samotného, osy X a Y leží v tečné rovině. I jejich orientace je taková, jakou lidé na povrchu Země očekávají: osa X ve směru západ - východ, osa Y ve směru jih - sever.

Následující obrázky takovou situaci znázorňují:

 

Obecný náhled Znázornění v rovině Vztahy


Obr. 8: Tečná rovina se soustavou souřadnou

 

Vzdálenost dvou blízkých bodů A a B lze pak určit takto:

Obecně jsou čáry mřížky na předchozím obrázku velmi složité křivky. Vzdálenost AB však je řádově do 100 metrů a proto můžeme mřížku považovat v naznačeném okolí za pravidelnou obdélníkovou síť. Diference šířek resp. délek se pohybuje kolem jednotek úhlových vteřin. Zjistíme-li délku jednovteřinového oblouku ve směru poledníku a  délku jednovteřinového oblouku ve směru rovnoběžky, pak po vyjádření diferencí délek a šířek v délkových (místo úhlových) jednotkách lze přímo aplikovat Pythagorovu větu.

Povrch elipsoidu pro uvedený účel nahraďme dotekovou koulí v bodě P s poloměrem rovným příčnému poloměru křivosti N. Jednovteřinový oblouk na poledníku pak bude mít délku p . N / (180 . 3600). Protože poloměr rovnoběžkové kružnice na šířce j je N . cos(j), je jednovteřinový oblouk na rovnoběžce roven p . N . cos(j) / (180 . 3600).

Obecnější úlohu - zjištění poloměrů rovnoběžkové kružnice a dotekové koule pro bod o dané zeměpisné šířce - řeší např. následující funkce:

Public Function PolomeryVsirceN _
      (ByVal qSirkaStupnu As Double, _
       Optional ByVal qA As Double = cPoloosaA, _
       Optional ByVal qFr As Double = cZplosteniRec) _
   As Double()

   Dim V(0 To 1) As Double    ' Pole pro výsledek: (0)-pro rovnoběžku (1)-pro kouli
   Dim a As Double            ' Velká poloosa
   Dim b As Double            ' Malá poloosa
   Dim psi As Double          ' Šířka v radiánech
   Dim eps2 As Double         ' eps2 = 1 - b^2/a^2
   Dim CosPsi As Double       ' cos (psi)
   Dim Sin2psi As Double      ' sin^2 (psi)
   Dim W As Double            ' První geodetická funkce
   Dim N As Double            ' Příčný poloměr křivosti
   Dim rR As Double           ' Poloměr rovnoběžkové kružnice

   a = qA
   If qA = cPoloosaA And qFr = cZplosteniRec Then
      b = cPoloosaB
      eps2 = cEps2
   Else
      b = qA * (qFr - 1#) / qFr
      eps2 = 1# - (b * b) / (a * a)
   End If

   psi = qSirkaStupnu * cDegToRad
   CosPsi = Cos(psi)
   Sin2psi = 1 - CosPsi * CosPsi

   W = Sqr(1 - eps2 * Sin2psi)
   N = a / W
   rR = N * CosPsi

   V(0) = rR                  ' Polomér rovnoběžky v bodě
   V(1) = N                   ' Polomér koule v bodě

   PolomeryVsirceN = V

End Function

Funkci je možno použít pro jakýkoliv elipsoid; nezadají-li se druhý a třetí parametr, implicitně počítá na WGS-84. Konstanty typu cXX jsou definovány na globální úrovni následovně:

' Konstanty - WGS-84:
Public Const cPoloosaA As Double = 6378137                 ' Hlavní poloosa [m]
Public Const cPoloosaB As Double = 6356752.31424518        ' Vedlejší poloosa [m]

Public Const cZplosteniRec As Double = 298.257223563       ' Reciproká hodnota zploštění
' Lze také Public Const cZplosteniRec As Double = cPoloosaA / (cPoloosaA - cPoloosaB)

Public Const cExcentricita As Double = 521854.008423385    ' Excentricita [m]
' Nelze však Public Const cExcentricita As Double = sqr(cPoloosaA*cPoloosaA - cPoloosaB*cPoloosaB) !!

Public Const cEps As Double = 0.081819190842622            ' První (numerická) excentricita
Public Const cEps2 As Double = cEps * cEps                 ' Kvadrát první excentricity
' Lze také Public Const cEps2 As Double = 1# - (cPoloosaB * cPoloosaB) / (cPoloosaA * cPoloosaA)

Public Const cPi As Double = 3.14159265358979              ' Ludolfovo číslo
Public Const cDegToRad As Double = cPi / 180#              ' Pro převod stupňů na radiány

Rozšíření ve Visual Studiu 2008

Rozšíření kolekcí

Klasické kolekce známé z Visual Studia 6 obsahovaly pouze metody Count, Add, Remove a Item pro zjištění počtu prvků, přidání a odebrání prvku, a přístup ke konkrétnímu prvku indexem nebo klíčem. Existovala sice objektová třída Dictionary poněkud rozšiřující spektrum metod (např. o zjištění existence klíče nebo řazení), avšak byla primárně určená pro použití ve skriptech a relativně pomalá.

Visual Studio 2008 přináší dosti podstatnou inovaci. Na principu kolekce ponechává obecný interface iEnumerable (rozšířený však o možnost zadat explicitně jednu objektovou třídu, jejímiž instancemi bude kontejner kolekce plněn), zavádí však dále seznam (List) instancí objektů konkrétní třídy; seznam disponuje metodami pro řazení, vyhledávání, filtrování, převody na pole a dalšími. Ukázkou dalšího "množinového" objektu je EnumerableRowCollection instancí objektů konkrétní třídy orientovaná zvláště na databázové zpracování. Na všechny tyto "množinové" objekty lze použít samozřejmě množinový iterátor (např. v Basicu reprezentovaný příkazem For Each).

Rozšíření jazyka

Všechna tato rozšíření byla nutná díky snaze integrovat do programovacích jazyků prvky dotazovacích jazyků. Konkrétně jde o příkaz select jazyka SQL. Zdrojem v tomto příkazu je množina a výsledkem opět množina - množinou se zde rozumí kolekce nebo kterékoliv její rozšíření (některá zmiňuje předchozí text). Specielně autoři těchto inovací dosáhli toho, že zdrojem může být tabulka relační databáze, data podaná jako XML a kolekce nebo seznam jakýchkoliv uživatelem definovaných objektů - a přitom se se všemi takovými zdroji pracuje jednotným způsobem.

Tato jazyková rozšíření jsou označována jako LINQ (line-in query) a článek přináší ukázku jejich použití na konkrétním problému - popisovaném zpracování trasy navigačního programu. Protože jde o podstatné rozšíření, je LINQ věnována níže celá kapitola.

Programové modely vstupních dat

Vstupními daty popisované aplikace jsou - výše popsaná - data GPX a data XLS. Prvním úkolem programové aplikace je tedy získat tato data bod po bodu, místo po místu v programem zpracovatelné podobě. Nabízí se dvě principiální řešení: klasické (jako pole) nebo moderní (jako kolekce). Z pochopitelných důvodů bylo zvoleno řešení moderní.

Data GPX - body trasy

Data generovaná navigačním programem mají XML (resp. GPX) formát popsaný výše. Logicky tedy je modelem dat jednoho bodu trasy objekt třídy nazvané clBodGPS.

UML schéma objektové třídy:

Zápis ve VB-2008:

Public Class clBodGPS         ' Jedno místo a čas průjezdu = bod z GPS.
   Public Lon As Double       ' Zeměpisná délka
   Public Lat As Double       ' Zeměpisná šířka
   Public Tim As Date         ' Datum a čas průjezdu
End Class
 

Všechny body trasy pak tvoří kolekci

Dim Body As IEnumerable (Of clBodGPS)

ve které žádné dva prvky nemají stejnou hodnotu datového pole Tim - to vyplývá ze způsobu záznamu navigačním programem. Naplnění kolekce Body je pak obsahem následující kapitoly.

Data XLS - místa silnic

Data připravená uživatelem v sešitu Excelu a tvořící pojmenovanou oblast jsou tabulkou relační databáze. Její databázová struktura je popsána v kapitole výše. Tomu odpovídá datový model, ve kterém jeden řádek = jeden záznam tvoří instanci objektové třídy nazvané clMistoSilnice.

UML schéma objektové třídy:

Zápis ve VB-2008:

Public Class clMistoSilnice
   ' Jedno místo / uzel z databáze míst; tou je pojmenovaná oblast v sešitu Excelu.
   Public SI As String               ' Kód silnice
   Public KM? As Double              ' Kilometráž silnice
   Public ID As String               ' Identifikace místa
   Public JM As String               ' Jméno místa
   Public TP As String               ' Typ místa
   Public RD? As Double              ' Rychlost do místa
   Public EN? As Double              ' Exit Number
   Public KR As String               ' Křižovatka - kód místa na jiné silnici
   Public UZ As String               ' Indikace zda jde o uzel
   Public SM As String               ' Umístění ve směru silnice
   Public PI As String               ' Point of Interest
   Public LA? As Double              ' Latitude
   Public LO? As Double              ' Longitude
End Class

Poznámka: Ve Visual Studiu 2008 indikuje znak ? (otazník) přípustnost null hodnoty - tj. případ, že daný údaj není zadaný.

Všechna místa pak tvoří seznam

Dim Mista As List (Of clMistoSilnice)

Všechna místa tvoří seznam (list - na rozdíl od bodů GPX, viz výše); k místům je totiž někdy potřeba přistupovat přímo, je potřeba je mít řazené (např. podle kilometráže silnic) a to všechno seznam zajišťuje svými metodami. Naplnění kolekce Místa je pak obsahem následující kapitoly.

Programové modely výstupních dat

Jak bylo řečeno v kapitole Definice cíle (viz), postačujícím informačním výstupem o skutečně absolvované trase je tabulka relační databáze o shora uvedené struktuře. Jejím programovým zdrojem je kolekce instancí objektové třídy, kterou nazvěme např. clSkutecnost.

UML schéma této objektové třídy:

Zápis ve VB-2008:

Public Class clSkutecnost
   Public SIL As String              ' Identifikátor silnice
   Public KMS As Double              ' Kilometr silnice
   Public KMC As Double              ' Kilometr cesty
   Public IDM As String              ' Identifikátor nejbližšího místa
   Public JMM As String              ' Textové označení nejbližšího místa
   Public TMC As Date                ' Čas průjezdu bodem cesty
   Public DST As Double              ' Vzdálenost předchozího a tohoto místa
   Public SPD As Double              ' Rychlost z předchozího do tohoto místa
   Public TMP As Double              ' Čas z předchozího do tohoto místa v [hod]
   Public VZD As Double              ' Vzdálenost bodu průjezdu od nejbližšího místa
   Public DIR As Double              ' Směr z bodu průjezdu k nejbližšímu místu
   Public LAC As Double              ' Latitude bodu cesty
   Public LOC As Double              ' Longitude bodu cesty
   Public LAM As Double              ' Latitude nejbližšího místa
   Public LOM As Double              ' Longitude nejbližšího místa
End Class

Na rozdíl od kapitoly Definice cíle (viz), zahrnuje tato objektová třída další datová pole (tučně, v barvě modře). To, že byla zjištěna totožnost místa bodu průjezdu a místa cesty, není samozřejmě absolutní - viz následující obrázek. Proto na výstupu jsou dodány informace potřebné pro zjištění korelace mezi záznamem a konkrétním místem: jak daleko je zaznamenaný bod od nejbližšího známého místa, jaký je mezi nimi azimut, a mezi jakými vlastně souřadnicemi bylo ztotožnění deklarováno. Pokud by totiž VZD = Vzdálenost bodu průjezdu od nejbližšího místa byla neúměrně velká, pak by to znamenalo s největší pravděpodobností buď absenci skutečného místa nebo jeho nepřesné určení v tabulce MÍSTA.

 



Obr. 9: Situace při rozhodování mezi místy

 

Programové modely aplikačních dat

Při zjišťování, který zaznamenaný bod trasy odpovídá kterému známému místu nezbývá než zkoumat vzájemné kombinace bodů a míst. Takovými vazebními prvky spojující kolekce Body a Místa mohou být instance objektové třídy nazvané clTRxUZ:

UML schéma objektové třídy:

Zápis ve VB-2008:

Public Class clTRxUZ
   Public TM As Date                ' Čas bodu trasy
   Public ID As String              ' Identifikátor místa
   Public VZ As Double              ' Vzájemná vzdálenost bodu a místa
End Class

Vzájemná vzdálenost je pak použita jako kriterium; pro dané místo se hledá bod s nejmenší vzdáleností, která navíc nepřesáhne rozumnou toleranci - např. 50 metrů (viz dále). Dvojice [bod; místo] nalezené uvedeným postupem se zkompletují již se všemi daty dostupnými z jediného místa; tím je instance objektu třídy nazvané clPrujezd:

UML schéma objektové třídy:

Zápis ve VB-2008:

Public Class clPrujezd
   ' Jeden bod průjezdu zkompletovaný s event. údaji nejbližšího místa.
   Public BodGPS As clBodGPS
   Public Vzdalenost As Double
   Public MistoSilnice As clMistoSilnice
   Sub New()
      BodGPS = Nothing
      Vzdalenost = -1
      MistoSilnice = Nothing
   End Sub
End Class

Vzhledem k tomu, že jedna instance této třídy obsahuje dva pointery a jednu hodnotu double (dohromady 16 bytů), je práce se seznamem takových dat velmi rychlá. Navíc, zpřístupněna ve vzrůstající posloupnosti času, tvoří datový základ požadovaných cílových informací. A zastřešením vstupních dat, mezivýsledků a cílové tabulky může být instance objektu třídy clCesta:

UML schéma objektové třídy:

Zápis ve VB-2008:

Public Class clCesta

   Public Body As IEnumerable(Of clBodGPS)
   Public Mista As List(Of clMistoSilnice)
   Public Prujezdy As IEnumerable(Of clTRxUZ)
   Public Cesta As List(Of clPrujezd)
   Public Skutecnost As List(Of clSkutecnost)

   Sub New()
      With Me
         .Body = Nothing
         .Mista = Nothing
         .Prujezdy = Nothing
         .Cesta = Nothing
         .Skutecnost = Nothing
      End With
   End Sub

   Public Function Info() As String
      Dim V As String
      ... Zkompletování textové informace o začátku a konci cesty ...
      V = V + GpsTimeDateFormat(lDatOd) + " ... " + lUzelOd + vbCrLf
      V = V + GpsTimeDateFormat(lDatDo) + " ... " + lUzelDo
      Info = V
   End Function

End Class

Do programovacího jazyka integrované dotazy (LINQ)

Obecný princip

LINQ je především množina vlastností ve Visual Studiu 2008, které rozšiřují výkonnost dotazů do syntaxe programovacích jazyků Studia, tj. Basicu a C. LINQ zavádí do jazyků standardní klauzule pro dotazování a aktualizaci dat, přičemž tato technologie může být rozšířena pro podporu potenciálně jakéhokoliv typu datového zdroje. Visual Studio 2008 obsahuje pro LINQ poskytovatele propojení na kolekce platformy Framework, databáze SQL serveru, objekty třídy DataSet z ADO.NET, a konečně na dokumenty XML.

Myšlenka, na které celá ta inovace stojí, je stařičká. Vidíme ji např. na (nejméně 20 let starém a pořád dobrém) jazyku xBase - jazyku pro programování databází použitém už firmou AshtonTate v databázovém programu dBase III a řadě dalších. Dodnes ho používá firma Fox (dnes již Microsoft Fox) ve svých databázových programech. Tento programovací jazyk má jednak běžné jazykové konstrukce jako jsou deklarace, příkazy, procedury a funkce atd, ale hlavně na úroveň běžných příkazů jazyka postavené příkazy SQL. U Microsoftů teď přišli na to, že i jiné jazyky lze syntakticky rozšířit o konstrukce de facto ekvivalentní nebo přímo totožné jednotlivým klauzulím SQL (viz např. [1]).

Když se ale už do toho pustili, tak to udělali poměrně důkladně. Zatímco xBase je jazyk určený především pro zpracování databází (ale lze v něm programovat i obecně), jazyky C a Basic jsou obecné programovací jazyky (ale má v nich jít programovat i databáze). Zvláště bylo nutno řešit problém kolize klíčových slov jazyků C a Basic na jedné, a jazyka SQL na druhé straně (viz hned např. SELECT). Dále: již v dřívějších verzích a databázových platformách bylo možno jazyky C a Basic zpracovávat databáze se vším všudy (a je tak možno i nyní). Využívalo se však objektů a jejich metod z příslušných připojovaných knihoven (DAO, ADO atd). Například příkaz CREATE jazyka SQL vykonala metoda Execute objektu Database z knihovny DAO, pakliže se jí dodal kompletní a syntakticky i sémanticky správný text příkazu jako textový řetězec. Jestliže tento řetězec obsahoval nesprávný text příkazu, byla chyba zjištěna až v okamžiku chodu programu. Rozšíření programovacího jazyka o konstrukce typu SQL dovolí přenést minimálně syntaktickou kontrolu do fáze překladu.

Konečně i pohled na data v databázích byl zobecněn. Databáze (rozumí se jejich relační model) jsou z datového hlediska kolekce tabulek a každá tabulka je kolekce řádků. Každý řádek je pak instancí objektu nějaké - definicí tabulky dané - datové struktury. Pro zpracování kolekcí mají však programovací jazyky už dlouho nástroje - Basic např. příkaz FOR EACH. Jestliže tedy půjde tabulku získat jako kolekci řádků (např. SQL příkazem SELECT), pak její zpracování zajistí příkaz typu

FOR EACH řádek IN tabulka
...
NEXT

Jenomže kolekce jsou obecnějším nástrojem: nemusí obsahovat jen objekty třídy "databázový řádek", ale vlastně cokoliv. Vždyť už jen pole (ARRAY) je možno nahlížet a (už dlouho) zpracovávat jako kolekci svých prvků příkazem FOR EACH. Stejně tak text XML definuje kolekce uzlů, elementů, atributů atd. Je však také pravda, že je dosti podstatný rozdíl ve způsobu získání kolekce obecných objektů, řádků databází a informací XML. Proto mezi LINQ jako takovým a konkrétní strukturou dat stojí čtyři různá propojení (obsažená ve Framework verze 3.5) podle následujícího schématu:

 


Obr. 10: Postavení LINQ v aplikacích (podle Microsoft Visual Studio 2008)

 

MDF / SDF - Microsoft SQL pomocí LINQ

Určující myšlenkou komponenty .NET Framework verze 3.5, kterou nazvali LINQ to SQL, je správa dat relačních databází jako objektů umístěných na serverech SQL resp. těmito servery spravovaných. Základem jsou ovšem známé dvourozměrné tabulky, jejichž event. provázanost zajišťují společná data jednoho nebo více sloupců. To zásadně nové však je - jak bylo uvedeno v předchozím odstavci - to, že objektový model ekvivalentní datovým strukturám v databázích vytváří a zapisuje do kódu své aplikace programátor - a to prostředky svého programovacího jazyka. Stejně tak příkazy SQL vytváří přímo prostředky tohoto jazyka, nikoliv už zápisem těchto příkazů jako textových řetězců (i když i tato možnost zůstala ponechána). Evidentně tedy došlo i k syntaktickému rozšíření jazyků, ve kterých má být tento nástroj použit - ve Visual Studiu 2008 tedy jazyků Basicu a C.

Tvorba a hlavně základní syntaktická kontrola příkazů SQL přešly tedy z fáze běhu programu do fáze jeho překladu. To je bezesporu významné ulehčení práce programátora při programování a ladění programů. Na druhé straně se však - podle názoru autora tohoto článku - poněkud omezila obecnost a pružnost vývoje a použití databázové aplikace: aby totiž už překladač mohl kontrolovat text programu, musí mít databázové objekty popsány přímo v programu jako objektové třídy, a tedy poměrně staticky. Pak ovšem vývoj aplikace, která by správně reagovala na strukturální změny databází provedené mimo tuto aplikaci, je velmi složitý - ne-li v mnoha případech zcela nemožný. Lze to posoudit hned v následujícím odstavci.

Popis struktury tabulky

Struktura tabulky je dána popisem všech jejich sloupců - to je pohled ze strany databázových systémů. Struktura tabulky je dána popisem datových polí každého řádku - to je pohled ze strany aplikačních programů. Aplikace pohlíží na každý řádek konkrétní tabulky jako na instanci objektu takové objektové třídy, jejíž datová pole (= vlastnosti, properties) jsou ekvivalentní datovým polím záznamu (řádku tabulky) databáze. Pohled databáze a pohled aplikace ztotožnili ve Visual Studiu 2008 tak, že rozšířili popis objektové třídy v aplikačním programu o možnost zadání databázových atributů a vytvořit tak vzájemné mapování mezi objekty databáze a objekty aplikačního programu.

Ukažme tento mechanismus na hypotetické tabulce PRUJEZDY, která databázově odpovídá kolekci instancí objektů shora uvedené třídy clTRxUZ:

<Table(name:="PRUJEZDY")> _
Public Class tbTRxUZ
   <Column(Name:="DatumCas")> Public TM As Date
   <column(DbType:="nvarchar(5)")> Public ID As String
   <Column(Name:="Vzdalenost")> Public VZ As Double
End Class

Definice objektové třídy tbTRxUZ má tedy zápis běžný v syntaxi daného objektového jazyka (zde Basicu, a to v nejjednodušší podobě), doplněný o specifikaci databázových atributů. Stejně tak je možno vytvářet instance ("proměnné") dané objektové třídy a používat je běžným způsobem, např.

Dim JedenPrujezd as tbTRxUZ

Pro účely mapování do databáze je zápis definice třídy doplněn jednak o atribut <table>, jednak o atributy <column>:

V uvedeném příkladu bude tedy instance (proměnná - např. JedenPrujezd) třídy tbTRxUZ asociována s řádkem tabulky PRUJEZDY. Vlastnost ID proměnné bude asociována s hodnotou ve sloupci ID tabulky (není uveden parametr Name atributu Column). Při vytváření tabulky bude tento sloupec vytvořen jako textový s max. počtem pěti znaků. Vlastnost VZ proměnné bude asociována s hodnotou ve sloupci Vzdalenost tabulky. Při vytváření tabulky bude tento sloupec vytvořen pro hodnoty v pohyblivé řádové čárce se dvojnásobnou přesností. Analogicky vlastnost TM a sloupec DatumCas.

Jak atribut <table>, tak atribut <column> mají řadu dalších parametrů.

Ve shora uvedeném příkladu obsahuje databáze ještě tabulku MISTA. Analogicky se tedy definuje třída např. tbMistoSilnice:

<Table(name:="MISTA")> _
Public Class tbMistoSilnice
   <column(DbType:="nvarchar(6)")> Public SI As String
   <Column()> Public KM As Double
   <column(DbType:="nvarchar(5)" primary key)> Public ID As String
   <Column()> Public JM As String
   <Column()> Public LA As Double
   <Column()> Public LO As Double
End Class

Při vytváření této tabulky bude pro sloupec ID vytvořen navíc primární klíč, jména sloupců budou stejná jako identifikátory datových polí objektové třídy.

Popis struktury databáze

Definice "databázové" třídy

Zdrojem všech databázových entit mapovaných přes databázové spojení je objekt třídy DataContext. Přestože jeho instanci lze principielně použít přímo, jeho optimální využití je dosaženo implementací v nějaké objektové třídě, která bude programovým reprezentantem databáze:

Public Class clCestovani
    Inherits DataContext

    Public PRUJEZDY As Table(Of tbTRxUZ)
    Public MISTA As Table(Of tbMisto)
    ... případně další tabulky, pokud mají být také aplikací zpracovány přímo pomocí LINQ

    Public Sub New(ByVal Spojeni As String)
       MyBase.New(Spojeni)
    End Sub

End Class

Instance proměnné této třídy pak reprezentuje celou databázi a přes tuto proměnnou je možno s databází běžně pracovat. Ovšem největší výhodu při zpracování databáze, kterou poskytuje popisovaný LINQ, je možno použít jen pro entity, které jsou obsaženy v definici "databázové" třídy - zde clCestovani. Databáze může totiž obsahovat např. další tabulky, pokud však nejsou explicitně uvedeny v definici příslušné třídy, nelze je přímo pomocí LINQ zpracovat; jejich obsah je nutno zpracovat "postaru".

Spojení na databázi

"Databázová" třída musí definovat svůj vlastní konstruktor New, kterým definuje spojení na vlastní databázi. V nejjednodušším případě volá pouze konstruktor své bázové třídy (tj. DataContext). Parametrem tohoto konstruktoru je textový řetězec, který obsahuje

Kompletní připojovací řetězec má např. tvar, který dodá následující funkce:

Function XdfConnString(ByVal qSoubor As String) As String

   Dim V As String = ""

   V = V + "Data Source=.\SQLEXPRESS;" 
   V = V + "AttachDbFilename=""" + qSoubor + """;"
   V = V + "Integrated Security=True;"
   V = V + "Connect Timeout=30;" 
   V = V + "User Instance=True"

   XdfConnString = V

End Function

Parametr "Data Source" obsahuje identifikaci odvolávaného SQL serveru, pod kterou je přístupný na daném počítači (resp. v jeho systému).

Zpřístupnění např. databáze MO_KL spočívá ve vytvoření instance "databázové" třídy takto:

Dim dbCesta As New clCestovani ("C:\DATA\MO_KL.MDF")

Vytvoření nové a otevření existující databáze

V okamžiku vzniku instance objektu "databázové" třídy (v našem případě clAnalyzy) fyzická databáze existovat může, ale také nemusí. Objektová třída DataContext (zděděná třídou clAnalyzy) obsahuje metodu jednak pro zjištění, zda fyzická databáze existuje, jednak pro vytvoření fyzické databáze dané struktury, pokud neexistuje. Nejjednodušeji to řekne přímo příklad:

Dim dbCesta As clCestovani
...
dbCesta = New clCestovani("MO_KL.MDF")
If Not dbCesta.DatabaseExists Then dbCesta.CreateDatabase
...

Metoda CreateDatabase vytvoří fyzicky soubor MO_KL.MDF, tj. vlastní (samozřejmě z hlediska dat prázdnou) databázi. V ní bude vytvořena tabulka PRUJEZDY (protože její popis je obsažen v deklaraci objektové třídy clCestovani), tabulka MISTA (ze stejného důvodu) a případně další tabulky tam obsažené.

Pokud v okamžiku vzniku instance objektu "databázové" třídy (v našem případě clCestovani) fyzická databáze existuje, pak přístup do jejich dat zajistí přímo metoda New. Jestliže se tedy ví, že databáze skutečně existuje, pak po provedení příkazů

Dim dbCesta As clCestovani
...
dbAnalyzy = New cclCestovani("MO_KL.MDF")
...

je možno hned začít pracovat s jejími daty.

Propojení a seskupení dat

Jak známo, operátor LEFT OUTER JOIN v SQL obsahuje všechny položky ze zdroje spojení uvedeného na LEVÉ straně, a to i tehdy, když ve zdroji na PRAVÉ straně není nalezen odpovídající záznam (v tom případě jsou datové položky čerpající z pravé strany rovny NULL). Přesněji, každá položka ze zdroje uvedeného na pravé straně, pokud nemá odpovídající položku ve zdroji uvedeného na levé straně, je vyloučena ze zpracování.

Klauzule GROUP JOIN z LINQ provádí v konečném důsledku stejnou činnost jako LEFT OUTER JOIN. Rozdíl mezi množinou dat získanou pomocí LEFT OUTER JOIN; a pomocí GROUP JOIN je tento: klauzule GROUP JOIN seskupuje výsledek z pravého zdroje dat spojení pro každou položku levého zdroje dat. V relačních databázích LEFT OUTER JOIN vrací neseskupený výsledek, v němž každá položka pocházející z levé strany zdroje dat se opakuje pro každou odpovídající položku z pravé strany, nejméně však jednou.

Výsledek získaný pomocí GROUP JOIN lze získat také jako neseskupený - tak, aby vrátil jednu položku pro každý výsledek seskupeného dotazu obdobně jako u databází. To zajistí použití metody DefaultIfEmpty seskupené kolekce. Položky levého zdroje dat jsou stále vloženy do výsledku dotazu i když nemají odpovídající položku v pravém zdroji dat, přičemž však lze programově zajistit dodání konkrétní implicitní hodnoty v případě neexistence odpovídající položky pravého zdroje dat.

Analogické postupy platí pro další dva typy propojení.

LINQ při zpracování XML

Soubor XML je v popisované problematice reprezentován souborem generovaným navigačním systémem - viz shora odstavec Data GPS - body trasy. Z jeho schématu je zřejmé, že každý element <time> je přímým následníkem elementu <trkpt> (v originále child element); naopak, každý element <trkpt> je bezprostředním předchůdcem (v originále parent element) jediného elementu <time>. Element <time> má za svůj obsah datum a čas, element <trkpt> má jako atributy zeměpisnou délku a šířku (WGS-84).

Tohoto faktu lze využít pro neuvěřitelně jednoduché přenesení obsahu XML dokumentu do kolekce objektů třídy clBodGPS: z dokumentu se vyberou všechny následnické (In lXML.Descendants) elementy, které jsou elementem <time> (Where xElm.Name.LocalName = cElmTime). Pro každý z nich se určí datum a čas převedený z jeho textového obsahu na hodnotu typu date, a dále jeho bezprostřední předchůdce (Let tmParent As XElement = xElm.Parent) - víme, že je to určitě <trkpt>. A konečně z jeho atributů lat a lon a dříve zjištěného datumu a času se zkompletuje jedna instance objektu třídy clBodGPS.

Celý popsaný postup je vytvořen jako funkce, jejímž parametrem je označení souboru a výsledkem je požadovaná kolekce zaznamenaných bodů trasy:

Public Const cElmTime As String = "time"
Public Const cElmLongitude As String = "lon"
Public Const cElmLatitude As String = "lat"


Function SeznamBoduTrasy(ByVal qSouborGPX As String) As IEnumerable(Of clBodGPS)

   ' Zpracování XML - tady musím dodržet jména elementů a atributů v GPX - viz cXX shora

   Dim lXML As XDocument = XDocument.Load(qSouborGPX)                ' lXML = GPX soubor

   SeznamBoduTrasy = _
      From xElm As XElement In lXML.Descendants _
      Where xElm.Name.LocalName = cElmTime _
         Let tmParent As XElement = xElm.Parent _
         Let tmCas As Date = GpxToDate(tmParent.Value) _
      Order By tmCas _
      Select New clBodGPS _
         With { _
            .Lon = GpxToDouble(tmParent.Attribute(cElmLongitude).Value), _
            .Lat = GpxToDouble(tmParent.Attribute(cElmLatitude).Value), _
            .Tim = tmCas _
         }

   End Function

Uvnitř funkce jsou volány dvě jednoduché funkce pro převod textové hodnoty tvaru "2007-11-02T10:30:15Z" na hodnotu typu date (GpxToDate) a pro převod textové hodnoty tvaru "50.64061499" na hodnotu typu double:

Function GpxToDate (ByVal qValue As String) As Date
Function GpxToDouble (ByVal qValue As String) As Double

Vytvoření kolekce bodů trasy pak v nějaké volající programové jednotce provede příkaz volání funkce typu

Dim BodyTrasy = SeznamBoduTrasy ("C:\DATA\OV_MO_07-11-02.GPX")

LINQ při zpracování XLS

Dalším zdrojem vstupních dat popisované aplikace je pojmenovaná oblast sešitu Excelu, tvořící tabulku relační databáze; její strukturu viz shora odstavec Data XLS - místa silnic. Kolekci jejich řádků lze velmi jednoduše získat pomocí LINQ pomocí Data Adapteru pro OLE: k volající aplikaci se přes Data Adapter jako instance objektu třídy DataTable připojí pojmenovaná oblast sešitu a z ní se LINQ vybere žádané. Protože ve zdroji (= tabulka v sešitu) principielně nemusí být všechna data zadána, zařadí se na výstup jen řádky, které mají zeměpisnou šířku a délku různé od null (Where lLA.HasValue And lLO.HasValue).

Tento postup zajišťuje funkce, jejímž parametrem je označení sešitu (jako souboru) a jméno oblasti v něm, a jejímž výsledkem je požadovaná kolekce řádků:

Function RadkyZnamychMist(ByVal qJmenoSesitu As String, ByVal qJmenoOblasti As String) _
   As EnumerableRowCollection(Of clMistoSilnice)

   Dim xtb As System.Data.DataTable = PripojTabXLS(qJmenoSesitu, qJmenoOblasti)

   RadkyZnamychMist = _
      From exy As DataRow In xtb.AsEnumerable _
         Let lLA As Double? = exy.Field(Of Double?)("LA") _
         Let lLO As Double? = exy.Field(Of Double?)("LO") _
      Where lLA.HasValue And lLO.HasValue _
      Select New clMistoSilnice _
         With { _
            .SI = exy.Field(Of String)("SI"), _
            .KM = exy.Field(Of Double?)("KM"), _
            .ID = exy.Field(Of String)("ID"), _
            .JM = exy.Field(Of String)("JM"), _
            .LA = exy.Field(Of Double?)("LA"), _
            .LO = exy.Field(Of Double?)("LO") _
         }

End Function

Z popsané funkce je volána zcela obecně použitelná další funkce autora pro přístup k datům v pojmenované oblasti sešitu:

Function PripojTabXLS(ByVal qSoubor As String, ByVal qTabulka As String) As DataTable
   Dim jda As OleDbDataAdapter
   Dim jds As New DataSet
   Dim jcs As String
   Dim jsq As String
   Try
      jcs = XlsConnString(qSoubor)
      jsq = "select * from " + qTabulka
      jda = New OleDbDataAdapter(jsq, jcs)
      jda.TableMappings.Add("Table", qTabulka)
      jds.Locale = CultureInfo.InvariantCulture
      jda.Fill(jds)
      PripojTabXLS = jds.Tables(qTabulka)
   Catch ex As OleDbException
      MsgBox("Chyba v OleDB pro XLS: " & ex.Message)
      PripojTabXLS = Nothing
   End Try
End Function

Pro upřesnění je dále uvedena funkce, která dodá Connection String do zdrojů formátu Excelu:

 

Function XlsConnString(ByVal qSoubor As String) As String
   Dim V As String = ""
   V = V + "Provider=Microsoft.Jet.OLEDB.4.0" + ";"
   V = V + "Data Source=" + qSoubor + ";"
   V = V + "Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"""
   XlsConnString = V
End Function

Vytvoření seznamu míst jako kolekce řádků tabulky pak v nějaké volající programové jednotce provede příkaz volání funkce typu

Dim Mista as List (Of clMistaSilnice)
Mista = RadkyZnamychMist ("C:/DATA/GPSDATA.XLS", "rgMista").ToList

LINQ při zpracování MDB

Jako třetí případ použití LINQ ukažme zpracování tabulek v Microsoft Database (MDB). Jak bylo popsáno v kapitole Programové modely aplikačních dat, třída clTRxUZ (přesněji kolekce těchto objektů) slouží v vytvoření kombinací typu "každý bod trasy s každým známým místem" - ale jen kombinací se vzájemnou vzdáleností menší než nějaká rozumná vzdálenost, např. 50 metrů (Where lVzd < cRozumnaVzdalenost). Takovou kolekci vytvoří funkce, jejímiž parametry jsou označení databáze a jména tabulek, do nichž byly uloženy seznamy bodů průjezdu a známých míst:

Function KombinacePrujezdyMista( _
   ByVal qJmenoDatabaze As String, _
   ByVal qJmenoTabulkyTracks As String, _
   ByVal qJmenoTabulkyMist As String) As IEnumerable(Of clTRxUZ)

   Const cRozumnaVzdalenost As Double = 50#
   
   Dim ttb As System.Data.DataTable
   Dim utb As System.Data.DataTable

   ttb = PripojTabMDB(qJmenoDatabaze, qJmenoTabulkyTracks)
   utb = PripojTabMDB(qJmenoDatabaze, qJmenoTabulkyMist)

   KombinacePrujezdyMista = _
      From tdr As DataRow In ttb.AsEnumerable, udr As DataRow In utb.AsEnumerable _
         Let ltLA As Double = tdr.Field(Of Double)("LA") _
         Let ltLO As Double = tdr.Field(Of Double)("LO") _
         Let luLA As Double = udr.Field(Of Double)("LA") _
         Let luLO As Double = udr.Field(Of Double)("LO") _
         Let lVzd As Double = VzdalenostMist(ltLO, ltLA, luLO, luLA) _
      Where lVzd < cRozumnaVzdalenost _
      Select New clTRxUZ _
         With { _
            .TM = tdr.Field(Of Date)("TM"), _
            .ID = udr.Field(Of String)("ID"), _
            .VZ = lVzd _
         }

   End Function

Z popsané funkce je volána zcela obecně použitelná další funkce autora pro přístup k datům v tabulce Microsoft databáze:

Function PripojTabMDB(ByVal qSoubor As String, ByVal qTabulka As String) As DataTable
   Dim jda As OleDbDataAdapter
   Dim jds As New DataSet
   Dim jcs As String
   Dim jsq As String
   Try
      jcs = MdbConnString(qSoubor)
      jsq = "select * from " + qTabulka
      jda = New OleDbDataAdapter(jsq, jcs)
      jda.TableMappings.Add("Table", qTabulka)
      jds.Locale = CultureInfo.InvariantCulture
      jda.Fill(jds)
      PripojTabMDB = jds.Tables(qTabulka)
   Catch ex As OleDbException
      MsgBox("Chyba v OleDB pro MDB: " & ex.Message)
      PripojTabMDB = Nothing
   End Try
End Function

Pro upřesnění je dále uvedena funkce, která dodá Connection String do zdrojů formátu Microsoft databáze:

Function MdbConnString(ByVal qSoubor As String) As String
   Dim V As String = ""
   V = V + "Provider=Microsoft.Jet.OLEDB.4.0" + ";"
   V = V + "Data Source=" + qSoubor + ";"
   V = V + "Jet OLEDB:Engine Type=5"
   MdbConnString = V
End Function

Dále je ze shora uvedené funkce volána funkce VzdalenostMist, jejíž hlavní součástí je realizace funkce PolomeryVsirceN popsaná v odstavci Rovina a obsahující dále jednoduchý přepočet na délku podle téhož odstavce.

Vytvoření seznamu kombinací bodů a míst jako kolekce řádků tabulky pak v nějaké volající programové jednotce provede příkaz volání funkce typu

Dim sqTRxUZ as IEnumerable(Of clTRxUZ)
sqTRxUZ = KombinacePrujezdyMista ("C:/DATA/GPSDATA.MDB", "tbBody", "tbMista")

Další vlastnosti LINQ

LINQ budované jako analogie k příkazu Select dotazovacího jazyka SQL má rovněž možnost seskupovat data zdrojů a odevzdávat kolekci dat, z nichž některá jsou výsledkem volání agregačních funkcí. Bez podrobnějšího komentáře ke kontextu uveďme příklad seskupování s agregační funkcí Min; příkaz zajistí ze všech shora vybraných kombinací bodů a míst ty nejbližší:

' Kolekce s minimálními vzdálenostmi každého průjezdu od nějakého uzlu:
Dim sqTMxVZ = From Kombinace In sqTRxUz Group By Kombinace.TM _
                 Into Skupina = Group, MINVZ = Min(Kombinace.VZ) _
                 Select New clGxVz With {.GT = Skupina, .VZ = MINVZ}

Shora uvedená funkce KombinacePrujezdyMista ukazuje zároveň, jak čerpat z více datových zdrojů. Zde je plně využito toho, že v takovém případě je výsledkem kartézský součin zdrojů, tj. "každý s každým". Častější případ v praxi je však takový, že data více datových zdrojů jsou propojena nějakou vazbou. V popisované aplikaci je to na několika místech; uveďme bez komentáře ke kontextu takový příklad:

' Kolekce s minimálními vzdálenostmi konkrétního průjezdu a konkrátního uzlu:
Dim lPrujezdy = From rA In sqTMxVZ Join rB In sqIDxVZ On rA.VZ Equals rB.VZ _
                   Select New clTRxUZ _
                   With {.TM = rA.GT.First.TM, .ID = rB.GT.First.ID, .VZ = rA.VZ}

V této druhé ukázce stojí za povšimnutí toto: výstupem první ukázky je kolekce, kde může existovat pro jeden bod několik míst, mají-li od bodu stejnou vzdálenost. A právě metoda First použita ve druhé ukázce zajistí, že se dále zpracuje už jen jedno místo, a sice to první.

 

Závěr

Článek se pokusil co možná stručně popsat řešení některých aspektů zpracování dat blížící se kategorii GIS. Rozebral matematickou problematiku s ohledem na počítačové zpracování a odůvodnil náhradu povrchu rotačního elipsoidu jednodušší plochou. Výsledky pak ukázal na konkrétní aplikaci, která řeší zpracování výstupů navigačních programů. Splnil zde další z deklarovaných cílů - použití rozšíření programovacích jazyků, které zavedl Microsoft ve svém Visual Studiu. Velmi příjemně pro aplikačního programátora je tak umožněno databázovým stylem zpracovávat množiny dat stejného typu resp. objektové třídy.

Pomocí LINQ je možno přistupovat k databázovým zdrojům dvěma způsoby: jeden byl ukázán v článku, druhý způsob je využití rozšíření deklarace uživatelských typů, struktur, tříd o atribut column. Tento druhý způsob v článku popsán není; autor ho považuje za jistou slabinu celého mechanismu, protože v tom případě se pracuje s datovými zdroji, které musí být známy už v okamžiku programování a při pozdějším spuštění hotové aplikace se jen velmi obtížně mění např. umístění datového zdroje.

Celkově lze však rozšíření hodnotit jako zdařilé, za jehož pomoci lze i poměrně složité datové vztahy řešit jednoduchými úseky programu, což se článek pokusil dokumentovat.

Literatura

[1] Microsoft Co.: MSDN for Visual Studio 2008 [online]. Dostupné na http://microsoft.com [cit. 13.6.2009] v sekci MSDN. Microsoft, 2007.

 

 

 

Rev. 07/2009