"use strict";
define([
"jquery",
"underscore",
"backbone",
"collections/maps/MapAssets",
"models/maps/MapInteraction",
"collections/maps/AssetCategories",
"collections/maps/viewfinder/ZoomPresets",
], ($, _, Backbone, MapAssets, Interactions, AssetCategories, ZoomPresets) => {
/**
* Determine if array is empty.
* @param {Array} a The array in question.
* @returns {boolean} Whether the array is empty.
*/
function isNonEmptyArray(a) {
return a && a.length && Array.isArray(a);
}
/**
* @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
* @augments Backbone.Model
*/
const 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. When layerCategories
* exist, this property will be ignored.
* @property {MapConfig#MapAssetConfig[]} [layerCategories] - A collection of
* layer categories to display in the tool bar. Categories wil be
* displayed in the order they appear. The array of the AssetCategoryConfig
* are passed to a {@link AssetCategories} collection. When layerCategories
* exist, the layers property will be ignored.
* @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} [showLayerList=true] - Whether or not to show the
* layer list in the toolbar. If true, the {@link ToolbarView} will render
* a {@link LayerListView}.
* @property {boolean} [showHomeButton=true] - Whether or not to show the
* home button in the toolbar.
* @property {boolean} [showViewfinder=false] - Whether or not to show the
* viewfinder UI and viewfinder button in the toolbar. The ViewfinderView
* requires a Google Maps API key present in the AppModel. In order to
* work properly the Geocoding API and Places API must be enabled.
* @property {boolean} [showShareUrl=false] - Whether or not to show the
* share as URL UI in the toolbar and update the URL as the user interacts
* with the map. This feature requires a `layerId` field on any layers
* that are expected to be saved to the URL search parameter, as that is
* the only unique identifier which can be used to turn the layer
* visibility on or off.
* @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}.
* @property {string} [clickFeatureAction="showDetails"] - The default
* action to take when a user clicks on a feature on the map. The
* available options are "showDetails" (show the feature details in the
* sidebar) or "zoom" (zoom to the feature's location).
* @property {boolean} [showNavHelp=true] - Whether or not to show
* navigation instructions in the toolbar.
* @property {boolean} [showFeedback=false] - Whether or not to show a
* feedback section in the toolbar with the text specified in
* feedbackText.
* @property {string} [feedbackText=null] - The text to show in the
* feedback section. showFeedback must be true for this to be shown.
* @property {String} [globeBaseColor=null] - The base color of the globe when no
* layer is shown.
* @property {ZoomPresets} [zoomPresets=null] - A Backbone.Collection of a
* predefined list of locations with an enabled list of layer IDs to be
* shown the zoom presets UI. Requires `showViewfinder` to be true as this
* UI appears within the ViewfinderView.
* UI appears within the ViewfinderView.
*
* @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
* }
*/
/**
* The type of model this is.
* @type {String}
* @default "MapModel"
* @since 2.25.0
*/
type: "MapModel",
/**
* 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. When layerCategories exist, this
* property will be ignored.
* @property {MapAssets} [allLayers = new MapAssets()] - The assets that
* correspond to the layers field or the layerCategories field depending
* upon which is used. If layerCategories, this contains a flattened list
* of the assets.
* @property {AssetCategories} [layerCategories = new AssetCategories()] -
* A collection of layer categories to display in the tool bar. Categories
* wil be displayed in the order they appear. The array of the AssetCategoryConfig
* are passed to a {@link AssetCategories} collection. When layerCategories
* exist, the layers property will be ignored.
* @property {boolean} [showToolbar=true] - Whether or not to show the
* side bar with layer list and other tools. True by default.
* @property {boolean} [showLayerList=true] - Whether or not to include
* the layer list in the toolbar. True by default.
* @property {boolean} [showHomeButton=true] - Whether or not to show the
* home button in the toolbar. True by default.
* @property {boolean} [showViewfinder=false] - Whether or not to show the
* viewfinder UI and viewfinder button in the toolbar. Defaults to false.
* @property {boolean} [showShareUrl=false] - Whether or not to show the
* share as URL UI. Defaults to false.
* @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 {string} [clickFeatureAction="showDetails"] - The default
* action to take when a user clicks on a feature on the map. The
* available options are "showDetails" (show the feature details in the
* sidebar) or "zoom" (zoom to the feature's location).
* @property {boolean} [showNavHelp=true] - Whether or not to show
* navigation instructions in the toolbar.
* @property {boolean} [showFeedback=false] - Whether or not to show a
* feedback section in the toolbar.
* @property {String} [feedbackText=null] - The text to show in the
* feedback section.
* @property {String} [globeBaseColor=null] - The base color of the globe when no
* layer is shown.
* @property {ZoomPresets} [zoomPresets=null] - A Backbone.Collection of a
* predefined list of locations with an enabled list of layer IDs to be
* shown the zoom presets UI. Requires `showViewfinder` to be true as this
* UI appears within the ViewfinderView.
* UI appears within the ViewfinderView.
*/
defaults() {
return {
homePosition: {
longitude: -65,
latitude: 56,
height: 10000000,
heading: 1,
pitch: -90,
roll: 0,
},
layers: new MapAssets([
{
type: "OpenStreetMapImageryProvider",
label: "Base layer",
},
]),
terrains: new MapAssets(),
showToolbar: true,
showLayerList: true,
showHomeButton: true,
showViewfinder: false,
showShareUrl: false,
toolbarOpen: false,
showScaleBar: true,
showFeatureInfo: true,
clickFeatureAction: "showDetails",
showNavHelp: true,
showFeedback: false,
feedbackText: null,
globeBaseColor: null,
zoomPresets: 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(config) {
try {
if (config && config instanceof Object) {
if (isNonEmptyArray(config.layerCategories)) {
const assetCategories = new AssetCategories(
config.layerCategories,
);
assetCategories.setMapModel(this);
this.set("layerCategories", assetCategories);
this.unset("layers");
this.set("allLayers", assetCategories.getMapAssetsFlat());
} else if (isNonEmptyArray(config.layers)) {
const layers = new MapAssets(config.layers);
this.set("layers", layers);
this.get("layers").setMapModel(this);
this.unset("layerCategories");
this.set("allLayers", layers);
}
if (isNonEmptyArray(config.terrains)) {
this.set("terrains", new MapAssets(config.terrains));
}
this.set(
"zoomPresetsCollection",
new ZoomPresets(
{
zoomPresetObjects: config.zoomPresets,
allLayers: this.get("allLayers"),
},
{ parse: true },
),
);
}
this.setUpInteractions();
} catch (error) {
console.log("Failed to initialize a Map model.", error);
}
},
/**
* Set or replace the MapInteraction model on the map.
* @returns {MapInteraction} The new interactions model.
* @since 2.27.0
*/
setUpInteractions() {
const interactions = new Interactions({
mapModel: this,
});
this.set("interactions", interactions);
return interactions;
},
/**
* Select features on the map. Updates the selectedFeatures attribute on
* the MapInteraction model.
* @param {Feature[]} features - An array of Feature models to select.
* since 2.28.0
*/
selectFeatures(features) {
this.get("interactions")?.selectFeatures(features);
},
/**
* Get the currently selected features on the map.
* @returns {Features} The selected Feature collection.
* @since 2.27.0
*/
getSelectedFeatures() {
return this.get("interactions")?.get("selectedFeatures");
},
/**
* Indicate that the map widget view should navigate to a given target.
* This is accomplished by setting the zoom target on the MapInteraction
* model. The map widget listens to this change and updates the camera
* position accordingly.
* @param {Feature|MapAsset|GeoBoundingBox|Object} target The target to
* zoom to. See {@link CesiumWidgetView#flyTo} for more details on types
* of targets.
*/
zoomTo(target) {
this.get("interactions")?.set("zoomTarget", target);
},
/**
* Indicate that the map widget view should navigate to the home position.
*/
flyHome() {
this.zoomTo(this.get("homePosition"));
},
/**
* Reset the visibility of all layers to the value that was in the intial
* configuration.
*/
resetLayerVisibility() {
this.get("allLayers").forEach((layer) => {
layer.set("visible", layer.get("configuredVisibility"));
});
},
/**
* Reset the layers to the default layers. This will set a new MapAssets
* collection on the layer attribute.
* @returns {MapAssets} The new layers collection.
* @since 2.25.0
*/
resetLayers() {
const newLayers = this.defaults()?.layers || new MapAssets();
this.set("layers", newLayers);
return newLayers;
},
/**
* @returns {MapAssets[]} When layerCategories are configured, each MapAssets
* represets layers from one category. When layerCategories doesn't exist, flat
* layers are used and the array includes exactly one MapAssets with all
* the layers. Returns an empty array if no layer are found.
*/
getLayerGroups() {
if (this.has("layerCategories")) {
return this.get("layerCategories").getMapAssets();
}
if (this.has("layers")) {
return [this.get("layers")];
}
return [];
},
/**
* Add a layer or other asset to the map. This is the best way to add a
* layer to the map because it will ensure that this map model is set on
* the layer model.
* @todo Enable adding a terrain asset.
* @param {Object | MapAsset} asset - A map asset model or object with
* attributes to set on a new map asset model.
* @returns {MapAsset} The new layer model.
* @since 2.25.0
*/
addAsset(asset) {
const layers = this.get("layers") || this.resetLayers();
return layers.addAsset(asset, this);
},
/**
* Remove a layer from the map.
* @param {MapAsset} asset - The layer model to remove from the map.
* @since 2.27.0
*/
removeAsset(asset) {
if (!asset) return;
const layers = this.get("layers");
if (!layers) return;
// Remove by ID because the model is passed directly. Not sure if this
// is a bug in the MapAssets collection or Backbone?
if (layers) layers.remove(asset.cid);
},
},
);
return MapModel;
});