Cesium 加载GEOJSON

本文探讨了在三维场景中叠加大量点位数据的需求,介绍了如何利用Cesium创建GEOJSON图层,并改造Cesium的PrimitiveCluster类以实现动态聚合效果,提升视图流畅性。尽管每个图层点位数量不超过10万,但未进行数据切割处理。

#原因
目前涉及在三维上叠加大量点位数据,考虑到使用场景不一样,同时结合以前Arcgis与Mapboxgl使用习惯顺便写了两个类
#思考
一个GEOJSON为一个图层(GrahicLayer),同时改造Cesium的聚合类(PrimitiveCluster,仅对点作了处理),实现放大小缩小时自动聚合显示提高流畅度,因为本人目前使用的每个图层点位不超过10万,因此没有对数据进行处理切割处理。

/**
 * Defines how screen space objects (billboards, points, labels) are clustered.
 *
 * @param {Object} [options] An object with the following properties:
 * @param {Boolean} [options.enabled=false] Whether or not to enable clustering.
 * @param {Number} [options.pixelRange=80] The pixel range to extend the screen space bounding box.
 * @param {Number} [options.minimumClusterSize=2] The minimum number of screen space objects that can be clustered.
 * @param {Boolean} [options.clusterBillboards=true] Whether or not to cluster the billboards of an entity.
 * @param {Boolean} [options.clusterLabels=true] Whether or not to cluster the labels of an entity.
 * @param {Boolean} [options.clusterPoints=true] Whether or not to cluster the points of an entity.
 * @param {Boolean} [options.show=true] Determines if the entities in the cluster will be shown.
 *
 * @alias PrimitiveCluster
 * @constructor
 *
 * @demo {@link https://sandcastle.cesium.com/index.html?src=Clustering.html|Cesium Sandcastle Clustering Demo}
 */
function PrimitiveCluster(options) {
    options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT);

    this._enabled = Cesium.defaultValue(options.enabled, false);
    this._pixelRange = Cesium.defaultValue(options.pixelRange, 80);
    this._minimumClusterSize = Cesium.defaultValue(options.minimumClusterSize, 2);
    this._clusterBillboards = Cesium.defaultValue(options.clusterBillboards, true);
    this._clusterLabels = Cesium.defaultValue(options.clusterLabels, true);
    this._clusterPoints = Cesium.defaultValue(options.clusterPoints, true);

    this._labelCollection = undefined;
    this._billboardCollection = undefined;
    this._pointCollection = undefined;

    this._clusterBillboardCollection = undefined;
    this._clusterLabelCollection = undefined;
    this._clusterPointCollection = undefined;

    this._collectionIndicesByEntity = {};

    this._unusedLabelIndices = [];
    this._unusedBillboardIndices = [];
    this._unusedPointIndices = [];

    this._previousClusters = [];
    this._previousHeight = undefined;

    this._enabledDirty = false;
    this._clusterDirty = false;

    this._cluster = undefined;
    this._removeEventListener = undefined;

    this._clusterEvent = new Cesium.Event();

    /**
     * Determines if entities in this collection will be shown.
     *
     * @type {Boolean}
     * @default true
     */
    this.show = Cesium.defaultValue(options.show, true);
}

function getX(point) {
    return point.coord.x;
}

function getY(point) {
    return point.coord.y;
}

function expandBoundingBox(bbox, pixelRange) {
    bbox.x -= pixelRange;
    bbox.y -= pixelRange;
    bbox.width += pixelRange * 2.0;
    bbox.height += pixelRange * 2.0;
}

var labelBoundingBoxScratch = new Cesium.BoundingRectangle();

function getBoundingBox(item, coord, pixelRange, entityCluster, result) {
    if (item &&
        Cesium.defined(item._billboardCollection) &&
        entityCluster._clusterBillboards
    ) {
        result = Cesium.Billboard.getScreenSpaceBoundingBox(item, coord, result);
    }

    expandBoundingBox(result, pixelRange);
    return result;
}

function addNonClusteredItem(item, entityCluster) {
    item.clusterShow = true;

    if (!Cesium.defined(item._labelCollection) &&
        Cesium.defined(item.id) &&
        hasLabelIndex(entityCluster, item.id.id) &&
        Cesium.defined(item.id._label)
    ) {
        var labelIndex =
            entityCluster._collectionIndicesByEntity[item.id.id].labelIndex;
        var label = entityCluster._labelCollection.get(labelIndex);
        label.clusterShow = true;
    }
}

