La plupart des développeurs considèrent une page PDF comme une feuille de papier avec du texte et des images dessus. Un PDF géoréférencé est plus que cela. Il contient suffisamment d'informations pour prendre un point sur la page, mesuré en unités de page ordinaires, et rapporter la latitude et la longitude au-dessus desquelles il se trouve dans le monde réel. Ce simple fait est ce qui transforme un PDF en un support utilisable pour une carte topographique, un plan de relevé cadastral, une exposition de zone inondable, ou toute exportation SIG qui doit être imprimée et conserver une signification. La géométrie est présente dans le fichier ; la seule question est de savoir si votre chargeur la lit
La raison pour laquelle cela passe inaperçu est qu'un GeoPDF s'ouvre et s'imprime exactement comme n'importe quel autre PDF. Rien dans la page rendue n'annonce que la carte est calée sur un système de coordonnées. L'enregistrement réside dans des dictionnaires rattachés à l'objet de page, jamais dessinés, et un visualiseur qui les ignore vous montre la carte tout de même. Pour faire quoi que ce soit de spatial avec le fichier, lecture de coordonnées de topographie, reprojection, superposition à d'autres couches, vous devez parcourir ces dictionnaires vous-même
Deux normes coexistent dans la nature
Un lecteur qui souhaite gérer des fichiers du monde réel doit faire face à deux schémas d'enregistrement géographique, car les deux sont en circulation et un fichier donné peut utiliser l'un ou l'autre. Le plus ancien est l'encodage OGC décrit dans OGC 08-139r2, qui attache un LGIDict (un dictionnaire d'enregistrement géospatial) à la page. Il précède toute approbation ISO et était le format de facto pour les premières sorties GeoPDF, de sorte qu'un grand nombre de cartes héritées le comportent et rien d'autre
Le schéma moderne est celui que l'ISO a normalisé dans ISO 32000-1 §8.8.2. Au lieu d'un seul dictionnaire au niveau de la page, il modélise les données géospatiales sous la forme d'une Viewport de page à laquelle est rattaché un dictionnaire Measure, et le dictionnaire de mesure nomme un système de coordonnées géographiques. C'est l'encodage qu'Acrobat et les exportateurs SIG actuels écrivent. Un importateur robuste vérifie les deux : il lit les fenêtres d'affichage pour le modèle ISO, et se rabat (ou inspecte en outre) sur le LGIDict pour les fichiers qui ne portent que l'enregistrement hérité
Les fenêtres d'affichage et leurs limites
Dans le modèle ISO, l'unité du géoréférencement est la fenêtre d'affichage (viewport), et une page peut en comporter plusieurs. Une grande feuille peut placer une carte principale dans un rectangle, un encart à une échelle différente dans un autre, et un panneau de légende qui n'est pas géoréférencé du tout. Chaque fenêtre d'affichage possède une BBox, le rectangle sur la page que la fenêtre d'affichage régit, de sorte que le lecteur sache à quelle partie de la feuille s'applique un système de coordonnées donné. Tester si un point cliqué se trouve dans ces boîtes est la façon dont un visualiseur décide quel dictionnaire de mesure utiliser
PDFlibPas expose directement les fenêtres d'affichage de la page sélectionnée. GetPageViewPortCount renvoie leur nombre, GetPageViewPortID transforme un index basé sur un en un descripteur ViewPortID, et GetViewPortBBox lit le rectangle de délimitation une dimension à la fois. L'argument Dimension sélectionne le bord ou l'étendue que vous souhaitez : 0 pour Gauche, 1 pour Haut, 2 pour Largeur, 3 pour Hauteur, 4 pour Droite, et 5 pour Bas
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 de zéro provenant de GetPageViewPortID signifie que la fenêtre d'affichage à cet index n'a pas pu être trouvée, vérifiez-le donc avant de transmettre le descripteur
À l'intérieur du dictionnaire de mesure
La géométrie qui cale la page au monde réside dans le dictionnaire de mesure attaché à une fenêtre d'affichage. GetViewPortMeasureDict renvoie un MeasureDictID pour un ViewPortID donné, ou zéro lorsque la fenêtre d'affichage n'a pas de dictionnaire de mesure, ce qui est le cas normal pour une légende ou un panneau de titre. Le dictionnaire de mesure contient trois choses valant la peine d'être lues : les systèmes de coordonnées qu'il référence, les tableaux qui lient les points de la page aux points géographiques, et l'unité dans laquelle les données de points sont exprimées
L'enregistrement lui-même est constitué de deux tableaux parallèles. GPTS est le tableau des points géographiques, paires de latitude et de longitude données dans le système de coordonnées géographiques. LPTS est le tableau des points dans l'espace de la page, exprimés sous forme de fractions de la BBox de la fenêtre d'affichage afin qu'ils survivent à la mise à l'échelle. L'élément n de LPTS et l'élément n de GPTS désignent le même emplacement physique, une fois en coordonnées de page et une fois sur le globe. Trois ou plus de ces paires définissent la transformation affine, ou dans le cas général projective, qui mappe toute coordonnée de page à l'intérieur de la fenêtre d'affichage vers une coordonnée mondiale. Les lire consiste à parcourir les deux tableaux de concert
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;
Le dictionnaire de mesure indique également ses unités d'affichage via GetMeasureDictPDU, qui prend un UnitIndex de 1 pour les unités linéaires, 2 pour les surfaces, ou 3 pour les unités angulaires et renvoie un code identifiant l'unité spécifique, par exemple un mètre ou un pied international pour la catégorie linéaire. Le tableau Bounds, lu avec GetMeasureDictBoundsItem, décrit le quadrilatère à l'intérieur de la fenêtre d'affichage que la mesure couvre réellement, ce qui n'est pas toujours le rectangle complet
WKT contre EPSG
La latitude et la longitude dans GPTS n'ont aucun sens sans savoir à quel système de coordonnées géographiques elles appartiennent, car une coordonnée de 51.5, -0.1 atterrit à un endroit physique différent sous WGS 84 que sous un système géodésique national plus ancien. Le dictionnaire de mesure répond à cela par un dictionnaire de système de coordonnées, atteint avec GetMeasureDictGCSDict pour le système géographique. Le PDF décrit ce système de l'une des deux manières interchangeables, et un lecteur doit accepter l'une ou l'autre
La première est WKT, Well-Known Text, une chaîne autonome qui détaille en entier le système géodésique (datum), l'ellipsoïde, le méridien principal et les unités. Elle est verbeuse mais sans ambiguïté et ne nécessite aucune table de recherche externe. La seconde est un code EPSG, un simple nombre entier qui indexe un système de coordonnées dans le registre EPSG ; 4326 est WGS 84, le cadre que la plupart des données GPS grand public utilisent. EPSG est compact mais suppose que le lecteur peut résoudre le code par rapport à une base de données. Les fichiers apparaissent avec l'un, l'autre, ou les deux, c'est pourquoi l'API expose les trois : GetCSDictType, GetCSDictEPSG et GetCSDictWKT. GetCSDictType indique si le système est géographique (un GEOGCS, valeur de retour 1) ou projeté (un PROJCS, valeur de retour 2), vous permettant d'interpréter le reste correctement avant de vous y fier
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;
Lecture du LGIDict hérité
Les fichiers antérieurs au modèle de fenêtre d'affichage (viewport), ou ceux qui ont été produits par des outils émettant encore l'ancien encodage, portent leur enregistrement dans un LGIDict sur la page plutôt que dans un dictionnaire de mesure. PDFlibPas indique combien de tels dictionnaires possède une page grâce à GetPageLGIDictCount et renvoie le contenu brut de chacun avec GetPageLGIDictContent, indexé à partir de un. Le texte renvoyé est le dictionnaire tel qu'il est écrit, contenant les champs d'enregistrement OGC 08-139r2, que votre code analyse ensuite pour récupérer le même type de mappage page-vers-monde que le dictionnaire de mesure fournit. Du côté de l'écriture, AddLGIDictToPage attache un LGIDict à la page courante, de sorte qu'un convertisseur puisse effectuer un aller-retour vers la forme héritée lorsqu'un ancien consommateur l'attend toujours
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;
Rassembler la lecture
Un importateur complet traite les deux schémas comme une paire de passages sur chaque page. Sélectionnez la page, demandez à GetPageViewPortCount les fenêtres d'affichage ISO, et pour chaque fenêtre d'affichage qui possède un dictionnaire de mesure, extrayez sa BBox, ses tableaux GPTS et LPTS, son unité de données de point et la description du GCS via le dictionnaire du système de coordonnées. Vérifiez ensuite GetPageLGIDictCount pour tout enregistrement hérité que le passage des fenêtres d'affichage n'a pas couvert. Une carte qui porte les deux devrait présenter une concordance entre eux ; une carte qui n'en porte qu'un se résout quand même, parce que vous avez regardé aux deux endroits. Les descripteurs renvoyés en cours de route, ViewPortID, MeasureDictID, CSDictID, sont de simples nombres entiers qui restent valides pendant que le document est chargé, de sorte que le parcours entier est constitué de quelques boucles imbriquées sur la liste des pages sans aucune allocation à gérer
Une fois que vous pouvez récupérer l'enregistrement, la page devient une source de données plutôt qu'une image. Les techniques complémentaires pour lire le reste d'une page sont couvertes dans l'article sur l'extraction de texte, d'image et de police, et le rendu d'une feuille géoréférencée sur un périphérique pour une mesure à l'écran est décrit dans le guide du contexte de périphérique d'impression et d'aperçu. Le lecteur géospatial décrit ici est fourni dans le cadre de la Bibliothèque PDF losLab pour Delphi et C++Builder, avec les API de chargement, d'extraction et de rendu couvertes ailleurs sur ce blog