Technical Article

GeoPDF v Delphiju: Geospatialne koordinate s PDFlibPas

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.

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.

PDFlibPas 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 describe the map area for this viewport
    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 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;

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 lands in a different physical spot under WGS 84 than under an older national datum. 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.

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) or projected (a PROJCS, return value 2), letting you interpret the rest correctly before you trust it.

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;

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. PDFlibPas 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 carries the OGC 08-139r2 registration to parse
  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.

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.