function addCluster(position, numPoints, ids, entityCluster) {
    var cluster = {
        billboard: entityCluster._clusterBillboardCollection.add(),
        label: entityCluster._clusterLabelCollection.add(),
        // point: entityCluster._clusterPointCollection.add(),
    };

    cluster.billboard.show = true;
    cluster.billboard.count = numPoints
        //cluster.billboard.position = position
    cluster.billboard.disableDepthTestDistance = Number.POSITIVE_INFINITY;
    cluster.billboard.pixelOffset = new Cesium.Cartesian2(0.0, -20.0)
    cluster.billboard.image = entityCluster.bpts.get(0).image
    cluster.billboard.height = entityCluster.bpts.get(0).height
    cluster.billboard.width = entityCluster.bpts.get(0).width
    ids[0].data.ids = ids;
    cluster.billboard.id = ids[0];
    // cluster.point.show = false;
    cluster.label.show = true;
    cluster.label.showBackground = true
    cluster.label.backgroundColor = new Cesium.Color(1, 1, 1, 0.8)
    cluster.label.fillColor = Cesium.Color.GREEN;
    cluster.label.font = "12px monospace"
    cluster.label.horizontalOrigin = Cesium.HorizontalOrigin.CENTER
    cluster.label.verticalOrigin = Cesium.VerticalOrigin.BOTTOM
    cluster.label.pixelOffset = new Cesium.Cartesian2(0.0, -40.0)
    cluster.label.text = ids[0].data.name;
    cluster.label.id = ids[0];
    cluster.billboard.position = cluster.label.position = position //= cluster.point.position = position;


}

function hasLabelIndex(entityCluster, entityId) {
    return (
        Cesium.defined(entityCluster) &&
        Cesium.defined(entityCluster._collectionIndicesByEntity[entityId]) &&
        Cesium.defined(entityCluster._collectionIndicesByEntity[entityId].labelIndex)
    );
}

function getScreenSpacePositions(
    collection,
    points,
    scene,
    occluder,
    entityCluster
) {
    if (!Cesium.defined(collection)) {
        return;
    }

    var length = collection.length;
    for (var i = 0; i < length; ++i) {
        var item = collection.get(i);
        item.clusterShow = false;

        if (!item.show ||
            (entityCluster._scene.mode === Cesium.SceneMode.SCENE3D &&
                !occluder.isPointVisible(item.position))
        ) {
            continue;
        }

        var canClusterLabels =
            entityCluster._clusterLabels && Cesium.defined(item._labelCollection);
        var canClusterBillboards =
            entityCluster._clusterBillboards && Cesium.defined(item);
        var canClusterPoints =
            entityCluster._clusterPoints && Cesium.defined(item._point);
        if (canClusterLabels && (canClusterPoints || canClusterBillboards)) {
            continue;
        }

        var coord = item.computeScreenSpacePosition(scene);
        if (!Cesium.defined(coord)) {
            continue;
        }

        points.push({
            index: i,
            collection: collection,
            clustered: false,
            coord: coord,
        });
    }
}

var pointBoundinRectangleScratch = new Cesium.BoundingRectangle();
var totalBoundingRectangleScratch = new Cesium.BoundingRectangle();
var neighborBoundingRectangleScratch = new Cesium.BoundingRectangle();

