Technical Article

GeoPDF di Delphi: Koordinat Geospasial Dengan PDFlibPas

Sebagian besar pengembang menganggap halaman PDF sebagai selembar kertas berisi teks dan gambar di atasnya. PDF yang direferensikan secara geografis (georeferenced PDF) lebih dari itu. File tersebut membawa cukup informasi untuk mengambil titik pada halaman, diukur dalam unit halaman biasa, dan melaporkan garis lintang (latitude) dan garis bujur (longitude) tempat ia berada di dunia nyata. Fakta tunggal tersebutlah yang mengubah PDF menjadi wadah yang dapat digunakan untuk peta topografi, plot survei kadaster, pameran zona banjir, atau ekspor GIS apa pun yang harus dicetak dan tetap memiliki arti. Geometrinya ada di dalam file; satu-satunya pertanyaan adalah apakah pemuat (loader) Anda membacanya.

Alasan mengapa hal ini terlewatkan adalah karena GeoPDF terbuka dan dicetak persis seperti PDF lainnya. Tidak ada apa pun di halaman yang dirender yang mengumumkan bahwa peta tersebut terdaftar ke sistem koordinat. Pendaftaran tersebut hidup di dalam kamus (dictionaries) yang menggantung di objek halaman, tidak pernah digambar, dan penampil yang mengabaikannya akan tetap menampilkan peta kepada Anda. Untuk melakukan tindakan spasial apa pun dengan file tersebut, seperti pembacaan koordinat survei, proyeksi ulang, overlay terhadap lapisan lain, Anda harus menelusuri kamus-kamus tersebut sendiri.

Dua standar hidup di lapangan

Pembaca yang ingin menangani file dunia nyata harus mampu menangani dua skema georegistrasi, karena keduanya beredar luas dan file tertentu dapat menggunakan salah satunya. Yang lebih tua adalah pengodean OGC yang dijelaskan dalam OGC 08-139r2, yang melampirkan LGIDict (kamus registrasi geospasial) ke halaman. Format ini mendahului persetujuan ISO apa pun dan merupakan format de facto untuk output GeoPDF awal, sehingga sebagian besar peta lama membawanya dan tidak ada yang lain.

Skema modern adalah yang distandarisasi ISO dalam ISO 32000-1 §8.8.2. Alih-alih satu kamus tingkat halaman, ia memodelkan data geospasial sebagai Viewport halaman dengan kamus Measure terlampir, dan kamus pengukuran tersebut menamai sistem koordinat geografis. Ini adalah pengodean yang ditulis oleh Acrobat dan pengekspor GIS saat ini. Importer yang tangguh memeriksa keduanya: baca viewport untuk model ISO, dan kembali ke (atau periksa tambahan) LGIDict untuk file yang hanya membawa registrasi lama.

Viewport dan batasannya

Dalam model ISO, unit georegistrasi adalah viewport, dan satu halaman dapat memiliki beberapa viewport. Selembar kertas besar dapat menempatkan peta utama di satu persegi panjang, inset dengan skala berbeda di persegi panjang lain, dan panel legenda yang tidak direferensikan secara geografis sama sekali. Setiap viewport membawa BBox, persegi panjang pada halaman yang diatur oleh viewport tersebut, sehingga pembaca mengetahui bagian lembar mana yang diterapkan sistem koordinat tertentu. Pengujian klik (hit-testing) titik yang diklik terhadap kotak-kotak tersebut adalah cara penampil memutuskan kamus pengukuran mana yang akan digunakan.

PDFlibPas mengekspos viewport dari halaman yang dipilih secara langsung. GetPageViewPortCount mengelleenkan berapa banyak viewport yang ada, GetPageViewPortID mengubah indeks berbasis satu menjadi handle ViewPortID, dan GetViewPortBBox membaca persegi panjang pembatas satu dimensi pada satu waktu. Argumen Dimension memilih tepi atau tingkat yang Anda inginkan: 0 adalah Kiri, 1 adalah Atas, 2 adalah Lebar, 3 adalah Tinggi, 4 adalah Kanan, dan 5 adalah Bawah.

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;

ViewPortID bernilai nol dari GetPageViewPortID berarti viewport pada indeks tersebut tidak dapat ditemukan, jadi periksa sebelum meneruskan handle tersebut.

Di dalam kamus pengukuran

Geometri yang mendaftarkan halaman ke dunia nyata hidup di kamus pengukuran yang terlampir pada viewport. GetViewPortMeasureDict mengembalikan MeasureDictID untuk ViewPortID yang diberikan, atau nol ketika viewport tidak memiliki kamus pengukuran, yang merupakan kasus normal untuk legenda atau panel judul. Kamus pengukuran menampung tiga hal yang layak dibaca: sistem koordinat yang dirujuknya, array yang mengikat titik halaman ke titik geografis, dan unit tempat data titik diekspresikan.

