مقاله فنی

GeoPDF در Delphi: مختصات مکانی جغرافیایی با PDFlibPas

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

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

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

ویوپورت‌ها و کران‌های آن‌ها

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

PDFlibPas ویوپورت‌های صفحه انتخاب شده را به طور مستقیم در معرض دید قرار می‌دهد. GetPageViewPortCount تعداد آنها را برمی‌گرداند، GetPageViewPortID یک نمایه مبتنی بر یک (one-based index) را به یک دستگیره 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 بدون دانستن اینکه به کدام سیستم مختصات جغرافیایی تعلق دارند بی‌معنی است، زیرا مختصات 51.5, -0.1 در نقطه‌ای فیزیکی متفاوت تحت WGS 84 نسبت به یک مبنای (datum) ملی قدیمی‌تر فرود می‌آید. لغت‌نامه اندازه‌گیری از طریق یک لغت‌نامه سیستم مختصات که با GetMeasureDictGCSDict برای سیستم جغرافیایی در دسترس است، به این پاسخ می‌دهد. PDF آن سیستم را به یکی از دو روش قابل تعویض توصیف می‌کند، و خواننده باید هر یک را بپذیرد

اولین مورد WKT، مخفف متنی که به خوبی شناخته شده است (Well-Known Text)، یک رشته مستقل است که مبنا، بیضوی، نصف‌النهار مبدأ و واحدها را به طور کامل بیان می‌کند. این پرطول‌وتفصیل است اما بدون ابهام است و به هیچ جدول جستجوی خارجی نیاز ندارد. دومین مورد کد 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);   // 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 را به صفحه فعلی پیوست می‌کند، بنابراین یک تبدیل‌کننده می‌تواند زمانی که مصرف‌کننده قدیمی هنوز آن را انتظار دارد، فرم قدیمی را رفت و برگشت (round-trip) کند

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;

کنار هم قرار دادن فرآیند خواندن

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

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