function createDeclutterCallback(entityCluster) {

    return async function(amount) {
        entityCluster._clusterEvent.raiseEvent({});
        if (!entityCluster.show) return;
        var scene = entityCluster._scene;

        var labelCollection = entityCluster._labelCollection;
        var billboardCollection = entityCluster._billboardCollection;
        var pointCollection = entityCluster._pointCollection;

        if (
            (!Cesium.defined(labelCollection) &&
                !Cesium.defined(billboardCollection) &&
                !Cesium.defined(pointCollection)) ||
            (!entityCluster._clusterBillboards &&
                !entityCluster._clusterLabels &&
                !entityCluster._clusterPoints)
        ) {
            return;
        }

        var clusteredLabelCollection = entityCluster._clusterLabelCollection;
        var clusteredBillboardCollection =
            entityCluster._clusterBillboardCollection;
        // var clusteredPointCollection = entityCluster._clusterPointCollection;

        if (Cesium.defined(clusteredLabelCollection)) {
            clusteredLabelCollection.removeAll();
        } else {
            clusteredLabelCollection = entityCluster._clusterLabelCollection = new Cesium.LabelCollection({
                scene: scene,
            });
        }

        if (Cesium.defined(clusteredBillboardCollection)) {
            clusteredBillboardCollection.removeAll();
        } else {
            clusteredBillboardCollection = entityCluster._clusterBillboardCollection = new Cesium.BillboardCollection({
                scene: scene,
            });
        }
        var pixelRange = entityCluster._pixelRange;
        var minimumClusterSize = entityCluster._minimumClusterSize;

        var clusters = entityCluster._previousClusters;
        var newClusters = [];

        var previousHeight = entityCluster._previousHeight;
        var currentHeight = scene.camera.positionCartographic.height;

        var ellipsoid = scene.mapProjection.ellipsoid;
        var cameraPosition = scene.camera.positionWC;
        var occluder = new Cesium.EllipsoidalOccluder(ellipsoid, cameraPosition);

        var points = [];

        if (entityCluster._clusterBillboards) {
            getScreenSpacePositions(
                billboardCollection,
                points,
                scene,
                occluder,
                entityCluster
            );
        }

        var i;
        var j;
        var length;
        var bbox;
        var neighbors;
        var neighborLength;
        var neighborIndex;
        var neighborPoint;
        var ids;
        var numPoints;

        var collection;
        var collectionIndex;

        var index = new Cesium.kdbush(points, getX, getY, 64, Int32Array);

        if (currentHeight < previousHeight) {
            length = clusters.length;
            for (i = 0; i < length; ++i) {
                var cluster = clusters[i];

                if (!occluder.isPointVisible(cluster.position)) {
                    continue;
                }

                var coord = Cesium.Billboard._computeScreenSpacePosition(
                    Cesium.Matrix4.IDENTITY,
                    cluster.position,
                    Cesium.Cartesian3.ZERO,
                    Cesium.Cartesian2.ZERO,
                    scene
                );
                if (!Cesium.defined(coord)) {
                    continue;
                }

                var factor = 1.0 - currentHeight / previousHeight;
                var width = (cluster.width = cluster.width * factor);
                var height = (cluster.height = cluster.height * factor);

                width = Math.max(width, cluster.minimumWidth);
                height = Math.max(height, cluster.minimumHeight);

                var minX = coord.x - width * 0.5;
                var minY = coord.y - height * 0.5;
                var maxX = coord.x + width;
                var maxY = coord.y + height;

                neighbors = index.range(minX, minY, maxX, maxY);
                neighborLength = neighbors.length;
                numPoints = 0;
                ids = [];

                for (j = 0; j < neighborLength; ++j) {
                    neighborIndex = neighbors[j];
                    neighborPoint = points[neighborIndex];
                    if (!neighborPoint.clustered) {
                        ++numPoints;

                        collection = neighborPoint.collection;
                        collectionIndex = neighborPoint.index;
                        ids.push(collection.get(collectionIndex).id);
                    }
                }

                if (numPoints >= minimumClusterSize) {
                    addCluster(cluster.position, numPoints, ids, entityCluster);
                    newClusters.push(cluster);

                    for (j = 0; j < neighborLength; ++j) {
                        points[neighbors[j]].clustered = true;
                    }
                }
            }
        }

        length = points.length;
        for (i = 0; i < length; ++i) {
            var point = points[i];
            if (point.clustered) {
                continue;
            }

            point.clustered = true;

            collection = point.collection;
            collectionIndex = point.index;

            var item = entityCluster.bpts.get(collectionIndex);
            // let _item = entityCluster.bpts.get(collectionIndex);
            bbox = getBoundingBox(
                item,
                point.coord,
                pixelRange,
                entityCluster,
                pointBoundinRectangleScratch
            );
            var totalBBox = Cesium.BoundingRectangle.clone(
                bbox,
                totalBoundingRectangleScratch
            );

            neighbors = index.range(
                bbox.x,
                bbox.y,
                bbox.x + bbox.width,
                bbox.y + bbox.height
            );
            neighborLength = neighbors.length;

            var clusterPosition = Cesium.Cartesian3.clone(item.position);
            numPoints = 1;
            ids = [item.id];

            for (j = 0; j < neighborLength; ++j) {
                neighborIndex = neighbors[j];
                neighborPoint = points[neighborIndex];
                if (!neighborPoint.clustered) {
                    var neighborItem = neighborPoint.collection.get(neighborPoint.index);
                    //  let _neighborItem = entityCluster.bpts.get(neighborPoint.index);
                    var neighborBBox = getBoundingBox(
                        neighborItem,
                        neighborPoint.coord,
                        pixelRange,
                        entityCluster,
                        neighborBoundingRectangleScratch
                    );

                    Cesium.Cartesian3.add(
                        neighborItem.position,
                        clusterPosition,
                        clusterPosition
                    );

                    Cesium.BoundingRectangle.union(totalBBox, neighborBBox, totalBBox);
                    ++numPoints;

                    ids.push(neighborItem);
                }
            }

            if (numPoints >= minimumClusterSize) {
                var position = Cesium.Cartesian3.multiplyByScalar(
                    clusterPosition,
                    1.0 / numPoints,
                    clusterPosition
                );
                addCluster(position, numPoints, ids, entityCluster);
                newClusters.push({
                    position: position,
                    width: totalBBox.width,
                    height: totalBBox.height,
                    minimumWidth: bbox.width,
                    minimumHeight: bbox.height,
                });

                for (j = 0; j < neighborLength; ++j) {
                    points[neighbors[j]].clustered = true;
                }
            } else {
                addNonClusteredItem(item, entityCluster);
            }
        }

        if (clusteredBillboardCollection.length === 0) {
            clusteredBillboardCollection.destroy();
            entityCluster._clusterBillboardCollection = undefined;
        }

        entityCluster._previousClusters = newClusters;
        entityCluster._previousHeight = currentHeight;
        entityCluster._clusterEvent.raiseEvent(entityCluster);
    };
}

PrimitiveCluster.prototype._initialize = function(scene, minzoom, pts) {
    this._scene = scene;
    this.minzoom = minzoom
    this.bpts = pts
    var cluster = createDeclutterCallback(this);
    this._cluster = cluster;
    this.handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
    this.handler.setInputAction((e) => {
        cluster();
    }, Cesium.ScreenSpaceEventType.WHEEL);
    //this._removeEventListener = scene.camera.changed.addEventListener(cluster);
};
PrimitiveCluster.prototype.clear = function() {
    if (this._billboardCollection)
        this._billboardCollection.removeAll();
    this._collectionIndicesByEntity = {}
}

