Tehnični članak

GeoPDF v Delphiju: Geospatialne koordinate s PDF Library for Delphi

Večina razvijalcev si stran PDF predstavlja kot list papirja z besedilom in slikami. Georeferenciran PDF pa je več kot to. Vsebuje dovolj podatkov, da lahko vzamete točko na strani, izmerjeno v običajnih enotah strani, in sporočite širino in dolžino (latitude in longitude), nad katero se nahaja v resničnem svetu. To dejstvo spremeni PDF v uporaben medij za topografski zemljevid, katastrski načrt, prikaz poplavnega območja ali kateri koli izvoz GIS, ki ga je treba natisniti in mora še vedno imeti pomen. Geometrija je prisotna v datoteki; edino vprašanje je, ali jo vaš bralnik prebere

Razlog, zakaj se to spregleda, je v tem, da se GeoPDF odpre in natisne natanko tako kot vsak drug PDF. Nič na upodobljeni strani ne naznanja, da je zemljevid registriran v koordinatnem sistemu. Registracija živi v slovarjih, ki so pripeti k objektu strani in se nikoli ne izrišejo, pregledovalnik, ki jih prezre, pa vam vseeno prikaže zemljevid. Če želite z datoteko narediti kar koli prostorskega, kot so odčitki koordinat, ponovna projekcija ali prekrivanje z drugimi sloji, morate sami prehoditi te slovarje

V praksi živita dva standarda

Bralnik, ki želi obravnavati datoteke iz resničnega sveta, se mora spopasti z dvema shemama georegistracije, saj sta v obtoku obe, posamezna datoteka pa lahko uporablja katero koli. Starejša je kodiranje OGC, opisano v standardu OGC 08-139r2, ki k strani pripne LGIDict (slovar za geospatialno registracijo). Nastala je pred kakršnim koli priznanjem ISO in je bila de facto format za zgodnje izhode GeoPDF, zato jo prenaša velik del dednih zemljevidov in nič drugaga

Sodobna shema je tista, ki jo je ISO standardiziral v ISO 32000-1 §8.8.2. Namesto enega slovarja na ravni strani modelira prostorske podatke kot Viewport strani s priloženim slovarjem Measure, ta slovar meritev pa poimenuje geografski koordinatni sistem. To je kodiranje, ki ga zapisujeta Acrobat in trenutni izvozniki GIS. Robusten uvoznik preveri oboje: prebere poglede (viewports) za model ISO in se vrne k slovarju LGIDict (ali ga dodatno pregleda) za datoteke, ki prenašajo le starejšo registracijo

Diagram PDF Library for Delphi, ki protistavlja modelu georegistracije z ISO vidnim poljem in slovarjem measure shemo LGIDict, ki jo nosijo starejše datoteke GeoPDF
V pravih datotekah GeoPDF krožita dve shemi georegistracije, stran pa lahko nosi eno ali obe. Vsaka pot razreši koordinate strani v zemljepisno širino in dolžino

Pogledi in njihove meje

V modelu ISO je enota georegistracije pogled (viewport) in stran jih lahko ima več. Velik list lahko postavi glavni zemljevid v en pravokotnik, vložek (inset) v drugi lestvici v drugega, ploščo z legendo pa pusti popolnoma brez georeference. Vsak pogled nosi BBox, pravokotnik na strani, ki ga pogled upravlja, tako da bralnik ve, za kateri del lista velja določen koordinatni sistem. Preizkušanje trkov kliknjene točke s temi pravokotniki je način, kako pregledovalnik določi, kateri slovar meritev naj uporabi

PDF Library for Delphi neposredno izpostavlja poglede izbrane strani. Metoda GetPageViewPortCount vrne njihovo število, GetPageViewPortID spremeni na ena temelječ indeks v ročico ViewPortID, GetViewPortBBox pa bere omejitveni pravokotnik eno dimenzijo naenkrat. Argument Dimension izbere, kateri rob ali obseg želite: 0 je Left, 1 is Top, 2 is Width, 3 is Height, 4 is Right in 5 is 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 opisujejo območje zemljevida za ta pogled
    end;
  finally
    Pdf.Free;
  end;
