diff --git a/src/js/collections/maps/Features.js b/src/js/collections/maps/Features.js index 768c17197..c49eb33b3 100644 --- a/src/js/collections/maps/Features.js +++ b/src/js/collections/maps/Features.js @@ -1,125 +1,118 @@ -'use strict'; +"use strict"; -define( - [ - 'jquery', - 'underscore', - 'backbone', - 'models/maps/Feature' - ], - function ( - $, - _, - Backbone, - Feature - ) { +define(["jquery", "underscore", "backbone", "models/maps/Feature"], function ( + $, + _, + Backbone, + Feature +) { + /** + * @class Features + * @classdesc A Features collection contains the relevant properties of a group of + * selected geo-spatial features from a map. + * @class Features + * @classcategory Collections/Maps + * @extends Backbone.Collection + * @since 2.18.0 + * @constructor + */ + var Features = Backbone.Collection.extend( + /** @lends Features.prototype */ { + /** + * The class/model that this collection contains. + * @type {Backbone.Model} + */ + model: Feature, - /** - * @class Features - * @classdesc A Features collection contains the relevant properties of a group of - * selected geo-spatial features from a map. - * @class Features - * @classcategory Collections/Maps - * @extends Backbone.Collection - * @since 2.18.0 - * @constructor - */ - var Features = Backbone.Collection.extend( - /** @lends Features.prototype */ { + /** + * Get an array of all of the unique Map Assets that are associated with this + * collection. (When a feature model is part of a layer, it will have the layer + * model (Map Asset) set as a property) + * @returns {MapAsset[]} Returns an a array of all the unique Map Assets (imagery, + * tile sets, etc.) in this collection. + */ + getMapAssets: function () { + return this.getUniqueAttrs("mapAsset"); + }, - /** - * The class/model that this collection contains. - * @type {Backbone.Model} - */ - model: Feature, + /** + * Get an array of all the unique feature objects associated with this collection. + * @param {string} [type] Optionally set a type of feature to return. If set, then + * only features that have this constructor name will be returned. + * @returns {Array} Returns an array of all of the unique feature objects in the + * collection. Feature objects are the objects used by the map widget to represent + * a feature in the map. For example, in Cesium this could be a + * Cesium3DTileFeature or an Entity. + */ + getFeatureObjects: function (type) { + let featureObjects = this.getUniqueAttrs("featureObject"); + if (type) { + featureObjects = featureObjects.filter(function (featureObject) { + return featureObject.constructor.name === type; + }); + } + return featureObjects; + }, - /** - * Get an array of all of the unique Map Assets that are associated with this - * collection. (When a feature model is part of a layer, it will have the layer - * model (Map Asset) set as a property) - * @returns {MapAsset[]} Returns an a array of all the unique Map Assets (imagery, - * tile sets, etc.) in this collection. - */ - getMapAssets: function () { - return this.getUniqueAttrs('mapAsset') - }, + /** + * Get an array of unique values for some attribute that may be set on the models + * in this collection + * @param {string} attrName The name of the attr to get unique values for + * @returns {Array} Returns an array of unique values of the given attribute + */ + getUniqueAttrs: function (attrName) { + try { + let uniqueAttrs = []; + this.each(function (featureModel) { + const attr = featureModel.get(attrName); + if (attr && !uniqueAttrs.includes(attr)) { + uniqueAttrs.push(attr); + } + }); + return uniqueAttrs; + } catch (error) { + console.log( + `Failed to get unique attributes for "${attrName}".`, + error + ); + } + }, - /** - * Get an array of all the unique feature objects associated with this collection. - * @param {string} [type] Optionally set a type of feature to return. If set, then - * only features that have this constructor name will be returned. - * @returns {Array} Returns an array of all of the unique feature objects in the - * collection. Feature objects are the objects used by the map widget to represent - * a feature in the map. For example, in Cesium this could be a - * Cesium3DTileFeature or an Entity. - */ - getFeatureObjects: function (type) { - let featureObjects = this.getUniqueAttrs('featureObject') - if (type) { - featureObjects = featureObjects.filter(function (featureObject) { - return featureObject.constructor.name === type - }) - } - return featureObjects - }, + /** + * Checks if a given feature object is an attribute in one of the Feature models + * in this collection. + * @param {Feature|Cesium.Cesium3DTilesetFeature|Cesium.Entity} featureObject + * @returns {boolean} Returns true if the given feature object is in this + * collection, false otherwise. + * @since 2.25.0 + */ + containsFeature: function (featureObject) { + if (this.models.length === 0) return false; + if (!featureObject) return false; + featureObject = + featureObject instanceof Feature + ? featureObject.get("featureObject") + : featureObject; + return this.findWhere({ 'featureObject': featureObject }) ? true : false; + }, - /** - * Get an array of unique values for some attribute that may be set on the models - * in this collection - * @param {string} attrName The name of the attr to get unique values for - * @returns {Array} Returns an array of unique values of the given attribute - */ - getUniqueAttrs: function (attrName) { - try { - let uniqueAttrs = [] - this.each(function (featureModel) { - const attr = featureModel.get(attrName) - if (attr && !uniqueAttrs.includes(attr)) { - uniqueAttrs.push(attr) - } - }) - return uniqueAttrs - } - catch (error) { - console.log( - 'Failed to get unique values for an attribute in a Features collection' + - '. Error details: ' + error - ); - } - }, + /** + * Checks if a given array of feature objects are attributes in one of the + * Feature models in this collection. + * @param {Array} featureObjects An array of feature objects to check if they are + * in this collection. + * @returns {boolean} Returns true if all of the given feature objects are in this + * collection, false otherwise. + */ + containsFeatures: function (featureObjects) { + if (!featureObjects || !featureObjects.length) return false; + return featureObjects.every((featureObject) => + this.containsFeature(featureObject) + ); + }, - /** - * Checks if a given feature object is an attribute in one of the Feature models - * in this collection. - * @param {Feature|Cesium.Cesium3DTilesetFeature|Cesium.Entity} featureObject - * @returns {boolean} Returns true if the given feature object is in this - * collection, false otherwise. - * @since 2.25.0 - */ - containsFeature: function (featureObject) { - if (!featureObject) return false; - featureObject = featureObject instanceof Feature ? featureObject.get('featureObject') : featureObject; - return this.findWhere({ featureObject: featureObject }) ? true : false; - }, + } + ); - /** - * Checks if a given array of feature objects are attributes in one of the - * Feature models in this collection. - * @param {Array} featureObjects An array of feature objects to check if they are - * in this collection. - * @returns {boolean} Returns true if all of the given feature objects are in this - * collection, false otherwise. - */ - containsFeatures: function (featureObjects) { - if (!featureObjects || !featureObjects.length) return false; - return featureObjects.every( - (featureObject) => this.containsFeature(featureObject)); - }, - - } - ); - - return Features; - - } -); \ No newline at end of file + return Features; +}); diff --git a/src/js/collections/maps/MapAssets.js b/src/js/collections/maps/MapAssets.js index 262ff3c2a..1dc3a6d7b 100644 --- a/src/js/collections/maps/MapAssets.js +++ b/src/js/collections/maps/MapAssets.js @@ -142,12 +142,8 @@ define([ this.each(function (mapAssetModel) { mapAssetModel.set("mapModel", mapModel); }); - } catch (error) { - console.log( - "Failed to set the map model on a MapAssets collection" + - ". Error details: " + - error - ); + } catch (e) { + console.log("Failed to set the map model on MapAssets collection", e); } }, @@ -236,6 +232,7 @@ define([ * @since 2.25.0 */ getFeatureAttributes: function (features) { + if (!Array.isArray(features)) features = [features]; return features.map((feature) => { const asset = this.findAssetWithFeature(feature); return asset?.getFeatureAttributes(feature); diff --git a/src/js/models/maps/Feature.js b/src/js/models/maps/Feature.js index f68c61e4a..66b2b0fc6 100644 --- a/src/js/models/maps/Feature.js +++ b/src/js/models/maps/Feature.js @@ -1,190 +1,156 @@ -'use strict'; - -define( - [ - 'jquery', - 'underscore', - 'backbone' - ], - function ( - $, - _, - Backbone - ) { - /** - * @classdesc A Feature Model organizes information about a single feature of a vector - * layer in a map. - * @classcategory Models/Maps - * @class Feature - * @name Feature - * @extends Backbone.Model - * @since 2.18.0 - * @constructor - */ - var Feature = Backbone.Model.extend( - /** @lends Feature.prototype */ { - - /** - * The name of this type of model - * @type {string} - */ - type: 'Feature', - - /** - * Default attributes for Feature models - * @name Feature#defaults - * @type {Object} - * @property {Object} properties Property names (keys) and property values - * (values) for properties set on this feature. For example, the properties that - * would be in an attributes table for a shapefile. - * @property {MapAsset} mapAsset If the feature is part of a Map Asset, then the - * model for that asset. For example, if this is a feature if a 3D tileset, then - * the Cesium3DTileset map asset model. - * @property {string|number} featureID An ID that's used to identify this feature - * in the map. It should be unique among all map features. (In Cesium, this is the - * Pick ID key.) - * @property {*} featureObject The object that a Map widget uses to represent this - * feature in the map. For example, in Cesium this could be a - * Cesium.Cesium3DTileFeature or a Cesium.Entity. - * @property {string} label An optional friendly label or name for this feature. - */ - defaults: function () { - return { - properties: {}, - mapAsset: null, - featureID: null, - featureObject: null, - label: null +"use strict"; + +define(["jquery", "underscore", "backbone"], function ($, _, Backbone) { + /** + * @classdesc A Feature Model organizes information about a single feature of + * a vector layer in a map. + * @classcategory Models/Maps + * @class Feature + * @name Feature + * @extends Backbone.Model + * @since 2.18.0 + * @constructor + */ + var Feature = Backbone.Model.extend( + /** @lends Feature.prototype */ { + /** + * The name of this type of model + * @type {string} + */ + type: "Feature", + + /** + * Default attributes for Feature models + * @name Feature#defaults + * @type {Object} + * @property {Object} properties Property names (keys) and property values + * (values) for properties set on this feature. For example, the + * properties that would be in an attributes table for a shapefile. + * @property {MapAsset} mapAsset If the feature is part of a Map Asset, + * then the model for that asset. For example, if this is a feature if a + * 3D tileset, then the Cesium3DTileset map asset model. + * @property {string|number} featureID An ID that's used to identify this + * feature in the map. It should be unique among all map features. (In + * Cesium, this is the Pick ID key.) + * @property {*} featureObject The object that a Map widget uses to + * represent this feature in the map. For example, in Cesium this could be + * a Cesium.Cesium3DTileFeature or a Cesium.Entity. + * @property {string} label An optional friendly label or name for this + * feature. + */ + defaults: function () { + return { + properties: {}, + mapAsset: null, + featureID: null, + featureObject: null, + label: null, + }; + }, + + /** + * Checks if the attributes for this model are only the default + * attributes. + * @returns {boolean} Returns true if all of the attributes are equal to + * the default attributes, false otherwise. + */ + isDefault: function () { + try { + var defaults = this.defaults(); + var current = this.attributes; + return _.isEqual(defaults, current); + } catch (error) { + console.log( + "Failed to check if a Feature model is the default.", + error + ); + } + }, + + /** + * Clears all the model attributes and sets them to the default values. + * This will trigger a change event. No event is triggered if all of the + * value are already set to default. + */ + setToDefault: function () { + try { + // Don't make changes if model is already the default + if (!this.isDefault()) { + this.clear({ silent: true }); + this.set(this.defaults()); } - }, - - // /** - // * Executed when a new Feature model is created. - // * @param {Object} [attributes] The initial values of the attributes, which will - // * be set on the model. - // * @param {Object} [options] Options for the initialize function. - // */ - // initialize: function (attributes, options) { - // try { - - // } - // catch (error) { - // console.log( - // 'There was an error initializing a Feature model' + - // '. Error details: ' + error - // ); - // } - // }, - - /** - * Checks if the attributes for this model are only the default attributes. - * @returns {boolean} Returns true if all of the attributes are equal to the - * default attributes, false otherwise. - */ - isDefault: function () { - try { - var defaults = this.defaults() - var current = this.attributes - return _.isEqual(defaults, current) + } catch (error) { + console.log( + "There was an error reset a Feature model to default" + + ". Error details: " + + error + ); + } + }, + + /** + * Given an map-widget-specific-object representing a feature, and a + * MapAssets collection, this function will attempt to find the + * corresponding MapAsset model in the collection, and extract the + * feature attributes from that model. + * @param {*} feature - An object representing a feature in the map. For + * example, in Cesium this could be a Cesium.Cesium3DTileFeature or a + * Cesium.Entity. + * @param {MapAssets} assets - A MapAssets collection to use to extract + * feature attributes from a feature object. + * @returns {object} - The JSON object of all the Feature attributes + * @since x.x.x + */ + attrsFromFeatureObject: function (feature, assets) { + if (feature instanceof Feature) { + return feature.clone().attributes; + } + let attrs = null; + // if this is already an object with feature attributes, return it + if (typeof feature == "object") { + if ( + feature.hasOwnProperty("mapAsset") && + feature.hasOwnProperty("properties") + ) { + attrs = feature; + } else if (assets) { + attrs = assets.getFeatureAttributes([feature])[0]; } - catch (error) { - console.log( - 'There was an error checking if a Feature model has only default attributes in a Feature' + - '. Error details: ' + error + } + return attrs; + }, + + /** + * Parses the given input into a JSON object to be set on the model. If + * passed a MapAssets collection as an option, and the input includes an + * assets property, then the parse function will attempt to find the + * feature object's corresponding MapAsset model in the collection, and + * extract the feature attributes from that model. + * + * @param {object} input - The raw response object + * @param {object} [options] - Options for the parse function + * @property {MapAssets} [options.assets] - A MapAssets collection to use + * to extract feature attributes from a feature object. + * @return {object} - The JSON object of all the Feature attributes + */ + parse: function (input, options) { + try { + if (!input) return null; + if (input.featureObject && options.assets) { + const attrs = this.attrsFromFeatureObject( + input.featureObject, + options.assets ); + input = Object.assign({}, input, attrs); } - }, - - /** - * Clears all the model attributes and sets them to the default values. This will - * trigger a change event. No event is triggered if all of the value are already - * set to default. - */ - setToDefault: function () { - try { - // Don't make changes if model is already the default - if (!this.isDefault()) { - this.clear({ silent: true }) - this.set(this.defaults()) - } - } - catch (error) { - console.log( - 'There was an error reset a Feature model to default' + - '. Error details: ' + error - ); - } - }, - - // /** - // * Parses the given input into a JSON object to be set on the model. - // * - // * @param {TODO} input - The raw response object - // * @return {TODO} - The JSON object of all the Feature attributes - // */ - // parse: function (input) { - - // try { - - // var modelJSON = {}; - - // return modelJSON - - // } - // catch (error) { - // console.log( - // 'There was an error parsing a Feature model' + - // '. Error details: ' + error - // ); - // } - - // }, - - // /** - // * Overrides the default Backbone.Model.validate.function() to check if this if - // * the values set on this model are valid. - // * - // * @param {Object} [attrs] - A literal object of model attributes to validate. - // * @param {Object} [options] - A literal object of options for this validation - // * process - // * - // * @return {Object} - Returns a literal object with the invalid attributes and - // * their corresponding error message, if there are any. If there are no errors, - // * returns nothing. - // */ - // validate: function (attrs, options) { - // try { - - // } - // catch (error) { - // console.log( - // 'There was an error validating a Feature model' + - // '. Error details: ' + error - // ); - // } - // }, - - // /** - // * Creates a string using the values set on this model's attributes. - // * @return {string} The Feature string - // */ - // serialize: function () { - // try { - // var serializedFeature = ''; - - // return serializedFeature; - // } - // catch (error) { - // console.log( - // 'There was an error serializing a Feature model' + - // '. Error details: ' + error - // ); - // } - // }, - - }); - return Feature; + return input; + } catch (error) { + console.log("Failed to parse a Feature model", error); + } + }, + } + ); - } -); + return Feature; +}); diff --git a/src/js/models/maps/GeoBoundingBox.js b/src/js/models/maps/GeoBoundingBox.js index 93dc4959c..87e4b7cb2 100644 --- a/src/js/models/maps/GeoBoundingBox.js +++ b/src/js/models/maps/GeoBoundingBox.js @@ -84,10 +84,7 @@ define(["backbone"], function (Backbone) { */ getArea: function () { if (!this.isValid()) { - console.warn( - `Bounds are invalid: ${JSON.stringify(bounds)}. ` + - `Returning the globe's area for the given bounding box.` - ); + console.warn("Invalid bounding box, returning globe area"); return 360 * 180; } const { north, south, east, west } = this.attributes; diff --git a/src/js/models/maps/Map.js b/src/js/models/maps/Map.js index 341c73962..f15cfe4a5 100644 --- a/src/js/models/maps/Map.js +++ b/src/js/models/maps/Map.js @@ -210,11 +210,7 @@ define([ } this.setUpInteractions(); } catch (error) { - console.log( - "There was an error initializing a Map model" + - ". Error details: " + - error - ); + console.log('Failed to initialize a Map model.', error); } }, diff --git a/src/js/models/maps/MapInteraction.js b/src/js/models/maps/MapInteraction.js index b7dc2efd4..efa0e6ce6 100644 --- a/src/js/models/maps/MapInteraction.js +++ b/src/js/models/maps/MapInteraction.js @@ -123,9 +123,9 @@ define([ * zoom) and sets the 'firstInteraction' attribute to true when it occurs. */ listenForFirstInteraction: function () { + const model = this; if (model.get("firstInteraction")) return; const listener = new Backbone.Model(); - const model = this; listener.listenTo( this, "change:previousAction", @@ -153,8 +153,11 @@ define([ // Clone the models in hovered features and set them as clicked features const hoveredFeatures = this.get("hoveredFeatures").models; this.setClickedFeatures(hoveredFeatures); - if (this.get("mapModel")?.get("clickFeatureAction") === "showDetails") { + const clickAction = this.get("mapModel")?.get("clickFeatureAction"); + if (clickAction === "showDetails") { this.selectFeatures(hoveredFeatures); + } else if (clickAction === "zoom") { + this.set("zoomTarget", hoveredFeatures[0]); } }, @@ -235,6 +238,7 @@ define([ * view. */ selectFeatures: function (features) { + const model = this; this.setFeatures(features, "selectedFeatures", true); }, @@ -257,13 +261,13 @@ define([ // Create a features collection if one doesn't already exist if (!model.get(type)) model.set(type, new Features()); - // Remove any null or undefined features + // Remove any null, undefined, or empty features if (Array.isArray(features)) features = features.filter((f) => f); - // Remove any default features (which are empty models) if (features instanceof Features) { features = features.filter((f) => !f.isDefault()); } - // If no feature is passed to this function (and replace is true), + + // Empty collection if features array is empty (and replace is true) if (!features || features.length === 0) { if (replace) model.get(type).set([], { remove: true }); return; @@ -280,51 +284,17 @@ define([ return; } - // Convert the feature objects, which may be types specific to the map - // widget (Cesium), to a generic Feature model - features = model.convertFeatures(features); + const assets = this.get("mapModel")?.get("layers"); - // Update the Feature model with the new selected feature information. - const newAttrs = features.map(function (feature) { - return Object.assign( - {}, - new Feature().defaults(), - feature.attributes - ); - }); - model.get(type).set(newAttrs, { remove: replace }); + const newAttrs = features.map((f) => ({ featureObject: f })); + + model + .get(type) + .set(newAttrs, { remove: replace, parse: true, assets: assets }); } catch (e) { console.log("Failed to select a Feature in a Map model.", e); } }, - - /** - * Convert an array of feature objects to an array of Feature models. - * @param {Cesium.Entity|Cesium.Cesium3DTileFeature|Feature[]} features - - * An array of feature objects selected directly from the map view, or - * @returns {Feature[]} An array of Feature models. - * @since 2.25.0 - */ - convertFeatures: function (features) { - if (!features) return []; - if (!features.map) features = [features]; - const mapModel = this.get("mapModel"); - const attrs = features.map(function (feature) { - if (!feature) return null; - if (feature instanceof Feature) return feature.attributes; - // if this is already an object with feature attributes, return it - if ( - feature.hasOwnProperty("mapAsset") && - feature.hasOwnProperty("properties") - ) { - return feature; - } - // Otherwise, assume it's a Cesium object and get the feature - // attributes - return mapModel.get("layers").getFeatureAttributes(features)?.[0]; - }); - return attrs.map((attr) => new Feature(attr)); - }, } ); diff --git a/src/js/models/maps/assets/Cesium3DTileset.js b/src/js/models/maps/assets/Cesium3DTileset.js index 0968dda8e..df560048a 100644 --- a/src/js/models/maps/assets/Cesium3DTileset.js +++ b/src/js/models/maps/assets/Cesium3DTileset.js @@ -2,22 +2,14 @@ define( [ - 'jquery', - 'underscore', - 'backbone', 'cesium', 'models/maps/assets/MapAsset', - 'models/maps/AssetColor', 'models/maps/AssetColorPalette', 'collections/maps/VectorFilters' ], function ( - $, - _, - Backbone, Cesium, MapAsset, - AssetColor, AssetColorPalette, VectorFilters ) { @@ -74,7 +66,8 @@ define( * specific to each type of asset. */ defaults: function () { - return _.extend( + return Object.assign( + {}, this.constructor.__super__.defaults(), { type: 'Cesium3DTileset', @@ -215,6 +208,9 @@ define( setListeners: function () { try { + // call the super method + this.constructor.__super__.setListeners.call(this); + // When opacity, color, or visibility changes (will also update the filters) this.stopListening(this, 'change:opacity change:color change:visible') this.listenTo( @@ -245,6 +241,8 @@ define( // changes on a Cesium map. const cesiumModel = model.get('cesiumModel') + if(!cesiumModel) return + // If the layer isn't visible at all, don't bother setting up colors or // filters. Just set every feature to hidden. if (!model.isVisible()) { diff --git a/src/js/models/maps/assets/CesiumVectorData.js b/src/js/models/maps/assets/CesiumVectorData.js index fee95fc5b..060c0b6d4 100644 --- a/src/js/models/maps/assets/CesiumVectorData.js +++ b/src/js/models/maps/assets/CesiumVectorData.js @@ -106,7 +106,7 @@ define( initialize: function (assetConfig) { try { - if(!assetConfig) assetConfig = {}; + if (!assetConfig) assetConfig = {}; MapAsset.prototype.initialize.call(this, assetConfig); @@ -240,19 +240,18 @@ define( */ setListeners: function () { try { + this.constructor.__super__.setListeners.call(this); const appearEvents = 'change:visible change:opacity change:color change:outlineColor' + ' change:temporarilyHidden' this.stopListening(this, appearEvents) this.listenTo(this, appearEvents, this.updateAppearance) - this.stopListening(this.get('filters'), 'update') - this.listenTo(this.get('filters'), 'update', this.updateFeatureVisibility) + const filters = this.get('filters'); + this.stopListening(filters, 'update') + this.listenTo(filters, 'update', this.updateFeatureVisibility) } catch (error) { - console.log( - 'There was an error setting listeners in a CesiumVectorData model' + - '. Error details: ' + error - ); + console.log('Failed to set CesiumVectorData listeners.', error); } }, @@ -289,7 +288,7 @@ define( * @returns {Cesium.Entity} - The Entity object if found, otherwise null. * @since 2.25.0 */ - getEntityFromMapObject(mapObject) { + getEntityFromMapObject: function(mapObject) { const entityType = this.get("featureType") if (mapObject instanceof entityType) return mapObject if (mapObject.id instanceof entityType) return mapObject.id @@ -328,7 +327,7 @@ define( * @returns {Object} An object containing key-value mapping of property names to * properties. */ - getPropertiesFromFeature(feature) { + getPropertiesFromFeature: function(feature) { feature = this.getEntityFromMapObject(feature) if (!feature) return null const featureProps = feature.properties @@ -382,7 +381,10 @@ define( try { const model = this; - const cesiumModel = this.get('cesiumModel') + const cesiumModel = this.get('cesiumModel'); + + if (!cesiumModel) return + const entities = cesiumModel.entities.values // Suspending events while updating a large number of entities helps @@ -564,10 +566,11 @@ define( * @since 2.25.0 */ getStyles: function (entity) { - if(!entity) return null + if (!entity) return null + entity = this.getEntityFromMapObject(entity) if (this.featureIsSelected(entity)) { return this.getSelectedStyles(entity) - } + } const color = this.colorForEntity(entity); if (!color) { return null } const outlineColor = this.colorToCesiumColor( diff --git a/src/js/models/maps/assets/MapAsset.js b/src/js/models/maps/assets/MapAsset.js index 0441f4b46..b47b0e42e 100644 --- a/src/js/models/maps/assets/MapAsset.js +++ b/src/js/models/maps/assets/MapAsset.js @@ -1,296 +1,285 @@ -'use strict'; - -define( - [ - 'jquery', - 'underscore', - 'backbone', - 'models/portals/PortalImage', - 'models/maps/AssetColorPalette', - MetacatUI.root + '/components/dayjs.min.js' - ], - function ( - $, - _, - Backbone, - PortalImage, - AssetColorPalette, - dayjs - ) { - /** - * @classdesc A MapAsset Model comprises information required to fetch source data for - * some asset or resource that is displayed in a map, such as imagery (raster) tiles, - * vector data, a 3D tileset, or a terrain model. This model also contains metadata - * about the source data, like an attribution and a description. It represents the - * most generic type of asset, and can be extended to support specific assets that are - * compatible with a given map widget. - * @classcategory Models/Maps/Assets - * @class MapAsset - * @name MapAsset - * @extends Backbone.Model - * @since 2.18.0 - * @constructor - */ - var MapAsset = Backbone.Model.extend( - /** @lends MapAsset.prototype */ { - - /** - * The name of this type of model - * @type {string} - */ - type: 'MapAsset', - - /** - * Default attributes for MapAsset models - * @name MapAsset#defaults - * @type {Object} - * @property {('Cesium3DTileset'|'BingMapsImageryProvider'|'IonImageryProvider'|'WebMapTileServiceImageryProvider'|'TileMapServiceImageryProvider'|'CesiumTerrainProvider')} type - * The format of the data. Must be one of the supported types. - * @property {string} label A user friendly name for this asset, to be displayed - * in a map. - * @property {string} [icon = ''] - * A PID for an SVG saved as a dataObject, or an SVG string. The SVG will be used - * as an icon that will be displayed next to the label in the layers list. It - * should be an SVG file that has no fills, borders, or styles set on it (since - * the icon will be shaded dynamically by the maps CSS using a fill attribute). It - * must use a viewbox property rather than a height and width. - * @property {string} [description = ''] A brief description about the asset, e.g. - * which area it covers, the resolution, etc. - * @property {string} [attribution = ''] A credit or attribution to display along - * with this map resource. - * @property {string} [moreInfoLink = ''] A link to show in a map where a user can - * find more information about this resource. - * @property {string} [downloadLink = ''] A link to show in a map where a user can - * go to download the source data. - * @property {string} [id = ''] If this asset's data is archived in a DataONE - * repository, the ID of the data package. - * @property {Boolean} [selected = false] Set to true when this asset has been - * selected by the user in the layer list. - * @property {Number} [opacity = 1] A number between 0 and 1 indicating the - * opacity of the layer on the map, with 0 representing fully transparent and 1 - * representing fully opaque. This applies to raster (imagery) and vector assets, - * not to terrain assets. - * @property {Boolean} [visible = true] Set to true if the layer is visible on the - * map, false if it is hidden. This applies to raster (imagery) and vector assets, - * not to terrain assets. - * @property {AssetColorPalette} [colorPalette] The color or colors mapped to - * attributes of this asset. This applies to raster/imagery and vector assets. For - * imagery, the colorPalette will be used to create a legend. For vector assets - * (e.g. 3Dtilesets), it will also be used to style the features. - * @property {MapConfig#FeatureTemplate} [featureTemplate] Configuration for - * content and layout of the Feature Info panel - the panel that shows information - * about a selected feature from a vector asset ({@link FeatureInfoView}). - * @property {Cesium.Entity|Cesium.3DTilesetFeature} [featureType] For vector - * and 3d tileset assets, the object type that cesium uses to represent features - * from the asset. Null for imagery and terrain assets. - * @property {MapConfig#CustomProperties} [customProperties] Configuration that - * allows for the definition of custom feature properties, potentially based on - * other properties. For example, a custom property could be a formatted version - * of an existing date property. - * @property {MapConfig#Notification} [notification] A custom badge and message to - * display about the layer in the Layer list. For example, this could highlight - * the layer if it is new, give a warning if they layer is under development, etc. - * @property {'ready'|'error'|null} [status = null] Set to 'ready' when the - * resource is loaded and ready to be rendered in a map view. Set to 'error' when - * the asset is not supported, or there was a problem requesting the resource. - * @property {string} [statusDetails = null] Any further details about the status, - * especially when there was an error. - */ - defaults: function () { - return { - type: '', - label: '', - icon: '', - description: '', - attribution: '', - moreInfoLink: '', - downloadLink: '', - id: '', - selected: false, - opacity: 1, - visible: true, - colorPalette: null, - customProperties: {}, - featureTemplate: {}, - featureType: null, - notification: {}, - status: null, - statusDetails: null - } - }, - - /** - * The source of a specific asset (i.e. layer or terrain data) to show on the map, - * as well as metadata and display properties of the asset. Some properties listed - * here do not apply to all asset types, but this is specified in the property - * description. - * @typedef {Object} MapAssetConfig - * @name MapConfig#MapAssetConfig - * @property {('Cesium3DTileset'|'BingMapsImageryProvider'|'IonImageryProvider'|'WebMapTileServiceImageryProvider'|'WebMapServiceImageryProvider'|'TileMapServiceImageryProvider'|'NaturalEarthII'|'CesiumTerrainProvider'|'GeoJsonDataSource'|'USGSImageryTopo'|'OpenStreetMapImageryProvider')} type - - * A string indicating the format of the data. Some of these types correspond - * directly to Cesium classes. The NaturalEarthII type is a special imagery layer - * that automatically sets the cesiumOptions to load the Natural Earth II imagery - * that is shipped with Cesium/MetacatUI. If this type is set, then no other - * cesiumOptions are required. The same is true for USGSImageryTopo, which pulls - * imagery directly from USGS. - * @property {(Cesium3DTileset#cesiumOptions|CesiumImagery#cesiumOptions|CesiumTerrain#cesiumOptions|CesiumVectorData#cesiumOptions)} [cesiumOptions] - - * For MapAssets that are configured for Cesium, like - * Cesium3DTilesets, an object with options to pass to the Cesium constructor - * function that creates the Cesium model. Options are specific to each type of - * asset. For details, see documentation for each of the types. - * @property {string} label - A user friendly name for this asset, to be displayed - * in a map. - * @property {string} [icon] - A PID for an SVG saved as a dataObject, or an SVG - * string. The SVG will be used as an icon that will be displayed next to the - * label in the layers list. It should be an SVG file that has no fills, borders, - * or styles set on it (since the icon will be shaded dynamically by the maps CSS - * using a fill attribute). It must use a viewbox property rather than a height - * and width. - * @property {Number} [opacity=1] - A number between 0 and 1 indicating the - * opacity of the layer on the map, with 0 representing fully transparent and 1 - * representing fully opaque. This applies to raster (imagery) and vector assets, - * not to terrain assets. - * @property {Boolean} [visible=true] - Set to true if the layer is visible on the - * map, false if it is hidden. This applies to raster (imagery) and vector assets, - * not to terrain assets. - * @property {string} [description] - A brief description about the asset, e.g. - * which area it covers, the resolution, etc. - * @property {string} [attribution] A credit or attribution to display along with - * this asset. - * @property {string} [moreInfoLink] A complete URL used to create a link to show - * in a map where a user can find more information about this resource. - * @property {string} [downloadLink] A complete URL used to show a link in a map - * where a user can go to download the source data. - * @property {string} [id] If this asset's data is archived in a DataONE - * repository, the ID of the data package. - * @property {MapConfig#ColorPaletteConfig} [colorPalette] The color or colors - * mapped to attributes of this asset. This applies to raster/imagery and vector - * assets. For imagery, the colorPalette will be used to create a legend. For - * vector assets (e.g. 3Dtilesets), it will also be used to style the features. - * @property {MapConfig#FeatureTemplate} [featureTemplate] Configuration for the - * content and layout of the Feature Info panel ({@link FeatureInfoView}) - the - * panel that shows information about a selected feature from a vector asset. If - * no feature template is set, then the default table layout is used. - * @property {MapConfig#CustomProperties} [customProperties] Definitions of custom - * properties of features, potentially based on existing properties. For example, - * a custom property could be a formatted version of another date property. These - * custom properties can be used in the filters, colorPalette, or featureTemplate. - * So far, custom strings and formatted dates are supported. Eventually, the - * custom properties may be expanded to support formatted numbers and booleans. - * @property {MapConfig#VectorFilterConfig} [filters] - A set of conditions used - * to show or hide specific features of this tileset. - * @property {MapConfig#Notification} [notification] A custom badge and message to - * display about the layer in the Layer list. For example, this could highlight - * the layer if it is new, give a warning if they layer is under development, etc. - */ - - /** - * A feature template configures the format and content of information displayed - * in the Feature Info panel ({@link FeatureInfoView}). The Feature Info panel is - * displayed in a map when a user clicks on a vector feature in a map. - * @typedef {Object} FeatureTemplate - * @name MapConfig#FeatureTemplate - * @since 2.19.0 - * @property {'story'|'table'} [template='table'] The name/ID of the template to - * use. This must match the name of one of the templates available in - * {@link FeatureInfoView#contentTemplates}. - * @property {string} [label] Sets which of the feature properties to use as the - * title for the FeatureInfoView. The string must exactly match the key for a - * property that exists in the feature. - * @property {MapConfig#StoryTemplateOptions} [options] A list of key-value pairs - * that map the template variable to a property/attribute of the the feature. Keys - * are the template variable names and values are the names of properties in the - * feature. Template variable names are specific to each template. Currently only - * the 'story' template allows variables. These are specified in the - * {@link FeatureInfoView#contentTemplates}. - * @example - * // Use the "story" template, which shows a secondary title, image, description, - * // and link. - * { - * "template": "story", - * "label": "title", - * "options": { - * "subtitle": "formattedDate", - * "description": "summary", - * "thumbnail": "imageSrc", - * "url": "newsLink", - * "urlText": "newsTitle", - * } - * } - * @example - * // Use the default template (a table), but use the "forestName" attribute for - * // the FeatureInfo panel label - * { - * "label": "forestName" - * } - */ - - /** - * An object that maps template variable to feature properties for the "story" - * template. - * @typedef {Object} - * @name MapConfig#StoryTemplateOptions - * @since 2.19.0 - * @property {string} subtitle The name of a feature property to use for a - * secondary title in the template - * @property {string} description The name of a feature property that contains a - * brief summary or description of the feature; displayed as a paragraph. - * @property {string} thumbnail The name of a feature property that contains a URL - * for an image. Displayed as a thumbnail next to the description. - * @property {string} url The name of a feature property with a URL to use to - * create a link (e.g. to learn more information about the given feature) - * @property {string} urlText The name of a feature property that has text to - * display for the url. Defaults to 'Read More' if none is set. - */ - - /** - * An object where the keys indicate the name/ID of the new custom property to - * create, and the values are an object that defines the new property. - * @typedef {Object.} CustomProperties - * @name MapConfig#CustomProperties - * @since 2.19.0 - * @example - * { - * "year": { - * "type": "date", - * "property": "dateTime", - * "format": "YYYY", - * }, - * "urlText": { - * "type": "string", - * "value": "Click here to learn more about this feature" - * } - * } - */ - - /** - * An object that defines a formatted date to use as a property in a feature. Used - * in the {@link MapConfig#CustomProperties} object. - * @typedef {Object} CustomDateProperty - * @name MapConfig#CustomDateProperty - * @since 2.19.0 - * @property {'date'} type Must be set to 'date' to indicate that this is a custom - * date property - * @property {string} property The name/ID of the existing date property to format - * @property {string} format A string that indicates the new format to use. - * Follows the syntax used by Day.JS, see - * {@link https://day.js.org/docs/en/display/format} - */ - - /** - * An object that defines a custom string to use as a property in a feature. Used - * in the {@link MapConfig#CustomProperties} object. - * @typedef {Object} CustomStringProperty - * @name MapConfig#CustomStringProperty - * @since 2.19.0 - * @property {'string'} type Must be set to 'string' to indicate that this is a - * custom string property - * @property {string} value The new string to use. So far only static strings are - * available. In the future, templates that include other properties may be - * supported. - */ - - /** +"use strict"; + +define([ + "underscore", + "backbone", + "models/portals/PortalImage", + "models/maps/AssetColorPalette", + MetacatUI.root + "/components/dayjs.min.js", +], function (_, Backbone, PortalImage, AssetColorPalette, dayjs) { + /** + * @classdesc A MapAsset Model comprises information required to fetch source data for + * some asset or resource that is displayed in a map, such as imagery (raster) tiles, + * vector data, a 3D tileset, or a terrain model. This model also contains metadata + * about the source data, like an attribution and a description. It represents the + * most generic type of asset, and can be extended to support specific assets that are + * compatible with a given map widget. + * @classcategory Models/Maps/Assets + * @class MapAsset + * @name MapAsset + * @extends Backbone.Model + * @since 2.18.0 + * @constructor + */ + var MapAsset = Backbone.Model.extend( + /** @lends MapAsset.prototype */ { + /** + * The name of this type of model + * @type {string} + */ + type: "MapAsset", + + /** + * Default attributes for MapAsset models + * @name MapAsset#defaults + * @type {Object} + * @property {('Cesium3DTileset'|'BingMapsImageryProvider'|'IonImageryProvider'|'WebMapTileServiceImageryProvider'|'TileMapServiceImageryProvider'|'CesiumTerrainProvider')} type + * The format of the data. Must be one of the supported types. + * @property {string} label A user friendly name for this asset, to be displayed + * in a map. + * @property {string} [icon = ''] + * A PID for an SVG saved as a dataObject, or an SVG string. The SVG will be used + * as an icon that will be displayed next to the label in the layers list. It + * should be an SVG file that has no fills, borders, or styles set on it (since + * the icon will be shaded dynamically by the maps CSS using a fill attribute). It + * must use a viewbox property rather than a height and width. + * @property {string} [description = ''] A brief description about the asset, e.g. + * which area it covers, the resolution, etc. + * @property {string} [attribution = ''] A credit or attribution to display along + * with this map resource. + * @property {string} [moreInfoLink = ''] A link to show in a map where a user can + * find more information about this resource. + * @property {string} [downloadLink = ''] A link to show in a map where a user can + * go to download the source data. + * @property {string} [id = ''] If this asset's data is archived in a DataONE + * repository, the ID of the data package. + * @property {Boolean} [selected = false] Set to true when this asset has been + * selected by the user in the layer list. + * @property {Number} [opacity = 1] A number between 0 and 1 indicating the + * opacity of the layer on the map, with 0 representing fully transparent and 1 + * representing fully opaque. This applies to raster (imagery) and vector assets, + * not to terrain assets. + * @property {Boolean} [visible = true] Set to true if the layer is visible on the + * map, false if it is hidden. This applies to raster (imagery) and vector assets, + * not to terrain assets. + * @property {AssetColorPalette} [colorPalette] The color or colors mapped to + * attributes of this asset. This applies to raster/imagery and vector assets. For + * imagery, the colorPalette will be used to create a legend. For vector assets + * (e.g. 3Dtilesets), it will also be used to style the features. + * @property {MapConfig#FeatureTemplate} [featureTemplate] Configuration for + * content and layout of the Feature Info panel - the panel that shows information + * about a selected feature from a vector asset ({@link FeatureInfoView}). + * @property {Cesium.Entity|Cesium.3DTilesetFeature} [featureType] For vector + * and 3d tileset assets, the object type that cesium uses to represent features + * from the asset. Null for imagery and terrain assets. + * @property {MapConfig#CustomProperties} [customProperties] Configuration that + * allows for the definition of custom feature properties, potentially based on + * other properties. For example, a custom property could be a formatted version + * of an existing date property. + * @property {MapConfig#Notification} [notification] A custom badge and message to + * display about the layer in the Layer list. For example, this could highlight + * the layer if it is new, give a warning if they layer is under development, etc. + * @property {'ready'|'error'|null} [status = null] Set to 'ready' when the + * resource is loaded and ready to be rendered in a map view. Set to 'error' when + * the asset is not supported, or there was a problem requesting the resource. + * @property {string} [statusDetails = null] Any further details about the status, + * especially when there was an error. + */ + defaults: function () { + return { + type: "", + label: "", + icon: '', + description: "", + attribution: "", + moreInfoLink: "", + downloadLink: "", + id: "", + selected: false, + opacity: 1, + visible: true, + colorPalette: null, + customProperties: {}, + featureTemplate: {}, + featureType: null, + notification: {}, + status: null, + statusDetails: null, + }; + }, + + /** + * The source of a specific asset (i.e. layer or terrain data) to show on the map, + * as well as metadata and display properties of the asset. Some properties listed + * here do not apply to all asset types, but this is specified in the property + * description. + * @typedef {Object} MapAssetConfig + * @name MapConfig#MapAssetConfig + * @property {('Cesium3DTileset'|'BingMapsImageryProvider'|'IonImageryProvider'|'WebMapTileServiceImageryProvider'|'WebMapServiceImageryProvider'|'TileMapServiceImageryProvider'|'NaturalEarthII'|'CesiumTerrainProvider'|'GeoJsonDataSource'|'USGSImageryTopo'|'OpenStreetMapImageryProvider')} type - + * A string indicating the format of the data. Some of these types correspond + * directly to Cesium classes. The NaturalEarthII type is a special imagery layer + * that automatically sets the cesiumOptions to load the Natural Earth II imagery + * that is shipped with Cesium/MetacatUI. If this type is set, then no other + * cesiumOptions are required. The same is true for USGSImageryTopo, which pulls + * imagery directly from USGS. + * @property {(Cesium3DTileset#cesiumOptions|CesiumImagery#cesiumOptions|CesiumTerrain#cesiumOptions|CesiumVectorData#cesiumOptions)} [cesiumOptions] - + * For MapAssets that are configured for Cesium, like + * Cesium3DTilesets, an object with options to pass to the Cesium constructor + * function that creates the Cesium model. Options are specific to each type of + * asset. For details, see documentation for each of the types. + * @property {string} label - A user friendly name for this asset, to be displayed + * in a map. + * @property {string} [icon] - A PID for an SVG saved as a dataObject, or an SVG + * string. The SVG will be used as an icon that will be displayed next to the + * label in the layers list. It should be an SVG file that has no fills, borders, + * or styles set on it (since the icon will be shaded dynamically by the maps CSS + * using a fill attribute). It must use a viewbox property rather than a height + * and width. + * @property {Number} [opacity=1] - A number between 0 and 1 indicating the + * opacity of the layer on the map, with 0 representing fully transparent and 1 + * representing fully opaque. This applies to raster (imagery) and vector assets, + * not to terrain assets. + * @property {Boolean} [visible=true] - Set to true if the layer is visible on the + * map, false if it is hidden. This applies to raster (imagery) and vector assets, + * not to terrain assets. + * @property {string} [description] - A brief description about the asset, e.g. + * which area it covers, the resolution, etc. + * @property {string} [attribution] A credit or attribution to display along with + * this asset. + * @property {string} [moreInfoLink] A complete URL used to create a link to show + * in a map where a user can find more information about this resource. + * @property {string} [downloadLink] A complete URL used to show a link in a map + * where a user can go to download the source data. + * @property {string} [id] If this asset's data is archived in a DataONE + * repository, the ID of the data package. + * @property {MapConfig#ColorPaletteConfig} [colorPalette] The color or colors + * mapped to attributes of this asset. This applies to raster/imagery and vector + * assets. For imagery, the colorPalette will be used to create a legend. For + * vector assets (e.g. 3Dtilesets), it will also be used to style the features. + * @property {MapConfig#FeatureTemplate} [featureTemplate] Configuration for the + * content and layout of the Feature Info panel ({@link FeatureInfoView}) - the + * panel that shows information about a selected feature from a vector asset. If + * no feature template is set, then the default table layout is used. + * @property {MapConfig#CustomProperties} [customProperties] Definitions of custom + * properties of features, potentially based on existing properties. For example, + * a custom property could be a formatted version of another date property. These + * custom properties can be used in the filters, colorPalette, or featureTemplate. + * So far, custom strings and formatted dates are supported. Eventually, the + * custom properties may be expanded to support formatted numbers and booleans. + * @property {MapConfig#VectorFilterConfig} [filters] - A set of conditions used + * to show or hide specific features of this tileset. + * @property {MapConfig#Notification} [notification] A custom badge and message to + * display about the layer in the Layer list. For example, this could highlight + * the layer if it is new, give a warning if they layer is under development, etc. + */ + + /** + * A feature template configures the format and content of information displayed + * in the Feature Info panel ({@link FeatureInfoView}). The Feature Info panel is + * displayed in a map when a user clicks on a vector feature in a map. + * @typedef {Object} FeatureTemplate + * @name MapConfig#FeatureTemplate + * @since 2.19.0 + * @property {'story'|'table'} [template='table'] The name/ID of the template to + * use. This must match the name of one of the templates available in + * {@link FeatureInfoView#contentTemplates}. + * @property {string} [label] Sets which of the feature properties to use as the + * title for the FeatureInfoView. The string must exactly match the key for a + * property that exists in the feature. + * @property {MapConfig#StoryTemplateOptions} [options] A list of key-value pairs + * that map the template variable to a property/attribute of the the feature. Keys + * are the template variable names and values are the names of properties in the + * feature. Template variable names are specific to each template. Currently only + * the 'story' template allows variables. These are specified in the + * {@link FeatureInfoView#contentTemplates}. + * @example + * // Use the "story" template, which shows a secondary title, image, description, + * // and link. + * { + * "template": "story", + * "label": "title", + * "options": { + * "subtitle": "formattedDate", + * "description": "summary", + * "thumbnail": "imageSrc", + * "url": "newsLink", + * "urlText": "newsTitle", + * } + * } + * @example + * // Use the default template (a table), but use the "forestName" attribute for + * // the FeatureInfo panel label + * { + * "label": "forestName" + * } + */ + + /** + * An object that maps template variable to feature properties for the "story" + * template. + * @typedef {Object} + * @name MapConfig#StoryTemplateOptions + * @since 2.19.0 + * @property {string} subtitle The name of a feature property to use for a + * secondary title in the template + * @property {string} description The name of a feature property that contains a + * brief summary or description of the feature; displayed as a paragraph. + * @property {string} thumbnail The name of a feature property that contains a URL + * for an image. Displayed as a thumbnail next to the description. + * @property {string} url The name of a feature property with a URL to use to + * create a link (e.g. to learn more information about the given feature) + * @property {string} urlText The name of a feature property that has text to + * display for the url. Defaults to 'Read More' if none is set. + */ + + /** + * An object where the keys indicate the name/ID of the new custom property to + * create, and the values are an object that defines the new property. + * @typedef {Object.} CustomProperties + * @name MapConfig#CustomProperties + * @since 2.19.0 + * @example + * { + * "year": { + * "type": "date", + * "property": "dateTime", + * "format": "YYYY", + * }, + * "urlText": { + * "type": "string", + * "value": "Click here to learn more about this feature" + * } + * } + */ + + /** + * An object that defines a formatted date to use as a property in a feature. Used + * in the {@link MapConfig#CustomProperties} object. + * @typedef {Object} CustomDateProperty + * @name MapConfig#CustomDateProperty + * @since 2.19.0 + * @property {'date'} type Must be set to 'date' to indicate that this is a custom + * date property + * @property {string} property The name/ID of the existing date property to format + * @property {string} format A string that indicates the new format to use. + * Follows the syntax used by Day.JS, see + * {@link https://day.js.org/docs/en/display/format} + */ + + /** + * An object that defines a custom string to use as a property in a feature. Used + * in the {@link MapConfig#CustomProperties} object. + * @typedef {Object} CustomStringProperty + * @name MapConfig#CustomStringProperty + * @since 2.19.0 + * @property {'string'} type Must be set to 'string' to indicate that this is a + * custom string property + * @property {string} value The new string to use. So far only static strings are + * available. In the future, templates that include other properties may be + * supported. + */ + + /** * A notification displays a badge in the {@link LayerListView} and a message in * the {@link LayerDetailsView}. This is useful for indicating some special status * of the layer: "new", "under development", etc. @@ -309,551 +298,596 @@ define( * @param {MapConfig#MapAssetConfig} [assetConfig] The initial values of the * attributes, which will be set on the model. */ - initialize: function (assetConfig) { - try { - - const model = this; - - if (!assetConfig || typeof assetConfig !== 'object') { - assetConfig = {} - } else { - assetConfig = JSON.parse(JSON.stringify(assetConfig)) - } - - // Set the color palette - if (assetConfig.colorPalette) { - this.set('colorPalette', new AssetColorPalette(assetConfig.colorPalette)) - } - - // Fetch the icon, if there is one - if (assetConfig.icon) { - if (model.isSVG(assetConfig.icon)) { - model.updateIcon(assetConfig.icon) - } else { - // If the string is not an SVG then assume it is a PID and try to fetch - // the SVG file. fetchIcon will update the icon when complete. - model.fetchIcon(assetConfig.icon) - } - } + initialize: function (assetConfig) { + try { + const model = this; - this.setListeners(); - } - catch (e) { - console.log('Error initializing a MapAsset model', e); + if (!assetConfig || typeof assetConfig !== "object") { + assetConfig = {}; + } else { + assetConfig = JSON.parse(JSON.stringify(assetConfig)); } - }, - - /** - * Set all of the listeners for this model - * @since x.x.x - */ - setListeners: function () { - try { - - // The map asset cannot be visible on the map if there was an error loading - // the asset - this.stopListening(this, 'change:status') - this.listenTo(this, 'change:status', function (model, status) { - if (status === 'error') { - this.set('visible', false) - } - }) - this.stopListening(this, 'change:visible') - this.listenTo(this, 'change:visible', function (model, visible) { - if (this.get('status') === 'error') { - this.set('visible', false) - } - }) - - // Update the style of the asset to highlight the selected features when - // features from this asset are selected in the map. - if (typeof this.updateAppearance === 'function') { - const setSelectFeaturesListeners = function () { - const mapModel = this.get('mapModel'); - if (!mapModel) { return } - const interactions = mapModel.get('interactions'); - const selectedFeatures = mapModel.getSelectedFeatures(); - - this.stopListening(selectedFeatures, 'update'); - this.listenTo(selectedFeatures, 'update', this.updateAppearance) - - this.stopListening(interactions, 'change:selectedFeatures') - this.listenTo(interactions, 'change:selectedFeatures', function () { - this.updateAppearance() - setSelectFeaturesListeners() - }) + // Set the color palette + if (assetConfig.colorPalette) { + this.set( + "colorPalette", + new AssetColorPalette(assetConfig.colorPalette) + ); + } - } - setSelectFeaturesListeners.call(this) - this.stopListening(this, 'change:mapModel', setSelectFeaturesListeners) - this.listenTo(this, 'change:mapModel', setSelectFeaturesListeners) + // Fetch the icon, if there is one + if (assetConfig.icon) { + if (model.isSVG(assetConfig.icon)) { + model.updateIcon(assetConfig.icon); + } else { + // If the string is not an SVG then assume it is a PID and try to fetch + // the SVG file. fetchIcon will update the icon when complete. + model.fetchIcon(assetConfig.icon); } - - // Listen for changes to the cesiumOptions object - this.stopListening(this, 'change:cesiumOptions'); - this.listenTo(this, 'change:cesiumOptions', function () { - this.createCesiumModel(true) - }) - } catch (e) { - console.log("Error setting MapAsset Listeners.", e); } - }, - - /** - * Get the asset config's cesiumOptions, if it has any. This will return - * a copy of the cesiumOptions object, so that changes made to the - * returned object will not affect the original cesiumOptions object. - * @returns {Object} A copy of the cesiumOptions object, or null if there - * are no cesiumOptions. - * @since 2.26.0 - */ - getCesiumOptions: function () { - const cesiumOptions = this.get('cesiumOptions') - if (!cesiumOptions) { return null } - return JSON.parse(JSON.stringify(cesiumOptions)) - }, - - /** - * Given a feature object from a Feature model, checks if it is part of the - * selectedFeatures collection. See featureObject property from - * {@link Feature#defaults}. For vector and 3d tile models only. - * @param {*} feature - An object that a Map widget uses to represent this feature - * in the map, e.g. a Cesium.Entity or a Cesium.Cesium3DTileFeature - * @returns {boolean} Returns true if the given feature is part of the - * selectedFeatures collection in this asset - */ - featureIsSelected: function (feature) { - const map = this.get('mapModel') - if (!map) { return false } - return map.getSelectedFeatures(); - }, - - /** - * Checks if a feature from the map (a Cesium object) is the type of - * feature that this map asset model contains. For example, if a - * Cesium3DTilesetFeature is passed to this function, this function - * will return true if it is a Cesium3DTileset model, and false if it - * is a CesiumVectorData model. - * @param {Cesium.Cesium3DTilesetFeature|Cesium.Entity} feature - * @returns {boolean} true if the feature is an instance of the feature - * type set on the asset model, false otherwise. - * @since 2.25.0 - */ - usesFeatureType: function(feature) { - const ft = this.get("featureType"); - if (!feature || !ft) return false - if (!feature instanceof ft) return false - return true - }, - - /** - * Given a feature object from a Feature model, checks if it is part of the - * selectedFeatures collection. See featureObject property from - * {@link Feature#defaults}. For vector and 3d tile models only. - * @param {*} feature - An object that a Map widget uses to represent this feature - * in the map, e.g. a Cesium.Entity or a Cesium.Cesium3DTileFeature - * @returns {boolean} Returns true if the given feature is part of the - * selectedFeatures collection in this asset - * @since 2.25.0 - */ - containsFeature: function (feature) { - if (!this.usesFeatureType(feature)) return false - if (!this.getCesiumModelFromFeature) return false - const cesiumModel = this.getCesiumModelFromFeature(feature) - if (!cesiumModel) return false - if (this.get('cesiumModel') == cesiumModel) return true - return false - }, - - /** - * Given a feature object from a Feature model, returns the attributes - * needed to create a Feature model. For vector and 3d tile models only. - * @param {*} feature - An object that a Map widget uses to represent this feature - * in the map, e.g. a Cesium.Entity or a Cesium.Cesium3DTileFeature - * @returns {Object} An object with properties, mapAsset, featureID, featureObject, - * and label properties. Returns null if the feature is not the correct type - * for this asset model. - */ - getFeatureAttributes: function (feature) { - if (!this.usesFeatureType(feature)) return null; - if (!this.getCesiumModelFromFeature) return null; - return { - properties: this.getPropertiesFromFeature(feature), - mapAsset: this, - featureID: this.getIDFromFeature(feature), - featureObject: feature, - label: this.getLabelFromFeature(feature), + this.setListeners(); + } catch (e) { + console.log("Error initializing a MapAsset model", e); + } + }, + + /** + * When the asset can't be loaded, hide it from the map and show an error. + * @since x.x.x + */ + handleError: function () { + this.set("visible", false); + this.stopListening(this, "change:visible"); + }, + + /** + * Set all of the listeners for this model + * @since x.x.x + */ + setListeners: function () { + try { + const status = this.get("status"); + if (status === "error") { + this.handleError(); + return; } - }, - - /** - * Given a set of properties from a Feature from this Map Asset model, add any - * custom properties to the properties object and return it. - * @since 2.19.0 - * @param {Object} properties A set of key-value pairs representing the existing - * properties of a feature from this asset. - * @returns {Object} The properties object with any custom properties added. - */ - addCustomProperties: function (properties) { - try { - - const model = this; - const customProperties = model.get('customProperties'); - const formattedProperties = {}; - - if (!customProperties || !Object.keys(customProperties).length) { - return properties + // The map asset cannot be visible on the map if there was an error + // loading the asset + this.stopListening(this, "change:status"); + this.listenTo(this, "change:status", function (model, status) { + if (status === "error") { + this.handleError(); + } else { + this.setListeners(); } + }); - if (!properties || typeof properties !== 'object') { - properties = {} - } + // Listen for changes to the cesiumOptions object + this.stopListening(this, "change:cesiumOptions"); + this.listenTo(this, "change:cesiumOptions", function () { + this.createCesiumModel(true); + }); - if (customProperties) { - _.each(customProperties, function (config, key) { - let formattedValue = ''; - if (config.type === 'date') { - formattedValue = model.formatDateProperty(config, properties) - // TODO: support formatted numbers and booleans... - // } else if (config.type === 'number') { - // formattedValue = model.formatNumberProperty(config, properties) - // } else if (config.type === 'boolean') { - // formattedValue = model.formatBooleanProperty(config, properties) - } else { - formattedValue = model.formatStringProperty(config, properties) - } - formattedProperties[key] = formattedValue; - }); - } - // merge the properties with the formatted properties - return Object.assign(properties, formattedProperties); - } catch (error) { - console.log( - 'There was an error adding custom properties. Returning properties ' + - 'unchanged. Error details: ' + - error - ); - return properties - } - }, + this.listenToSelectedFeatures(); + } catch (e) { + console.log("Error setting MapAsset Listeners.", e); + } + }, + + /** + * Update the appearance of features from this asset when they are + * selected or deselected in the map widget. + * @since x.x.x + */ + listenToSelectedFeatures: function () { + if (typeof this.updateAppearance !== "function") { + return; + } + + const mapModel = this.get("mapModel"); + if (!mapModel) { + this.listenToOnce( + this, + "change:mapModel", + this.listenToSelectedFeatures + ); + return; + } + + const interactions = mapModel.get("interactions"); + + if (!interactions) { + this.listenToOnce( + mapModel, + "change:interactions", + this.listenToSelectedFeatures + ); + return; + } + + const selectedFeatures = mapModel.getSelectedFeatures(); + + if(selectedFeatures){ + this.stopListening(selectedFeatures, "update"); + this.listenTo(selectedFeatures, "update", this.updateAppearance); + } + + // Reset the listeners if the selectedFeatures collection or the + // interactions model is replaced + this.listenToOnce(interactions, "change:selectedFeatures", function () { + this.updateAppearance(); + this.listenToSelectedFeatures(); + }); + + this.listenToOnce(mapModel, "change:interactions", function () { + this.updateAppearance(); + this.listenToSelectedFeatures(); + }); + }, + + /** + * Get the asset config's cesiumOptions, if it has any. This will return + * a copy of the cesiumOptions object, so that changes made to the + * returned object will not affect the original cesiumOptions object. + * @returns {Object} A copy of the cesiumOptions object, or null if there + * are no cesiumOptions. + * @since 2.26.0 + */ + getCesiumOptions: function () { + const cesiumOptions = this.get("cesiumOptions"); + if (!cesiumOptions) { + return null; + } + return JSON.parse(JSON.stringify(cesiumOptions)); + }, + + /** + * Given a feature object from a Feature model, checks if it is part of the + * selectedFeatures collection. See featureObject property from + * {@link Feature#defaults}. For vector and 3d tile models only. + * @param {*} feature - An object that a Map widget uses to represent this feature + * in the map, e.g. a Cesium.Entity or a Cesium.Cesium3DTileFeature + * @returns {boolean} Returns true if the given feature is part of the + * selectedFeatures collection in this asset + */ + featureIsSelected: function (feature) { + const map = this.get("mapModel"); + if (!map) { + return false; + } + const selectedFeatures = map.getSelectedFeatures(); + if (!selectedFeatures) { + return false; + } + const isSelected = selectedFeatures.containsFeature(feature); + return isSelected; + }, + + /** + * Checks if a feature from the map (a Cesium object) is the type of + * feature that this map asset model contains. For example, if a + * Cesium3DTilesetFeature is passed to this function, this function + * will return true if it is a Cesium3DTileset model, and false if it + * is a CesiumVectorData model. + * @param {Cesium.Cesium3DTilesetFeature|Cesium.Entity} feature + * @returns {boolean} true if the feature is an instance of the feature + * type set on the asset model, false otherwise. + * @since 2.25.0 + */ + usesFeatureType: function (feature) { + const ft = this.get("featureType"); + if (!feature || !ft) return false; + if (!feature instanceof ft) return false; + return true; + }, + + /** + * Given a feature object from a Feature model, checks if it is part of the + * selectedFeatures collection. See featureObject property from + * {@link Feature#defaults}. For vector and 3d tile models only. + * @param {*} feature - An object that a Map widget uses to represent this feature + * in the map, e.g. a Cesium.Entity or a Cesium.Cesium3DTileFeature + * @returns {boolean} Returns true if the given feature is part of the + * selectedFeatures collection in this asset + * @since 2.25.0 + */ + containsFeature: function (feature) { + if (!this.usesFeatureType(feature)) return false; + if (!this.getCesiumModelFromFeature) return false; + const cesiumModel = this.getCesiumModelFromFeature(feature); + if (!cesiumModel) return false; + if (this.get("cesiumModel") == cesiumModel) return true; + return false; + }, + + /** + * Given a feature object from a Feature model, returns the attributes + * needed to create a Feature model. For vector and 3d tile models only. + * @param {*} feature - An object that a Map widget uses to represent this feature + * in the map, e.g. a Cesium.Entity or a Cesium.Cesium3DTileFeature + * @returns {Object} An object with properties, mapAsset, featureID, featureObject, + * and label properties. Returns null if the feature is not the correct type + * for this asset model. + */ + getFeatureAttributes: function (feature) { + if (!this.usesFeatureType(feature)) return null; + if (!this.getCesiumModelFromFeature) return null; + return { + properties: this.getPropertiesFromFeature(feature), + mapAsset: this, + featureID: this.getIDFromFeature(feature), + featureObject: feature, + label: this.getLabelFromFeature(feature), + }; + }, + + /** + * Given a set of properties from a Feature from this Map Asset model, add any + * custom properties to the properties object and return it. + * @since 2.19.0 + * @param {Object} properties A set of key-value pairs representing the existing + * properties of a feature from this asset. + * @returns {Object} The properties object with any custom properties added. + */ + addCustomProperties: function (properties) { + try { + const model = this; + const customProperties = model.get("customProperties"); + const formattedProperties = {}; - /** - * Given a definition for a new date property, and the properties that already - * exist on a specific feature, returns a new string with the formatted date. - * @since 2.19.0 - * @param {MapConfig#CustomDateProperty} config - An object that defines the new - * date property to create - * @param {Object} properties key-value pairs representing existing properties in - * a Feature - * @returns {string} The value for the new date property, formatted as defined by - * config, for the given feature - */ - formatDateProperty: function (config, properties) { - try { - if (!properties) { - properties = {} - } - let formattedDate = '' - if (!config || !config.format) { - return formattedDate; - } - const value = properties[config.property]; - if (value) { - formattedDate = dayjs(value).format(config.format); - } - return formattedDate; - } - catch (error) { - console.log( - 'There was an error formatting a date for a Feature model' + - '. Error details: ' + error - ); - return ''; + if (!customProperties || !Object.keys(customProperties).length) { + return properties; } - }, - /** - * For a given set of Feature properties and a definition for a new sting - * property, returns the value of the custom property. Note that since only static - * strings are supported so far, this function essentially just returns the value - * of config.value. This function exists to allow support of dynamic strings in - * the future (e.g. combining strings from existing properties) - * @since 2.19.0 - * @param {MapConfig#CustomStringProperty} config The object the defines the new - * custom property - * @param {Object} properties key-value pairs representing existing properties in - * a Feature - * @returns {string} The new string for the given Feature property - */ - formatStringProperty: function (config, properties) { - try { - if (!properties) { - properties = {} - } - let formattedString = '' - if (!config || !config.value) { - return formattedString; - } - formattedString = config.value; - return formattedString; + if (!properties || typeof properties !== "object") { + properties = {}; } - catch (error) { - console.log( - 'There was an error formatting a string for a Feature model' + - '. Error details: ' + error - ); - return ''; - } - }, - - // formatNumberProperty: function (config, properties) { - // try { - // if (!properties) { - // properties = {} - // } - // let formattedNumber = '' - // // TODO... - // } - // catch (error) { - // console.log( - // 'There was an error formatting a number for a Feature model' + - // '. Error details: ' + error - // ); - // return ''; - // } - // }, - - // formatBooleanProperty: function (config, properties) { - // try { - // if (!properties) { - // properties = {} - // } - // let formattedBoolean = '' - // // TODO... - // } - // catch (error) { - // console.log( - // 'There was an error formatting a boolean for a Feature model' + - // '. Error details: ' + error - // ); - // return ''; - // } - // }, - /** - * Sanitizes an SVG string and updates the model's 'icon' attribute the sanitized - * string. Also sets the 'iconStatus' attribute to 'success'. - * @param {string} icon An SVG string to use for the MapAsset icon - */ - updateIcon: function (icon) { - const model = this - try { - model.sanitizeIcon(icon, function (sanitizedIcon) { - model.set('icon', sanitizedIcon) - model.set('iconStatus', 'success') - }) + if (customProperties) { + _.each(customProperties, function (config, key) { + let formattedValue = ""; + if (config.type === "date") { + formattedValue = model.formatDateProperty(config, properties); + // TODO: support formatted numbers and booleans... + // } else if (config.type === 'number') { + // formattedValue = model.formatNumberProperty(config, properties) + // } else if (config.type === 'boolean') { + // formattedValue = model.formatBooleanProperty(config, properties) + } else { + formattedValue = model.formatStringProperty(config, properties); + } + formattedProperties[key] = formattedValue; + }); } - catch (error) { - console.log( - 'There was an error updating an icon in a MapAsset model' + - '. Error details: ' + error - ); + // merge the properties with the formatted properties + return Object.assign(properties, formattedProperties); + } catch (error) { + console.log( + "There was an error adding custom properties. Returning properties " + + "unchanged. Error details: " + + error + ); + return properties; + } + }, + + /** + * Given a definition for a new date property, and the properties that already + * exist on a specific feature, returns a new string with the formatted date. + * @since 2.19.0 + * @param {MapConfig#CustomDateProperty} config - An object that defines the new + * date property to create + * @param {Object} properties key-value pairs representing existing properties in + * a Feature + * @returns {string} The value for the new date property, formatted as defined by + * config, for the given feature + */ + formatDateProperty: function (config, properties) { + try { + if (!properties) { + properties = {}; } - }, - - /** - * Simple test to see if a string is an SVG - * @param {string} str The string to check - * @returns {Boolean} Returns true if the string starts with ``, regardless of case - */ - isSVG: function (str) { - const strLower = str.toLowerCase() - return strLower.startsWith('') - }, - - /** - * Fetches an SVG given a pid, sanitizes it, then updates the model's icon - * attribute with the new and SVG string (after sanitizing it) - * @param {string} pid - */ - fetchIcon: function (pid) { - const model = this - try { - model.set('iconStatus', 'fetching') - - // Use the portal image model to get the correct baseURL for an image - const imageURL = new PortalImage({ - identifier: pid - }).get('imageURL') - - fetch(imageURL) - .then(function (response) { - return response.text(); - }) - .then(function (data) { - if (model.isSVG(data)) { - model.updateIcon(data) - } - }) - .catch(function (response) { - model.set('iconStatus', 'error') - }); + let formattedDate = ""; + if (!config || !config.format) { + return formattedDate; } - catch (error) { - console.log( - 'Failed to fetch an icon for a MapAsset' + - '. Error details: ' + error - ); - model.set('iconStatus', 'error') + const value = properties[config.property]; + if (value) { + formattedDate = dayjs(value).format(config.format); } - }, - - /** - * Takes an SVG string and returns it with only the allowed tags and attributes - * @param {string} icon The SVG icon string to sanitize - * @param {function} callback Function to call once the icon has been sanitized. - * Will pass the sanitized icon string. - */ - sanitizeIcon: function (icon, callback) { - try { - // Use the showdown xss filter to sanitize the SVG string - require(['showdown', 'showdownXssFilter'], function (showdown, showdownXss) { - var converter = new showdown.Converter({ - extensions: ['xssfilter'] - }); - let sanitizedIcon = converter.makeHtml(icon); - // Remove the

tags that showdown wraps the string in - sanitizedIcon = sanitizedIcon.replace(/^(

)/, '') - sanitizedIcon = sanitizedIcon.replace(/(<\/p>)$/, '') - // Call the callback - if (callback && typeof callback === 'function') { - callback(sanitizedIcon) - } - }) + return formattedDate; + } catch (error) { + console.log( + "There was an error formatting a date for a Feature model" + + ". Error details: " + + error + ); + return ""; + } + }, + + /** + * For a given set of Feature properties and a definition for a new sting + * property, returns the value of the custom property. Note that since only static + * strings are supported so far, this function essentially just returns the value + * of config.value. This function exists to allow support of dynamic strings in + * the future (e.g. combining strings from existing properties) + * @since 2.19.0 + * @param {MapConfig#CustomStringProperty} config The object the defines the new + * custom property + * @param {Object} properties key-value pairs representing existing properties in + * a Feature + * @returns {string} The new string for the given Feature property + */ + formatStringProperty: function (config, properties) { + try { + if (!properties) { + properties = {}; } - catch (error) { - console.log( - 'There was an error sanitizing an SVG icon in a MapAsset model' + - '. Error details: ' + error - ); + let formattedString = ""; + if (!config || !config.value) { + return formattedString; } - }, - - /** - * Resets the Map Asset's status and statusDetails attributes to their default - * values. - * @since 2.21.0 - */ - resetStatus: function () { - const defaults = this.defaults() - this.set('status', defaults.status) - this.set('statusDetails', defaults.statusDetails) - }, - - /** - * Checks if the asset information has been fetched and is ready to use. - * @returns {Promise} Returns a promise that resolves to this model when ready. - */ - whenReady: function () { - const model = this; - return new Promise(function (resolve, reject) { - if (model.get('status') === 'ready') { - resolve(model) - return - } - model.stopListening(model, 'change:status') - model.listenTo(model, 'change:status', function () { - if (model.get('status') === 'ready') { - model.stopListening(model, 'change:status') - resolve(model) + formattedString = config.value; + return formattedString; + } catch (error) { + console.log( + "There was an error formatting a string for a Feature model" + + ". Error details: " + + error + ); + return ""; + } + }, + + // formatNumberProperty: function (config, properties) { + // try { + // if (!properties) { + // properties = {} + // } + // let formattedNumber = '' + // // TODO... + // } + // catch (error) { + // console.log( + // 'There was an error formatting a number for a Feature model' + + // '. Error details: ' + error + // ); + // return ''; + // } + // }, + + // formatBooleanProperty: function (config, properties) { + // try { + // if (!properties) { + // properties = {} + // } + // let formattedBoolean = '' + // // TODO... + // } + // catch (error) { + // console.log( + // 'There was an error formatting a boolean for a Feature model' + + // '. Error details: ' + error + // ); + // return ''; + // } + // }, + + /** + * Sanitizes an SVG string and updates the model's 'icon' attribute the sanitized + * string. Also sets the 'iconStatus' attribute to 'success'. + * @param {string} icon An SVG string to use for the MapAsset icon + */ + updateIcon: function (icon) { + const model = this; + try { + model.sanitizeIcon(icon, function (sanitizedIcon) { + model.set("icon", sanitizedIcon); + model.set("iconStatus", "success"); + }); + } catch (error) { + console.log( + "There was an error updating an icon in a MapAsset model" + + ". Error details: " + + error + ); + } + }, + + /** + * Simple test to see if a string is an SVG + * @param {string} str The string to check + * @returns {Boolean} Returns true if the string starts with ``, regardless of case + */ + isSVG: function (str) { + const strLower = str.toLowerCase(); + return strLower.startsWith(""); + }, + + /** + * Fetches an SVG given a pid, sanitizes it, then updates the model's icon + * attribute with the new and SVG string (after sanitizing it) + * @param {string} pid + */ + fetchIcon: function (pid) { + const model = this; + try { + model.set("iconStatus", "fetching"); + + // Use the portal image model to get the correct baseURL for an image + const imageURL = new PortalImage({ + identifier: pid, + }).get("imageURL"); + + fetch(imageURL) + .then(function (response) { + return response.text(); + }) + .then(function (data) { + if (model.isSVG(data)) { + model.updateIcon(data); } }) + .catch(function (response) { + model.set("iconStatus", "error"); + }); + } catch (error) { + console.log( + "Failed to fetch an icon for a MapAsset" + + ". Error details: " + + error + ); + model.set("iconStatus", "error"); + } + }, + + /** + * Takes an SVG string and returns it with only the allowed tags and attributes + * @param {string} icon The SVG icon string to sanitize + * @param {function} callback Function to call once the icon has been sanitized. + * Will pass the sanitized icon string. + */ + sanitizeIcon: function (icon, callback) { + try { + // Use the showdown xss filter to sanitize the SVG string + require(["showdown", "showdownXssFilter"], function ( + showdown, + showdownXss + ) { + var converter = new showdown.Converter({ + extensions: ["xssfilter"], + }); + let sanitizedIcon = converter.makeHtml(icon); + // Remove the

tags that showdown wraps the string in + sanitizedIcon = sanitizedIcon.replace(/^(

)/, ""); + sanitizedIcon = sanitizedIcon.replace(/(<\/p>)$/, ""); + // Call the callback + if (callback && typeof callback === "function") { + callback(sanitizedIcon); + } }); - }, - - /** - * Given properties of a Feature model from this MapAsset, returns the color - * associated with that feature. - * @param {Object} properties The properties of the feature to get the color for; - * An object containing key-value mapping of property names to properties. (See - * the 'properties' attribute of {@link Feature#defaults}.) - * @returns {AssetColor#Color} The color associated with the given set of - * properties. - */ - getColor: function (properties) { - try { - const model = this - const colorPalette = model.get('colorPalette') - return ( - colorPalette?.getColor(properties) || - new AssetColorPalette().getDefaultColor() - ) - } - catch (e) { - console.log('Failed to a color in a MapAsset model', e); - } - }, - - /** - * This function checks whether a feature from the MapAsset is visible on the map - * based on the properties of the feature and the MapAsset's filter settings. - * @param {Object} properties The properties of the feature to be filtered. (See - * the 'properties' attribute of {@link Feature#defaults}.) - * @returns {boolean} Returns true if the feature passes all the filters, or if - * there are no filters set for this MapAsset. Returns false if the feature fails - * any of the filters. - */ - featureIsVisible: function (properties) { - const model = this - const filters = model.get('filters') - if (filters && filters.length) { - return filters.featureIsVisible(properties) - } else { - return true - } - }, - - /** - * Indicate that the map widget should navigate to a given feature from - * this MapAsset. - * @param {Feature} feature The feature to navigate to. - * @since x.x.x - */ - zoomTo: function (target) { - this.get('mapModel')?.zoomTo(target) - }, - - /** - * Checks that the visible attribute is set to true and that the opacity attribute - * is greater than zero. If both conditions are met, returns true. - * @returns {boolean} Returns true if the MapAsset has opacity > 0 and is visible. - */ - isVisible: function () { - if(this.get('temporarilyHidden') === true) return false - return this.get('visible') && this.get('opacity') > 0 - }, - - /** - * Make sure the layer is visible. Sets visibility to true if false, and sets - * opacity to 0.5 if it's less than 0.05. - */ - show: function () { - // If the opacity is very low, set it to 50% - if (this.get('opacity') < 0.05) { - this.set('opacity', 0.5) - } - // Make sure the layer is visible - if (this.get('visible') === false) { - this.set('visible', true) + } catch (error) { + console.log( + "There was an error sanitizing an SVG icon in a MapAsset model" + + ". Error details: " + + error + ); + } + }, + + /** + * Resets the Map Asset's status and statusDetails attributes to their default + * values. + * @since 2.21.0 + */ + resetStatus: function () { + const defaults = this.defaults(); + this.set("status", defaults.status); + this.set("statusDetails", defaults.statusDetails); + }, + + /** + * Checks if the asset information has been fetched and is ready to use. + * @returns {Promise} Returns a promise that resolves to this model when ready. + */ + whenReady: function () { + const model = this; + return new Promise(function (resolve, reject) { + if (model.get("status") === "ready") { + resolve(model); + return; } - }, - - }); - - return MapAsset; - - } -); + model.stopListening(model, "change:status"); + model.listenTo(model, "change:status", function () { + if (model.get("status") === "ready") { + model.stopListening(model, "change:status"); + resolve(model); + } + }); + }); + }, + + /** + * Given properties of a Feature model from this MapAsset, returns the color + * associated with that feature. + * @param {Object} properties The properties of the feature to get the color for; + * An object containing key-value mapping of property names to properties. (See + * the 'properties' attribute of {@link Feature#defaults}.) + * @returns {AssetColor#Color} The color associated with the given set of + * properties. + */ + getColor: function (properties) { + try { + const model = this; + const colorPalette = model.get("colorPalette"); + return ( + colorPalette?.getColor(properties) || + new AssetColorPalette().getDefaultColor() + ); + } catch (e) { + console.log("Failed to a color in a MapAsset model", e); + } + }, + + /** + * This function checks whether a feature from the MapAsset is visible on the map + * based on the properties of the feature and the MapAsset's filter settings. + * @param {Object} properties The properties of the feature to be filtered. (See + * the 'properties' attribute of {@link Feature#defaults}.) + * @returns {boolean} Returns true if the feature passes all the filters, or if + * there are no filters set for this MapAsset. Returns false if the feature fails + * any of the filters. + */ + featureIsVisible: function (properties) { + const model = this; + const filters = model.get("filters"); + if (filters && filters.length) { + return filters.featureIsVisible(properties); + } else { + return true; + } + }, + + /** + * Indicate that the map widget should navigate to a given feature from + * this MapAsset. + * @param {Feature} feature The feature to navigate to. + * @since x.x.x + */ + zoomTo: function (target) { + this.get("mapModel")?.zoomTo(target); + }, + + /** + * Checks that the visible attribute is set to true and that the opacity attribute + * is greater than zero. If both conditions are met, returns true. + * @returns {boolean} Returns true if the MapAsset has opacity > 0 and is visible. + */ + isVisible: function () { + if (this.get("temporarilyHidden") === true) return false; + return this.get("visible") && this.get("opacity") > 0; + }, + + /** + * Make sure the layer is visible. Sets visibility to true if false, and sets + * opacity to 0.5 if it's less than 0.05. + */ + show: function () { + // If the opacity is very low, set it to 50% + if (this.get("opacity") < 0.05) { + this.set("opacity", 0.5); + } + // Make sure the layer is visible + if (this.get("visible") === false) { + this.set("visible", true); + } + }, + } + ); + + return MapAsset; +});