Наверх ▲

Создание картографического сервиса "на коленке" (PostGIS/MapServer)

Андрей Костенко Андрей Костенко Директор VisaMap Inc., Москва.

Андрей Костенко: Тема доклада — создание собственных маленьких Google- или Яндекс-карт с использованием своих хранилищ и технологий без применения каких-то API от Google- и Яндекс-карт.

Свой картографический сервис я делал на основе открытых исходников. Честно скажу: у меня нет денег, чтобы покупать Oracle, MS SQL Server или даже Windows. Поэтому я использую только открытые технологии. Стоимость созданного мной проекта равна нулю. Я ничего не потратил.

Первый и закономерный вопрос: «Зачем я все это затеял?». У нас уже есть всем известные Google- и Яндекс-карты, у которых огромные возможности.

Давайте посмотрим, что мы можем получить, используя интерфейс программирования приложений (API), который предоставляет Google или Яндекс. Мы можем поставить точку и сказать: «Мы здесь». Отлично! Очень информативно. Но этого не всегда и хватает.

Мы можем работать с некоторыми векторными данными. Например, нарисовать улыбающуюся рожицу. Это мы вполне можем сделать. Но этого недостаточно для создания картографического сервиса.

В Google есть полезная технология (не знаю, есть ли она в Яндексе) — это геокодинг. Это возможность по текстовому адресу (Красная Площадь, дом 1) найти географические координаты (широту и долготу), либо, наоборот, по широте и долготе найти адрес. Иногда это бывает полезным.

Давайте посмотрим, чего не может обеспечить API от Google и Яндекс-карт. Используя Яндекс, вы будете ограничены только планетой Земля. Google также предоставляет возможность увидеть на картах Луну и Марс. Но, к примеру, карту Средиземья там показать не получится.

Вы не можете модифицировать границы стран. Те данные, которые вам отдаются, — это одна большая картинка, с которой вы ничего не можете сделать. Например, мечту о воссоединении России с Украиной реализовать нельзя.

Допустим, мне понадобилось разукрасить страны. Разукрасить с помощью векторной графики не вышло, иначе бы "умер" браузер, а в растре разукрасить не позволял API.

Я решил сделать свой картографический сервис. Почитал документацию и убедился, что все реально. Ничего сверхъестественного не нужно. Например, на карте может быть показано, в какие страны гражданам Японии не нужна виза. 

Для хранения данных я решил использовать PostGIS, который является бесплатным расширением к PostgrеSQL. MySQL тоже поддерживает работу с геометрическими данными. Но основной недостаток этого — платность MySQL.

Для чего используется PostGIS? Он позволяет нам добавлять геометрические типы данных (то, что мы в школе на геометрии изучали, — точки, линии, многоугольники и так далее) и оперировать с ними.

Если быть точным, то он добавляет стандартные типы данных: точка, ломаная и многоугольник. Для нашей карты этого достаточно. К примеру, точка — это город, линия — река или еще что-то, многоугольник — страна.

Также существуют еще кривые и коллекции. Кривые позволяют рисовать не ломаные линии, а плавные, сплайны и так далее. Коллекции типов — это что-то вроде ряда, набора (набор точек, линий, многоугольников).

Кривые в PostGIS реализованы не лучшим образом, некоторые функции работать не будут. Лучше использовать либо хорошо "отточенные" многоугольники, либо потом делать преобразования в прямоугольный многоугольник (для этого в PostGIS есть специальная функция). С коллекциями то же самое. Если нужно найти максимальный икс для коллекции точек или многоугольников, PostGIS выдает неправильные данные.

Кажется, все просто — для стран многоугольники есть, линии и точки есть. Вот только Земля у нас круглая, даже чуть-чуть приплюснутая. А мониторы у нас плоские. Нам нужно данные с круглой Земли как-то показать на плоской карте.

Есть способы показать данные Земли на карте. Но каждый из этих способов что-то вам сломает: какой-то искажает длины, какой-то углы, какой-то все сразу.

Обычно для Google-карт и Яндекс-карт используется Меркаторова проекция. Она была изобретена в 15 или 16 веке Меркатором, который про компьютеры и телефоны знать просто не мог.

Данные преобразовываются из широты и долготы в проекцию Меркатора, в (х, у) по достаточно несложной и компактной формуле. 

На самом деле, PostGIS тоже не так прост. Каждому типу данных (точка, многоугольник, линия и так далее) он присваивает ID проекции. Данные в координатах широты и долготы можно преобразовать в метры, используя Меркаторову проекцию. Потом я расскажу, как эти преобразования можно делать. 

После выбора геометрического столбца в PostGIS появляется строчка с очень странными данными, понять которую неспециалисту будет очень сложно.

