Majoritatea dezvoltatorilor se gândesc la o pagină PDF ca la o foaie de hârtie cu text și imagini pe ea. Un PDF georeferențiat este mai mult decât atât. Conține suficiente informații pentru a lua un punct de pe pagină, măsurat în unități de pagină obișnuite, și a raporta latitudinea și longitudinea peste care se află în lumea reală. Acest singur fapt este cel care transformă un PDF într-un suport utilizabil pentru o hartă topografică, un plan de cadastru, o prezentare de zonă inundabilă sau orice export GIS care trebuie tipărit și să aibă în continuare o semnificație. Geometria se află în fișier; singura întrebare este dacă încărcătorul dvs. o citește.
Motivul pentru care acest lucru este trecut cu vederea este că un GeoPDF se deschide și se tipărește exact ca orice alt PDF. Nimic din pagina redată nu anunță că harta este înregistrată într-un sistem de coordonate. Înregistrarea trăiește în dicționare legate de obiectul pagină, care nu sunt desenate niciodată, iar un vizualizator care le ignoră vă arată harta oricum. Pentru a face ceva spațial cu fișierul, cum ar fi citirea coordonatelor de măsurare, reproiecția, suprapunerea peste alte straturi, trebuie să parcurgeți acele dicționare singuri.
Două standarde utilizate în practică
Un cititor care dorește să gestioneze fișiere din lumea reală trebuie să facă față la două scheme de georegistrare, deoarece ambele sunt în circulație și un anumit fișier poate folosi oricare dintre ele. Cea mai veche este codificarea OGC descrisă în OGC 08-139r2, care atașează paginii un LGIDict (un dicționar de înregistrare geospațială). Aceasta precede orice aprobare ISO și a fost formatul de facto pentru primele fișiere GeoPDF, astfel încât un corp mare de hărți vechi o conține doar pe aceasta.
Schema modernă este cea pe care ISO a standardizat-o în ISO 32000-1 §8.8.2. În loc de un singur dicționar la nivel de pagină, modelează datele geospațiale ca o fereastră de vizualizare (Viewport) a paginii cu un dicționar de măsurare (Measure) atașat, iar dicționarul de măsurare numește un sistem de coordonate geografice. Aceasta este codificarea pe care o scriu Acrobat și exportatorii GIS actuali. Un importator robust verifică ambele: citește ferestrele de vizualizare pentru modelul ISO și recurge la (sau inspectează suplimentar) LGIDict pentru fișierele care conțin doar înregistrarea veche.
Viewport-urile și limitele lor
În modelul ISO, unitatea de georegistrare este viewport-ul, iar o pagină poate avea mai multe. O foaie mare poate plasa o hartă principală într-un dreptunghi, o casetă de detaliu (inset) la o scară diferită în altul și un panou de legendă care nu este georeferențiat deloc. Fiecare viewport poartă un BBox, dreptunghiul de pe pagină pe care îl guvernează viewport-ul, astfel încât cititorul știe ce parte a foii se aplică unui anumit sistem de coordonate. Verificarea unui punct selectat în raport cu acele casete este modul în care un vizualizator decide ce dicționar de măsurare să folosească.
PDFlibPas expune direct ferestrele de vizualizare ale paginii selectate. GetPageViewPortCount returnează câte sunt, GetPageViewPortID transformă un index bazat pe 1 într-un descriptor ViewPortID, iar GetViewPortBBox citește dreptunghiul de încadrare dimensiune cu dimensiune. Argumentul Dimension selectează marginea sau extinderea pe care o doriți: 0 este Left, 1 este Top, 2 este Width, 3 este Height, 4 is Right și 5 este 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;
Un ViewPortID cu valoarea zero de la GetPageViewPortID înseamnă că viewport-ul de la acel index nu a putut fi găsit, așa că verificați-l înainte de a transmite descriptorul mai departe.
În interiorul dicționarului de măsurare
Geometria care înregistrează pagina în raport cu lumea se află în dicționarul de măsurare atașat unui viewport. GetViewPortMeasureDict returnează un MeasureDictID pentru un ViewPortID dat sau zero când viewport-ul nu are dicționar de măsurare, ceea ce reprezintă cazul normal pentru o legendă sau un panou de titlu. Dicționarul de măsurare conține trei lucruri care merită citite: sistemele de coordonate pe care le referă, matricele care leagă punctele paginii de punctele geografice și unitatea în care sunt exprimate datele punctelor.
Înregistrarea în sine este formată din două matrice paralele. GPTS este matricea de puncte geografice, perechi de latitudine și longitudine oferite în sistemul de coordonate geografice. LPTS este matricea de puncte din spațiul paginii, exprimate ca fracțiuni ale BBox-ului viewport-ului, astfel încât să supraviețuiască scalării. Elementul n din LPTS și elementul n din GPTS definesc aceeași locație fizică, o dată în coordonate de pagină și o dată pe glob. Trei sau mai multe astfel de perechi fixează transformarea afină sau, în cazul general, proiectivă, care mapează orice coordonată de pagină din interiorul viewport-ului într-o coordonată globală. Citirea lor este o chestiune de parcurgere a ambelor matrice în mod sincron.
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;
Dicționarul de măsurare își raportează de asemenea unitățile de afișare prin GetMeasureDictPDU, care preia un UnitIndex cu valoarea 1 pentru unități liniare, 2 pentru arie sau 3 pentru unități unghiulare și returnează un cod care identifică unitatea specifică, de exemplu un metru sau un picior internațional pentru categoria liniară. Matricea Bounds, citită cu GetMeasureDictBoundsItem, descrie patrulaterul din cadrul viewport-ului pe care îl acoperă efectiv măsurătoarea, care nu este întotdeauna întregul dreptunghi.
WKT versus EPSG
Latitudinea și longitudinea din GPTS nu au nicio semnificație fără a ști cărui sistem de coordonate geografice îi aparțin, deoarece o coordonată de 51.5, -0.1 ajunge într-un punct fizic diferit sub WGS 84 decât sub un datum național mai vechi. Dicționarul de măsurare oferă acest răspuns printr-un dicționar al sistemului de coordonate, accesat cu GetMeasureDictGCSDict pentru sistemul geografic. PDF descrie acel sistem în una dintre cele două modalități interschimbabile, iar un cititor trebuie să le accepte pe ambele.
Prima este WKT, Well-Known Text, un șir de caractere de sine stătător care definește în întregime datumul, elipsoidul, meridianul zero și unitățile. Este detaliat, dar lipsit de ambiguitate și nu necesită un tabel de căutare extern. Al doilea este un cod EPSG, un singur întreg care indexează un sistem de coordonate în registrul EPSG; 4326 este WGS 84, cadrul pe care îl folosesc majoritatea datelor GPS de consum. EPSG este compact, dar presupune că cititorul poate rezolva codul în raport cu o bază de date. Fișierele apar cu una, cealaltă sau ambele, motiv pentru care API-ul expune toate cele trei funcții: GetCSDictType, GetCSDictEPSG și GetCSDictWKT. GetCSDictType raportează dacă sistemul este geografic (un GEOGCS, valoarea de returnare 1) sau proiectat (un PROJCS, valoarea de returnare 2), permițându-vă să interpretați corect restul înainte de a avea încredere în el.
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;
Citirea dicționarului LGIDict moștenit
Fișierele care preced modelul viewport, sau care au fost produse de instrumente care încă emit codificarea mai veche, își transportă înregistrarea într-un LGIDict de pe pagină, mai degrabă decât într-un dicționar de măsurare. PDFlibPas raportează câte astfel de dicționare are o pagină prin GetPageLGIDictCount și returnează conținutul brut al fiecăruia cu GetPageLGIDictContent, indexat de la unu. Textul returnat este dicționarul așa cum a fost scris, conținând câmpurile de înregistrare OGC 08-139r2, pe care codul dvs. le analizează apoi pentru a recupera același tip de mapare pagină-lume pe care îl oferă dicționarul de măsurare. Pe partea de scriere, AddLGIDictToPage atașează un LGIDict paginii curente, astfel încât un convertor poate rula forma veche când un client vechi încă o așteaptă.
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;
Asamblarea citirii
Un importator complet tratează cele două scheme ca pe o pereche de treceri peste fiecare pagină. Selectați pagina, solicitați GetPageViewPortCount pentru viewport-urile ISO și, pentru fiecare viewport care deține un dicționar de măsurare, extrageți BBox-ul, matricele GPTS și LPTS, unitatea sa de date a punctului și descrierea GCS prin dicționarul sistemului de coordonate. Apoi verificați GetPageLGIDictCount pentru orice înregistrare veche pe care trecerea viewport-ului nu a acoperit-o. O hartă care le conține pe ambele ar trebui să fie de acord între ele; o hartă care conține doar una se rezolvă în continuare, deoarece ați căutat în ambele locuri. Descriptorii returnați pe parcurs, ViewPortID, MeasureDictID, CSDictID, sunt întregi simpli care rămân valabili în timp ce documentul este încărcat, astfel încât întreaga parcurgere constă în câteva bucle imbricate peste lista de pagini, fără alocări de gestionat.
Odată ce puteți recupera înregistrarea, pagina devine o sursă de date în loc de o imagini. Tehnicile asociate pentru citirea restului unei pagini sunt acoperite în articolul despre extragerea de text, imagini și fonturi, iar redarea unei foi georeferențiate pe un dispozitiv pentru măsurători pe ecran este descrisă în ghidul contextului de dispozitiv pentru tipărire și previzualizare. Cititorul geospațial descris aici este livrat ca parte a losLab PDF Library pentru Delphi și C++Builder, alături de API-urile de încărcare, extragere și redare acoperite în alte părți ale acestui blog.