Object.defineProperties(PrimitiveCluster.prototype, {
    /**
     * Gets or sets whether clustering is enabled.
     * @memberof PrimitiveCluster.prototype
     * @type {Boolean}
     */
    enabled: {
        get: function() {
            return this._enabled;
        },
        set: function(value) {
            this._enabledDirty = value !== this._enabled;
            this._enabled = value;
        },
    },
    /**
     * Gets or sets the pixel range to extend the screen space bounding box.
     * @memberof PrimitiveCluster.prototype
     * @type {Number}
     */
    pixelRange: {
        get: function() {
            return this._pixelRange;
        },
        set: function(value) {
            this._clusterDirty = this._clusterDirty || value !== this._pixelRange;
            this._pixelRange = value;
        },
    },
    /**
     * Gets or sets the minimum number of screen space objects that can be clustered.
     * @memberof PrimitiveCluster.prototype
     * @type {Number}
     */
    minimumClusterSize: {
        get: function() {
            return this._minimumClusterSize;
        },
        set: function(value) {
            this._clusterDirty =
                this._clusterDirty || value !== this._minimumClusterSize;
            this._minimumClusterSize = value;
        },
    },
    /**
     * Gets the event that will be raised when a new cluster will be displayed. The signature of the event listener is {@link PrimitiveCluster.newClusterCallback}.
     * @memberof PrimitiveCluster.prototype
     * @type {Event}
     */
    clusterEvent: {
        get: function() {
            return this._clusterEvent;
        },
    },
    /**
     * Gets or sets whether clustering billboard entities is enabled.
     * @memberof PrimitiveCluster.prototype
     * @type {Boolean}
     */
    clusterBillboards: {
        get: function() {
            return this._clusterBillboards;
        },
        set: function(value) {
            this._clusterDirty =
                this._clusterDirty || value !== this._clusterBillboards;
            this._clusterBillboards = value;
        },
    },
    /**
     * Gets or sets whether clustering labels entities is enabled.
     * @memberof PrimitiveCluster.prototype
     * @type {Boolean}
     */
    clusterLabels: {
        get: function() {
            return this._clusterLabels;
        },
        set: function(value) {
            this._clusterDirty = this._clusterDirty || value !== this._clusterLabels;
            this._clusterLabels = value;
        },
    },
    /**
     * Gets or sets whether clustering point entities is enabled.
     * @memberof PrimitiveCluster.prototype
     * @type {Boolean}
     */
    clusterPoints: {
        get: function() {
            return this._clusterPoints;
        },
        set: function(value) {
            this._clusterDirty = this._clusterDirty || value !== this._clusterPoints;
            this._clusterPoints = value;
        },
    },
});

function createGetEntity(
    collectionProperty,
    CollectionConstructor,
    unusedIndicesProperty,
    entityIndexProperty
) {
    return function(entity) {
        var collection = this[collectionProperty];

        if (!Cesium.defined(this._collectionIndicesByEntity)) {
            this._collectionIndicesByEntity = {};
        }

        var entityIndices = this._collectionIndicesByEntity[entity.id];

        if (!Cesium.defined(entityIndices)) {
            entityIndices = this._collectionIndicesByEntity[entity.id] = {
                billboardIndex: undefined,
                labelIndex: undefined,
                pointIndex: undefined,
            };
        }

        if (Cesium.defined(collection) && Cesium.defined(entityIndices[entityIndexProperty])) {
            return collection.get(entityIndices[entityIndexProperty]);
        }

        if (!Cesium.defined(collection)) {
            collection = this[collectionProperty] = new CollectionConstructor({
                scene: this._scene,
            });
        }

        var index;
        var entityItem;

        var unusedIndices = this[unusedIndicesProperty];
        if (unusedIndices.length > 0) {
            index = unusedIndices.pop();
            entityItem = collection.get(index);
        } else {
            entityItem = collection.add();
            // entityItem.point = entity.point;
            // entityItem.id = entity.id;
            // entityItem.image=entity.image
            // entityItem.data = entity.data;
            index = collection.length - 1;
        }

        entityIndices[entityIndexProperty] = index;

        this._clusterDirty = true;

        return entityItem;
    };
}

function removeEntityIndicesIfUnused(entityCluster, entityId) {
    var indices = entityCluster._collectionIndicesByEntity[entityId];

    if (!Cesium.defined(indices.billboardIndex) &&
        !Cesium.defined(indices.labelIndex) &&
        !Cesium.defined(indices.pointIndex)
    ) {
        delete entityCluster._collectionIndicesByEntity[entityId];
    }
}

/**
 * Returns a new {@link Label}.
 * @param {Entity} entity The entity that will use the returned {@link Label} for visualization.
 * @returns {Label} The label that will be used to visualize an entity.
 *
 * @private
 */
PrimitiveCluster.prototype.getLabel = createGetEntity(
    "_labelCollection",
    Cesium.LabelCollection,
    "_unusedLabelIndices",
    "labelIndex"
);

/**
 * Removes the {@link Label} associated with an entity so it can be reused by another entity.
 * @param {Entity} entity The entity that will uses the returned {@link Label} for visualization.
 *
 * @private
 */
