Source: src/js/collections/maps/MapAssets.js

"use strict";

define([
  "jquery",
  "underscore",
  "backbone",
  "models/maps/assets/MapAsset",
  "models/maps/assets/Cesium3DTileset",
  "models/maps/assets/CesiumVectorData",
  "models/maps/assets/CesiumImagery",
  "models/maps/assets/CesiumTerrain",
  "models/maps/assets/CesiumGeohash",
], function (
  $,
  _,
  Backbone,
  MapAsset,
  Cesium3DTileset,
  CesiumVectorData,
  CesiumImagery,
  CesiumTerrain,
  CesiumGeohash
) {
  /**
   * @class MapAssets
   * @classdesc A MapAssets collection is a group of MapAsset models - models
   * that provide the information required to render geo-spatial data on a map,
   * including imagery (raster), vector, and terrain data.
   * @class MapAssets
   * @classcategory Collections/Maps
   * @extends Backbone.Collection
   * @since 2.18.0
   * @constructor
   */
  var MapAssets = Backbone.Collection.extend(
    /** @lends MapAssets.prototype */ {
      /**
       * Creates the type of Map Asset based on the given type. This function is
       * typically not called directly. It is used by Backbone.js when adding a
       * new model to the collection.
       * @param {MapConfig#MapAssetConfig} assetConfig - An object that
       * configured the source the asset data, as well as metadata and display
       * properties of the asset.
       * @returns
       * {(Cesium3DTileset|CesiumImagery|CesiumTerrain|CesiumVectorData)}
       * Returns a MapAsset model
       */
      model: function (assetConfig) {
        try {
          // Supported types: Matches each 'type' attribute to the appropriate
          // MapAsset model. See also CesiumWidgetView.mapAssetRenderFunctions
          var mapAssetTypes = [
            {
              types: ["Cesium3DTileset"],
              model: Cesium3DTileset,
            },
            {
              types: ["GeoJsonDataSource", "CzmlDataSource", "CustomDataSource"],
              model: CesiumVectorData,
            },
            {
              types: [
                "BingMapsImageryProvider",
                "IonImageryProvider",
                "WebMapTileServiceImageryProvider",
                "TileMapServiceImageryProvider",
                "WebMapServiceImageryProvider",
                "NaturalEarthII",
                "USGSImageryTopo",
                "OpenStreetMapImageryProvider",
              ],
              model: CesiumImagery,
            },
            {
              types: ["CesiumTerrainProvider"],
              model: CesiumTerrain,
            },
            {
              types: ["CesiumGeohash"],
              model: CesiumGeohash,
            },
          ];

          var type = assetConfig.type;
          var modelOption = _.find(mapAssetTypes, function (option) {
            return option.types.includes(type);
          });

          // Don't add an unsupported type to  the collection
          if (modelOption) {
            return new modelOption.model(assetConfig);
          } else {
            // Return a generic MapAsset as a default
            return new MapAsset(assetConfig);
          }
        } catch (e) {
          console.log("Failed to add a new model to a MapAssets collection", e);
        }
      },

      /**
       * Executed when a new MapAssets collection is created.
       */
      initialize: function () {
        try {
          // Only allow one Map Asset in the collection to be selected at a
          // time. When a Map Asset model's 'selected' attribute is changed to
          // true, change all of the other models' selected attributes to false.
          this.stopListening(this, "change:selected");
          this.listenTo(
            this,
            "change:selected",
            function (changedAsset, newValue) {
              if (newValue === true) {
                var otherModels = this.reject(function (assetModel) {
                  return assetModel === changedAsset;
                });
                otherModels.forEach(function (otherModel) {
                  otherModel.set("selected", false);
                });
              }
            }
          );
        } catch (error) {
          console.log(
            "There was an error initializing a MapAssets collection" +
              ". Error details: " +
              error
          );
        }
      },

      /**
       * Set the parent map model on each of the MapAsset models in this
       * collection. This must be the Map model that contains this asset
       * collection.
       * @param {MapModel} mapModel The map model to set on each of the MapAsset
       * models
       */
      setMapModel: function (mapModel) {
        try {
          this.each(function (mapAssetModel) {
            mapAssetModel.set("mapModel", mapModel);
          });
        } catch (e) {
          console.log("Failed to set the map model on MapAssets collection", e);
        }
      },

      /**
       * Get a list of MapAsset models from this collection that are of a given
       * type.
       * @param
       * {'Cesium3DTileset'|'CesiumVectorData'|'CesiumImagery'|'CesiumTerrain'}
       * assetType - The general type of asset to filter the collection by.
       * @returns {MapAsset[]} - Returns an array of MapAsset models that are
       * instances of the given asset type.
       * @since 2.22.0
       */
      getAll: function (assetType) {
        try {
          // map strings to the models they represent
          var assetTypeMap = {
            Cesium3DTileset: Cesium3DTileset,
            CesiumVectorData: CesiumVectorData,
            CesiumImagery: CesiumImagery,
            CesiumTerrain: CesiumTerrain,
            CesiumGeohash: CesiumGeohash,
          };
          if (assetType) {
            return this.filter(function (assetModel) {
              return assetModel instanceof assetTypeMap[assetType];
            });
          } else {
            return this.models;
          }
        } catch (e) {
          console.log(
            "Failed to get all of the MapAssets in a MapAssets collection." +
              " Returning all models in the asset collection." +
              e
          );
          return this.models;
        }
      },

      /**
       * Add a new MapAsset model to this collection. This is useful if adding
       * the collection from a Map model, since this method will attach the Map
       * model to the MapAsset model.
       * @param {MapConfig#MapAssetConfig | MapAsset } asset - The configuration
       * object for the MapAsset model, or the MapAsset model itself.
       * @param {MapModel} [mapModel] - The Map model that contains this
       * collection. This is optional, but if provided, it will be attached to
       * the MapAsset model.
       * @returns {MapAsset} - Returns the MapAsset model that was added to the
       * collection.
       * @since 2.25.0
       */
      addAsset: function (asset, mapModel) {
        try {
          const newModel = this.add(asset);
          if (mapModel) newModel.set("mapModel", mapModel);
          return newModel;
        } catch (e) {
          console.log("Failed to add a layer to a MapAssets collection", e);
        }
      },

      /**
       * Find the map asset model that contains a feature selected directly from
       * the map view widget.
       * @param {Cesium.Entity|Cesium.Cesium3DTilesetFeature} feature - The
       * feature selected from the map view widget.
       * @returns {MapAsset} - Returns the MapAsset model that contains the
       * feature.
       * @since 2.25.0
       */
      findAssetWithFeature: function (feature) {
        return this.models.find((model) => {
          return model.containsFeature(feature);
        });
      },

      /**
       * Given features selected from the map view widget, get the attributes
       * required to create new Feature models.
       * @param {Cesium.Entity|Cesium.Cesium3DTilesetFeature[]} features - The
       * feature selected from the map view widget.
       * @returns {Object[]} - Returns an array of attributes that can be used
       * to create new Feature models.
       * @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);
        });
      },
    }
  );

  return MapAssets;
});