Все статьи
Содержание

    Новые карты CUBA

    Работа с геопространственными данными и отображение карт являются неотъемлемыми составляющими множества бизнес-приложений. Это могут быть городские и региональные информационные системы, приложения для нефтегазовой отрасли, системы управления транспортной инфраструктурой, а также службы доставки и многие другие. У нас в CUBA Platform для построения подобных приложений помимо базовых возможностей, предоставляемых из коробки, существует довольно обширный набор дополнений и компонентов. Одним из них является Charts and Maps, которое помимо отображения графиков позволяет интегрировать в визуальную часть приложения Google-карты. В прошлом году Google обновил условия использования своих картографических сервисов, что повлекло за собой рост стоимости, а также ввел условие обязательного наличия платежного профиля для использования API. Эти обстоятельства заставили большинство наших клиентов задуматься об альтернативных поставщиках карт, а нас подтолкнули к разработке нового компонента карт.

    Теперь мы рады представить совершенно новый компонент - CUBA Maps. CUBA Maps дополняет функционал приложения визуальным представлением и интуитивными инструментами редактирования геопространственных данных. Компонент работает как с растровыми данными, так и с векторными. В качестве растровых данных вы можете использовать любой провайдер карт, совместимый с протоколом Web Map Service, или предоставляющий тайлы в формате XYZ. Для работы с векторными данными компонент использует геометрические типы данных (точка, полилиния, полигон) из библиотеки JTS Topology Suite (JTS) — самой популярной Java библиотеки для работы с геопространственными данными. Компонент предоставляет все необходимые инструменты для создания комплексной геоинформационной системы на базе CUBA.

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

    Структура, основанная на слоях

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

    Компонент поддерживает следующие типы слоев:

    • Tile layer (слой тайлов) отображает тайлы, предоставляемые тайловыми сервисами в формате XYZ.
    • Слой Web Map Service (WMS) отображает изображения, предоставляемые WMS-сервисами.
    • Векторный слой содержит гео-объекты (сущности с геометрическими атрибутами).

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

    layers

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

    CUBA Maps предоставляет новый визуальный компонент — GeoMap. В XML дескрипторе компонента можно задать основные параметры карты, а также набор отображаемых слоев. Пример такой конфигурации:

    <maps:geoMap id="map" height="600px" width="100%" center="37.615, 55.752" zoom="10">
        <maps:layers selectedLayer="addressLayer">
            <maps:tile id="tiles" tileProvider="maps_OpenStreetMap"/>
            <maps:vector id="territoryLayer" dataContainer="territoryDc"/>
            <maps:vector id="addressLayer" dataContainer="addressDc" editable="true"/>
        </maps:layers>
    </maps:geoMap>
    

    Подобный подход позволяет добиться большей гибкости, которой не хватало в Charts and Maps:

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

    • Слои обеспечивают абстракцию, которая объединяет однородные объекты. В компоненте Charts and Maps все содержимое карты (например, точки, полигоны, и т.д.) было свалено в общую кучу в UI компоненте. Чтобы как-то структурировать эти объекты, проектным командам приходилось писать дополнительную логику.

    • Декларативный метод описания слоев. Как было показано в примере выше, вы можете полностью задать структуру карты (набор слоев) в XML дескрипторе. Во многих случаях этого достаточно, чтобы не реализовывать никакой дополнительной логики в контроллере экрана. Используя Charts and Maps было практически невозможно обойтись без написания дополнительной логики.

    Использование слоев тайлов или WMS позволяет работать с любым предпочитаемым провайдером карт. Вы не привязаны к определенному провайдеру, как это было в Charts and Maps.

    Векторные слои значительно упрощают отображение, интерактивное редактирование и рисование гео-объектов на карте.

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

    Гео-объекты

    Предположим, у нас есть сущность, содержащая атрибут, связанный с геометрией (точкой, полилинией, полигоном). Эту сущность мы назовем гео-объектом. Так вот, компонент значительно упрощает работу с гео-объектами.

    Например, рассмотрим гео-объект Адрес:

    @Entity
    public class Address extends StandardEntity {
    ...
    
    
     @Column(name = "LOCATION")
     @Geometry
     @MetaProperty(datatype = "GeoPoint")
     @Convert(converter = CubaPointWKTConverter.class)
     protected Point location;
    
    
     ...
    }
    

    У него есть атрибут location типа org.locationtech.jts.geom.Point из библиотеки JTS Topology Suite (JTS). Компонент поддерживает следующие геометрические типы из JTS:

    • org.locationtech.jts.geom.Point — точка.
    • org.locationtech.jts.geom.LineString — полилиния.
    • org.locationtech.jts.geom.Polygon — полигон.

    Атрибут location помечен аннотацией @Geometry. Эта аннотация объявляет о том, что значение данного атрибута должно использоваться при отображении гео-объекта на карте. Атрибут также помечен следующими аннотациями:

    • @MetaProperty — в данном случае используется для указания datatype атрибута. Интерфейс Datatype используется фреймворком CUBA для конвертации значений в строку и из строки.
    • @Convert — определяет JPA конвертер для персистентного атрибута. JPA-конвертер осуществляет конвертацию значений атрибута между его представлениями в базе данных и Java-коде. Компонент предоставляет набор пространственных datatype-ов и JPA-конвертеров. Более подробная информация доступна в документации компонента. Также можно использовать свою реализацию JPA-конвертера, что дает возможность работать с различными источниками пространственных данных (например, PostGIS).

    Таким образом, чтобы превратить сущность в гео-объект, нужно определить атрибут, имеющий тип JTS-геометрии, и аннотировать его @Geometry. Есть и еще один вариант - создать неперсистентный атрибут, предоставив getter/setter методы. Это может быть полезно в том случае, если вы не хотите вносить изменения в модель данных и перегенерировать DDL скрипты.

    Например, рассмотрим сущность Адрес с отдельными атрибутами для широты и долготы:

    import com.haulmont.addon.maps.gis.utils.GeometryUtils;
    ...
    
    @Entity
    public class Address extends StandardEntity {
    ...
    
    
     @Column(name = "LATITUDE")
     protected Double latitude;
    
     @Column(name = "LONGITUDE")
     protected Double longitude;
    
    
    ...
    
     @Geometry
     @MetaProperty(datatype = "GeoPoint", related = {"latitude", "longitude"})
     public Point getLocation() {
       if (getLatitude() == null || getLongitude() == null) {
           return null;
       }
       return GeometryUtils.createPoint(getLongitude(), getLatitude());
     }
    
     @Geometry
     @MetaProperty(datatype = "GeoPoint")
     public void setLocation(Point point) {
       Point prevValue = getLocation();
       if (point == null) {
           setLatitude(null);
           setLongitude(null);
       } else {
           setLatitude(point.getY());
           setLongitude(point.getX());
       }
       propertyChanged("location", prevValue, point);
     }
    
     ...
    }
    

    Если вы решили использовать такой подход, убедитесь, что в сеттере вызывается метод propertyChanged, поскольку компонент реагирует на это событие обновлением геометрии на карте.

    Теперь, когда мы подготовили класс нашего гео-объекта, мы можем добавлять экземпляры этого класса на векторный слой. Векторный слой по сути является связующим элементом между данными (гео-объектами) и картой. Чтобы связать гео-объекты cо слоем, нужно передать data container или, в случае работы с устаревшими экранами (до 7 версии CUBA), datasource в векторный слой. Это можно сделать в XML дескрипторе:

    <maps:geoMap id="map">
       <maps:layers>
           ...
           <maps:vector id="addressesLayer" dataContainer="addressesDc"/>
       </maps:layers>
    </maps:geoMap>
    

    В результате экземпляры класса Address, содержащиеся в контейнере addressesDc, будут отображены на карте.

    Рассмотрим элементарную задачу: создание экрана редактирования гео-объекта с картой, где можно редактировать геометрию объекта. Для решения задачи нужно объявить визуальный компонент GeoMap в XML дескрипторе экрана редактирования и добавить векторный слой, связанный с контейнером, содержащим редактируемый гео-объект:

    <maps:geoMap id="map" height="600px" width="100%" center="37.615, 55.752" zoom="10">
        <maps:layers selectedLayer="addressLayer">
            <maps:tile ..."/>
            <maps:vector id="addressLayer" dataContainer="addressDc" editable="true"/>
        </maps:layers>
    </maps:geoMap>
    

    Если пометить векторный слой как редактируемый, активизируется интерактивное редактирование гео-объекта на карте. В случае, если геометрия объекта имеет пустое значение, карта автоматически перейдет в режим рисования. Как видите, для решения задачи достаточно объявить векторный слой на карте и передать ему data container/datasource.

    Вот и всё. Если бы мы использовали Charts and Maps для решения этой же задачи, нам пришлось бы написать довольно много кода в контроллере экрана для обеспечения подобной функциональности. С новым компонентом Maps решать такие задачи значительно проще.

    Canvas

    Бывают случаи, когда вам нужно работать не с сущностями. Вместо этого вы хотите простой API для добавления и рисования геометрий на карте, как это было в Charts and Maps. Для этого у визуального компонента GeoMap есть специальный слой — Canvas. Это вспомогательный слой, который есть на карте по умолчанию и который предоставляет простой API для добавления и рисования геометрий на карте. Получить Canvas карты можно вызвав метод map.getCanvas().

    Далее мы рассмотрим несколько простых задач, как они решались в Charts and Maps и как можно сделать то же самое, используя Canvas.

    Отображение геометрий на карте

    В Charts and Maps объекты геометрий создавались с помощью визуального компонента карты, используемого в качестве фабрики, а затем уже добавлялись на карту:

    Marker marker = map.createMarker();
    GeoPoint position = map.createGeoPoint(lat, lon);
    marker.setPosition(position);
    map.addMarker(marker);
    

    Новый компонент Maps работает непосредственно с классами из библиотеки JTS:

    CanvasLayer canvasLayer = map.getCanvas();
    Point point = address.getLocation();
    canvasLayer.addPoint(point);
    

    Редактирование геометрий

    В Charts and Maps можно было обозначить геометрию как редактируемую. Когда такие геометрии изменялись через UI, вызывались соответствующие события:

    Marker marker = map.createMarker();
    GeoPoint position = map.createGeoPoint(lat, lon);
    marker.setPosition(position);
    marker.setDraggable(true);
    map.addMarker(marker);
    
    map.addMarkerDragListener(event -> {
           // do something
    });
    

    В компоненте Maps при добавлении JTS-геометрии на Canvas соответствующий метод возвращает специальный объект, который является представлением этой геометрии на карте: CanvasLayer.Point, CanvasLayer.Polyline или CanvasLayer.Polygon. Этот объект имеет fluent интерфейс для задания различных параметров геометрии, также он может быть использован для подписки на события, связанные с геометрией, либо для удаления геометрии с Canvas.

    CanvasLayer canvasLayer = map.getCanvas();
    CanvasLayer.Point location = canvasLayer.addPoint(address.getLocation());
    location.setEditable(true)
           .setPopupContent(address.getName())
           .addModifiedListener(modifiedEvent ->
                address.setLocation(modifiedEvent.getGeometry()));
    

    Рисование геометрий

    В старом аддоне Charts and Maps присутствовал вспомогательный компонент — DrawingOptions. Он использовался для активации возможности рисования на карте. После того как геометрия была нарисована, вызывалось соответствующее событие:

    DrawingOptions options = new DrawingOptions();
    PolygonOptions polygonOptions = new PolygonOptions(true, true, "#993366", 0.6);
    ControlOptions controlOptions = new ControlOptions(
    Position.TOP_CENTER, Arrays.asList(OverlayType.POLYGON));
    options.setEnableDrawingControl(true);
    options.setPolygonOptions(polygonOptions);
    options.setDrawingControlOptions(controlOptions);
    options.setInitialDrawingMode(OverlayType.POLYGON);
    map.setDrawingOptions(options);
    
    
    map.addPolygonCompleteListener(event -> {
       //do something
    });
    

    Компонент Maps позволяет сделать то же самое гораздо проще. В новом Maps Canvas содержит набор методов для рисования геометрий. Например, чтобы нарисовать полигон, используйте метод canvas.drawPolygon(). После вызова этого метода карта перейдет в режим рисования полигона. Метод принимает функцию Consumer<CanvasLayer.Polygon>, в которой можно осуществить дополнительные действия с нарисованным полигоном.

    canvasLayer.drawPolygon(polygon -> {
       territory.setPolygon(polygon.getGeometry());
    });
    

    Инструменты для гео-анализа

    Кластеризация

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

    cluster

    Кластеризация включается добавлением тега cluster внутри тега vector в XML дескрипторе:

    <maps:vector id="locations" dataContainer="locationsDc" >
     <maps:cluster/>
    </maps:vector>
    

    Также можно включить кластеризацию, основанную на “весах” точек. В качестве веса точки выступает значение атрибута, указанного в параметре weightProperty.

    <maps:vector id="orders" dataContainer="ordersDc" >
     <maps:cluster weightProperty="amount"/>
    </maps:vector>
    

    Тепловые карты

    Тепловые карты - это визуальное представление плотности данных среди множества географических точек. Визуальный компонент GeoMap содержит метод для добавления тепловой карты: addHeatMap(Map<Point, Double> intensityMap).

    heatmap

    Заключение

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

    Структура, основанная на слоях, помогает в построении карт с любым содержимым. Со слоями тайлов/WMS вы можете использовать любой нужный вам провайдер в качестве базовой карты. Векторные слои позволяют эффективно работать с наборами однородных гео-объектов. Canvas предоставляет простой API для отображения и рисования геометрий на карте.

    Компонент работает с пространственными типами из библиотеки JTS, что делает его совместимым со многими другими фреймворками (например, GeoTools) для решения широкого круга задач, связанных с обработкой и анализом географических данных.

    Надеемся, что вам понравится компонент. Ждем ваших отзывов!

    Jmix - это open-source платфора быстрой разработки бизнес-приложений на Java