Clustered Markers

This tutorial will show you how to add a clustered marker layer.

Clustered Markers

Clustered Markers

A marker layer represents points of interest with an image or label. A clustered marker layer is designed to handle a very large number of these by automatically clustering them based on an weight assigned to each one. The higher the weight value, the more important that area of interest.

The data for each marker consists of:

  • Geographic position (lat, lon, altidude)
  • Image
  • Image anchor point (which pixel of the image should fall on the geographic position)
  • Weight
  • Rotation

In this tutorial we'll walk you through how to create a clustered marker layer with thousands or randomly dispersed markers with varying weights that are automatically clustered by the mapping engine.

Texture Creation
//Creates a square image with the given dimensions and colors
function squareImage(width, height, red, green, blue, alpha){
    var arr = new AltusUnified.VectorByte();
    for (i = 0; i < width * height; i += 1) {
        arr.push_back(red);
        arr.push_back(green);
        arr.push_back(blue);
        arr.push_back(alpha);
    }
    var image = new AltusUnified.Image(width, height, arr);
    arr.delete();
    return image;
}
//Converts an image to a texture
function textureFromImage(image){
    var tex = new AltusUnified.Texture(image, false);
    return tex;
}
//Returns a square texture with the given dimensions and colors
function squareTexture(width, height, red, green, blue, alpha){
    var image = squareImage(width, height, red, green, blue, alpha);
    var tex = textureFromImage(image);
    image.delete();
    return tex;
}
MarkerData Creation
//Returns a populated MarkerData object
function createMarkerData(uid, lat, lon, alt, weight, minLevel, metaData, texture, anchorX, anchorY){
    var markerData = new AltusUnified.MarkerData(uid, lat, lon, alt, weight, metaData, texture);
    markerData.minimumLevel = minLevel;
    var anchorPoint = new AltusUnified.vec2d(anchorX, anchorY);
    markerData.anchorPoint_set(anchorPoint);
    anchorPoint.delete();
    return markerData;
}
ClusteredMarkerMap Creation
//Returns a clustered marker map object
function createClusteredMarkerMap(name, markers, clusterDistance, maxLevel, targetImageFormat, hitTestingEnabled){
    var markerMap = new AltusUnified.ClusteredMarkerMap(name, markers, clusterDistance, maxLevel, targetImageFormat, hitTestingEnabled);
    return markerMap;
}
Creating Lots of Markers
//Creates markers with random locations and weights. Returns them in an AltusUnified.VectorMarkerData object.
function createRandomMarkers(markerCount, markerWidth, markerHeight){

    //Extents markers will appear on planet
    var minX = -180;
    var maxX = 180;
    var maxY = 80;
    var minY = -80;

    //Create some color square textures for markers
    var redTexture = squareTexture(markerWidth, markerHeight, 255, 0, 0, 255);
    var greenTexture = squareTexture(markerWidth, markerHeight, 0, 255, 0, 255);
    var blueTexture = squareTexture(markerWidth, markerHeight, 0, 0, 255, 255);

    //Create a vector of markers we'll be returning
    var markers = new AltusUnified.VectorMarkerData();

    for(i = 0; i<markerCount; i++){
        var lon = minX + Math.random() * (maxX-minX);
        var lat = minY + Math.random() * (maxY-minY);
        var alt = 0;
        var weight=Math.random() * 100;
        var minLevel=0;
        var metaData="Marker "+i;
        var texture = blueTexture;
        if(weight>33) texture = greenTexture;
        if(weight>66) texture = redTexture;
        var marker;
        if(weight>70){
            marker = createMarkerData(i, lat, lon, alt, weight, minLevel, metaData, texture, markerWidth/2, markerHeight/2);
        }
        else{
            marker = createMarkerData(i, lat, lon, alt, weight, minLevel, metaData, texture, markerWidth/2, markerHeight/2);
        }
        markers.push_back(marker);
        marker.delete();
    }

    //Clean up
    redTexture.delete();
    greenTexture.delete();
    blueTexture.delete();

    return markers;
}
Adding the Layer
//Adds a layer of randomly place markers represented by colored squares
function addRandomClusteredMarkerLayer(layerName, markerCount, zOrder){

    console.log("Adding " + layerName + " with " + markerCount + " markers.");
    //Variables to conveniently change the clustered marker layer
    var markerWidth = 16;
    var markerHeight = 16;
    var maxLevel = 20;
    var clusterDistance = 30;

    //Prototype for a marker map delegate
    var StringMarkerMapDelegate = AltusUnified.IMarkerMapDelegate.extend("IMarkerMapDelegate", {

        //Called if user clicks or taps a marker
        onMarkerTapped: function (marker, screenPoint, markerPoint, mapName) {
            console.log("onMarkerTapped: " + marker.metadata);
        },

        //Called if user clicks or taps a marker
        onMarkersTapped: function (markerHits) {
        },

        //Called if a marker image is needed (i.e. one is not supplied with the marker data)
        getMarkerImage: function (markerInfo, mapName) {
            if (markerInfo.textureCreatedOnMainThread == null) {
                var labelText = markerInfo.markerData().metadata;
                var fontSize = 19;
                var fontName = "Arial, Helvetica, sans-serif";
                var fontBold = false;
                addTextLabelToMarker(markerInfo, fontSize, fontName, fontBold, labelText)
            }
        },
        phrase : "phrase"
    });

    //Create a set of markers
    var markers = createRandomMarkers(markerCount, markerWidth, markerHeight);

    //Create the clustered marker layer
    var markerMap = createClusteredMarkerMap(layerName, markers, clusterDistance, maxLevel, AltusUnified.TargetImageFormat.FOUR_BPP, false);

    //Create and assign the delegate
    var delegate = new StringMarkerMapDelegate();
    delegate.phrase = "marker";
    markerMap.setDelegate(delegate);

    //Enable hit testing
    markerMap.setHitTestingEnabled(true);

    //Add the clustered marker map layer
    AltusUnified.scene.addMap(markerMap);

    //Set the z order of the layer above everything else
    markerMap.setOrder(zOrder);

    //Clean up
    markers.delete();
    markerMap.delete();
    delegate.delete();
    console.log("done");
}

