Пользователь
0,0
рейтинг
4 июня 2012 в 02:02

Разработка → OpenLayers или делаем сервис мониторинга транспорта

Сейчас на рынке много предложений по продаже мобильных устройств, предназначенных для контроля движущихся объектов или трекеров. В большинстве из них есть функция передачи информации по GPRS на любой заданный веб-адрес через определенный интервал времени. Чаще всего формат передачи данных разный. Поэтому мы не будем рассматривать вопрос загрузки данных с трекера в базу. Предположим данные есть и мы хотим приступить к созданию сервиса мониторинга транспорта. Основу такой системы образуют возможности:
-выбор карты и ее отображение
-отображение точки или картинки и подписи к ней
-отображение полигона и его редактирование
-отображение линии и ее редактирование
-отображение информации связной с полигонами, линиями, точками (всплывающие подсказки)
-немного математики (подсчет пройденного пути, площадь полигона, принадлежность точки полигону)
Все эти функции легко реализовать с помощью OpenLayers, библиотеки на JavaScript.


Выбор карты и ее отображение
Для этого на вашей страничке нужно создать элемент div c нужными размерами. В нем и будет отображаться карта. Пример:
 div id="map" style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px;"

Подключить OpenLayers:
script type="text/javascript" src="Scripts/OpenLayers.js"

Создать глобальную переменную map, в которой будет храниться объект карты. Создать вызов функции на событие загрузки страницы, в которой будет код непосредственного создания карты. Пример:
body onload="LoadMap()"

Описать функцию LoadMap:
//Границы задаются для проекции EPSG:900913. 
var maxExtent = new OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508);
var restrictedExtent = maxExtent.clone();
var maxResolution = 156543.0339;
//Отображаться карта будет в проекции EPSG:4326. 
var options = {
   projection: new OpenLayers.Projection("EPSG:900913"),
   displayProjection: new OpenLayers.Projection("EPSG:4326"),
   numZoomLevels: 18,
   maxResolution: maxResolution,
   maxExtent: maxExtent,
   restrictedExtent: restrictedExtent
};
//Создаем карту
map = new OpenLayers.Map('map', options);

// Создаем слои-карты VirtualEarth
var veroad = new OpenLayers.Layer.VirtualEarth("Virtual Earth Roads", { 'type': VEMapStyle.Road, sphericalMercator: true });
var veaer = new OpenLayers.Layer.VirtualEarth("Virtual Earth Aerial", { 'type': VEMapStyle.Aerial, hericalMercator: true });
var vehyb = new OpenLayers.Layer.VirtualEarth("Virtual Earth Hybrid", { 'type': VEMapStyle.Hybrid, sphericalMercator: true });
// Создаем слои-карты OpenStreet 
var mapnik = new OpenLayers.Layer.OSM();
//Добавляем подсказку масштаба карты и позици курсора
map.addControl(new OpenLayers.Control.ScaleLine());
map.addControl(new OpenLayers.Control.MousePosition());
//Складываем созданные слои в объект OpenLayers.Map
map.addLayers([ mapnik, veroad, veaer, vehyb]);
//Добавляем панель управления слоями 
map.addControl(new OpenLayers.Control.LayerSwitcher());
//Создаем точку на которую будет центрироваться карта при старте
var point0 = new OpenLayers.Geometry.Point(37.600328, 55.574624);
//Помним что карта отображается в одной проекции, а с данными работает в другой проекции. 
point0.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
//Выполняем центрирование карты на точку с масштабом 9. По умолчанию их 15.   
map.setCenter(new OpenLayers.LonLat(point0.x, point0.y), 9);
        }

На сайте проекта openlayers можно найти много примеров, около двухсот. Пример визуализации карты из заданного сервиса, можно увидеть здесь. Большинство решаемых задач в статье эти примеры покрывают. В представленном коде отсутствует присоединение карт google и yandex, для работы этих карт нужны ключи. Вы должны зарегистрироваться, получить ключ доступа к сервису и только после этого можно подсоединяться к этим сервисам. Результат кода выше:
1
Отображение точки или картинки и подписи к ней
Создаем слои, которые будет хранить наши объекты(точки и картинки).
//Создаем объект стиля отображения картинок. Указываем параметры ширены, высоты, сдвига по вертикале относительно точки, подпись(${} - это объявление параметра, можно выводить константу для всех точек), url к графику, размер шрифта. 
var styleImage = new OpenLayers.Style(
   {
      graphicWidth: 21,  
      graphicHeight: 25,  
      graphicYOffset: -28, 
      label: "${label}",   
      externalGraphic: "http://www.openlayers.org/dev/img/marker.png",   
      fontSize: 12 
   });
