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 можно найти тут.
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 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
              Если кому нибудь нужна площадь в СИ метрах, а не в черт-пойми-чем от WebMercator проекции (см. habrahabr.ru/post/145117/#comment_4877057), использовать надо OpenLayers.Geometry.Polygon.getGeodesicArea()
              • 0
                Хотя это и лучше, чем условные «метры» проекции Меркатора, однако все равно стоит понимать (и в документации это написано — approximate area) что это приблизительная прощадь.
                Чтобы выяснить, на сколько она приблизительна, нужно лезть в код библиотеки, т.к. в документации не сказано, какой способ проекции выбирается для вычисления. А результат может различаться, например, в случае использования стандартной проекции UTM и в случае создания для каждого такого обсчитываемого полигона местной системы координат.

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