Search code examples
openlayers

Assertion error while trying to cluster multigeometry features


I have a very big KML file (8MB | 8300+ features) and I need a way to load it offline. After some research I liked the clusterization method, using OpenLayers Cluster, but it only works for points:

Layer source to cluster vector data. Works out of the box with point geometries. For other geometry types, or if not all geometries should be considered for clustering, a custom geometryFunction can be defined.

If I try to use the method with the polygons nothing shows up. So, I tried to 'transform' the features in points while zoom is out and and show these features as a point cluster, then revert to original geometry when zoom is close enough. I found some very similar problem that I relied on. But now I get an error that I didn't receive before when applying the method without converting to points:

AssertionError {
    code: 10, 
    name: "AssertionError", 
    message: "Assertion failed. See https://openlayers.org/en/v5.3.3/doc/errors/#10 for details."
}

Error code #10:

The default geometryFunction can only handle ol/geom/Point geometries.

I found it strange to receive this error, because in the problem link I mentioned the OP does the same procedure and does not receive any error.


Load layer function

// Create layer and sources
let newVectorSource = new VectorSource({})
let newVectorLayer = new VectorLayer({
    source: new Cluster({
        distance: 50,
        source: newVectorSource,
    }),
    name: layer['layer_id'],
    visible: false
});

// Read file and add to map
this.file.readAsText(path, filename).then(layer_file => {
    let format = new KML({});                                   
    newVectorSource.addFeatures(format.readFeatures(layer_file, {
        featureProjection:"EPSG:3857",
        dataProjection: "EPSG:4326"
    }));
    this.map.addLayer(newVectorLayer);

    for (let i = newVectorSource.getFeatures().length - 1; i >= 0; i--) {
        /* Create style function to polygons act like points, 
            so we can show them like clusters
        */

        let stroke = new Stroke({
            color: 'orange',
            width: 3
        });
        let fill = new Fill({
            color: 'rgba(255, 165, 0, 0.1)'
        })
    
        // Create geometry function
        let zoomInStyle = new Style({
            stroke: stroke,
            fill: fill,
            geometry: (feature) => {
                let originalFeature = newVectorSource.getFeatures()[i];
                return originalFeature.getGeometry();
            }
            
        });
        let zoomOutStyle = new Style({
            stroke: stroke,
            fill: fill,
            geometry: (feature) => {
                let type = feature.getGeometry().getType();
                if (type === 'Polygon') {
                    return feature.getGeometry().getInteriorPoint();
    
                } else if (type === 'LineString') {
                    // also tried feature.getClosestPoint();
                    return getCenter(feature.getGeometry().getExtent());
                }
            }                                   
        });
    
        let featureStyleFunction = (feature, res) => {
            if (res > 10) {
                return zoomOutStyle;                                      
            } else {
                return zoomInStyle;
            }                                   
        };
    
        // Set new style to feature
        newVectorSource.getFeatures()[i].setStyle(featureStyleFunction);                                    
    }
});

EDIT Cache style

let styleCache = {};
let newVectorLayerCluster = new VectorLayer({
    source: new Cluster({
        distance: 100,
        source: newVectorSource,
        geometryFunction: (feature) => {
            let resolution = this.map.getView().getResolution();
            if (resolution > 10) {
                let type = feature.getGeometry().getType();
                if (type === 'Polygon') {
                    return feature.getGeometry().getInteriorPoint();

                } else if (type === 'LineString') {
                    return feature.getGeometry().getCoordinateAt(0.5);
                }
            }
        }
    }),
    style: (feature) => {
        let size = feature.get('features').length;
        let style = styleCache[size];
        if (!style) {
            style = new Style({
                image: new CircleStyle({
                    radius: 10,
                    stroke: new Stroke({
                        color: '#fff',
                    }),
                    fill: new Fill({
                        color: '#3399CC',
                    }),
                }),
                text: new Text({
                    text: size.toString(),
                    fill: new Fill({
                        color: '#fff',
                    }),
                })
            });
            styleCache[size] = style;
        }
        return style;
    },
    name: layer['layer_id'],
    visible: false
});

Solution

  • The geometryFunction the documentation and the linked question refer to is an option in the cluster source

    source: new Cluster({
        distance: 50,
        source: newVectorSource,
        geometryFunction: function(feature) {
            let type = feature.getGeometry().getType();
            if (type === 'Polygon') {
                return feature.getGeometry().getInteriorPoint();
            } else if (type === 'LineString') {
                return feature.getGeometry().getCoordinateAt(0.5);
            }
        },
    }),
    

    The midpoint (label point) of a linestring may be more convenient than the extent center