Technical Article

GeoPDF Delphissä: Paikkatietokoordinaatit PDFlibPas-kirjastolla

Useimmat kehittäjät mieltävät PDF-sivun paperiarkiksi, jolla on tekstiä ja kuvia. Geoviitteistetty (georeferenced) PDF on enemmän kuin se. Se kantaa tarpeeksi tietoa ottamaan sivulla olevan pisteen, mitattuna tavallisissa sivuyksiköissä, ja ilmoittamaan leveys- ja pituusasteen, jonka päällä se sijaitsee todellisessa maailmassa. Tämä yksittäinen tosiasia tekee PDF-tiedostosta käyttökelpoisen alustan topografiselle kartalle, tonttikartalle, tulva-alueen esitykselle tai mille tahansa GIS-viennille, joka on tulostettava ja jolla on silti oltava merkitys. Geometria on tiedostossa; ainoa kysymys on, lukeeko lataajasi sen.

Syy siihen, miksi tämä jää huomaamatta, on se, että GeoPDF avautuu ja tulostuu aivan kuten mikä tahansa muu PDF. Mikään renderöidyllä sivulla ei ilmoita, että kartta on rekisteröity koordinaattijärjestelmään. Rekisteröinti elää sivuelimen sanakirjoissa, joita ei koskaan piirretä, ja katseluohjelma, joka ohittaa ne, näyttää silti kartan. Tehdäksesi tiedostolle mitään paikkatietoon liittyvää - kuten maanmittauskoordinaattien lukemista, uudelleenprojektiota tai muiden tasojen päällekkäinasettelua - sinun on käytävä kyseiset sanakirjat läpi itse.

Kaksi standardia elää käytännössä

Lukijan, joka haluaa käsitellä todellisen maailman tiedostoja, on selvittävä kahdesta georekisteröintijärjestelmästä, koska molemmat ovat käytössä ja tietty tiedosto voi käyttää kumpaa tahansa. Vanhempi on OGC-koodaus, joka on kuvattu dokumentissa OGC 08-139r2 ja joka liittää sivuun LGIDict-sanakirjan (geospatiaalinen rekisteröintisanakirja). Se on peräisin ajalta ennen ISO-hyväksyntää ja oli de facto -formaatti varhaiselle GeoPDF-tulosteelle, joten suuri määrä perinteisiä karttoja kantaa sitä eikä mitään muuta.

Moderni järjestelmä on se, jonka ISO standardoi ISO 32000-1 §8.8.2 -kohdassa. Sivutason yksittäisen sanakirjan sijaan se mallintaa paikkatietoa sivun Viewport-katseluikkunana, johon on liitetty Measure-mittasanakirja, ja mittasanakirja nimeää maantieteellisen koordinaattijärjestelmän. Tämä on se koodaus, jota Acrobat ja nykyiset GIS-viejät kirjoittavat. Kestävän maahantuojan tulisi tarkistaa molemmat: lue ISO-mallin katseluikkunat ja palaa perinteiseen (tai tarkasta lisäksi) LGIDict tiedostoissa, jotka kantavat vain vanhaa rekisteröintiä.

Katseluikkunat ja niiden rajat

ISO-mallissa georekisteröinnin yksikkö on katseluikkuna (viewport), ja sivulla voi olla useita. Suuri arkki voi sijoittaa pääkartan yhteen suorakulmioon, eri mittakaavassa olevan upotuksen toiseen ja selitepaneelin, jolla ei ole lainkaan geoviitteistystä. Jokainen katseluikkuna kantaa BBox-arvoa, sivulla olevaa suorakulmiota, jota katseluikkuna hallitsee, joten lukija tietää, mihin arkin osaan tietty koordinaattijärjestelmä pätee. Klikatun pisteen osumatestaus näitä laatikoita vasten on tapa, jolla katseluohjelma päättää, mitä mittasanakirjaa käytetään.

