Technical Article

GeoPDF v Delphi: Geoprostorové souřadnice s PDFlibPas

Většina vývojářů si stránku PDF představuje jako list papíru s textem a obrázky. Georeferencované PDF je však něčím víc. Nese dostatek informací k tomu, aby bylo možné vzít bod na stránce, změřený v běžných jednotkách stránky, a nahlásit zeměpisnou šířku a délku, nad kterými leží v reálném světě. Tato jediná skutečnost mění PDF v použitelný nosič pro topografickou mapu, katastrální mapu, zákres záplavové zóny nebo jakýkoli GIS export, který se musí vytisknout a přesto si zachovat svůj význam. Geometrie v souboru je přítomna. Jedinou otázkou zůstává, zda ji váš načítač přečte.

Důvodem, proč se to přehlíží, je to, že GeoPDF se otevírá a tiskne přesně jako jakékoli jiné PDF. Nic na vykreslené stránce neoznamuje, že je mapa registrována k souřadnicovému systému. Registrace se nachází ve slovnících (dictionaries) připojených k objektu stránky, které se nikdy nevykreslují. Prohlížeč, který je ignoruje, vám mapu zobrazí i tak. Abyste se souborem mohli dělat jakékoli prostorové operace, jako je odečítání souřadnic, reprojekce nebo překrývání s jinými vrstvami, musíte tyto slovníky projít sami.

V praxi existují dva standardy

Čtečka, která chce zpracovávat reálné soubory, se musí vyrovnat se dvěma schématy georegistrace, protože obě se používají a daný soubor může obsahovat kterékoli z nich. Starším je kódování OGC popsané v OGC 08-139r2, které k objektu stránky připojuje slovník LGIDict (geospatial registration dictionary). Předcházelo jakémukoli schválení ze strany ISO a bylo de facto formátem pro rané výstupy GeoPDF, takže velké množství starších map obsahuje pouze jej.

Moderním schématem je to, které ISO standardizovalo v ISO 32000-1 §8.8.2. Místo jediného slovníku na úrovni stránky modeluje geoprostorová data jako Viewport stránky s připojeným slovníkem Measure, který určuje zeměpisný souřadnicový systém. Jedná se o kódování, které zapisují Acrobat a současné GIS exportéry. Robustní importér kontroluje obě možnosti: načte viewports pro model ISO a u souborů, které nesou pouze starší registraci, se vrátí k LGIDict (případně jej zkontroluje navíc).

Viewports a jejich hranice

V modelu ISO je jednotkou georegistrace viewport a stránka jich může mít několik. Velký list může umístit hlavní mapu do jednoho obdélníku, výřez v jiném měřítku do druhého a panel legendy, který není georeferencován vůbec, mimo ně. Každý viewport nese BBox, což je obdélník na stránce, který daný viewport ovládá. Čtečka tak ví, na kterou část listu se daný souřadnicový systém vztahuje. Testování kliknutí na bod vůči těmto ohraničením (hit-testing) je způsobem, jakým prohlížeč rozhoduje, který slovník měření použije.

PDFlibPas vystavuje viewports vybrané stránky přímo. GetPageViewPortCount vrací jejich celkový počet, GetPageViewPortID převádí index číslovaný od jedné na handle ViewPortID a GetViewPortBBox načítá ohraničující obdélník po jednotlivých rozměrech. Argument Dimension vybírá požadovaný okraj nebo rozsah: 0 je Left, 1 je Top, 2 is Width, 3 je Height, 4 je Right a 5 je Bottom.

var
  Pdf: TPDFlib;
  vpCount, i, vpID: Integer;
  Left, Top, Width, Height: Double;
begin
  Pdf := TPDFlib.Create;
  try
    if Pdf.LoadFromFile('topo_sheet.pdf', '') <> 1 then
      raise Exception.Create('load failed');
    Pdf.SelectPage(1);

    vpCount := Pdf.GetPageViewPortCount;
    for i := 1 to vpCount do
    begin
      vpID := Pdf.GetPageViewPortID(i);
      Left   := Pdf.GetViewPortBBox(vpID, 0);
      Top    := Pdf.GetViewPortBBox(vpID, 1);
      Width  := Pdf.GetViewPortBBox(vpID, 2);
      Height := Pdf.GetViewPortBBox(vpID, 3);
      // Left/Top/Width/Height describe the map area for this viewport
    end;
  finally
    Pdf.Free;
  end;