PrimitiveCluster.prototype.removeLabel = function(entity) {
    var entityIndices =
        this._collectionIndicesByEntity &&
        this._collectionIndicesByEntity[entity.id];
    if (!Cesium.defined(this._labelCollection) ||
        !Cesium.defined(entityIndices) ||
        !Cesium.defined(entityIndices.labelIndex)
    ) {
        return;
    }

    var index = entityIndices.labelIndex;
    entityIndices.labelIndex = undefined;
    removeEntityIndicesIfUnused(this, entity.id);

    var label = this._labelCollection.get(index);
    label.show = false;
    label.text = "";
    label.id = undefined;

    this._unusedLabelIndices.push(index);

    this._clusterDirty = true;
};

/**
 * Returns a new {@link Billboard}.
 * @param {Entity} entity The entity that will use the returned {@link Billboard} for visualization.
 * @returns {Billboard} The label that will be used to visualize an entity.
 *
 * @private
 */
PrimitiveCluster.prototype.getBillboard = createGetEntity(
    "_billboardCollection",
    Cesium.BillboardCollection,
    "_unusedBillboardIndices",
    "billboardIndex"
);

/**
 * Removes the {@link Billboard} associated with an entity so it can be reused by another entity.
 * @param {Entity} entity The entity that will uses the returned {@link Billboard} for visualization.
 *
 * @private
 */
PrimitiveCluster.prototype.removeBillboard = function(entity) {
    var entityIndices =
        this._collectionIndicesByEntity &&
        this._collectionIndicesByEntity[entity.id];
    if (!Cesium.defined(this._billboardCollection) ||
        !Cesium.defined(entityIndices) ||
        !Cesium.defined(entityIndices.billboardIndex)
    ) {
        return;
    }

    var index = entityIndices.billboardIndex;
    entityIndices.billboardIndex = undefined;
    removeEntityIndicesIfUnused(this, entity.id);

    var billboard = this._billboardCollection.get(index);
    billboard.id = undefined;
    billboard.show = false;
    billboard.image = undefined;

    this._unusedBillboardIndices.push(index);

    this._clusterDirty = true;
};

/**
 * Returns a new {@link Point}.
 * @param {Entity} entity The entity that will use the returned {@link Point} for visualization.
 * @returns {Point} The label that will be used to visualize an entity.
 *
 * @private
 */
PrimitiveCluster.prototype.getPoint = createGetEntity(
    "_pointCollection",
    Cesium.PointPrimitiveCollection,
    "_unusedPointIndices",
    "pointIndex"
);

/**
 * Removes the {@link Point} associated with an entity so it can be reused by another entity.
 * @param {Entity} entity The entity that will uses the returned {@link Point} for visualization.
 *
 * @private
 */
PrimitiveCluster.prototype.removePoint = function(entity) {
    var entityIndices =
        this._collectionIndicesByEntity &&
        this._collectionIndicesByEntity[entity.id];
    if (!Cesium.defined(this._pointCollection) ||
        !Cesium.defined(entityIndices) ||
        !Cesium.defined(entityIndices.pointIndex)
    ) {
        return;
    }

    var index = entityIndices.pointIndex;
    entityIndices.pointIndex = undefined;
    removeEntityIndicesIfUnused(this, entity.id);

    var point = this._pointCollection.get(index);
    point.show = false;
    point.id = undefined;

    this._unusedPointIndices.push(index);

    this._clusterDirty = true;
};

function disableCollectionClustering(collection) {
    if (!Cesium.defined(collection)) {
        return;
    }

    var length = collection.length;
    for (var i = 0; i < length; ++i) {
        collection.get(i).clusterShow = true;
    }
}

function updateEnable(entityCluster) {
    if (entityCluster.enabled) {
        return;
    }

    if (Cesium.defined(entityCluster._clusterLabelCollection)) {
        entityCluster._clusterLabelCollection.destroy();
    }
    if (Cesium.defined(entityCluster._clusterBillboardCollection)) {
        entityCluster._clusterBillboardCollection.destroy();
    }
    if (Cesium.defined(entityCluster._clusterPointCollection)) {
        entityCluster._clusterPointCollection.destroy();
    }

    entityCluster._clusterLabelCollection = undefined;
    entityCluster._clusterBillboardCollection = undefined;
    entityCluster._clusterPointCollection = undefined;

    disableCollectionClustering(entityCluster._labelCollection);
    disableCollectionClustering(entityCluster._billboardCollection);
    disableCollectionClustering(entityCluster._pointCollection);
}

/**
 * Gets the draw commands for the clustered billboards/points/labels if enabled, otherwise,
 * queues the draw commands for billboards/points/labels created for entities.
 * @private
 */
PrimitiveCluster.prototype.update = function(frameState) {

    if (!this.show) {
        return;
    }

    if (this._enabledDirty) {
        this._enabledDirty = false;
        updateEnable(this);
        this._clusterDirty = true;
    }

    if (this._clusterDirty && this._cluster) {
        this._clusterDirty = false;
        this._cluster();
    }

    if (Cesium.defined(this._clusterLabelCollection)) {
        this._clusterLabelCollection.update(frameState);
    }
    if (Cesium.defined(this._clusterBillboardCollection)) {
        this._clusterBillboardCollection.update(frameState);
    }
};

