مقاله فنی

GeoPDF در دلفی: خواندن مختصات جغرافیایی از PDF

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

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

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

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

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

دیاگرام PDF Library for Delphi که مدل زمین‌ثبتی viewport و دیکشنری اندازه‌گیری ISO را با طرح قدیمی LGIDict که فایل‌های قدیمی‌تر GeoPDF حمل می‌کنند مقایسه می‌کند
دو طرح زمین‌ثبتی در فایل‌های واقعی GeoPDF در گردش‌اند و یک صفحه ممکن است یکی یا هر دو را حمل کند. هر مسیر مختصات صفحه را به عرض و طول جغرافیایی حل می‌کند

viewportها و کران‌های آن‌ها

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

PDF Library for Delphi مستقیماً viewportهای صفحه انتخاب‌شده را در دسترس قرار می‌دهد. GetPageViewPortCount تعداد آن‌ها را برمی‌گرداند، GetPageViewPortID یک اندیس یک‌مبنا را به یک هندل ViewPortID تبدیل می‌کند، و GetViewPortBBox مستطیل مرزی را هر بار یک بُعد می‌خواند. آرگومان Dimension انتخاب می‌کند کدام لبه یا گستره را می‌خواهید: 0 یعنی Left، 1 یعنی Top، 2 یعنی Width، 3 یعنی Height، 4 یعنی Right و 5 یعنی Bottom

PDF Library for Delphi: ماکت صفحه PDF که یک viewport نقشه اصلی، یک viewport ضمیمه و یک راهنمای بدون مرجع را با آزمایش برخورد کلیک در برابر BBox هر viewport نشان می‌دهد
هر viewport فقط بر ناحیه درون BBox خود حاکم است، پس آزمایش برخورد به خواننده می‌گوید کدام دیکشنری اندازه‌گیری اعمال می‌شود. ViewPortID صفر یعنی viewport درخواستی پیدا نشد
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 ناحیه نقشه را برای این viewport توصیف می‌کنند
    end;
  finally
    Pdf.Free;
  end;
end;

ViewPortID صفر از GetPageViewPortID یعنی viewport در آن اندیس پیدا نشد، پس پیش از پاس‌دادن هندل آن را بررسی کنید

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

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

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

PDF Library for Delphi: کسرهای فضای صفحه LPTS و آرایه‌های عرض و طول جغرافیایی GPTS که عنصر به عنصر جفت شده‌اند تا تبدیل آفین صفحه به جهان را تثبیت کنند
عنصر n از LPTS و عنصر n از GPTS یک مکان فیزیکی را یک بار روی صفحه و یک بار روی کره زمین نام می‌برند. سه یا چند جفت تبدیل آفین یا تصویری را تثبیت می‌کنند
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 جفت‌های lat/lon را نگه می‌دارد؛ LPTS کسرهای صفحه متناظر را نگه می‌دارد.
    // هر دو آرایه با اندیس‌های عنصر یک‌مبنا خوانده می‌شوند.
    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) روی صفحه متناظر است با (lat, lon) روی زمین
      Inc(j, 2);
    end;
  end;
end;

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

WKT در برابر EPSG

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

اولی WKT است، Well-Known Text، رشته‌ای خودکفا که datum، بیضوی، نصف‌النهار مبدأ و واحدها را به‌طور کامل بیان می‌کند. پرگو اما بی‌ابهام است و به هیچ جدول جست‌وجوی خارجی نیاز ندارد. دومی یک کد EPSG است، یک عدد صحیح واحد که یک سیستم مختصات را در رجیستری EPSG اندیس می‌کند؛ 4326 همان 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);   // مثلاً 4326 برای WGS 84، در صورت نبود 0
    wkt    := Pdf.GetCSDictWKT(gcsID);    // توصیف متنی کامل، در صورت نبود ''
    // در صورت وجود EPSG را ترجیح دهید؛ در غیر این صورت به تجزیه WKT بازگردید.
  end;
end;

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

فایل‌هایی که پیش از مدل viewport ساخته شده‌اند، یا با ابزارهایی تولید شده‌اند که هنوز کدگذاری قدیمی‌تر را می‌نویسند، ثبت خود را به‌جای دیکشنری اندازه‌گیری در یک LGIDict روی صفحه حمل می‌کنند. PDF Library for Delphi تعداد این دیکشنری‌ها را در یک صفحه از طریق 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 ثبت OGC 08-139r2 را برای تجزیه حمل می‌کند
  end;
end;

کنار هم گذاشتن خواندن

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

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