Technical Article

مؤلفه GeoPDF در Delphi: مختصات جغرافیایی با PDFlibPas

بیشتر توسعه‌دهندگان یک صفحه PDF را به عنوان برگی از کاغذ با متن و تصاویر روی آن تصور می‌کنند. یک PDF زمین‌مرجع‌شده (Georeferenced) چیزی فراتر از این است. این فایل اطلاعات کافی برای گرفتن یک نقطه از صفحه، اندازه‌گیری‌شده در واحدهای معمولی صفحه، و گزارش طول و عرض جغرافیایی را که در دنیای واقعی روی آن قرار دارد حمل می‌کند. این حقیقت واحد چیزی است که یک PDF را به یک حامل قابل استفاده برای یک نقشه توپوگرافی، یک طرح نقشه‌برداری کاداستر، یک نمایشگاه منطقه سیل‌زده یا هر خروجی GIS تبدیل می‌کند که باید چاپ شود و همچنان معنایی داشته باشد. هندسه در فایل وجود دارد؛ تنها سوال این است که آیا بارگذار شما آن را می‌خواند یا خیر

دلیل نادیده گرفته شدن این موضوع این است که یک GeoPDF دقیقاً مانند هر PDF دیگری باز و چاپ می‌شود. هیچ چیز در صفحه رندرشده اعلام نمی‌کند که نقشه با یک سیستم مختصات ثبت شده است. ثبت در دیکشنری‌های آویزان از شیء صفحه زندگی می‌کند، هرگز رسم نمی‌شود، و نمایشگری که آن‌ها را نادیده می‌گیرد نقشه را به همان شکل به شما نشان می‌دهد. برای انجام هر کار فضایی با فایل، خواندن مختصات نقشه‌برداری، پروژه مجدد، قرار دادن روی لایه‌های دیگر، باید خودتان آن دیکشنری‌ها را پیمایش کنید

دو استاندارد در دنیای واقعی وجود دارند

خواننده‌ای که می‌خواهد فایل‌های دنیای واقعی را مدیریت کند باید با دو طرح ثبت جغرافیایی کنار بیاید، زیرا هر دو در گردش هستند و یک فایل ممکن است از هر کدام استفاده کند. طرح قدیمی‌تر، رمزگذاری OGC است که در سند OGC 08-139r2 توصیف شده و یک LGIDict (دیکشنری ثبت جغرافیایی) را به صفحه متصل می‌کند. این طرح قبل از تأیید ISO وجود داشت و فرمت دوفاکتو برای خروجی‌های اولیه GeoPDF بود، بنابراین بخش بزرگی از نقشه‌های قدیمی آن را حمل می‌کنند و نه چیز دیگری را

طرح مدرن موردی است که ISO در بخش ۸.۸.۲ استاندارد ISO 32000-1 استاندارد کرده است. به جای یک دیکشنری واحد در سطح صفحه، داده‌های جغرافیایی را به عنوان یک پورت دید (Viewport) صفحه با یک دیکشنری اندازه‌گیری (Measure) متصل مدل‌سازی می‌کند و دیکشنری اندازه‌گیری نام یک سیستم مختصات جغرافیایی را مشخص می‌سازد. این رمزگذاری است که Acrobat و صادرکنندگان فعلی GIS می‌نویسند. یک واردکننده قوی هر دو را بررسی می‌کند: خواندن پورت‌های دید برای مدل ISO و بازگشت به (یا بازرسی اضافی) LGIDict برای فایل‌هایی که فقط ثبت قدیمی را حمل می‌کنند

پورت‌های دید و مرزهای آن‌ها

در مدل ISO، واحد ثبت جغرافیایی همان پورت دید (Viewport) است و یک صفحه ممکن است چندین پورت دید داشته باشد. یک برگ بزرگ می‌تواند نقشه اصلی را در یک مستطیل، یک نمای نزدیک (Inset) با مقیاس متفاوت را در مستطیل دیگر و پنل راهنمای نقشه را که اصلاً زمین‌مرجع نیست قرار دهد. هر پورت دید شامل یک BBox، یعنی همان مستطیل روی صفحه که پورت دید آن را اداره می‌کند، بنابراین خواننده می‌داند که یک سیستم مختصات مشخص به کدام بخش از برگ اعمال می‌شود. تست کلیک روی نقطه در برابر آن کادرها روشی است که یک نمایشگر تصمیم می‌گیرد از کدام دیکشنری اندازه‌گیری استفاده کند