//labelYOffset - сдвиг текста по вертикале относительно точки
var stylePoint = new OpenLayers.Style(
   { 
      pointRadius: 5,
      strokeColor: "red",
      strokeWidth: 2,
      fillColor: "lime",
      labelYOffset: -16,
      label: "${label}",
      fontSize: 16 
   });
//Создаем слой для точек. В свойстве styleMap указываем как отображать в обычном случае. Свойство select будет применено после выбора элемента. 
var vectorPoint = new OpenLayers.Layer.Vector("Точки",
{
        styleMap: new OpenLayers.StyleMap(
        { "default": stylePoint,
          "select": { pointRadius: 20}
        })
    });
//В отличие от слоя с точками, где выделение объекта взывает увеличение радиуса точки, на слое с картинками будет их поворот на 45 градусов

var vectorImage = new OpenLayers.Layer.Vector("Картинки",
{
        styleMap: new OpenLayers.StyleMap(
        { "default": styleImage,
          "select": { rotation: 45}
        })
    });
//Складываем слои на карту
 map.addLayer(vectorImage );
 map.addLayer(vectorPoint );

Слои созданы, создаем функции которые будут работать со слоями.
//Функция добавляет изображение с подписью. Аргументы: долгота, широта, подпись, уникальный идентификатор, слой. В случае, если объект уже есть функция сдвигает его в новое положение. 
function addImg(lon,lat,title,ident, layr){
var ttt = new OpenLayers.LonLat(parseFloat(lon), parseFloat(lat));
ttt.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
// features-массив объектов на карте. Объект может быть например: точкой, полигоном, кривой, изображением
for (var k = 0; k < layr.features.length; k++) 
{
// У объектов features есть атрибуты, как предопределенные так и добавленные пользователем
if(layr.features[k].attributes.ImgId==ident) {
// move - функция перемещения объекта в данную точку
		layr.features[k].move(ttt);
		layr.features[k].attributes.label=title;
		return false;

	}
}
var point0 = new OpenLayers.Geometry.Point(parseFloat(lon), parseFloat(lat));
point0.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
//Для создание объекта Feature использовался класс OpenLayers.Feature.Vector
layr.addFeatures(new OpenLayers.Feature.Vector(point0, { label: title, name: title, ImgId: ident }));              
}
function addPoint(lon,lat,title,ident){
var ttt = new OpenLayers.LonLat(parseFloat(lon), parseFloat(lat));
ttt.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
for (var k = 0; k < map.layers[5].features.length; k++) 
{
if(map.layers[5].features[k].attributes.PointId==ident) {
		map.layers[5].features[k].move(ttt);
		map.layers[5].features[k].attributes.label=title;
		return false;

	}
}
var point0 = new OpenLayers.Geometry.Point(parseFloat(lon), parseFloat(lat));
point0.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
map.layers[5].addFeatures(new OpenLayers.Feature.Vector(point0, { label: title, name: title, PointId: ident }));
}
</code>
Теперь осталось выполнить добавление точек и изображений:
<code>
 addPoint(37,55.5,'Точка 1','1',map.layers[4]);
 addPoint(37,55.1,'Точка 2','2',map.layers[4]);
  addImg(37,55.6,'Изображение 1','2',map.layers[5]);
   addImg(37,55.9,'Изображение 2','2',map.layers[5]);

Результат:
1
Добавилось только одно изображение потому, что вызов функции был с одинаковыми идентификаторами изображений. Объект сместился, название было изменено на новое.
Отображение полигона и его редактирование
Функция создания полигона:
//Предполагаемый форма данных: координаты разделены точкой с запятой, долгота с широтой разделены пробелом
function addPoly(data,title,ident,layr){
var featuress = Array();
var coords = data.split(';');
for (var i = 0; i < coords.length; i++) {
                var d = coords[i].split(' ');
                var ttt = new OpenLayers.LonLat(parseFloat(d[0]), parseFloat(d[1]));
                var point0 = new OpenLayers.Geometry.Point(ttt.lon, ttt.lat);
                point0.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
                featuress.push(point0);
            }
//Массив точек порождает линию, из нее создается полигон
            var linearRing2 = new OpenLayers.Geometry.LinearRing(featuress);
            var polygonFeature2 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon([linearRing2]), { label: title, PolyID: ident });
            layr.addFeatures(polygonFeature2);
	}