/**
 * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
 * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
 * <p>
 * Unlike other objects that use WebGL resources, this object can be reused. For example, if a data source is removed
 * from a data source collection and added to another.
 * </p>
 */
PrimitiveCluster.prototype.destroy = function() {
    this._labelCollection =
        this._labelCollection && this._labelCollection.destroy();
    this._billboardCollection =
        this._billboardCollection && this._billboardCollection.destroy();
    this._pointCollection =
        this._pointCollection && this._pointCollection.destroy();

    this._clusterLabelCollection =
        this._clusterLabelCollection && this._clusterLabelCollection.destroy();
    this._clusterBillboardCollection =
        this._clusterBillboardCollection &&
        this._clusterBillboardCollection.destroy();
    this._clusterPointCollection =
        this._clusterPointCollection && this._clusterPointCollection.destroy();

    if (Cesium.defined(this._removeEventListener)) {
        this._removeEventListener();
        this._removeEventListener = undefined;
    }

    this._labelCollection = undefined;
    this._billboardCollection = undefined;
    this._pointCollection = undefined;

    this._clusterBillboardCollection = undefined;
    this._clusterLabelCollection = undefined;
    this._clusterPointCollection = undefined;

    this._collectionIndicesByEntity = undefined;

    this._unusedLabelIndices = [];
    this._unusedBillboardIndices = [];
    this._unusedPointIndices = [];

    this._previousClusters = [];
    this._previousHeight = undefined;

    this._enabledDirty = false;
    this._pixelRangeDirty = false;
    this._minimumClusterSizeDirty = false;

    return undefined;
};

/**
 * A event listener function used to style clusters.
 * @callback PrimitiveCluster.newClusterCallback
 *
 * @param {Entity[]} clusteredEntities An array of the entities contained in the cluster.
 * @param {Object} cluster An object containing billboard, label, and point properties. The values are the same as
 * billboard, label and point entities, but must be the values of the ConstantProperty.
 *
 * @example
 * // The default cluster values.
 * dataSource.clustering.clusterEvent.addEventListener(function(entities, cluster) {
 *     cluster.label.show = true;
 *     cluster.label.text = entities.length.toLocaleString();
 * });
 */
export default PrimitiveCluster;
import PrimitiveCluster from './PrimitiveCluster.js'
import Util from "./Util.js";

function GrahicLayer() {
    this.id = ''
    this.name = ''
    this.labels = new Cesium.LabelCollection();
    this.points = new Cesium.BillboardCollection();
    this.clustering = []
    this.cluster = false;
    this.clusterColor = '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).substr(-6)
    this.polylines = [];
    this.polygons = [];
    this.map = null
    this._show = true
    this.showDistance = Number.MAX_VALUE;
    this.minzoom = 0;
    this.textField = "name"
    this.idField = "id"
    this.imageField = ""
    this.imageHeight = -1;
    this.imageWidth = -1
    this.autoHeight = true;
    this.imageUrl = "images/collect/blue_marker.png"
    this.graphics = [];
    this.filter = []
    this.type = '' //"fill", "line", "symbol", "circle", "heatmap", "fill-extrusion", "raster", "hillshade", "background"
}