Putting It All Together

After Altus is initialized you can then create the clustered marker layer like this:
 //Called by the mapping engine after it has initialized
        function onAltusEngineReady() {

            //Make the planet always lit from the observer's point of view
            sunLocationOffset();
            setSunLocation(0,0);

            //Turn on the stars
            setStarsEnabled(true);

            //Posiitont he camer over the US.
            setCameraPosition(39, -98, 7000000);

            //Show place labels
            addPlacesClusteredMarkerLayer();

            //Add an aerial map layer
            setMapBoxAerialBaseMap();

            //Enable UI
            document.getElementById("clusterMarkersTypeGroup").disabled = false;

        };

        var squaresLayerName = "RandomSquares";
        var placesLayerName = "Places";

        function removeIfLoaded(layerName){
             if(mapIsLoaded(layerName)){
                removeMap(layerName);
            }
        }

        function removeSquareLayers(count){
             for(i=0; i<count; i++){
                removeIfLoaded(squaresLayerName+i);
             }
        }

        function showPlaces(){
            removeSquareLayers(10);
            if(!mapIsLoaded(placesLayerName)) addPlacesClusteredMarkerLayer();
        }

        function showRandomSquares(){
            if(mapIsLoaded(placesLayerName)) removeMap(placesLayerName);
            if(!mapIsLoaded(squaresLayerName+0)){
                addRandomClusteredMarkerLayer(squaresLayerName+0, 10000, 300);
            }
        }

        function showBoth(){
            if(!mapIsLoaded(placesLayerName)) addPlacesClusteredMarkerLayer();
            if(!mapIsLoaded(squaresLayerName)) addRandomClusteredMarkerLayer();
        }

        function showTenRandomSquaresLayers(){
            if(mapIsLoaded(placesLayerName)){
                removeMap(placesLayerName);
            }

            for(var i=0; i<10; i++){
                if(!mapIsLoaded(squaresLayerName+i)){
                    addRandomClusteredMarkerLayer(squaresLayerName+i, 10000, 300+i);
                }
            }
        }

See Demo


AltusMappingEngine Web v2.0.ut.2084.g47ffcd3 master

COPYRIGHT (C) 2017, BA3, LLC ALL RIGHTS RESERVED