PDFlibPas tuo valitun sivun katseluikkunat suoraan näkyviin. GetPageViewPortCount palauttaa niiden määrän, GetPageViewPortID muuttaa yksipohjaisen indeksin ViewPortID-kahvaksi, ja GetViewPortBBox lukee rajaavan suorakulmion yksi ulottuvuus kerrallaan. Dimension-argumentti valitsee minkä reunan tai ulottuvuuden haluat: 0 on vasen (Left), 1 on yläreuna (Top), 2 on leveys (Width), 3 on korkeus (Height), 4 on oikea (Right) ja 5 on alareuna (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;

Nollan arvoinen ViewPortID GetPageViewPortID-metodista tarkoittaa, ettei katseluikkunaa kyseisessä indeksissä löytynyt, joten tarkista se ennen kahvan välittämistä eteenpäin.

Mittasanakirjan sisällä

Geometria, joka rekisteröi sivun maailmaan, elää katseluikkunaan liitetyssä mittasanakirjassa. GetViewPortMeasureDict palauttaa MeasureDictID-arvon annetulle ViewPortID-kahvalle, tai nollan, kun katseluikkunalla ei ole mittasanakirjaa, mikä on tavallinen tapaus selite- tai otsikkopaneelille. Mittasanakirja sisältää kolme lukemisen arvoista asiaa: koordinaattijärjestelmät, joihin se viittaa, taulukot, jotka sitovat sivupisteet maantieteellisiin pisteisiin, ja yksikön, jossa pistetiedot ilmaistaan.

Itse rekisteröinti on kaksi rinnakkaista taulukkoa. GPTS on maantieteellisten pisteiden taulukko eli maantieteellisessä koordinaattijärjestelmässä annetut leveys- ja pituusasteparit. LPTS on sivuavaruuden pisteiden taulukko, ilmaistuna katseluikkunan BBox-arvon murto-osina, jotta ne selviävät skaalauksesta. LPTS-taulukon alkio n ja GPTS-taulukon alkio n nimeävät saman fyysisen sijainnin, kerran sivukoordinaateissa ja kerran maapallolla. Kolme tai useampi tällaista paria määrittää affiinin tai yleisessä tapauksessa projektiivisen muunnoksen, joka kartoittaa minkä tahansa katseluikkunan sisällä olevan sivukoordinaatin maailmankoordinaatiksi. Niiden lukeminen on kummankin taulukon läpikäyntiä samassa tahdissa.

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;

Mittasanakirja ilmoittaa myös näyttöyksikkönsä GetMeasureDictPDU-metodilla, joka ottaa UnitIndex-arvon 1 lineaarisille, 2 pinta-alalle tai 3 kulmayksiköille ja palauttaa tietyn yksikön tunnistavan koodin, esimerkiksi metrin tai kansainvälisen jalan lineaarisessa luokassa. Bounds-taulukko, joka luetaan GetMeasureDictBoundsItem-metodilla, kuvailee katseluikkunan sisällä olevan nelikulmion, jonka mittaus todellisuudessa kattaa, mikä ei aina ole koko suorakulmio.

WKT vastaan EPSG

GPTS-taulukon leveys- ja pituusasteet ovat merkityksettömiä tietämättä, mihin maantieteelliseen koordinaattijärjestelmään ne kuuluvat, sillä koordinaatti 51.5, -0.1 laskeutuu eri fyysiseen paikkaan WGS 84 -järjestelmässä kuin vanhemmassa kansallisessa datassa. Mittasanakirja vastaa tähän koordinaattijärjestelmäsanakirjan kautta, joka saavutetaan GetMeasureDictGCSDict-metodilla maantieteelliselle järjestelmälle. PDF kuvailee järjestelmän kahdella vaihtoehtoisella tavalla, ja lukijan on hyväksyttävä molemmat.

Ensimmäinen on WKT (Well-Known Text), itsenäinen merkkijono, joka ilmoittaa datumin, ellipsoidin, nollameridiaanin ja yksiköt kokonaisuudessaan. Se on monisanaisen tarkka mutta yksiselitteinen eikä vaadi ulkoista hakutaulukkoa. Toinen on EPSG-koodi, yksittäinen kokonaisluku, joka indeksoi koordinaattijärjestelmän EPSG-rekisterissä; 4326 on WGS 84, jota useimmat kuluttaja-GPS-laitteet käyttävät. EPSG on kompakti mutta olettaa lukijan pystyvän selvittämään koodin tietokantaa vasten. Tiedostoissa esiintyy joko toinen, molemmat tai ei kumpikaan, minkä vuoksi rajapinta tuo esiin kaikki kolme: GetCSDictType, GetCSDictEPSG ja GetCSDictWKT. GetCSDictType ilmoittaa, onko järjestelmä maantieteellinen (GEOGCS, paluuarvo 1) vai projisoitu (PROJCS, paluuarvo 2), antaen sinun tulkita loput oikein ennen kuin luotat siihen.

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;

Vanhan LGIDict-sanakirjan lukeminen

Tiedostot, jotka ovat peräisin ajalta ennen katseluikkunamallia tai jotka on tuotettu vanhempaa koodausta käyttävillä työkaluilla, kantavat rekisteröintiään sivun LGIDict-sanakirjassa mittasanakirjan sijaan. PDFlibPas ilmoittaa sivun tällaisten sanakirjojen määrän GetPageLGIDictCount-metodilla ja palauttaa kunkin raa'an sisällön GetPageLGIDictContent-metodilla, indeksöitynä yhdestä alkaen. Palautettu teksti on sanakirja sellaisena kuin se on kirjoitettu, sisältäen OGC 08-139r2 -rekisteröintikentät, jotka koodisi sitten jäsentää palauttaakseen samanlaisen sivu-maailma-kartoituksen kuin mittasanakirja tarjoaa. Kirjoituspuolella AddLGIDictToPage liittää LGIDict-sanakirjan nykyiseen sivuun, joten muunnin voi round-trippata perinteisen muodon, kun vanha kuluttaja sitä vielä odottaa.

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;

Lukutoiminnon kokoaminen

Täydellinen maahantuoja kohtelee kahta järjestelmää parina vaiheita jokaisella sivulla. Valitse sivu, pyydä GetPageViewPortCount-metodilla ISO-katseluikkunat, ja jokaiselle katseluikkunalle, joka omistaa mittasanakirjan, vedä sen BBox, sen GPTS- ja LPTS-taulukot, sen pisteyksikkö ja GCS-kuvaus koordinaattijärjestelmäsanakirjan kautta. Tarkista sitten GetPageLGIDictCount-metodilla mahdolliset vanhat rekisteröinnit, joita katseluikkunavaihe ei kattanut. Kartan, joka kantaa molempia, tulisi olla yhtä mieltä niiden välillä; kartta, joka kantaa vain toista, ratkeaa silti, koska katsoit molempiin paikkoihin. Matkan varrella palautetut kahvat, ViewPortID, MeasureDictID, CSDictID, ovat tavallisia kokonaislukuja, jotka pysyvät voimassa dokumentin ollessa ladattuna, joten koko läpikäynti on muutamia sisäkkäisiä silmukoita sivuluettelon yli ilman hallittavia varauksia.

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.