Technical Article

Delphi'de GeoPDF: PDFlibPas ile Coğrafi Koordinatlar

Çoğu geliştirici bir PDF sayfasını üzerinde metin ve resimler olan bir kağıt yaprağı olarak düşünür. Coğrafi referanslı bir PDF bundan daha fazlasıdır. Sayfadaki sıradan sayfa birimleriyle ölçülen bir noktayı alıp gerçek dünyada üzerinde oturduğu enlem ve boylamı bildirecek kadar bilgi taşır. Bu tek gerçek, bir PDF'i topoğrafik bir harita, bir kadastro araştırma planı, bir sel bölgesi sergisi veya basılması gereken ve hala bir şey ifade eden herhangi bir Coğrafi Bilgi Sistemi (GIS) aktarımı için kullanılabilir bir taşıyıcıya dönüştüren şeydir. Geometri dosyada mevcuttur; tek soru yükleyicinizin bunu okuyup okumadığıdır

Bunun gözden kaçmasının nedeni, bir GeoPDF'in tıpkı diğer tüm PDF'ler gibi açılması ve basılmasıdır. İşlenen sayfada haritanın bir koordinat sistemine kayıtlı olduğunu bildiren hiçbir şey yoktur. Kayıt, sayfa nesnesine bağlı olan, asla çizilmeyen sözlüklerde yaşar ve bunları yok sayan bir görüntüleyici size haritayı yine de gösterir. Dosyayla mekansal herhangi bir şey yapmak için (ölçüm koordinat okumaları, yeniden projeksiyon, diğer katmanlarla üst üste bindirme) o sözlükleri kendiniz dolaşmak zorundasınız

Doğada iki standart yaşamaktadır

Gerçek dünyadaki dosyaları işlemek isteyen bir okuyucu iki coğrafi kayıt şemasıyla başa çıkmak zorundadır, çünkü ikisi de dolaşımdadır ve belirli bir dosya herhangi birini kullanabilir. Eski olanı, sayfaya bir LGIDict (coğrafi kayıt sözlüğü) bağlayan, OGC 08-139r2'de açıklanan OGC kodlamasıdır. Herhangi bir ISO onayından öncesine dayanır ve erken GeoPDF çıktısı için fiili biçimdi; bu nedenle eski haritaların büyük bir kısmı bunu ve başka hiçbir şeyi taşımaz

Modern şema, ISO'nun ISO 32000-1 §8.8.2'de standartlaştırdığı şemadır. Tek bir sayfa düzeyinde sözlük yerine coğrafi verileri, ekli bir Ölçü (Measure) sözlüğüne sahip bir sayfa Görünüm Penceresi (Viewport) olarak modeller ve ölçü sözlüğü bir coğrafi koordinat sistemini adlandırır. Bu, Acrobat ve mevcut GIS aktarıcılarının yazdığı kodlamadır. Sağlam bir ithalatçı ikisini de kontrol eder: ISO modeli için görünüm pencerelerini okur ve yalnızca eski kaydı taşıyan dosyalar için LGIDict'e geri döner (veya ek olarak inceler)

Görünüm pencereleri ve sınırları

ISO modelinde coğrafi kaydın birimi görünüm penceresidir ve bir sayfanın birkaç görünüm penceresi olabilir. Büyük bir pafta, bir dikdörtgene ana haritayı, diğerine farklı bir ölçekte bir ayrıntıyı ve coğrafi referansı hiç olmayan bir lejant panelini yerleştirebilir. Her görünüm penceresi, görünüm penceresinin yönettiği sayfadaki dikdörtgen olan bir BBox taşır, böylece okuyucu belirli bir koordinat sisteminin paftanın hangi bölümüne uygulanacağını bilir. Tıklanan bir noktanın bu kutulara karşı isabet testi yapılması, görüntüleyicinin hangi ölçü sözlüğünü kullanacağına karar verme şeklidir

PDFlibPas seçilen sayfanın görünüm pencerelerini doğrudan ortaya çıkarır. GetPageViewPortCount kaç tane olduğunu döndürür, GetPageViewPortID bir tabanlı bir dizini bir ViewPortID tutamacına dönüştürür ve GetViewPortBBox sınırlayıcı dikdörtgeni bir seferde bir boyut olarak okur. Dimension bağımsız değişkeni hangi kenarı veya uzantıyı istediğinizi seçer: 0 Sol, 1 Üst, 2 Genişlik, 3 Yükseklik, 4 Sağ ve 5 Alt'tır

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;

GetPageViewPortID'den sıfır değerinde bir ViewPortID gelmesi, o dizindeki görünüm penceresinin bulunamadığı anlamına gelir, bu nedenle tutamacı geçirmeden önce kontrol edin

Ölçü sözlüğünün içi

Sayfayı dünyaya kaydeden geometri, bir görünüm penceresine ekli ölçü sözlüğünde yaşar. GetViewPortMeasureDict, belirli bir ViewPortID için bir MeasureDictID veya görünüm penceresinin ölçü sözlüğü olmadığında (bu, bir lejant veya başlık paneli için normal durumdur) sıfır döndürür. Ölçü sözlüğü okumaya değer üç şey tutar: başvurduğu koordinat sistemleri, sayfa noktalarını coğrafi noktalara bağlayan diziler ve nokta verilerinin ifade edildiği birim