Registrasi itu sendiri adalah dua array paralel. GPTS adalah array titik geografis, pasangan garis lintang dan garis bujur yang diberikan dalam sistem koordinat geografis. LPTS adalah array titik ruang halaman, yang dinyatakan sebagai pecahan dari BBox viewport sehingga mereka bertahan dari penskalaan. Item n dari LPTS dan item n dari GPTS menyebutkan lokasi fisik yang sama, satu kali dalam koordinat halaman dan satu kali di bola dunia. Tiga atau lebih pasangan tersebut menetapkan transformasi afin, atau dalam kasus umum proyektif, yang memetakan koordinat halaman mana pun di dalam viewport ke koordinat dunia. Membacanya adalah masalah berjalan melalui kedua array secara bersamaan.

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;

Kamus pengukuran juga melaporkan unit tampilannya melalui GetMeasureDictPDU, yang mengambil UnitIndex bernilai 1 untuk linier, 2 untuk area, or 3 for angular units and returns a code identifying the specific unit, for example a meter or an international foot for the linear category. The Bounds array, read with GetMeasureDictBoundsItem, describes the quadrilateral within the viewport that the measurement actually covers, which is not always the full rectangle.

Konteks praktis

Garis lintang dan garis bujur di GPTS tidak berarti apa-apa tanpa mengetahui sistem koordinat geografis mana yang mereka miliki, karena koordinat 51.5, -0.1 mendarat di tempat fisik yang berbeda di bawah WGS 84 daripada di bawah datum nasional yang lebih tua. Kamus pengukuran menjawab ini melalui kamus sistem koordinat, yang dijangkau dengan GetMeasureDictGCSDict untuk sistem geografis. PDF menjelaskan sistem tersebut dalam salah satu dari dua cara yang dapat dipertukarkan, dan pembaca harus menerima salah satunya.

Pertama adalah WKT, Well-Known Text, string mandiri yang mengeja datum, ellipsoid, meridian utama, dan unit secara penuh. Ini verbose tetapi tidak ambigu dan tidak memerlukan tabel pencarian eksternal. Kedua adalah kode EPSG, integer tunggal yang mengindeks sistem koordinat dalam registri EPSG; 4326 adalah WGS 84, kerangka yang paling banyak digunakan oleh data GPS konsumen. EPSG kompak tetapi mengasumsikan pembaca dapat menyelesaikan kode tersebut terhadap database. File muncul dengan salah satu, yang lain, atau keduanya, itulah sebabnya API memunculkan ketiga fungsi GetCSDictType, GetCSDictEPSG, dan GetCSDictWKT. GetCSDictType melaporkan apakah sistem tersebut geografis (GEOGCS, nilai kembalian 1) atau terproyeksi (PROJCS, nilai kembalian 2), memungkinkan Anda menafsirkannya dengan benar sebelum Anda mempercayainya.

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;

Membaca LGIDict lama

File yang mendahului model viewport, atau yang diproduksi oleh alat yang masih mengeluarkan pengodean lama, membawa registrasi mereka di LGIDict pada halaman, bukan di kamus pengukuran. PDFlibPas melaporkan berapa banyak kamus tersebut yang dimiliki halaman melalui GetPageLGIDictCount dan menyerahkan kembali konten mentah masing-masing dengan GetPageLGIDictContent, diindeks dari satu. Teks yang dikembalikan adalah kamus seperti yang ditulis, memegang kolom registrasi OGC 08-139r2, yang kemudian diurai oleh kode Anda untuk memulihkan jenis pemetaan halaman-ke-dunia yang sama dengan yang disediakan oleh kamus pengukuran. Pada sisi penulisan, AddLGIDictToPage melampirkan LGIDict ke halaman saat ini, sehingga konverter dapat memproses pulang-pergi bentuk warisan tersebut ketika konsumen lama masih mengharapkannya.

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;

Menyatukan pembacaan

Importir lengkap memperlakukan kedua skema sebagai sepasang lintasan di setiap halaman. Pilih halaman, minta GetPageViewPortCount untuk viewport ISO, dan untuk setiap viewport yang memiliki kamus pengukuran, tarik BBox, array GPTS dan LPTS, unit data titik, dan deskripsi GCS melalui kamus sistem koordinat. Kemudian periksa GetPageLGIDictCount untuk setiap registrasi lama yang tidak tercakup oleh lintasan viewport. Peta yang membawa keduanya harus sepakat di antara mereka; peta yang hanya membawa satu tetap diselesaikan, karena Anda melihat di kedua tempat. Handle yang dikembalikan di sepanjang jalan, ViewPortID, MeasureDictID, CSDictID, adalah integer biasa yang tetap valid saat dokumen dimuat, sehingga seluruh alur penelusuran adalah beberapa loop bersarang di atas daftar halaman tanpa ada alokasi memori yang perlu dikelola.

Setelah Anda dapat memulihkan registrasi, halaman tersebut menjadi sumber data, bukan hanya sekadar gambar. Teknik pendamping untuk membaca sisa halaman dibahas dalam artikel tentang ekstraksi teks, gambar, dan font, dan merender lembar yang direferensikan secara geografis ke perangkat untuk pengukuran di layar dijelaskan dalam panduan konteks-perangkat cetak dan pratinjau. Pembaca geospasial yang dijelaskan di sini dikirimkan sebagai bagian dari losLab PDF Library untuk Delphi and C++Builder, alongside the loading, extraction, and rendering APIs covered elsewhere on this blog.