Большинство разработчиков воспринимают страницу PDF как обычный лист бумаги с текстом и рисунками. Геопривязанный PDF представляет собой гораздо большее. Он содержит достаточно информации, чтобы по точке на странице, измеренной в обычных единицах разметки, определить широту и долготу этого места в реальном мире. Этот факт превращает PDF в удобный контейнер для топографических карт, кадастровых планов земельных участков, планов зон затопления или любых других данных ГИС, которые должны нести практический смысл после печати. Вся геометрия уже присутствует в файле, вопрос лишь в том, умеет ли ваш загрузчик считывать ее
Эту деталь часто упускают из виду, так как GeoPDF открывается и печатается точно так же, как любой обычный PDF. На самой странице ничто не указывает на то, что карта привязана к системе координат. Данные привязки хранятся в невидимых словарях метаданных объекта страницы, и простая программа просмотра, игнорирующая их, отобразит карту без изменений. Чтобы работать с пространственными данными (считывание геодезических координат, перепроецирование, наложение слоев), вам придется обойти эти словари вручную
Два стандарта, используемые на практике
Средство чтения, предназначенное для работы с реальными файлами, должно поддерживать две схемы георегистрации, так как обе они распространены. Более старой является кодировка OGC, описанная в OGC 08-139r2, при которой к странице прикрепляется словарь LGIDict (словарь геопространственной регистрации). Эта спецификация появилась до официальной стандартизации ISO и служила форматом по умолчанию для ранних файлов GeoPDF, поэтому множество старых карт содержат только ее
Современная схема стандартизована в ISO 32000-1 §8.8.2. Вместо одного словаря на уровне страницы она описывает пространственные данные как область просмотра страницы (Viewport) с прикрепленным словарем измерений (Measure), указывающим на географическую координатную систему. Именно это кодирование используют Acrobat и современные экспортеры ГИС. Надежный импортер должен проверять оба варианта: считывать области просмотра для модели ISO и обращаться к LGIDict для файлов, содержащих только устаревшую разметку
Области просмотра и их границы
В модели ISO базовой единицей георегистрации является область просмотра, и на странице их может быть несколько. На одном большом листе можно расположить основную карту в одной рамке, врезку с другим масштабом во второй, а легенду без географической привязки разместить в третьей. Каждая область просмотра имеет параметр BBox (прямоугольник на странице, которым управляет эта область), благодаря чему средство чтения определяет, к какой части листа относится данная система координат. Сопоставление точки нажатия с этими рамками позволяет программе просмотра выбрать правильный словарь измерений
Библиотека PDFlibPas предоставляет прямой доступ к областям просмотра выбранной страницы. Функция 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 указывает на то, что область просмотра с данным индексом не найдена, поэтому проверяйте это значение перед использованием дескриптора
Внутри словаря измерений
Геометрия, связывающая страницу с реальным миром, содержится в словаре измерений, прикрепленном к области просмотра. Функция GetViewPortMeasureDict возвращает MeasureDictID для указанного ViewPortID или ноль при отсутствии словаря измерений (что обычно наблюдается у легенды или панели заголовка). Словарь измерений хранит три группы параметров: целевые координатные системы, массивы связей точек страницы с географическими координатами, а также единицы измерения пространственных данных
Фактически привязка представлена двумя параллельными массивами. GPTS является массивом географических точек (пары широты и долготы в заданной системе координат). LPTS хранит координаты точек на странице, выраженные в виде долей от BBox области просмотра, что позволяет сохранять точность при масштабировании. Элемент 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 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 и в старой государственной системе координат. Словарь измерений решает эту задачу с помощью словаря координатных систем, возвращаемого функцией 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 страницы, а не в словаре измерений. Функция GetPageLGIDictCount возвращает количество таких словарей на странице, а GetPageLGIDictContent возвращает их исходное содержимое по индексу с отсчетом от единицы. Возвращаемый текст содержит поля разметки спецификации OGC 08-139r2, которые ваш код должен разобрать для получения координатной сетки. Для записи метод AddLGIDictToPage прикрепляет словарь к текущей странице, позволяя сохранять обратную совместимость с устаревшими системами
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, и для каждой области просмотра со словарем измерений извлеките параметры BBox, массивы GPTS и LPTS, единицы измерения и спецификацию системы координат. Затем выполните проверку GetPageLGIDictCount на предмет наличия устаревших параметров привязки, которые могли быть пропущены на первом шаге. Карта, содержащая обе схемы, должна иметь согласующиеся данные, но если присутствует только одна схема, данные все равно корректно считаются. Возвращаемые в процессе обхода дескрипторы ViewPortID, MeasureDictID и CSDictID представляют собой обычные целые числа, которые остаются валидными на протяжении всего времени загрузки документа, поэтому весь процесс обхода сводится к нескольким вложенным циклам без необходимости ручного управления памятью
После считывания привязки страница превращается в источник структурированных данных, а не просто в изображение. Вспомогательные методы чтения остального содержимого страницы описаны в статье об извлечении текста, изображений и шрифтов, а рендеринг страницы с геопривязкой на устройство для измерений на экране рассматривается в руководстве по печати и предварительному просмотру с контекстом устройства. Модуль работы с геопространственными данными поставляется в составе библиотеки losLab PDF Library для Delphi и C++Builder вместе с API для загрузки, извлечения данных и рендеринга