Skip to content

Commit

Permalink
Speed up vector layer rendering, fix drawing
Browse files Browse the repository at this point in the history
- Fix issues with drawing polygons, including drawing polygons over poles
- Allow drawn polygons to have properties set like other layers (color, opacity, etc)
- When drawing, draw both points and polygons
- Add CustomDataSource support
- Greatly reduce the number of re-renders Cesium must do (improve map performance)
- Add connectors between GeoPoints collection and polygons & points Entities

Issues #2180 and #2189
  • Loading branch information
robyngit committed Oct 6, 2023
1 parent 92e29eb commit c89ff99
Show file tree
Hide file tree
Showing 15 changed files with 1,270 additions and 960 deletions.
17 changes: 14 additions & 3 deletions src/js/collections/maps/GeoPoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,8 @@ define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) {
*/
getCZMLPolygon: function () {
const coords = this.toECEFArray();
// make a random ID:
const id = "polygon_" + Math.floor(Math.random() * 1000000);
return {
id: id,
id: this.cid,
name: "Polygon",
polygon: {
positions: {
Expand Down Expand Up @@ -309,6 +307,19 @@ define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) {
return model.toECEFArray();
});
},

/**
* Convert the collection to an array of coordinates in the format
* native to the map widget. For Cesium, this is an array of
* Cartesian3 objects in ECEF coordinates.
* @returns {Array} An array of coordinates that can be used by the map
* widget.
*/
asMapWidgetCoords: function () {
return this.models.map((model) => {
return model.get("mapWidgetCoords");
});
},
}
);

