Technical Article

„GeoPDF“ naudojant „Delphi“: geografinės koordinatės su „PDFlibPas“

Daugelis kūrėjų mano, kad PDF puslapis yra tik popieriaus lapas su tekstu ir paveikslėliais. Geografiškai susietas (georeferenced) PDF yra daugiau nei tai. Jis turi pakankamai informacijos, kad paėmus puslapio tašką, išmatuotą įprastais puslapio vienetais, būtų galima nustatyti platumą ir ilgumą, kurioje jis yra realiame pasaulyje. Šis faktas paverčia PDF naudingu nešiotoju topografiniams žemėlapiams, kadastro sklypų brėžiniams, potvynių zonų schemoms ar bet kuriam GIS eksportui, kurį reikia atspausdinti ir vis tiek išlaikyti jo erdvinę prasmę. Geometrija yra faile – klausimas tik tame, ar jūsų įkėlimo programa ją perskaito.

Šis dalykas dažnai praleidžiamas todėl, kad GeoPDF atsidaro ir spausdinasi lygiai taip pat, kaip bet kuris kitas PDF failas. Niekas atvaizduotame puslapyje nepraneša, kad žemėlapis susietas su koordinačių sistema. Sąsaja saugoma žodynuose prie puslapio objekto, kurie niekada nėra piešiami, o peržiūros programa, kuri juos ignoruoja, vis tiek sėkmingai rodo žemėlapį. Kad galėtumėte atlikti erdvinius veiksmus su failu – matuoti koordinates, keisti projekciją, dėti sluoksnius – turite patys pereiti šiuos žodynus.

Praktikoje egzistuoja du standartai

Skaitytuvas, kuris nori palaikyti realius failus, turi susidoroti su dviem geografinio susiejimo schemomis, nes abi jos yra paplitusios ir failas gali naudoti bet kurią iš jų. Senesnė yra OGC kodavimas, aprašytas OGC 08-139r2 standarte, kuris prie puslapio prideda LGIDict (geospatial registration dictionary) žodyną. Jis sukurtas dar prieš ISO patvirtinimą ir buvo de facto formatas ankstyvajai GeoPDF išvesties daliai, todėl didelė dalis senų žemėlapių naudoja būtent jį.

Šiuolaikinė schema yra ta, kurią ISO standartizavo ISO 32000-1 §8.8.2. Vietoj vieno puslapio lygio žodyno ji modeliuoja geografinius duomenis kaip puslapio vaizdo sritį (Viewport) su pridedamu matavimo (Measure) žodynu, o šis nurodo geografinę koordinačių sistemą. Tai yra kodavimas, kurį įrašo Acrobat ir dabartinės GIS eksporto programos. Patikimas importuotojas tikrina abu variantus: nuskaito vaizdo sritis ISO modeliui ir papildomai tikrina LGIDict failams, kurie turi tik senąjį susiejimą.

Vaizdo sritys ir jų ribos

ISO modelyje geografinio susiejimo vienetas yra vaizdo sritis (viewport), ir puslapis gali turėti kelias tokias sritis. Dideliame lape galima patalpinti pagrindinį žemėlapį viename stačiakampyje, kito mastelio intarpą kitame, o legendos skydelį, kuris išvis neturi geografinio ryšio – atskirai. Kiekviena vaizdo sritis turi BBox lauką (stačiakampį puslapyje), kurį vaizdo sritis valdo, todėl skaitytuvas žino, kuriai lapo daliai taikoma konkreti koordinačių sistema. Spustelėto taško tikrinimas su šiais rėmeliais yra būdas, kuriuo peržiūros programa nusprendžia, kurį matavimo žodyną naudoti.

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;

Jei GetPageViewPortID grąžina vaizdo srities ID lygų nuliui, tai reiškia, kad vaizdo sritis tuo indeksu nerasta, todėl patikrinkite tai prieš perduodami deskriptorių toliau.

Bendra informacija apie matavimo žodyną

Geometrija, susiejanti puslapį su pasauliu, yra matavimo žodyne, priskirtame vaizdo sričiai. GetViewPortMeasureDict grąžina MeasureDictID nurodytam vaizdo srities ID arba nulį, kai vaizdo sritis neturi matavimo žodyno (tai įprasta legendoms ar antraščių skydeliams). Matavimo žodynas turi tris svarbius dalykus: koordinačių sistemas, kurias jis nurodo, masyvus, susiejantys puslapio taškus su geografiniais taškais, ir vienetus, kuriais išreiškiami taško duomenys.

Pats susiejimas yra du lygiagretūs masyvai. GPTS yra geografinių taškų masyvas (platumos ir ilgumos poros geografinėje koordinačių sistemoje). LPTS yra puslapio erdvės taškų masyvas, išreikštas vaizdo srities BBox dalimis, kad išliktų teisingas keičiant mastelį. Masyvo LPTS elementas n ir GPTS elementas n nurodo tą pačią fizinę vietą – vieną kartą puslapio koordinatėmis, o kitą kartą gaublyje. Trys ar daugiau tokių porų apibrėžia afiniąją (arba bendruoju atveju projekcinę) transformaciją, kuri susieja bet kurią puslapio koordinatę vaizdo srityje su pasaulio koordinate. Jų nuskaitymas yra abiejų masyvų sinchroninis perėjimas.

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;

