Skip to content

Clustered Data

MapServer can combine point features together into clusters based on their location, using the CLUSTER directive.

In our example points representing trees are clustered together, with the number of points in each cluster used as the label.

Mapfile Notes

The Mapfile contains a class for clustered features, and a class for non-clustered features. If there is no expression in the CLASS then it will be applied to all features. A feature is checked against each CLASS until a match is found, from first to last. If you want to add a "catch-all" CLASS then add it last in the Mapfile without an EXPRESSION.

# class for clustered features
CLASS
    EXPRESSION ("[Cluster_FeatureCount]" != "1")
    ...
END

# add a class for non-clustered features
CLASS
    ...

Code

clusters.js
import '../css/style.css';
import ImageWMS from 'ol/source/ImageWMS.js';
import Map from 'ol/Map.js';
import OSM from 'ol/source/OSM.js';
import View from 'ol/View.js';
import { Image as ImageLayer, Tile as TileLayer } from 'ol/layer.js';

const mapserverUrl = import.meta.env.VITE_MAPSERVER_BASE_URL;
const mapfilesPath = import.meta.env.VITE_MAPFILES_PATH;

const layers = [
    new TileLayer({
        source: new OSM(),
    }),
    new ImageLayer({
        extent: [2968743.65508978, 8038921.67212233, 2982981.8632402, 8053818.05714347],
        source: new ImageWMS({
            url: mapserverUrl + mapfilesPath + 'clusters.map&',
            params: { 'LAYERS': 'trees', 'STYLES': '' },
            ratio: 1
        }),
    }),
];
const map = new Map({
    layers: layers,
    target: 'map',
    view: new View({
        center: [2975862.75916499, 8046369.8646329],
        zoom: 14,
    }),
});
clusters.map
MAP
    NAME "Clusters"
    EXTENT 26.668678 58.339241 26.796582 58.40941
    UNITS DD
    SIZE 800 600
    PROJECTION
        "init=epsg:4326"
    END
    FONTSET "data/fonts/fontset.txt"
    WEB
        METADATA
            "ows_enable_request" "*" 
            "ows_srs" "EPSG:4326 EPSG:3857" 
        END
    END
    SYMBOL
        NAME "circle"
        TYPE ELLIPSE
        POINTS
            1 1
        END
        FILLED TRUE
    END
    LAYER
        NAME "trees"
        STATUS OFF
        TYPE POINT
        CONNECTIONTYPE OGR 
        # cluster does not seem to work with the native FLATGEOBUF driver
        # CONNECTIONTYPE FLATGEOBUF
        # DATA "data/osm/natural.fgb"

        CONNECTION "data/osm/natural.fgb"
        CLUSTER
            MAXDISTANCE 20
            REGION "ellipse"
        END

        # LABELITEM "Cluster_FeatureCount"

        # PROCESSING "CLUSTER_ALGORITHM=SIMPLE"
        # PROCESSING "CLUSTER_GET_ALL_SHAPES=OFF"
        # PROCESSING "CLUSTER_KEEP_LOCATIONS=OFF"

        CLASSITEM "Cluster_FeatureCount"

        # class for clustered features
        CLASS
            EXPRESSION ("[Cluster_FeatureCount]" != "1")
            STYLE
                SIZE 30
                # In MapServer 8.2 we can use an expression for SIZE
                # SIZE ([Cluster_FeatureCount] / 3)
                SYMBOL "circle"
                COLOR "#4A993A"
            END
            LABEL
                FONT "LiberationSans"
                TEXT "[Cluster_FeatureCount]"
                TYPE TRUETYPE
                SIZE 12
                COLOR 255 255 255
                ALIGN CENTER
                FORCE TRUE # otherwise numbers can disappear
            END
        END

        # add a class for non-clustered features
        CLASS
            STYLE
                SIZE 20
                SYMBOL "circle"
                COLOR "#4A993A"
            END
            LABEL
                FONT "LiberationSans"
                TEXT "1"
                TYPE TRUETYPE
                SIZE 10
                COLOR 255 255 255
                ALIGN CENTER
            END
        END

    END
END

Exercises

  1. Try changing the MAXDISTANCE and REGION parameters to see the effect this has on clustering.

    CLUSTER
        MAXDISTANCE 50
        REGION "ellipse"
    END