کتابخانه PDFlibPas پورت‌های دید صفحه انتخاب‌شده را مستقیماً نشان می‌دهد. متد GetPageViewPortCount تعداد آن‌ها را برمی‌گرداند، GetPageViewPortID یک نمایه مبتنی بر یک را به یک هندل ViewPortID تبدیل می‌کند و GetViewPortBBox مستطیل محدودکننده را در هر زمان برای یک بعد می‌خواند. آرگومان Dimension لبه یا محدوده مورد نظر شما را انتخاب می‌کند: 0 برای چپ، 1 برای بالا، 2 برای عرض، 3 برای ارتفاع، 4 برای راست و 5 برای پایین

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 از GetPageViewPortID به این معنی است که پورت دید در آن نمایه یافت نشد، بنابراین قبل از ارسال هندل آن را بررسی کنید

درون دیکشنری اندازه‌گیری

هندسه‌ای که صفحه را به دنیا ثبت می‌کند در دیکشنری اندازه‌گیری متصل به یک پورت دید قرار دارد. متد GetViewPortMeasureDict یک MeasureDictID برای یک ViewPortID مشخص برمی‌گرداند، یا زمانی که پورت دید فاقد دیکشنری اندازه‌گیری است (که حالت معمولی برای راهنما یا پنل عنوان است) مقدار صفر را بازمی‌گرداند. دیکشنری اندازه‌گیری شامل سه مورد است که ارزش خواندن دارند: سیستم‌های مختصاتی که به آن‌ها ارجاع می‌دهد، آرایه‌هایی که نقاط صفحه را به نقاط جغرافیایی گره می‌زنند و واحدی که داده‌های نقطه در آن بیان می‌شوند

خود ثبت، شامل دو آرایه موازی است. آرایه GPTS همان آرایه نقاط جغرافیایی، یعنی جفت‌های طول و عرض جغرافیایی ارائه شده در سیستم مختصات جغرافیایی است. آرایه LPTS آرایه نقاط فضای صفحه است که به صورت کسرهایی از BBox پورت دید بیان می‌شوند تا در هنگام مقیاس‌گذاری زنده بمانند. آیتم n از LPTS و آیتم n از GPTS یک مکان فیزیکی را نام می‌برند، یک بار در مختصات صفحه و یک بار روی کره زمین. سه یا چند جفت از این دست، تبدیل افین (Affine) یا در حالت کلی پروژکتیو (Projective) را که هر مختصات صفحه‌ای را در پورت دید به یک مختصات جهانی نگاشت می‌کند، مشخص می‌سازند. خواندن آن‌ها به معنای پیمایش هم‌زمان هر دو آرایه است

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;

دیکشنری اندازه‌گیری همچنین واحدهای نمایش خود را از طریق GetMeasureDictPDU گزارش می‌دهد که یک UnitIndex با مقدار 1 برای خطی، 2 برای مساحت، یا 3 برای واحدهای زاویه‌ای می‌گیرد و کدی را برای شناسایی واحد خاص برمی‌گرداند، به عنوان مثال متر یا فوت بین‌المللی برای دسته خطی. آرایه Bounds که با GetMeasureDictBoundsItem خوانده می‌شود، چهارضلعی داخل پورت دید را توصیف می‌کند که اندازه‌گیری در واقع آن را پوشش می‌دهد، که همیشه مستطیل کامل نیست

مقایسه WKT و EPSG

طول و عرض جغرافیایی در GPTS بدون دانستن اینکه به کدام سیستم مختصات جغرافیایی تعلق دارند بی‌معنی هستند، زیرا مختصات ۵۱.۵، -۰.۱ در سیستم WGS 84 در مکانی فیزیکی متفاوت از یک مبدا ملی قدیمی‌تر قرار می‌گیرد. دیکشنری اندازه‌گیری این موضوع را از طریق یک دیکشنری سیستم مختصات پاسخ می‌دهد که با GetMeasureDictGCSDict برای سیستم جغرافیایی به دست می‌آید. قالب PDF آن سیستم را به یکی از دو روش قابل تعویض توصیف می‌کند و یک خواننده باید هر دو را بپذیرد