end;

Hodnota ViewPortID rovná nule z GetPageViewPortID znamená, že viewport na daném indexu nebyl nalezen. Před dalším předáním handle jej proto zkontrolujte.

Uvnitř slovníku měření

Geometrie, která registruje stránku vůči světu, se nachází ve slovníku měření (measure dictionary) připojeném k viewportu. GetViewPortMeasureDict vrací MeasureDictID pro dané ViewPortID, nebo nulu, pokud viewport žádný slovník měření nemá (což je běžný případ u panelu legendy nebo záhlaví). Slovník měření obsahuje tři věci, které stojí za načtení: souřadnicové systémy, na které odkazuje, pole propojující body stránky se zeměpisnými body a jednotku, ve které jsou data bodů vyjádřena. Samotná registrace se skládá ze dvou paralelních polí. GPTS je pole zeměpisných bodů, tedy dvojic zeměpisné šířky a délky zadaných v geografickém souřadnicovém systému. LPTS je pole bodů v prostoru stránky vyjádřených jako zlomky BBoxu viewportu, díky čemuž zůstávají zachovány i při změně měřítka. Prvek n v LPTS a prvek n v GPTS označují stejné fyzické umístění, jednou v souřadnicích stránky a jednou na zeměkouli. Tři nebo více takových dvojic určují afinní (nebo v obecném případě projektivní) transformaci, která mapuje jakoukoli souřadnici stránky uvnitř viewportu na souřadnici světa. Načtení vyžaduje společné procházení obou polí.

var
  measID, gptsCount, lptsCount, j: Integer;
  lat, lon, px, py: Double;
begin
  measID := Pdf.GetViewPortMeasureDict(vpID);
  if measID <> 0 then
  begin
    gptsCount := Pdf.GetMeasureDictGPTSCount(measID);
    lptsCount := Pdf.GetMeasureDictLPTSCount(measID);
    // GPTS holds lat/lon pairs; LPTS holds the matching page fractions.
    // Both arrays are read with one-based item indices.
    j := 1;
    while j < gptsCount do
    begin
      lat := Pdf.GetMeasureDictGPTSItem(measID, j);
      lon := Pdf.GetMeasureDictGPTSItem(measID, j + 1);
      px  := Pdf.GetMeasureDictLPTSItem(measID, j);
      py  := Pdf.GetMeasureDictLPTSItem(measID, j + 1);
      // (px, py) on the page corresponds to (lat, lon) on the ground
      Inc(j, 2);
    end;
  end;
end;

Slovník měření také hlásí své zobrazovací jednotky prostřednictvím GetMeasureDictPDU, která přijímá UnitIndex s hodnotou 1 pro lineární, 2 pro plošné nebo 3 pro úhlové jednotky a vrací kód identifikující konkrétní jednotku, například metr nebo mezinárodní stopu pro lineární kategorii. Pole Bounds, načítané pomocí GetMeasureDictBoundsItem, popisuje čtyřúhelník uvnitř viewportu, který měření skutečně pokrývá (což nemusí být vždy celý obdélník).

Praktický kontext

Zeměpisná šířka a délka v GPTS nemají význam bez znalosti geografického souřadnicového systému, ke kterému patří. Souřadnice 51.5, -0.1 totiž leží na jiném fyzickém místě v systému WGS 84 než ve starším národním datumu. Slovník měření na to odpovídá prostřednictvím slovníku souřadnicového systému, ke kterému se přistupuje pomocí GetMeasureDictGCSDict pro geografický systém. PDF popisuje tento systém jedním ze dvou zaměnitelných způsobů a čtečka musí akceptovat oba. Prvním je WKT (Well-Known Text), což je samostatný řetězec, který kompletně popisuje datum, elipsoid, základní poledník a jednotky. Je sice obsáhlý, ale jednoznačný a nevyžaduje žádnou externí vyhledávací tabulku. Druhým je kód EPSG, což je jedno celé číslo odkazující na souřadnicový systém v registru EPSG. Kód 4326 představuje WGS 84, což je systém, který používá většina spotřebitelských GPS dat. EPSG je kompaktní, ale předpokládá, že čtečka dokáže kód přeložit proti databázi. Soubory se objevují s jedním, druhým nebo oběma způsoby, což je důvod, proč API zpřístupňuje všechny tři metody: GetCSDictType, GetCSDictEPSG a GetCSDictWKT. GetCSDictType hlásí, zda je systém geografický (GEOGCS, návratová hodnota 1) nebo projektovaný (PROJCS, návratová hodnota 2), což vám umožňuje zbytek správně interpretovat dříve, než mu začnete důvěřovat.

