بیشتر توسعهدهندگان یک صفحه 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 بازمیگردد (یا آن را بهطور اضافی بازرسی میکند)
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
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 را به یک مختصات جهانی نگاشت میکند. خواندن آنها یعنی پیمایش همگام هر دو آرایه
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های بارگذاری، استخراج و رندر که در جاهای دیگر این وبلاگ پوشش داده شدهاند