All articles
Contents

    New Maps for CUBA

    @Column(name = "LOCATION")
    @Geometry
    @MetaProperty(datatype = "GeoPoint")
    @Convert(converter = CubaPointWKTConverter.class)
    protected Point location;

    ...
    }

    It has a property location of the org.locationtech.jts.geom.Point type that comes from the JTS Topology Suite (JTS) library. The add-on supports the following JTS geometry types:

    • org.locationtech.jts.geom.Point — point.
    • org.locationtech.jts.geom.LineString — polyline.
    • org.locationtech.jts.geom.Polygon — polygon.

    The location property is annotated with @Geometry annotation. This annotation marks the property to be used when displaying the entity on a map. The geometry property also has the following annotations:

    • @MetaProperty — specifies the corresponding datatype for the attribute. Datatypes are used by CUBA framework for converting values to and from strings.
    • @Convert — specifies a JPA converter for a persistent attribute. A JPA converter performs type conversion between the database and Java representation of an attribute.

    The component comes with a set of spatial datatypes and JPA converters. For more information please refer to the component’s documentation. It is possible to specify your own implementation of the JPA converter, which means that you can work with various providers of spatial data (for example, PostGIS).

    Thus, to turn your entity into a geo-object, you should declare a meta property of a JTS geometry type and annotate it with @Geometry. Another option is to create a non-persistent geometry property by exposing getter/setter methods. It can be helpful if you don't want to change your model and regenerate DDL scripts.

    For example, consider the Address entity having latitude/longitude in separate fields:

    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);
    }

    ...
    }

    If you take the second option, make sure to invoke propertyChanged method in the setter, because the component automatically updates the geometry on a map after this event was fired.

    Now that we have prepared our geo-object class, we can add instances of this class to the vector layer. Vector layer is a data-aware component acting as a connector between data (geo-objects) and a map. To bind geo-objects to the layer you need to pass a datacontainer (or datasource in case of using in legacy screens) to the vector layer. This can be accomplished in the XML descriptor:

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

    As a result, the instances of the Address class located in the addressesDc data container will be displayed on the map.

    Let’s consider a basic task of creating an editor screen for a geo-object with the map, where you can edit a geometry. To achieve this, you need to declare the GeoMap UI component in the screen XML descriptor and define a vector layer connected with the editing object.

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

    By marking the layer as editable you enable interactive geo-object editing. If geometry property of the editing geo-object has the empty value, then the map will be automatically turned into the drawing mode. As you can see, the only thing you need to do is to declare a vector layer and provide a datacontainer to it.

    And this is it. If we had used Charts and Maps for solving this problem, it would be necessary to write a pretty big amount of code in the screen controller in order to achieve this functionality. The new Maps component provides a really straightforward way of solving such tasks.

    Canvas

    Sometimes you do not want to work with entities. Instead, you want a simple API for easy adding and drawing geometries on a map like it was in Charts and Maps. For this purpose, the GeoMap UI component has a special layer called Canvas. It is a utility layer that a map has by default and which provides a straightforward API for adding and drawing geometries on a map. You can get the Canvas by calling the map.getCanvas() method.

    Next, we will overview some basic tasks, how they were solved in Charts and Maps and how we can do the same using the Canvas.

    Displaying geometries on a map

    In Charts and Maps geometry objects were created using the map UI component as a factory and then added to the map:

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

    New Maps add-on works with geometry classes from the JTS library:

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

    Editing geometries

    In Charts and Maps you were able to mark geometry objects as editable. When these geometries were modified via UI, corresponding events were fired:

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

    map.addMarkerDragListener(event -> {
    // do something
    });

    In Maps, when you add a JTS geometry on the Canvas, the method returns you a special object that represents this geometry on a map: CanvasLayer.Point, CanvasLayer.Polyline or CanvasLayer.Polygon. Using this object you can set multiple geometry parameters using fluent API, subscribe to geometry-related events, or use this object when you want to remove the geometry from the Canvas.

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

    Drawing geometries

    In Charts and Maps there was an auxiliary drawing component — DrawingOptions. It was used to enable drawing capabilities in a map. After a geometry was drawn the corresponding event was fired:

    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
    });

    Jmix is an open-source platform for building enterprise applications in Java