Expand Down
2 changes: 1 addition & 1 deletion src/js/collections/maps/MapAssets.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ define([
model: Cesium3DTileset,
},
{
types: ["GeoJsonDataSource", "CzmlDataSource"],
types: ["GeoJsonDataSource", "CzmlDataSource", "CustomDataSource"],
model: CesiumVectorData,
},
{
Expand Down
163 changes: 163 additions & 0 deletions src/js/models/connectors/GeoPoints-Cesium.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
"use strict";

/*global define */
define([
"backbone",
"cesium",
"collections/maps/GeoPoints",
"models/maps/assets/CesiumVectorData",
], function (Backbone, Cesium, GeoPoints, CesiumVectorData) {
/**
* @class GeoPointsCesiumConnector
* @classdesc This is the base model for other connectors that create geometry
* in Cesium based on points in a GeoPoints collection.
* @name GeoPointsCesiumConnector
* @extends Backbone.Model
* @constructor
* @classcategory Models/Connectors
* @since x.x.x
*/
return Backbone.Model.extend(
/** @lends GeoPointsCesiumConnector.prototype */ {
/**
* The type of Backbone.Model this is.
* @type {string}
* @default "GeoPointsCesiumConnector"
*/
type: "GeoPointsCesiumConnector",

/**
* Extends the default Backbone.Model.defaults() function to specify
* default attributes for the GeoPointsCesiumConnector model.
* @returns {Object} The default attributes
* @property {GeoPoints} geoPoints - The points collection to visualize
* @property {CesiumVectorData} layer - The CesiumVectorData model to use
* to visualize the points. This must be a CesiumVectorData model.
* @property {Boolean} isConnected - Whether the layer is currently being
* updated with changes to the points collection.
*/
defaults: function () {
return {
geoPoints: null,
layer: null,
isConnected: false,
};
},

/**
* Initialize the model.
* @param {Object} attrs - The attributes for this model.
* @param {GeoPoints | Array} [attributes.geoPoints] - The GeoPoints
* collection to use for this connector or an array of JSON attributes to
* create a new GeoPoints collection. If not provided, a new empty
* GeoPoints collection will be created.
* @param {CesiumVectorData | Object} [attributes.layer] - The
* CesiumVectorData CesiumVectorData model to use for this connector or a
* JSON object with options to create a model. If not provided, a new
* layer will be created.
*/
initialize: function (attrs) {
try {
attrs = attrs || {};
this.setGeoPoints(attrs.geoPoints);
this.setLayer(attrs.layer);
if (attrs.isConnected) {
this.connect();
}
} catch (e) {
console.log("Error initializing a GeoPointsCesiumConnector", e);
}
},

/**
* Set or create and set the GeoPoints collection for this connector.
* @param {GeoPoints | Object} [points] - The GeoPoints collection to use
* for this connector or an array of JSON attributes to create points.
* @returns {GeoPoints} The GeoPoints collection for this connector.
*/
setGeoPoints: function (geoPoints) {
if (geoPoints instanceof GeoPoints) {
this.set("geoPoints", geoPoints);
} else {
this.set("geoPoints", new GeoPoints(geoPoints));
}
return this.get("geoPoints");
},

/**
* Set or create and set the CesiumVectorData model for this connector.
* @param {CesiumVectorData | Object} [layer] - The CesiumVectorData model
* to use for this connector or a JSON object with options to create a new
* CesiumVectorData model. If not provided, a new CesiumVectorData model
* will be created.
* @returns {CesiumVectorData} The CesiumVectorData model for this
* connector.
*/
setLayer: function (layer) {
if (layer instanceof CesiumVectorData) {
this.set("layer", layer);
} else {
this.set("layer", new CesiumVectorData(layer));
}
return this.get("layer");
},

/**
* Listen for changes to the Points collection and update the
* CesiumVectorData model with point entities.
*/
connect: function () {
try {
// Listen for changes to the points collection and update the layer
let geoPoints = this.get("geoPoints");
const events = ["update", "reset"];
events.forEach((eventName) => {
this.listenTo(geoPoints, eventName, function (...args) {
this.handleCollectionChange(eventName, ...args);
});
});

// Restart listeners when points or the layer is replaced
this.listenToOnce(this, "change:geoPoints change:layer", () => {
if (this.get("isConnected")) {
this.connect();
}
});
// Restart listeners when points or the layer is replaced
this.listenToOnce(this, "change:geoPoints change:layer", () => {
if (this.get("isConnected")) {
this.connect();
}
});

this.set("isConnected", true);
} catch (e) {
console.warn("Error connecting Points to Cesium. Disconnecting.", e);
this.disconnect();
}
},

/**
* Stop listening for changes to the Points collection.
*/
disconnect: function () {
this.stopListening(this.get("geoPoints"));
this.set("isConnected", false);
},

/**
* Handle add, remove, merge, and reset events from the points collection
* @param {"update"|"reset"} eventName - The name of the event
* @param {GeoPoints} collection - The points collection
* @param {Object} options - Options for the event, as passed by Backbone
*/
handleCollectionChange(eventName, collection, options) {
try {
// What to do when the collection changes
} catch (e) {
console.warn('Error handling a "' + eventName + '" event.', e);
}
},
}
);
});
169 changes: 169 additions & 0 deletions src/js/models/connectors/GeoPoints-CesiumPoints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
"use strict";

/*global define */
define(["cesium", "models/connectors/GeoPoints-Cesium"], function (
Cesium,
GeoPointsCesiumConnector
) {
/**
* @class GeoPointsCesiumPointsConnector
* @classdesc This connector keeps a CesiumVectorData model in sync with the
* points in a GeoPoints collection. This connector will listen for changes to
* the GeoPoints collection and update the cesiumModel with point entities
* created from the points in the collection.
* @name GeoPointsCesiumPointsConnector
* @extends GeoPointsCesiumConnector
* @constructor
* @classcategory Models/Connectors
* @since x.x.x
*/
return GeoPointsCesiumConnector.extend(
/** @lends GeoPointsCesiumPointsConnector.prototype */ {
/**
* The type of Backbone.Model this is.
* @type {string}
* @default "GeoPointsCesiumPointsConnector"
*/
type: "GeoPointsCesiumPointsConnector",

/**
* Extends the default Backbone.Model.defaults() function to specify
* default attributes for the GeoPointsCesiumPointsConnector model.
* @extends GeoPointsCesiumConnector.defaults
* @returns {Object} The default attributes
* @property {Array} layerPoints - The list of point entities that have
* been added to the layer.
*/
defaults: function () {
return {
// extend the defaults from the parent class
...GeoPointsCesiumConnector.prototype.defaults(),
layerPoints: [],
};
},

/**
* Handle add, remove, merge, and reset events from the points collection
* @param {"update"|"reset"} eventName - The name of the event
* @param {GeoPoints} collection - The points collection
* @param {Object} options - Options for the event, as passed by Backbone
*/
handleCollectionChange(eventName, collection, options) {
try {
// For merges and resets, just remove all points and re-add them
if (!options?.add && !options?.remove) {
this.resetLayerPoints();
return;
}
// For adds and removes, just add or remove the points that changed
if (eventName === "update") {
if (options.add) {
const newModels = options.changes.added;
newModels.forEach((model) => {
this.addLayerPoint(model);
});
}
if (options.remove) {
const removedModels = options.changes.removed;
removedModels.forEach((model) => {
this.removeLayerPoint(model);
});
}
}
} catch (e) {
console.warn('Error handling a "' + eventName + '" event.', e);
}
},

/**
* Resync the layer points with the points from the points collection.
* This removes all point entities previously added to the layer and adds
* new ones for each point in the points collection.
*/
resetLayerPoints: function () {
const layer = this.get("layer");
layer.suspendEvents();
this.removeAllLayerPoints();
this.addAllLayerPoints();
layer.resumeEvents();
},

/**
* Remove all layer points previously added to the layer.
* @returns {Boolean} Whether the layer points were removed
*/
removeAllLayerPoints: function () {
const layer = this.get("layer");
if (!layer) return false;
const layerPoints = this.get("layerPoints");
layerPoints.forEach((entity) => {
layer.removeEntity(entity);
});
return true;
},

/**
* Add all points from the points collection to the layer.
* @returns {Boolean} Whether the layer points were added
*/
addAllLayerPoints: function () {
const layer = this.get("layer");
if (!layer) return false;
const geoPoints = this.get("geoPoints");
geoPoints.each((model) => {
this.addLayerPoint(model);
});
return true;
},

/**
* Add a point from the points collection to the layer. Adds the point
* entity to the layerPoints array for tracking.
* @param {GeoPoint} model - The point model to add to the layer
* @returns {Cesium.Entity} The layer point that was created
*/
addLayerPoint: function (model) {
try {
const layer = this.get("layer") || this.setLayer();
const layerPoint = layer.addEntity({
id: model.cid,
position: model.get("mapWidgetCoords"),
point: {
pixelSize: 2,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
});
// Track the layer point so we can remove it later
const layerPoints = this.get("layerPoints");
layerPoints.push(layerPoint);
return layerPoint;
} catch (e) {
console.log("Failed to add a point to a CesiumVectorData.", e);
}
},

/**
* Remove a point from the points collection from the layer. Removes the
* point entity from the layerPoints array.
* @param {GeoPoint} model - The point model to remove from the layer
* @returns {Cesium.Entity} The layer point that was removed
*/
removeLayerPoint: function (model) {
try {
const layer = this.get("layer");
if (!layer) return false;
const removedPoint = layer.removeEntity(model.cid);
// Remove the layer point from the list of layer points
const layerPoints = this.get("layerPoints");
const index = layerPoints.indexOf(removedPoint);
if (index > -1) {
layerPoints.splice(index, 1);
}
return removedPoint;
} catch (e) {
console.log("Failed to remove a point from a CesiumVectorData.", e);
}
},
}
);
});
Loading

0 comments on commit c89ff99

Please sign in to comment.