Search code examples
javageolocationgeocodinggeotools

Geotools: Add points with different styles to same map layer


I'm using GeoTools in my java project to draw points in different sizes, color and opacity into a map. As there are a lot of different styles for them, I ended up with thousands of layers in my map. Currently each added point has his own layer cause I did not manage to add them to one single layer.

I'm getting the points from my Entity object. Every point from the same entity has the same color. There are more entities with the same name/color.

This is how one entity should be drawn on the map:

enter image description here

The size of the points is calculated dynamically in the following code snippet:

public class Stackoverflow {

private final MapContent mapContent;

public Stackoverflow() {
    this.mapContent = new MapContent();
}

public void addPoints(final HashMap<String, Color> colorsForEntities, final List<Entity> toDraw){
    final GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
    // Create SimpleFeatureType for Point
    final SimpleFeatureTypeBuilder pointTb = new SimpleFeatureTypeBuilder();
    pointTb.setName("points");
    pointTb.setCRS(DefaultGeographicCRS.WGS84);
    pointTb.add("point", Point.class);
    final SimpleFeatureBuilder pointFeatureBuilder = new SimpleFeatureBuilder(pointTb.buildFeatureType());

    for (final Entity entity : toDraw) {

        final List<Point2D> pathPoints = entity.getPoints(); 

        Color color = colorsForEntities.get(entity.getName());

        Layer layerPoints;
        Style pointStyle;


        final float maxOpacity = MyProperties.getFloat(Constants.POINT_STYLE_OPACITY);
        final float maxSize = MyProperties.getFloat(Constants.POINT_STYLE_SIZE);

        final int NumPoints = pathPoints.size();
        float opacity = maxOpacity;
        float size = maxSize;

        final float deltaOpacity = maxOpacity / NumPoints;
        final float deltaSize = maxSize / NumPoints;

        for (final Point2D point2D : pathPoints) {
            final Point point = geometryFactory.createPoint(new Coordinate(point2D.getX(), point2D.getY()));
            pointFeatureBuilder.add(point);

            final SimpleFeature feature = pointFeatureBuilder.buildFeature(null);
            final DefaultFeatureCollection pointCollection = new DefaultFeatureCollection();
            pointCollection.add(feature);

            pointStyle = SLD.createPointStyle("Circle", color, color, opacity, size);

            opacity = opacity - deltaOpacity;
            size = size - deltaSize;
            layerPoints = new FeatureLayer(pointCollection, pointStyle);
            mapContent.addLayer(layerPoints);
        }

    }
}

}

Is it possible to add the points to one single layer or at least getting much smaller amount of layers?


Solution

  • It can be done more efficiently :-) I think the easiest way would be to build your SimpleFeatures from the entitys so that they contain the color, size and opacity required. Something like:

    final SimpleFeatureTypeBuilder pointTb = new SimpleFeatureTypeBuilder();
    pointTb.setName("points");
    pointTb.setCRS(DefaultGeographicCRS.WGS84);
    pointTb.add("point", Point.class);
    pointTb.add("color", String.class);
    pointTb.add("size", Integer.class);
    pointTb.add("opacity", Float.class);
    final SimpleFeatureBuilder pointFeatureBuilder = new SimpleFeatureBuilder(pointTb.buildFeatureType());
    

    Then you can create a single Style that takes those properties and uses them to style each point. Something like (untested):

    StyleBuilder sb = new StyleBuilder();
    FilterFactory2 ff = sb.getFilterFactory();
    
    Mark testMark = sb.createMark(sb.literalExpression("Circle"), 
        sb.createFill(sb.attributeExpression("color"),sb.attributeExpression("opacity")),
            null);
    Graphic graph = sb.createGraphic(null, // An external graphics if needed
            new Mark[] { testMark }, // a Mark if not an external graphics
            null, // aSymbol
            ff.literal(sb.attributeExpression("opacity")), // opacity
            ff.property("size"), // read from feature "size" attribute
            ff.literal(0)); // rotation, here read into the feature
    PointSymbolizer aPointSymbolizer = sb.createPointSymbolizer(graph);
    
    // creation of the style
    org.geotools.styling.Style style = sb.createStyle(aPointSymbolizer);
    

    Since SLD doesn't provide any support for looping, I don't think there is any way to avoid having to break each entity into a set of individual points with their own styling, unless you want to write a custom mark factory.