Source: src/js/models/maps/Map.js

'use strict';

define(
  [
    'jquery',
    'underscore',
    'backbone',
    'collections/maps/Features',
    'models/maps/Feature',
    'collections/maps/MapAssets',
  ],
  function (
    $,
    _,
    Backbone,
    Features,
    Feature,
    MapAssets,
  ) {
    /**
     * @class MapModel
     * @classdesc The Map Model contains all of the settings and options for a required to
     * render a map view.
     * @classcategory Models/Maps
     * @name MapModel
     * @since 2.18.0
     * @extends Backbone.Model
     */
    var MapModel = Backbone.Model.extend(
      /** @lends MapModel.prototype */ {

        /**
         * Configuration options for a {@link MapModel} that control the appearance of the
         * map, the data/imagery displayed, and which UI components are rendered. A
         * MapConfig object can be used when initializing a Map model, e.g. `new
         * Map(myMapConfig)`
         * @namespace {Object} MapConfig
         * @property {MapConfig#CameraPosition} [homePosition] - A set of coordinates that
         * give the (3D) starting point of the Viewer. This position is also where the
         * "home" button in the Cesium widget will navigate to when clicked.
         * @property {MapConfig#MapAssetConfig[]} [layers] - A collection of imagery,
         * tiles, vector data, etc. to display on the map. Layers wil be displayed in the
         * order they appear. The array of the layer MapAssetConfigs are passed to a
         * {@link MapAssets} collection.
         * @property {MapConfig#MapAssetConfig[]} [terrains] - Configuration for one or more digital
         * elevation models (DEM) for the surface of the earth. Note: Though multiple
         * terrains are supported, currently only the first terrain is used in the
         * CesiumWidgetView and there is not yet a UI for switching terrains in the map.
         * The array of the terrain MapAssetConfigs are passed to a {@link MapAssets}
         * collection.
         * @property {Boolean} [showToolbar=true] - Whether or not to show the side bar
         * with layer list, etc. If true, the {@link MapView} will render a
         * {@link ToolbarView}.
         * @property {Boolean} [toolbarOpen=false] - Whether or not the toolbar
         * is open when the map is initialized. Set to false by default, so
         * that the toolbar is hidden by default.
         * @property {Boolean} [showScaleBar=true] - Whether or not to show a scale bar.
         * If true, the {@link MapView} will render a {@link ScaleBarView}.
         * @property {Boolean} [showFeatureInfo=true] - Whether or not to allow users to
         * click on map features to show more information about them.  If true, the
         * {@link MapView} will render a {@link FeatureInfoView} and will initialize
         * "picking" in the {@link CesiumWidgetView}.
         * 
         * @example
         * {
         *   "homePosition": {
         *     "latitude": 74.23,
         *     "longitude": -105.7
         *   },
         *   "layers": [
         *     {
         *       "label": "My 3D Tile layer",
         *       "type": "Cesium3DTileset",
         *       "description": "This is an example 3D tileset. This description will be visible in the LayerDetailsView. It will be the default color, since to colorPalette is specified.",
         *       "cesiumOptions": {
         *         "ionAssetId": "555"
         *       },
         *     }
         *   ],
         *   "terrains": [
         *     {
         *       "label": "Arctic DEM",
         *       "type": "CesiumTerrainProvider",
         *       "cesiumOptions": {
         *         "ionAssetId": "3956",
         *         "requestVertexNormals": true
         *       }
         *     }
         *   ],
         *   "showToolbar": true,
         *   "showScaleBar": false,
         *   "showFeatureInfo": false
         * }
        */

        /**
         * Coordinates that describe a camera position for Cesium. Requires at least a
         * longitude and latitude.
         * @typedef {Object} MapConfig#CameraPosition
         * @property {number} longitude - Longitude of the central home point
         * @property {number} latitude - Latitude of the central home point
         * @property {number} [height] - Height above sea level (meters)
         * @property {number} [heading] -  The rotation about the negative z axis
         * (degrees)
         * @property {number} [pitch] - The rotation about the negative y axis (degrees)
         * @property {number} [roll] - The rotation about the positive x axis (degrees)
         * 
         * @example
         * {
         *  longitude: -119.8489,
         *  latitude: 34.4140
         * }
         * 
         * @example
         * {
         *  longitude: -65,
         *  latitude: 56,
         *  height: 10000000,
         *  heading: 1,
         *  pitch: -90,
         *  roll: 0
         * }
        */

        /**
         * Overrides the default Backbone.Model.defaults() function to specify default
         * attributes for the Map
         * @name MapModel#defaults
         * @type {Object}
         * @property {MapConfig#CameraPosition} [homePosition={longitude: -65, latitude: 56, height: 10000000, heading: 1, pitch: -90, roll: 0}]
         * A set of coordinates that give the
         * (3D) starting point of the Viewer. This position is also where the "home"
         * button in the Cesium viewer will navigate to when clicked.
         * @property {MapAssets} [terrains = new MapAssets()] - The terrain options to
         * show in the map.
         * @property {MapAssets} [layers = new MapAssets()] - The imagery and vector data
         * to render in the map.
         * @property {Features} [selectedFeatures = new Features()] - Particular features
         * from one or more layers that are highlighted/selected on the map. The
         * 'selectedFeatures' attribute is updated by the map widget (cesium) with a
         * Feature model when a user selects a geographical feature on the map (e.g. by
         * clicking)
         * @property {Boolean} [showToolbar=true] - Whether or not to show the side bar
         * with layer list, etc. True by default.
         * @property {Boolean} [toolbarOpen=false] - Whether or not the toolbar
         * is open when the map is initialized. Set to false by default, so
         * that the toolbar is hidden by default.
         * @property {Boolean} [showScaleBar=true] - Whether or not to show a scale bar.
         * @property {Boolean} [showFeatureInfo=true] - Whether or not to allow users to
         * click on map features to show more information about them.
         * @property {Object} [currentPosition={ longitude: null, latitude: null, height: null}]
         * An object updated by the map widget to show the longitude, latitude, and
         * height (elevation) at the position of the mouse on the map. Note: The
         * CesiumWidgetView does not yet update the height property.
         * @property {Object} [currentScale={ meters: null, pixels: null }] An object
         * updated by the map widget that gives two equivalent measurements based on the
         * map's current position and zoom level: The number of pixels on the screen that
         * equal the number of meters on the map/globe.
         * @property {Object} [currentViewExtent={ north: null, east: null, south: null, west: null }]
         * An object updated by the map widget that gives the extent of the current
         * visible area as a bounding box in longitude/latitude coordinates.
        */
        defaults: function () {
          return {
            homePosition: {
              longitude: -65,
              latitude: 56,
              height: 10000000,
              heading: 1,
              pitch: -90,
              roll: 0
            },
            layers: new MapAssets([{
                type: 'NaturalEarthII',
                label: 'Base layer'
              }]),
            terrains: new MapAssets(),
            selectedFeatures: new Features(),
            showToolbar: true,
            toolbarOpen: false,
            showScaleBar: true,
            showFeatureInfo: true,
            currentPosition: {
              longitude: null,
              latitude: null,
              height: null
            },
            currentScale: {
              meters: null,
              pixels: null
            },
            currentViewExtent: {
              north: null,
              east: null,
              south: null,
              west: null
            }
          };
        },

        /**
         * Run when a new Map is created.
         * @param {MapConfig} config - An object specifying configuration options for the
         * map. If any config option is not specified, the default will be used instead
         * (see {@link MapModel#defaults}).
         */
        initialize: function (config) {
          try {
            if (config) {

              if (config.layers && config.layers.length && Array.isArray(config.layers)) {
                this.set('layers', new MapAssets(config.layers))
                this.get('layers').setMapModel(this)
              }

              if (config.terrains && config.terrains.length && Array.isArray(config.terrains)) {
                this.set('terrains', new MapAssets(config.terrains))
              }

            }
          }
          catch (error) {
            console.log(
              'There was an error initializing a Map model' +
              '. Error details: ' + error
            );
          }
        },

        /**
         * Set or unset the selected Features on the map model. A selected feature is a
         * polygon, line, point, or other element of vector data that is in focus on the
         * map (e.g. because a user clicked it to show more details.)
         * @param {(Feature|Object[])} features - An array of Feature models or objects with
         * attributes to set on new Feature models. If no features argument is passed to
         * this function, then any currently selected feature will be removed (as long as
         * replace is set to true)
         * @param {Boolean} [replace=true] - If true, any currently selected features will
         * be replaced with the newly selected features. If false, then the newly selected
         * features will be added to any that are currently selected.
         */
        selectFeatures(features, replace = true) {
          try {

            const model = this;
            const defaults = new Feature().defaults()

            if (!model.get('selectedFeatures')) {
              model.set('selectedFeatures', new Features())
            }

            // If no feature is passed to this function (and replace is true), then empty
            // the Features collection
            if (!features || !Array.isArray(features)) {
              features = []
            }

            // If feature is a Feature model, get the attributes to update the model.
            features.forEach(function (feature, i) {
              if (feature instanceof Feature) {
                feature = feature.attributes
              }
              features[i] = _.extend(_.clone(defaults), feature)
            })

            // Update the Feature model with the new selected feature information.
            const options = {
              remove: replace
            }
            model.get('selectedFeatures').set(features, options)

          }
          catch (error) {
            console.log(
              'Failed to select a Feature in a Map model' +
              '. Error details: ' + error
            );
          }
        },


      });

    return MapModel;
  });