end;

Vrednost ViewPortID enaka nič iz GetPageViewPortID pomeni, da pogleda pri tem indeksu ni bilo mogoče najti, zato jo preverite, preden posredujete ročico naprej

Znotraj slovarja meritev

Geometrija, ki registrira stran s svetom, živi v slovarju meritev, ki je priložen pogledu. Metoda GetViewPortMeasureDict vrne MeasureDictID za določen ViewPortID ali nič, kadar pogled nima slovarja meritev, kar je običajen primer za legendo ali ploščo z naslovom. Slovar meritev vsebuje tri stvari, ki jih je vredno prebrati: koordinatne sisteme, na katere se nanaša, polja, ki povezujejo točke strani z geografskimi točkami, in enoto, v kateri so izraženi podatki o točkah

Sama registracija sta dve vzporedni polji. GPTS je polje geografskih točk, parov širine in dolžine (latitude in longitude), podanih v geografskem koordinatnem sistemu. LPTS je polje točk v prostoru strani, izraženih kot delež BBox pogleda, tako da preživijo spreminjanje merila. Element n iz LPTS in element n iz GPTS poimenujeta isto fizično lokacijo, enkrat v koordinatah strani in enkrat na globusu. Trije ali več takšnih parov določijo afino ali v splošnem projektivno transformacijo, ki preslika katero koli koordinato strani znotraj pogleda v svetovne koordinate. Branje pomeni sprehod skozi obe polji korak za korakom

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 vsebuje pare širine/dolžine; LPTS vsebuje ustrezne deleže strani.
    // Oba polja se berete z indeksi elementov, ki se začnejo z ena.
    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) na strani ustreza (lat, lon) na tleh
      Inc(j, 2);
    end;
  end;
end;

Slovar meritev sporoča tudi svoje prikazne enote prek GetMeasureDictPDU, ki sprejme UnitIndex z vrednostjo 1 za linearne, 2 za površinske ali 3 za kotne enote in vrne kodo, ki identificira specifično enoto, na primer meter ali mednarodno stopalo za linearno kategorijo. Polje Bounds, ki se bere z GetMeasureDictBoundsItem, opisuje štirikotnik znotraj pogleda, ki ga meritev dejansko pokriva, kar ni vedno celoten pravokotnik

WKT proti EPSG

Širina in dolžina v GPTS sta nesmiselni brez poznavanja geografskega koordinatnega sistema, ki mu pripadata, saj točka s koordinatami 51.5, -0.1 po WGS 84 leži na drugem fizičnem mestu kot po starejšem nacionalnem datumu. Slovar meritev na to odgovori prek slovarja koordinatnih sistemov, ki ga dosežemo z GetMeasureDictGCSDict za geografski sistem. PDF opisuje ta sistem na enega od dveh zamenljivih načinov in bralnik mora sprejeti katerega koli

PDF Library for Delphi: maketa strani PDF z glavnim vidnim poljem zemljevida, vstavljenim vidnim poljem in nepovezano legendo ter s preizkušanjem zadetkov klikov ob BBox vsakega vidnega polja
Vsak pogled upravlja le območje znotraj svojega BBox, zato testiranje zadetkov bralniku pove, kateri merilni slovar velja. ViewPortID nič pomeni, da zahtevanega pogleda ni bilo mogoče najti