Пример вызова функции прорисовки полигона:
addPoly('37 55.9;37.5 55.4;37 55','Полигон','1',map.layers[5]); 

Редактирование в OpenLayrs осуществляется с помощью OpenLayers.Control.ModifyFeature. Нужно создать экземпляр этого класса указав слой объекты которого нужно модифицировать:
var modef = new OpenLayers.Control.ModifyFeature(map.layers[5])
modef.clickout = false;
modef.toggle = false;
modef.standalone = true;
map.addControls([ modef]);
modef.activate();
//Выбираем созданный полигон  
modef.selectFeature(map.layers[5].features[2]);
//Получаем центр полигона
var centr = map.layers[5].features[2].geometry.getCentroid()
//Карту центрируем по полученной точке
map.setCenter(new OpenLayers.LonLat(centr.x, centr.y));

Каждая вершина полигона в момент редактирования обозначается точкой и в силу того, что полигон мы разместили на слое для точек, к ним был применен стиль этого слоя. Результат: подписи «undefined» у вершин полигона. Это приме того, что для каждого типа объектов лучше создавать отдельные слои. Пример отображения режима редактирования:
3
Выход из режима редактирования:
modef.deactivate();

Отображение линии и ее редактирование
Как было уже сказано, полигон создается из линии, таким образом в функции AddPoly нужно изменить одну строку:
 layr.addFeatures(polygonFeature2);

на
layr.addFeatures(linearRing2 );

Процесс редактирование ничем не отличается.
Всплывающие подсказки
Для реализации всплывающей подсказки нужно добавить на карту обработчик событий, который представляет собой экземпляр класса: OpenLayers.Control.SelectFeature.
var selectControl = new OpenLayers.Control.SelectFeature([map.layers[5]]);
 map.addControls([selectControl],
                {
                    clickout: true, toggle: false,
                    multiple: false, hover: true,
                    toggleKey: "ctrlKey", 
                    multipleKey: "shiftKey" 
                });
selectControl.activate();

После этого будет происходить обработка нажатий на объекты слоев. Точки будут увеличиваться в размере, картинки поворачиваться на 45 градусов. Создаем обработчик события выделения точки:
//Событие on - это событие выделения   
map.layers[5].events.on( {
 "featureselected": function (e) {
       var HTMLcontent;
       var point
//Здесь можно генерировать любой контент
       HTMLcontent = 'table style="width: 100%;" tr td Информация об объекте td tr table ';
//getCentroid() - получить центр фигуры, в данном случае лишнее, но это унифицированный способ получения места всплытия подсказки
       point = new OpenLayers.LonLat(e.feature.geometry.getCentroid().x, e.feature.geometry.getCentroid().y);
//OpenLayers.Popup.AnchoredBubble - всплывающий прямоугольник, есть другие варианты в OpenLayers.Popup
       var popup = new OpenLayers.Popup.AnchoredBubble("SDVegetationInfo",
           point,
           new OpenLayers.Size(100, 100), 
           HTMLcontent,                   
           null,
           false);
       popup.opacity = 0.9;
       popup.autoSize = true;
       popup.setBackgroundColor("#bcd2bb");
//добавление на карту 
       map.addPopup(popup, true);
      }
// когда выделение убрано, через секунду окно погаснет
      , "featureunselected": function (e) {
           setTimeout('if(map.popups.length - 1>-1){map.removePopup(map.popups[map.popups.length - 1]);}', 1000);
        }
});

Подсчет пройденного пути, площадь полигона, принадлежность точки полигону
Подсчет пройденного пути
map.layers[4].features[2].feature.geometry.getLength()

площадь полигона
map.layers[4].features[2].feature.geometry.getArea()

принадлежность точки полигону
map.layers[4].features[2].containsPoint(map.layers[4].features[0])

Результат всех предыдущих действий можно увидеть на странице: ссылка

