# Source: src/js/models/maps/assets/MapAsset.js

    '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 = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.2 13.7 3.6 3.5l7.2 8.7zM5 3.3c2 .3 3.5.4 4.4.4H13l3 6.3-4 1.8-7-8.5Zm9.1.6 2.8 6 4.5-2.2a23 23 0 0 1-.7-4c0-.8-1.2-.7-2-.7l-4.6.9ZM1 15l11-1.9c1.2-.3 2.7-1 4.2-2l2.5 5.2c-1.2.3-3.6.4-7.3.4-2.8 0-7.3 0-9.4-.8a2 2 0 0 1-1-.9Zm16.4-4.2 4-1.8 1.3 5.5c0 .4.1 1-.4 1.2l-2.5.5-2.4-5.4ZM1.7 17l-.5 2.6c0 .7.2 1 1 1.3a64.1 64.1 0 0 0 19.8 0c.4-.3 1-.5.7-1.6l-.5-2.3a52.6 52.6 0 0 1-20.6 0Z"/></svg>']
             * 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 {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: '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.2 13.7 3.6 3.5l7.2 8.7zM5 3.3c2 .3 3.5.4 4.4.4H13l3 6.3-4 1.8-7-8.5Zm9.1.6 2.8 6 4.5-2.2a23 23 0 0 1-.7-4c0-.8-1.2-.7-2-.7l-4.6.9ZM1 15l11-1.9c1.2-.3 2.7-1 4.2-2l2.5 5.2c-1.2.3-3.6.4-7.3.4-2.8 0-7.3 0-9.4-.8a2 2 0 0 1-1-.9Zm16.4-4.2 4-1.8 1.3 5.5c0 .4.1 1-.4 1.2l-2.5.5-2.4-5.4ZM1.7 17l-.5 2.6c0 .7.2 1 1 1.3a64.1 64.1 0 0 0 19.8 0c.4-.3 1-.5.7-1.6l-.5-2.3a52.6 52.6 0 0 1-20.6 0Z"/></svg>',
                description: '',
                attribution: '',
                moreInfoLink: '',
                downloadLink: '',
                id: '',
                selected: false,
                opacity: 1,
                visible: true,
                colorPalette: null,
                customProperties: {},
                featureTemplate: {},
                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')} 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.<string, (MapConfig#CustomDateProperty|MapConfig#CustomStringProperty)>} 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.
             * @typedef {Object} Notification
             * @name MapConfig#Notification
             * @since 2.22.0
             * @property {'yellow'|'green'|'blue'|'contrast'} [style] - The badge and message
             * color. If none is set, then notification elements will be similar to the
             * background colour (subtle).
             * @property {string} badge - The text to display in the badge element next to the
             * layer label in the list. This badge should be as few characters as possible.
             * @property {string} message - A longer message to display explaining the status.

            /**
             * Executed when a new MapAsset model is created.
             * @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 = {}
                }

                // Set the color palette
                if (assetConfig.colorPalette) {
                  this.set('colorPalette', new AssetColorPalette(assetConfig.colorPalette))
                }

                // The map asset cannot be visible on the map if there was an error loading
                // the asset
                this.listenTo(this, 'change:status', function (model, status) {
                  if (status === 'error') {
                    this.set('visible', false)
                  }
                })
                this.listenTo(this, 'change:visible', function (model, visible) {
                  if (this.get('status') === 'error') {
                    this.set('visible', false)
                  }
                })

                // 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)
                  }
                }

                // 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 selectedFeatures = mapModel.get('selectedFeatures')

                    this.stopListening(selectedFeatures, 'update');
                    this.listenTo(selectedFeatures, 'update', this.updateAppearance)

                    this.stopListening(mapModel, 'change:selectedFeatures')
                    this.listenTo(mapModel, 'change:selectedFeatures', function () {
                      this.updateAppearance()
                      setSelectFeaturesListeners()
                    })

                  }
                  setSelectFeaturesListeners.call(this)
                  this.listenTo(this, 'change:mapModel', setSelectFeaturesListeners)
                  this.listenTo(this, 'change:mapModel', setSelectFeaturesListeners)
                }
              }
              catch (error) {
                console.log(
                  'There was an error initializing a MapAsset model' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * Given a feature object from a Feature model, checks if it is part of the
             * selectedFeatures collection. See featureObject property from
             * {@link Feature#defaults}.
             * @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.get('selectedFeatures').containsFeature(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 = {};

                if (!customProperties || !Object.keys(customProperties).length) {
                  return properties
                }

                if (!properties || typeof properties !== 'object') {
                  properties = {}
                }

                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
              }
            },

            /**
             * 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 '';
              }
            },

            /**
             * 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;
              }
              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 `<svg` and ends with
             * `</svg>`, regardless of case
             */
            isSVG: function (str) {
              const strLower = str.toLowerCase()
              return strLower.startsWith('<svg') && strLower.endsWith('</svg>')
            },

            /**
             * 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 <p></p> tags that showdown wraps the string in
                  sanitizedIcon = sanitizedIcon.replace(/^(<p>)/, '')
                  sanitizedIcon = sanitizedIcon.replace(/(<\/p>)$/, '')
                  // Call the callback
                  if (callback && typeof callback === 'function') {
                    callback(sanitizedIcon)
                  }
                })
              }
              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 X.X.X
             */
            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)
                  }
                })
              });
            },

            /**
             * 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')
                if (colorPalette) {
                  return colorPalette.getColor(properties)
                } else {
                  return new AssetColorPalette().getDefaultColor()
                }
              }
              catch (error) {
                console.log(
                  'There was an error getting a color for a MapAsset model' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * 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
              }
            },

            /**
             * 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 () {
              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)
              }
            },

            // /**
            //  * 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 MapAsset attributes
            //  */
            // parse: function (input) {

            //   try {

            //     var modelJSON = {};

            //     return modelJSON

            //   }
            //   catch (error) {
            //     console.log(
            //       'There was an error parsing a MapAsset 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 {
            //     // Required attributes: type, url, label, description (all strings)
            //   }
            //   catch (error) {
            //     console.log(
            //       'There was an error validating a MapAsset model' +
            //       '. Error details: ' + error
            //     );
            //   }
            // },

            // /**
            //  * Creates a string using the values set on this model's attributes.
            //  * @return {string} The MapAsset string
            //  */
            // serialize: function () {
            //   try {
            //     var serializedMapAsset = '';

            //     return serializedMapAsset;
            //   }
            //   catch (error) {
            //     console.log(
            //       'There was an error serializing a MapAsset model' +
            //       '. Error details: ' + error
            //     );
            //   }
            // },

          });

        return MapAsset;

      }
    );