Специально для людей существует "Well-Known Text". Как и в школе на уроке геометрии, точка имеет координаты -  x, y. Может быть и x, y, z. 

Но "Well-Known Text" не дает нам понятия о том, в какой системе единиц заданы координаты. Мы не можем быть уверены, что в качестве единицы измерения используются метры.

Поэтому для того, чтобы присутствовали еще и единицы измерения, существует "Extended Well-Known Text", который используется в PostGIS. У него есть ID проекции. По ID проекции, по числу 4326 каждый образованный человек может сказать, что здесь находятся градусы. Теперь человек может все понять.

Как говорится, «что русскому хорошо, то немцу — смерть». Что для нас удобно (точка и красивые числа), то для компьютера — не очень удобно. Если для какой-то другой программы потребуется передать данные не в текстовом формате, а в бинарном, чтобы писать парсы, то надо будет воспользоваться EWKB. Он предназначен именно для передачи данных между бинарными приложениями.

Форматов вывода у PostGIS великое множество. Есть вывод в SVG, еще куча других типов вывода, о которых можно почитать в документации. Там иногда встречаются интересные вещи.

Перейдем к тому, что можно делать с точками, многоугольниками, всякими геометрическими объектами с помощью функций PostGIS.

ST_Contains. Самый простой вариант — проверить, находится ли точка в каком-то многоугольнике. Попробуйте мысленно представить, по какой формуле вы бы это вычисляли. Наверное, через 5 минут вам надоест. А PostGIS может определить, находится ли линия в каком-то многоугольнике, другой многоугольник внутри данного многоугольника. Это самое простое.

ST_Distance. Расстояние между двумя точками считается элементарно: корень из (∆х2+∆у2). Найти минимальное расстояние между двумя линиями или многоугольниками посложнее. PostGIS это тоже "умеет". Но если каждый многоугольник будет включать по 1000 точек, то иногда он будет работать медленно.

У меня в проекте это используется для того, чтобы человек, заходя в Сеть из какого-либо местоположения (из Владивостока или еще откуда-нибудь), мог видеть ближайшие к нему адреса. Их можно сортировать по расстоянию. Это работает вменяемое количество времени, довольно удобно — никаких формул писать не нужно.

ST_Area — посчитать площадь. Вычисляются только площади многоугольников. Тоже очень удобно. Формула, которую я упоминал выше, позволяет преобразовывать один тип проекции в другой. Если у вас данные в широте и долготе, вы можете получить координаты в метрах для Меркаторовой проекции.

У меня в проекте (в базе данных) используются широта и долгота, с которыми очень удобно работать. Для вывода на карту используются Меркаторовы проекции, которые отображают все объекты в плоском виде. Я пытался все эти формулы писать сам.

ST_Simplify. Иногда у вас бывает очень хороший геометрический объект, в котором много точек, но его нужно показать на картинке 100*100. Это будет работать очень медленно — картинка маленькая. Функция ST_Simplify по какому-то умному алгоритму упрощает геометрию, "выбрасывая" те точки, без которых не сильно изменится геометрия.

Можно запросто уменьшить количество точек в 3 раза, ускорить работу в 10-20 раз, при этом совершенно не изменив отображение. Параметр "tolerancy" как раз регулирует количество "выбрасываемых" точек. Минимальная и максимальная координата для объекта задаются просто — можно очертить его квадратной рамкой и так далее.

Я вспомнил еще про пару функций. Например, ST_Balance. Точка — это нематериальная сущность, у нее нет ширины, высоты и так далее. Ее нужно как-то показывать. Обычно вызывается функция ST_Balance, которая чертит вокруг нее кружок с заданным радиусом. Так как круг с помощью многоугольника отобразить не получится, она делает полигон из n точек, который внешне напоминает круг.

Еще я рассказывал про функцию, которая преобразовывает кривые и сплайны в многоугольники. Она тоже есть, я про нее почему-то забыл. В базе у нас карта есть — отлично. Но пользователям на сайте EWKT не показывают, и не дай Бог EWKB показывать. Бинарный формат точно не стоит. Нужно как-то пользователям это показать - графически и желательно странам.

Я для этих целей использую MapServer. Есть еще другие технологии вроде Mapnik и так далее. Мне хватает технологий MapServer.

Что он может предложить нашему проекту?

  • Он может нарисовать по геометрическим объектам карту, разукрасить эту картинку. Многоугольники с одним типом залить одним цветом, многоугольники с другим типом — другим.
  • MapServer "умеет" из коробки нарезать эту картинку на "плитку" (англ. tile) так, как это делает сервис "Карты Google" (по той же формуле, в том же виде).
  • Для объектов можно применить стили. В MapServer чуть ли не собственный CSS придумали (собственные иконки и так далее).
  • MapServer может подписывать объекты. Кто пытался это сделать сам, наверное, меня поймет. Найти центроид, всунуть туда текст, выровнять его — не очень удобно. MapServer сам это делает, причем даже в нарезанных картинках.
  • Помимо объектов PostGIS, MapServer умеет работать еще с кучей разных типов объектов (за что он мне и понравился).
  • Многое другое, что я сам пока не изучил подробно.