Kaydın kendisi iki paralel dizidir. GPTS, coğrafi koordinat sisteminde verilen enlem ve boylam çiftleri olan coğrafi noktalar dizisidir. LPTS, ölçeklendirmeden sağ çıkabilmeleri için görünüm penceresinin BBox'ının kesirleri olarak ifade edilen sayfa alanı noktaları dizisidir. LPTS'nin n. öğesi ve GPTS'nin n. öğesi, biri sayfa koordinatlarında ve diğeri yerküre üzerinde olmak üzere aynı fiziksel konumu adlandırır. Üç veya daha fazla bu tür çift, görünüm penceresi içindeki herhangi bir sayfa koordinatını bir dünya koordinatına eşleyen afin (affine) veya genel durumda projektif dönüşümü sabitler. Bunları okumak, her iki diziyi de adım adım dolaşma meselesidir

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;

Ölçü sözlüğü ayrıca, doğrusal birimler için 1, alan birimleri için 2 veya açısal birimler için 3 olan bir UnitIndex alan ve belirli birimi tanımlayan bir kod döndüren (örneğin doğrusal kategori için bir metre veya uluslararası fit) GetMeasureDictPDU aracılığıyla görüntüleme birimlerini bildirir. GetMeasureDictBoundsItem ile okunan Bounds dizisi, ölçümün gerçekte kapsadığı görünüm penceresi içindeki dörtgeni tanımlar ki bu her zaman tam dikdörtgen değildir

WKT'ye karşı EPSG

GPTS'deki enlem ve boylam, hangi coğrafi koordinat sistemine ait olduklarını bilmeden anlamsızdır, çünkü 51.5, -0.1 koordinatı, WGS 84 altında eski bir ulusal datuma göre farklı bir fiziksel noktaya düşer. Ölçü sözlüğü bunu coğrafi sistem için GetMeasureDictGCSDict ile ulaşılan bir koordinat sistemi sözlüğü aracılığıyla yanıtlar. PDF bu sistemi birbiriyle değiştirilebilir iki yoldan biriyle tanımlar ve bir okuyucu her ikisini de kabul etmek zorundadır

Birincisi, datum, elipsoid, başlangıç meridyeni ve birimleri tam olarak açıklayan bağımsız bir dize olan WKT'dir (Well-Known Text). Ayrıntılıdır ancak nettir ve harici bir arama tablosuna ihtiyaç duymaz. İkincisi, EPSG kaydındaki bir koordinat sistemini dizinleyen tek bir tamsayı olan EPSG kodudur; 4326, çoğu tüketici GPS verisinin kullandığı çerçeve olan WGS 84'tür. EPSG kompakttır ancak okuyucunun kodu bir veritabanına karşı çözebileceğini varsayar. Dosyalar biriyle, diğeriyle veya her ikisiyle birlikte görünür; bu nedenle API, GetCSDictType, GetCSDictEPSG ve GetCSDictWKT'nin üçünü de yüzeye çıkarır. GetCSDictType, sistemin coğrafi mi (GEOGCS, dönüş değeri 1) yoksa projeksiyonlu mu (PROJCS, dönüş değeri 2) olduğunu bildirerek güvenmeden önce geri kalanını doğru yorumlamanızı sağlar

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;

Eski LGIDict'i okuma

Görünüm penceresi modelinden öncesine dayanan veya hala eski kodlamayı yayan araçlar tarafından üretilen dosyalar, kayıtlarını bir ölçü sözlüğü yerine sayfadaki bir LGIDict'te taşır. PDFlibPas, bir sayfanın bu tür kaç sözlüğe sahip olduğunu GetPageLGIDictCount aracılığıyla bildirir ve bir tabanlı dizinlenmiş her birinin ham içeriğini GetPageLGIDictContent ile geri verir. Döndürülen metin, yazıldığı şekliyle sözlüktür; OGC 08-139r2 kayıt alanlarını tutar ve kodunuz daha sonra ölçü sözlüğünün sağladığı aynı türden sayfadan dünyaya eşlemeyi kurtarmak için bunu ayrıştırır. Yazma tarafında, AddLGIDictToPage geçerli sayfaya bir LGIDict bağlar, böylece eski bir tüketici hala bunu beklediğinde bir dönüştürücü eski biçimi dönüştürebilir

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;

Okumayı bir araya getirme

Tam bir ithalatçı, iki şemayı her sayfa üzerinde bir çift geçiş olarak ele alır. Sayfayı seçin, ISO görünüm pencereleri için GetPageViewPortCount'a sorun ve ölçü sözlüğüne sahip her görünüm penceresi için BBox'ını, GPTS ve LPTS dizilerini, nokta veri birimini ve koordinat sistemi sözlüğü aracılığıyla GCS tanımını çekin. Ardından, görünüm penceresi geçişinin kapsamadığı herhangi bir eski kayıt için GetPageLGIDictCount'u kontrol edin. Her ikisini de taşıyan bir harita kendi aralarında anlaşmalıdır; yalnızca birini taşıyan bir harita yine de çözülür, çünkü her iki yere de baktınız. Yol boyunca döndürülen tutamaçlar (ViewPortID, MeasureDictID, CSDictID) belge yüklü kaldığı sürece geçerli kalan düz tamsayılardır; bu nedenle tüm dolaşma, yönetilecek hiçbir tahsis olmadan sayfa listesi üzerinde birkaç iç içe döngüdür

Kaydı kurtarabildiğinizde, sayfa bir resimden ziyade bir veri kaynağı haline gelir. Bir sayfanın geri kalanını okumak için yardımcı teknikler, metin, resim ve yazı tipi çıkarma hakkındaki makalede ele alınmıştır ve ekran ölçümü için coğrafi referanslı bir paftayı bir cihaza işlemek, yazdırma ve önizleme cihaz bağlamı kılavuzunda açıklanmıştır. Burada açıklanan coğrafi veri okuyucusu, bu blogun başka yerlerinde ele alınan yükleme, çıkarma ve işleme API'lerinin yanı sıra Delphi ve C++Builder için losLab PDF Kitaplığı'nın bir parçası olarak sunulmaktadır