GrahicLayer.prototype = {
    loadImg: function(url) {
        if (this.image) return this.image
        return new Promise(async(resolve, reject) => {
            var myImage = new Image();
            myImage.src = url; //背景图片 
            myImage.onload = () => {
                this.image = myImage
                resolve(this.image)
            }
        });
    },
    drawImgText: async function(url, number) {
        const canvas = document.createElement('canvas') // 不停切换canvas对象
        var context = canvas.getContext('2d');
        // canvas.width  = document.body.clientWidth;//canvas的宽度
        canvas.width = 100; //图片的宽度
        canvas.height = canvas.width
        var swidth = canvas.width
        var sheight = canvas.height
        context.rect(0, 0, swidth, sheight);
        context.fill();
        var myImage = await this.loadImg(url)
        context.drawImage(myImage, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
        //    添加文字
        context.font = 'bolder 36px Microsoft YaHei';
        context.textAlign = 'right';
        context.textBaseline = 'bottom';
        var left = canvas.width * 0.78;
        var top = canvas.height * 0.066;
        context.fillStyle = '#fff';
        context.fillText(number, left, top);

        let result = canvas.toDataURL("image/png");

        return result
    },
    get show() {
        return this._show;
    },
    set show(val) {
        this._show = val;
        if (!val) {
            this.clear()
        } else {
            this.update()
        }
        if (this.cluster) {
            this.clustering._cluster();
        }
    },
    drawLayer: function(fea) {
        let pro = fea.properties;
        let geo = fea.geometry
        if (!this._show) return;
        // if (fea.show) return;
        //  fea.show = true;
        switch (geo.type.toUpperCase()) {
            case 'POINT':
                {
                    let pt = Cesium.Cartesian3.fromDegrees(geo.coordinates[0], geo.coordinates[1], geo.coordinates[2])
                    let img = pro[this.imageField] || this.imageUrl;
                    let id = pro[this.idField] || Util.getId()
                    if (this.cluster) {
                        let _pt = this.clustering.getBillboard({ id, position: pt, image: img, data: pro });
                        _pt.position = pt
                    }
                    let __pt = {
                        id: { id: id, data: JSON.parse(JSON.stringify(pro)) },
                        text: pro[this.textField],
                        position: pt,
                        disableDepthTestDistance: Number.POSITIVE_INFINITY,
                        //    distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, this.showDistance),
                        // heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
                        image: img,
                        pixelOffset: new Cesium.Cartesian2(0.0, -20.0)
                    }
                    if (this.imageWidth != -1) {
                        __pt.width = this.imageWidth;
                    }
                    if (this.imageHeight != -1) {
                        __pt.height = this.imageHeight
                    }
                    this.points.add(__pt)
                    if (this.textField) {
                        this.labels.add({
                            id: { id: id, data: JSON.parse(JSON.stringify(pro))  },
                            position: pt,
                            disableDepthTestDistance: Number.POSITIVE_INFINITY,
                            //    distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, this.showDistance),
                            // heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
                            showBackground: true,
                            backgroundColor: new Cesium.Color(1, 1, 1, 0.8),
                            fillColor: Cesium.Color.GREEN,
                            font: "12px monospace",
                            horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
                            verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                            text: pro[this.textField],
                            pixelOffset: new Cesium.Cartesian2(0.0, -40.0)
                        });
                    }
                }
                break;
            case 'POLYGON':
                {
                    //   const polygonArr = features[i].geometry.coordinates[j].toString().split(',');
                    for (let i = 0; i < geo.coordinates.length; i++) {
                        const coor = geo.coordinates[i];
                        let pts = []
                        for (let j = 0; j < coor.length; j++) {
                            const xy = coor[j];
                            pts.push(...xy)
                        }
                        var polygon = Cesium.PolygonGeometry.fromPositions({
                            positions: Cesium.Cartesian3.fromDegreesArray(pts)
                        });
                        const geometry = Cesium.PolygonGeometry.createGeometry(polygon);
                        this.polygons.push(new Cesium.GeometryInstance({
                            geometry: geometry,
                            attributes: {
                                color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({ alpha: 0.7 })),
                            },
                        }));
                    }

                }
                break;
            case 'LINESTRING':
                {
                    const coor = geo.coordinates;
                    let pts = []
                    for (let j = 0; j < coor.length; j++) {
                        const xy = coor[j];
                        pts.push(...xy)
                    }
                    let cpts = Cesium.Cartesian3.fromDegreesArray(pts);
                    const poline = new Cesium.PolylineGeometry({
                        positions: cpts,
                        vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
                    });
                    const geometry = Cesium.PolylineGeometry.createGeometry(poline);
                    // geometry.rectangle=Cesium.PolygonGeometry.computeRectangle({polygonHierarchy:new Cesium.PolygonHierarchy(cpts)});
                    this.polylines.push(new Cesium.GeometryInstance({
                        geometry: geometry,
                        attributes: {
                            color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({ alpha: 0.7 })),
                        },
                    }));
                }
                break;

        }
    },
    addGraphic: function(fea) {
        let pro = fea.properties;
        let _fea = this.graphics.find((f) => f.id == fea.id)
        if (!_fea);
        this.graphics.push(fea)
        this.drawLayer(fea)
    },
    loadIconDom: async function(url) {
        let iconDom = await Cesium.Resource.fetchImage(url)
        return iconDom
    },
    combineNewIcon: function(number, size = 60) {
        if (!this.offset) this.offset = 0
        const canvas = document.createElement('canvas') // 不停切换canvas对象
        canvas.width = size
        canvas.height = size
        const center_x = canvas.width / 2
        const center_y = canvas.height / 2
        const step = Math.PI * 0.006
        var context = canvas.getContext('2d')
        context.clearRect(0, 0, canvas.width, canvas.height)
        context.beginPath()
            // 填充背景色
        context.arc(center_x, center_y, size / 2, 0, 2 * Math.PI)
        context.fillStyle = this.clusterColor;
        context.fill()
        context.strokeStyle = '#1E90FF'
            // 画点
        context.lineWidth = 2
        for (let curr = 0 + this.offset; curr < 2 * Math.PI; curr += Math.PI * 0.06) {
            context.beginPath()
            context.arc(center_x, center_y, size / 2 - 2, curr, curr + 2 * Math.PI * 0.01)
            context.stroke()
            context.closePath()
        }
        context.strokeStyle = '#ffffFF'
        for (let curr = 2 * Math.PI - this.offset; curr > 0; curr -= Math.PI * 0.06) {
            context.beginPath()
            context.arc(center_x, center_y, size / 2 - 4, curr, curr - 2 * Math.PI * 0.01, true)
            context.stroke()
            context.closePath()
        }
        // 切换标识,进而切换canvas对象
        this.curCanvas = this.curCanvas === 'a' ? 'b' : 'a'
        this.offset = this.offset > 0.1 * Math.PI ? 0 : this.offset + step
        context.font = "12px Verdana";
        // 创建渐变
        var gradient = context.createLinearGradient(0, 0, canvas.width, 0);
        gradient.addColorStop("0", "magenta");
        gradient.addColorStop("0.5", "blue");
        gradient.addColorStop("1.0", "red");
        // 用渐变填色
        context.fillStyle = gradient;
        context.textAlign = 'center'
        context.textBaseline = "middle";
        context.fillText(number, center_x, center_y);
        let result = canvas.toDataURL("image/png");

        return result
    },

    setSource: function(features) {
        this.clear();
        this.graphics.length = 0;
        let positions = [];
        for (let i = 0; i < features.length; i++) {
            let fea = features[i];
            let geo = fea.geometry
            if (geo.type.toUpperCase() !== 'POINT' || !this.autoHeight)
                this.addGraphic(fea)
            else {

                let p = Cesium.Cartographic.fromDegrees(geo.coordinates[0], geo.coordinates[1])
                positions.push(p);
            }
        }
        if (this.type !== 'symbol' || !this.autoHeight)
            return;
        var promise = Cesium.sampleTerrainMostDetailed(jagis.viewMap.terrainProvider, positions);
        Cesium.when(promise, (upts) => {
            this.autoHeight = false;
            this.points.show = false;
            this.labels.show = false;
            for (var i = 0; i < upts.length; i++) {
                var y = Cesium.Math.toDegrees(upts[i].latitude);
                var x = Cesium.Math.toDegrees(upts[i].longitude);
                let fea = features[i];
                fea.geometry.coordinates.push(upts[i].height)
                if (!fea.id) fea.id = i;
                if (!fea.properties.name) fea.properties.name = this.id + "_" + i
                this.addGraphic(fea)
            }
            this.points.show = this.show;
            this.labels.show = this.show;
            if (this.cluster) {
                this.clustering._cluster();
            }
        });
    },

    update: function() {
        if (!this.map) return;
        for (let i = 0; i < this.graphics.length; i++) {
            const fea = this.graphics[i];
            this.drawLayer(fea)
        }
        const polygon = new Cesium.Primitive({
            geometryInstances: this.polygons,
            classificationType: Cesium.ClassificationType.TERRAIN
        });
        this.polygonP = this.map.scene.primitives.add(polygon);
        const line = new Cesium.Primitive({
            geometryInstances: this.polylines,
            appearance: new Cesium.PerInstanceColorAppearance({ // 为每个instance着色
                translucent: true,
                closed: false
            }),
        });
        this.lineP = this.map.scene.primitives.add(line);
    },
    clearLineAndPolygon: function() {
        if (this.polygonP) {
            this.map.scene.primitives.remove(this.polygonP)
            this.polygonP.destroy();
            this.polygonP = null;
        }
        if (this.lineP) {
            this.map.scene.primitives.remove(this.lineP)
            this.lineP.destroy();
            this.lineP = null;
        }
    },
    clear: function() {
        this.clearLineAndPolygon();
        this.polylines.length = 0
        this.polygons.length = 0
        this.points.removeAll();
        this.labels.removeAll();
        if (this.cluster) {
            this.clustering.clear();
        }
        // this.update()
    },
    getFeatureById(id) {
        return this.graphics.find(item => item.id === id)
    },
    removeByid(id) {
        //this.graphics.filfil(item => item.id !== id)
        var index = this.graphics.findIndex(item => item.id === id)
        if (index > -1) {
            this.graphics.splice(index, 1)
        }
        this.update()
    },
    //视角高度 cesiun知识点
    get cameraHeight() {
        if (this.map) {
            return Math.ceil(this.map.camera.positionCartographic.height)
        }
    },
    addToMap: function(map, scale) {
        this.scale = scale
        this.map = map

        let _level = this.map.scene.globe._surface._debug.maxDepthVisited + 1;
        let show = true;
        if (this.minzoom > 0 && this.minzoom < _level)
            show = false;
        this.labels = new Cesium.LabelCollection({ scene: this.map.scene, show: show });
        this.points = new Cesium.BillboardCollection({ scene: this.map.scene, show: show });
        this.pointsP = this.map.scene.primitives.add(this.points)
            // this.pointsP.show = false;
        this.labelsP = this.map.scene.primitives.add(this.labels)
        if (this.cluster) {
            this.clustering = new PrimitiveCluster({ enabled: true })
            this.clustering._initialize(this.map.scene, this.minzoom, this.points, this.labels);
            this.clusterP = this.map.scene.primitives.add(this.clustering)
           
            this.clustering.clusterEvent.addEventListener((cluster) => {
                let _level = this.map.scene.globe._surface._debug.maxDepthVisited + 1;
                if (_level >= this.minzoom) {
                    this.points.show = true;
                    this.labels.show = true;
                    this.clusterP.show = false;
                } else {
                    this.points.show = false;
                    this.labels.show = false;
                    this.clusterP.show = true;
                }
            });
        }

    },
}

export default GrahicLayer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tufeibobo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值