Существуют MapFile и MapServer. Если сравнить с SQL, то, насколько вы знаете, есть SQL и есть его процедуры. Это приблизительно то же самое.

MapFile — это декларативный файл, в котором мы описываем, что у нас есть такая-то карта с такими-то слоями, выбирается она таким-то образом, описывается такими-то стилями. Написав этот конфигурационный файл, мы получим хорошую нарезанную карту. Он поддерживает различные шрифты и цвета.

Если вам этого недостаточно (мне этого было достаточно), помимо этого файла есть еще MapScript. Он позволяет либо на С++, либо на Python написать скрипт для растеризации карты. Он позволит вам прочитать конфигурационный скрипт MapFile, что-то там поменять с помощью логики и отрендерить.

Почему я использовал Perl? Конечно, Excess-версия есть. Но при попытке забрать нужные данные мне в Perl возвращают "pointer" языка "Си" и пишут: «У нас до этого руки не дошли». То есть забрать полученные данные в Perl не получалось. 

MapServer, конечно, хорошая штука. Но по умолчанию он стартует в CGI. Растеризация карт в CGI — это классно, но очень медленно. Поэтому первая вещь, которую стоит поднять на любой проект с MapServer, — это FastCGI. Как ни странно, он даже работает в FastCGI и с довольно неплохой скоростью.

То, о чем я вам рассказывал. ST_Simplify — пытайтесь упрощать объекты до того уровня, чтобы не было лишних точек и эти объекты как-то нормально отрисовывались. В моем случае мне еще помог кэш.

Я забыл рассказать еще про одну особенность PostGIS. Он позволяет создавать индексы для геометрических столбцов, которые без полного последовательного сканирования таблицы позволяют посмотреть, находится ли какая-то точка в таблице.

К примеру, MapServer выбирает с карты не полностью всю информацию, а только ту, которая находится в нашей видимой части. Если я приблизил что-то с 20-кратным увеличением, то он выберет только тот прямоугольник, который мне нужно отобразить. Всю карту он трогать не будет. Для этого нужно создавать гистовый индекс, который все ускоряет.

Если вы все-таки решите написать свой сервис, я поделюсь парой интересных библиотек, которые будут вам полезны. Изначально сервис писался в не самом презентабельном виде. Растеризовалась SVG, которую я честно взял из Википедии, делались патчи и сохранялись в файлы.

Все это изначально писалось на языках Perl и Catalyst. Как только я начал иметь дело с геометрическими данными, я понял, что в Catalyst я хорошо разбираюсь, но все-таки лучше использовать Python.

Помимо библиотеки Gios, которая позволяет реализовать на Python то, что я вам рассказывал про PostGIS, у Python есть очень интересное расширение geo django. Помимо расширения ORM, которое позволяет вам работать с функциями нативно, помимо интеграции с Gios, который позволяет на Python пользоваться геометрическими объектами (точка, линия и так далее), она еще расширяет возможности админпанели. В админке можно редактировать геометрические контуры из JavaScript, перетаскивая линии и точки. Это бывает очень удобно.

OJR — это библиотека, которая преобразовывает различные типы данных. У вас карта может быть в одном формате ("shapefile" либо формат MySQL), а нужен другой. OJR поможет преобразовать данные в формат PostGIS.

Я рассказал маленькую часть того, что я проделал, потому что самое сложное — это найти данные. Большая часть данных защищена авторским правом. Найди те данные, за которые платить не нужно, было очень сложно.

Open Street Maps я сначала скачал, потом пытался загнать это в PostGIS. Занимает это приложение 140 гигабайт. Там присутствует практически вся информация о Москве (не знаю, как насчет регионов). Днные для крупных городов сравнимы с Яндекс-картами, Google-картами, - с векторной их частью.

Мировые границы (англ. World boundaries) — это уменьшенная часть сервиса с точностью до крупных городов, границ стран и так далее. Она занимает всего 50 мегабайт. Можно скачать ее, и карта мира у вас уже будет. Open Street Maps — очень хороший хороший и точный сервис. К сожалению, аэрофотосъемки с открытым исходным кодом я не нашел. Наверное, у Ричарда Столлмана все-таки не хватило денег, чтобы облететь на вертолете весь мир и сфотографировать его.

Собственно, с данными вот так. Это работает - на мой сервис можно посмотреть, поиграть с масштабом карт.

Кстати, в качестве JavaScript-версии я все-таки использую JavaScript от Google. С него можно убрать стандартные слои и применить свой, если хорошо постараться.