اولین مورد WKT یا Well-Known Text است، یک رشته خودکفا که بیانیه مبدا (Datum)، بیضوی، نصف‌النهار مبدا و واحدها را به طور کامل بیان می‌کند. این روش طولانی اما بدون ابهام است و نیازی به جدول جستجوی خارجی ندارد. دومین مورد یک کد EPSG است، یک عدد صحیح واحد که یک سیستم مختصات را در رجیستری EPSG نمایه می‌کند؛ مقدار ۴۳۲۶ همان WGS 84 است، فریمی که بیشتر داده‌های GPS مصرف‌کننده از آن استفاده می‌کنند. استاندارد EPSG فشرده است اما فرض می‌کند خواننده می‌تواند کد را در برابر یک پایگاه داده حل کند. فایل‌ها با یکی، دیگری یا هر دو ظاهر می‌شوند، به همین دلیل است که API هر سه متد GetCSDictType، GetCSDictEPSG و GetCSDictWKT را ارائه می‌دهد. متد GetCSDictType گزارش می‌دهد که آیا سیستم جغرافیایی است (GEOGCS با مقدار بازگشتی 1) یا تصویرشده (PROJCS با مقدار بازگشتی 2)، که به شما اجازه می‌دهد قبل از اعتماد به آن، بقیه موارد را به درستی تفسیر کنید

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;

خواندن LGIDict قدیمی

فایل‌هایی که قبل از مدل پورت دید ساخته شده‌اند، یا توسط ابزارهایی تولید شده‌اند که هنوز رمزگذاری قدیمی‌تر را صادر می‌کنند، ثبت خود را به جای دیکشنری اندازه‌گیری در یک LGIDict روی صفحه حمل می‌کنند. کتابخانه PDFlibPas تعداد این دیکشنری‌ها را در صفحه از طریق GetPageLGIDictCount گزارش می‌دهد و محتوای خام هر کدام را با GetPageLGIDictContent با نمایه‌سازی از یک برمی‌گرداند. متن بازگردانده‌شده همان دیکشنری نوشته‌شده است که فیلدهای ثبت OGC 08-139r2 را نگه می‌دارد، که کد شما سپس آن را برای بازیابی همان نوع نگاشت صفحه به دنیا که دیکشنری اندازه‌گیری ارائه می‌دهد، تجزیه می‌کند. در سمت نوشتن، متد AddLGIDictToPage یک LGIDict را به صفحه فعلی متصل می‌کند، بنابراین یک تبدیل‌کننده می‌تواند فرم قدیمی را در زمانی که یک مصرف‌کننده قدیمی هنوز انتظار آن را دارد، به صورت رفت و برگشت مدیریت کند

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;

جمع‌بندی عملیات خواندن

یک واردکننده کامل با این دو طرح به عنوان یک جفت مرحله روی هر صفحه برخورد می‌کند. صفحه را انتخاب کنید، از GetPageViewPortCount پورت‌های دید ISO را بخواهید، و برای هر پورت دید که مالک یک دیکشنری اندازه‌گیری است، مقادیر BBox، آرایه‌های GPTS و LPTS، واحد داده نقطه آن و توصیف GCS را از طریق دیکشنری سیستم مختصات استخراج کنید. سپس مقدار GetPageLGIDictCount را برای هرگونه ثبت قدیمی که مرحله پورت دید پوشش نداده است بررسی نمایید. نقشه‌ای که هر دو را حمل می‌کند باید بین آن‌ها توافق داشته باشد؛ نقشه‌ای که فقط یکی را حمل می‌کند همچنان حل می‌شود، زیرا شما هر دو مکان را بررسی کرده‌اید. هندل‌های بازگردانده‌شده در طول مسیر، ViewPortID، MeasureDictID، CSDictID، یک عدد صحیح ساده‌ای هستند که تا زمان بارگذاری سند معتبر می‌مانند، بنابراین کل پیمایش شامل چند حلقه تو در تو روی لیست صفحات بدون نیاز به مدیریت تخصیص حافظه است

هنگامی که بتوانید ثبت جغرافیایی را بازیابی کنید، صفحه به جای یک تصویر به یک منبع داده تبدیل می‌شود. تکنیک‌های همراه برای خواندن بقیه صفحه در مقاله مربوط به استخراج متن، تصویر و فونت پوشش داده شده است، و رندر کردن یک برگ زمین‌مرجع‌شده روی یک دستگاه برای اندازه‌گیری روی صفحه در راهنمای کانتکست دستگاه چاپ و پیش‌نمایش توصیف شده است. خواننده اطلاعات جغرافیایی توضیح داده شده در اینجا به عنوان بخشی از کتابخانه PDF losLab برای Delphi و C++Builder همراه با APIهای بارگذاری، استخراج و رندر که در بخش‌های دیگر این وبلاگ پوشش داده شده‌اند، عرضه می‌شود