В заключение отмечу, что многое компании уже сделали свои трекеры на OpenLayers это дешево и просто. Документацию по OpenLayers можно найти тут.
@RedQuark
карма
17,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (16)

  • +1
    Используйте тег <source>, пожалуйста (выпадающий список над полем редактирования). Тег <code> — для инлайновых вставок. Также в тексте можно <ul> использовать и ещё много разных тегов.
  • 0
    А что за карты используются? И как там с покрытием россии?
    • +2
      Из кода можно заключить, что это карты Openstreetmap и бывшие Virtual Earth, ныне — Bing Maps. Однако можно с тем же успехом использовать практически что угодно, включая тайлы Google Maps, Яндекс, либо данные из собственного источника.
      Покрытие — оцените сами, т.к. карты такой большой территории — штука составная, и в итоге у одного сервиса есть преимущества в одном населенном пункте, а у другого — в другом. Безусловного лидера выделить крайне затруднительно, тем более, что и оценивать можно по разным показателям.
      • 0
        Яндекс запрещает явное использование своих карт для целей сервисов коммерческого мониторинга транспортных средств.
        • 0
          А гугл позволяет, но надо лицензию покупать
  • 0
    Продолжение будет?
    Если нет, то уже сюда, наверное, стоило добавить ответы на очевидные вопросы об использовании разных источников картографических данных и о функциях (раз уж это, практически, пошаговая инструкция, а не просто описание возможностей), нужных именно системе мониторинга — периодическое обновление, загрузка данных о положении объектов, выдаваемых сторонним скриптом, а не добавляемых непосредственно в коде страницы.
    • 0
      Пока не знаю. Будут улучшения текущей по замечаниям. Описание одних только форматов займет не меньше двух страниц. В документации, во вкладке формат, описывается больше тридцати форматов. Что бы с ними поработать надо разворачивать ГИС сервера(MapServer,GeoServer или ArcGisServer), писать сервисы или добыть файлы нужного формата. Это отделанная статья. Постараюсь вечером прикрутить демонстрационный сервис поставщика данных.
      • 0
        Нет, я ни в коем случае не имел в виду цитирование документации и перечисление всех форматов.
        Речь шла о том, чтобы раз уж статья о сервисе мониторинга транспорта, то и примеры (столь же частные) были бы на уже эту конкретную тему.
  • 0
    Натыкался как-то на то что getLength() возвращает длину ровно в два раза больше. Это пофикшено сейчас?
    • +1
      Это не косяк OpenLayers — это следствие Web Mercator. Тамошние «метры» становятся все менее метрами при удалении от экватора. В наших широтах действительно соотношение выходит около 2:1. Если нужна точная длина, рекомендую воспользоваться функцией OpenLayers.Util.distVincenty, которая вычисляет геодезическое расстояние между двумя точками, на основе чего можно написать свою реализацию getLength().

      Подробнее о scale factor-е WebMercator можно почитать в EPSG Guidance Note 7 part 2, p.38, если кому-то, конечно, интересно. :)
      • 0
        О! Спасибо! О том что «метры» не одинаковые на экваторе и на нашей широте я уже знал, но думал что getLength() работает не так примитивно.
  • 0
    Спасибо за подробное описание реализации сервиса!
    Можно написать встречную статью для тех кто ленится познать OpenLayers: «Написание сервиса мониторинга транспорта средствами картографических API»
    например 2gis API (вдвойне актуально из-за приближающегося релиза транспортного API) или Яндекс API

    кода 100пудова меньше получится да + парк велосипедов в придачу!
    • 0
      Спасибо за отзыв. С 2gis API не работал, нужно разбираться на каких условиях происходит использование их сервиса, а вот с Яндексом переписывался, оказывается они ни за какие деньги не предоставляют свой картографический сервис для коммерческого использования.
      • 0
        API 2GIS можно использовать для коммерческого использования: api.2gis.ru/about/enterprise/
  • 0
    Если кому нибудь нужна площадь в СИ метрах, а не в черт-пойми-чем от WebMercator проекции (см. habrahabr.ru/post/145117/#comment_4877057), использовать надо OpenLayers.Geometry.Polygon.getGeodesicArea()
    • 0
      Хотя это и лучше, чем условные «метры» проекции Меркатора, однако все равно стоит понимать (и в документации это написано — approximate area) что это приблизительная прощадь.
      Чтобы выяснить, на сколько она приблизительна, нужно лезть в код библиотеки, т.к. в документации не сказано, какой способ проекции выбирается для вычисления. А результат может различаться, например, в случае использования стандартной проекции UTM и в случае создания для каждого такого обсчитываемого полигона местной системы координат.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.