Привет Хабр, сегодня я расскажу немного про использование osm для предприятий и b2b.
А именно, как и зачем перейти от google maps api к osm, openlayers и счастью.
Начнем с того, что использование google maps api для непубличных сервисов ограничено условиями предоставления сервиса. Второе: апи карт гугл — это именно апи карт гугл, а не апи для отображения всего, что напоминает карту. Т.е. если вам захотелось отображать другую подложку или добавить растровый слой, отрисовываемый сервером в локальной сети предприятия, готовтесь лепить костыли. Ну и просто будьте готовы, что если чего-то в апи нет, добавлять будет мучительно. Третье: вы не можете получить данные карты, соответсвенно, вы не можете создать локальный картографический сервис для сотрудников предприятия. То есть с каждой клиентской машины должны быть доступны карты гугл. Звучит диковато, но далеко не всегда сотрудникам открыт доступ во внешние сети.
Первое, подключаем карту в openlayers. Тут все просто и мало чем отличается от google, yandex, leaflet.
Крупные компании зачастую могут себе позволить купить картографический сервер и карты и раздавать их в локальной сети через wms или tms. WMS и TMS — это стандарты, по которым вы можете получить кусочки изображения карт. Основное отличие: по wms вы можете запросить произвольный кусочек карты произвольного масштаба, по tms — набор масштабов и деление карты на квадратики, которые могут быть получены фиксированно.
Соответсвенно, меняем строку создания слоя для wms на
для tms:
Также никто не мешает вам добавить все 3 слоя на карту и переключаться между ними, или добавить один из слоев поверх других.
Теперь пару слов о граблях, на которые вы скорее всего наступите.
Допустим вы добавили один слой с мапником с osm.org (OpenLayers.Layer.OSM), но хотите, чтобы карта открывалась отзуммированной на Московскую область, а не на весь мир. Смотрим широту и долготу Москвы и вместо
пишем
И попадаем в океан. Все дело в системе координат. Для слоя мапника родная система координат EPSG:900913. Точкой отсчета координат в ней является пересечение гринвичского меридиана и экватора, а единицами измерения являются метры. Соответсвенно, мы попали на 37 метров восточнее и на 55 метров севернее точки отсчета.
Привычные всем долгота и широта подразумевают, что заданы они в EPSG:4326. Соответсвенно, надо пересчитать координаты.
О том, что систем координат на свете много, лучше помнить при задании любых координат в openlayers. Это добавляет головной боли, но позволяет работать с данными клиентов, если они используют нечто экзотическое, к примеру, Пулково 42.
Для этого подключаем proj4js ( trac.osgeo.org/proj4js/wiki/Download ) и добавляем строчку
Тепперь вы можете при пересчете координат указывать новую проекцию.
Это не совсем Пулково 42, но, немного поподбирав параметры, можно добиться нормального отображения данных поверх слоев в других системах координат.
Теперь, когда с добавлением и отображением слоев мы разобрались, перейдем к маркерам, линиям и обработке событий.
В леерсах их 2 типа:
HTML (OpenLayers.Marker) и векторные (OpenLayers.Geometry.Point). Чуть позже я объясню, что здесь имеется ввиду под маркером и почему point.
HTML маркер создает один или несколько дивов, в который помещает картинку и располагает над картой в соответсвии с координатами. Если вы когда-нибудь смотрели фаербагом или другим отладчиком как устроен маркер на карте гугл, вам все будет знакомо. К ним относительно легко подключаются попапы, с ними легко (т.к. есть html объект) работать из jQuerry, и тем не менее создатели библиотеки не рекомендуют ими пользоваться. Почему? Они несколько тяжеловесны: когда у вас 1 маркер — все хорошо, когда тысяча — плохо. Они выбиваются из общей концепции хранения и отображения данных принятой в ol. Ну и из практических соображений: когда я начинал работать с леерсами, для таких маркеров не было drag контрола. Впрочем, его и сейчас нет.
Тем не менее, немного кода для добавления маркеров на карту:
Да, слоев с маркерами может быть несколько. Да, вы можете управлять видимостью слоев и прятать группы маркеров по своему усмотрению.
Теперь о том, кто такие векторные маркеры и о «концепции хранения и отображения данных».
Мало кого сейчас устроит возможность просто показывать разные растровые слои с картами. Основная прелесть в отображении своих уникальных данных поверх них. Итак, предположим, мы хотим отображать поверх карты оптические кабели, медные кабели и колодцы/опоры, через которые это добро проходит. Каждый объект будет содержать геометрическую информацию (как собственно проходит кабель / где располагается опора или колодец) и атрибутивную (тип кабеля, количество жил кабеля, затухание сигнала, высота опоры… список можно пополнять до бесконечности). Собственно эта идея напрямую реализуется в леерсах:
Объект — «OpenLayers.Feature.Vector», хранится вместе со своими атрибутами, геометрией и, опционально, стилем отображения.
Для точечных объектов:
Пару слов про стили.
В стилях можно задать, как именно отображать геометрию, используя атрибуты объекта. В зависимости от типа можно использовать различные иконки, например, колодцы рисовать кружками, а опоры — столбиками. Можно определить цвет заливки, толщину и цвет обводки. На основе атрибутов можно выводить текстовые подписи к объектам и т.д. Можно задать стиль конкретному объекту, можно, например, назначить стиль для слоя, чтобы все объекты слоя отображались в соответсвии с ним.
И все же к маркерам:
*Дорогой grammar-nazi, это была аллюзия к детской песенке.
Если вам надо добавить несколько маркеров с одним стилем, достаточно использовать один объект стиля, но важно помнить при этом что изменения в инстансе стиля отразятся на всех маркерах.
Итак, маркер мы добавили. Теперь давайте добавим возможность его перемещать, кликать по нему, и реагировать на прочие события. Собственно обработкой событий леерсы сильнее всего отличаются от остальных библиотек, с которыми довелось поработать (внимательный читатель, который прочтет всю статью, не пропуская абзацы, узнает, что, в первую очередь, это google и чуток leaflet с яндексом).
Добавляем драг:
Если память меня не подводит, вы, наконец, добъетесь перемещения маркеров по карте.
Или добавим нашему маркеру попап по клику:
Можно посмотреть пример вот тут: openlayers.org/dev/examples/select-feature-openpopup.html
Большие проблемы начинаются, когда вам одновременно нужно:
Это решаемая беда, но решение, пожалуй, заслуживает отдельной статьи.
Фуф, мы добавили OSM на сайт и научились отображать свои данные поверх. Но все же значительная часть api гугла (яндекса) осталась за бортом. А именно геокодирование (получение координат по адресу и адреса по координатам) и прокладка маршрутов.
Про маршрутизацию по картам в осм я расскажу в другой раз, сейчас пару слов о геокодировании. Я не делал обратный геокодинг (адрес по координатам), поэтому остановлюсь пока на поиске координат по адресу.
Тут есть несколько вариантов: использовать готовый поиск от nominatim или openstreetmap.ru, написать свой велосипед. Пару слов о том, почему вам может понадобиться свой геокодер.
1. К примеру, вас интересует только Москва, соответсвенно, данные будет импортировать проще, и поисковые запросы будут простыми, без указания города.
2. По каким-то причинам вы не можете дать доступ во внешку ни с клиентских машин, ни с сервера.
3. Вам нужен геокодер по собственной базе адресов клиента.
Что же, никакой магии нет: самый простой вариант — использовать Solr или Sphynx. По сути я просто сохраняю в solr документы с полным адресом и координатами объекта.
Чтобы получить список адресов, можно к примеру взять интересующий вас регион в shp формате, загрузить в postgis, после чего достать адреса запросом вида:
На сегодня все. В следующий раз постараюсь подробнее рассказать про систему событий в openlayers.
Документация к openlayers — dev.openlayers.org/docs/files/OpenLayers/Map-js.html
Там же песочница с примерами — openlayers.org/dev/examples
Proj4js — trac.osgeo.org/proj4js
Сайт с описанием различных систем координат в разных форматах,
в том числе в формате proj4 — spatialreference.org
А именно, как и зачем перейти от google maps api к osm, openlayers и счастью.
Первый вопрос, который непременно возникнет: зачем?
Начнем с того, что использование google maps api для непубличных сервисов ограничено условиями предоставления сервиса. Второе: апи карт гугл — это именно апи карт гугл, а не апи для отображения всего, что напоминает карту. Т.е. если вам захотелось отображать другую подложку или добавить растровый слой, отрисовываемый сервером в локальной сети предприятия, готовтесь лепить костыли. Ну и просто будьте готовы, что если чего-то в апи нет, добавлять будет мучительно. Третье: вы не можете получить данные карты, соответсвенно, вы не можете создать локальный картографический сервис для сотрудников предприятия. То есть с каждой клиентской машины должны быть доступны карты гугл. Звучит диковато, но далеко не всегда сотрудникам открыт доступ во внешние сети.
Предположим, я вас убедил. С чего начать?
Первое, подключаем карту в openlayers. Тут все просто и мало чем отличается от google, yandex, leaflet.
// создаем карту, использовав в качестве контейнера элемент с id='map' var map = new OpenLayers.Map('map'); // создаем слой с типом OSM и именем "OSM mapnik" var layer = new OpenLayers.Layer.OSM('OSM mapnik'); //добавляем слой на карту map.addLayer(layer); //выставляем зум и центр карты, таким образом, чтобы поместился весь мир. map.zoomToMaxExtent();
Крупные компании зачастую могут себе позволить купить картографический сервер и карты и раздавать их в локальной сети через wms или tms. WMS и TMS — это стандарты, по которым вы можете получить кусочки изображения карт. Основное отличие: по wms вы можете запросить произвольный кусочек карты произвольного масштаба, по tms — набор масштабов и деление карты на квадратики, которые могут быть получены фиксированно.
Соответсвенно, меняем строку создания слоя для wms на
//Создаем новый слой типа WMS var wms = new OpenLayers.Layer.WMS( //имя слоя для контрола выбора слоев "NASA Global Mosaic", //адрес wms сервера "http://wms.jpl.nasa.gov/wms.cgi", //параметры специфичные для wms: //набор слоев, которые wms сервер склеит в картинку перед тем, как вернуть вам результат {layers: "modis,global_mosaic"} );
для tms:
//Создаем новый слой типа TMS var layer = new OpenLayers.Layer.TMS( //Имя для контролла выбора слоев "My TMS Layer", //путь до TMS сервера "http://tilecache.osgeo.org/wms-c/Basic.py/", //параметры специфичные для TMS: //набор слоев и тип картинки, которую вы желаете получить (png/jpeg/gif) {layername: "basic", type: "png"} );
Также никто не мешает вам добавить все 3 слоя на карту и переключаться между ними, или добавить один из слоев поверх других.
Проекции
Теперь пару слов о граблях, на которые вы скорее всего наступите.
Допустим вы добавили один слой с мапником с osm.org (OpenLayers.Layer.OSM), но хотите, чтобы карта открывалась отзуммированной на Московскую область, а не на весь мир. Смотрим широту и долготу Москвы и вместо
map.zoomToMaxExtent
пишем
map.moveTo( //Примерные координаты центра Москвы new OpenLayers.LonLat(37.16, 55.604), //зум 9 );
И попадаем в океан. Все дело в системе координат. Для слоя мапника родная система координат EPSG:900913. Точкой отсчета координат в ней является пересечение гринвичского меридиана и экватора, а единицами измерения являются метры. Соответсвенно, мы попали на 37 метров восточнее и на 55 метров севернее точки отсчета.
Привычные всем долгота и широта подразумевают, что заданы они в EPSG:4326. Соответсвенно, надо пересчитать координаты.
map.moveTo( //пересчитать координаты точки new OpenLayers.LonLat(37.16, 55.604).transform( //из системы координат EPSG:4326 new OpenLayers.Projection('EPSG:4326'), //в систему координат карты (EPSG:900913) map.getProjectionObject() ), 9 );
О том, что систем координат на свете много, лучше помнить при задании любых координат в openlayers. Это добавляет головной боли, но позволяет работать с данными клиентов, если они используют нечто экзотическое, к примеру, Пулково 42.
Для этого подключаем proj4js ( trac.osgeo.org/proj4js/wiki/Download ) и добавляем строчку
Proj4js.defs['EPSG:28403'] = '+proj=tmerc +lat_0=0 +lon_0=39 +k=1 ' + '+x_0=500000 +y_0=0 +no_defs +a=6378140 +rf=298,257223563 +units=m ' + '+towgs84=28.000,-130.000,-95.000 +to_meter=1';
Тепперь вы можете при пересчете координат указывать новую проекцию.
Это не совсем Пулково 42, но, немного поподбирав параметры, можно добиться нормального отображения данных поверх слоев в других системах координат.
Теперь, когда с добавлением и отображением слоев мы разобрались, перейдем к маркерам, линиям и обработке событий.
Маркеры
В леерсах их 2 типа:
HTML (OpenLayers.Marker) и векторные (OpenLayers.Geometry.Point). Чуть позже я объясню, что здесь имеется ввиду под маркером и почему point.
HTML маркер создает один или несколько дивов, в который помещает картинку и располагает над картой в соответсвии с координатами. Если вы когда-нибудь смотрели фаербагом или другим отладчиком как устроен маркер на карте гугл, вам все будет знакомо. К ним относительно легко подключаются попапы, с ними легко (т.к. есть html объект) работать из jQuerry, и тем не менее создатели библиотеки не рекомендуют ими пользоваться. Почему? Они несколько тяжеловесны: когда у вас 1 маркер — все хорошо, когда тысяча — плохо. Они выбиваются из общей концепции хранения и отображения данных принятой в ol. Ну и из практических соображений: когда я начинал работать с леерсами, для таких маркеров не было drag контрола. Впрочем, его и сейчас нет.
Тем не менее, немного кода для добавления маркеров на карту:
//создаем слой с маркерами var markers = new OpenLayers.Layer.Markers( "Markers" ); //добавляем его на карту map.addLayer(markers); //координаты, куда добавляем маркер var lonLat = new OpenLayers.LonLat( 0, 0 ); //создаем маркер с дефолтной картинкой с координатами 0, 0 //идобавляем его в слой markers.addMarker(new OpenLayers.Marker(lonLat));
Да, слоев с маркерами может быть несколько. Да, вы можете управлять видимостью слоев и прятать группы маркеров по своему усмотрению.
Теперь о том, кто такие векторные маркеры и о «концепции хранения и отображения данных».
Мало кого сейчас устроит возможность просто показывать разные растровые слои с картами. Основная прелесть в отображении своих уникальных данных поверх них. Итак, предположим, мы хотим отображать поверх карты оптические кабели, медные кабели и колодцы/опоры, через которые это добро проходит. Каждый объект будет содержать геометрическую информацию (как собственно проходит кабель / где располагается опора или колодец) и атрибутивную (тип кабеля, количество жил кабеля, затухание сигнала, высота опоры… список можно пополнять до бесконечности). Собственно эта идея напрямую реализуется в леерсах:
Объект — «OpenLayers.Feature.Vector», хранится вместе со своими атрибутами, геометрией и, опционально, стилем отображения.
Для точечных объектов:
new OpenLayers.Feature.Vector( //Геометрия - точка с координатами x, y (x -longitude, y - latitude) new OpenLayers.Geometry.Point(x, y), //Атрибуты: тип, высота {'type':'pillon', 'height':100}, //Стиль, в соответсвие с правилами которого мы хотим отображать объект. //В данном случае будет использован тиль по умолчанию null );
Пару слов про стили.
В стилях можно задать, как именно отображать геометрию, используя атрибуты объекта. В зависимости от типа можно использовать различные иконки, например, колодцы рисовать кружками, а опоры — столбиками. Можно определить цвет заливки, толщину и цвет обводки. На основе атрибутов можно выводить текстовые подписи к объектам и т.д. Можно задать стиль конкретному объекту, можно, например, назначить стиль для слоя, чтобы все объекты слоя отображались в соответсвии с ним.
И все же к маркерам:
//Создаем слой с маркерами. //Markers - имя для отображения в списке слоев. //Передав в конструктор вторым атрибутом {showInLayerSwitcher: false} //можно спрятать слой из контролла выбора слоев. var markers = new OpenLayers.Layer.Vector('Markers'); //добавляем его на карту map.addLayer(markers); //объект, для которого рисуем маркер var marker = new OpenLayers.Feature.Vector( //долгота/широта new OpenLayers.Geometry.Point(0, 0), //данные по вкусу {}, //Стиль, как отрисовывать { //рисуем картинку externalGraphic:'http://someware.com/my_favorite_marker_icon.png', //вот такой ширины graphicWidth:16, //вот такой вышины * graphicHeight:16, //сместив картинку на 8 пикселей влево //относительно координат геометрии graphicXOffset:-8, //и на 16 пикселей вверх graphicYOffset:-16, //с милой подписью (подпись будет выводиться прямо на карту) label:'Мой самый любимый маркер', //с базовой точкой текста подписи посередине-сверху текста labelAlign: 'ct', //сдвинув текст на 5 пикселей вниз labelYOffset: '5' } ); markers.addFeatures([marker]);
*Дорогой grammar-nazi, это была аллюзия к детской песенке.
Если вам надо добавить несколько маркеров с одним стилем, достаточно использовать один объект стиля, но важно помнить при этом что изменения в инстансе стиля отразятся на всех маркерах.
Итак, маркер мы добавили. Теперь давайте добавим возможность его перемещать, кликать по нему, и реагировать на прочие события. Собственно обработкой событий леерсы сильнее всего отличаются от остальных библиотек, с которыми довелось поработать (внимательный читатель, который прочтет всю статью, не пропуская абзацы, узнает, что, в первую очередь, это google и чуток leaflet с яндексом).
Добавляем драг:
//Создаем контрол для слоя с маркерами, который позволяет перемещать объекты по карте. var drag = OpenLayers.Control.DragFeature(markers); //Если у вас много слоев с объектами, можно добавить их внутрь //OpenLayers.Layer.Vector.RootContainer и передать его //контролу //добавляем контрол на карту map.addControl(drag); //включить контрол //(при добавлении на карту должен включиться автоматом, но вдруг...) drag.activate();
Если память меня не подводит, вы, наконец, добъетесь перемещения маркеров по карте.
Или добавим нашему маркеру попап по клику:
selectControl = new OpenLayers.Control.SelectFeature(markers, { //колбэк на клик по маркеру onSelect: onFeatureSelect, //колбэк на клик вне маркера onUnselect: onFeatureUnselect }); function onFeatureSelect(feature) { popup = new OpenLayers.Popup.FramedCloud("chicken", feature.geometry.getBounds().getCenterLonLat(), null, "<div style='font-size:.8em'>Привет Habr!</div>", null, true, onPopupClose ); feature.popup = popup; map.addPopup(popup); } function onFeatureUnselect(feature) { map.removePopup(feature.popup); feature.popup.destroy(); feature.popup = null; }
Можно посмотреть пример вот тут: openlayers.org/dev/examples/select-feature-openpopup.html
Большие проблемы начинаются, когда вам одновременно нужно:
- иметь возможность двигать маркеры
- отображать hover (т.е. обрабатывать onmousein и onmouseout)
- обрабатывать клик, даблклик по объекту
- обрабатывать клик, даблклик вне объекта
Это решаемая беда, но решение, пожалуй, заслуживает отдельной статьи.
Геокодирование
Фуф, мы добавили OSM на сайт и научились отображать свои данные поверх. Но все же значительная часть api гугла (яндекса) осталась за бортом. А именно геокодирование (получение координат по адресу и адреса по координатам) и прокладка маршрутов.
Про маршрутизацию по картам в осм я расскажу в другой раз, сейчас пару слов о геокодировании. Я не делал обратный геокодинг (адрес по координатам), поэтому остановлюсь пока на поиске координат по адресу.
Тут есть несколько вариантов: использовать готовый поиск от nominatim или openstreetmap.ru, написать свой велосипед. Пару слов о том, почему вам может понадобиться свой геокодер.
1. К примеру, вас интересует только Москва, соответсвенно, данные будет импортировать проще, и поисковые запросы будут простыми, без указания города.
2. По каким-то причинам вы не можете дать доступ во внешку ни с клиентских машин, ни с сервера.
3. Вам нужен геокодер по собственной базе адресов клиента.
Что же, никакой магии нет: самый простой вариант — использовать Solr или Sphynx. По сути я просто сохраняю в solr документы с полным адресом и координатами объекта.
Чтобы получить список адресов, можно к примеру взять интересующий вас регион в shp формате, загрузить в postgis, после чего достать адреса запросом вида:
select bldng.osm_id, bldng."A_STRT", bldng."A_SBRB", bldng."A_HSNMBR", settle."NAME", ST_AsText(ST_Centroid(bldng.geom)) from building-polygon bldng join settlement-polygon settle on ST_Within(bldng.geom, settle.geom)
На сегодня все. В следующий раз постараюсь подробнее рассказать про систему событий в openlayers.
Ссылки
Документация к openlayers — dev.openlayers.org/docs/files/OpenLayers/Map-js.html
Там же песочница с примерами — openlayers.org/dev/examples
Proj4js — trac.osgeo.org/proj4js
Сайт с описанием различных систем координат в разных форматах,
в том числе в формате proj4 — spatialreference.org