Prvi je WKT (Well-Known Text), samostojen niz, ki v celoti opisuje datum, elipsoid, začetni poldnevnik (prime meridian) in enote. Je obsežen, a nedvoumen in ne potrebuje zunanje iskalne tabele. Drugi je koda EPSG, eno celo število, ki indeksira koordinatni sistem v registru EPSG; 4326 predstavlja WGS 84, okvir, ki ga uporablja večina uporabniških podatkov GPS. EPSG je kompakten, a predvideva, da bralnik zna razrešiti kodo v zbirki podatkov. Datoteke se pojavljajo z enim, drugim ali obema možnostma, zato vmesnik API ponuja vse tri metode: GetCSDictType, GetCSDictEPSG in GetCSDictWKT. GetCSDictType sporoči, ali je sistem geografski (GEOGCS, povratna vrednost 1) ali projekcijski (PROJCS, povratna vrednost 2), kar vam omogoči pravilno razlago preostanka, preden mu zaupate

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);   // npr. 4326 za WGS 84, 0 če ni prisotno
    wkt    := Pdf.GetCSDictWKT(gcsID);    // poln besedilni opis, '' če ni prisotno
    // Če je EPSG prisoten, ga uporabi; sicer razčleni WKT.
  end;
end;

Branje starejšega LGIDict

Datoteke, ki so nastale pred modelom pogledov, ali tiste, ki so jih ustvarila orodja, ki še vedno oddajajo starejše kodiranje, prenašajo svojo registracijo v LGIDict na strani in ne v slovarju meritev. PDF Library for Delphi sporoči, koliko takšnih slovarjev ima stran, prek GetPageLGIDictCount in vrne surovo vsebino vsakega z GetPageLGIDictContent, indeksirano od ena. Vrnjeno besedilo je slovar, kot je zapisan, in vsebuje registracijska polja OGC 08-139r2, ki jih vaša koda nato razčleni, da obnovi enako preslikavo strani v svet, kot jo ponuja slovar meritev. Na strani zapisovanja AddLGIDictToPage pripne LGIDict trenutni strani, tako da lahko pretvornik krožno predeluje (round-trip) starejšo obliko, ko jo star uporabnik še vedno pričakuje

var
  lgiCount, k: Integer;
  dictText: WideString;
begin
  lgiCount := Pdf.GetPageLGIDictCount;
  for k := 1 to lgiCount do
  begin
    dictText := Pdf.GetPageLGIDictContent(k);
    // dictText vsebuje registracijo OGC 08-139r2 za razčlenitev
  end;
end;

Celovit pregled branja

Popoln uvoznik obravnava obe shemi kot par prehodov čez vsako stran. Izberite stran, povprašajte GetPageViewPortCount za poglede ISO in za vsak pogled, ki ima slovar meritev, povlecite njegov BBox, polji GPTS in LPTS, enoto podatkov točk ter opis GCS prek slovarja koordinatnih sistemov. Nato preverite GetPageLGIDictCount za morebitno starejšo registracijo, ki je prehod pogledov ni zajel. Zemljevid, ki prenaša oboje, bi se moral ujemati v obeh delih; zemljevid, ki prenaša le eno možnost, se še vedno razreši, saj ste preverili na obeh mestih. Ročice, vrnjene med potjo (ViewPortID, MeasureDictID, CSDictID), so preprosta cela števila, ki ostanejo veljavna, ko je dokument naložen, zato je celoten prehod le nekaj gnezdnih zank čez seznam strani brez upravljanja pomnilniških dodelitev

PDF Library for Delphi: pripeti ulomki prostora strani LPTS in polji zemljepisne širine ter dolžine GPTS, povezana predmet za predmetom, da se utrdi afina transformacija iz strani v svet
Predmet n iz LPTS in predmet n iz GPTS poimenujeta isto fizično lokacijo, enkrat na strani in enkrat na globusu. Trije ali več parov pripne afino ali projektivno transformacijo

Ko lahko obnovite registracijo, stran postane vir podatkov in ne le slika. Spremljajoče tehnike za branje preostanka strani so obravnavane v članku o ekstrakciji besedila, slik in pisav, upodabljanje georeferenciranega lista na napravo za meritve na zaslonu pa je opisano v vodniku za tiskanje in predogled v kontekstu naprave. Geografski bralnik, opisan tukaj, se dostavlja kot del knjižnice losLab PDF Library za Delphi in C++Builder, skupaj z vmesniki API za nalaganje, ekstrakcijo in upodabljanje, ki so obravnavani drugje na tem blogu