У меня все. Вопросы?

Вопросы:

Вопрос:
— Сломался ты, наверное, когда делал свои формулы на итерационных эллиптических интегралах. Или не дошел до них?
Хорошо когда есть векторные карты, и их надо растеризовать. А есть еще обратная задача: у нас есть растеризованная карта. Среди туристов популярны сканированные. Естественно, это нелегально. Но этот вопрос мы оставляем в стороне.
Есть огромное количество карт, в системе координат Пулково 42 со всей нашей необъятной Родины. Каждая карта представляет из себя какой-то картографированный кусок поверхности нашей страны, у которого есть все четыре границы. Есть ли какой-то разумный способ залить эту карту в MapServer или куда-то еще, привязав к ней границы, чтобы потом можно было на эту карту поставить какую-то точку и сказать: «Мы здесь»?
Андрей Костенко:
— В MapServer — без проблем. Помимо работы с PostGIS, у него есть возможности работы и с растеризованными картами. Это даже проще сделать. Совершенно без проблем. Логику с использованием PostGIS вы можете "навесить" сверху.
У меня приблизительно так и делается. Когда человек "тыцает" по какому-то месту (я не знаю, куда он по картинке "тыцает"), я по географическим координатам вычисляю страну. Это совершенно несложно, если что — могу подсказать.
Вопрос:
— В данном случае вы использовали данные с Open Street Maps в формате OSM или Shape?
Андрей Костенко:
— В формате OSM — это огромный xml-файл,который... Да, OSM.
Вопрос:
— Каким образом там была реализована привязка к геокоординатам? Например, той же Москвы. Была ли она там? Или вы потом вручную это делали?
Андрей Костенко:
— Я неделю убивал свой сервер, пытался засунуть это в PostGIS. После чего засунул, посмотрел: «Нет, мне этого слишком много». Скачал "World boundaries" в формате shapefile, их мне пока хватает.
Вопрос:
— Не совсем было понятно, в каком ключе идет работа с сервером. MapServer формирует сразу готовую картинку и отдает клиенту, или набор tile’ов на лету формирует? Или же tile’ы уже сформированы и хранятся на сервере?
Андрей Костенко:
— Есть много вариантов работы. Один из вариантов — сгенерировать картинку в каком-то масштабе. Второй вариант — сгенерировать набор tile’ов напрямую из PostGIS. Третий вариант — набор tile’ов с кэшированием.
Вопрос:
— Что было использовано вами в данном случае?
Андрей Костенко:
— Набор tile’ов.
Вопрос:
— Почему в качестве клиента вы не захотели использовать, например, OpenLayers?
Андрей Костенко:
— Если честно, сначала они использовались, но они не показались мне хорошим решением. Потом мне Google Клиент понравился, потому что когда я прокручиваю колесико, карта ко мне «приезжает».
Вопрос:
— Хотелось бы узнать по поводу всех этих оптимизаций внутри PostGIS. Во-первых, как там насчет 180-го меридиана? Там получается двоичный случай: плюс/минус. Поддерживаются ли они?
Второй момент. Когда нам нужно прочертить кратчайшее расстояние — понятное дело, что это уже будет на нашей карте не прямая, а некоторая дуга. Как с этим обстоят дела?
Андрей Костенко:
— Насчет второго вопроса не очень уверен. Думаю, вам стоит посмотреть это. Насчет этого меридиана — в PostGIS в версии 1.4 или 1.5 появился новый тип данных, называется Geography. Он как раз решает эту проблему с переходом. У меня ее не возникло, поэтому все в Geometry используется.
Вопрос:
— Почему презентация не будет опубликована?
Андрей Костенко:
— Я попытаюсь это сделать. Просто когда я пытаюсь сохранить из Google Docs эту презентацию, появляется «java lang number point exception» — серверная ошибка Google. Я ее не могу ни скачать, ни распечатать — ничего. Она есть только в таком экземпляре - и все. Могу дать доступ.

Комментарии

Нет ни одного комментария

Только пользователи могут оставлять комментарии

Возможно, вам будет интересно:

Доклад от компании "Рамблер" о том, почему почтовые веб-приложения лучше старых добрых веб-сайтов.

Юрий Востриков

Юрий Востриков

Один из ведущих разработчиков Mail.Ru.

Юрий Востриков (Mail.Ru) рассказывает о Tarantool/Silverbox - высокопроизводительной базе данных в оперативной памяти.

Андрей Ситник

Андрей Ситник

Фронтендер в Злых марсианах. Работал над русским Групоном, Рокетбанком и Ebay Social. Автор Автопрефиксера и PostCSS.

Андрей Ситник (Evil Martians) о том, что анимация и 3D - это не только промо, но и юзабилити.