Повечето разработчици си представят PDF страницата като лист хартия с текст и картини върху него. Геореферираният PDF е нещо повече от това. Той носи достатъчно информация, за да вземе точка от страницата, измерена в обикновени единици на страницата, и да отчете географската ширина и дължина, над които се намира тя в реалния свят. Този единствен факт превръща PDF файла в използваем носител за топографска карта, кадастрален план, чертеж на наводнена зона или всеки GIS експорт, който трябва да бъде отпечатан и все пак да има смисъл. Геометрията е там във файла, единственият въпрос е дали вашият модул за зареждане я чете.
Причината това да се пропуска е, че GeoPDF се отваря и отпечатва точно както всеки друг PDF. Нищо в рендираната страница не съобщава, че картата е регистрирана към координатна система. Регистрацията живее в речници, закачени за обекта на страницата, които никога не се изчертават, а четец, който ги игнорира, ви показва картата по същия начин. За да направите нещо пространствено с файла (отчитане на геодезически координати, репроектиране, наслагване спрямо други слоеве), трябва сами да обходите тези речници.
Два стандарта съществуват в практиката
Четец, който иска да обработва файлове от реалния свят, трябва да се справи с две схеми за георегистрация, тъй като и двете са в обращение и даден файл може да използва всяка от тях. По-старата е кодирането на OGC, описано в OGC 08-139r2, което прикачва LGIDict (речник за геопространствена регистрация) към страницата. То предхожда всяко одобрение от ISO и беше де факто форматът за ранните GeoPDF изходни файлове, така че голям обем от наследствени карти съдържа него и нищо друго.
Модерната схема е тази, която ISO стандартизира в ISO 32000-1 §8.8.2. Вместо един речник на ниво страница, тя моделира геопространствените данни като Viewport на страницата с прикачен речник Measure, а речникът за измерване указва географска координатна система. Това е кодирането, което записват Acrobat и настоящите GIS експортиращи програми. Надеждният импортер проверява и за двете: чете viewports за ISO модела и се връща към LGIDict (или допълнително го проверява) за файлове, които носят само наследената регистрация.
Viewports и техните граници
В ISO модела единицата за георегистрация е viewport-ът и една страница може да има няколко. Голям лист може да разположи основна карта в един правоъгълник, вмъкната карта с различен мащаб в друг и панел с легенда, който изобщо не е георефериран. Всеки viewport носи BBox – правоъгълникът на страницата, който управлява viewport-ът, така че четецът знае коя част от листа се управлява от дадена координатна система. Проверката на кликната точка спрямо тези полета е начинът, по който четецят решава кой речник за измерване да използва.
PDFlibPas излага графичните прозорци (viewports) на избраната страница директно. 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 describe the map area for this viewport
end;
finally
Pdf.Free;
end;
end;
Стойност ViewPortID, равна на нула, от GetPageViewPortID означава, че viewport-ът на този индекс не може да бъде намерен, така че я проверете, преди да прехвърлите дескриптора нататък.
Вътре в речника за измерване (measure dictionary)
Геометрията, която регистрира страницата към света, живее в речника за измерване, прикачен към даден viewport. GetViewPortMeasureDict връща MeasureDictID за даден ViewPortID, или нула, когато viewport-ът няма речник за измерване, което е нормалният случай за легенда или заглавен панел. Речникът за измерване съдържа три неща, които си струва да бъдат прочетени: координатните системи, които реферира, масивите, които свързват точките от страницата с географски точки, и единицата, в която се изразяват данните за точките.
Самата регистрация представлява два паралелни масива. GPTS е масивът от географски точки – двойки географска ширина и дължина, дадени в географската координатна система. LPTS е масивът от точки в пространството на страницата, изразени като части от BBox на viewport-а, така че да оцелеят при мащабиране. Елемент n от LPTS и елемент n от GPTS обозначават едно и също физическо местоположение, веднъж в координати на страницата и веднъж на глобуса. Три или повече такива двойки определят афинната (или в общия случай проективната) трансформация, която съпоставя всяка координата на страницата във 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 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, описва четириъгълника във viewport-а, който измерването действително обхваща, което не винаги е целият правоъгълник.
WKT срещу EPSG
Географската ширина и дължина в GPTS са безсмислени, без да се знае към коя географска координатна система принадлежат, тъй като координата 51.5, -0.1 попада на различно физическо място при WGS 84 в сравнение с по-стара национална координатна система. Речникът за измерване отговаря на това чрез речник за координатна система, достъпен с 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
Файловете, които предхождат модела viewport, или които са произведени от инструменти, все още излъчващи по-старото кодиране, носят своята регистрация в 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 viewports и за всеки viewport, който притежава речник за измерване, изтеглете неговия BBox, неговите масиви GPTS и LPTS, неговата мерна единица за данни за точки и описанието на GCS чрез речника за координатна система. След това проверете GetPageLGIDictCount за всяка наследена регистрация, която преминаването през viewport-а не е обхванало. Карта, която носи и двете регистрации, трябва да показва съгласие между тях; карта, която носи само една, все пак се разрешава, тъй като сте проверили и на двете места. Дескрипторите, върнати по пътя – ViewPortID, MeasureDictID, CSDictID, са обикновени цели числа, които остават валидни, докато документът е зареден, така че цялото обхождане представлява няколко вложени цикъла над списъка със страници без необходимост от управление на заделена памет.
След като можете да възстановите регистрацията, страницата се превръща в източник на данни, а не просто в картина. Свързаните техники за четене на останалата част от страницата са разгледани в статията за извличане на текст, изображения и шрифтове, а изобразяването на георефериран лист на устройство за измерване на екрана е описано в ръководството за устройство за печат и предварителен преглед. Геопространственият четец, описан тук, се доставя като част от losLab PDF Library за Delphi и C++Builder, заедно с API за зареждане, извличане и рендиране, разгледани на други места в този блог.