Matavimo žodynas taip pat praneša apie rodomus vienetus per GetMeasureDictPDU, kuris priima UnitIndex: 1 – linijiniams, 2 – ploto arba 3 – kampiniams vienetams ir grąžina kodą, identifikuojantį konkretų vienetą, pavyzdžiui, metrą arba tarptautinę pėdą linijinėje kategorijoje. Masyvas Bounds, nuskaitomas per GetMeasureDictBoundsItem, aprašo keturkampį vaizdo srityje, kurį faktiškai apima matavimas (tai ne visada yra pilnas stačiakampis).

WKT prieš EPSG

Platuma ir ilguma GPTS masyve yra beprasmės nežinant, kuriai geografinei koordinačių sistemai jos priklauso, nes koordinatė 51.5, -0.1 patenka į skirtingas fizines vietas WGS 84 sistemoje ir senesnėje šalies koordinačių sistemoje. Matavimo žodynas atsako į tai per koordinačių sistemos žodyną, pasiekiamą per GetMeasureDictGCSDict. PDF aprašo šią sistemą vienu iš dviejų pakaitomis naudojamų būdų, ir skaitytuvas turi priimti bet kurį iš jų.

Pirmasis yra WKT (Well-Known Text) – atskira eilutė, kuri pilnai aprašo koordinačių sistemą, elipsoidą, pradinį meridianą ir vienetus. Ji yra išsami, bet vienareikšmė ir jai nereikia išorinės lentelės. Antrasis yra EPSG kodas – vienas sveikasis skaičius, nurodantis koordinačių sistemą EPSG registre (pavyzdžiui, 4326 yra WGS 84, kurį naudoja dauguma GPS įrenginių). EPSG yra kompaktiškas, tačiau reikalauja, kad skaitytuvas sugebėtų išspręsti kodą duomenų bazėje. Failai gali turėti vieną iš jų arba abu, todėl API pateikia tris metodus: GetCSDictType, GetCSDictEPSG ir GetCSDictWKT. GetCSDictType praneša, ar sistema yra geografinė (GEOGCS, reikšmė 1), ar projekcinė (PROJCS, reikšmė 2), leidžiant teisingai interpretuoti likusius duomenis prieš jais pasitikint.

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;

Senojo LGIDict žodyno nuskaitymas

Failai, sukurti prieš vaizdo sričių modelį, arba sukurti įrankiais, vis dar naudojančiais senąjį kodavimą, turi savo susiejimą puslapio LGIDict žodyne, o ne matavimo žodyne. PDFlibPas praneša, kiek tokių žodynų turi puslapis, per GetPageLGIDictCount ir grąžina kiekvieno iš jų turinį per GetPageLGIDictContent. Gautas tekstas yra žodynas su OGC 08-139r2 registracijos laukais, kurį jūsų kodas gali išanalizuoti ir gauti tokį patį puslapio susiejimą su pasauliu, kokį pateikia ir matavimo žodynas. Įrašymo pusėje AddLGIDictToPage prideda LGIDict prie esamo puslapio, kad konverteris galėtų atkurti senąją formą, kai jos tikisi senesnės programos.

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;

Pilnas nuskaitymo apjungimas

Pilnas importuotojas traktuoja šias dvi schemas kaip du žingsnius kiekvienam puslapiui. Pasirinkite puslapį, gaukite ISO vaizdo sričių skaičių per GetPageViewPortCount ir kiekvienai vaizdo sričiai, turinčiai matavimo žodyną, paimkite BBox, GPTS ir LPTS masyvus, matavimo vienetus bei koordinačių sistemos aprašymą. Tada patikrinkite GetPageLGIDictCount bet kokiam senam susiejimui, kurio neapėmė vaizdo sričių žingsnis. Žemėlapis, kuris turi abu, turėtų rodyti sutampančius duomenis; žemėlapis su vienu iš jų vis tiek bus sėkmingai perskaitytas, nes patikrinote abi vietas. Proceso metu grąžinami ID (ViewPortID, MeasureDictID, CSDictID) yra paprasti sveikieji skaičiai, kurie lieka galiojantys, kol dokumentas yra įkeltas, todėl visas procesas tėra keli ciklai per puslapių sąrašą be jokio papildomo atminties valdymo.

Once you can recover the registration, the page becomes a data source rather than a picture. The companion techniques for reading the rest of a page are covered in the article on text, image, and font extraction, and rendering a georeferenced sheet to a device for on-screen measurement is described in the print and preview device-context walkthrough. The geospatial reader described here ships as part of the losLab PDF Library for Delphi and C++Builder, alongside the loading, extraction, and rendering APIs covered elsewhere on this blog.