var
  gcsID, csType, epsg: Integer;
  wkt: WideString;
begin
  gcsID := Pdf.GetMeasureDictGCSDict(measID);
  if gcsID <> 0 then
  begin
    csType := Pdf.GetCSDictType(gcsID);   // 1 = GEOGCS, 2 = PROJCS
    epsg   := Pdf.GetCSDictEPSG(gcsID);   // e.g. 4326 for WGS 84, 0 if absent
    wkt    := Pdf.GetCSDictWKT(gcsID);    // full text description, '' if absent
    // Prefer EPSG when present; fall back to parsing WKT otherwise.
  end;
end;

Čtení staršího slovníku LGIDict

Soubory, které předcházejí modelu viewportu, nebo které byly vytvořeny nástroji stále generujícími starší kódování, nesou svou registraci v LGIDict na stránce namísto slovníku měření. PDFlibPas hlásí, kolik takových slovníků stránka má, prostřednictvím GetPageLGIDictCount a vrací surový obsah každého z nich pomocí GetPageLGIDictContent (indexováno od jedné). Vrácený text je slovník v zapsané podobě obsahující registrační pole dle OGC 08-139r2, která váš kód následně analyzuje, aby získal stejný druh mapování stránky na svět, jaký poskytuje slovník měření. On the writing side, AddLGIDictToPage attaches an LGIDict to the current page, so a converter can round-trip the legacy form when an old consumer still expects it.

var
  lgiCount, k: Integer;
  dictText: WideString;
begin
  lgiCount := Pdf.GetPageLGIDictCount;
  for k := 1 to lgiCount do
  begin
    dictText := Pdf.GetPageLGIDictContent(k);
    // dictText carries the OGC 08-139r2 registration to parse
  end;
end;

Kompletní sestavení postupu čtení

Kompletní importér přistupuje k těmto dvěma schématům jako k dvojici průchodů nad každou stránkou. Vyberte stránku, dotazem na GetPageViewPortCount získejte viewports ISO a pro každý viewport, který vlastní slovník měření, vytáhněte jeho BBox, pole GPTS a LPTS, jednotku dat bodů a popis GCS přes slovník souřadnicového systému. Poté zkontrolujte GetPageLGIDictCount na přítomnost jakékoli starší registrace, kterou průchod viewportů nepokryl. Mapa obsahující obojí by měla v obou případech vykazovat shodu. Mapa nesoucí pouze jedno schéma se stále vyřeší, protože jste hledali na obou místech. Handly vracené v průběhu procesu (ViewPortID, MeasureDictID, CSDictID) jsou obyčejná celá čísla, která zůstávají platná po celou dobu načtení dokumentu. Celé procházení je tak jen otázkou několika vnořených smyček nad seznamem stránek bez nutnosti spravovat alokaci paměti. Jakmile dokážete obnovit registraci, stránka se stává spíše zdrojem dat než pouhým obrázkem. Související techniky pro čtení zbytku stránky jsou popsány v článku o extrakci textu, obrázků a písem a vykreslování georeferenčního listu na zařízení pro měření na obrazovce je popsáno v průvodci kontextem tiskového a náhledového zařízení. Geoprostorová čtečka popsaná v tomto článku je dodávána jako součást losLab PDF Library pro Delphi a C++Builder společně s rozhraními API pro načítání, extrakci a vykreslování popsanými jinde na tomto blogu.