La maggior parte degli sviluppatori considera una pagina PDF come un foglio di carta contenente testo e immagini. Un PDF georeferenziato è molto di più. Contiene informazioni sufficienti per individuare un punto sulla pagina, misurato in normali unità di pagina, e restituire la latitudine e la longitudine corrispondenti nel mondo reale. Questo singolo fattore trasforma il PDF in un contenitore utile per mappe topografiche, planimetrie catastali, planimetrie di zone alluvionali o qualsiasi esportazione GIS che debba essere stampata conservando la sua validità geografica. La geometria è presente nel file; il problema risiede nel verificare se il proprio loader sia in grado di leggerla.
Questo aspetto viene spesso trascurato poiché un file GeoPDF si apre e si stampa esattamente come qualsiasi altro PDF. Nulla nella pagina tracciata segnala che la mappa sia associata a un sistema di coordinate. La georeferenziazione risiede nei dizionari associati all'oggetto pagina, non viene mai disegnata, e un visualizzatore che la ignora mostra comunque la mappa. Per eseguire qualsiasi operazione spaziale con il file, come la lettura di coordinate topografiche, la riproiezione o la sovrapposizione con altri layer, è necessario esaminare tali dizionari direttamente.
Due standard coesistono nel settore
Un lettore che debba gestire file reali deve supportare due schemi di georeferenziazione, poiché entrambi sono diffusi e un file può utilizzare l'uno o l'altro. Il sistema più datato è la codifica OGC descritta in OGC 08-139r2, che associa alla pagina un dizionario LGIDict (dizionario di registrazione geospaziale). È antecedente alle approvazioni ISO e rappresentava lo standard di fatto per le prime esportazioni GeoPDF; pertanto, un'ampia quantità di mappe storiche adotta questa specifica.
Lo schema moderno è quello standardizzato ISO in ISO 32000-1 §8.8.2. Invece di un singolo dizionario a livello di pagina, modella i dati geospaziali come una pagina Viewport con associato un dizionario di misura (Measure), e tale dizionario specifica il sistema di coordinate geografiche. Questa è la codifica scritta da Acrobat e dagli esportatori GIS attuali. Un importer solido esegue controlli per entrambi: legge le viewport per il modello ISO e ripiega sul dizionario LGIDict per i file che presentano solo la registrazione legacy.
Le viewport e i loro confini
Nel modello ISO, l'unità di georeferenziazione è la viewport, e una pagina può contenerne diverse. Un foglio di grandi dimensioni può ospitare una mappa principale in un rettangolo, un riquadro con scala differente in un altro e un pannello per la legenda privo di georeferenziazione. Ogni viewport contiene un elemento BBox, il rettangolo sulla pagina controllato dalla viewport, che indica al lettore quale parte del foglio sia associata al sistema di coordinate. Eseguire una verifica di corrispondenza (hit-test) tra il punto cliccato e questi riquadri consente al visualizzatore di stabilire quale dizionario di misura adottare.
PDFlibPas espone direttamente le viewport della pagina selezionata. GetPageViewPortCount restituisce il numero di viewport presenti, GetPageViewPortID converte un indice a base uno in un handle ViewPortID e GetViewPortBBox legge il rettangolo di delimitazione una coordinata alla volta. L'argomento Dimension seleziona il bordo o l'estensione desiderata: 0 indica Left, 1 Top, 2 Width, 3 Height, 4 Right e 5 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 valore ViewPortID pari a zero restituito da GetPageViewPortID indica che la viewport all'indice specificato non è stata trovata, quindi è opportuno effettuare una verifica prima di passare l'handle.
All'interno del dizionario di misura
La geometria che collega la pagina al mondo reale risiede nel dizionario di misura associato a una viewport. La funzione GetViewPortMeasureDict restituisce un valore MeasureDictID per una determinata ViewPortID, o zero se la viewport ne è priva, condizione comune per legende o pannelli dei titoli. Il dizionario di misura contiene tre elementi importanti: i sistemi di coordinate a cui fa riferimento, gli array che collegano i punti della pagina ai punti geografici e l'unità in cui sono espressi i dati dei punti.
La georeferenziazione si compone di due array paralleli. GPTS è l'array dei punti geografici, coppie di latitudine e longitudine espresse nel sistema di coordinate geografiche. LPTS is l'array dei punti nello spazio pagina, espressi come frazioni del BBox della viewport per garantirne la validità in caso di ridimensionamento. L'elemento n di LPTS e l'elemento n di GPTS identificano la stessa posizione fisica, espressa prima in coordinate di pagina e poi sul globo. Tre o più coppie di questo tipo definiscono la trasformazione affine o, nel caso generale, proiettiva che mappa qualsiasi coordinata di pagina nella viewport in una coordinata geografica. La lettura richiede l'analisi allineata di entrambi gli array.
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;
Il dizionario di misura indica anche le sue unità di visualizzazione tramite GetMeasureDictPDU, che accetta un valore UnitIndex pari a 1 per le unità lineari, 2 per la superficie o 3 per le unità angolari e restituisce un codice che identifica l'unità specifica (ad esempio, il metro o il piede internazionale per la categoria lineare). L'array Bounds, letto tramite GetMeasureDictBoundsItem, definisce il quadrilatero all'interno della viewport coperto dalla misurazione, che non sempre coincide con l'intero rettangolo.
WKT rispetto a EPSG
La latitudine e la longitudine in GPTS non hanno significato se non si conosce il sistema di coordinate geografiche a cui appartengono, poiché una coordinata impostata a 51.5, -0.1 si posiziona in un punto geografico diverso sotto lo standard WGS 84 rispetto a un sistema nazionale più datato. Il dizionario di misura chiarisce questo aspetto tramite un dizionario del sistema di coordinate, accessibile tramite GetMeasureDictGCSDict per il sistema geografico. Il PDF descrive tale sistema in uno dei due modi intercambiabili, ed è necessario che il lettore li supporti entrambi.
Il primo è WKT (Well-Known Text), una stringa definita che descrive il datum, l'ellissoide, il primo meridiano e le unità. È dettagliata ma priva di ambiguità e non richiede tabelle esterne. Il secondo è un codice EPSG, un singolo valore intero che indica un sistema di coordinate nel registro EPSG (ad esempio, 4326 rappresenta WGS 84, lo standard utilizzato dalla maggior parte dei GPS commerciali). L'EPSG è compatto ma richiede che il lettore sia in grado di risolvere il codice tramite un database. I file possono contenere l'uno, l'altro o entrambi, motivo per cui l'API fornisce le tre funzioni GetCSDictType, GetCSDictEPSG e GetCSDictWKT. La funzione GetCSDictType segnala se il sistema sia geografico (GEOGCS, valore restituito 1) o proiettato (PROJCS, valore restituito 2), consentendo di interpretare correttamente i restanti dati.
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;
Lettura del dizionario storico LGIDict
I file precedenti al modello viewport, o generati da strumenti che adottano le specifiche meno recenti, memorizzano la registrazione in un elemento LGIDict sulla pagina invece che in un dizionario di misura. PDFlibPas indica quanti dizionari di questo tipo siano presenti su una pagina tramite GetPageLGIDictCount e ne restituisce il contenuto non elaborato tramite GetPageLGIDictContent, con indicizzazione a partire da uno. Il testo restituito corrisponde al dizionario così come scritto, contenente i campi OGC 08-139r2 che il tuo codice dovrà analizzare per ricostruire il mapping tra pagina e spazio geografico fornito dal dizionario di misura. Per la scrittura, AddLGIDictToPage associa un LGIDict alla pagina corrente, consentendo a un convertitore di generare il formato legacy qualora l'applicazione destinataria lo richieda.
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;
Integrare le operazioni di lettura
Un importer completo considera i due schemi come una coppia di passaggi per ogni pagina. Seleziona la pagina, verifica tramite GetPageViewPortCount le viewport ISO e, per ogni viewport dotata di dizionario di misura, acquisisce il BBox, gli array GPTS e LPTS, l'unità di misura e la descrizione GCS tramite il dizionario del sistema di coordinate. Successivamente, verifica con GetPageLGIDictCount l'eventuale presenza di georeferenziazioni legacy non rilevate nel passaggio precedente. Una mappa che contiene entrambi i sistemi deve presentare dati allineati; una mappa con un solo sistema viene comunque risolta poiché la ricerca è stata eseguita in entrambi i punti. Gli handle restituiti nel processo, ovvero ViewPortID, MeasureDictID, CSDictID, sono semplici valori interi validi per l'intera durata di caricamento del documento, limitando l'intera scansione a pochi cicli nidificati sull'elenco delle pagine, senza allocazioni da gestire.
Una volta recuperata la georeferenziazione, la pagina diventa una sorgente di dati anziché una semplice immagine. Le tecniche correlate per leggere gli altri elementi della pagina sono trattate nell'articolo sull'estrazione di testo, immagini e font, e il rendering di un foglio georeferenziato su un dispositivo per misurazioni a schermo è descritto nella guida al contesto di stampa e anteprima. Il lettore geospaziale descritto qui è fornito all'interno di losLab PDF Library per Delphi e C++Builder, insieme alle API di caricamento, estrazione e rendering trattate in altre sezioni di questo blog.