From 2cc420d28b76ddc8bcf8b9eeda5747efc34f414b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 06:15:38 +0000 Subject: [PATCH 01/43] Bump nokogiri from 1.13.9 to 1.14.3 in /docs Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.13.9 to 1.14.3. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.13.9...v1.14.3) --- updated-dependencies: - dependency-name: nokogiri dependency-type: indirect ... Signed-off-by: dependabot[bot] --- docs/Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 6a9055f2a..d3030e575 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -205,14 +205,14 @@ GEM rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) - mini_portile2 (2.8.0) + mini_portile2 (2.8.1) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) minitest (5.17.0) multipart-post (2.1.1) - nokogiri (1.13.9) + nokogiri (1.14.3) mini_portile2 (~> 2.8.0) racc (~> 1.4) octokit (4.20.0) @@ -221,7 +221,7 @@ GEM pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (4.0.6) - racc (1.6.0) + racc (1.6.2) rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) From 76f40f0eb232d0a7ba85a52bd794871f443d25a8 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 23 Aug 2023 17:34:58 -0400 Subject: [PATCH 02/43] Create structure for tool for drawing on map [WIP] - Create the initial DrawTool view and layout methods Issue #2180 --- src/js/views/maps/CesiumWidgetView.js | 41 ++++++ src/js/views/maps/DrawTool.js | 200 ++++++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 src/js/views/maps/DrawTool.js diff --git a/src/js/views/maps/CesiumWidgetView.js b/src/js/views/maps/CesiumWidgetView.js index a9837adeb..b57aa1e0b 100644 --- a/src/js/views/maps/CesiumWidgetView.js +++ b/src/js/views/maps/CesiumWidgetView.js @@ -1148,6 +1148,21 @@ define( } }, + /** + * Add a new asset (layer) to the map model and render it on the map + * @param {Object} mapAsset - The properties of the map model to create + * and add to the map + * @returns {MapAsset} Returns the newly created MapAsset model + * @since x.x.x + */ + addNewAsset: function (mapAsset) { + // TODO: Set a listener on the layers collection for add events, and + // call this function when a new layer is added + const newAsset = this.model.addAsset(mapAsset); + this.addAsset(newAsset); + return newAsset + }, + /** * Finds the function that is configured for the given asset model type in the * {@link CesiumWidgetView#mapAssetRenderFunctions} array, then renders the asset @@ -1211,6 +1226,31 @@ define( } }, + /** + * Remove an asset (layer) from the map model and remove it from the map + * @param {MapAsset} mapAsset - The MapAsset model to remove from the map + * @since x.x.x + */ + removeAsset: function (mapAsset) { + // TODO: Set a listener on the layers collection for remove events, and + // call this function when a new layer is removed + try { + if (!mapAsset) { + return + } + // TODO: Implement this! + // this.model.removeAsset(mapAsset) + // Remove the layer from the map + // ... + } + catch (error) { + console.log( + 'There was an error removing an asset from a CesiumWidgetView' + + '. Error details: ' + error + ); + } + }, + /** * Renders peaks and valleys in the 3D version of the map, given a terrain model. * If a terrain model has already been set on the map, this will replace it. @@ -1218,6 +1258,7 @@ define( * use for the map */ updateTerrain: function (cesiumModel) { + // TODO: Add listener to the map model for when the terrain changes this.scene.terrainProvider = cesiumModel this.requestRender(); }, diff --git a/src/js/views/maps/DrawTool.js b/src/js/views/maps/DrawTool.js new file mode 100644 index 000000000..6e6e27838 --- /dev/null +++ b/src/js/views/maps/DrawTool.js @@ -0,0 +1,200 @@ +"use strict"; + +define(["backbone", "cesium"], function (Backbone, Cesium) { + // TODO <- Does Cesium need to be a dependency? + /** + * @class DrawTool + * @classdesc Functionality for drawing an arbitrary polygon on a Cesium map + * using the mouse. + * @classcategory Views/Maps + * @name DrawTool + * @extends Backbone.View + * @screenshot views/maps/DrawTool.png + * @since x.x.x + * @constructs DrawTool + */ + var DrawTool = Backbone.View.extend( + /** @lends DrawTool.prototype */ { + /** + * The type of View this is + * @type {string} + */ + type: "DrawTool", + + /** + * The HTML classes to use for this view's element + * @type {string} + */ + className: "draw-tool", + + /** + * Whether or not the draw tool is currently active. If not active, it + * will not listen for mouse clicks. + * @type {boolean} + */ + activated: false, + + /** + * The Cesium map view to draw on + * @type {CesiumWidgetView} + */ + mapView: undefined, + + /** + * The CesiumVectorData model that we will use to store the drawn polygon + * @type { + */ + mapData: undefined, + + /** + * Initializes the DrawTool + * @param {Object} options + */ + initialize: function (options) { + this.mapView = options.mapView; + this.activated = options.activated || false; + this.makeAsset(); + if (this.activated) { + this.activate(); + } + }, + + /** + * Creates the polygon object that will be modified as a user draws on the + * map. Saves it to the polygon property. + */ + makeAsset: function () { + this.mapData = this.mapView.addNewAsset({ + type: "GeoJsonDataSource", + cesiumOptions: { + data: { + type: "FeatureCollection", + features: [ + { + type: "Feature", + properties: {}, + geometry: { + coordinates: [], + type: "Polygon", + }, + }, + ], + }, + }, + }); + }, + + /** + * Removes the polygon object from the map + */ + removeAsset: function () { + if (this.mapData) { + this.mapView.removeAsset(this.mapData); + } + }, + + /** + * Renders the DrawTool + * @returns {DrawTool} Returns the view + */ + render: function () { + if (!this.mapView) { + this.handleNoMapView(); + return; + } + this.renderToolbar(); + this.startListeners(); + }, + + /** + * What to do when this view doesn't have a map view to draw on + */ + handleNoMapView: function () { + console.warn("No map view provided to DrawTool"); + }, + + /** + * Create and insert the buttons for drawing and clearing the polygon + */ + renderToolbar: function () { + // TODO: At a minimum we need buttons to: Start drawing, Clear drawing + }, + + /** + * Starts the listeners for the draw tool + */ + startListeners: function () { + this.stopListening(); + // TODO: We should either make a general method in the map that gives + // the coordinates of the mouse click, or we should add the Cesium event + // handler here. + this.listenTo(this.mapView, "click", this.handleClick); + }, + + /** + * Stops the listeners for the draw tool + */ + stopListeners: function () { + this.stopListening(this.mapView); + }, + + /** + * Handles a click on the map. If the draw tool is active, it will add the + * coordinates of the click to the polygon being drawn. + * @param {Event} event - The click event + */ + handleClick: function (event) { + if (!this.activated) { + return; + } + var coords = this.mapView.getMouseCoords(event); // <- TODO: This method doesn't exist yet + this.addCoordinate(coords); + }, + + /** + * Adds a coordinate to the polygon being drawn + * @param {Array} coords - The coordinates to add + */ + addCoordinate: function (coords) { + // TODO: Something like this... We may also want to add a general method + // to the VectorData model that allows us to add a coordinate, but this + // will be specific to the GeoJsonDataSource + const geoJsonDataSource = this.mapData.get("cesiumModel"); + const geoJsonFeature = geoJsonDataSource.entities.values[0]; + const coordinates = geoJsonFeature.geometry.coordinates; + coordinates.push(coords); + geoJsonFeature.geometry.coordinates = coordinates; + geoJsonDataSource.entities.values[0] = geoJsonFeature; + this.mapData.updateAppearance(); + }, + + /** + * Activates the draw tool. This means that it will listen for mouse + * clicks on the map and draw a polygon based on those clicks. + */ + activate: function () { + this.activated = true; + this.startListeners(); + }, + + /** + * Deactivates the draw tool. This means that it will no longer listen for + * mouse clicks on the map. + */ + deactivate: function () { + this.activated = false; + this.stopListeners(); + }, + + /** + * Clears the polygon that is being drawn + */ + onClose: function () { + this.removeAsset(); + this.deactivate(); + }, + } + ); + + return DrawTool; +}); From ddb9543debfa43b4fa248c17be7f2b365b256d33 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 24 Aug 2023 13:06:45 -0400 Subject: [PATCH 03/43] Update Cesium on add/remove layers collection - Add listeners for when layers are added or removed from the MapAsset collection and update the layers rendered on the Cesium map and the layers displayed in the layer list. Issue #2180, #1923, #1775 --- src/js/models/connectors/Map-Search.js | 2 - src/js/models/maps/Map.js | 21 +++++- src/js/views/maps/CesiumWidgetView.js | 100 ++++++++++++++++++------- src/js/views/maps/LayerListView.js | 30 ++++++++ 4 files changed, 120 insertions(+), 33 deletions(-) diff --git a/src/js/models/connectors/Map-Search.js b/src/js/models/connectors/Map-Search.js index 827593203..62958b408 100644 --- a/src/js/models/connectors/Map-Search.js +++ b/src/js/models/connectors/Map-Search.js @@ -206,8 +206,6 @@ define([ * See {@link MapSearchFiltersConnector#onMoveEnd} */ onMoveEnd: function () { - const searchResults = this.get("searchResults"); - const map = this.get("map"); this.showGeoHashLayer(); this.updateFacet(); }, diff --git a/src/js/models/maps/Map.js b/src/js/models/maps/Map.js index 6b7390d08..9ae10fd0b 100644 --- a/src/js/models/maps/Map.js +++ b/src/js/models/maps/Map.js @@ -351,16 +351,31 @@ define([ * 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. - * @param {Object | MapAsset} layer - A map asset model or object with + * @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: function (layer) { + addAsset: function (asset) { const layers = this.get("layers") || this.resetLayers(); - return layers.addAsset(layer, this); + return layers.addAsset(asset, this); }, + /** + * Remove a layer from the map. + * @param {MapAsset} asset - The layer model to remove from the map. + * @since x.x.x + */ + removeAsset: function (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); + } + } ); diff --git a/src/js/views/maps/CesiumWidgetView.js b/src/js/views/maps/CesiumWidgetView.js index b57aa1e0b..ae5cbdbf9 100644 --- a/src/js/views/maps/CesiumWidgetView.js +++ b/src/js/views/maps/CesiumWidgetView.js @@ -81,23 +81,30 @@ define( * @property {string} renderFunction The name of the function in the view that * will add the asset to the map and render it, when passed the cesiumModel * attribute from the MapAsset model + * @property {string} removeFunction The name of the function in the view that + * will remove the asset from the map, when passed the cesiumModel attribute from + * the MapAsset model */ mapAssetRenderFunctions: [ { types: ['Cesium3DTileset'], - renderFunction: 'add3DTileset' + renderFunction: 'add3DTileset', + removeFunction: 'remove3DTileset' }, { types: ['GeoJsonDataSource', 'CzmlDataSource'], - renderFunction: 'addVectorData' + renderFunction: 'addVectorData', + removeFunction: 'removeVectorData' }, { types: ['BingMapsImageryProvider', 'IonImageryProvider', 'TileMapServiceImageryProvider', 'WebMapTileServiceImageryProvider', 'WebMapServiceImageryProvider', 'OpenStreetMapImageryProvider'], - renderFunction: 'addImagery' + renderFunction: 'addImagery', + removeFunction: 'removeImagery' }, { types: ['CesiumTerrainProvider'], - renderFunction: 'updateTerrain' + renderFunction: 'updateTerrain', + removeFunction: null } ], @@ -194,7 +201,6 @@ define( // raised. view.camera.percentChanged = 0.1 - // Disable HDR lighting for better performance and to avoid changing imagery colors. view.scene.highDynamicRange = false; view.scene.globe.enableLighting = false; @@ -251,6 +257,14 @@ define( const view = this; + // Listen for addition or removal of layers + // TODO: Add similar listeners for terrain + const layers = view.model.get('layers') + view.stopListening(layers, 'add'); + view.listenTo(layers, 'add', view.addAsset); + view.stopListening(layers, 'remove'); + view.listenTo(layers, 'remove', view.removeAsset); + // Zoom functions executed after each scene render view.scene.postRender.addEventListener(function () { view.postRender(); @@ -1156,10 +1170,10 @@ define( * @since x.x.x */ addNewAsset: function (mapAsset) { - // TODO: Set a listener on the layers collection for add events, and - // call this function when a new layer is added + if(!mapAsset) return const newAsset = this.model.addAsset(mapAsset); - this.addAsset(newAsset); + // The add event on the layers collection will trigger the addAsset + // function below, which will render the asset in the map return newAsset }, @@ -1227,27 +1241,25 @@ define( }, /** - * Remove an asset (layer) from the map model and remove it from the map - * @param {MapAsset} mapAsset - The MapAsset model to remove from the map + * When an asset is removed from the map model, remove it from the map. + * @param {MapAsset} mapAsset - The MapAsset model removed from the map * @since x.x.x */ - removeAsset: function (mapAsset) { - // TODO: Set a listener on the layers collection for remove events, and - // call this function when a new layer is removed - try { - if (!mapAsset) { - return - } - // TODO: Implement this! - // this.model.removeAsset(mapAsset) - // Remove the layer from the map - // ... - } - catch (error) { - console.log( - 'There was an error removing an asset from a CesiumWidgetView' + - '. Error details: ' + error - ); + removeAsset: function (mapAsset, b, c) { + if (!mapAsset) return + // Get the cesium model from the asset + const cesiumModel = mapAsset.get('cesiumModel') + if (!cesiumModel) return + // Find the remove function for this type of asset + const removeFunctionName = this.mapAssetRenderFunctions.find(function (option) { + return option.types.includes(mapAsset.get('type')) + })?.removeFunction + const removeFunction = this[removeFunctionName] + // If there is a function for this type of asset, call it + if (removeFunction && typeof removeFunction === 'function') { + removeFunction.call(this, cesiumModel) + } else { + console.log('No remove function found for this type of asset', mapAsset); } }, @@ -1272,6 +1284,16 @@ define( this.scene.primitives.add(cesiumModel) }, + /** + * Remove a 3D tileset from the map. + * @param {Cesium.Cesium3DTileset} cesiumModel The Cesium 3D tileset model to + * remove from the map + * @since x.x.x + */ + remove3DTileset: function (cesiumModel) { + this.scene.primitives.remove(cesiumModel) + }, + /** * Renders vector data (excluding 3D tilesets) in the Map. * @param {Cesium.GeoJsonDataSource} cesiumModel - The Cesium data source @@ -1281,6 +1303,16 @@ define( this.dataSourceCollection.add(cesiumModel) }, + /** + * Remove vector data (excluding 3D tilesets) from the Map. + * @param {Cesium.GeoJsonDataSource} cesiumModel - The Cesium data source + * model to remove from the map + * @since x.x.x + */ + removeVectorData: function (cesiumModel) { + this.dataSourceCollection.remove(cesiumModel) + }, + /** * Renders imagery in the Map. * @param {Cesium.ImageryLayer} cesiumModel The Cesium imagery model to render @@ -1290,12 +1322,24 @@ define( this.sortImagery() }, + /** + * Remove imagery from the Map. + * @param {Cesium.ImageryLayer} cesiumModel The Cesium imagery model to remove + * from the map + * @since x.x.x + */ + removeImagery: function (cesiumModel) { + console.log('Removing imagery from map', cesiumModel); + console.log('Imagery layers', this.scene.imageryLayers); + this.scene.imageryLayers.remove(cesiumModel) + }, + /** * Arranges the imagery that is rendered the Map according to the order * that the imagery is arranged in the layers collection. * @since 2.21.0 */ - sortImagery() { + sortImagery: function() { try { const imageryInMap = this.scene.imageryLayers const imageryModels = this.model.get('layers').getAll('CesiumImagery') diff --git a/src/js/views/maps/LayerListView.js b/src/js/views/maps/LayerListView.js index e55ce24ee..f53e300c4 100644 --- a/src/js/views/maps/LayerListView.js +++ b/src/js/views/maps/LayerListView.js @@ -79,12 +79,42 @@ define( this[key] = value; } } + this.setListeners(); } catch (e) { console.log('A LayerListView failed to initialize. Error message: ' + e); } }, + /** + * Remove any event listeners on the collection + * @since x.x.x + */ + removeListeners: function () { + try { + if (this.collection) { + this.stopListening(this.collection); + } + } catch (e) { + console.log('Failed to remove listeners:', e); + } + }, + + /** + * Add or remove items from the list when the collection changes + * @since x.x.x + */ + setListeners: function () { + try { + if (this.collection) { + this.listenTo(this.collection, 'add', this.render); + this.listenTo(this.collection, 'remove', this.render); + } + } catch (e) { + console.log('Failed to set listeners:', e); + } + }, + /** * Renders this view * @return {LayerListView} Returns the rendered view element From ef5afacd764d5acc545c57c398d0a55377952f67 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 24 Aug 2023 17:32:17 -0400 Subject: [PATCH 04/43] Expand on DrawTool methods, add to ToolbarView - Still a WIP - Make draw tool work directly with Map model, independent of widget - Rename DrawTool to DrawToolView Issue #2180 --- src/js/views/maps/CesiumWidgetView.js | 18 +-- .../maps/{DrawTool.js => DrawToolView.js} | 108 +++++++++++------- src/js/views/maps/ToolbarView.js | 14 ++- 3 files changed, 82 insertions(+), 58 deletions(-) rename src/js/views/maps/{DrawTool.js => DrawToolView.js} (58%) diff --git a/src/js/views/maps/CesiumWidgetView.js b/src/js/views/maps/CesiumWidgetView.js index ae5cbdbf9..b30fb0f4e 100644 --- a/src/js/views/maps/CesiumWidgetView.js +++ b/src/js/views/maps/CesiumWidgetView.js @@ -528,6 +528,9 @@ define( } else if (action === 'zoom') { view.flyTo(pickedFeature) } + // TODO: Make the click actions more configurable. On every click, + // add the coordinates to the map model's clickedCoordinates. + // (keep a history of the last 10 clicks?) }, Cesium.ScreenSpaceEventType.LEFT_CLICK); } @@ -1162,21 +1165,6 @@ define( } }, - /** - * Add a new asset (layer) to the map model and render it on the map - * @param {Object} mapAsset - The properties of the map model to create - * and add to the map - * @returns {MapAsset} Returns the newly created MapAsset model - * @since x.x.x - */ - addNewAsset: function (mapAsset) { - if(!mapAsset) return - const newAsset = this.model.addAsset(mapAsset); - // The add event on the layers collection will trigger the addAsset - // function below, which will render the asset in the map - return newAsset - }, - /** * Finds the function that is configured for the given asset model type in the * {@link CesiumWidgetView#mapAssetRenderFunctions} array, then renders the asset diff --git a/src/js/views/maps/DrawTool.js b/src/js/views/maps/DrawToolView.js similarity index 58% rename from src/js/views/maps/DrawTool.js rename to src/js/views/maps/DrawToolView.js index 6e6e27838..dfc4ec029 100644 --- a/src/js/views/maps/DrawTool.js +++ b/src/js/views/maps/DrawToolView.js @@ -1,7 +1,6 @@ "use strict"; -define(["backbone", "cesium"], function (Backbone, Cesium) { - // TODO <- Does Cesium need to be a dependency? +define(["backbone"], function (Backbone) { /** * @class DrawTool * @classdesc Functionality for drawing an arbitrary polygon on a Cesium map @@ -35,25 +34,31 @@ define(["backbone", "cesium"], function (Backbone, Cesium) { activated: false, /** - * The Cesium map view to draw on - * @type {CesiumWidgetView} + * The Cesium map model to draw on. This must be the same model that the + * mapWidget is using. + * @type {Map} */ - mapView: undefined, + model: undefined, /** - * The CesiumVectorData model that we will use to store the drawn polygon + * The CesiumVectorData model that we will use to store the drawn + * polygon(s) * @type { */ - mapData: undefined, + drawLayer: undefined, /** * Initializes the DrawTool * @param {Object} options */ initialize: function (options) { - this.mapView = options.mapView; + this.model = this.model; + if (!this.model) { + this.handleNoMapModel(); + return + } this.activated = options.activated || false; - this.makeAsset(); + this.makeDrawLayer(); if (this.activated) { this.activate(); } @@ -63,9 +68,13 @@ define(["backbone", "cesium"], function (Backbone, Cesium) { * Creates the polygon object that will be modified as a user draws on the * map. Saves it to the polygon property. */ - makeAsset: function () { - this.mapData = this.mapView.addNewAsset({ + makeDrawLayer: function () { + if (!this.model) return + this.drawLayer = this.model.addAsset({ type: "GeoJsonDataSource", + hideInLayerList: true, // <- TODO: Look for this property in the + // layer list view. If it's true, don't show it. Document it in the + // map config docs. cesiumOptions: { data: { type: "FeatureCollection", @@ -73,10 +82,10 @@ define(["backbone", "cesium"], function (Backbone, Cesium) { { type: "Feature", properties: {}, - geometry: { - coordinates: [], - type: "Polygon", - }, + "geometry": { + "coordinates": [], + "type": "Polygon" + } }, ], }, @@ -87,10 +96,9 @@ define(["backbone", "cesium"], function (Backbone, Cesium) { /** * Removes the polygon object from the map */ - removeAsset: function () { - if (this.mapData) { - this.mapView.removeAsset(this.mapData); - } + removeDrawLayer: function () { + if (!this.model) return + this.model.removeAsset(this.model); }, /** @@ -98,8 +106,8 @@ define(["backbone", "cesium"], function (Backbone, Cesium) { * @returns {DrawTool} Returns the view */ render: function () { - if (!this.mapView) { - this.handleNoMapView(); + if (!this.model) { + this.handleNoMapModel(); return; } this.renderToolbar(); @@ -109,15 +117,31 @@ define(["backbone", "cesium"], function (Backbone, Cesium) { /** * What to do when this view doesn't have a map view to draw on */ - handleNoMapView: function () { - console.warn("No map view provided to DrawTool"); + handleNoMapModel: function () { + console.warn("No map model provided to DrawTool"); }, /** * Create and insert the buttons for drawing and clearing the polygon */ renderToolbar: function () { - // TODO: At a minimum we need buttons to: Start drawing, Clear drawing + // TODO: At a minimum we need buttons to: Start drawing, Clear drawing. + // Just some place holder buttons for now: + const view = this; + const el = this.el; + const drawButton = document.createElement("button"); + drawButton.innerHTML = "Draw"; + drawButton.addEventListener("click", function () { + view.activate(); + }); + el.appendChild(drawButton); + const clearButton = document.createElement("button"); + clearButton.innerHTML = "Clear"; + clearButton.addEventListener("click", function () { + view.removeDrawLayer(); + }); + el.appendChild(clearButton); + }, /** @@ -125,30 +149,28 @@ define(["backbone", "cesium"], function (Backbone, Cesium) { */ startListeners: function () { this.stopListening(); - // TODO: We should either make a general method in the map that gives - // the coordinates of the mouse click, or we should add the Cesium event - // handler here. - this.listenTo(this.mapView, "click", this.handleClick); + // TODO: Make a general method in the map widget that gives the + // coordinates of the mouse click + this.listenTo(this.model, "change:clickedCoordinates", this.handleClick); }, /** * Stops the listeners for the draw tool */ stopListeners: function () { - this.stopListening(this.mapView); + this.stopListening(this.model); }, /** * Handles a click on the map. If the draw tool is active, it will add the * coordinates of the click to the polygon being drawn. - * @param {Event} event - The click event + * @param {Number[]} coordinates - The most recently clicked coordinates */ - handleClick: function (event) { + handleClick: function (coordinates) { if (!this.activated) { return; } - var coords = this.mapView.getMouseCoords(event); // <- TODO: This method doesn't exist yet - this.addCoordinate(coords); + this.addCoordinate(coordinates); }, /** @@ -159,13 +181,19 @@ define(["backbone", "cesium"], function (Backbone, Cesium) { // TODO: Something like this... We may also want to add a general method // to the VectorData model that allows us to add a coordinate, but this // will be specific to the GeoJsonDataSource - const geoJsonDataSource = this.mapData.get("cesiumModel"); - const geoJsonFeature = geoJsonDataSource.entities.values[0]; - const coordinates = geoJsonFeature.geometry.coordinates; - coordinates.push(coords); - geoJsonFeature.geometry.coordinates = coordinates; - geoJsonDataSource.entities.values[0] = geoJsonFeature; - this.mapData.updateAppearance(); + const layer = this.drawLayer; + const geoJSON = layer.get("cesiumOptions")?.data + const coordinates = geoJSON?.features[0]?.geometry?.coordinates + if (!coordinates) { + // Create new coordinates array + geoJSON.features[0].geometry.coordinates = [coords] + } else { + // Add to existing coordinates array + coordinates.push(coords) + } + layer.set("cesiumOptions", { data: geoJSON }) + // TODO: In all MapAsset models, listen for changes to the cesiumOptions + // object and re-create the cesiumModel when it changes. }, /** diff --git a/src/js/views/maps/ToolbarView.js b/src/js/views/maps/ToolbarView.js index ad50f2da7..e17974027 100644 --- a/src/js/views/maps/ToolbarView.js +++ b/src/js/views/maps/ToolbarView.js @@ -8,8 +8,9 @@ define( 'backbone', 'text!templates/maps/toolbar.html', 'models/maps/Map', - // Sub-views - 'views/maps/LayerListView' + // Sub-views - TODO: import these as needed + 'views/maps/LayerListView', + 'views/maps/DrawToolView' ], function ( $, @@ -18,7 +19,8 @@ define( Template, Map, // Sub-views - LayerListView + LayerListView, + DrawTool ) { /** @@ -172,6 +174,12 @@ define( action: function (view, model) { model.trigger('flyHome') } + }, + { + label: 'Draw', + icon: 'pencil', + view: DrawTool, + viewOptions: {} } ], From 24c80506b9ffd63e684c5b28579749705a097ba1 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 31 Aug 2023 16:36:38 -0400 Subject: [PATCH 05/43] Improve Cesium Map models, views, & collections - Add MapInteraction, GeoPoint and GeoBoundingBox models - Make the CesiumWidgetView methods smaller and more modular - Move Map model attributes to the MapInteraction model (selectedFeatures, currentPosition, currentScale, and currentViewExtent) - In MapAsset models, listen for changes to cesiumOptions and update Cesium model - In map connectors, don't use new models as defaults to avoid instantiating new models unnecessarily - Pass GeoPoint and GeoScale models directly to the ScaleBarView Issues #2189, #2180, #2187 --- src/js/collections/maps/Geohashes.js | 119 +- src/js/models/connectors/Filters-Map.js | 33 +- .../models/connectors/Map-Search-Filters.js | 17 +- src/js/models/connectors/Map-Search.js | 8 +- src/js/models/filters/SpatialFilter.js | 32 +- src/js/models/maps/GeoBoundingBox.js | 185 ++ src/js/models/maps/GeoPoint.js | 76 + src/js/models/maps/GeoScale.js | 62 + src/js/models/maps/Map.js | 164 +- src/js/models/maps/MapInteraction.js | 332 ++ src/js/models/maps/assets/CesiumGeohash.js | 46 +- src/js/models/maps/assets/MapAsset.js | 140 +- src/js/views/maps/CesiumWidgetView.js | 2766 +++++++++-------- src/js/views/maps/DrawToolView.js | 2 - src/js/views/maps/FeatureInfoView.js | 4 +- src/js/views/maps/LayerNavigationView.js | 9 +- src/js/views/maps/MapView.js | 106 +- src/js/views/maps/ScaleBarView.js | 72 +- src/js/views/maps/ToolbarView.js | 2 +- src/js/views/search/CatalogSearchView.js | 2 +- 20 files changed, 2454 insertions(+), 1723 deletions(-) create mode 100644 src/js/models/maps/GeoBoundingBox.js create mode 100644 src/js/models/maps/GeoPoint.js create mode 100644 src/js/models/maps/GeoScale.js create mode 100644 src/js/models/maps/MapInteraction.js diff --git a/src/js/collections/maps/Geohashes.js b/src/js/collections/maps/Geohashes.js index f606140b1..8c2846f45 100644 --- a/src/js/collections/maps/Geohashes.js +++ b/src/js/collections/maps/Geohashes.js @@ -125,21 +125,22 @@ define([ * Creates hashStrings for geohashes that are within the provided bounding * boxes at the given precision. The returned hashStrings are not * necessarily in the collection. - * @param {Object} bounds - Bounding box with north, south, east, and west + * @param {GeoBoundingBox} bounds - Bounding box with north, south, east, and west * properties. * @param {number} precision - Geohash precision level. * @returns {string[]} Array of geohash hashStrings. */ getHashStringsForBounds: function (bounds, precision) { this.validatePrecision(precision, false); - if (!this.boundsAreValid(bounds)) { + if (!bounds.isValid()) { throw new Error("Bounds are invalid"); } let hashStrings = []; - bounds = this.splitBoundingBox(bounds); + bounds = bounds.split(); bounds.forEach(function (b) { + const c = b.getCoords(); hashStrings = hashStrings.concat( - nGeohash.bboxes(b.south, b.west, b.north, b.east, precision) + nGeohash.bboxes(c.south, c.west, c.north, c.east, precision) ); }); return hashStrings; @@ -171,29 +172,10 @@ define([ return this.models.map((geohash) => geohash.get(attr)); }, - /** - * Splits a given bounding box if it crosses the prime meridian. Returns - * an array of bounding boxes. - * @param {Object} bounds - Bounding box object with north, south, east, - * and west properties. - * @returns {Object[]} Array of bounding box objects. - */ - splitBoundingBox: function (bounds) { - if (!bounds) return []; - const { north, south, east, west } = bounds; - if (east < west) { - return [ - { north, south, east: 180, west }, - { north, south, east, west: -180 }, - ]; - } else { - return [{ north, south, east, west }]; - } - }, /** * Add geohashes to the collection based on a bounding box. - * @param {Object} bounds - Bounding box with north, south, east, and west + * @param {GeoBoundingBox} bounds - Bounding box with north, south, east, and west * properties. * @param {boolean} [consolidate=false] - Whether to consolidate the * geohashes into the smallest set of geohashes that cover the same area. @@ -231,7 +213,7 @@ define([ maxGeohashes ); } else { - const area = this.getBoundingBoxArea(bounds); + const area = bounds.getArea(); const precision = this.getMaxPrecision( area, maxGeohashes, @@ -289,53 +271,6 @@ define([ return this.precisionAreas; }, - /** - * Get the area of a bounding box in degrees. - * @param {Object} bounds - Bounding box with north, south, east, and west - * properties. - * @returns {Number} The area of the bounding box in degrees. - */ - getBoundingBoxArea: function (bounds) { - if (!this.boundsAreValid(bounds)) { - console.warn( - `Bounds are invalid: ${JSON.stringify(bounds)}. ` + - `Returning the globe's area for the given bounding box.` - ); - return 360 * 180; - } - const { north, south, east, west } = bounds; - // Account for cases where east < west, due to the bounds crossing the - // prime meridian - const lonDiff = east < west ? 360 - (west - east) : east - west; - const latDiff = north - south; - return Math.abs(latDiff * lonDiff); - }, - - /** - * Check that a bounds object is valid for the purposes of other methods - * in this Collection. - * @param {Object} bounds - Bounding box with north, south, east, and west - * properties. - * @returns {boolean} Whether the bounds object is valid. - */ - boundsAreValid: function (bounds) { - return ( - bounds && - typeof bounds.north === "number" && - typeof bounds.south === "number" && - typeof bounds.east === "number" && - typeof bounds.west === "number" && - bounds.north <= 90 && - bounds.north >= -90 && - bounds.south >= -90 && - bounds.south <= 90 && - bounds.east <= 180 && - bounds.east >= -180 && - bounds.west >= -180 && - bounds.west <= 180 - ); - }, - /** * Given a bounding box, estimate the maximum geohash precision that can * be used to cover the area without exceeding a specified number of @@ -426,8 +361,8 @@ define([ /** * Get the optimal range of precision levels to consider using for a given * bounding box. See {@link getMaxPrecision} and {@link getMinPrecision}. - * @param {Object} bounds - Bounding box with north, south, east, and west - * properties. + * @param {GeoBoundingBox} bounds - Bounding box with north, south, east, + * and west properties. * @param {Number} maxGeohashes - The maximum number of geohashes that can * be used to cover the area. * @param {Number} absMin - The absolute minimum precision level to @@ -443,7 +378,7 @@ define([ absMin = this.MIN_PRECISION, absMax = this.MAX_PRECISION ) { - if (!this.boundsAreValid(bounds)) { + if (!bounds.isValid()){ console.warn( `Bounds are invalid: ${JSON.stringify(bounds)}. ` + `Returning the min and max allowable precision levels.` @@ -452,7 +387,7 @@ define([ } absMin = this.validatePrecision(absMin); absMax = this.validatePrecision(absMax); - const area = this.getBoundingBoxArea(bounds); + const area = bounds.getArea(); const minP = this.getMinPrecision(area, absMin, absMax); if (minP === absMax || maxGeohashes === Infinity) return [minP, absMax]; return [minP, this.getMaxPrecision(area, maxGeohashes, minP, absMax)]; @@ -463,7 +398,7 @@ define([ * bounding box. This will return the optimal set of potentially * mixed-precision geohashes that cover the bounding box at the highest * precision possible without exceeding the maximum number of geohashes. - * @param {Object} bounds - Bounding box with north, south, east, and west + * @param {GeoBoundingBox} bounds - Bounding box with north, south, east, and west * properties. * @param {Number} [minPrecision] - The minimum precision level to * consider when calculating the optimal set of geohashes. Defaults to the @@ -485,7 +420,7 @@ define([ maxGeohashes = Infinity ) { // Check the inputs - if (!this.boundsAreValid(bounds)) return []; + if (!bounds.isValid()) return []; minPrecision = this.validatePrecision(minPrecision); maxPrecision = this.validatePrecision(maxPrecision); if (minPrecision > maxPrecision) minPrecision = maxPrecision; @@ -502,35 +437,23 @@ define([ // Base32 is the set of characters used to encode geohashes const base32 = [..."0123456789bcdefghjkmnpqrstuvwxyz"]; - // In case the bounding box crosses the prime meridian, split it in two - const allBounds = this.splitBoundingBox(bounds); - // If the bounds cover the world, return the base set of geohashes - if (bounds.north >= 90 && bounds.south <= -90 && bounds.east >= 180 && bounds.west <= -180) { + if (bounds.coversEarth()) { return base32; } - // Checks if the given bounds are fully within the bounding box - function fullyContained(n, e, s, w, north, east, south, west) { - return s >= south && w >= west && n <= north && e <= east; - } - - // Checks if the given bounds are fully outside the bounding box, assuming that - function fullyOutside(n, e, s, w, north, east, south, west) { - return n < south || s > north || e < west || w > east; - } - // Checks if a hash is fully contained, fully outside, or overlapping - // the bounding box + // the bounding box. In case the bounding box crosses the prime + // meridian, split it in two + const allBounds = bounds.split(); function hashPlacement(hash) { let [s, w, n, e] = nGeohash.decode_bbox(hash); let outside = []; for (const b of allBounds) { - if (fullyContained(n, e, s, w, b.north, b.east, b.south, b.west)) { + if (bounds.boundsAreFullyContained(n, e, s, w)) { return "inside"; } else if ( - fullyOutside(n, e, s, w, b.north, b.east, b.south, b.west) - ) { + bounds.boundsAreFullyOutside(n, e, s, w)) { outside.push(true); } } @@ -596,12 +519,12 @@ define([ /** * Get a subset of geohashes from this collection that are within the * provided bounding box. - * @param {Object} bounds - Bounding box with north, south, east, and west + * @param {GeoBoundingBox} bounds - Bounding box with north, south, east, and west * properties. * @returns {Geohashes} Subset of geohashes. */ getSubsetByBounds: function (bounds) { - if (!this.boundsAreValid(bounds)) { + if (!bounds || !bounds.isValid()) { console.warn( `Bounds are invalid: ${JSON.stringify(bounds)}. ` + `Returning an empty Geohashes collection.` diff --git a/src/js/models/connectors/Filters-Map.js b/src/js/models/connectors/Filters-Map.js index 505d89a62..b3afd7772 100644 --- a/src/js/models/connectors/Filters-Map.js +++ b/src/js/models/connectors/Filters-Map.js @@ -47,9 +47,9 @@ define(["backbone", "collections/Filters", "models/maps/Map"], function ( */ defaults: function () { return { - filters: new Filters([], { catalogSearch: true }), + filters: null, spatialFilters: [], - map: new Map(), + map: null, isConnected: false, onMoveEnd: this.updateSpatialFilters, }; @@ -68,6 +68,12 @@ define(["backbone", "collections/Filters", "models/maps/Map"], function ( */ initialize: function (attr, options) { try { + if (!this.get("filters")) { + this.set("filters", new Filters([], { catalogSearch: true })); + } + if (!this.get("map")) { + this.set("map", new Map()); + } const add = options?.addSpatialFilter ?? true; this.findAndSetSpatialFilters(add); } catch (e) { @@ -177,8 +183,9 @@ define(["backbone", "collections/Filters", "models/maps/Map"], function ( if (resetSpatialFilter) { this.resetSpatialFilter(); } + const interactions = this.get("map")?.get("interactions"); this.stopListening(this.get("filters"), "add remove"); - this.stopListening(this.get("map"), "moveEnd moveStart"); + this.stopListening(interactions, "moveEnd moveStart"); this.set("isConnected", false); } catch (e) { console.log("Error stopping Filter-Map listeners: ", e); @@ -186,23 +193,24 @@ define(["backbone", "collections/Filters", "models/maps/Map"], function ( }, /** - * Starts listening to the Map model for changes in the - * 'currentViewExtent' attribute, and calls the updateSpatialFilters - * function when changes are detected. This method needs to be called for - * the connector to work. + * Starts listening to the Map Interaction model for changes in the + * 'viewExtent' attribute, and calls the updateSpatialFilters function + * when changes are detected. This method needs to be called for the + * connector to work. */ connect: function () { try { this.disconnect(); const map = this.get("map"); + const interactions = map.get("interactions"); // Constrain the spatial filter to the current map extent right away this.updateSpatialFilters(); // Trigger a 'changing' event on the filters collection to // indicate that the spatial filter is being updated - this.listenTo(map, "moveStart", function () { + this.listenTo(interactions, "moveStart", function () { this.get("filters").trigger("changing"); }); - this.listenTo(map, "moveEnd", function () { + this.listenTo(interactions, "moveEnd", function () { const moveEndFunc = this.get("onMoveEnd"); if (typeof moveEndFunc === "function") { moveEndFunc.call(this); @@ -220,7 +228,12 @@ define(["backbone", "collections/Filters", "models/maps/Map"], function ( updateSpatialFilters: function () { try { const map = this.get("map"); - const extent = map.get("currentViewExtent"); + // TODO: If we update the spatialFilter to use the GeoBoundingBox + // model (instead of north, south, east, west attributes), then we can + // point directly to the MapInteraction model's 'viewExtent' attribute + // instead of getting the extent from the map. They will stay in sync + // automatically. + const extent = map.get("interactions").get("viewExtent").toJSON(); const spatialFilters = this.get("spatialFilters"); if (!spatialFilters?.length) { diff --git a/src/js/models/connectors/Map-Search-Filters.js b/src/js/models/connectors/Map-Search-Filters.js index cc7557aca..d37bed28d 100644 --- a/src/js/models/connectors/Map-Search-Filters.js +++ b/src/js/models/connectors/Map-Search-Filters.js @@ -35,14 +35,14 @@ define([ /** * The default values for this model. * @type {object} - * @property {Map} map - * @property {SolrResults} searchResults - * @property {Filters} filters + * @property {Map} map - The Map model to use for this connector. + * @property {SolrResults} searchResults - The SolrResults model to use + * @property {Filters} filters - The Filters model to use for this */ defaults: function () { return { - map: new Map(), - searchResults: new SearchResults(), + map: null, + searchResults: null, filterGroups: [], filters: null, }; @@ -83,7 +83,7 @@ define([ initialize: function (attrs, options = {}) { if (!options) options = {}; const app = MetacatUI.appModel; - const map = options.map || app.get("catalogSearchMapOptions"); + const map = options.map || app.get("catalogSearchMapOptions") || {}; const searchResults = options.searchResults || null; const filterGroups = options.filterGroups || app.get("defaultFilterGroups"); @@ -242,6 +242,7 @@ define([ this.resetMoveEndSearch(); const map = this.get("map"); + const interactions = map.get("interactions"); const mapConnectors = this.getMapConnectors(); // Stop the sub-connectors from doing anything on moveEnd by setting @@ -253,7 +254,7 @@ define([ // Set the single moveEnd listener here, and run the default moveEnd // behaviour for each sub-connector. This effectively triggers only one // search per moveEnd. - this.listenTo(map, "moveEnd", function () { + this.listenTo(interactions, "moveEnd", function () { mapConnectors.forEach((connector) => { const moveEndFunc = connector.defaults().onMoveEnd; if (typeof moveEndFunc === "function") { @@ -270,7 +271,7 @@ define([ * @see coordinateMoveEndSearch */ resetMoveEndSearch: function () { - this.stopListening(this.get("map"), "moveEnd"); + this.stopListening(this.get("map").get("interactions"), "moveEnd"); const mapConnectors = this.getMapConnectors(); mapConnectors.forEach((connector) => { connector.set("onMoveEnd", connector.defaults().onMoveEnd); diff --git a/src/js/models/connectors/Map-Search.js b/src/js/models/connectors/Map-Search.js index 62958b408..ae7b5f266 100644 --- a/src/js/models/connectors/Map-Search.js +++ b/src/js/models/connectors/Map-Search.js @@ -163,6 +163,7 @@ define([ const searchResults = this.get("searchResults"); const map = this.get("map"); + const interactions = map.get("interactions"); // Pass the facet counts to the GeoHash layer when the search results // are returned. @@ -178,12 +179,12 @@ define([ // When the user is panning/zooming in the map, hide the GeoHash layer // to indicate that the map is not up to date with the search results, // which are about to be updated. - this.listenTo(map, "moveStart", this.hideGeoHashLayer); + this.listenTo(interactions, "moveStart", this.hideGeoHashLayer); // When the user is done panning/zooming in the map, show the GeoHash // layer again and update the search results (thereby updating the // facet counts on the GeoHash layer) - this.listenTo(map, "moveEnd", function () { + this.listenTo(interactions, "moveEnd", function () { const moveEndFunc = this.get("onMoveEnd"); if (typeof moveEndFunc === "function") { moveEndFunc.call(this); @@ -235,10 +236,11 @@ define([ */ disconnect: function () { const map = this.get("map"); + const interactions = map?.get("interactions"); const searchResults = this.get("searchResults"); this.stopListening(searchResults, "update reset"); this.stopListening(searchResults, "change:showOnMap"); - this.stopListening(map, "moveStart moveEnd"); + this.stopListening(interactions, "moveStart moveEnd"); this.stopListening(searchResults, "request"); this.set("isConnected", false); }, diff --git a/src/js/models/filters/SpatialFilter.js b/src/js/models/filters/SpatialFilter.js index d380387fe..6d2e9719e 100644 --- a/src/js/models/filters/SpatialFilter.js +++ b/src/js/models/filters/SpatialFilter.js @@ -1,11 +1,9 @@ define([ "underscore", "jquery", - "backbone", "models/filters/Filter", - "collections/maps/Geohashes", - "collections/Filters", -], function (_, $, Backbone, Filter, Geohashes, Filters) { + "collections/maps/Geohashes" +], function (_, $, Filter, Geohashes) { /** * @classdesc A SpatialFilter represents a spatial constraint on the query to * be executed. @@ -117,16 +115,26 @@ define([ /** * Convert the coordinate attributes to a bounds object - * @returns {object} An object with north, south, east, and west props + * @param {string} [as="object"] - The format to return the bounds in. + * Defaults to "object". Can set to GeoBoundingBox to return a Backbone + * model instead. + * @returns {object|GeoBoundingBox} An object with north, south, east, and west props or + * a GeoBoundingBox model * @since 2.25.0 */ - getBounds: function () { - return { + getBounds: function (as="object") { + const coords = { north: this.get("north"), south: this.get("south"), east: this.get("east"), west: this.get("west"), }; + if (as === "GeoBoundingBox") { + const GeoBoundingBox = require("models/maps/GeoBoundingBox"); + return new GeoBoundingBox(coords); + } else { + return coords; + } }, /** @@ -135,13 +143,7 @@ define([ * @since 2.25.0 */ coversEarth: function () { - const bounds = this.getBounds(); - return ( - bounds.north === 90 && - bounds.south === -90 && - bounds.east === 180 && - bounds.west === -180 - ); + const bounds = this.getBounds("GeoBoundingBox").coversEarth(); }, /** @@ -163,7 +165,7 @@ define([ } const geohashes = new Geohashes(); - const bounds = this.getBounds(); + const bounds = this.getBounds("GeoBoundingBox"); const limit = this.get("maxGeohashValues"); geohashes.addGeohashesByBounds(bounds, true, limit, true); this.set({ diff --git a/src/js/models/maps/GeoBoundingBox.js b/src/js/models/maps/GeoBoundingBox.js new file mode 100644 index 000000000..93dc4959c --- /dev/null +++ b/src/js/models/maps/GeoBoundingBox.js @@ -0,0 +1,185 @@ +"use strict"; + +define(["backbone"], function (Backbone) { + /** + * @class GeoBoundingBox + * @classdesc The GeoBoundingBox model stores the geographical boundaries for + * an area on the Earth's surface. It includes the northernmost, southernmost, + * easternmost, and westernmost latitudes and longitudes, as well as an + * optional height parameter. + * @classcategory Models/Maps + * @name GeoBoundingBox + * @since x.x.x + * @extends Backbone.Model + */ + var GeoBoundingBox = Backbone.Model.extend( + /** @lends GeoBoundingBox.prototype */ { + /** + * The type of model this is. + * @type {String} + */ + type: "GeoBoundingBox", + + /** + * Overrides the default Backbone.Model.defaults() function to specify + * default attributes for the GeoBoundingBox. + * @property {number|null} north The northernmost latitude of the bounding + * box. Should be a number between -90 and 90. + * @property {number|null} south The southernmost latitude of the bounding + * box. Should be a number between -90 and 90. + * @property {number|null} east The easternmost longitude of the bounding + * box. Should be a number between -180 and 180. + * @property {number|null} west The westernmost longitude of the bounding + * box. Should be a number between -180 and 180. + * @property {number|null} [height] The height of the camera above the + * bounding box. Represented in meters above sea level. This attribute is + * optional and can be null. + */ + defaults: function () { + return { + north: null, + south: null, + east: null, + west: null, + height: null, + }; + }, + + // /** + // * Run when a new GeoBoundingBox is created. + // * @param {Object} attrs - An object specifying configuration options for + // * the GeoBoundingBox. If any config option is not specified, the default + // * will be used instead (see {@link GeoBoundingBox#defaults}). + // */ + // initialize: function (attrs, options) { + // try { + // // ... + // } catch (e) { + // console.log("Error initializing a GeoBoundingBox model", e); + // } + // }, + + /** + * Splits the given bounding box if it crosses the prime meridian. + * Returns one or two new GeoBoundingBox models. + * @returns {GeoBoundingBox[]} An array of GeoBoundingBox models. One if + * the bounding box does not cross the prime meridian, two if it does. + */ + split: function () { + const { north, south, east, west } = this.getCoords(); + if (east < west) { + return [ + new GeoBoundingBox({ north, south, east: 180, west }), + new GeoBoundingBox({ north, south, east, west: -180 }), + ]; + } else { + return [this.clone()]; + } + }, + + /** + * Get the area of this bounding box in degrees. + * @returns {Number} The area of the bounding box in degrees. Will return + * the globe's area if the bounding box is invalid. + */ + getArea: function () { + if (!this.isValid()) { + console.warn( + `Bounds are invalid: ${JSON.stringify(bounds)}. ` + + `Returning the globe's area for the given bounding box.` + ); + return 360 * 180; + } + const { north, south, east, west } = this.attributes; + // Account for cases where east < west, due to the bounds crossing the + // prime meridian + const lonDiff = east < west ? 360 - (west - east) : east - west; + const latDiff = north - south; + return Math.abs(latDiff * lonDiff); + }, + + /** + * Return the four sides of the bounding box as an array. + * @returns {Object} An object with the northernmost, southernmost, + * easternmost, and westernmost coordinates of the bounding box. + */ + getCoords: function () { + return { + north: this.get("north"), + south: this.get("south"), + east: this.get("east"), + west: this.get("west"), + }; + }, + + /** + * Check if the bounding box covers the entire Earth. + * @returns {Boolean} True if the bounding box covers the entire Earth, + * false otherwise. + */ + coversEarth: function () { + const { north, south, east, west } = this.getCoords(); + return north >= 90 && south <= -90 && east >= 180 && west <= -180; + }, + + /** + * Check if another bounding box is fully contained within this bounding + * box. + * @param {Number} n - The northernmost latitude of the bounding box. + * @param {Number} e - The easternmost longitude of the bounding box. + * @param {Number} s - The southernmost latitude of the bounding box. + * @param {Number} w - The westernmost longitude of the bounding box. + * @returns {Boolean} True if the other bounding box is fully contained + * within this bounding box, false otherwise. + */ + boundsAreFullyContained: function (n, e, s, w) { + const { north, south, east, west } = this.getCoords(); + return s >= south && w >= west && n <= north && e <= east; + }, + + /** + * Check if another bounding box is fully outside of this bounding box. + * @param {Number} n - The northernmost latitude of the bounding box. + * @param {Number} e - The easternmost longitude of the bounding box. + * @param {Number} s - The southernmost latitude of the bounding box. + * @param {Number} w - The westernmost longitude of the bounding box. + * @returns {Boolean} True if the other bounding box is fully outside this + * bounding box, false otherwise. + */ + boundsAreFullyOutside: function (n, e, s, w) { + const { north, south, east, west } = this.getCoords(); + return n < south || s > north || e < west || w > east; + }, + + /** + * Validate the model attributes + * @param {Object} attrs - The model's attributes + */ + validate: function (attrs, options) { + const bounds = attrs; + const isValid = + bounds && + typeof bounds.north === "number" && + typeof bounds.south === "number" && + typeof bounds.east === "number" && + typeof bounds.west === "number" && + bounds.north <= 90 && + bounds.north >= -90 && + bounds.south >= -90 && + bounds.south <= 90 && + bounds.east <= 180 && + bounds.east >= -180 && + bounds.west >= -180 && + bounds.west <= 180; + if (!isValid) { + return ( + "Bounds must include a number between -90 and 90 for north " + + "and south, and between -180 and 180 for east and west." + ); + } + }, + } + ); + + return GeoBoundingBox; +}); diff --git a/src/js/models/maps/GeoPoint.js b/src/js/models/maps/GeoPoint.js new file mode 100644 index 000000000..adb826709 --- /dev/null +++ b/src/js/models/maps/GeoPoint.js @@ -0,0 +1,76 @@ +"use strict"; + +define(["backbone"], function (Backbone) { + /** + * @class GeoPoint + * @classdesc The GeoPoint model stores geographical coordinates including + * latitude, longitude, and height in meters above sea level. + * @classcategory Models/Maps + * @name GeoPoint + * @since x.x.x + * @extends Backbone.Model + */ + var GeoPoint = Backbone.Model.extend( + /** @lends GeoPoint.prototype */ { + /** + * The type of model this is. + * @type {String} + */ + type: "GeoPoint", + + /** + * Overrides the default Backbone.Model.defaults() function to specify + * default attributes for the GeoPoint. + * @returns {Object} The default attributes + * @property {number} latitude - The latitude of the point in degrees + * @property {number} longitude - The longitude of the point in degrees + * @property {number} height - The height of the point in meters above sea + * level + */ + defaults: function () { + return { + latitude: null, + longitude: null, + height: null + }; + }, + + // /** + // * Run when a new GeoPoint is created. + // * @param {Object} attrs - An object specifying configuration options for + // * the GeoPoint. If any config option is not specified, the default will + // * be used instead (see {@link GeoPoint#defaults}). + // */ + // initialize: function (attrs, options) { + // try { + // // ... + // } catch (e) { + // console.log("Error initializing a GeoPoint model", e); + // } + // }, + + /** + * Validate the model attributes + * @param {Object} attrs - The model's attributes + */ + validate: function(attrs) { + if (attrs.latitude < -90 || attrs.latitude > 90) { + return "Invalid latitude. Must be between -90 and 90."; + } + + if (attrs.longitude < -180 || attrs.longitude > 180) { + return "Invalid longitude. Must be between -180 and 180."; + } + + // Assuming height is in meters and can theoretically be below sea + // level. Adjust the height constraints as needed for your specific + // application. + if (typeof attrs.height !== 'number') { + return "Invalid height. Must be a number."; + } + } + } + ); + + return GeoPoint; +}); diff --git a/src/js/models/maps/GeoScale.js b/src/js/models/maps/GeoScale.js new file mode 100644 index 000000000..ecfd43623 --- /dev/null +++ b/src/js/models/maps/GeoScale.js @@ -0,0 +1,62 @@ +"use strict"; + +define(["backbone"], function (Backbone) { + /** + * @class GeoScale + * @classdesc The GeoScale model stores the relative scale of the map in + * meters vs. pixels. This can be used to convert between the two. + * @classcategory Models/Maps + * @name GeoScale + * @since x.x.x + * @extends Backbone.Model + */ + var GeoScale = Backbone.Model.extend( + /** @lends GeoScale.prototype */ { + /** + * The type of model this is. + * @type {String} + */ + type: "GeoScale", + + /** + * Overrides the default Backbone.Model.defaults() function to specify + * default attributes for the GeoScale. + */ + defaults: function () { + return { + pixel: 1, + meters: null, + }; + }, + + // /** + // * Run when a new GeoScale is created. + // * @param {Object} attrs - An object specifying configuration options + // * for the GeoScale. If any config option is not specified, the default will be + // * used instead (see {@link GeoScale#defaults}). + // */ + // initialize: function (attrs, options) { + // try { + // // ... + // } catch (e) { + // console.log("Error initializing a GeoScale model", e); + // } + // }, + + /** + * Validate the model attributes + * @param {Object} attrs - The model's attributes + */ + validate: function (attrs, options) { + if (attrs.pixel < 0) { + return "Invalid pixel scale. Must be greater than 0."; + } + if (attrs.meters < 0) { + return "Invalid meters scale. Must be greater than 0."; + } + }, + } + ); + + return GeoScale; +}); diff --git a/src/js/models/maps/Map.js b/src/js/models/maps/Map.js index 9ae10fd0b..341c73962 100644 --- a/src/js/models/maps/Map.js +++ b/src/js/models/maps/Map.js @@ -4,10 +4,9 @@ define([ "jquery", "underscore", "backbone", - "collections/maps/Features", - "models/maps/Feature", "collections/maps/MapAssets", -], function ($, _, Backbone, Features, Feature, MapAssets) { + "models/maps/MapInteraction", +], function ($, _, Backbone, MapAssets, Interactions) { /** * @class MapModel * @classdesc The Map Model contains all of the settings and options for a @@ -119,8 +118,8 @@ define([ * pitch: -90, * roll: 0 * } - */ - + */ + /** * The type of model this is. * @type {String} @@ -143,11 +142,6 @@ define([ * 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 and other tools. True by default. * @property {Boolean} [showLayerList=true] - Whether or not to include @@ -161,20 +155,6 @@ define([ * 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, as well as the height/altitude in - * meters. * @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 @@ -197,29 +177,12 @@ define([ }, ]), terrains: new MapAssets(), - selectedFeatures: new Features(), showToolbar: true, showLayerList: true, showHomeButton: 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, - height: null, - }, clickFeatureAction: "showDetails", }; }, @@ -245,6 +208,7 @@ define([ this.set("terrains", new MapAssets(config.terrains)); } } + this.setUpInteractions(); } catch (error) { console.log( "There was an error initializing a Map model" + @@ -255,84 +219,55 @@ define([ }, /** - * Set or unset the selected Features. 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. + * Set or replace the MapInteraction model on the map. + * @returns {MapInteraction} The new interactions model. + * @since x.x.x */ - selectFeatures: function (features, replace = true) { - try { - const model = this; - - // Create a features collection if one doesn't already exist - if (!model.get("selectedFeatures")) { - model.set("selectedFeatures", new Features()); - } - - // Don't update the selected features collection if the newly selected - // features are identical. - const currentFeatures = model.get("selectedFeatures"); - if ( - features && - currentFeatures && - currentFeatures.length === features.length && - currentFeatures.containsFeatures(features) - ) { - return; - } + setUpInteractions: function () { + const interactions = new Interactions({ + mapModel: this, + }); + this.set("interactions", interactions); + return interactions; + }, - // If no feature is passed to this function (and replace is true), - // then empty the Features collection - features = !features || !Array.isArray(features) ? [] : features; + /** + * Select features on the map. Updates the selectedFeatures attribute on + * the MapInteraction model. + * @param {Feature[]} features - An array of Feature models to select. + * since x.x.x + */ + selectFeatures: function (features) { + this.get("interactions")?.selectFeatures(features); + }, - // Convert the feature objects that are types specific to the map view - // (Cesium) to a generic Feature model - features = model.convertFeatures(features); + /** + * Get the currently selected features on the map. + * @returns {Features} The selected Feature collection. + * @since x.x.x + */ + getSelectedFeatures: function () { + return this.get("interactions")?.get("selectedFeatures"); + }, - // 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("selectedFeatures").set(newAttrs, { remove: replace }); - } catch (e) { - console.log("Failed to select a Feature in a Map model.", e); - } + /** + * 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: function (target) { + this.get("interactions")?.set("zoomTarget", target); }, /** - * 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 + * Indicate that the map widget view should navigate to the home position. */ - convertFeatures: function (features) { - const model = this; - 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 model.get("layers").getFeatureAttributes(features)?.[0]; - }); - return attrs.map((attr) => new Feature(attr)); + flyHome: function () { + this.zoomTo(this.get("homePosition")); }, /** @@ -368,14 +303,13 @@ define([ * @since x.x.x */ removeAsset: function (asset) { - if(!asset) return; + if (!asset) return; const layers = this.get("layers"); - if(!layers) return; + 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); - } - + }, } ); diff --git a/src/js/models/maps/MapInteraction.js b/src/js/models/maps/MapInteraction.js new file mode 100644 index 000000000..b7dc2efd4 --- /dev/null +++ b/src/js/models/maps/MapInteraction.js @@ -0,0 +1,332 @@ +"use strict"; + +define([ + "backbone", + "collections/maps/Features", + "models/maps/Feature", + "models/maps/GeoBoundingBox", + "models/maps/GeoPoint", + "models/maps/GeoScale", +], function (Backbone, Features, Feature, GeoBoundingBox, GeoPoint, GeoScale) { + /** + * @class MapInteraction + * @classdesc The Map Interaction stores information about user interaction + * with a map, including the current position of the mouse, the feature that + * the mouse is currently hovering over, and the position on the map that the + * user has clicked, as well as the current view extent of the map. + * @classcategory Models/Maps + * @name MapInteraction + * @since x.x.x + * @extends Backbone.Model + */ + var MapInteraction = Backbone.Model.extend( + /** @lends MapInteraction.prototype */ { + /** + * The type of model this is. + * @type {String} + */ + type: "MapInteraction", + + /** + * Overrides the default Backbone.Model.defaults() function to specify + * default attributes for the Map. + * @returns {Object} The default attributes for the Map. + * @property {GeoPoint} mousePosition - The current position of the mouse + * on the map. + * @property {GeoPoint} clickedPosition - The position on the map that the + * user last clicked. + * @property {GeoScale} scale - The current scale of the map in + * pixels:meters. + * @property {GeoBoundingBox} viewExtent - The current extent of the map + * view. + * @property {Features} hoveredFeatures - The feature that the mouse is + * currently hovering over. + * @property {Features} clickedFeatures - The feature that the user last + * clicked. + * @property {Features} selectedFeatures - The feature that is currently + * selected. + * @property {Boolean} firstInteraction - Whether or not the user has + * interacted with the map yet. This is set to true when the user has + * clicked, hovered, panned, or zoomed the map. The only action that is + * ignored is mouse movement over the map. + * @property {String} previousAction - The previous action that was + * performed on the map. This may be any of the labels in the Cesium + * ScreenSpaceEventType enumeration: + * {@link https://cesium.com/learn/cesiumjs/ref-doc/global.html#ScreenSpaceEventType} + * @property {Feature|MapAsset|GeoBoundingBox} zoomTarget - The feature or + * map asset that the map should zoom to. The map widget should listen to + * this property and zoom to the specified feature or map asset when this + * property is set. The property should be cleared after the map widget + * has zoomed to the specified feature or map asset. + * + * TODO + * * @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, as well as the height/altitude in + * meters. + * + * * @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) + */ + defaults: function () { + return { + mousePosition: new GeoPoint(), + clickedPosition: new GeoPoint(), + scale: new GeoScale(), + viewExtent: new GeoBoundingBox(), + hoveredFeatures: new Features(), + clickedFeatures: new Features(), + selectedFeatures: new Features(), + firstInteraction: false, // <- "hasInteracted"? + previousAction: null, + zoomTarget: null, + }; + }, + + /** + * Run when a new Map is created. + * @param {MapConfig} attrs - An object specifying configuration options + * for the map. If any config option is not specified, the default will be + * used instead (see {@link MapInteraction#defaults}). + */ + initialize: function (attrs, options) { + try { + this.connectEvents(); + } catch (e) { + console.log("Error initializing a Map Interaction model", e); + } + }, + + /** + * Connects the MapInteraction model to events from the map widget. + */ + connectEvents: function () { + this.listenForFirstInteraction(); + this.listenTo(this, "change:previousAction", this.handleClick); + }, + + /** + * Listens for the first interaction with the map (click, hover, pan, or + * zoom) and sets the 'firstInteraction' attribute to true when it occurs. + */ + listenForFirstInteraction: function () { + if (model.get("firstInteraction")) return; + const listener = new Backbone.Model(); + const model = this; + listener.listenTo( + this, + "change:previousAction", + function (m, eventType) { + if (eventType != "MOUSE_MOVE") { + model.set("firstInteraction", true); + listener.stopListening(); + listener.destroy(); + } + } + ); + }, + + /** + * Handles a mouse click on the map. If the user has clicked on a feature, + * the feature is set as the 'clickedFeatures' attribute. If the map is + * configured to show details when a feature is clicked, the feature is + * also set as the 'selectedFeatures' attribute. + * @param {MapInteraction} m - The MapInteraction model. + * @param {String} action - The type of mouse click event that occurred. + * All except LEFT_CLICK are ignored. + */ + handleClick: function (m, action) { + if (action !== "LEFT_CLICK") return; + // 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") { + this.selectFeatures(hoveredFeatures); + } + }, + + /** + * Sets the position of the mouse on the map. Creates a new GeoPoint model + * if one doesn't already exist on the mousePosition attribute. + * @param {Object} position - An object with 'longitude' and 'latitude' + * properties. + * @returns {GeoPoint} The mouse position as a GeoPoint model. + */ + setMousePosition: function (position) { + let mousePosition = this.get("mousePosition"); + if (!mousePosition) { + mousePosition = new GeoPoint(); + this.set("mousePosition", mousePosition); + } + mousePosition.set(position); + return mousePosition; + }, + + /** + * Set the pixel:meter scale of the map. Creates a new GeoScale model if + * one doesn't already exist on the scale attribute. + * @param {Object} scale - An object with 'meters' and 'pixels' + * properties. + * @returns {GeoScale} The scale as a GeoScale model. + */ + setScale: function (scale) { + let scaleModel = this.get("scale"); + if (!scaleModel) { + scaleModel = new GeoScale(); + this.set("scale", scaleModel); + } + scaleModel.set(scale); + return scaleModel; + }, + + /** + * Set the extent of the map view. Creates a new GeoBoundingBox model if + * one doesn't already exist on the viewExtent attribute. + * @param {Object} extent - An object with 'north', 'east', 'south', and + * 'west' properties. + * @returns {GeoBoundingBox} The view extent as a GeoBoundingBox model. + */ + setViewExtent: function (extent) { + let viewExtent = this.get("viewExtent"); + if (!viewExtent) { + viewExtent = new GeoBoundingBox(); + this.set("viewExtent", viewExtent); + } + viewExtent.set(extent); + return viewExtent; + }, + + /** + * Set the feature that the mouse is currently hovering over. + * @param {Cesium.Entity|Cesium.Cesium3DTileFeature|Feature[]} features - + * An array of feature objects selected directly from the map view. + */ + setHoveredFeatures: function (features) { + this.setFeatures(features, "hoveredFeatures", true); + }, + + /** + * Set the feature that the user last clicked. + * @param {Cesium.Entity|Cesium.Cesium3DTileFeature|Feature[]|Object[]} + * features - An array of feature objects selected directly from the map + * view. + */ + setClickedFeatures: function (features) { + this.setFeatures(features, "clickedFeatures", true); + }, + + /** + * Set the feature that is currently selected. + * @param {Cesium.Entity|Cesium.Cesium3DTileFeature|Feature[|Object[]]} + * features - An array of feature objects selected directly from the map + * view. + */ + selectFeatures: function (features) { + this.setFeatures(features, "selectedFeatures", true); + }, + + /** + * Set features on either the hoveredFeatures, clickedFeatures, or + * selectedFeatures attribute. If the replace parameter is true, then the + * features will replace the current features on the attribute. + * @param {Cesium.Entity|Cesium.Cesium3DTileFeature|Feature[]|Object[]} + * features - An array of feature objects selected directly from the map + * view. + * @param {'hoveredFeatures'|'clickedFeatures'|'selectedFeatures'} type - + * The type of feature to set. + * @param {Boolean} [replace=true] - Whether or not to replace the current + * features on the attribute with the new features. + */ + setFeatures: function (features, type, replace = true) { + try { + const model = this; + + // 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 + 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), + if (!features || features.length === 0) { + if (replace) model.get(type).set([], { remove: true }); + return; + } + + // Ignore if new features are identical to the current features + const currentFeatures = model.get(type); + if ( + features && + currentFeatures && + currentFeatures.length === features.length && + currentFeatures.containsFeatures(features) + ) { + return; + } + + // Convert the feature objects, which may be types specific to the map + // widget (Cesium), to a generic Feature model + features = model.convertFeatures(features); + + // 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 }); + } 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)); + }, + } + ); + + return MapInteraction; +}); diff --git a/src/js/models/maps/assets/CesiumGeohash.js b/src/js/models/maps/assets/CesiumGeohash.js index c07e8e72c..b4f23328f 100644 --- a/src/js/models/maps/assets/CesiumGeohash.js +++ b/src/js/models/maps/assets/CesiumGeohash.js @@ -153,8 +153,7 @@ define([ getPrecision: function () { const limit = this.get("maxGeoHashes"); const geohashes = this.get("geohashes") - const bounds = this.get("mapModel").get("currentViewExtent"); - const area = geohashes.getBoundingBoxArea(bounds); + const area = this.getViewExtent().getArea(); return this.get("geohashes").getMaxPrecision(area, limit); }, @@ -220,12 +219,19 @@ define([ * @since 2.25.0 */ getGeohashesForExtent: function () { - const extent = this.get("mapModel")?.get("currentViewExtent"); - const bounds = Object.assign({}, extent); - delete bounds.height; + const bounds = this.getViewExtent()?.clone(); + if (!bounds) return this.get("geohashes"); return this.get("geohashes")?.getSubsetByBounds(bounds); }, + /** + * Get the current map extent from the Map Interaction model. + * @returns {GeoBoundingBox} The current map extent + */ + getViewExtent: function () { + return this.get("mapModel")?.get("interactions")?.get("viewExtent") + }, + /** * Returns the GeoJSON representation of the geohashes. * @param {Boolean} [limitToExtent = true] - Set to false to return the @@ -289,19 +295,23 @@ define([ * @since 2.25.0 */ selectGeohashes: function (geohashes) { - const toSelect = [...new Set(geohashes.map((geohash) => { - const parent = this.get("geohashes").getContainingGeohash(geohash); - return parent?.get("hashString"); - }, this))]; - const entities = this.get("cesiumModel").entities.values; - const selected = entities.filter((entity) => { - const hashString = this.getPropertiesFromFeature(entity).hashString; - return toSelect.includes(hashString); - }); - const featureAttrs = selected.map((feature) => { - return this.getFeatureAttributes(feature); - }); - this.get("mapModel").selectFeatures(featureAttrs); + try { + const toSelect = [...new Set(geohashes.map((geohash) => { + const parent = this.get("geohashes").getContainingGeohash(geohash); + return parent?.get("hashString"); + }, this))]; + const entities = this.get("cesiumModel").entities.values; + const selected = entities.filter((entity) => { + const hashString = this.getPropertiesFromFeature(entity).hashString; + return toSelect.includes(hashString); + }); + const featureAttrs = selected.map((feature) => { + return this.getFeatureAttributes(feature); + }); + this.get("mapModel").selectFeatures(featureAttrs); + } catch (e) { + console.log("Error selecting geohashes", e); + } }, } ); diff --git a/src/js/models/maps/assets/MapAsset.js b/src/js/models/maps/assets/MapAsset.js index cccbcf4b0..0441f4b46 100644 --- a/src/js/models/maps/assets/MapAsset.js +++ b/src/js/models/maps/assets/MapAsset.js @@ -325,44 +325,61 @@ define( 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) + } + } + + this.setListeners(); + } + catch (e) { + console.log('Error initializing a MapAsset model', e); + } + }, + + /** + * 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) } }) - // 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') + const mapModel = this.get('mapModel'); if (!mapModel) { return } - const selectedFeatures = mapModel.get('selectedFeatures') + const interactions = mapModel.get('interactions'); + const selectedFeatures = mapModel.getSelectedFeatures(); this.stopListening(selectedFeatures, 'update'); this.listenTo(selectedFeatures, 'update', this.updateAppearance) - this.stopListening(mapModel, 'change:selectedFeatures') - this.listenTo(mapModel, 'change:selectedFeatures', function () { + this.stopListening(interactions, 'change:selectedFeatures') + this.listenTo(interactions, 'change:selectedFeatures', function () { this.updateAppearance() setSelectFeaturesListeners() }) @@ -372,13 +389,16 @@ define( this.stopListening(this, 'change:mapModel', setSelectFeaturesListeners) this.listenTo(this, 'change:mapModel', setSelectFeaturesListeners) } + + // 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); } - catch (error) { - console.log( - 'There was an error initializing a MapAsset model' + - '. Error details: ' + error - ); - } + }, /** @@ -407,7 +427,7 @@ define( featureIsSelected: function (feature) { const map = this.get('mapModel') if (!map) { return false } - return map.get('selectedFeatures').containsFeature(feature) + return map.getSelectedFeatures(); }, /** @@ -796,6 +816,16 @@ define( } }, + /** + * 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. @@ -821,72 +851,6 @@ define( } }, - // /** - // * 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; diff --git a/src/js/views/maps/CesiumWidgetView.js b/src/js/views/maps/CesiumWidgetView.js index b30fb0f4e..fd8ea913c 100644 --- a/src/js/views/maps/CesiumWidgetView.js +++ b/src/js/views/maps/CesiumWidgetView.js @@ -1,1422 +1,1558 @@ -'use strict'; - -define( - [ - 'jquery', - 'underscore', - 'backbone', - 'cesium', - 'models/maps/Map', - 'models/maps/assets/MapAsset', - 'models/maps/assets/Cesium3DTileset', - 'models/maps/Feature', - 'text!templates/maps/cesium-widget-view.html' - ], - function ( - $, - _, - Backbone, - Cesium, - Map, - MapAsset, - Cesium3DTileset, - Feature, - Template - ) { - - /** - * @class CesiumWidgetView - * @classdesc An interactive 2D and/or 3D map/globe rendered using CesiumJS. This view - * comprises the globe without any of the UI elements like the scalebar, layer list, - * etc. - * @classcategory Views/Maps - * @name CesiumWidgetView - * @extends Backbone.View - * @screenshot views/maps/CesiumWidgetView.png - * @since 2.18.0 - * @constructs - * @fires CesiumWidgetView#moved - * @fires CesiumWidgetView#moveEnd - * @fires CesiumWidgetView#moveStart - * @fires Map#moved - * @fires Map#moveEnd - * @fires Map#moveStart - */ - var CesiumWidgetView = Backbone.View.extend( - /** @lends CesiumWidgetView.prototype */{ - - /** - * The type of View this is - * @type {string} - */ - type: 'CesiumWidgetView', - - /** - * The HTML classes to use for this view's element. Note that the first child - * element added to this view by cesium will have the class "cesium-widget". - * @type {string} - */ - className: 'cesium-widget-view', - - /** - * The model that this view uses - * @type {Map} - */ - model: null, - - /** - * The primary HTML template for this view - * @type {Underscore.template} - */ - template: _.template(Template), - - /** - * An array of objects the match a Map Asset's type property to the function in - * this view that adds and renders that asset on the map, given the Map Asset - * model. Each object in the array has two properties: 'types' and - * 'renderFunction'. - * @type {Object[]} - * @property {string[]} types The list of types that can be added to the map given - * the renderFunction - * @property {string} renderFunction The name of the function in the view that - * will add the asset to the map and render it, when passed the cesiumModel - * attribute from the MapAsset model - * @property {string} removeFunction The name of the function in the view that - * will remove the asset from the map, when passed the cesiumModel attribute from - * the MapAsset model - */ - mapAssetRenderFunctions: [ - { - types: ['Cesium3DTileset'], - renderFunction: 'add3DTileset', - removeFunction: 'remove3DTileset' - }, - { - types: ['GeoJsonDataSource', 'CzmlDataSource'], - renderFunction: 'addVectorData', - removeFunction: 'removeVectorData' - }, - { - types: ['BingMapsImageryProvider', 'IonImageryProvider', 'TileMapServiceImageryProvider', 'WebMapTileServiceImageryProvider', 'WebMapServiceImageryProvider', 'OpenStreetMapImageryProvider'], - renderFunction: 'addImagery', - removeFunction: 'removeImagery' - }, - { - types: ['CesiumTerrainProvider'], - renderFunction: 'updateTerrain', - removeFunction: null - } - ], - - /** - * The border color to use on vector features that a user clicks. - * See {@link https://cesium.com/learn/cesiumjs/ref-doc/Color.html?classFilter=color} - * @type {Cesium.Color} - */ - // TODO - Consider making this color configurable in the Map model - highlightBorderColor: Cesium.Color.WHITE, - - /** - * Executed when a new CesiumWidgetView is created - * @param {Object} [options] - A literal object with options to pass to the view - */ - initialize: function (options) { - try { - - // Set the Cesium Ion token (required for some map features) - Cesium.Ion.defaultAccessToken = MetacatUI.appModel.get('cesiumToken'); - - // Get all the options and apply them to this view - if (typeof options == 'object') { - for (const [key, value] of Object.entries(options)) { - this[key] = value; - } +"use strict"; + +define([ + "jquery", + "underscore", + "backbone", + "cesium", + "models/maps/Map", + "models/maps/assets/MapAsset", + "models/maps/assets/Cesium3DTileset", + "models/maps/Feature", + "text!templates/maps/cesium-widget-view.html", +], function ( + $, + _, + Backbone, + Cesium, + Map, + MapAsset, + Cesium3DTileset, + Feature, + Template +) { + /** + * @class CesiumWidgetView + * @classdesc An interactive 2D and/or 3D map/globe rendered using CesiumJS. + * This view comprises the globe without any of the UI elements like the + * scalebar, layer list, etc. + * @classcategory Views/Maps + * @name CesiumWidgetView + * @extends Backbone.View + * @screenshot views/maps/CesiumWidgetView.png + * @since 2.18.0 + * @constructs + * @fires MapInteraction#moved + * @fires MapInteraction#moveEnd + * @fires MapInteraction#moveStart + */ + var CesiumWidgetView = Backbone.View.extend( + /** @lends CesiumWidgetView.prototype */ { + /** + * The type of View this is + * @type {string} + */ + type: "CesiumWidgetView", + + /** + * The HTML classes to use for this view's element. Note that the first + * child element added to this view by cesium will have the class + * "cesium-widget". + * @type {string} + */ + className: "cesium-widget-view", + + /** + * The model that this view uses + * @type {Map} + */ + model: null, + + /** + * The primary HTML template for this view + * @type {Underscore.template} + */ + template: _.template(Template), + + /** + * An array of objects the match a Map Asset's type property to the + * function in this view that adds and renders that asset on the map, + * given the Map Asset model. Each object in the array has two properties: + * 'types' and 'renderFunction'. + * @type {Object[]} + * @property {string[]} types The list of types that can be added to the + * map given the renderFunction + * @property {string} renderFunction The name of the function in the view + * that will add the asset to the map and render it, when passed the + * cesiumModel attribute from the MapAsset model + * @property {string} removeFunction The name of the function in the view + * that will remove the asset from the map, when passed the cesiumModel + * attribute from the MapAsset model + */ + mapAssetRenderFunctions: [ + { + types: ["Cesium3DTileset"], + renderFunction: "add3DTileset", + removeFunction: "remove3DTileset", + }, + { + types: ["GeoJsonDataSource", "CzmlDataSource"], + renderFunction: "addVectorData", + removeFunction: "removeVectorData", + }, + { + types: [ + "BingMapsImageryProvider", + "IonImageryProvider", + "TileMapServiceImageryProvider", + "WebMapTileServiceImageryProvider", + "WebMapServiceImageryProvider", + "OpenStreetMapImageryProvider", + ], + renderFunction: "addImagery", + removeFunction: "removeImagery", + }, + { + types: ["CesiumTerrainProvider"], + renderFunction: "updateTerrain", + removeFunction: null, + }, + ], + + /** + * The border color to use on vector features that a user clicks. See + * {@link https://cesium.com/learn/cesiumjs/ref-doc/Color.html?classFilter=color} + * @type {Cesium.Color} + */ + // TODO - Make this color configurable in the Map model + highlightBorderColor: Cesium.Color.WHITE, + + /** + * Executed when a new CesiumWidgetView is created + * @param {Object} [options] - A literal object with options to pass to + * the view + */ + initialize: function (options) { + try { + // Set the Cesium Ion token (required for some map features) + Cesium.Ion.defaultAccessToken = MetacatUI.appModel.get("cesiumToken"); + + // Get all the options and apply them to this view + if (typeof options == "object") { + for (const [key, value] of Object.entries(options)) { + this[key] = value; } + } - // Make sure that there is a Map model and that it has a selectedFeature - // attribute. The selectedFeature attribute is used to store information about - // the vector feature, if any, that is currently in focus on the map. - if (!this.model) { - this.model = new Map() - } - if (!this.model.get('selectedFeatures')) { - this.model.selectFeatures() - } + if (!this.model) { + this.model = new Map(); + } + if (!this.model.get("interactions")) { + this.model.setUpInteractions(); + } + this.interactions = this.model.get("interactions"); + // The selectedFeature attribute is used to store information about + // the vector feature, if any, that is currently in focus on the map. + if (!this.interactions.get("selectedFeatures")) { + this.interactions.selectFeatures(); + } + } catch (e) { + console.log("Failed to initialize a CesiumWidgetView. ", e); + } + }, + + /** + * Renders this view + * @return {CesiumWidgetView} Returns the rendered view element + */ + render: function () { + try { + // If Cesium features are disabled in the AppConfig, then exit without + // rendering anything. + if (!MetacatUI.appModel.get("enableCesium")) { + return; + } + // Save a reference to this view + const view = this; - } catch (e) { - console.log('Failed to initialize a CesiumWidgetView. Error message: ' + e); - } + // Insert the template into the view + view.$el.html(view.template({})); - }, + // Create the Cesium Widget + view.renderWidget(); - /** - * Renders this view - * @return {CesiumWidgetView} Returns the rendered view element - */ - render: function () { + // Configure the lighting on the globe + view.setLighting(); - try { + // Prepare Cesium to handle vector datasources (e.g. + // geoJsonDataSources) + view.setUpDataSourceDisplay(); - // If Cesium features are disabled in the AppConfig, then exit without rendering - // anything. - if (!MetacatUI.appModel.get('enableCesium')) { - return; - } + // Listeners for changes & events to the layers & map + view.setAssetListeners(); + view.setNavigationListeners(); + // Listen to Cesium screen space events and update Interactions model + view.setCameraListeners(); + view.setMouseListeners(); + // Listen to Interactions model and react when e.g. something is + // clicked + view.setInteractionListeners(); - // Save a reference to this view - const view = this; - - // Insert the template into the view - view.$el.html(view.template({})); - - // Ensure the view's main element has the given class name - view.el.classList.add(view.className); - - // Clock will be used for the timeline component, and for the clock.ontick - // event - view.clock = new Cesium.Clock({ shouldAnimate: false }) - - // Create the Cesium Widget and save a reference to it to the view - view.widget = new Cesium.CesiumWidget(view.el, { - clock: view.clock, - // We will add a base imagery layer after initialization - imageryProvider: false, - terrain: false, - useBrowserRecommendedResolution: false, - // Use explicit rendering to make the widget must faster. - // See https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance - requestRenderMode: true, - // Need to change the following once we support a time/clock component. - // See https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/#handling-simulation-time-changes. - maximumRenderTimeChange: Infinity - }); + // Render the layers + view.addLayers(); - // Save references to parts of the widget that the view will access often - view.scene = view.widget.scene; - view.camera = view.widget.camera; - view.inputHandler = view.widget.screenSpaceEventHandler; + // Go to the home position, if one is set. + view.flyHome(0); - // Decrease the amount the camera must change before the changed event is - // raised. - view.camera.percentChanged = 0.1 + // Set the map up so that selected features may be highlighted + view.setUpSilhouettes(); - // Disable HDR lighting for better performance and to avoid changing imagery colors. - view.scene.highDynamicRange = false; - view.scene.globe.enableLighting = false; + return this; + } catch (e) { + console.log("Failed to render a CesiumWidgetView,", e); + // TODO: Render a fallback map or error message + } + }, + + /** + * Create the Cesium Widget and save a reference to it to the view + * @since x.x.x + * @returns {Cesium.CesiumWidget} The Cesium Widget + */ + renderWidget: function () { + const view = this; + // Clock for timeline component & updating data sources + view.clock = new Cesium.Clock({ shouldAnimate: false }); + + // Create the Cesium Widget and save a reference to it to the view + view.widget = new Cesium.CesiumWidget(view.el, { + clock: view.clock, + // We will add a base imagery layer after initialization + imageryProvider: false, + terrain: false, + useBrowserRecommendedResolution: false, + // Use explicit rendering to make the widget must faster. See + // https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance + requestRenderMode: true, + // Need to change the following once we support a time/clock + // component. See + // https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/#handling-simulation-time-changes. + maximumRenderTimeChange: Infinity, + }); + + // Save references to parts of widget the view will access often + view.scene = view.widget.scene; + view.camera = view.widget.camera; + + return view.widget; + }, + + /** + * Create a DataSourceDisplay and DataSourceCollection for the Cesium + * widget, and listen to the clock tick to update the display. This is + * required to display vector data (e.g. GeoJSON) on the map. + * @since x.x.x + * @returns {Cesium.DataSourceDisplay} The Cesium DataSourceDisplay + */ + setUpDataSourceDisplay: function () { + const view = this; + view.dataSourceCollection = new Cesium.DataSourceCollection(); + view.dataSourceDisplay = new Cesium.DataSourceDisplay({ + scene: view.scene, + dataSourceCollection: view.dataSourceCollection, + }); + view.clock.onTick.removeEventListener( + view.updateDataSourceDisplay, + view + ); + view.clock.onTick.addEventListener(view.updateDataSourceDisplay, view); + return view.dataSourceDisplay; + }, + + /** + * Because the Cesium widget is configured to use explicit rendering (see + * {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/}), + * we need to tell Cesium when to render a new frame if it's not one of + * the cases handle automatically. This function tells the Cesium scene to + * render, but is limited by the underscore.js debounce function to only + * happen a maximum of once every 50 ms (see + * {@link https://underscorejs.org/#debounce}). + */ + requestRender: _.debounce(function () { + this.scene.requestRender(); + }, 90), + + /** + * Functions called after each time the scene renders. If a zoom target + * has been set by the {@link CesiumWidgetView#flyTo} function, then calls + * the functions that calculates the bounding sphere and zooms to it + * (which required to visual elements to be rendered first.) + */ + postRender: function () { + try { + const view = this; + if (view.zoomTarget) { + view.completeFlight(view.zoomTarget, view.zoomOptions); + } + } catch (e) { + console.log("Error calling post render functions:", e); + } + }, + + /** + * Runs on every Cesium clock tick. Updates the display of the + * CesiumVectorData models in the scene. Similar to + * Cesium.DataSourceDisplay.update function, in that it runs update() on + * each DataSource and each DataSource's visualizer, except that it also + * updates each CesiumVectorData model's 'displayReady' attribute. (Sets + * to true when the asset is ready to be rendered in the map, false + * otherwise). Also re-renders the scene when the displayReady attribute + * changes. + */ + updateDataSourceDisplay: function () { + try { + const view = this; + const layers = view.model.get("layers"); - // Keep all parts of the globe lit regardless of what time the Cesium clock is - // set to. This avoids data and imagery appearing too dark. - view.scene.light = new Cesium.DirectionalLight({ - direction: new Cesium.Cartesian3(1, 0, 0) - }); - view.scene.preRender.addEventListener(function (scene, time) { - view.scene.light.direction = Cesium.Cartesian3.clone( - scene.camera.directionWC, view.scene.light.direction - ); - }); + var dataSources = view.dataSourceDisplay.dataSources; + if (!dataSources || !dataSources.length) { + return; + } - // Prepare Cesium to handle vector datasources (e.g. geoJsonDataSources) - view.dataSourceCollection = new Cesium.DataSourceCollection(); - view.dataSourceDisplay = new Cesium.DataSourceDisplay({ - scene: view.scene, - dataSourceCollection: view.dataSourceCollection, - }); - view.clock.onTick.addEventListener(function () { - view.updateDataSourceDisplay.call(view) - }) + let allReady = true; + const allReadyBefore = view.dataSourceDisplay._ready; - view.setListeners(); - view.addLayers(); + for (let i = 0, len = dataSources.length; i < len; i++) { + const time = view.clock.currentTime; + const dataSource = dataSources.get(i); + const visualizers = dataSource._visualizers; - // Go to the home position, if one is set. - view.flyHome(0) + const assetModel = layers.findWhere({ + cesiumModel: dataSource, + }); + let displayReadyNow = dataSource.update(time); - // If users are allowed to click on features for more details, - // initialize picking behavior on the map. - if (view.model.get('showFeatureInfo')) { - view.initializePicking() + for (let x = 0; x < visualizers.length; x++) { + displayReadyNow = visualizers[x].update(time) && displayReadyNow; } - return this + assetModel.set("displayReady", displayReadyNow); + allReady = displayReadyNow && allReady; } - catch (error) { - console.log( - 'Failed to render a CesiumWidgetView. Error details: ' + error - ); - } - }, - - /** - * Set all of the listeners for the CesiumWidgetView. This function is - * called during the render function. - * @since 2.26.0 - */ - setListeners: function () { + // If any dataSource has switched display states, then re-render the + // scene. + if (allReady !== allReadyBefore) { + view.scene.requestRender(); + } + // The dataSourceDisplay must be set to 'ready' to get bounding + // spheres for dataSources + view.dataSourceDisplay._ready = allReady; + } catch (e) { + console.log("Error updating the data source display.", e); + } + }, + + /** + * Configure the lighting on the globe. + */ + setLighting: function () { + const view = this; + // Disable HDR lighting for better performance & to keep imagery + // consistently lit. + view.scene.highDynamicRange = false; + view.scene.globe.enableLighting = false; + + // Keep all parts of the globe lit regardless of what time the Cesium + // clock is set to. This avoids data and imagery appearing too dark. + view.scene.light = new Cesium.DirectionalLight({ + direction: new Cesium.Cartesian3(1, 0, 0), + }); + view.scene.preRender.addEventListener(function (scene, time) { + view.scene.light.direction = Cesium.Cartesian3.clone( + scene.camera.directionWC, + view.scene.light.direction + ); + }); + }, + + /** + * Set up the Cesium scene and set listeners and behavior that enable + * users to click on vector features on the map to highlight them. + * @since x.x.x + */ + setUpSilhouettes: function () { + try { + // Save a reference to this view the Cesium scene + var view = this; + var scene = this.scene; + + // To add an outline to 3D tiles in Cesium, we 'silhouette' them. Set + // up the the scene to support silhouetting. + view.silhouettes = + Cesium.PostProcessStageLibrary.createEdgeDetectionStage(); + view.silhouettes.uniforms.color = view.highlightBorderColor; + view.silhouettes.uniforms.length = 0.02; + view.silhouettes.selected = []; + scene.postProcessStages.add( + Cesium.PostProcessStageLibrary.createSilhouetteStage([ + view.silhouettes, + ]) + ); + } catch (e) { + console.log("Error initializing picking in a CesiumWidgetView", e); + } + }, + + /** + * Listen for changes to the assets and update the map accordingly. + * @since x.x.x + */ + setAssetListeners: function () { + const view = this; + const model = view.model; + const layers = model.get("layers"); + + // Listen for addition or removal of layers TODO: Add similar listeners + // for terrain + view.stopListening(layers, "add"); + view.listenTo(layers, "add", view.addAsset); + view.stopListening(layers, "remove"); + view.listenTo(layers, "remove", view.removeAsset); + + // Each layer fires 'appearanceChanged' whenever the color, opacity, + // etc. has been updated. Re-render the scene when this happens. + view.stopListening(layers, "appearanceChanged"); + view.listenTo(layers, "appearanceChanged", view.requestRender); + + // Reset asset listeners if the layers collection is replaced + view.stopListening(model, "change:layers"); + view.listenTo(model, "change:layers", view.setAssetListeners); + }, + + /** + * Remove listeners for dynamic navigation. + * @since x.x.x + */ + removeNavigationListeners: function () { + this.stopListening(this.interactions, "change:zoomTarget", this.flyTo); + if (this.removePostRenderListener) this.removePostRenderListener(); + }, + + /** + * Set up listeners to allow for dynamic navigation. This includes zooming + * to the extent of a layer and zooming to the home position. Note that + * other views may trigger an event on the layer/asset model that + * indicates that the map should navigate to a given extent. + * @since x.x.x + */ + setNavigationListeners: function () { + this.removeNavigationListeners(); + // Zoom functions executed after each scene render + this.removePostRenderListener = this.scene.postRender.addEventListener( + this.postRender, + this + ); + this.listenTo(this.interactions, "change:zoomTarget", function () { + const target = this.interactions.get("zoomTarget"); + if (target) { + this.flyTo(target); + } + }); + }, + + /** + * Remove any previously set camera listeners. + * @since x.x.x + */ + removeCameraListeners: function () { + if (!this.cameraListeners) this.cameraListeners = []; + this.cameraListeners.forEach(function (removeListener) { + removeListener(); + }); + }, + + /** + * Listen to cesium camera events, and translate them to events on the + * interactions model. Also update the scale (pixels:meters) and the view + * extent when the camera has moved. + */ + setCameraListeners: function () { + try { const view = this; - - // Listen for addition or removal of layers - // TODO: Add similar listeners for terrain - const layers = view.model.get('layers') - view.stopListening(layers, 'add'); - view.listenTo(layers, 'add', view.addAsset); - view.stopListening(layers, 'remove'); - view.listenTo(layers, 'remove', view.removeAsset); - - // Zoom functions executed after each scene render - view.scene.postRender.addEventListener(function () { - view.postRender(); + const camera = view.camera; + const interactions = view.interactions; + + // Remove any previously set camera listeners + view.removeCameraListeners(); + // Amount camera must change before firing 'changed' event. + camera.percentChanged = 0.1; + + // Functions to run for each Cesium camera event + const cameraEvents = { + moveEnd: [], + moveStart: [], + changed: ["updateScale", "updateViewExtent"], + }; + // add a listener that triggers the same event on the interactions + // model, and runs any functions configured above. + Object.entries(cameraEvents).forEach(function ([label, functions]) { + const callback = function () { + interactions.trigger(label); + functions.forEach(function (func) { + view[func].call(view); + }); + }; + const remover = camera[label].addEventListener(callback, view); + view.cameraListeners.push(remover); }); - - // When the user first interacts with the map, update the model. - // Ignore the event where the user just moves the mouse over the map. - view.listenOnceForInteraction(function () { - view.model.set('firstInteraction', true); - }, ["MOUSE_MOVE"]); - - // Set listeners for when the Cesium camera changes a significant - // amount. - view.camera.changed.addEventListener(function () { - view.trigger('moved') - view.model.trigger('moved') - // Update the bounding box for the visible area in the Map model - view.updateViewExtent() - // If the scale bar is showing, update the pixel to meter scale on - // the map model when the camera angle/zoom level changes - if (view.model.get('showScaleBar')) { - view.updateCurrentScale() + } catch (e) { + console.log("Error updating the model on camera events", e); + } + }, + + /** + * Remove any previously set mouse listeners. + * @since x.x.x + */ + removeMouseListeners: function () { + if (this.mouseEventHandler) this.mouseEventHandler.destroy(); + }, + + /** + * Set up listeners for mouse events on the map. This includes listening + * for mouse clicks, mouse movement, and mouse hovering over features. + * These listeners simply update the interactions model with mouse events. + * @since x.x.x + */ + setMouseListeners: function () { + const view = this; + const events = Cesium.ScreenSpaceEventType; + + // Remove previous listeners if they exist. + view.removeMouseListeners; + // Create Cesium object that handles interactions with the map. + const handler = (view.mouseEventHandler = + new Cesium.ScreenSpaceEventHandler(view.scene.canvas)); + + // Every time the user interacts with the map, update the interactions + // model with the type of interaction that occurred. + Object.entries(events).forEach(function ([label, value]) { + handler.setInputAction(function (event) { + view.interactions.set("previousAction", label); + if (label == "MOUSE_MOVE") { + const position = event.position || event.endPosition; + view.setMousePosition(position); + view.setHoveredFeatures(position); } - }) - - view.camera.moveEnd.addEventListener(function () { - view.trigger('moveEnd') - view.model.trigger('moveEnd') - }) - view.camera.moveStart.addEventListener(function () { - view.trigger('moveStart') - view.model.trigger('moveStart') - }) - - // Sets listeners for when the mouse moves, depending on the value - // of the map model's showScaleBar and showFeatureInfo attributes - view.setMouseMoveListeners() - - // When the appearance of a layer has been updated, then tell Cesium - // to re-render the scene. Each layer model triggers the - // 'appearanceChanged' function whenever the color, opacity, etc. - // has been updated in the associated Cesium model. - view.stopListening(view.model.get('layers'), 'appearanceChanged') - view.listenTo(view.model.get('layers'), 'appearanceChanged', view.requestRender) - - // Other views may trigger an event on the layer/asset model that - // indicates that the map should navigate to the extent of the data, - // or on the Map model to navigate to the home position. - view.stopListening(view.model.get('layers'), 'flyToExtent') - view.listenTo(view.model.get('layers'), 'flyToExtent', view.flyTo) - view.stopListening(view.model, 'flyHome') - view.listenTo(view.model, 'flyHome', view.flyHome) - }, - - /** - * Listen for any user interaction with the map. Once an interaction has - * occurred, run the callback function and stop listening for - * interactions. Useful for detecting the first user interaction with the - * map. - * @param {function} callback - The function to run once the interaction - * has occurred. - * @param {string[]} ignore - An array of Cesium.ScreenSpaceEventType - * labels to ignore. See - * {@link https://cesium.com/learn/cesiumjs/ref-doc/ScreenSpaceEventType.html} - * @since 2.26.0 - */ - listenOnceForInteraction: function ( - callback, - ignore = [] - ) { - const view = this; - const events = Cesium.ScreenSpaceEventType; - const inputHandler = new Cesium.ScreenSpaceEventHandler( - view.scene.canvas - ); - if (!ignore || !Array.isArray(ignore)) ignore = []; - - Object.entries(events).forEach(function ([label, value]) { - if (ignore.includes(label)) return; - inputHandler.setInputAction(function () { - callback(); - inputHandler.destroy(); - }, value); + }, value); + }); + }, + + /** + * When the mouse is moved over the map, update the interactions model + * with the current mouse position. + * @param {Object} event - The event object from Cesium + * @since x.x.x + */ + setMousePosition: function (position) { + if (!position) return; + const view = this; + const pickRay = view.camera.getPickRay(position); + const cartesian = view.scene.globe.pick(pickRay, view.scene); + let newPosition = null; + if (cartesian) { + newPosition = view.getDegreesFromCartesian(cartesian); + } + view.interactions.setMousePosition(newPosition); + }, + + setHoveredFeatures: function (position, delay = 200) { + const view = this; + const lastCall = this.setHoveredFeaturesLastCall || 0; + const now = new Date().getTime(); + if (now - lastCall < delay) return; + this.setHoveredFeaturesLastCall = now; + const pickedFeature = view.scene.pick(position); + view.interactions.setHoveredFeatures([pickedFeature]); + }, + + setInteractionListeners: function () { + // TODO: unset listeners too + const interactions = this.interactions; + const hoveredFeatures = interactions.get("hoveredFeatures"); + this.listenTo(hoveredFeatures, "change update", this.updateCursor); + // this.listenTo( interactions, "change update", + // this.handleClickedFeatures + // ); + }, + + /** + * Change the cursor to a pointer when the mouse is hovering over a + * feature. + * @param {Object|null} hoveredFeatures - The feature that the mouse is + * hovering over or null if the mouse is not hovering over a feature. + */ + // When mouse moves? maybe throttle mouse move... + updateCursor: function (hoveredFeatures) { + const view = this; + let cursorStyle = "default"; + if (hoveredFeatures && hoveredFeatures.length) { + cursorStyle = "pointer"; + } + view.el.style.cursor = cursorStyle; + }, + + // TODO + showSelectedFeatures: function () { + // Remove highlights from previously selected 3D tiles + view.silhouettes.selected = []; + // Highlight the newly selected 3D tiles + selectedFeatures + .getFeatureObjects("Cesium3DTileFeature") + .forEach(function (featureObject) { + view.silhouettes.selected.push(featureObject); }); - }, - - /** - * Add all of the model's layers to the map. This function is called - * during the render function. - * @since 2.26.0 - */ - addLayers: function () { - + }, + + /** + * Add all of the model's layers to the map. This function is called + * during the render function. + * @since 2.26.0 + */ + addLayers: function () { + const view = this; + + // Add each layer from the Map model to the Cesium widget. Render using + // the function configured in the View's mapAssetRenderFunctions + // property. Add in reverse order for layers to appear in the correct + // order on the map. + const layers = view.model.get("layers"); + _.each(layers.last(layers.length).reverse(), function (mapAsset) { + view.addAsset(mapAsset); + }); + + // The Cesium Widget will support just one terrain option to start. + // Later, we'll allow users to switch between terrains if there is more + // than one. + var terrains = view.model.get("terrains"); + var terrainModel = terrains ? terrains.first() : false; + if (terrainModel) { + view.addAsset(terrainModel); + } + }, + + /** + * Move the camera position and zoom to the specified target entity or + * position on the map, using a nice animation. This function starts the + * flying/zooming action by setting a zoomTarget and zoomOptions on the + * view and requesting the scene to render. The actual zooming is done by + * {@link CesiumWidgetView#completeFlight} after the scene has finished + * rendering. + * @param {MapAsset|Cesium.BoundingSphere|Object|Feature} target The + * target asset, bounding sphere, or location to change the camera focus + * to. If target is a MapAsset, then the bounding sphere from that asset + * will be used for the target destination. If target is an Object, it may + * contain any of the properties that are supported by the Cesium camera + * flyTo options, see + * {@link https://cesium.com/learn/cesiumjs/ref-doc/Camera.html#flyTo}. If + * the target is a Feature, then it must be a Feature of a + * CesiumVectorData layer (currently Cesium3DTileFeatures are not + * supported). The target can otherwise be a Cesium BoundingSphere, see + * {@link https://cesium.com/learn/cesiumjs/ref-doc/BoundingSphere.html} + * @param {object} options - For targets that are a bounding sphere or + * asset, options to pass to Cesium Camera.flyToBoundingSphere(). See + * {@link https://cesium.com/learn/cesiumjs/ref-doc/Camera.html#flyToBoundingSphere}. + */ + flyTo: function (target, options) { + this.zoomTarget = target; + this.zoomOptions = options; + this.requestRender(); + }, + + /** + * This function is called by {@link CesiumWidgetView#postRender}; it + * should only be called once the target has been fully rendered in the + * scene. This function gets the bounding sphere, if required, and moves + * the scene to encompass the full extent of the target. + * @param {MapAsset|Cesium.BoundingSphere|Object|Feature|GeoPoint} target + * The target asset, bounding sphere, or location to change the camera + * focus to. If target is a MapAsset, then the bounding sphere from that + * asset will be used for the target destination. If target is an Object, + * it may contain any of the properties that are supported by the Cesium + * camera flyTo options, see + * {@link https://cesium.com/learn/cesiumjs/ref-doc/Camera.html#flyTo}. + * The object may also be a position with longitude, latitude, and height. + * If the target is a Feature, then it must be a Feature of a + * CesiumVectorData layer (currently Cesium3DTileFeatures are not + * supported). The target can otherwise be a Cesium BoundingSphere, see + * {@link https://cesium.com/learn/cesiumjs/ref-doc/BoundingSphere.html} + * @param {object} options - For targets that are a bounding sphere or + * asset, options to pass to Cesium Camera.flyToBoundingSphere(). See + * {@link https://cesium.com/learn/cesiumjs/ref-doc/Camera.html#flyToBoundingSphere}. + * For other targets, options will be merged with the target object and + * passed to Cesium Camera.flyTo(). See + * {@link https://cesium.com/learn/cesiumjs/ref-doc/Camera.html#flyTo} + */ + completeFlight: function (target, options) { + try { const view = this; + if (typeof options !== "object") options = {}; - // Add each layer from the Map model to the Cesium widget. Render - // using the function configured in the View's mapAssetRenderFunctions - // property. Add in reverse order for layers to appear in the correct - // order on the map. - const layers = view.model.get('layers') - _.each(layers.last(layers.length).reverse(), function (mapAsset) { - view.addAsset(mapAsset) - }); - - // The Cesium Widget will support just one terrain option to start. - // Later, we'll allow users to switch between terrains if there is - // more than one. - var terrains = view.model.get('terrains') - var terrainModel = terrains ? terrains.first() : false; - if (terrainModel) { - view.addAsset(terrainModel) + // A target is required + if (!target) { + return; } - }, - /** - * Because the Cesium widget is configured to use explicit rendering (see - * {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/}), - * we need to tell Cesium when to render a new frame if it's not one of the cases - * handle automatically. This function tells the Cesium scene to render, but is - * limited by the underscore.js debounce function to only happen a maximum of once - * every 50 ms (see {@link https://underscorejs.org/#debounce}). - */ - requestRender: _.debounce(function () { - this.scene.requestRender() - }, 50), - - /** - * Functions called after each time the scene renders. If a zoom target has been - * set by the {@link CesiumWidgetView#flyTo} function, then calls the functions - * that calculates the bounding sphere and zooms to it (which required to visual - * elements to be rendered first.) - */ - postRender: function () { - try { - if (this.zoomTarget) { - this.completeFlight(this.zoomTarget, this.zoomOptions) - this.zoomTarget = null; - this.zoomOptions = null; - } + // If the target is a Bounding Sphere, use the camera's built-in + // function + if (target instanceof Cesium.BoundingSphere) { + view.camera.flyToBoundingSphere(target, options); + view.resetZoomTarget(); + return; } - catch (error) { - console.log( - 'There was an error calling post render functions in a CesiumWidgetView' + - '. Error details: ' + error - ); - } - }, - - /** - * Runs on every Cesium clock tick. Updates the display of the CesiumVectorData - * models in the scene. Similar to Cesium.DataSourceDisplay.update function, in - * that it runs update() on each DataSource and each DataSource's visualizer, - * except that it also updates each CesiumVectorData model's 'displayReady' - * attribute. (Sets to true when the asset is ready to be rendered in the map, - * false otherwise). Also re-renders the scene when the displayReady attribute - * changes. - */ - updateDataSourceDisplay: function () { - try { - const view = this; - const layers = view.model.get('layers') - - var dataSources = view.dataSourceDisplay.dataSources; - if (!dataSources || !dataSources.length) { - return - } - - let allReady = true; - const allReadyBefore = view.dataSourceDisplay._ready; - - for (let i = 0, len = dataSources.length; i < len; i++) { - - const time = view.clock.currentTime; - const dataSource = dataSources.get(i); - const visualizers = dataSource._visualizers; - - const assetModel = layers.findWhere({ - cesiumModel: dataSource - }) - const displayReadyBefore = assetModel.get('displayReady') - let displayReadyNow = dataSource.update(time) - - for (let x = 0; x < visualizers.length; x++) { - displayReadyNow = visualizers[x].update(time) && displayReadyNow; - } - - assetModel.set('displayReady', displayReadyNow) - allReady = displayReadyNow && allReady - - } - - // If any dataSource has switched display states, then re-render the scene. - if (allReady !== allReadyBefore) { - view.scene.requestRender() - } - // The dataSourceDisplay must be set to 'ready' to get bounding spheres for - // dataSources - view.dataSourceDisplay._ready = allReady - - } - catch (error) { - console.log( - 'There was an error updating the data source display in a CesiumWidgetView' + - '. Error details: ' + error - ); + // If the target is some type of map asset, then get a Bounding Sphere + // for that asset and call this function again. + if ( + target instanceof MapAsset && + typeof target.getBoundingSphere === "function" + ) { + // Pass the dataSourceDisplay for CesiumVectorData models + target + .getBoundingSphere(view.dataSourceDisplay) + .then(function (assetBoundingSphere) { + // Base value offset required to zoom in close enough to 3D + // tiles for them to render. + if ( + target instanceof Cesium3DTileset && + !Cesium.defined(options.offset) + ) { + options.offset = new Cesium.HeadingPitchRange( + 0.0, + -0.5, + assetBoundingSphere.radius + ); + } + view.flyTo(assetBoundingSphere, options); + }); + return; } - }, - - /** - * Set up the Cesium scene and set listeners and behavior that enable users to - * click on vector features on the map to view more information about them. - */ - initializePicking: function () { - try { - // Save a reference to this view the Cesium scene - var view = this; - var scene = this.scene - - // To add an outline to 3D tiles in Cesium, we 'silhouette' them. Set up the the - // scene to support silhouetting. - view.silhouettes = Cesium.PostProcessStageLibrary.createEdgeDetectionStage(); - view.silhouettes.uniforms.color = view.highlightBorderColor; - view.silhouettes.uniforms.length = 0.02; - view.silhouettes.selected = []; - scene.postProcessStages.add( - Cesium.PostProcessStageLibrary.createSilhouetteStage([view.silhouettes]) - ); - // When any Feature models in the Map model's selectedFeature collection are - // changed, added, or removed, update silhouetting of 3D tiles. - function setSelectedFeaturesListeners() { - const selectedFeatures = view.model.get('selectedFeatures') - view.stopListening(selectedFeatures, 'update') - view.listenTo(selectedFeatures, 'update', function () { - // Remove highlights from previously selected 3D tiles - view.silhouettes.selected = [] - // Highlight the newly selected 3D tiles - selectedFeatures - .getFeatureObjects('Cesium3DTileFeature') - .forEach(function (featureObject) { - view.silhouettes.selected.push(featureObject) - }) - }) - } - - setSelectedFeaturesListeners() - // If the Selected Features collection is ever completely replaced for any - // reason, make sure to reset the listeners onto the new collection - view.stopListening(view.model, 'change:selectedFeatures') - view.listenTo(view.model, 'change:selectedFeatures', setSelectedFeaturesListeners) - - // When a feature is clicked update the Map model's `selectedFeatures` - // collection with the newly selected features. This will also trigger an - // event to update styling of map assets with selected features, and tells the - // parent map view to open the feature details panel. - view.inputHandler.setInputAction(function (movement) { - const pickedFeature = scene.pick(movement.position); - const action = view.model.get('clickFeatureAction'); - if (action === 'showDetails') { - view.model.selectFeatures([pickedFeature]) - } else if (action === 'zoom') { - view.flyTo(pickedFeature) - } - // TODO: Make the click actions more configurable. On every click, - // add the coordinates to the map model's clickedCoordinates. - // (keep a history of the last 10 clicks?) - }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + // Note: This doesn't work yet for Cesium3DTilesetFeatures - + // Cesium.BoundingSphereState gets stuck in "PENDING" and never + // resolves. There's no native way of getting the bounding sphere or + // location from a 3DTileFeature! + if (target instanceof Feature) { + // If the object saved in the Feature is an Entity, then this + // function will get the bounding sphere for the entity on the next + // run. + setTimeout(() => { + // TODO check if needed + view.flyTo(target.get("featureObject"), options); + }, 0); + return; } - catch (error) { - console.log( - 'There was an error initializing picking in a CesiumWidgetView' + - '. Error details: ' + error + + // If the target is a Cesium Entity, then get the bounding sphere for + // the entity and call this function again. + const entity = target instanceof Cesium.Entity ? target : target.id; + if (entity instanceof Cesium.Entity) { + let entityBoundingSphere = new Cesium.BoundingSphere(); + view.dataSourceDisplay.getBoundingSphere( + entity, + false, + entityBoundingSphere ); + setTimeout(() => { + // TODO check if needed + view.flyTo(entityBoundingSphere, options); + }, 0); + return; } - }, - /** - * Move the camera position and zoom to the specified target entity or position on - * the map, using a nice animation. This function starts the flying/zooming - * action by setting a zoomTarget and zoomOptions on the view and requesting the - * scene to render. The actual zooming is done by - * {@link CesiumWidgetView#completeFlight} after the scene has finished rendering. - * @param {MapAsset|Cesium.BoundingSphere|Object|Feature} target The target asset, - * bounding sphere, or location to change the camera focus to. If target is a - * MapAsset, then the bounding sphere from that asset will be used for the target - * destination. If target is an Object, it may contain any of the properties that - * are supported by the Cesium camera flyTo options, see - * {@link https://cesium.com/learn/cesiumjs/ref-doc/Camera.html#flyTo}. If the - * target is a Feature, then it must be a Feature of a CesiumVectorData layer - * (currently Cesium3DTileFeatures are not supported). The target can otherwise be - * a Cesium BoundingSphere, see - * {@link https://cesium.com/learn/cesiumjs/ref-doc/BoundingSphere.html} - * @param {object} options - For targets that are a bounding sphere or asset, - * options to pass to Cesium Camera.flyToBoundingSphere(). See - * {@link https://cesium.com/learn/cesiumjs/ref-doc/Camera.html#flyToBoundingSphere}. - */ - flyTo: function (target, options) { - this.zoomTarget = target; - this.zoomOptions = options; - this.requestRender(); - }, + if (target.type && target.type == "GeoPoint") { + view.flyTo(target.toJSON(), options); + return; + } - /** - * This function is called by {@link CesiumWidgetView#postRender}; it should only - * be called once the target has been fully rendered in the scene. This function - * gets the bounding sphere, if required, and moves the scene to encompass the - * full extent of the target. - * @param {MapAsset|Cesium.BoundingSphere|Object|Feature} target The target asset, - * bounding sphere, or location to change the camera focus to. If target is a - * MapAsset, then the bounding sphere from that asset will be used for the target - * destination. If target is an Object, it may contain any of the properties that - * are supported by the Cesium camera flyTo options, see - * {@link https://cesium.com/learn/cesiumjs/ref-doc/Camera.html#flyTo}. If the - * target is a Feature, then it must be a Feature of a CesiumVectorData layer - * (currently Cesium3DTileFeatures are not supported). The target can otherwise be - * a Cesium BoundingSphere, see - * {@link https://cesium.com/learn/cesiumjs/ref-doc/BoundingSphere.html} - * @param {object} options - For targets that are a bounding sphere or asset, - * options to pass to Cesium Camera.flyToBoundingSphere(). See - * {@link https://cesium.com/learn/cesiumjs/ref-doc/Camera.html#flyToBoundingSphere}. - */ - completeFlight: function (target, options) { - - try { - - const view = this; - if (typeof options !== 'object') options = {} - - // A target is required - if (!target) { - return - } + if ( + typeof target === "object" && + typeof target.longitude === "number" && + typeof target.latitude === "number" + ) { + const pointTarget = view.positionToFlightTarget(target); + view.flyTo(pointTarget, options); + return; + } - // If the target is a Bounding Sphere, use the camera's built-in function - if (target instanceof Cesium.BoundingSphere) { - view.camera.flyToBoundingSphere(target, options) - return + // If not a Map Asset or a BoundingSphere, then the target must be an + // Object. Assume target are options for the Cesium camera flyTo + // function + if (typeof target === "object") { + // Merge the options with the target object, if there are any + // options + if (options && Object.keys(options).length) { + target = Object.assign(target, options); } + // Fly to the target + view.camera.flyTo(target); + view.resetZoomTarget(); + } + } catch (e) { + console.log("Failed to navigate to a target in Cesium.", e); + } + }, + + resetZoomTarget: function () { + const view = this; + view.zoomTarget = null; + view.interactions.set("zoomTarget", null); + view.zoomOptions = null; + }, + + /** + * Navigate to the homePosition that's set on the Map. + * @param {number} duration The duration of the flight in seconds. + */ + flyHome: function (duration) { + const home = this.model.get("homePosition"); + this.flyTo(home, { duration }); + }, + + /** + * Navigate to the homePosition that's set on the Map. + * @param {Object} position The position to navigate to. Must have + * longitude, latitude, and may have a height (elevation) in meters, + * heading, pitch, and roll in degrees. + * @param {number} duration The duration of the flight in seconds. + */ + positionToFlightTarget: function (position, duration) { + try { + if (!position) { + return null; + } - // If the target is some type of map asset, then get a Bounding Sphere for - // that asset and call this function again. - if (target instanceof MapAsset && typeof target.getBoundingSphere === 'function') { - // Pass the dataSourceDisplay for CesiumVectorData models - target.getBoundingSphere(view.dataSourceDisplay) - .then(function (assetBoundingSphere) { - // Base value offset required to zoom in close enough to 3D tiles for - // them to render. - if ((target instanceof Cesium3DTileset) && !Cesium.defined(options.offset)) { - options.offset = new Cesium.HeadingPitchRange( - 0.0, -0.5, assetBoundingSphere.radius - ) - } - view.flyTo(assetBoundingSphere, options) - }) - return + if ( + position && + Cesium.defined(position.longitude) && + Cesium.defined(position.latitude) + ) { + // Set a default height (elevation) if there isn't one set + if (!Cesium.defined(position.height)) { + position.height = 1000000; } - // Note: This doesn't work yet for Cesium3DTilesetFeatures - - // Cesium.BoundingSphereState gets stuck in "PENDING" and never resolves. - // There's no native way of getting the bounding sphere or location from a - // 3DTileFeature! - if (target instanceof Feature) { - // If the object saved in the Feature is an Entity, then this - // function will get the bounding sphere for the entity on the - // next run. - setTimeout(() => { - view.flyTo(target.get('featureObject'), options) - }, 0); - return - } + const target = {}; + target.destination = Cesium.Cartesian3.fromDegrees( + position.longitude, + position.latitude, + position.height + ); - // If the target is a Cesium Entity, then get the bounding sphere for the - // entity and call this function again. - const entity = target instanceof Cesium.Entity ? target : target.id; - if (entity instanceof Cesium.Entity) { - let entityBoundingSphere = new Cesium.BoundingSphere(); - view.dataSourceDisplay.getBoundingSphere( - entity, false, entityBoundingSphere - ) - setTimeout(() => { - view.flyTo(entityBoundingSphere, options) - }, 0); - return + if ( + Cesium.defined(position.heading) && + Cesium.defined(position.pitch) && + Cesium.defined(position.roll) + ) { + target.orientation = { + heading: Cesium.Math.toRadians(position.heading), + pitch: Cesium.Math.toRadians(position.pitch), + roll: Cesium.Math.toRadians(position.roll), + }; } - - // If not a Map Asset or a BoundingSphere, then the target must be an Object. - // Assume target are options for the Cesium camera flyTo function - if (typeof target === 'object') { - view.camera.flyTo(target) + if (Cesium.defined(duration)) { + target.duration = duration; } + return target; } - catch (e) { - console.log('Failed to navigate to a target in Cesium.', e); - } - }, - - /** - * Navigate to the homePosition that's set on the Map. - * @param {number} duration The duration of the flight in seconds. - */ - flyHome: function (duration) { - try { - var position = this.model.get('homePosition') - - if (position && Cesium.defined(position.longitude) && Cesium.defined(position.latitude)) { - - // Set a default height (elevation) if there isn't one set - if (!Cesium.defined(position.height)) { - position.height = 1000000; - } - - const target = {} - target.destination = Cesium.Cartesian3.fromDegrees( - position.longitude, - position.latitude, - position.height - ) - - if ( - Cesium.defined(position.heading) && - Cesium.defined(position.pitch) && - Cesium.defined(position.roll) - ) { - target.orientation = { - heading: Cesium.Math.toRadians(position.heading), - pitch: Cesium.Math.toRadians(position.pitch), - roll: Cesium.Math.toRadians(position.roll) - } - } - if (Cesium.defined(duration)) { - target.duration = duration + } catch (e) { + console.log("Failed to convert a position to a flight target.", e); + return null; + } + }, + + /** + * Get the current positioning of the camera in the view. + * @returns {MapConfig#CameraPosition} Returns an object with the + * longitude, latitude, height, heading, pitch, and roll in the same + * format that the Map model uses for the homePosition (see + * {@link Map#defaults}) + */ + getCameraPosition: function () { + return this.getDegreesFromCartesian(this.camera.position); + }, + + /** + * Update the 'currentViewExtent' attribute in the Map model with the + * bounding box of the currently visible area of the map. + */ + updateViewExtent: function () { + try { + this.interactions.setViewExtent(this.getViewExtent()); + } catch (e) { + console.log("Failed to update the Map view extent.", e); + } + }, + + /** + * Get the north, south, east, and west-most lat/long that define a + * bounding box around the currently visible area of the map. Also gives + * the height/ altitude of the camera in meters. + * @returns {MapConfig#ViewExtent} The current view extent. + */ + getViewExtent: function () { + const view = this; + const scene = view.scene; + const camera = view.camera; + // Get the height in meters + const height = camera.positionCartographic.height; + + // This will be the bounding box of the visible area + let coords = { + north: null, + south: null, + east: null, + west: null, + height: height, + }; + + // First try getting the visible bounding box using the simple method + if (!view.scratchRectangle) { + // Store the rectangle that we use for the calculation (reduces + // pressure on garbage collector system since this function is called + // often). + view.scratchRectangle = new Cesium.Rectangle(); + } + var rect = camera.computeViewRectangle( + scene.globe.ellipsoid, + view.scratchRectangle + ); + coords.north = Cesium.Math.toDegrees(rect.north); + coords.east = Cesium.Math.toDegrees(rect.east); + coords.south = Cesium.Math.toDegrees(rect.south); + coords.west = Cesium.Math.toDegrees(rect.west); + + // Check if the resulting coordinates cover the entire globe (happens if + // some of the sky is visible). If so, limit the bounding box to a + // smaller extent + if (view.coversGlobe(coords)) { + // Find points at the top, bottom, right, and left corners of the + // globe + const edges = view.findEdges(); + + // Get the midPoint between the top and bottom points on the globe. + // Use this to decide if the northern or southern hemisphere is more + // in view. + let midPoint = view.findMidpoint(edges.top, edges.bottom); + if (midPoint) { + // Get the latitude of the mid point + const midPointLat = view.getDegreesFromCartesian(midPoint).latitude; + + // Get the latitudes of all the edge points so that we can calculate + // the southern and northern most coordinate + const edgeLatitudes = []; + Object.values(edges).forEach(function (point) { + if (point) { + edgeLatitudes.push( + view.getDegreesFromCartesian(point).latitude + ); } + }); - this.flyTo(target); + if (midPointLat > 0) { + // If the midPoint is in the northern hemisphere, limit the + // southern part of the bounding box to the southern most edge + // point latitude + coords.south = Math.min(...edgeLatitudes); + } else { + // Vice versa for the southern hemisphere + coords.north = Math.max(...edgeLatitudes); } } - catch (error) { - console.log( - 'There was an error navigating to the home position in a CesiumWidgetView' + - '. Error details: ' + error - ); - } - }, - /** - * Get the current positioning of the camera in the view. - * @returns {MapConfig#CameraPosition} Returns an object with the longitude, latitude, - * height, heading, pitch, and roll in the same format that the Map model uses - * for the homePosition (see {@link Map#defaults}) - */ - getCameraPosition: function () { - try { - return this.getDegreesFromCartesian(this.camera.position) + // If not focused directly on one of the poles, then also limit the + // east and west sides of the bounding box + const northPointLat = view.getDegreesFromCartesian( + edges.top + ).latitude; + const southPointLat = view.getDegreesFromCartesian( + edges.bottom + ).latitude; + + if (northPointLat > 25 && southPointLat < -25) { + if (edges.right) { + coords.east = view.getDegreesFromCartesian(edges.right).longitude; + } + if (edges.left) { + coords.west = view.getDegreesFromCartesian(edges.left).longitude; + } } - catch (error) { - console.log( - 'There was an error getting the current position in a CesiumWidgetView' + - '. Error details: ' + error + } + + return coords; + }, + + /** + * Check if a given bounding box covers the entire globe. + * @param {Object} coords - An object with the north, south, east, and + * west coordinates of a bounding box + * @param {Number} latAllowance - The number of degrees latitude to allow + * as a buffer. If the north and south coords range from -90 to 90, minus + * this buffer * 2, then it is considered to cover the globe. + * @param {Number} lonAllowance - The number of degrees longitude to allow + * as a buffer. + * @returns {Boolean} Returns true if the bounding box covers the entire + * globe, false otherwise. + */ + coversGlobe: function (coords, latAllowance = 0.5, lonAllowance = 1) { + const maxLat = 90 - latAllowance; + const minLat = -90 + latAllowance; + const maxLon = 180 - lonAllowance; + const minLon = -180 + lonAllowance; + + return ( + coords.west <= minLon && + coords.east >= maxLon && + coords.south <= minLat && + coords.north >= maxLat + ); + }, + + /** + * Get longitude and latitude degrees from a cartesian point. + * @param {Cesium.Cartesian3} cartesian - The point to get degrees for + * @returns Returns an object with the longitude and latitude in degrees, + * as well as the height in meters + */ + getDegreesFromCartesian: function (cartesian) { + const cartographic = Cesium.Cartographic.fromCartesian(cartesian); + const degrees = { + height: cartographic.height, + }; + const coordinates = [ + "longitude", + "latitude", + "heading", + "pitch", + "roll", + ]; + coordinates.forEach(function (coordinate) { + if (Cesium.defined(cartographic[coordinate])) { + degrees[coordinate] = Cesium.Math.toDegrees( + cartographic[coordinate] ); } - }, - - /** - * Update the 'currentViewExtent' attribute in the Map model with the - * bounding box of the currently visible area of the map. - */ - updateViewExtent: function () { - try { this.model.set('currentViewExtent', this.getViewExtent()) } - catch (e) { console.log('Failed to update the Map view extent.', e) } - }, - - /** - * Get the north, south, east, and west-most lat/long that define a - * bounding box around the currently visible area of the map. Also gives - * the height/ altitude of the camera in meters. - * @returns {MapConfig#ViewExtent} The current view extent. - */ - getViewExtent: function () { + }); + return degrees; + }, + + /** + * Find four points that exist on the globe that are closest to the + * top-center, bottom-center, right-middle, and left-middle points of the + * screen. Note that these are not necessarily the northern, southern, + * eastern, and western -most points, since the map may be oriented in any + * direction (e.g. facing the north pole). + * + * @returns {Cesium.Cartesian3[]} Returns an object with the top, bottom, + * left, and right points of the globe. + */ + findEdges: function () { + try { const view = this; - const scene = view.scene; - const camera = view.camera; - // Get the height in meters - const height = camera.positionCartographic.height - - // This will be the bounding box of the visible area - let coords = { - north: null, south: null, east: null, west: null, height: height + const canvas = view.scene.canvas; + const maxX = canvas.clientWidth; + const maxY = canvas.clientHeight; + const midX = (maxX / 2) | 0; + const midY = (maxY / 2) | 0; + + // Points at the extreme edges of the cesium canvas. These may not be + // points on the globe (i.e. they could be in the sky) + const topCanvas = new Cesium.Cartesian2(midX, 0); + const rightCanvas = new Cesium.Cartesian2(maxX, midY); + const bottomCanvas = new Cesium.Cartesian2(midX, maxY); + const leftCanvas = new Cesium.Cartesian2(0, midY); + + // Find the real world coordinate that is closest to the canvas edge + // points + const points = { + top: view.findPointOnGlobe(topCanvas, bottomCanvas), + right: view.findPointOnGlobe(rightCanvas, leftCanvas), + bottom: view.findPointOnGlobe(bottomCanvas, topCanvas), + left: view.findPointOnGlobe(leftCanvas, rightCanvas), + }; + + return points; + } catch (error) { + console.log( + "There was an error finding the edge points in a CesiumWidgetView" + + ". Error details: " + + error + ); + } + }, + + /** + * Given two Cartesian3 points, compute the midpoint. + * @param {Cesium.Cartesian3} p1 The first point + * @param {Cesium.Cartesian3} p2 The second point + * @returns {Cesium.Cartesian3 | null} The midpoint or null if p1 or p2 is + * not defined. + */ + findMidpoint: function (p1, p2) { + try { + if (!p1 || !p2) { + return null; } + // Compute vector from p1 to p2 + let p1p2 = new Cesium.Cartesian3(0.0, 0.0, 0.0); + Cesium.Cartesian3.subtract(p2, p1, p1p2); + + // Compute vector to midpoint + let halfp1p2 = new Cesium.Cartesian3(0.0, 0.0, 0.0); + Cesium.Cartesian3.multiplyByScalar(p1p2, 0.5, halfp1p2); + + // Compute point half way between p1 and p2 + let p3 = new Cesium.Cartesian3(0.0, 0.0, 0.0); + p3 = Cesium.Cartesian3.add(p1, halfp1p2, p3); + + // Force point onto surface of ellipsoid + const midPt = Cesium.Cartographic.fromCartesian(p3); + const p3a = Cesium.Cartesian3.fromRadians( + midPt.longitude, + midPt.latitude, + 0.0 + ); - // First try getting the visible bounding box using the simple method - if (!view.scratchRectangle) { - // Store the rectangle that we use for the calculation (reduces pressure on - // garbage collector system since this function is called often). - view.scratchRectangle = new Cesium.Rectangle(); - } - var rect = camera.computeViewRectangle( - scene.globe.ellipsoid, view.scratchRectangle + return p3a; + } catch (error) { + console.log( + "There was an error finding a midpoint in a CesiumWidgetView" + + ". Error details: " + + error ); - coords.north = Cesium.Math.toDegrees(rect.north) - coords.east = Cesium.Math.toDegrees(rect.east) - coords.south = Cesium.Math.toDegrees(rect.south) - coords.west = Cesium.Math.toDegrees(rect.west) - - // Check if the resulting coordinates cover the entire globe (happens - // if some of the sky is visible). If so, limit the bounding box to a - // smaller extent - if (view.coversGlobe(coords)) { - - // Find points at the top, bottom, right, and left corners of the globe - const edges = view.findEdges() - - // Get the midPoint between the top and bottom points on the globe. Use this - // to decide if the northern or southern hemisphere is more in view. - let midPoint = view.findMidpoint(edges.top, edges.bottom) - if (midPoint) { - - // Get the latitude of the mid point - const midPointLat = view.getDegreesFromCartesian(midPoint).latitude - - // Get the latitudes of all the edge points so that we can calculate the - // southern and northern most coordinate - const edgeLatitudes = [] - Object.values(edges).forEach(function (point) { - if (point) { - edgeLatitudes.push( - view.getDegreesFromCartesian(point).latitude - ) - } - }) - - if (midPointLat > 0) { - // If the midPoint is in the northern hemisphere, limit the southern part - // of the bounding box to the southern most edge point latitude - coords.south = Math.min(...edgeLatitudes) - } else { - // Vice versa for the southern hemisphere - coords.north = Math.max(...edgeLatitudes) - } - } + } + }, + + /** + * Find a coordinate that exists on the surface of the globe between two + * Cartesian points. The points do not need to be withing the bounds of + * the globe/map (i.e. they can be points in the sky). Uses the Bresenham + * Algorithm to traverse pixels from the first coordinate to the second, + * until it finds a valid coordinate. + * @param {Cesium.Cartesian2} startCoordinates The coordinates to start + * searching, in pixels + * @param {Cesium.Cartesian2} endCoordinates The coordinates to stop + * searching, in pixels + * @returns {Cesium.Cartesian3 | null} Returns the x, y, z coordinates of + * the first real point, or null if a valid point was not found. + * + * @see {@link https://groups.google.com/g/cesium-dev/c/e2H7EefikAk} + */ + findPointOnGlobe: function (startCoordinates, endCoordinates) { + const view = this; + const camera = view.camera; + const ellipsoid = view.scene.globe.ellipsoid; + + if (!startCoordinates || !endCoordinates) { + return null; + } - // If not focused directly on one of the poles, then also limit the east and - // west sides of the bounding box - const northPointLat = view.getDegreesFromCartesian(edges.top).latitude - const southPointLat = view.getDegreesFromCartesian(edges.bottom).latitude + let coordinate = camera.pickEllipsoid(startCoordinates, ellipsoid); + + // Translate coordinates + let x1 = startCoordinates.x; + let y1 = startCoordinates.y; + const x2 = endCoordinates.x; + const y2 = endCoordinates.y; + // Define differences and error check + const dx = Math.abs(x2 - x1); + const dy = Math.abs(y2 - y1); + const sx = x1 < x2 ? 1 : -1; + const sy = y1 < y2 ? 1 : -1; + let err = dx - dy; + + coordinate = camera.pickEllipsoid({ x: x1, y: y1 }, ellipsoid); + if (coordinate) { + return coordinate; + } - if (northPointLat > 25 && southPointLat < -25) { - if (edges.right) { - coords.east = view.getDegreesFromCartesian(edges.right).longitude - } - if (edges.left) { - coords.west = view.getDegreesFromCartesian(edges.left).longitude - } - } + // Main loop + while (!(x1 == x2 && y1 == y2)) { + const e2 = err << 1; + if (e2 > -dy) { + err -= dy; + x1 += sx; } - - return coords - }, - - /** - * Check if a given bounding box covers the entire globe. - * @param {Object} coords - An object with the north, south, east, and - * west coordinates of a bounding box - * @param {Number} latAllowance - The number of degrees latitude to - * allow as a buffer. If the north and south coords range from -90 to - * 90, minus this buffer * 2, then it is considered to cover the globe. - * @param {Number} lonAllowance - The number of degrees longitude to - * allow as a buffer. - * @returns {Boolean} Returns true if the bounding box covers the entire - * globe, false otherwise. - */ - coversGlobe: function (coords, latAllowance = 0.5, lonAllowance = 1) { - const maxLat = 90 - latAllowance; - const minLat = -90 + latAllowance; - const maxLon = 180 - lonAllowance; - const minLon = -180 + lonAllowance; - - return coords.west <= minLon && - coords.east >= maxLon && - coords.south <= minLat && - coords.north >= maxLat - }, - - /** - * Get longitude and latitude degrees from a cartesian point. - * @param {Cesium.Cartesian3} cartesian - The point to get degrees for - * @returns Returns an object with the longitude and latitude in degrees, as well - * as the height in meters - */ - getDegreesFromCartesian: function (cartesian) { - const cartographic = Cesium.Cartographic.fromCartesian(cartesian); - const degrees = { - height: cartographic.height + if (e2 < dx) { + err += dx; + y1 += sy; } - const coordinates = ['longitude', 'latitude', 'heading', 'pitch', 'roll'] - coordinates.forEach(function (coordinate) { - if (Cesium.defined(cartographic[coordinate])) { - degrees[coordinate] = Cesium.Math.toDegrees(cartographic[coordinate]) - } - }); - return degrees - }, - /** - * Find four points that exist on the globe that are closest to the top-center, - * bottom-center, right-middle, and left-middle points of the screen. Note that - * these are not necessarily the northern, southern, eastern, and western -most - * points, since the map may be oriented in any direction (e.g. facing the north - * pole). - * - * @returns {Cesium.Cartesian3[]} Returns an object with the top, bottom, left, - * and right points of the globe. - */ - findEdges: function () { - try { - const view = this; - const canvas = view.scene.canvas - const maxX = canvas.clientWidth; - const maxY = canvas.clientHeight; - const midX = (maxX / 2) | 0; - const midY = (maxY / 2) | 0; - - // Points at the extreme edges of the cesium canvas. These may not be points on - // the globe (i.e. they could be in the sky) - const topCanvas = new Cesium.Cartesian2(midX, 0) - const rightCanvas = new Cesium.Cartesian2(maxX, midY) - const bottomCanvas = new Cesium.Cartesian2(midX, maxY) - const leftCanvas = new Cesium.Cartesian2(0, midY) - - // Find the real world coordinate that is closest to the canvas edge points - const points = { - top: view.findPointOnGlobe(topCanvas, bottomCanvas), - right: view.findPointOnGlobe(rightCanvas, leftCanvas), - bottom: view.findPointOnGlobe(bottomCanvas, topCanvas), - left: view.findPointOnGlobe(leftCanvas, rightCanvas), - } - - return points - } - catch (error) { - console.log( - 'There was an error finding the edge points in a CesiumWidgetView' + - '. Error details: ' + error - ); + coordinate = camera.pickEllipsoid({ x: x1, y: y1 }, ellipsoid); + if (coordinate) { + return coordinate; } - }, - - /** - * Given two Cartesian3 points, compute the midpoint. - * @param {Cesium.Cartesian3} p1 The first point - * @param {Cesium.Cartesian3} p2 The second point - * @returns {Cesium.Cartesian3 | null} The midpoint or null if p1 or p2 is not - * defined. - */ - findMidpoint: function (p1, p2) { - try { - if (!p1 || !p2) { - return null - } - // Compute vector from p1 to p2 - let p1p2 = new Cesium.Cartesian3(0.0, 0.0, 0.0); - Cesium.Cartesian3.subtract(p2, p1, p1p2); - - // Compute vector to midpoint - let halfp1p2 = new Cesium.Cartesian3(0.0, 0.0, 0.0); - Cesium.Cartesian3.multiplyByScalar(p1p2, 0.5, halfp1p2); - - // Compute point half way between p1 and p2 - let p3 = new Cesium.Cartesian3(0.0, 0.0, 0.0); - p3 = Cesium.Cartesian3.add(p1, halfp1p2, p3); + } - // Force point onto surface of ellipsoid - const midPt = Cesium.Cartographic.fromCartesian(p3); - const p3a = Cesium.Cartesian3.fromRadians(midPt.longitude, midPt.latitude, 0.0); + return null; + }, - return p3a - } - catch (error) { - console.log( - 'There was an error finding a midpoint in a CesiumWidgetView' + - '. Error details: ' + error - ); + /** + * Update the map model's currentScale attribute, which is used for the + * scale bar. Finds the distance between two pixels at the *bottom center* + * of the screen. + */ + updateScale: function () { + try { + const view = this; + let currentScale = { + pixels: null, + meters: null, + }; + const onePixelInMeters = view.pixelToMeters(); + if (onePixelInMeters || onePixelInMeters === 0) { + currentScale = { + pixels: 1, + meters: onePixelInMeters, + }; } - }, - - /** - * Find a coordinate that exists on the surface of the globe between two Cartesian - * points. The points do not need to be withing the bounds of the globe/map (i.e. - * they can be points in the sky). Uses the Bresenham Algorithm to traverse pixels - * from the first coordinate to the second, until it finds a valid coordinate. - * @param {Cesium.Cartesian2} startCoordinates The coordinates to start searching, - * in pixels - * @param {Cesium.Cartesian2} endCoordinates The coordinates to stop searching, in - * pixels - * @returns {Cesium.Cartesian3 | null} Returns the x, y, z coordinates of the - * first real point, or null if a valid point was not found. - * - * @see {@link https://groups.google.com/g/cesium-dev/c/e2H7EefikAk} - */ - findPointOnGlobe: function (startCoordinates, endCoordinates) { - + view.interactions.setScale(currentScale); + } catch (e) { + console.log("Error updating the scale from a CesiumWidgetView", e); + } + }, + + /** + * Finds the geodesic distance (in meters) between two points that are 1 + * pixel apart at the bottom, center of the Cesium canvas. Adapted from + * TerriaJS. See + * {@link https://github.com/TerriaJS/terriajs/blob/main/lib/ReactViews/Map/Legend/DistanceLegend.jsx} + * @returns {number|boolean} Returns the distance on the globe, in meters, + * that is equivalent to 1 pixel on the screen at the center bottom point + * of the current scene. Returns false if there was a problem getting the + * measurement. + */ + pixelToMeters: function () { + try { const view = this; - const camera = view.camera; - const ellipsoid = view.scene.globe.ellipsoid; + const scene = view.scene; + const globe = scene.globe; + const camera = scene.camera; - if (!startCoordinates || !endCoordinates) { - return null + // For measuring geodesic distances (shortest route between two points + // on the Earth's surface) + if (!view.geodesic) { + view.geodesic = new Cesium.EllipsoidGeodesic(); } - let coordinate = camera.pickEllipsoid(startCoordinates, ellipsoid); + // Find two points that are 1 pixel apart at the bottom center of the + // cesium canvas. + const width = scene.canvas.clientWidth; + const height = scene.canvas.clientHeight; - // Translate coordinates - let x1 = startCoordinates.x; - let y1 = startCoordinates.y; - const x2 = endCoordinates.x; - const y2 = endCoordinates.y; - // Define differences and error check - const dx = Math.abs(x2 - x1); - const dy = Math.abs(y2 - y1); - const sx = (x1 < x2) ? 1 : -1; - const sy = (y1 < y2) ? 1 : -1; - let err = dx - dy; - - coordinate = camera.pickEllipsoid({ x: x1, y: y1 }, ellipsoid); - if (coordinate) { - return coordinate - } + const left = camera.getPickRay( + new Cesium.Cartesian2((width / 2) | 0, height - 1) + ); + const right = camera.getPickRay( + new Cesium.Cartesian2((1 + width / 2) | 0, height - 1) + ); - // Main loop - while (!((x1 == x2) && (y1 == y2))) { - const e2 = err << 1; - if (e2 > -dy) { - err -= dy; - x1 += sx; - } - if (e2 < dx) { - err += dx; - y1 += sy; - } + const leftPosition = globe.pick(left, scene); + const rightPosition = globe.pick(right, scene); - coordinate = camera.pickEllipsoid({ x: x1, y: y1 }, ellipsoid); - if (coordinate) { - return coordinate - } + // A point must exist at both positions to get the distance + if (!Cesium.defined(leftPosition) || !Cesium.defined(rightPosition)) { + return false; } - return null; - }, - - /** - * Set a Cesium event handler for when the mouse moves. If the scale bar is - * enabled, then a updates the Map model's current position attribute whenever the - * mouse moves. If showFeatureInfo is enabled, then changes the cursor to a - * pointer when it hovers over a feature. - */ - setMouseMoveListeners: function () { - try { - - const view = this; - - // Change the cursor to a pointer when it hovers over a clickable feature - // (e.g. a 3D tile) if picking is enabled. - const updateCursor = function (mousePosition) { - var pickedFeature = view.scene.pick(mousePosition); - if (Cesium.defined(pickedFeature)) { - view.el.style.cursor = 'pointer'; - } else { - view.el.style.cursor = 'default'; - } - } + // Find the geodesic distance, in meters, between the two points that + // are 1 pixel apart + const leftCartographic = + globe.ellipsoid.cartesianToCartographic(leftPosition); + const rightCartographic = + globe.ellipsoid.cartesianToCartographic(rightPosition); - // Slow this function down a little. Picking is quite slow. - const updateCursorThrottled = _.throttle(updateCursor, 150) + view.geodesic.setEndPoints(leftCartographic, rightCartographic); - // Update the model with long and lat when the mouse moves, if the map model - // is set to show the scale bar - const setCurrentPosition = function (mousePosition) { - var pickRay = view.camera.getPickRay(mousePosition); - var cartesian = view.scene.globe.pick(pickRay, view.scene); - if (cartesian) { - view.model.set('currentPosition', view.getDegreesFromCartesian(cartesian)) - } - } + const onePixelInMeters = view.geodesic.surfaceDistance; - // Handle mouse move - this.inputHandler.setInputAction(function (movement) { - const mousePosition = movement.endPosition; - if (view.model.get('showScaleBar')) { - setCurrentPosition(mousePosition) - } - if (view.model.get('showFeatureInfo')) { - updateCursorThrottled(mousePosition) - } - }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); - - } - catch (error) { - console.log( - 'There was an error setting the mouse listeners in a CesiumWidgetView' + - '. Error details: ' + error - ); - } - }, - - /** - * Update the map model's currentScale attribute, which is used for the scale bar. - * Finds the distance between two pixels at the *bottom center* of the screen. - */ - updateCurrentScale: function () { - try { - const view = this; - let currentScale = { - pixels: null, - meters: null - } - const onePixelInMeters = view.pixelToMeters() - if (onePixelInMeters || onePixelInMeters === 0) { - currentScale = { - pixels: 1, - meters: onePixelInMeters - } - } - view.model.set('currentScale', currentScale); + return onePixelInMeters; + } catch (error) { + console.log( + "Failed to get a pixel to meters measurement in a CesiumWidgetView" + + ". Error details: " + + error + ); + return false; + } + }, + + /** + * Finds the function that is configured for the given asset model type in + * the {@link CesiumWidgetView#mapAssetRenderFunctions} array, then + * renders the asset in the map. If there is a problem rendering the asset + * (e.g. it is an unsupported type that is not configured in the + * mapAssetRenderFunctions), then sets the AssetModel's status to error. + * @param {MapAsset} mapAsset A MapAsset layer to render in the map, such + * as a Cesium3DTileset or a CesiumImagery model. + */ + addAsset: function (mapAsset) { + try { + if (!mapAsset) { + return; } - catch (error) { - console.log( - 'There was an error updating the scale from a CesiumWidgetView' + - '. Error details: ' + error + var view = this; + var type = mapAsset.get("type"); + // Find the render option from the options configured in the view, + // given the asset model type + const renderOption = + _.find(view.mapAssetRenderFunctions, function (option) { + return option.types.includes(type); + }) || {}; + // Get the function for this type + const renderFunction = view[renderOption.renderFunction]; + + // If the cesium widget does not have a way to display this error, + // update the error status in the model (this will be reflected in the + // LayerListView) + if (!renderFunction || typeof renderFunction !== "function") { + mapAsset.set( + "statusDetails", + "This type of resource is not supported in the map widget." ); + mapAsset.set("status", "error"); + return; } - }, - - /** - * Finds the geodesic distance (in meters) between two points that are 1 pixel - * apart at the bottom, center of the Cesium canvas. Adapted from TerriaJS. See - * {@link https://github.com/TerriaJS/terriajs/blob/main/lib/ReactViews/Map/Legend/DistanceLegend.jsx} - * @returns {number|boolean} Returns the distance on the globe, in meters, that is - * equivalent to 1 pixel on the screen at the center bottom point of the current - * scene. Returns false if there was a problem getting the measurement. - */ - pixelToMeters: function () { - try { - - const view = this - const scene = view.scene - const globe = scene.globe - const camera = scene.camera - - // For measuring geodesic distances (shortest route between two points on the - // Earth's surface) - if (!view.geodesic) { - view.geodesic = new Cesium.EllipsoidGeodesic(); - } - - // Find two points that are 1 pixel apart at the bottom center of the cesium - // canvas. - const width = scene.canvas.clientWidth; - const height = scene.canvas.clientHeight; - - const left = camera.getPickRay( - new Cesium.Cartesian2((width / 2) | 0, height - 1) - ); - const right = camera.getPickRay( - new Cesium.Cartesian2((1 + width / 2) | 0, height - 1) - ); - - const leftPosition = globe.pick(left, scene); - const rightPosition = globe.pick(right, scene); - // A point must exist at both positions to get the distance - if (!Cesium.defined(leftPosition) || !Cesium.defined(rightPosition)) { - return false + // The asset should be visible and the cesium model should be ready + // before starting to render the asset + const checkAndRenderAsset = function () { + let shouldRender = + mapAsset.get("visible") && mapAsset.get("status") === "ready"; + if (shouldRender) { + renderFunction.call(view, mapAsset.get("cesiumModel")); + view.stopListening(mapAsset); } + }; - // Find the geodesic distance, in meters, between the two points that are 1 - // pixel apart - const leftCartographic = globe.ellipsoid.cartesianToCartographic( - leftPosition - ); - const rightCartographic = globe.ellipsoid.cartesianToCartographic( - rightPosition - ); + checkAndRenderAsset(); - view.geodesic.setEndPoints(leftCartographic, rightCartographic); - - const onePixelInMeters = view.geodesic.surfaceDistance; - - return onePixelInMeters - - } - catch (error) { - console.log( - 'Failed to get a pixel to meters measurement in a CesiumWidgetView' + - '. Error details: ' + error - ); - return false + if (!mapAsset.get("visible")) { + view.listenToOnce(mapAsset, "change:visible", checkAndRenderAsset); } - }, - - /** - * Finds the function that is configured for the given asset model type in the - * {@link CesiumWidgetView#mapAssetRenderFunctions} array, then renders the asset - * in the map. If there is a problem rendering the asset (e.g. it is an - * unsupported type that is not configured in the mapAssetRenderFunctions), then - * sets the AssetModel's status to error. - * @param {MapAsset} mapAsset A MapAsset layer to render in the map, such as a - * Cesium3DTileset or a CesiumImagery model. - */ - addAsset: function (mapAsset) { - try { - if (!mapAsset) { - return - } - var view = this - var type = mapAsset.get('type') - // Find the render option from the options configured in the view, given the - // asset model type - const renderOption = _.find(view.mapAssetRenderFunctions, function (option) { - return option.types.includes(type) - }) || {}; - // Get the function for this type - const renderFunction = view[renderOption.renderFunction] - - // If the cesium widget does not have a way to display this error, update the - // error status in the model (this will be reflected in the LayerListView) - if (!renderFunction || typeof renderFunction !== 'function') { - mapAsset.set('statusDetails', 'This type of resource is not supported in the map widget.') - mapAsset.set('status', 'error') - return - } - - // The asset should be visible and the cesium model should be ready before - // starting to render the asset - const checkAndRenderAsset = function () { - let shouldRender = mapAsset.get('visible') && mapAsset.get('status') === 'ready' - if (shouldRender) { - renderFunction.call(view, mapAsset.get('cesiumModel')) - view.stopListening(mapAsset) - } - } - - checkAndRenderAsset() - if (!mapAsset.get('visible')) { - view.listenToOnce(mapAsset, 'change:visible', checkAndRenderAsset) - } - - if (mapAsset.get('status') !== 'ready') { - view.listenTo(mapAsset, 'change:status', checkAndRenderAsset) - } - - } - catch (error) { - console.error( - 'There was an error rendering an asset in a CesiumWidgetView' + - '. Error details: ' + error - ); - mapAsset.set('statusDetails', 'There was a problem rendering this resource in the map widget.') - mapAsset.set('status', 'error') + if (mapAsset.get("status") !== "ready") { + view.listenTo(mapAsset, "change:status", checkAndRenderAsset); } - }, - - /** - * When an asset is removed from the map model, remove it from the map. - * @param {MapAsset} mapAsset - The MapAsset model removed from the map - * @since x.x.x - */ - removeAsset: function (mapAsset, b, c) { - if (!mapAsset) return - // Get the cesium model from the asset - const cesiumModel = mapAsset.get('cesiumModel') - if (!cesiumModel) return - // Find the remove function for this type of asset - const removeFunctionName = this.mapAssetRenderFunctions.find(function (option) { - return option.types.includes(mapAsset.get('type')) - })?.removeFunction - const removeFunction = this[removeFunctionName] - // If there is a function for this type of asset, call it - if (removeFunction && typeof removeFunction === 'function') { - removeFunction.call(this, cesiumModel) - } else { - console.log('No remove function found for this type of asset', mapAsset); + } catch (e) { + console.error("Error rendering an asset", e, mapAsset); + mapAsset.set( + "statusDetails", + "There was a problem rendering this resource in the map widget." + ); + mapAsset.set("status", "error"); + } + }, + + /** + * When an asset is removed from the map model, remove it from the map. + * @param {MapAsset} mapAsset - The MapAsset model removed from the map + * @since x.x.x + */ + removeAsset: function (mapAsset) { + if (!mapAsset) return; + // Get the cesium model from the asset + const cesiumModel = mapAsset.get("cesiumModel"); + if (!cesiumModel) return; + // Find the remove function for this type of asset + const removeFunctionName = this.mapAssetRenderFunctions.find(function ( + option + ) { + return option.types.includes(mapAsset.get("type")); + })?.removeFunction; + const removeFunction = this[removeFunctionName]; + // If there is a function for this type of asset, call it + if (removeFunction && typeof removeFunction === "function") { + removeFunction.call(this, cesiumModel); + } else { + console.log( + "No remove function found for this type of asset", + mapAsset + ); + } + }, + + /** + * Renders peaks and valleys in the 3D version of the map, given a terrain + * model. If a terrain model has already been set on the map, this will + * replace it. + * @param {Cesium.TerrainProvider} cesiumModel a Cesium Terrain Provider + * model to use for the map + */ + updateTerrain: function (cesiumModel) { + // TODO: Add listener to the map model for when the terrain changes + this.scene.terrainProvider = cesiumModel; + this.requestRender(); + }, + + /** + * Renders a 3D tileset in the map. + * @param {Cesium.Cesium3DTileset} cesiumModel The Cesium 3D tileset model + * that contains the information about the 3D tiles to render in the map + */ + add3DTileset: function (cesiumModel) { + this.scene.primitives.add(cesiumModel); + }, + + /** + * Remove a 3D tileset from the map. + * @param {Cesium.Cesium3DTileset} cesiumModel The Cesium 3D tileset model + * to remove from the map + * @since x.x.x + */ + remove3DTileset: function (cesiumModel) { + this.scene.primitives.remove(cesiumModel); + }, + + /** + * Renders vector data (excluding 3D tilesets) in the Map. + * @param {Cesium.GeoJsonDataSource} cesiumModel - The Cesium data source + * model to render on the map + */ + addVectorData: function (cesiumModel) { + this.dataSourceCollection.add(cesiumModel); + }, + + /** + * Remove vector data (excluding 3D tilesets) from the Map. + * @param {Cesium.GeoJsonDataSource} cesiumModel - The Cesium data source + * model to remove from the map + * @since x.x.x + */ + removeVectorData: function (cesiumModel) { + this.dataSourceCollection.remove(cesiumModel); + }, + + /** + * Renders imagery in the Map. + * @param {Cesium.ImageryLayer} cesiumModel The Cesium imagery model to + * render + */ + addImagery: function (cesiumModel) { + this.scene.imageryLayers.add(cesiumModel); + this.sortImagery(); + }, + + /** + * Remove imagery from the Map. + * @param {Cesium.ImageryLayer} cesiumModel The Cesium imagery model to + * remove from the map + * @since x.x.x + */ + removeImagery: function (cesiumModel) { + console.log("Removing imagery from map", cesiumModel); + console.log("Imagery layers", this.scene.imageryLayers); + this.scene.imageryLayers.remove(cesiumModel); + }, + + /** + * Arranges the imagery that is rendered the Map according to the order + * that the imagery is arranged in the layers collection. + * @since 2.21.0 + */ + sortImagery: function () { + try { + const imageryInMap = this.scene.imageryLayers; + const imageryModels = this.model + .get("layers") + .getAll("CesiumImagery"); + + // If there are no imagery layers, or just one, return + if ( + !imageryInMap || + !imageryModels || + imageryInMap.length <= 1 || + imageryModels.length <= 1 + ) { + return; } - }, - - /** - * Renders peaks and valleys in the 3D version of the map, given a terrain model. - * If a terrain model has already been set on the map, this will replace it. - * @param {Cesium.TerrainProvider} cesiumModel a Cesium Terrain Provider model to - * use for the map - */ - updateTerrain: function (cesiumModel) { - // TODO: Add listener to the map model for when the terrain changes - this.scene.terrainProvider = cesiumModel - this.requestRender(); - }, - /** - * Renders a 3D tileset in the map. - * @param {Cesium.Cesium3DTileset} cesiumModel The Cesium 3D tileset model that - * contains the information about the 3D tiles to render in the map - */ - add3DTileset: function (cesiumModel) { - this.scene.primitives.add(cesiumModel) - }, - - /** - * Remove a 3D tileset from the map. - * @param {Cesium.Cesium3DTileset} cesiumModel The Cesium 3D tileset model to - * remove from the map - * @since x.x.x - */ - remove3DTileset: function (cesiumModel) { - this.scene.primitives.remove(cesiumModel) - }, - - /** - * Renders vector data (excluding 3D tilesets) in the Map. - * @param {Cesium.GeoJsonDataSource} cesiumModel - The Cesium data source - * model to render on the map - */ - addVectorData: function (cesiumModel) { - this.dataSourceCollection.add(cesiumModel) - }, - - /** - * Remove vector data (excluding 3D tilesets) from the Map. - * @param {Cesium.GeoJsonDataSource} cesiumModel - The Cesium data source - * model to remove from the map - * @since x.x.x - */ - removeVectorData: function (cesiumModel) { - this.dataSourceCollection.remove(cesiumModel) - }, - - /** - * Renders imagery in the Map. - * @param {Cesium.ImageryLayer} cesiumModel The Cesium imagery model to render - */ - addImagery: function (cesiumModel) { - this.scene.imageryLayers.add(cesiumModel) - this.sortImagery() - }, - - /** - * Remove imagery from the Map. - * @param {Cesium.ImageryLayer} cesiumModel The Cesium imagery model to remove - * from the map - * @since x.x.x - */ - removeImagery: function (cesiumModel) { - console.log('Removing imagery from map', cesiumModel); - console.log('Imagery layers', this.scene.imageryLayers); - this.scene.imageryLayers.remove(cesiumModel) - }, - - /** - * Arranges the imagery that is rendered the Map according to the order - * that the imagery is arranged in the layers collection. - * @since 2.21.0 - */ - sortImagery: function() { - try { - const imageryInMap = this.scene.imageryLayers - const imageryModels = this.model.get('layers').getAll('CesiumImagery') - - // If there are no imagery layers, or just one, return - if ( - !imageryInMap || !imageryModels || - imageryInMap.length <= 1 || imageryModels.length <= 1 - ) { - return - } - - // If there are more than one imagery layer, arrange them in the order that - // they were added to the map - for (let i = 0; i < imageryModels.length; i++) { - const cesiumModel = imageryModels[i].get('cesiumModel') - if (cesiumModel) { - if (imageryInMap.contains(cesiumModel)) { - imageryInMap.lowerToBottom(cesiumModel) - } + // If there are more than one imagery layer, arrange them in the order + // that they were added to the map + for (let i = 0; i < imageryModels.length; i++) { + const cesiumModel = imageryModels[i].get("cesiumModel"); + if (cesiumModel) { + if (imageryInMap.contains(cesiumModel)) { + imageryInMap.lowerToBottom(cesiumModel); } } } - catch (error) { + } catch (error) { + console.log( + "There was an error sorting displayed imagery in a CesiumWidgetView" + + ". Error details: " + + error + ); + } + }, + + /** + * Display a box around every rendered tile in the tiling scheme, and draw + * a label inside it indicating the X, Y, Level indices of the tile. This + * is mostly useful for debugging terrain and imagery rendering problems. + * This function should be called after the other imagery layers have been + * added to the map, e.g. at the end of the render function. + * @param {string} [color='#ffffff'] The color of the grid outline and + * labels. Must be a CSS color string, beginning with a #. + * @param {'GeographicTilingScheme'|'WebMercatorTilingScheme'} + * [tilingScheme='GeographicTilingScheme'] The tiling scheme to use. + * Defaults to GeographicTilingScheme. + */ + showImageryGrid: function ( + color = "#ffffff", + tilingScheme = "GeographicTilingScheme" + ) { + try { + const view = this; + // Check the color is valid + if (!color || typeof color !== "string" || !color.startsWith("#")) { console.log( - 'There was an error sorting displayed imagery in a CesiumWidgetView' + - '. Error details: ' + error + `${color} is an invalid color for imagery grid. ` + + `Must be a hex color starting with '#'. ` + + `Setting color to white: '#ffffff'` ); + color = "#ffffff"; } - }, - /** - * Display a box around every rendered tile in the tiling scheme, and - * draw a label inside it indicating the X, Y, Level indices of the - * tile. This is mostly useful for debugging terrain and imagery - * rendering problems. This function should be called after the other - * imagery layers have been added to the map, e.g. at the end of the - * render function. - * @param {string} [color='#ffffff'] The color of the grid outline and - * labels. Must be a CSS color string, beginning with a #. - * @param {'GeographicTilingScheme'|'WebMercatorTilingScheme'} - * [tilingScheme='GeographicTilingScheme'] The tiling scheme to use. - * Defaults to GeographicTilingScheme. - */ - showImageryGrid: function ( - color = '#ffffff', - tilingScheme = 'GeographicTilingScheme' - ) { - try { - const view = this - // Check the color is valid - if (!color || typeof color !== 'string' || !color.startsWith('#')) { - console.log(`${color} is an invalid color for imagery grid. ` + - `Must be a hex color starting with '#'. ` + - `Setting color to white: '#ffffff'`) - color = '#ffffff' - } - - // Check the tiling scheme is valid - const availableTS = ['GeographicTilingScheme', 'WebMercatorTilingScheme'] - if (availableTS.indexOf(tilingScheme) == -1) { - console.log(`${tilingScheme} is not a valid tiling scheme ` + - `for the imagery grid. Using WebMercatorTilingScheme`) - tilingScheme = 'WebMercatorTilingScheme' - } - - // Create the imagery grid - const gridOpts = { - tilingScheme: new Cesium[tilingScheme](), - color: Cesium.Color.fromCssColorString(color) - } - - const gridOutlines = new Cesium.GridImageryProvider(gridOpts) - const gridCoords = new Cesium.TileCoordinatesImageryProvider(gridOpts) - view.scene.imageryLayers.addImageryProvider(gridOutlines) - view.scene.imageryLayers.addImageryProvider(gridCoords) - } - catch (error) { + // Check the tiling scheme is valid + const availableTS = [ + "GeographicTilingScheme", + "WebMercatorTilingScheme", + ]; + if (availableTS.indexOf(tilingScheme) == -1) { console.log( - 'There was an error showing the imagery grid in a CesiumWidgetView' + - '. Error details: ' + error + `${tilingScheme} is not a valid tiling scheme ` + + `for the imagery grid. Using WebMercatorTilingScheme` ); + tilingScheme = "WebMercatorTilingScheme"; } - } - } - ); + // Create the imagery grid + const gridOpts = { + tilingScheme: new Cesium[tilingScheme](), + color: Cesium.Color.fromCssColorString(color), + }; - return CesiumWidgetView; + const gridOutlines = new Cesium.GridImageryProvider(gridOpts); + const gridCoords = new Cesium.TileCoordinatesImageryProvider( + gridOpts + ); + view.scene.imageryLayers.addImageryProvider(gridOutlines); + view.scene.imageryLayers.addImageryProvider(gridCoords); + } catch (error) { + console.log( + "There was an error showing the imagery grid in a CesiumWidgetView" + + ". Error details: " + + error + ); + } + }, + } + ); - } -); + return CesiumWidgetView; +}); diff --git a/src/js/views/maps/DrawToolView.js b/src/js/views/maps/DrawToolView.js index dfc4ec029..9c829c11c 100644 --- a/src/js/views/maps/DrawToolView.js +++ b/src/js/views/maps/DrawToolView.js @@ -192,8 +192,6 @@ define(["backbone"], function (Backbone) { coordinates.push(coords) } layer.set("cesiumOptions", { data: geoJSON }) - // TODO: In all MapAsset models, listen for changes to the cesiumOptions - // object and re-create the cesiumModel when it changes. }, /** diff --git a/src/js/views/maps/FeatureInfoView.js b/src/js/views/maps/FeatureInfoView.js index 2e64ee8f6..ada1774e6 100644 --- a/src/js/views/maps/FeatureInfoView.js +++ b/src/js/views/maps/FeatureInfoView.js @@ -460,7 +460,7 @@ define( }, /** - * Trigger an event from the parent Map Asset model that tells the Map Widget to + * Trigger an event from the parent Map model that tells the Map Widget to * zoom to the full extent of this feature in the map. Also make sure that the Map * Asset layer is visible in the map. */ @@ -469,7 +469,7 @@ define( const model = this.model; const mapAsset = model ? model.get('mapAsset') : false; if (mapAsset) { - mapAsset.trigger('flyToExtent', model) + mapAsset.zoomTo(model) } } catch (error) { diff --git a/src/js/views/maps/LayerNavigationView.js b/src/js/views/maps/LayerNavigationView.js index 73aeaf2ec..4fca1cae6 100644 --- a/src/js/views/maps/LayerNavigationView.js +++ b/src/js/views/maps/LayerNavigationView.js @@ -134,13 +134,10 @@ define( flyToExtent : function(){ try { this.model.show() - this.model.trigger('flyToExtent', this.model) + this.model.zoomTo(this.model) } - catch (error) { - console.log( - 'There was an error triggering a "flyToExtent" event in a LayerNavigationView' + - '. Error details: ' + error - ); + catch (e) { + console.log("Error flying to extent of a layer", e); } }, diff --git a/src/js/views/maps/MapView.js b/src/js/views/maps/MapView.js index d26fa5085..2ee79a5a7 100644 --- a/src/js/views/maps/MapView.js +++ b/src/js/views/maps/MapView.js @@ -234,35 +234,31 @@ define( */ renderFeatureInfo: function () { try { - this.featureInfo = new FeatureInfoView({ - el: this.subElements.featureInfoContainer, - model: this.model.get('selectedFeatures').at(0) + const view = this; + const interactions = view.model.get('interactions') + const features = view.model.getSelectedFeatures(); + + view.featureInfo = new FeatureInfoView({ + el: view.subElements.featureInfoContainer, + model: features.at(0) + }).render() + + // When the selectedFeatures collection changes, update the feature + // info view + view.stopListening(features, 'update') + view.listenTo(features, 'update', function () { + view.featureInfo.changeModel(features.at(-1)) }) - this.featureInfo.render() - - // When the selectedFeatures collection changes, update the feature info view - function setSelectFeaturesListeners() { - this.stopListening(this.model.get('selectedFeatures'), 'update') - this.listenTo(this.model.get('selectedFeatures'), 'update', function () { - this.featureInfo.changeModel(this.model.get('selectedFeatures').at(-1)) - }) - } - setSelectFeaturesListeners.call(this) - - // If the Feature model is ever completely replaced for any reason, make the - // the Feature Info view gets updated. - this.stopListening(this.model, 'change:selectedFeatures') - this.listenTo(this.model, 'change:selectedFeatures', function (mapModel, featuresCollection) { - this.featureInfo.changeModel(featuresCollection.at(-1)) - setSelectFeaturesListeners.call(this) - }) - return this.featureInfo + + // If the Feature model is ever completely replaced for any reason, + // make the the Feature Info view gets updated. + const event = 'change:selectedFeatures' + view.stopListening(interactions, event) + view.listenTo(interactions, event, view.renderFeatureInfo); + return view.featureInfo } - catch (error) { - console.log( - 'There was an error rendering a FeatureInfoView in a MapView' + - '. Error details: ' + error - ); + catch (e) { + console.log('Error rendering a FeatureInfoView in a MapView', e); } }, @@ -311,23 +307,24 @@ define( */ renderScaleBar: function () { try { + const interactions = this.model.get('interactions') + if (!interactions) { + this.listenToOnce(this.model, 'change:interactions', this.renderScaleBar); + return + } this.scaleBar = new ScaleBarView({ - el: this.subElements.scaleBarContainer - }) - this.scaleBar.render() - - this.stopListening(this.model, 'change:currentPosition') - this.listenTo(this.model, 'change:currentPosition', function (model, position) { - this.scaleBar.updateCoordinates(position.latitude, position.longitude) - }) - - this.stopListening(this.model, 'change:currentScale') - this.listenTo(this.model, 'change:currentScale', function (model, scale) { - this.scaleBar.updateScale(scale.pixels, scale.meters) + el: this.subElements.scaleBarContainer, + scaleModel: interactions.get('scale'), + pointModel: interactions.get('mousePosition') }) + this.scaleBar.render(); + // If the interaction model or relevant sub-models are ever completely + // replaced for any reason, re-render the scale bar. + this.listenToOnce(interactions, 'change:scale change:mousePosition', this.renderScaleBar); + this.listenToOnce(this.model, 'change:interactions', this.renderScaleBar); - return this.scaleBar + return this.scaleBar; } catch (error) { console.log( @@ -337,6 +334,35 @@ define( } }, + /** + * Get a list of the views that this view contains. + * @returns {Backbone.View[]} Returns an array of all of the sub-views. + * Some may be undefined if they have not been rendered yet. + * @since x.x.x + */ + getSubViews: function () { + return [ + this.mapWidget, + this.toolbar, + this.featureInfo, + this.layerDetails, + this.scaleBar + ] + }, + + /** + * Executed when the view is closed. This will close all of the sub-views. + * @since x.x.x + */ + onClose: function () { + const subViews = this.getSubViews() + subViews.forEach(subView => { + if (subView && typeof subView.onClose === 'function') { + subView.onClose() + } + }) + } + } ); diff --git a/src/js/views/maps/ScaleBarView.js b/src/js/views/maps/ScaleBarView.js index 76a0abaa6..ddb05fba1 100644 --- a/src/js/views/maps/ScaleBarView.js +++ b/src/js/views/maps/ScaleBarView.js @@ -42,6 +42,20 @@ define( */ className: 'scale-bar', + /** + * The model that holds the current scale of the map in pixels:meters + * @type {GeoScale} + * @since x.x.x + */ + scaleModel: null, + + /** + * The model that holds the current position of the mouse on the map + * @type {GeoPoint} + * @since x.x.x + */ + pointModel: null, + /** * The primary HTML template for this view * @type {Underscore.template} @@ -145,7 +159,7 @@ define( } } } catch (e) { - console.log('A ScaleBarView failed to initialize. Error message: ' + e); + console.log('A ScaleBarView failed to initialize.', e); } }, @@ -177,6 +191,10 @@ define( this.updateCoordinates() this.updateScale() + // Listen for changes to the models + this.listenToScaleModel() + this.listenToPointModel() + return this } @@ -188,6 +206,49 @@ define( } }, + /** + * Update the scale bar when the pixel:meters ratio changes + * @since x.x.x + */ + listenToScaleModel: function () { + const view = this; + this.listenTo(this.scaleModel, 'change', function () { + view.updateScale( + view.scaleModel.get('pixels'), + view.scaleModel.get('meters') + ); + }); + }, + + /** + * Stop listening to the scale model + * @since x.x.x + */ + stopListeningToScaleModel: function () { + this.stopListening(this.scaleModel, 'change'); + }, + + /** + * Update the scale bar view when the lat and long change + * @since x.x.x + */ + listenToPointModel: function () { + const view = this; + this.listenTo(this.pointModel, 'change:latitude change:longitude', function () { + view.updateCoordinates( + view.pointModel.get('latitude'), + view.pointModel.get('longitude') + ); + }); + }, + + /** + * Stop listening to the point model + */ + stopListeningToPointModel: function () { + this.stopListening(this.pointModel, 'change:latitude change:longitude'); + }, + /** * Updates the displayed coordinates on the scale bar view. Numbers are rounded so * that long and lat have 5 digits after the decimal point. @@ -340,6 +401,15 @@ define( } }, + /** + * Function to execute when this view is removed from the DOM + * @since x.x.x + */ + onClose: function () { + this.stopListeningToScaleModel() + this.stopListeningToPointModel() + } + } ); diff --git a/src/js/views/maps/ToolbarView.js b/src/js/views/maps/ToolbarView.js index e17974027..b6a6de0fa 100644 --- a/src/js/views/maps/ToolbarView.js +++ b/src/js/views/maps/ToolbarView.js @@ -172,7 +172,7 @@ define( label: 'Home', icon: 'home', action: function (view, model) { - model.trigger('flyHome') + model.flyHome(); } }, { diff --git a/src/js/views/search/CatalogSearchView.js b/src/js/views/search/CatalogSearchView.js index 08075dbd9..e16ec678e 100644 --- a/src/js/views/search/CatalogSearchView.js +++ b/src/js/views/search/CatalogSearchView.js @@ -382,7 +382,7 @@ define([ if (this.limitSearchToMapOnInteraction && !this.limitSearchToMapArea) { this.listenToOnce( - this.model.get("map"), + this.model.get("map").get("interactions"), "change:firstInteraction", function () { this.toggleMapFilter(true); From 0668c2422ade38ad11817e3e9ecfe8d39d004790 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 6 Sep 2023 14:48:42 -0400 Subject: [PATCH 06/43] Add unit tests for new map models Issue #2189 --- .../specs/unit/models/maps/GeoPoint.spec.js | 61 +++ .../specs/unit/models/maps/GeoScale.spec.js | 47 +++ .../unit/models/maps/MapInteraction.spec.js | 386 ++++++++++++++++++ test/scripts/generate-tests.py | 11 +- 4 files changed, 497 insertions(+), 8 deletions(-) create mode 100644 test/js/specs/unit/models/maps/GeoPoint.spec.js create mode 100644 test/js/specs/unit/models/maps/GeoScale.spec.js create mode 100644 test/js/specs/unit/models/maps/MapInteraction.spec.js diff --git a/test/js/specs/unit/models/maps/GeoPoint.spec.js b/test/js/specs/unit/models/maps/GeoPoint.spec.js new file mode 100644 index 000000000..96319bf73 --- /dev/null +++ b/test/js/specs/unit/models/maps/GeoPoint.spec.js @@ -0,0 +1,61 @@ +define([ + "../../../../../../../../src/js/models/maps/GeoPoint", +], function (GeoPoint) { + // Configure the Chai assertion library + var should = chai.should(); + var expect = chai.expect; + + describe("GeoPoint Test Suite", function () { + /* Set up */ + beforeEach(function () {}); + + /* Tear down */ + afterEach(function () {}); + + describe("Initialization", function () { + it("should create a GeoPoint instance", function () { + new GeoPoint().should.be.instanceof(GeoPoint); + }); + }); + + describe("Validation", function () { + it("should validate a valid GeoPoint", function () { + var point = new GeoPoint({ + latitude: 0, + longitude: 0, + height: 0 + }); + point.isValid().should.be.true; + }); + + it("should invalidate a GeoPoint with an invalid latitude", function () { + var point = new GeoPoint({ + latitude: 100, + longitude: 0, + height: 0 + }); + point.isValid().should.be.false; + }); + + it("should invalidate a GeoPoint with an invalid longitude", function () { + var point = new GeoPoint({ + latitude: 0, + longitude: 200, + height: 0 + }); + point.isValid().should.be.false; + }); + + it("should invalidate a GeoPoint with an invalid height", function () { + var point = new GeoPoint({ + latitude: 0, + longitude: 0, + height: "foo" + }); + point.isValid().should.be.false; + }); + }); + + + }); +}); \ No newline at end of file diff --git a/test/js/specs/unit/models/maps/GeoScale.spec.js b/test/js/specs/unit/models/maps/GeoScale.spec.js new file mode 100644 index 000000000..5375fda25 --- /dev/null +++ b/test/js/specs/unit/models/maps/GeoScale.spec.js @@ -0,0 +1,47 @@ +define([ + "../../../../../../../../src/js/models/maps/GeoScale", +], function (GeoScale) { + // Configure the Chai assertion library + var should = chai.should(); + var expect = chai.expect; + + describe("GeoScale Test Suite", function () { + /* Set up */ + beforeEach(function () {}); + + /* Tear down */ + afterEach(function () {}); + + describe("Initialization", function () { + it("should create a GeoScale instance", function () { + new GeoScale().should.be.instanceof(GeoScale); + }); + }); + + describe("Validation", function () { + it("should validate a valid GeoScale", function () { + var scale = new GeoScale({ + pixel: 1, + meters: 1 + }); + scale.isValid().should.be.true; + }); + + it("should invalidate a GeoScale with an invalid pixel scale", function () { + var scale = new GeoScale({ + pixel: -1, + meters: 1 + }); + scale.isValid().should.be.false; + }); + + it("should invalidate a GeoScale with an invalid meters scale", function () { + var scale = new GeoScale({ + pixel: 1, + meters: -1 + }); + scale.isValid().should.be.false; + }); + }); + }); +}); \ No newline at end of file diff --git a/test/js/specs/unit/models/maps/MapInteraction.spec.js b/test/js/specs/unit/models/maps/MapInteraction.spec.js new file mode 100644 index 000000000..58fe67ffb --- /dev/null +++ b/test/js/specs/unit/models/maps/MapInteraction.spec.js @@ -0,0 +1,386 @@ +// "use strict"; + +// define([ +// "backbone", +// "collections/maps/Features", +// "models/maps/Feature", +// "models/maps/GeoBoundingBox", +// "models/maps/GeoPoint", +// "models/maps/GeoScale", +// ], function (Backbone, Features, Feature, GeoBoundingBox, GeoPoint, GeoScale) { +// /** +// * @class MapInteraction +// * @classdesc The Map Interaction stores information about user interaction +// * with a map, including the current position of the mouse, the feature that +// * the mouse is currently hovering over, and the position on the map that the +// * user has clicked, as well as the current view extent of the map. +// * @classcategory Models/Maps +// * @name MapInteraction +// * @since x.x.x +// * @extends Backbone.Model +// */ +// var MapInteraction = Backbone.Model.extend( +// /** @lends MapInteraction.prototype */ { +// /** +// * The type of model this is. +// * @type {String} +// */ +// type: "MapInteraction", + +// /** +// * Overrides the default Backbone.Model.defaults() function to specify +// * default attributes for the Map. +// * @returns {Object} The default attributes for the Map. +// * @property {GeoPoint} mousePosition - The current position of the mouse +// * on the map. +// * @property {GeoPoint} clickedPosition - The position on the map that the +// * user last clicked. +// * @property {GeoScale} scale - The current scale of the map in +// * pixels:meters. +// * @property {GeoBoundingBox} viewExtent - The current extent of the map +// * view. +// * @property {Features} hoveredFeatures - The feature that the mouse is +// * currently hovering over. +// * @property {Features} clickedFeatures - The feature that the user last +// * clicked. +// * @property {Features} selectedFeatures - The feature that is currently +// * selected. +// * @property {Boolean} firstInteraction - Whether or not the user has +// * interacted with the map yet. This is set to true when the user has +// * clicked, hovered, panned, or zoomed the map. The only action that is +// * ignored is mouse movement over the map. +// * @property {String} previousAction - The previous action that was +// * performed on the map. This may be any of the labels in the Cesium +// * ScreenSpaceEventType enumeration: +// * {@link https://cesium.com/learn/cesiumjs/ref-doc/global.html#ScreenSpaceEventType} +// * @property {Feature|MapAsset|GeoBoundingBox} zoomTarget - The feature or +// * map asset that the map should zoom to. The map widget should listen to +// * this property and zoom to the specified feature or map asset when this +// * property is set. The property should be cleared after the map widget +// * has zoomed to the specified feature or map asset. +// * +// * TODO +// * * @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, as well as the height/altitude in +// * meters. +// * +// * * @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) +// */ +// defaults: function () { +// return { +// mousePosition: new GeoPoint(), +// clickedPosition: new GeoPoint(), +// scale: new GeoScale(), +// viewExtent: new GeoBoundingBox(), +// hoveredFeatures: new Features(), +// clickedFeatures: new Features(), +// selectedFeatures: new Features(), +// firstInteraction: false, // <- "hasInteracted"? +// previousAction: null, +// zoomTarget: null, +// }; +// }, + +// /** +// * Run when a new Map is created. +// * @param {MapConfig} attrs - An object specifying configuration options +// * for the map. If any config option is not specified, the default will be +// * used instead (see {@link MapInteraction#defaults}). +// */ +// initialize: function (attrs, options) { +// try { +// this.connectEvents(); +// } catch (e) { +// console.log("Error initializing a Map Interaction model", e); +// } +// }, + +// /** +// * Connects the MapInteraction model to events from the map widget. +// */ +// connectEvents: function () { +// this.listenForFirstInteraction(); +// this.listenTo(this, "change:previousAction", this.handleClick); +// }, + +// /** +// * Listens for the first interaction with the map (click, hover, pan, or +// * zoom) and sets the 'firstInteraction' attribute to true when it occurs. +// */ +// listenForFirstInteraction: function () { +// if (model.get("firstInteraction")) return; +// const listener = new Backbone.Model(); +// const model = this; +// listener.listenTo( +// this, +// "change:previousAction", +// function (m, eventType) { +// if (eventType != "MOUSE_MOVE") { +// model.set("firstInteraction", true); +// listener.stopListening(); +// listener.destroy(); +// } +// } +// ); +// }, + +// /** +// * Handles a mouse click on the map. If the user has clicked on a feature, +// * the feature is set as the 'clickedFeatures' attribute. If the map is +// * configured to show details when a feature is clicked, the feature is +// * also set as the 'selectedFeatures' attribute. +// * @param {MapInteraction} m - The MapInteraction model. +// * @param {String} action - The type of mouse click event that occurred. +// * All except LEFT_CLICK are ignored. +// */ +// handleClick: function (m, action) { +// if (action !== "LEFT_CLICK") return; +// // 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") { +// this.selectFeatures(hoveredFeatures); +// } +// }, + +// /** +// * Sets the position of the mouse on the map. Creates a new GeoPoint model +// * if one doesn't already exist on the mousePosition attribute. +// * @param {Object} position - An object with 'longitude' and 'latitude' +// * properties. +// * @returns {GeoPoint} The mouse position as a GeoPoint model. +// */ +// setMousePosition: function (position) { +// let mousePosition = this.get("mousePosition"); +// if (!mousePosition) { +// mousePosition = new GeoPoint(); +// this.set("mousePosition", mousePosition); +// } +// mousePosition.set(position); +// return mousePosition; +// }, + +// /** +// * Set the pixel:meter scale of the map. Creates a new GeoScale model if +// * one doesn't already exist on the scale attribute. +// * @param {Object} scale - An object with 'meters' and 'pixels' +// * properties. +// * @returns {GeoScale} The scale as a GeoScale model. +// */ +// setScale: function (scale) { +// let scaleModel = this.get("scale"); +// if (!scaleModel) { +// scaleModel = new GeoScale(); +// this.set("scale", scaleModel); +// } +// scaleModel.set(scale); +// return scaleModel; +// }, + +// /** +// * Set the extent of the map view. Creates a new GeoBoundingBox model if +// * one doesn't already exist on the viewExtent attribute. +// * @param {Object} extent - An object with 'north', 'east', 'south', and +// * 'west' properties. +// * @returns {GeoBoundingBox} The view extent as a GeoBoundingBox model. +// */ +// setViewExtent: function (extent) { +// let viewExtent = this.get("viewExtent"); +// if (!viewExtent) { +// viewExtent = new GeoBoundingBox(); +// this.set("viewExtent", viewExtent); +// } +// viewExtent.set(extent); +// return viewExtent; +// }, + +// /** +// * Set the feature that the mouse is currently hovering over. +// * @param {Cesium.Entity|Cesium.Cesium3DTileFeature|Feature[]} features - +// * An array of feature objects selected directly from the map view. +// */ +// setHoveredFeatures: function (features) { +// this.setFeatures(features, "hoveredFeatures", true); +// }, + +// /** +// * Set the feature that the user last clicked. +// * @param {Cesium.Entity|Cesium.Cesium3DTileFeature|Feature[]|Object[]} +// * features - An array of feature objects selected directly from the map +// * view. +// */ +// setClickedFeatures: function (features) { +// this.setFeatures(features, "clickedFeatures", true); +// }, + +// /** +// * Set the feature that is currently selected. +// * @param {Cesium.Entity|Cesium.Cesium3DTileFeature|Feature[|Object[]]} +// * features - An array of feature objects selected directly from the map +// * view. +// */ +// selectFeatures: function (features) { +// this.setFeatures(features, "selectedFeatures", true); +// }, + +// /** +// * Set features on either the hoveredFeatures, clickedFeatures, or +// * selectedFeatures attribute. If the replace parameter is true, then the +// * features will replace the current features on the attribute. +// * @param {Cesium.Entity|Cesium.Cesium3DTileFeature|Feature[]|Object[]} +// * features - An array of feature objects selected directly from the map +// * view. +// * @param {'hoveredFeatures'|'clickedFeatures'|'selectedFeatures'} type - +// * The type of feature to set. +// * @param {Boolean} [replace=true] - Whether or not to replace the current +// * features on the attribute with the new features. +// */ +// setFeatures: function (features, type, replace = true) { +// try { +// const model = this; + +// // 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 +// 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), +// if (!features || features.length === 0) { +// if (replace) model.get(type).set([], { remove: true }); +// return; +// } + +// // Ignore if new features are identical to the current features +// const currentFeatures = model.get(type); +// if ( +// features && +// currentFeatures && +// currentFeatures.length === features.length && +// currentFeatures.containsFeatures(features) +// ) { +// return; +// } + +// // Convert the feature objects, which may be types specific to the map +// // widget (Cesium), to a generic Feature model +// features = model.convertFeatures(features); + +// // 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 }); +// } 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)); +// }, +// } +// ); + +// return MapInteraction; +// }); + + +define([ + "../../../../../../../../src/js/models/maps/MapInteraction", +], function (MapInteraction) { + // Configure the Chai assertion library + var should = chai.should(); + var expect = chai.expect; + + describe("MapInteraction Test Suite", function () { + /* Set up */ + beforeEach(function () {}); + + /* Tear down */ + afterEach(function () {}); + + describe("Initialization", function () { + it("should create a MapInteraction instance", function () { + new MapInteraction().should.be.instanceof(MapInteraction); + }); + }); + + + describe("setting user interactions", function () { + + it("should set the mouse position", function () { + const model = new MapInteraction(); + const position = { longitude: 1, latitude: 2 }; + model.setMousePosition(position); + model.get("mousePosition").get("latitude").should.equal(2); + model.get("mousePosition").get("longitude").should.equal(1); + }); + + it("should set the scale", function () { + const model = new MapInteraction(); + const scale = { meters: 1, pixels: 2 }; + model.setScale(scale); + model.get("scale").get("meters").should.equal(1); + model.get("scale").get("pixels").should.equal(2); + }); + + it("should set the view extent", function () { + const model = new MapInteraction(); + const extent = { north: 1, east: 2, south: 3, west: 4 }; + model.setViewExtent(extent); + model.get("viewExtent").get("north").should.equal(1); + model.get("viewExtent").get("east").should.equal(2); + model.get("viewExtent").get("south").should.equal(3); + model.get("viewExtent").get("west").should.equal(4); + }); + }); + + }); +}); \ No newline at end of file diff --git a/test/scripts/generate-tests.py b/test/scripts/generate-tests.py index 3612949c5..55b5609ad 100644 --- a/test/scripts/generate-tests.py +++ b/test/scripts/generate-tests.py @@ -3,14 +3,9 @@ # Update these paths to those that you would like to create test files for. test_files = [ - "collections/maps/Geohashes.js", - "models/connectors/Filters-Map.js", - "models/connectors/Filters-Search.js", - "models/connectors/Map-Search-Filters.js", - "models/connectors/Map-Search.js", - "models/filters/SpatialFilter.js", - "models/maps/Geohash.js", - "models/maps/assets/CesiumGeohash.js", + "models/maps/GeoPoint.js", + "models/maps/GeoScale.js", + "models/maps/MapInteraction.js", ] test_template = """ From e2d5bdec934c9c2c2b2e5b31453b85992c22eeed Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 20 Sep 2023 16:21:30 -0400 Subject: [PATCH 07/43] Fix issues with selected features in Cesium Map Issues #2189, 2180 --- src/js/collections/maps/Features.js | 227 ++- src/js/collections/maps/MapAssets.js | 9 +- src/js/models/maps/Feature.js | 334 ++-- src/js/models/maps/GeoBoundingBox.js | 5 +- src/js/models/maps/Map.js | 6 +- src/js/models/maps/MapInteraction.js | 60 +- src/js/models/maps/assets/Cesium3DTileset.js | 16 +- src/js/models/maps/assets/CesiumVectorData.js | 27 +- src/js/models/maps/assets/MapAsset.js | 1664 +++++++++-------- 9 files changed, 1151 insertions(+), 1197 deletions(-) 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; +}); From 90fdc5cd9e7b1bf3283c35180679c22d08fa4da5 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 20 Sep 2023 18:01:36 -0400 Subject: [PATCH 08/43] Ensure layer is visible after load error is fixed Issues #2189, 2180 --- src/js/models/maps/assets/CesiumVectorData.js | 13 +++++---- src/js/models/maps/assets/MapAsset.js | 14 +++++----- src/js/views/maps/CesiumWidgetView.js | 28 ++++++++++--------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/js/models/maps/assets/CesiumVectorData.js b/src/js/models/maps/assets/CesiumVectorData.js index 060c0b6d4..7972cd173 100644 --- a/src/js/models/maps/assets/CesiumVectorData.js +++ b/src/js/models/maps/assets/CesiumVectorData.js @@ -197,6 +197,12 @@ define( const data = cesiumOptions.data; delete cesiumOptions.data + if(!dataSource){ + model.set('status', 'error') + model.set('statusDetails', 'Failed to create a Cesium DataSource model.') + return + } + dataSource.load(data, cesiumOptions) .then(function (loadedData) { model.set('cesiumModel', loadedData) @@ -228,10 +234,7 @@ define( } } catch (error) { - console.log( - 'Failed to create a Cesium Model for a CesiumVectorData model' + - '. Error details: ' + error - ); + console.log('Failed to create a VectorData Cesium Model.', error); } }, @@ -240,7 +243,7 @@ define( */ setListeners: function () { try { - this.constructor.__super__.setListeners.call(this); + MapAsset.prototype.setListeners.call(this) const appearEvents = 'change:visible change:opacity change:color change:outlineColor' + ' change:temporarilyHidden' diff --git a/src/js/models/maps/assets/MapAsset.js b/src/js/models/maps/assets/MapAsset.js index b47b0e42e..f50d34bc9 100644 --- a/src/js/models/maps/assets/MapAsset.js +++ b/src/js/models/maps/assets/MapAsset.js @@ -338,6 +338,7 @@ define([ * @since x.x.x */ handleError: function () { + this.set("originalVisibility", this.get("visible")); this.set("visible", false); this.stopListening(this, "change:visible"); }, @@ -352,17 +353,16 @@ define([ if (status === "error") { this.handleError(); return; + } else { + const vis = this.get("originalVisibility") + if(typeof vis === "boolean"){ + this.set("visible", vis); + } } // 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(); - } - }); + this.listenTo(this, "change:status", this.setListeners); // Listen for changes to the cesiumOptions object this.stopListening(this, "change:cesiumOptions"); diff --git a/src/js/views/maps/CesiumWidgetView.js b/src/js/views/maps/CesiumWidgetView.js index fd8ea913c..641592e87 100644 --- a/src/js/views/maps/CesiumWidgetView.js +++ b/src/js/views/maps/CesiumWidgetView.js @@ -407,16 +407,15 @@ define([ // Listen for addition or removal of layers TODO: Add similar listeners // for terrain - view.stopListening(layers, "add"); - view.listenTo(layers, "add", view.addAsset); - view.stopListening(layers, "remove"); - view.listenTo(layers, "remove", view.removeAsset); - - // Each layer fires 'appearanceChanged' whenever the color, opacity, - // etc. has been updated. Re-render the scene when this happens. - view.stopListening(layers, "appearanceChanged"); - view.listenTo(layers, "appearanceChanged", view.requestRender); - + if(layers){ + view.stopListening(layers); + view.listenTo(layers, "add", view.addAsset); + view.listenTo(layers, "remove", view.removeAsset); + + // Each layer fires 'appearanceChanged' whenever the color, opacity, + // etc. has been updated. Re-render the scene when this happens. + view.listenTo(layers, "appearanceChanged", view.requestRender); + } // Reset asset listeners if the layers collection is replaced view.stopListening(model, "change:layers"); view.listenTo(model, "change:layers", view.setAssetListeners); @@ -620,9 +619,12 @@ define([ // property. Add in reverse order for layers to appear in the correct // order on the map. const layers = view.model.get("layers"); - _.each(layers.last(layers.length).reverse(), function (mapAsset) { - view.addAsset(mapAsset); - }); + if (layers && layers.length) { + const layersReverse = layers.last(layers.length).reverse(); + layersReverse.forEach(function (layer) { + view.addAsset(layer); + }) + } // The Cesium Widget will support just one terrain option to start. // Later, we'll allow users to switch between terrains if there is more From ef72df50a5db6bec051146f5ff7897d412a3d61f Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 20 Sep 2023 18:11:06 -0400 Subject: [PATCH 09/43] Enable basic drawing on Cesium map - Still needs methods to clear the polygon and stop drawing; proper UI; testing Issue #2180 --- src/js/views/maps/DrawToolView.js | 105 ++++++++++++++++++++++-------- src/js/views/maps/ToolbarView.js | 12 +--- 2 files changed, 82 insertions(+), 35 deletions(-) diff --git a/src/js/views/maps/DrawToolView.js b/src/js/views/maps/DrawToolView.js index 9c829c11c..abda39abe 100644 --- a/src/js/views/maps/DrawToolView.js +++ b/src/js/views/maps/DrawToolView.js @@ -43,7 +43,7 @@ define(["backbone"], function (Backbone) { /** * The CesiumVectorData model that we will use to store the drawn * polygon(s) - * @type { + * @type {CesiumVectorData} */ drawLayer: undefined, @@ -52,13 +52,13 @@ define(["backbone"], function (Backbone) { * @param {Object} options */ initialize: function (options) { - this.model = this.model; + this.model = options.model; if (!this.model) { this.handleNoMapModel(); - return + return; } - this.activated = options.activated || false; this.makeDrawLayer(); + this.activated = options.activated || false; if (this.activated) { this.activate(); } @@ -69,7 +69,7 @@ define(["backbone"], function (Backbone) { * map. Saves it to the polygon property. */ makeDrawLayer: function () { - if (!this.model) return + if (!this.model) return; this.drawLayer = this.model.addAsset({ type: "GeoJsonDataSource", hideInLayerList: true, // <- TODO: Look for this property in the @@ -82,10 +82,10 @@ define(["backbone"], function (Backbone) { { type: "Feature", properties: {}, - "geometry": { - "coordinates": [], - "type": "Polygon" - } + geometry: { + coordinates: [], + type: "Polygon", + }, }, ], }, @@ -97,7 +97,7 @@ define(["backbone"], function (Backbone) { * Removes the polygon object from the map */ removeDrawLayer: function () { - if (!this.model) return + if (!this.model) return; this.model.removeAsset(this.model); }, @@ -112,6 +112,7 @@ define(["backbone"], function (Backbone) { } this.renderToolbar(); this.startListeners(); + return this; }, /** @@ -133,65 +134,117 @@ define(["backbone"], function (Backbone) { drawButton.innerHTML = "Draw"; drawButton.addEventListener("click", function () { view.activate(); + // make the button green for testing + drawButton.style.backgroundColor = "green"; }); el.appendChild(drawButton); const clearButton = document.createElement("button"); clearButton.innerHTML = "Clear"; clearButton.addEventListener("click", function () { view.removeDrawLayer(); + // make the button red for testing + drawButton.style.backgroundColor = "red"; }); el.appendChild(clearButton); - }, /** * Starts the listeners for the draw tool */ startListeners: function () { - this.stopListening(); - // TODO: Make a general method in the map widget that gives the - // coordinates of the mouse click - this.listenTo(this.model, "change:clickedCoordinates", this.handleClick); + this.stopListeners(); + + const mapModel = this.model; + this.interactions = mapModel?.get("interactions"); + this.clickedPosition = this.interactions?.get("clickedPosition"); + + this.listenToOnce(mapModel, "change:interactions", this.startListeners); + this.listenToOnce( + this.interactions, + "change:clickedPosition", + this.startListeners + ); + + if (!this.originalClickAction) { + this.originalClickAction = this.model.get("clickFeatureAction"); + } + this.model.set("clickFeatureAction", null); + + this.listenTo( + this.clickedPosition, + "change:latitude change:longitude", + this.handleClick + ); }, /** * Stops the listeners for the draw tool */ stopListeners: function () { - this.stopListening(this.model); + const targets = [this.model, this.interactions, this.clickedPosition]; + targets.forEach((target) => { + if (target) this.stopListening(target); + }, this); + if (this.originalClickAction) { + this.model.set("clickFeatureAction", this.originalClickAction); + this.originalClickAction = null; + } }, /** * Handles a click on the map. If the draw tool is active, it will add the * coordinates of the click to the polygon being drawn. - * @param {Number[]} coordinates - The most recently clicked coordinates */ - handleClick: function (coordinates) { + handleClick: function () { if (!this.activated) { return; } + const coordinates = [ + this.clickedPosition.get("longitude"), + this.clickedPosition.get("latitude"), + ]; this.addCoordinate(coordinates); }, /** * Adds a coordinate to the polygon being drawn - * @param {Array} coords - The coordinates to add + * @param {Array} coords - The coordinates to add, in the form [longitude, + * latitude] */ addCoordinate: function (coords) { + // TODO: Something like this... We may also want to add a general method // to the VectorData model that allows us to add a coordinate, but this // will be specific to the GeoJsonDataSource const layer = this.drawLayer; - const geoJSON = layer.get("cesiumOptions")?.data - const coordinates = geoJSON?.features[0]?.geometry?.coordinates - if (!coordinates) { + const geoJSON = layer.get("cesiumOptions")?.data; + const coordinates = geoJSON?.features[0]?.geometry?.coordinates?.[0]; + + if (!coordinates || !coordinates.length) { // Create new coordinates array - geoJSON.features[0].geometry.coordinates = [coords] + geoJSON.features[0].geometry.coordinates = [[]]; + // Add the coordinate to the new array + geoJSON.features[0].geometry.coordinates[0].push(coords); } else { - // Add to existing coordinates array - coordinates.push(coords) + // Check if the last coordinate is the same as the first coordinate. If + // so, we want to add the new coordinate as the second to last. Otherwise + // we want to add it to the end. + const lastCoord = coordinates[coordinates.length - 1]; + const firstCoord = coordinates[0]; + if (lastCoord[0] == firstCoord[0] && lastCoord[1] == firstCoord[1]) { + // Add the coordinate as the second to last + coordinates.splice(coordinates.length - 1, 0, coords); + } else { + // Add the coordinate to the end + coordinates.push(coords); + // Make the coordinates valid for a GeoJSON polygon by adding the first + // coordinate to the end + coordinates.push(coordinates[0]); + } } - layer.set("cesiumOptions", { data: geoJSON }) + + layer.set("cesiumOptions", { data: geoJSON }); + layer.createCesiumModel(true); }, /** diff --git a/src/js/views/maps/ToolbarView.js b/src/js/views/maps/ToolbarView.js index b6a6de0fa..e8eac5e69 100644 --- a/src/js/views/maps/ToolbarView.js +++ b/src/js/views/maps/ToolbarView.js @@ -1,4 +1,3 @@ - 'use strict'; define( @@ -460,10 +459,7 @@ define( return contentContainer } catch (error) { - console.log( - 'There was an error rendering section content in a ToolbarView' + - '. Error details: ' + error - ); + console.log('Error rendering ToolbarView section', error); } }, @@ -506,6 +502,7 @@ define( * @param {SectionElement} sectionEl The section to activate */ activateSection: function (sectionEl) { + if(!sectionEl) return; try { if (sectionEl.action && typeof sectionEl.action === 'function') { const view = this; @@ -518,10 +515,7 @@ define( } } catch (error) { - console.log( - 'There was an error showing a toolbar section in a ToolbarView' + - '. Error details: ' + error - ); + console.log('Failed to show a section in a ToolbarView', error); } }, From 0fc87fb9adbf9c90998f6f07378835f22fa4903a Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 21 Sep 2023 17:31:25 -0400 Subject: [PATCH 10/43] Add connector & collection for drawing on map - Add the GeoPoints collection, with methods for serializing to GeoJson - Add a model that listens to a GeoPoints collection and updates a CesiumVectorData model with new geometry - Use these in the DrawToolView - Enable clearing a polygon - Show on first click, line on second click, polygon on subsequent clicks Issue #2180 --- src/js/collections/maps/GeoPoints.js | 215 +++++++++++ .../models/connectors/GeoPoints-VectorData.js | 169 +++++++++ src/js/models/maps/GeoPoint.js | 45 ++- src/js/models/maps/MapInteraction.js | 58 ++- src/js/models/maps/assets/CesiumVectorData.js | 5 +- src/js/views/maps/DrawToolView.js | 348 ++++++++++-------- 6 files changed, 656 insertions(+), 184 deletions(-) create mode 100644 src/js/collections/maps/GeoPoints.js create mode 100644 src/js/models/connectors/GeoPoints-VectorData.js diff --git a/src/js/collections/maps/GeoPoints.js b/src/js/collections/maps/GeoPoints.js new file mode 100644 index 000000000..25cf9569b --- /dev/null +++ b/src/js/collections/maps/GeoPoints.js @@ -0,0 +1,215 @@ +"use strict"; + +define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) { + /** + * @class GeoPoints + * @classdesc A group of ordered geographic points. + * @class GeoPoints + * @classcategory Collections/Maps + * @extends Backbone.Collection + * @since x.x.x + * @constructor + */ + var GeoPoints = Backbone.Collection.extend( + /** @lends GeoPoints.prototype */ { + /** + * The class/model that this collection contains. + * @type {Backbone.Model} + */ + model: GeoPoint, + + /** + * Given a point in various formats, format it such that it can be used to + * add to this collection. + * @param {Array|Object|GeoPoint} point - Accepted formats are: + * - An array of the form [longitude, latitude], with an optional third + * element for height + * - An object with a "longitude" and "latitude" property, and + * optionally a "height" property + * - A GeoPoint model + * @returns {Object|GeoPoint} Returns an object with "longitude" and + * "latitude" properties, and optionally a "height" property, or a + * GeoPoint model. + */ + formatPoint: function (point) { + let attributes = {}; + if (Array.isArray(point) && point.length > 1) { + attributes.longitude = point[0]; + attributes.latitude = point[1]; + if (point[2]) { + attributes.height = point[2]; + } + } else if ( + point instanceof GeoPoint || + (point.latitude && point.longitude) + ) { + attributes = point; + } + return attributes; + }, + + /** + * Add a point to the collection. Use this rather than the Backbone add + * method to allow for different formats of points to be added. + * @param {Array|Object|GeoPoint} point - See {@link formatPoint} for + * accepted formats. + * @returns {GeoPoint} Returns the GeoPoint model that was added. + */ + addPoint: function (point) { + point = this.formatPoint(point); + return this.add(point); + }, + + /** + * Remove a specific point from the collection. Use this rather than the + * Backbone remove method to allow for different formats of points to be + * removed. + * @param {Array|Object|GeoPoint|Number} indexOrPoint - The index of the + * point to remove, or the point itself. See {@link formatPoint} for + * accepted formats. + * @returns {GeoPoint} Returns the GeoPoint model that was removed. + */ + removePoint(indexOrPoint) { + if (typeof indexOrPoint === "number") { + this.removePointByIndex(indexOrPoint); + } else if (Array.isArray(indexOrPoint)) { + this.removePointByAttr(indexOrPoint); + } + }, + + /** + * Remove a point from the collection based on its attributes. + * @param {Array|Object|GeoPoint} point - Any format supported by + * {@link formatPoint} is accepted. + * @returns {GeoPoint} Returns the GeoPoint model that was removed. + */ + removePointByAttr: function (point) { + point = this.formatPoint(point); + const model = this.findWhere(point); + return this.remove(model); + }, + + /** + * Remove a point from the collection based on its index. + * @param {Number} index - The index of the point to remove. + * @returns {GeoPoint} Returns the GeoPoint model that was removed. + */ + removePointByIndex: function (index) { + if (index < 0 || index >= this.length) { + console.warn("Index out of bounds, GeoPoint not removed."); + return; + } + const model = this.at(index); + return this.remove(model); + }, + + /** + * Convert the collection to a GeoJSON object. The output can be the + * series of points as Point features, the points connected as a + * LineString feature, or the points connected and closed as a Polygon. + * + * Note: For a "Polygon" geometry type, when there's only one point in the + * collection, the output will be a "Point". If there are only two points, + * the output will be a "LineString", unless `forceAsPolygon` is set to + * true. + * + * @param {String} geometryType - The type of geometry to create. Can be + * "Point", "LineString", or "Polygon". + * @param {Boolean} [forceAsPolygon=false] - Set to true to enforce the + * output as a polygon for the "Polygon" geometry type, regardless of the + * number of points in the collection. + * @returns {Object} Returns a GeoJSON object of type "Point", + * "LineString", or "Polygon". + */ + toGeoJson: function (geometryType, forceAsPolygon = false) { + if (!forceAsPolygon && geometryType === "Polygon" && this.length < 3) { + geometryType = this.length === 1 ? "Point" : "LineString"; + } + return { + type: "FeatureCollection", + features: this.toGeoJsonFeatures(geometryType), + }; + }, + + /** + * Convert the collection to a GeoJSON object. The output can be the + * series of points as Point features, the points connected as a + * LineString feature, or the points connected and closed as a Polygon. + * @param {"Point"|"LineString"|"Polygon"} geometryType - The type of + * geometry to create. + * @returns {Object[]} Returns an array of GeoJSON features. + */ + toGeoJsonFeatures: function (geometryType) { + switch (geometryType) { + case "Point": + return this.toGeoJsonPointFeatures(); + case "LineString": + return [this.toGeoJsonLineStringFeature()]; + case "Polygon": + return [this.toGeoJsonPolygonFeature()]; + default: + return []; + } + }, + + /** + * Convert the collection to an array of GeoJSON point features. + * @returns {Object[]} Returns an array of GeoJSON point features. + */ + toGeoJsonPointFeatures: function () { + return this.models.map((model) => { + return model.toGeoJsonFeature(); + }); + }, + + /** + * Convert the collection to a GeoJSON LineString feature. + * @returns {Object} Returns a GeoJSON LineString feature. + */ + toGeoJsonLineStringFeature: function () { + return { + type: "Feature", + geometry: { + type: "LineString", + coordinates: this.to2DArray(), + }, + properties: {}, + }; + }, + + /** + * Convert the collection to a GeoJSON Polygon feature. The polygon will + * be closed if it isn't already. + * @returns {Object} Returns a GeoJSON Polygon feature. + */ + toGeoJsonPolygonFeature: function () { + const coordinates = this.to2DArray(); + // Make sure the polygon is closed + if (coordinates[0] != coordinates[coordinates.length - 1]) { + coordinates.push(coordinates[0]); + } + return { + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [coordinates], + }, + properties: {}, + }; + }, + + /** + * Convert the collection to an array of arrays, where each sub-array + * contains the longitude and latitude of a point. + * @returns {Array[]} Returns an array of arrays. + */ + to2DArray: function () { + return this.models.map((model) => { + return model.to2DArray(); + }); + }, + } + ); + + return GeoPoints; +}); diff --git a/src/js/models/connectors/GeoPoints-VectorData.js b/src/js/models/connectors/GeoPoints-VectorData.js new file mode 100644 index 000000000..02bc732b4 --- /dev/null +++ b/src/js/models/connectors/GeoPoints-VectorData.js @@ -0,0 +1,169 @@ +/*global define */ +define([ + "backbone", + "collections/maps/GeoPoints", + "models/maps/assets/CesiumVectorData", +], function (Backbone, GeoPoints, CesiumVectorData) { + "use strict"; + + /** + * @class PointsVectorDataConnector + * @classdesc This connector keeps a CesiumVectorData model in sync with the + * points in a GeoPoints collection. This connector will listen for changes to + * the GeoPoints collection and update the cesiumModel with the features + * created from the points in the collection. + * @name PointsVectorDataConnector + * @extends Backbone.Model + * @constructor + * @classcategory Models/Connectors + * @since x.x.x + * + * TODO: Extend to allow for a collection of GeoPoints collections, where each + * GeoPoints collection can be represented as a different polygon in the + * CesiumVectorData model. + */ + return Backbone.Model.extend( + /** @lends PointsVectorDataConnector.prototype */ { + /** + * The type of Backbone.Model this is. + * @type {string} + * @default "PointsVectorDataConnector" + */ + type: "PointsVectorDataConnector", + + /** + * Extends the default Backbone.Model.defaults() function to specify + * default attributes for the PointsVectorDataConnector model. + */ + defaults: function () { + return { + points: null, + vectorLayer: null, + isConnected: false, + }; + }, + + /** + * Initialize the model. + * @param {Object} attrs - The attributes for this model. + * @param {GeoPoints | Object} [attributes.points] - The GeoPoints + * collection to use for this connector or a JSON object with options to + * create a new GeoPoints collection. If not provided, a new GeoPoints + * collection will be created. + * @param {CesiumVectorData | Object} [attributes.vectorLayer] - The + * CesiumVectorData model to use for this connector or a JSON object with + * options to create a new CesiumVectorData model. If not provided, a new + * CesiumVectorData model will be created. + */ + initialize: function (attrs) { + try { + attrs = attrs || {}; + this.setPoints(attrs.points); + this.setVectorLayer(attrs.vectorLayer); + if (attrs.isConnected) { + this.connect(); + } + } catch (e) { + console.log("Error initializing a PointsVectorDataConnector", e); + } + }, + + /** + * Set or create and set the GeoPoints collection for this connector. + * @param {GeoPoints | Object} [points] - The GeoPoints collection to use + * for this connector or a JSON object with options to create a new + * GeoPoints collection. If not provided, a new GeoPoints collection will + * be created. + * @returns {GeoPoints} The GeoPoints collection for this connector. + */ + setPoints: function (points) { + if (points instanceof GeoPoints) { + this.set("points", points); + } else { + this.set("points", new GeoPoints(points)); + } + return this.get("points"); + }, + + /** + * Set or create and set the CesiumVectorData model for this connector. + * @param {CesiumVectorData | Object} [vectorLayer] - The CesiumVectorData + * model to use for this connector or a JSON object with options to create + * a new CesiumVectorData model. If not provided, a new CesiumVectorData + * model will be created. + * @returns {CesiumVectorData} The CesiumVectorData model for this + * connector. + */ + setVectorLayer: function (vectorLayer) { + if (vectorLayer instanceof CesiumVectorData) { + this.set("vectorLayer", vectorLayer); + } else { + this.set("vectorLayer", new CesiumVectorData(vectorLayer)); + } + return this.get("vectorLayer"); + }, + + /** + * Listen for changes to the Points collection and update the + * CesiumVectorData model with the features created from the points in + * the collection. + */ + connect: function () { + try { + const connector = this; + this.disconnect(); + + const handler = (this.eventHandler = new Backbone.Model()); + const points = this.get("points") || this.setPoints(); + + // Update the vectorLayer when the points collection is updated. + handler.listenTo(points, "update reset", () => { + connector.updateVectorLayer(); + }); + + // Restart listeners the points collection or the vectorLayer is + // replaced with a new collection or model. + handler.listenToOnce(this, "change:points change:vectorLayer", () => { + if (this.get("isConnected")) { + connector.connect(); + } + }); + + this.set("isConnected", true); + } catch (e) { + console.warn( + "Error connecting a PointsVectorDataConnector, disconnecting.", + e + ); + connector.disconnect(); + } + }, + + /** + * Stop listening for changes to the Points collection. + */ + disconnect: function () { + const handler = this.eventHandler; + if (handler) { + handler.stopListening(); + handler.clear(); + handler = null; + } + this.set("isConnected", false); + }, + + /** + * Update the CesiumVectorData model with the features created from the + * points in the collection. + */ + updateVectorLayer: function () { + const points = this.get("points") || this.setPoints(); + const layer = this.get("vectorLayer") || this.setVectorLayer(); + const geoJson = points.toGeoJson("Polygon"); + const opts = layer.getCesiumOptions() || {}; + opts.data = geoJson; + layer.set("cesiumOptions", opts); + }, + } + ); +}); diff --git a/src/js/models/maps/GeoPoint.js b/src/js/models/maps/GeoPoint.js index adb826709..7626864da 100644 --- a/src/js/models/maps/GeoPoint.js +++ b/src/js/models/maps/GeoPoint.js @@ -35,19 +35,38 @@ define(["backbone"], function (Backbone) { }; }, - // /** - // * Run when a new GeoPoint is created. - // * @param {Object} attrs - An object specifying configuration options for - // * the GeoPoint. If any config option is not specified, the default will - // * be used instead (see {@link GeoPoint#defaults}). - // */ - // initialize: function (attrs, options) { - // try { - // // ... - // } catch (e) { - // console.log("Error initializing a GeoPoint model", e); - // } - // }, + /** + * Get the long and lat of the point as an array + * @returns {Array} An array in the form [longitude, latitude] + */ + to2DArray: function () { + return [this.get("longitude"), this.get("latitude")]; + }, + + /** + * Convert the point to a GeoJSON geometry object + * @returns {Object} A GeoJSON geometry object with the type (Point) and + * coordinates of the point + */ + toGeoJsonGeometry: function () { + return { + type: "Point", + coordinates: this.to2DArray() + }; + }, + + /** + * Convert the point to a GeoJSON feature object + * @returns {Object} A GeoJSON feature object with the type (Feature) and + * geometry of the point + */ + toGeoJsonFeature: function () { + return { + type: "Feature", + geometry: this.toGeoJsonGeometry(), + properties: {} + }; + }, /** * Validate the model attributes diff --git a/src/js/models/maps/MapInteraction.js b/src/js/models/maps/MapInteraction.js index efa0e6ce6..fdc15e83a 100644 --- a/src/js/models/maps/MapInteraction.js +++ b/src/js/models/maps/MapInteraction.js @@ -159,23 +159,61 @@ define([ } else if (clickAction === "zoom") { this.set("zoomTarget", hoveredFeatures[0]); } + // TODO: throttle this? + this.setClickedPositionFromMousePosition(); }, /** - * Sets the position of the mouse on the map. Creates a new GeoPoint model - * if one doesn't already exist on the mousePosition attribute. + * Sets the clicked position to the current mouse position. + */ + setClickedPositionFromMousePosition: function () { + const mousePosition = this.get("mousePosition"); + // get just the longitude and latitude + const coords = { + longitude: mousePosition.get("longitude"), + latitude: mousePosition.get("latitude") + }; + this.setClickedPosition(coords); + }, + + /** + * Set the position for either the mousePosition or clickedPosition + * attribute. Creates a new GeoPoint model if one doesn't already exist + * on the attribute. + * @param {'mousePosition'|'clickedPosition'} attributeName - The name of + * the attribute to set. * @param {Object} position - An object with 'longitude' and 'latitude' * properties. - * @returns {GeoPoint} The mouse position as a GeoPoint model. + * @returns {GeoPoint} The corresponding position as a GeoPoint model. */ - setMousePosition: function (position) { - let mousePosition = this.get("mousePosition"); - if (!mousePosition) { - mousePosition = new GeoPoint(); - this.set("mousePosition", mousePosition); + setPosition: function(attributeName, position) { + let point = this.get(attributeName); + if (!point) { + point = new GeoPoint(); + this.set(attributeName, point); } - mousePosition.set(position); - return mousePosition; + point.set(position); + return point; + }, + + /** + * Sets the position on the map that the user last clicked. + * @param {Object} position - An object with 'longitude' and 'latitude' + * properties. + * @returns {GeoPoint} The clicked position as a GeoPoint model. + */ + setClickedPosition: function(position) { + return this.setPosition("clickedPosition", position); + }, + + /** + * Sets the position of the mouse on the map. + * @param {Object} position - An object with 'longitude' and 'latitude' + * properties. + * @returns {GeoPoint} The mouse position as a GeoPoint model. + */ + setMousePosition: function(position) { + return this.setPosition("mousePosition", position); }, /** diff --git a/src/js/models/maps/assets/CesiumVectorData.js b/src/js/models/maps/assets/CesiumVectorData.js index 7972cd173..b58b2e62a 100644 --- a/src/js/models/maps/assets/CesiumVectorData.js +++ b/src/js/models/maps/assets/CesiumVectorData.js @@ -190,14 +190,14 @@ define( if (dataSourceFunction && typeof dataSourceFunction === 'function') { - if (!recreate) { + if (!recreate || !dataSource) { dataSource = new dataSourceFunction(label) } const data = cesiumOptions.data; delete cesiumOptions.data - if(!dataSource){ + if (!dataSource) { model.set('status', 'error') model.set('statusDetails', 'Failed to create a Cesium DataSource model.') return @@ -212,6 +212,7 @@ define( model.updateFeatureVisibility() model.updateAppearance() model.set('status', 'ready') + }) .otherwise(function (error) { // See https://cesium.com/learn/cesiumjs/ref-doc/RequestErrorEvent.html diff --git a/src/js/views/maps/DrawToolView.js b/src/js/views/maps/DrawToolView.js index abda39abe..3700780b4 100644 --- a/src/js/views/maps/DrawToolView.js +++ b/src/js/views/maps/DrawToolView.js @@ -1,10 +1,14 @@ "use strict"; -define(["backbone"], function (Backbone) { +define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( + Backbone, + GeoPointsVectorData +) { /** * @class DrawTool - * @classdesc Functionality for drawing an arbitrary polygon on a Cesium map - * using the mouse. + * @classdesc The DrawTool view allows a user to draw an arbitrary polygon on + * the map. The polygon is stored in a GeoPoints collection and displayed on + * the map using a connected CesiumVectorData model. * @classcategory Views/Maps * @name DrawTool * @extends Backbone.View @@ -27,78 +31,128 @@ define(["backbone"], function (Backbone) { className: "draw-tool", /** - * Whether or not the draw tool is currently active. If not active, it - * will not listen for mouse clicks. - * @type {boolean} + * The current mode of the draw tool. This could be "draw", "edit", + * "delete", or false to indicate that the draw tool is not active. + * Currently only "draw" and false are supported. */ - activated: false, + mode: false, /** * The Cesium map model to draw on. This must be the same model that the * mapWidget is using. * @type {Map} */ - model: undefined, + mapModel: undefined, /** - * The CesiumVectorData model that we will use to store the drawn - * polygon(s) + * A reference to the MapInteraction model on the MapModel that is used to + * listen for clicks on the map. + * @type {MapInteraction} + */ + interactions: undefined, + + /** + * The CesiumVectorData model that will display the polygon that is being + * drawn. * @type {CesiumVectorData} */ - drawLayer: undefined, + layer: undefined, + + /** + * The GeoPoints collection that stores the points of the polygon that is + * being drawn. + * @type {GeoPoints} + */ + points: undefined, /** * Initializes the DrawTool - * @param {Object} options + * @param {Object} options - A literal object with options to pass to the + * view + * @param {Map} options.model - The Cesium map model to draw on. This must + * be the same model that the mapWidget is using. + * @param {string} [options.mode=false] - The initial mode of the draw + * tool. */ initialize: function (options) { - this.model = options.model; - if (!this.model) { + this.mapModel = options.model; + if (!this.mapModel) { this.handleNoMapModel(); return; } - this.makeDrawLayer(); - this.activated = options.activated || false; - if (this.activated) { - this.activate(); - } + // Add models & collections and add interactions, layer, connector, + // points, and originalAction properties to this view + this.setUpMapModel(); + this.setUpLayer(); + this.setUpConnector(); }, /** - * Creates the polygon object that will be modified as a user draws on the - * map. Saves it to the polygon property. + * Sets up the map model and adds the interactions and originalAction + * properties to this view. */ - makeDrawLayer: function () { - if (!this.model) return; - this.drawLayer = this.model.addAsset({ + setUpMapModel: function () { + this.originalAction = this.mapModel.get("clickFeatureAction"); + this.interactions = + this.mapModel.get("interactions") || + this.mapModel.setUpInteractions(); + }, + + /** + * Sets up the layer to show the polygon on the map that is being drawn. + * Adds the layer property to this view. + * @returns {CesiumVectorData} The CesiumVectorData model that will + * display the polygon that is being drawn. + */ + setUpLayer: function () { + this.layer = this.mapModel.addAsset({ type: "GeoJsonDataSource", - hideInLayerList: true, // <- TODO: Look for this property in the - // layer list view. If it's true, don't show it. Document it in the - // map config docs. - cesiumOptions: { - data: { - type: "FeatureCollection", - features: [ - { - type: "Feature", - properties: {}, - geometry: { - coordinates: [], - type: "Polygon", - }, - }, - ], - }, - }, + hideInLayerList: true, // <- TODO: Hide in LayerList, doc in mapConfig + }); + return this.layer; + }, + + /** + * Sets up the connector to connect the GeoPoints collection to the + * CesiumVectorData model. Adds the connector and points properties to + * this view. + * @returns {GeoPointsVectorData} The connector + */ + setUpConnector: function () { + this.connector = new GeoPointsVectorData({ + vectorLayer: this.layer, }); + this.points = this.connector.get("points"); + this.connector.connect(); + return this.connector; + }, + + /** + * Adds a point to the polygon that is being drawn. + * @param {Object} point - The point to add to the polygon. This should + * have a latitude and longitude property. + * @returns {GeoPoint} The GeoPoint model that was added to the polygon. + */ + addPoint: function (point) { + return this.points.addPoint(point); + }, + + /** + * Clears the polygon that is being drawn. + */ + clearPoints: function () { + this.points.reset(null); }, /** * Removes the polygon object from the map + * TODO: Test this */ - removeDrawLayer: function () { - if (!this.model) return; - this.model.removeAsset(this.model); + removeLayer: function () { + if (!this.mapModel || !this.layer) return; + this.connector.disconnect(); + this.connector.set("vectorLayer", null); + this.mapModel.removeAsset(this.layer); }, /** @@ -106,12 +160,7 @@ define(["backbone"], function (Backbone) { * @returns {DrawTool} Returns the view */ render: function () { - if (!this.model) { - this.handleNoMapModel(); - return; - } this.renderToolbar(); - this.startListeners(); return this; }, @@ -120,157 +169,138 @@ define(["backbone"], function (Backbone) { */ handleNoMapModel: function () { console.warn("No map model provided to DrawTool"); + // TODO: Add a message to the view to let the user know that the draw + // tool is not available }, /** - * Create and insert the buttons for drawing and clearing the polygon + * Create and insert the buttons for drawing and clearing the polygon. + * TODO: Add all buttons and style them. This is just a WIP for now. */ renderToolbar: function () { - // TODO: At a minimum we need buttons to: Start drawing, Clear drawing. - // Just some place holder buttons for now: const view = this; const el = this.el; const drawButton = document.createElement("button"); drawButton.innerHTML = "Draw"; drawButton.addEventListener("click", function () { - view.activate(); - // make the button green for testing - drawButton.style.backgroundColor = "green"; + if (view.mode === "draw") { + view.setMode(false); + } else { + view.setMode("draw"); + } }); + this.drawButton = drawButton; el.appendChild(drawButton); const clearButton = document.createElement("button"); clearButton.innerHTML = "Clear"; clearButton.addEventListener("click", function () { - view.removeDrawLayer(); - // make the button red for testing - drawButton.style.backgroundColor = "red"; + view.clearPoints(); + view.setMode(false); }); el.appendChild(clearButton); }, /** - * Starts the listeners for the draw tool + * Sets the mode of the draw tool. Currently only "draw" and false are + * supported. + * @param {string|boolean} mode - The mode to set. This can be "draw" or + * false to indicate that the draw tool should not be active. */ - startListeners: function () { - this.stopListeners(); - - const mapModel = this.model; - this.interactions = mapModel?.get("interactions"); - this.clickedPosition = this.interactions?.get("clickedPosition"); - - this.listenToOnce(mapModel, "change:interactions", this.startListeners); - this.listenToOnce( - this.interactions, - "change:clickedPosition", - this.startListeners - ); - - if (!this.originalClickAction) { - this.originalClickAction = this.model.get("clickFeatureAction"); - } - this.model.set("clickFeatureAction", null); - - this.listenTo( - this.clickedPosition, - "change:latitude change:longitude", - this.handleClick - ); - }, - - /** - * Stops the listeners for the draw tool - */ - stopListeners: function () { - const targets = [this.model, this.interactions, this.clickedPosition]; - targets.forEach((target) => { - if (target) this.stopListening(target); - }, this); - if (this.originalClickAction) { - this.model.set("clickFeatureAction", this.originalClickAction); - this.originalClickAction = null; + setMode: function (mode) { + if (this.mode === mode) return; + this.mode = mode; + if (mode === "draw") { + this.setClickListeners(); + this.drawButton.style.backgroundColor = "green"; + } else if (mode === false) { + this.removeClickListeners(); + this.drawButton.style.backgroundColor = "grey"; } }, /** - * Handles a click on the map. If the draw tool is active, it will add the - * coordinates of the click to the polygon being drawn. + * Removes the click listeners from the map model and sets the + * clickFeatureAction back to its original value. */ - handleClick: function () { - if (!this.activated) { - return; + removeClickListeners: function () { + const handler = this.clickHandler; + if (handler) { + handler.stopListening(); + handler.clear(); + this.clickHandler = null; } - const coordinates = [ - this.clickedPosition.get("longitude"), - this.clickedPosition.get("latitude"), - ]; - this.addCoordinate(coordinates); + this.mapModel.set("clickFeatureAction", this.originalClickAction); + this.listeningForClicks = false; }, /** - * Adds a coordinate to the polygon being drawn - * @param {Array} coords - The coordinates to add, in the form [longitude, - * latitude] + * Set listeners to call the handleClick method when the user clicks on + * the map. */ - addCoordinate: function (coords) { - - // TODO: Something like this... We may also want to add a general method - // to the VectorData model that allows us to add a coordinate, but this - // will be specific to the GeoJsonDataSource - const layer = this.drawLayer; - const geoJSON = layer.get("cesiumOptions")?.data; - const coordinates = geoJSON?.features[0]?.geometry?.coordinates?.[0]; - - if (!coordinates || !coordinates.length) { - // Create new coordinates array - geoJSON.features[0].geometry.coordinates = [[]]; - // Add the coordinate to the new array - geoJSON.features[0].geometry.coordinates[0].push(coords); - } else { - // Check if the last coordinate is the same as the first coordinate. If - // so, we want to add the new coordinate as the second to last. Otherwise - // we want to add it to the end. - const lastCoord = coordinates[coordinates.length - 1]; - const firstCoord = coordinates[0]; - if (lastCoord[0] == firstCoord[0] && lastCoord[1] == firstCoord[1]) { - // Add the coordinate as the second to last - coordinates.splice(coordinates.length - 1, 0, coords); - } else { - // Add the coordinate to the end - coordinates.push(coords); - // Make the coordinates valid for a GeoJSON polygon by adding the first - // coordinate to the end - coordinates.push(coordinates[0]); + setClickListeners: function () { + const view = this; + const handler = (this.clickHandler = new Backbone.Model()); + const interactions = this.interactions; + const clickedPosition = interactions.get("clickedPosition"); + this.mapModel.set("clickFeatureAction", null); + handler.listenTo( + clickedPosition, + "change:latitude change:longitude", + () => { + view.handleClick(); } - } - - layer.set("cesiumOptions", { data: geoJSON }); - layer.createCesiumModel(true); - }, - - /** - * Activates the draw tool. This means that it will listen for mouse - * clicks on the map and draw a polygon based on those clicks. - */ - activate: function () { - this.activated = true; - this.startListeners(); + ); + this.listeningForClicks = true; + // When the clickedPosition GeoPoint model or the MapInteractions model + // is replaced, restart the listeners on the new model. + handler.listenToOnce( + interactions, + "change:clickedPosition", + function () { + if (view.listeningForClicks) { + view.handleClick(); + view.setClickListeners(); + } + } + ); + handler.listenToOnce(this.mapModel, "change:interactions", function () { + if (view.listeningForClicks) { + view.handleClick(); + view.setClickListeners(); + } + }); }, /** - * Deactivates the draw tool. This means that it will no longer listen for - * mouse clicks on the map. + * Handles a click on the map. If the draw tool is active, it will add the + * coordinates of the click to the polygon being drawn. + * @param {Number} [throttle=50] - The number of milliseconds to block + * clicks for after a click is handled. This prevents double clicks. */ - deactivate: function () { - this.activated = false; - this.stopListeners(); + handleClick: function (throttle = 50) { + // Prevent double clicks + if (this.blockClick) return; + this.blockClick = true; + setTimeout(() => { + this.blockClick = false; + }, throttle); + // Add the point to the polygon + if (this.mode === "draw") { + const point = this.interactions.get("clickedPosition"); + console.log("Adding point", point); + this.addPoint({ + latitude: point.get("latitude"), + longitude: point.get("longitude"), + }); + } }, /** * Clears the polygon that is being drawn */ onClose: function () { - this.removeAsset(); - this.deactivate(); + this.removeLayer(); + this.removeClickListeners(); }, } ); From 92e29eb607160fe5ac2df5e38405aaa7229e91aa Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 28 Sep 2023 17:48:04 -0400 Subject: [PATCH 11/43] Add buttons & actions to DrawToolView; use CZML - Switch from GeoJson to CZML (improves ability to draw around poles) - Set up the DrawTool for actions like deleting & moving points, running a callback with user-created points as argument Issue #2180 --- src/js/collections/maps/GeoPoints.js | 101 +++++++++ .../models/connectors/GeoPoints-VectorData.js | 6 +- src/js/models/maps/GeoPoint.js | 39 +++- src/js/models/maps/GeoUtilities.js | 58 ++++++ src/js/models/maps/Geohash.js | 21 +- src/js/views/maps/DrawToolView.js | 194 +++++++++++++++--- test/config/tests.json | 3 + 7 files changed, 370 insertions(+), 52 deletions(-) create mode 100644 src/js/models/maps/GeoUtilities.js diff --git a/src/js/collections/maps/GeoPoints.js b/src/js/collections/maps/GeoPoints.js index 25cf9569b..48ee17555 100644 --- a/src/js/collections/maps/GeoPoints.js +++ b/src/js/collections/maps/GeoPoints.js @@ -131,6 +131,95 @@ define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) { }; }, + // TODO: Move this to a CZML model, use in GeoHash/es + + /** + * Get the header object for a CZML document. + * @returns {Object} Returns a CZML header object. + */ + getCZMLHeader: function () { + return { + id: "document", + version: "1.0", + name: "GeoPoints", + }; + }, + + /** + * Convert the collection to a CZML document. + * @param {String} geometryType - The type of geometry to create. + * @param {Boolean} [forceAsPolygon=false] - Set to true to enforce the + * output as a polygon for the "Polygon" geometry type, regardless of the + * number of points in the collection. + * @returns {Object[]} Returns an array of CZML objects. + */ + toCzml: function (geometryType, forceAsPolygon = false) { + if (!forceAsPolygon && geometryType === "Polygon" && this.length < 3) { + geometryType = this.length === 1 ? "Point" : "LineString"; + } + const czml = [this.getCZMLHeader()]; + switch (geometryType) { + case "Point": + czml.concat(this.toCZMLPoints()); + break; + case "LineString": + czml.push(this.getCZMLLineString()); + break; + case "Polygon": + czml.push(this.getCZMLPolygon()); + break; + default: + break; + } + return czml; + }, + + /** + * Convert the collection to an array of CZML point objects. + * @returns {Object[]} Returns an array of CZML point objects. + */ + toCZMLPoints: function () { + return this.models.map((model) => { + return model.toCZML(); + }) + }, + + /** + * Convert the collection to a CZML polygon object. + * @returns {Object} Returns a CZML polygon object. + */ + getCZMLPolygon: function () { + const coords = this.toECEFArray(); + // make a random ID: + const id = "polygon_" + Math.floor(Math.random() * 1000000); + return { + id: id, + name: "Polygon", + polygon: { + positions: { + cartesian: coords, + }, + }, + }; + }, + + /** + * Convert the collection to a CZML line string object. + * @returns {Object} Returns a CZML line string object. + */ + getCZMLLineString: function () { + const coords = this.toECEFArray(); + return { + id: this.cid, + name: "LineString", + polyline: { + positions: { + cartesian: coords, + }, + }, + }; + }, + /** * Convert the collection to a GeoJSON object. The output can be the * series of points as Point features, the points connected as a @@ -208,6 +297,18 @@ define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) { return model.to2DArray(); }); }, + + /** + * Convert the collection to a cartesian array, where each every three + * elements represents the x, y, and z coordinates of a vertex, e.g. + * [x1, y1, z1, x2, y2, z2, ...]. + * @returns {Array} Returns an array of numbers. + */ + toECEFArray: function () { + return this.models.flatMap((model) => { + return model.toECEFArray(); + }); + }, } ); diff --git a/src/js/models/connectors/GeoPoints-VectorData.js b/src/js/models/connectors/GeoPoints-VectorData.js index 02bc732b4..95a7a9b3a 100644 --- a/src/js/models/connectors/GeoPoints-VectorData.js +++ b/src/js/models/connectors/GeoPoints-VectorData.js @@ -159,9 +159,11 @@ define([ updateVectorLayer: function () { const points = this.get("points") || this.setPoints(); const layer = this.get("vectorLayer") || this.setVectorLayer(); - const geoJson = points.toGeoJson("Polygon"); + const type = model.get("type"); + const geom = "Polygon"; + const data = type === "geojson" ? points.toGeoJson(geom) : this.toCzml(geom); const opts = layer.getCesiumOptions() || {}; - opts.data = geoJson; + opts.data = data; layer.set("cesiumOptions", opts); }, } diff --git a/src/js/models/maps/GeoPoint.js b/src/js/models/maps/GeoPoint.js index 7626864da..2ff0d7415 100644 --- a/src/js/models/maps/GeoPoint.js +++ b/src/js/models/maps/GeoPoint.js @@ -1,6 +1,6 @@ "use strict"; -define(["backbone"], function (Backbone) { +define(["backbone", "models/maps/GeoUtilities"], function (Backbone, GeoUtilities) { /** * @class GeoPoint * @classdesc The GeoPoint model stores geographical coordinates including @@ -68,6 +68,43 @@ define(["backbone"], function (Backbone) { }; }, + /** + * Convert the point to a feature in a CZML document + * @returns {Object} A CZML feature object with the type (Feature) and + * geometry of the point. + */ + toCZML: function () { + const ecefCoord = this.toECEFArray(); + return { + id: this.cid, + point: { + pixelSize: 10, + show: true, + heightReference: "CLAMP_TO_GROUND", + }, + position: { + cartesian: ecefCoord + } + }; + }, + + /** + * Convert the point to an array of ECEF coordinates + * @returns {Array} An array in the form [x, y, z] + */ + toECEFArray: function () { + return this.geodeticToECEF(this.to2DArray()); + }, + + /** + * Convert a given point to an array of ECEF coordinates + * @param {Array} coord - An array in the form [longitude, latitude] + * @returns {Array} An array in the form [x, y, z] + */ + geodeticToECEF: function (coord) { + return GeoUtilities.prototype.geodeticToECEF(coord); + }, + /** * Validate the model attributes * @param {Object} attrs - The model's attributes diff --git a/src/js/models/maps/GeoUtilities.js b/src/js/models/maps/GeoUtilities.js new file mode 100644 index 000000000..4ea00f806 --- /dev/null +++ b/src/js/models/maps/GeoUtilities.js @@ -0,0 +1,58 @@ +"use strict"; + +define(["backbone", "models/maps/GeoUtilities"], function ( + Backbone, + GeoUtilities +) { + /** + * @class GeoUtilities + * @classdesc The GeoUtilities model has methods foe handling spatial data + * that are used across multiple models/collections/views, and that don't + * belong in any one of them. + * @classcategory Models/Maps + * @name GeoUtilities + * @since x.x.x + * @extends Backbone.Model + */ + var GeoUtilities = Backbone.Model.extend( + /** @lends GeoUtilities.prototype */ { + /** + * The type of model this is. + * @type {String} + */ + type: "GeoUtilities", + + /** + * Convert geodetic coordinates to Earth-Centered, Earth-Fixed (ECEF) + * coordinates. Currently this function assumes the WGS-84 ellipsoid, + * and does not account for altitude/height (it's assumed the coordinate + * is at sea level) + * @param {Array} coord The geodetic coordinates in the form [longitude, + * latitude]. + * @returns {Array} The ECEF coordinates. + */ + geodeticToECEF: function (coord) { + const a = 6378137; // WGS-84 semi-major axis (meters) + const f = 1 / 298.257223563; // WGS-84 flattening + const e2 = 2 * f - f * f; // Square of eccentricity + + const lon = coord[0] * (Math.PI / 180); // Convert longitude to radians + const lat = coord[1] * (Math.PI / 180); // Convert latitude to radians + const alt = 10000; + const sinLon = Math.sin(lon); + const cosLon = Math.cos(lon); + const sinLat = Math.sin(lat); + const cosLat = Math.cos(lat); + + const N = a / Math.sqrt(1 - e2 * sinLat * sinLat); // Prime vertical radius of curvature + const x = (N + alt) * cosLat * cosLon; + const y = (N + alt) * cosLat * sinLon; + const z = (N * (1 - e2) + alt) * sinLat; + + return [x, y, z]; + }, + } + ); + + return GeoUtilities; +}); diff --git a/src/js/models/maps/Geohash.js b/src/js/models/maps/Geohash.js index 429e3c436..24372d065 100644 --- a/src/js/models/maps/Geohash.js +++ b/src/js/models/maps/Geohash.js @@ -287,7 +287,7 @@ define(["jquery", "underscore", "backbone", "nGeohash"], function ( /** * Get the geohash as a CZML Feature. - * @param {*} label The key for the property to display as a label. + * @param {string} label The key for the property to display as a label. * @returns {Object} A CZML Feature representing the geohash, including * a polygon of the geohash area and a label with the value of the * property specified by the label parameter. @@ -346,24 +346,7 @@ define(["jquery", "underscore", "backbone", "nGeohash"], function ( * @returns {Array} The ECEF coordinates. */ geodeticToECEF: function (coord) { - const a = 6378137; // WGS-84 semi-major axis (meters) - const f = 1 / 298.257223563; // WGS-84 flattening - const e2 = 2 * f - f * f; // Square of eccentricity - - const lon = coord[0] * (Math.PI / 180); // Convert longitude to radians - const lat = coord[1] * (Math.PI / 180); // Convert latitude to radians - const alt = 10000; - const sinLon = Math.sin(lon); - const cosLon = Math.cos(lon); - const sinLat = Math.sin(lat); - const cosLat = Math.cos(lat); - - const N = a / Math.sqrt(1 - e2 * sinLat * sinLat); // Prime vertical radius of curvature - const x = (N + alt) * cosLat * cosLon; - const y = (N + alt) * cosLat * sinLon; - const z = (N * (1 - e2) + alt) * sinLat; - - return [x, y, z]; + return GeoUtilities.geodeticToECEF(coord); }, } ); diff --git a/src/js/views/maps/DrawToolView.js b/src/js/views/maps/DrawToolView.js index 3700780b4..75fa7dc17 100644 --- a/src/js/views/maps/DrawToolView.js +++ b/src/js/views/maps/DrawToolView.js @@ -31,9 +31,51 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( className: "draw-tool", /** - * The current mode of the draw tool. This could be "draw", "edit", - * "delete", or false to indicate that the draw tool is not active. - * Currently only "draw" and false are supported. + * Class to use for the buttons + * @type {string} + */ + buttonClass: "map-view__button", + + /** + * The buttons to display in the toolbar and their corresponding actions. + * TODO: Finish documenting this when more finalized. + */ + buttons: [ + { + name: "draw", // === mode + label: "Draw Polygon", + icon: "pencil", + }, + { + name: "move", + label: "Move Point", + icon: "move", + }, + { + name: "remove", + label: "Remove Point", + icon: "eraser", + }, + { + name: "clear", + label: "Clear Polygon", + icon: "trash", + method: "clearPoints", + }, + { + name: "save", + label: "Save", + icon: "save", + method: "save", + }, + ], + + buttonEls: {}, + + /** + * The current mode of the draw tool. This can be "draw", "move", + * "remove", or "add" - any of the "name" properties of the buttons array, + * excluding buttons like "clear" and "save" that have a method property. */ mode: false, @@ -106,8 +148,19 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( */ setUpLayer: function () { this.layer = this.mapModel.addAsset({ - type: "GeoJsonDataSource", - hideInLayerList: true, // <- TODO: Hide in LayerList, doc in mapConfig + type: "CzmlDataSource", + label: "Your Polygon", + description: "The polygon that you are drawing on the map", + hideInLayerList: true, // TODO: Hide in LayerList, doc in mapConfig + outlineColor: "#FF3E41", // TODO + opacity: 0.55, // TODO + colorPalette: { + colors: [ + { + color: "#FF3E41", // TODO + }, + ], + }, }); return this.layer; }, @@ -134,14 +187,14 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( * @returns {GeoPoint} The GeoPoint model that was added to the polygon. */ addPoint: function (point) { - return this.points.addPoint(point); + return this.points?.addPoint(point); }, /** * Clears the polygon that is being drawn. */ clearPoints: function () { - this.points.reset(null); + this.points?.reset(null); }, /** @@ -180,24 +233,45 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( renderToolbar: function () { const view = this; const el = this.el; - const drawButton = document.createElement("button"); - drawButton.innerHTML = "Draw"; - drawButton.addEventListener("click", function () { - if (view.mode === "draw") { - view.setMode(false); - } else { - view.setMode("draw"); - } - }); - this.drawButton = drawButton; - el.appendChild(drawButton); - const clearButton = document.createElement("button"); - clearButton.innerHTML = "Clear"; - clearButton.addEventListener("click", function () { - view.clearPoints(); - view.setMode(false); + + // Create the buttons + view.buttons.forEach(options => { + const button = document.createElement("button"); + button.className = this.buttonClass; + button.innerHTML = ` ${options.label}`; + button.addEventListener("click", function () { + const method = options.method; + if(method) view[method](); + else view.toggleMode(options.name); + }); + if(!view.buttonEls) view.buttonEls = {}; + view.buttonEls[options.name + "Button"] = button; + el.appendChild(button); }); - el.appendChild(clearButton); + }, + + /** + * Sends the polygon coordinates to a callback function to do something + * with them. + * TODO: This is a WIP. + */ + save: function () { + this.setMode(false); + this.removeClickListeners(); + console.log(this.points.toJSON()); + // TODO: Call a callback function to save the polygon + }, + + /** + * Toggles the mode of the draw tool. + * @param {string} mode - The mode to toggle to. + */ + toggleMode: function (mode) { + if (this.mode === mode) { + this.setMode(false); + } else { + this.setMode(mode); + } }, /** @@ -209,12 +283,34 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( setMode: function (mode) { if (this.mode === mode) return; this.mode = mode; - if (mode === "draw") { - this.setClickListeners(); - this.drawButton.style.backgroundColor = "green"; - } else if (mode === false) { + if (mode) { + if (!this.listeningForClicks) this.setClickListeners(); + this.activateButton(mode); + } else { + this.resetButtonStyles(); this.removeClickListeners(); - this.drawButton.style.backgroundColor = "grey"; + } + }, + + /** + * Sets the style of the button with the given name to indicate that it is + * active. + */ + activateButton: function (buttonName) { + const buttonEl = this.buttonEls[buttonName + "Button"]; + if(!buttonEl) return; + this.resetButtonStyles(); + buttonEl.style.backgroundColor = "blue"; // TODO - create active style + }, + + /** + * Resets the styles of all of the buttons to indicate that they are not + * active. + */ + resetButtonStyles: function () { + // Iterate through the buttonEls object and reset the styles + for (const button in this.buttonEls) { + this.buttonEls[button].style.backgroundColor = "grey"; // TODO - create default style } }, @@ -287,7 +383,6 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( // Add the point to the polygon if (this.mode === "draw") { const point = this.interactions.get("clickedPosition"); - console.log("Adding point", point); this.addPoint({ latitude: point.get("latitude"), longitude: point.get("longitude"), @@ -295,6 +390,45 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( } }, + /** + * The action to perform when the mode is "draw" and the user clicks on + * the map. + */ + handleDrawClick: function () { + if (!this.mode === "draw") return + const point = this.interactions.get("clickedPosition"); + if(!point) return + this.addPoint({ + latitude: point.get("latitude"), + longitude: point.get("longitude"), + }); + }, + + /** + * The action to perform when the mode is "move" and the user clicks on + * the map. + */ + handleMoveClick: function () { + if (!this.mode === "move") return + const feature = this.interactions.get("clickedFeature"); + if (!feature) return + // TODO: Set a listener to update the point feature and coords + // when it is clicked and dragged + }, + + /** + * The action to perform when the mode is "remove" and the user clicks on + * the map. + */ + handleRemoveClick: function () { + if (!this.mode === "remove") return + const feature = this.interactions.get("clickedFeature"); + if (!feature) return + // TODO: Get the coords of the clicked feature and remove the point + // from the polygon + console.log("remove feature", feature); + }, + /** * Clears the polygon that is being drawn */ diff --git a/test/config/tests.json b/test/config/tests.json index a558e0dec..a668b5fd9 100644 --- a/test/config/tests.json +++ b/test/config/tests.json @@ -23,6 +23,9 @@ "./js/specs/unit/models/metadata/eml211/EMLDistribution.spec.js", "./js/specs/unit/models/maps/assets/CesiumImagery.spec.js", "./js/specs/unit/collections/maps/Geohashes.spec.js", + "./js/specs/unit/models/maps/GeoPoint.spec.js", + "./js/specs/unit/models/maps/GeoScale.spec.js", + "./js/specs/unit/models/maps/MapInteraction.spec.js", "./js/specs/unit/models/connectors/Filters-Map.spec.js", "./js/specs/unit/models/connectors/Filters-Search.spec.js", "./js/specs/unit/models/connectors/Map-Search-Filters.spec.js", From c89ff998c94dcb66a022e7c17ed79aa869640df2 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 5 Oct 2023 20:01:35 -0400 Subject: [PATCH 12/43] Speed up vector layer rendering, fix drawing - Fix issues with drawing polygons, including drawing polygons over poles - Allow drawn polygons to have properties set like other layers (color, opacity, etc) - When drawing, draw both points and polygons - Add CustomDataSource support - Greatly reduce the number of re-renders Cesium must do (improve map performance) - Add connectors between GeoPoints collection and polygons & points Entities Issues #2180 and #2189 --- src/js/collections/maps/GeoPoints.js | 17 +- src/js/collections/maps/MapAssets.js | 2 +- src/js/models/connectors/GeoPoints-Cesium.js | 163 ++ .../connectors/GeoPoints-CesiumPoints.js | 169 ++ .../connectors/GeoPoints-CesiumPolygon.js | 73 + .../models/connectors/GeoPoints-VectorData.js | 171 --- src/js/models/maps/GeoPoint.js | 27 +- src/js/models/maps/GeoUtilities.js | 5 +- src/js/models/maps/Geohash.js | 15 +- src/js/models/maps/MapInteraction.js | 7 +- src/js/models/maps/assets/CesiumGeohash.js | 4 +- src/js/models/maps/assets/CesiumVectorData.js | 1360 +++++++++-------- src/js/models/maps/assets/MapAsset.js | 31 + src/js/views/maps/CesiumWidgetView.js | 96 +- src/js/views/maps/DrawToolView.js | 90 +- 15 files changed, 1270 insertions(+), 960 deletions(-) create mode 100644 src/js/models/connectors/GeoPoints-Cesium.js create mode 100644 src/js/models/connectors/GeoPoints-CesiumPoints.js create mode 100644 src/js/models/connectors/GeoPoints-CesiumPolygon.js delete mode 100644 src/js/models/connectors/GeoPoints-VectorData.js diff --git a/src/js/collections/maps/GeoPoints.js b/src/js/collections/maps/GeoPoints.js index 48ee17555..d225ee0be 100644 --- a/src/js/collections/maps/GeoPoints.js +++ b/src/js/collections/maps/GeoPoints.js @@ -190,10 +190,8 @@ define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) { */ getCZMLPolygon: function () { const coords = this.toECEFArray(); - // make a random ID: - const id = "polygon_" + Math.floor(Math.random() * 1000000); return { - id: id, + id: this.cid, name: "Polygon", polygon: { positions: { @@ -309,6 +307,19 @@ define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) { return model.toECEFArray(); }); }, + + /** + * Convert the collection to an array of coordinates in the format + * native to the map widget. For Cesium, this is an array of + * Cartesian3 objects in ECEF coordinates. + * @returns {Array} An array of coordinates that can be used by the map + * widget. + */ + asMapWidgetCoords: function () { + return this.models.map((model) => { + return model.get("mapWidgetCoords"); + }); + }, } ); diff --git a/src/js/collections/maps/MapAssets.js b/src/js/collections/maps/MapAssets.js index 1dc3a6d7b..bfea0be14 100644 --- a/src/js/collections/maps/MapAssets.js +++ b/src/js/collections/maps/MapAssets.js @@ -55,7 +55,7 @@ define([ model: Cesium3DTileset, }, { - types: ["GeoJsonDataSource", "CzmlDataSource"], + types: ["GeoJsonDataSource", "CzmlDataSource", "CustomDataSource"], model: CesiumVectorData, }, { diff --git a/src/js/models/connectors/GeoPoints-Cesium.js b/src/js/models/connectors/GeoPoints-Cesium.js new file mode 100644 index 000000000..3fc55b160 --- /dev/null +++ b/src/js/models/connectors/GeoPoints-Cesium.js @@ -0,0 +1,163 @@ +"use strict"; + +/*global define */ +define([ + "backbone", + "cesium", + "collections/maps/GeoPoints", + "models/maps/assets/CesiumVectorData", +], function (Backbone, Cesium, GeoPoints, CesiumVectorData) { + /** + * @class GeoPointsCesiumConnector + * @classdesc This is the base model for other connectors that create geometry + * in Cesium based on points in a GeoPoints collection. + * @name GeoPointsCesiumConnector + * @extends Backbone.Model + * @constructor + * @classcategory Models/Connectors + * @since x.x.x + */ + return Backbone.Model.extend( + /** @lends GeoPointsCesiumConnector.prototype */ { + /** + * The type of Backbone.Model this is. + * @type {string} + * @default "GeoPointsCesiumConnector" + */ + type: "GeoPointsCesiumConnector", + + /** + * Extends the default Backbone.Model.defaults() function to specify + * default attributes for the GeoPointsCesiumConnector model. + * @returns {Object} The default attributes + * @property {GeoPoints} geoPoints - The points collection to visualize + * @property {CesiumVectorData} layer - The CesiumVectorData model to use + * to visualize the points. This must be a CesiumVectorData model. + * @property {Boolean} isConnected - Whether the layer is currently being + * updated with changes to the points collection. + */ + defaults: function () { + return { + geoPoints: null, + layer: null, + isConnected: false, + }; + }, + + /** + * Initialize the model. + * @param {Object} attrs - The attributes for this model. + * @param {GeoPoints | Array} [attributes.geoPoints] - The GeoPoints + * collection to use for this connector or an array of JSON attributes to + * create a new GeoPoints collection. If not provided, a new empty + * GeoPoints collection will be created. + * @param {CesiumVectorData | Object} [attributes.layer] - The + * CesiumVectorData CesiumVectorData model to use for this connector or a + * JSON object with options to create a model. If not provided, a new + * layer will be created. + */ + initialize: function (attrs) { + try { + attrs = attrs || {}; + this.setGeoPoints(attrs.geoPoints); + this.setLayer(attrs.layer); + if (attrs.isConnected) { + this.connect(); + } + } catch (e) { + console.log("Error initializing a GeoPointsCesiumConnector", e); + } + }, + + /** + * Set or create and set the GeoPoints collection for this connector. + * @param {GeoPoints | Object} [points] - The GeoPoints collection to use + * for this connector or an array of JSON attributes to create points. + * @returns {GeoPoints} The GeoPoints collection for this connector. + */ + setGeoPoints: function (geoPoints) { + if (geoPoints instanceof GeoPoints) { + this.set("geoPoints", geoPoints); + } else { + this.set("geoPoints", new GeoPoints(geoPoints)); + } + return this.get("geoPoints"); + }, + + /** + * Set or create and set the CesiumVectorData model for this connector. + * @param {CesiumVectorData | Object} [layer] - The CesiumVectorData model + * to use for this connector or a JSON object with options to create a new + * CesiumVectorData model. If not provided, a new CesiumVectorData model + * will be created. + * @returns {CesiumVectorData} The CesiumVectorData model for this + * connector. + */ + setLayer: function (layer) { + if (layer instanceof CesiumVectorData) { + this.set("layer", layer); + } else { + this.set("layer", new CesiumVectorData(layer)); + } + return this.get("layer"); + }, + + /** + * Listen for changes to the Points collection and update the + * CesiumVectorData model with point entities. + */ + connect: function () { + try { + // Listen for changes to the points collection and update the layer + let geoPoints = this.get("geoPoints"); + const events = ["update", "reset"]; + events.forEach((eventName) => { + this.listenTo(geoPoints, eventName, function (...args) { + this.handleCollectionChange(eventName, ...args); + }); + }); + + // Restart listeners when points or the layer is replaced + this.listenToOnce(this, "change:geoPoints change:layer", () => { + if (this.get("isConnected")) { + this.connect(); + } + }); + // Restart listeners when points or the layer is replaced + this.listenToOnce(this, "change:geoPoints change:layer", () => { + if (this.get("isConnected")) { + this.connect(); + } + }); + + this.set("isConnected", true); + } catch (e) { + console.warn("Error connecting Points to Cesium. Disconnecting.", e); + this.disconnect(); + } + }, + + /** + * Stop listening for changes to the Points collection. + */ + disconnect: function () { + this.stopListening(this.get("geoPoints")); + this.set("isConnected", false); + }, + + /** + * Handle add, remove, merge, and reset events from the points collection + * @param {"update"|"reset"} eventName - The name of the event + * @param {GeoPoints} collection - The points collection + * @param {Object} options - Options for the event, as passed by Backbone + */ + handleCollectionChange(eventName, collection, options) { + try { + // What to do when the collection changes + } catch (e) { + console.warn('Error handling a "' + eventName + '" event.', e); + } + }, + } + ); +}); diff --git a/src/js/models/connectors/GeoPoints-CesiumPoints.js b/src/js/models/connectors/GeoPoints-CesiumPoints.js new file mode 100644 index 000000000..c6354cbc3 --- /dev/null +++ b/src/js/models/connectors/GeoPoints-CesiumPoints.js @@ -0,0 +1,169 @@ +"use strict"; + +/*global define */ +define(["cesium", "models/connectors/GeoPoints-Cesium"], function ( + Cesium, + GeoPointsCesiumConnector +) { + /** + * @class GeoPointsCesiumPointsConnector + * @classdesc This connector keeps a CesiumVectorData model in sync with the + * points in a GeoPoints collection. This connector will listen for changes to + * the GeoPoints collection and update the cesiumModel with point entities + * created from the points in the collection. + * @name GeoPointsCesiumPointsConnector + * @extends GeoPointsCesiumConnector + * @constructor + * @classcategory Models/Connectors + * @since x.x.x + */ + return GeoPointsCesiumConnector.extend( + /** @lends GeoPointsCesiumPointsConnector.prototype */ { + /** + * The type of Backbone.Model this is. + * @type {string} + * @default "GeoPointsCesiumPointsConnector" + */ + type: "GeoPointsCesiumPointsConnector", + + /** + * Extends the default Backbone.Model.defaults() function to specify + * default attributes for the GeoPointsCesiumPointsConnector model. + * @extends GeoPointsCesiumConnector.defaults + * @returns {Object} The default attributes + * @property {Array} layerPoints - The list of point entities that have + * been added to the layer. + */ + defaults: function () { + return { + // extend the defaults from the parent class + ...GeoPointsCesiumConnector.prototype.defaults(), + layerPoints: [], + }; + }, + + /** + * Handle add, remove, merge, and reset events from the points collection + * @param {"update"|"reset"} eventName - The name of the event + * @param {GeoPoints} collection - The points collection + * @param {Object} options - Options for the event, as passed by Backbone + */ + handleCollectionChange(eventName, collection, options) { + try { + // For merges and resets, just remove all points and re-add them + if (!options?.add && !options?.remove) { + this.resetLayerPoints(); + return; + } + // For adds and removes, just add or remove the points that changed + if (eventName === "update") { + if (options.add) { + const newModels = options.changes.added; + newModels.forEach((model) => { + this.addLayerPoint(model); + }); + } + if (options.remove) { + const removedModels = options.changes.removed; + removedModels.forEach((model) => { + this.removeLayerPoint(model); + }); + } + } + } catch (e) { + console.warn('Error handling a "' + eventName + '" event.', e); + } + }, + + /** + * Resync the layer points with the points from the points collection. + * This removes all point entities previously added to the layer and adds + * new ones for each point in the points collection. + */ + resetLayerPoints: function () { + const layer = this.get("layer"); + layer.suspendEvents(); + this.removeAllLayerPoints(); + this.addAllLayerPoints(); + layer.resumeEvents(); + }, + + /** + * Remove all layer points previously added to the layer. + * @returns {Boolean} Whether the layer points were removed + */ + removeAllLayerPoints: function () { + const layer = this.get("layer"); + if (!layer) return false; + const layerPoints = this.get("layerPoints"); + layerPoints.forEach((entity) => { + layer.removeEntity(entity); + }); + return true; + }, + + /** + * Add all points from the points collection to the layer. + * @returns {Boolean} Whether the layer points were added + */ + addAllLayerPoints: function () { + const layer = this.get("layer"); + if (!layer) return false; + const geoPoints = this.get("geoPoints"); + geoPoints.each((model) => { + this.addLayerPoint(model); + }); + return true; + }, + + /** + * Add a point from the points collection to the layer. Adds the point + * entity to the layerPoints array for tracking. + * @param {GeoPoint} model - The point model to add to the layer + * @returns {Cesium.Entity} The layer point that was created + */ + addLayerPoint: function (model) { + try { + const layer = this.get("layer") || this.setLayer(); + const layerPoint = layer.addEntity({ + id: model.cid, + position: model.get("mapWidgetCoords"), + point: { + pixelSize: 2, + heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, + }, + }); + // Track the layer point so we can remove it later + const layerPoints = this.get("layerPoints"); + layerPoints.push(layerPoint); + return layerPoint; + } catch (e) { + console.log("Failed to add a point to a CesiumVectorData.", e); + } + }, + + /** + * Remove a point from the points collection from the layer. Removes the + * point entity from the layerPoints array. + * @param {GeoPoint} model - The point model to remove from the layer + * @returns {Cesium.Entity} The layer point that was removed + */ + removeLayerPoint: function (model) { + try { + const layer = this.get("layer"); + if (!layer) return false; + const removedPoint = layer.removeEntity(model.cid); + // Remove the layer point from the list of layer points + const layerPoints = this.get("layerPoints"); + const index = layerPoints.indexOf(removedPoint); + if (index > -1) { + layerPoints.splice(index, 1); + } + return removedPoint; + } catch (e) { + console.log("Failed to remove a point from a CesiumVectorData.", e); + } + }, + } + ); +}); diff --git a/src/js/models/connectors/GeoPoints-CesiumPolygon.js b/src/js/models/connectors/GeoPoints-CesiumPolygon.js new file mode 100644 index 000000000..5d726d745 --- /dev/null +++ b/src/js/models/connectors/GeoPoints-CesiumPolygon.js @@ -0,0 +1,73 @@ +"use strict"; + +/*global define */ +define(["cesium", "models/connectors/GeoPoints-Cesium"], function ( + Cesium, + GeoPointsCesiumConnector +) { + /** + * @class GeoPointsCesiumPolygonConnector + * @classdesc This connector keeps a CesiumVectorData model in sync with the + * points in a GeoPoints collection. This connector will listen for changes to + * the GeoPoints collection and update the cesiumModel a polygon with vertices + * created from the points in the collection. + * @name GeoPointsCesiumPolygonConnector + * @extends GeoPointsCesiumConnector + * @constructor + * @classcategory Models/Connectors + * @since x.x.x + */ + return GeoPointsCesiumConnector.extend( + /** @lends GeoPointsCesiumPolygonConnector.prototype */ { + /** + * The type of Backbone.Model this is. + * @type {string} + * @default "GeoPointsCesiumPolygonConnector" + */ + type: "GeoPointsCesiumPolygonConnector", + + /** + * Extends the default Backbone.Model.defaults() function to specify + * default attributes for the GeoPointsCesiumPolygonConnector model. + * @extends GeoPointsCesiumConnector.defaults + * @returns {Object} The default attributes + * @property {Cesium.Entity} polygon - The polygon entity that has + * vertices created from the points in the collection. + */ + defaults: function () { + return { + // extend the defaults from the parent class + ...GeoPointsCesiumConnector.prototype.defaults(), + polygon: null, + }; + }, + + /** + * Create a Cesium.Polygon entity and add it to the layer. + * @returns {Cesium.Entity} The Cesium.Polygon entity that was added to + * the CesiumVectorData model. + */ + addPolygon: function () { + const layer = this.get("layer") || this.setVectorLayer(); + const geoPoints = this.get("geoPoints") || this.setPoints(); + return layer.addEntity({ + polygon: { + height: null, // <- clamp to ground + hierarchy: new Cesium.CallbackProperty(() => { + return new Cesium.PolygonHierarchy(geoPoints.asMapWidgetCoords()); + }, false), + }, + }); + }, + + /** + * Reset the positions of the polygon vertices to the current points in + * the GeoPoints collection. + */ + handleCollectionChange: function () { + this.get("polygon") || this.addPolygon(); + this.get("layer").updateAppearance(); + }, + } + ); +}); diff --git a/src/js/models/connectors/GeoPoints-VectorData.js b/src/js/models/connectors/GeoPoints-VectorData.js deleted file mode 100644 index 95a7a9b3a..000000000 --- a/src/js/models/connectors/GeoPoints-VectorData.js +++ /dev/null @@ -1,171 +0,0 @@ -/*global define */ -define([ - "backbone", - "collections/maps/GeoPoints", - "models/maps/assets/CesiumVectorData", -], function (Backbone, GeoPoints, CesiumVectorData) { - "use strict"; - - /** - * @class PointsVectorDataConnector - * @classdesc This connector keeps a CesiumVectorData model in sync with the - * points in a GeoPoints collection. This connector will listen for changes to - * the GeoPoints collection and update the cesiumModel with the features - * created from the points in the collection. - * @name PointsVectorDataConnector - * @extends Backbone.Model - * @constructor - * @classcategory Models/Connectors - * @since x.x.x - * - * TODO: Extend to allow for a collection of GeoPoints collections, where each - * GeoPoints collection can be represented as a different polygon in the - * CesiumVectorData model. - */ - return Backbone.Model.extend( - /** @lends PointsVectorDataConnector.prototype */ { - /** - * The type of Backbone.Model this is. - * @type {string} - * @default "PointsVectorDataConnector" - */ - type: "PointsVectorDataConnector", - - /** - * Extends the default Backbone.Model.defaults() function to specify - * default attributes for the PointsVectorDataConnector model. - */ - defaults: function () { - return { - points: null, - vectorLayer: null, - isConnected: false, - }; - }, - - /** - * Initialize the model. - * @param {Object} attrs - The attributes for this model. - * @param {GeoPoints | Object} [attributes.points] - The GeoPoints - * collection to use for this connector or a JSON object with options to - * create a new GeoPoints collection. If not provided, a new GeoPoints - * collection will be created. - * @param {CesiumVectorData | Object} [attributes.vectorLayer] - The - * CesiumVectorData model to use for this connector or a JSON object with - * options to create a new CesiumVectorData model. If not provided, a new - * CesiumVectorData model will be created. - */ - initialize: function (attrs) { - try { - attrs = attrs || {}; - this.setPoints(attrs.points); - this.setVectorLayer(attrs.vectorLayer); - if (attrs.isConnected) { - this.connect(); - } - } catch (e) { - console.log("Error initializing a PointsVectorDataConnector", e); - } - }, - - /** - * Set or create and set the GeoPoints collection for this connector. - * @param {GeoPoints | Object} [points] - The GeoPoints collection to use - * for this connector or a JSON object with options to create a new - * GeoPoints collection. If not provided, a new GeoPoints collection will - * be created. - * @returns {GeoPoints} The GeoPoints collection for this connector. - */ - setPoints: function (points) { - if (points instanceof GeoPoints) { - this.set("points", points); - } else { - this.set("points", new GeoPoints(points)); - } - return this.get("points"); - }, - - /** - * Set or create and set the CesiumVectorData model for this connector. - * @param {CesiumVectorData | Object} [vectorLayer] - The CesiumVectorData - * model to use for this connector or a JSON object with options to create - * a new CesiumVectorData model. If not provided, a new CesiumVectorData - * model will be created. - * @returns {CesiumVectorData} The CesiumVectorData model for this - * connector. - */ - setVectorLayer: function (vectorLayer) { - if (vectorLayer instanceof CesiumVectorData) { - this.set("vectorLayer", vectorLayer); - } else { - this.set("vectorLayer", new CesiumVectorData(vectorLayer)); - } - return this.get("vectorLayer"); - }, - - /** - * Listen for changes to the Points collection and update the - * CesiumVectorData model with the features created from the points in - * the collection. - */ - connect: function () { - try { - const connector = this; - this.disconnect(); - - const handler = (this.eventHandler = new Backbone.Model()); - const points = this.get("points") || this.setPoints(); - - // Update the vectorLayer when the points collection is updated. - handler.listenTo(points, "update reset", () => { - connector.updateVectorLayer(); - }); - - // Restart listeners the points collection or the vectorLayer is - // replaced with a new collection or model. - handler.listenToOnce(this, "change:points change:vectorLayer", () => { - if (this.get("isConnected")) { - connector.connect(); - } - }); - - this.set("isConnected", true); - } catch (e) { - console.warn( - "Error connecting a PointsVectorDataConnector, disconnecting.", - e - ); - connector.disconnect(); - } - }, - - /** - * Stop listening for changes to the Points collection. - */ - disconnect: function () { - const handler = this.eventHandler; - if (handler) { - handler.stopListening(); - handler.clear(); - handler = null; - } - this.set("isConnected", false); - }, - - /** - * Update the CesiumVectorData model with the features created from the - * points in the collection. - */ - updateVectorLayer: function () { - const points = this.get("points") || this.setPoints(); - const layer = this.get("vectorLayer") || this.setVectorLayer(); - const type = model.get("type"); - const geom = "Polygon"; - const data = type === "geojson" ? points.toGeoJson(geom) : this.toCzml(geom); - const opts = layer.getCesiumOptions() || {}; - opts.data = data; - layer.set("cesiumOptions", opts); - }, - } - ); -}); diff --git a/src/js/models/maps/GeoPoint.js b/src/js/models/maps/GeoPoint.js index 2ff0d7415..ea73452a1 100644 --- a/src/js/models/maps/GeoPoint.js +++ b/src/js/models/maps/GeoPoint.js @@ -1,6 +1,9 @@ "use strict"; -define(["backbone", "models/maps/GeoUtilities"], function (Backbone, GeoUtilities) { +define(["backbone", "models/maps/GeoUtilities"], function ( + Backbone, + GeoUtilities +) { /** * @class GeoPoint * @classdesc The GeoPoint model stores geographical coordinates including @@ -26,12 +29,16 @@ define(["backbone", "models/maps/GeoUtilities"], function (Backbone, GeoUtilitie * @property {number} longitude - The longitude of the point in degrees * @property {number} height - The height of the point in meters above sea * level + * @property {*} mapWidgetCoords - Optionally, Coordinates in the format + * provided by the map widget. For example, for Cesium, this is the Cesium + * Cartesian3 ECEF coordinates. */ defaults: function () { return { latitude: null, longitude: null, - height: null + height: null, + mapWidgetCoords: null, }; }, @@ -51,7 +58,7 @@ define(["backbone", "models/maps/GeoUtilities"], function (Backbone, GeoUtilitie toGeoJsonGeometry: function () { return { type: "Point", - coordinates: this.to2DArray() + coordinates: this.to2DArray(), }; }, @@ -64,7 +71,7 @@ define(["backbone", "models/maps/GeoUtilities"], function (Backbone, GeoUtilitie return { type: "Feature", geometry: this.toGeoJsonGeometry(), - properties: {} + properties: {}, }; }, @@ -83,8 +90,8 @@ define(["backbone", "models/maps/GeoUtilities"], function (Backbone, GeoUtilitie heightReference: "CLAMP_TO_GROUND", }, position: { - cartesian: ecefCoord - } + cartesian: ecefCoord, + }, }; }, @@ -109,11 +116,11 @@ define(["backbone", "models/maps/GeoUtilities"], function (Backbone, GeoUtilitie * Validate the model attributes * @param {Object} attrs - The model's attributes */ - validate: function(attrs) { + validate: function (attrs) { if (attrs.latitude < -90 || attrs.latitude > 90) { return "Invalid latitude. Must be between -90 and 90."; } - + if (attrs.longitude < -180 || attrs.longitude > 180) { return "Invalid longitude. Must be between -180 and 180."; } @@ -121,10 +128,10 @@ define(["backbone", "models/maps/GeoUtilities"], function (Backbone, GeoUtilitie // Assuming height is in meters and can theoretically be below sea // level. Adjust the height constraints as needed for your specific // application. - if (typeof attrs.height !== 'number') { + if (typeof attrs.height !== "number") { return "Invalid height. Must be a number."; } - } + }, } ); diff --git a/src/js/models/maps/GeoUtilities.js b/src/js/models/maps/GeoUtilities.js index 4ea00f806..62e51cddc 100644 --- a/src/js/models/maps/GeoUtilities.js +++ b/src/js/models/maps/GeoUtilities.js @@ -1,9 +1,6 @@ "use strict"; -define(["backbone", "models/maps/GeoUtilities"], function ( - Backbone, - GeoUtilities -) { +define(["backbone"], function (Backbone) { /** * @class GeoUtilities * @classdesc The GeoUtilities model has methods foe handling spatial data diff --git a/src/js/models/maps/Geohash.js b/src/js/models/maps/Geohash.js index 24372d065..4f9e865a2 100644 --- a/src/js/models/maps/Geohash.js +++ b/src/js/models/maps/Geohash.js @@ -1,11 +1,12 @@ "use strict"; -define(["jquery", "underscore", "backbone", "nGeohash"], function ( - $, - _, - Backbone, - nGeohash -) { +define([ + "jquery", + "underscore", + "backbone", + "nGeohash", + "models/maps/GeoUtilities", +], function ($, _, Backbone, nGeohash, GeoUtilities) { /** * @classdesc A Geohash Model represents a single geohash. * @classcategory Models/Geohashes @@ -346,7 +347,7 @@ define(["jquery", "underscore", "backbone", "nGeohash"], function ( * @returns {Array} The ECEF coordinates. */ geodeticToECEF: function (coord) { - return GeoUtilities.geodeticToECEF(coord); + return GeoUtilities.prototype.geodeticToECEF(coord); }, } ); diff --git a/src/js/models/maps/MapInteraction.js b/src/js/models/maps/MapInteraction.js index fdc15e83a..bf731a408 100644 --- a/src/js/models/maps/MapInteraction.js +++ b/src/js/models/maps/MapInteraction.js @@ -168,10 +168,11 @@ define([ */ setClickedPositionFromMousePosition: function () { const mousePosition = this.get("mousePosition"); - // get just the longitude and latitude const coords = { longitude: mousePosition.get("longitude"), - latitude: mousePosition.get("latitude") + latitude: mousePosition.get("latitude"), + height: mousePosition.get("height"), + mapWidgetCoords: mousePosition.get("mapWidgetCoords"), }; this.setClickedPosition(coords); }, @@ -191,7 +192,7 @@ define([ if (!point) { point = new GeoPoint(); this.set(attributeName, point); - } + } point.set(position); return point; }, diff --git a/src/js/models/maps/assets/CesiumGeohash.js b/src/js/models/maps/assets/CesiumGeohash.js index b4f23328f..499a716f5 100644 --- a/src/js/models/maps/assets/CesiumGeohash.js +++ b/src/js/models/maps/assets/CesiumGeohash.js @@ -277,11 +277,11 @@ define([ // Set the GeoJSON representing geohashes on the model const cesiumOptions = this.getCesiumOptions(); const type = model.get("type"); - const data = type === "geojson" ? this.getGeoJSON() : this.getCZML(); + const data = type === "GeoJsonDataSource" ? this.getGeoJSON() : this.getCZML(); cesiumOptions["data"] = data; cesiumOptions["height"] = 0; model.set("cesiumOptions", cesiumOptions); - // Create the model like a regular GeoJSON data source + // Create the model like a regular vector data source CesiumVectorData.prototype.createCesiumModel.call(this, recreate); } catch (e) { console.log("Error creating a CesiumGeohash model. ", e); diff --git a/src/js/models/maps/assets/CesiumVectorData.js b/src/js/models/maps/assets/CesiumVectorData.js index b58b2e62a..e95ec7ab6 100644 --- a/src/js/models/maps/assets/CesiumVectorData.js +++ b/src/js/models/maps/assets/CesiumVectorData.js @@ -1,674 +1,746 @@ -'use strict'; - -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 - ) { - /** - * @classdesc A CesiumVectorData Model is a vector layer (excluding - * Cesium3DTilesets) that can be used in Cesium maps. This model corresponds - * to "DataSource" models in Cesium. For example, this could represent - * vectors rendered from a Cesium GeoJSONDataSource. - * {@link https://cesium.com/learn/cesiumjs/ref-doc/GeoJsonDataSource.html}. - * Note: The GeoJsonDataSource and CzmlDataSource are the only supported - * DataSources so far, but eventually this model could be used to support - * the KmlDataSource (and perhaps a Cesium CustomDataSource). - * @classcategory Models/Maps/Assets - * @class CesiumVectorData - * @name CesiumVectorData - * @extends MapAsset - * @since 2.19.0 - * @constructor - */ - var CesiumVectorData = MapAsset.extend( - /** @lends CesiumVectorData.prototype */ { - - /** - * The name of this type of model - * @type {string} - */ - type: 'CesiumVectorData', - - /** - * Options that are supported for creating Cesium DataSources. The object will be - * passed to the cesium DataSource's load method as options, so the properties - * listed in the Cesium documentation are also supported. Each type of Cesium Data - * Source has a specific set of load method options. See for example, the - * GeoJsonDataSource options: - * {@link https://cesium.com/learn/cesiumjs/ref-doc/GeoJsonDataSource.html} - * @typedef {Object} CesiumVectorData#cesiumOptions - * @property {string|Object} data - The url, GeoJSON object, or TopoJSON object to - * be loaded. - */ - - /** - * Default attributes for CesiumVectorData models - * @name CesiumVectorData#defaults - * @extends MapAsset#defaults - * @type {Object} - * @property {'GeoJsonDataSource'} type The format of the data. Must be - * 'GeoJsonDataSource' or 'CzmlDataSource'. - * @property {VectorFilters} [filters=new VectorFilters()] A set of conditions - * used to show or hide specific features of this vector data. - * @property {AssetColorPalette} [colorPalette=new AssetColorPalette()] The color - * or colors mapped to attributes of this asset. Used to style the features and to - * make a legend. - * @property {Cesium.GeoJsonDataSource} cesiumModel A Cesium DataSource model - * created and used by Cesium that organizes the data to display in the Cesium - * Widget. See - * {@link https://cesium.com/learn/cesiumjs/ref-doc/DataSource.html?classFilter=DataSource} - * @property {CesiumVectorData#cesiumOptions} cesiumOptions options are passed to - * the function that creates the Cesium model. The properties of options are - * specific to each type of asset. - * @property {outlineColor} [outlineColor=null] The color of the outline of the - * features. If null, the outline will not be shown. If a string, it should be a - * valid CSS color string. If an object, it should be an AssetColor object, or - * a set of RGBA values. - */ - defaults: function () { - return Object.assign( - this.constructor.__super__.defaults(), - { - type: 'GeoJsonDataSource', - filters: new VectorFilters(), - cesiumModel: null, - cesiumOptions: {}, - colorPalette: new AssetColorPalette(), - icon: '', - outlineColor: null, - featureType: Cesium.Entity - } - ); - }, - - /** - * Executed when a new CesiumVectorData model is created. - * @param {MapConfig#MapAssetConfig} [assetConfig] The initial values of the - * attributes, which will be set on the model. - */ - initialize: function (assetConfig) { - try { +"use strict"; + +define([ + "underscore", + "cesium", + "models/maps/assets/MapAsset", + "models/maps/AssetColor", + "models/maps/AssetColorPalette", + "collections/maps/VectorFilters", +], function ( + _, + Cesium, + MapAsset, + AssetColor, + AssetColorPalette, + VectorFilters +) { + /** + * @classdesc A CesiumVectorData Model is a vector layer (excluding + * Cesium3DTilesets) that can be used in Cesium maps. This model corresponds + * to "DataSource" models in Cesium. For example, this could represent vectors + * rendered from a Cesium GeoJSONDataSource. + * {@link https://cesium.com/learn/cesiumjs/ref-doc/GeoJsonDataSource.html}. + * Note: GeoJsonDataSource, CzmlDataSource, and CustomDataSource are + * supported. Eventually this model could support the KmlDataSource. + * @classcategory Models/Maps/Assets + * @class CesiumVectorData + * @name CesiumVectorData + * @extends MapAsset + * @since 2.19.0 + * @constructor + */ + var CesiumVectorData = MapAsset.extend( + /** @lends CesiumVectorData.prototype */ { + /** + * The name of this type of model + * @type {string} + */ + type: "CesiumVectorData", + + /** + * Options that are supported for creating Cesium DataSources. The object + * will be passed to the cesium DataSource's load method as options, so + * the properties listed in the Cesium documentation are also supported. + * Each type of Cesium Data Source has a specific set of load method + * options. See for example, the GeoJsonDataSource options: + * {@link https://cesium.com/learn/cesiumjs/ref-doc/GeoJsonDataSource.html} + * @typedef {Object} CesiumVectorData#cesiumOptions + * @property {string|Object} data - The url, GeoJSON object, or TopoJSON + * object to be loaded. + */ + + /** + * Default attributes for CesiumVectorData models + * @name CesiumVectorData#defaults + * @extends MapAsset#defaults + * @type {Object} + * @property {'GeoJsonDataSource'} type The format of the data. Must be + * 'GeoJsonDataSource' or 'CzmlDataSource'. + * @property {VectorFilters} [filters=new VectorFilters()] A set of + * conditions used to show or hide specific features of this vector data. + * @property {AssetColorPalette} [colorPalette=new AssetColorPalette()] + * The color or colors mapped to attributes of this asset. Used to style + * the features and to make a legend. + * @property {Cesium.GeoJsonDataSource} cesiumModel A Cesium DataSource + * model created and used by Cesium that organizes the data to display in + * the Cesium Widget. See + * {@link https://cesium.com/learn/cesiumjs/ref-doc/DataSource.html?classFilter=DataSource} + * @property {CesiumVectorData#cesiumOptions} cesiumOptions options are + * passed to the function that creates the Cesium model. The properties of + * options are specific to each type of asset. + * @property {string|AssetColor} [outlineColor=null] The color of the + * outline of the features. If null, the outline will not be shown. If a + * string, it should be a valid CSS color string. If an object, it should + * be an AssetColor object, or a set of RGBA values. + */ + defaults: function () { + return Object.assign(this.constructor.__super__.defaults(), { + type: "GeoJsonDataSource", + filters: new VectorFilters(), + cesiumModel: null, + cesiumOptions: {}, + colorPalette: new AssetColorPalette(), + icon: '', + outlineColor: null, + featureType: Cesium.Entity, + }); + }, + + /** + * Executed when a new CesiumVectorData model is created. + * @param {MapConfig#MapAssetConfig} [assetConfig] The initial values of + * the attributes, which will be set on the model. + */ + initialize: function (assetConfig) { + try { + if (!assetConfig) assetConfig = {}; + + MapAsset.prototype.initialize.call(this, assetConfig); + + if (assetConfig.filters) { + this.set("filters", new VectorFilters(assetConfig.filters)); + } - if (!assetConfig) assetConfig = {}; + // displayReady will be updated by the Cesium map within which the + // asset is rendered. The map will set it to true when the data is + // ready to be rendered. Used to know when it's safe to calculate a + // bounding sphere. + this.set("displayReady", false); + + if ( + assetConfig.outlineColor && + !(assetConfig.outlineColor instanceof AssetColor) + ) { + this.set( + "outlineColor", + new AssetColor({ color: assetConfig.outlineColor }) + ); + } - MapAsset.prototype.initialize.call(this, assetConfig); + if ( + assetConfig.highlightColor && + !(assetConfig.highlightColor instanceof AssetColor) + ) { + this.set( + "highlightColor", + new AssetColor({ color: assetConfig.highlightColor }) + ); + } - if (assetConfig.filters) { - this.set('filters', new VectorFilters(assetConfig.filters)) + this.createCesiumModel(); + } catch (error) { + console.log("Error initializing a CesiumVectorData model.", error); + } + }, + + /** + * Creates a Cesium.DataSource model and sets it to this model's + * 'cesiumModel' attribute. This cesiumModel contains all the information + * required for Cesium to render the vector data. See + * {@link https://cesium.com/learn/cesiumjs/ref-doc/DataSource.html?classFilter=DataSource} + * @param {Boolean} [recreate = false] - Set recreate to true to force + * the function create the Cesium Model again. Otherwise, if a cesium + * model already exists, that is returned instead. + */ + createCesiumModel: function (recreate = false) { + try { + const model = this; + const cesiumOptions = this.getCesiumOptions(); + const type = model.get("type"); + const label = model.get("label") || ""; + const dataSourceFunction = Cesium[type]; + + // If the cesium model already exists, don't create it again unless + // specified + let dataSource = model.get("cesiumModel"); + if (dataSource) { + if (!recreate) { + return dataSource; + } else { + // If we are recreating the model, remove all entities first. see + // https://stackoverflow.com/questions/31426796/loading-updated-data-with-geojsondatasource-in-cesium-js + dataSource.entities.removeAll(); } + } - // displayReady will be updated by the Cesium map within which the asset is - // rendered. The map will set it to true when the data is ready to be - // rendered. Used to know when it's safe to calculate a bounding sphere. - this.set('displayReady', false) - - if ( - assetConfig.outlineColor && - !(assetConfig.outlineColor instanceof AssetColor) - ) { - this.set( - "outlineColor", - new AssetColor({ color: assetConfig.outlineColor }) - ); - } - - if ( - assetConfig.highlightColor && - !(assetConfig.highlightColor instanceof AssetColor) - ) { - this.set( - "highlightColor", - new AssetColor({ color: assetConfig.highlightColor }) - ); - } + model.set("displayReady", false); + model.resetStatus(); - this.createCesiumModel(); + if (typeof dataSourceFunction !== "function") { + model.setError(`${type} is not a supported data type.`); + return; + } + if (!dataSource) { + dataSource = new dataSourceFunction(label); + } + if (!dataSource) { + model.setError("Failed to create a Cesium DataSource model."); + return; + } + // There is no data to load for a CustomDataSource + if (type === "CustomDataSource") { + model.set("cesiumModel", dataSource); + model.setListeners(); + model.setReady(); + model.runVisualizers(); + return; } - catch (error) { - console.log('Error initializing a CesiumVectorData model.', error); + + // For GeoJSON and CZML data sources + if (!cesiumOptions || !cesiumOptions.data) { + model.setError( + "No data was provided to create a Cesium DataSource model." + ); + return; } - }, - - /** - * Creates a Cesium.DataSource model and sets it to this model's - * 'cesiumModel' attribute. This cesiumModel contains all the - * information required for Cesium to render the vector data. See - * {@link https://cesium.com/learn/cesiumjs/ref-doc/DataSource.html?classFilter=DataSource} - * @param {Boolean} [recreate = false] - Set recreate to true to force - * the function create the Cesium Model again. Otherwise, if a cesium - * model already exists, that is returned instead. - */ - createCesiumModel: function (recreate = false) { - - try { - - const model = this; - const cesiumOptions = this.getCesiumOptions(); - const type = model.get('type') - const label = model.get('label') || '' - const dataSourceFunction = Cesium[type] - - // If the cesium model already exists, don't create it again unless specified - let dataSource = model.get('cesiumModel') - if (dataSource) { + const data = JSON.parse(JSON.stringify(cesiumOptions.data)); + delete cesiumOptions.data; + + dataSource + .load(data, cesiumOptions) + .then(function (loadedData) { + model.set("cesiumModel", loadedData); if (!recreate) { - return dataSource - } else { - // If we are recreating the model, remove all entities first. - // see https://stackoverflow.com/questions/31426796/loading-updated-data-with-geojsondatasource-in-cesium-js - dataSource.entities.removeAll(); - // Make sure the CesiumWidgetView re-renders the data - model.set('displayReady', false); + model.setListeners(); } + model.updateFeatureVisibility(); + model.updateAppearance(); + model.setReady(); + }) + .otherwise(model.setError.bind(model)); + } catch (error) { + console.log("Failed to create a VectorData Cesium Model.", error); + } + }, + + /** + * Set listeners that update the cesium model when the backbone model is + * updated. + */ + setListeners: function () { + try { + MapAsset.prototype.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); + const filters = this.get("filters"); + this.stopListening(filters, "update"); + this.listenTo(filters, "update", this.updateFeatureVisibility); + } catch (error) { + console.log("Failed to set CesiumVectorData listeners.", error); + } + }, + + /** + * Checks that the map is ready to display this asset. The displayReady + * attribute is updated by the Cesium map when the dataSourceDisplay is + * updated. + * @returns {Promise} Returns a promise that resolves to this model when + * ready to be displayed. + */ + whenDisplayReady: function () { + return this.whenReady().then(function (model) { + return new Promise(function (resolve, reject) { + if (model.get("displayReady")) { + resolve(model); + return; } - - model.resetStatus(); - - if (!cesiumOptions || !cesiumOptions.data) { - model.set('status', 'error'); - model.set('statusDetails', 'Vector data source is missing: A URL or data object is required') - return - } - - if (dataSourceFunction && typeof dataSourceFunction === 'function') { - - if (!recreate || !dataSource) { - dataSource = new dataSourceFunction(label) + model.stopListening(model, "change:displayReady"); + model.listenTo(model, "change:displayReady", function () { + if (model.get("displayReady")) { + model.stopListening(model, "change:displayReady"); + resolve(model); } + }); + }); + }); + }, + + /** + * Try to find Entity object that comes from an object passed from the + * Cesium map. This is useful when the map is clicked and the map returns + * an object that may or may not be an Entity. + * @param {Object} mapObject - An object returned from the Cesium map + * @returns {Cesium.Entity} - The Entity object if found, otherwise null. + * @since 2.25.0 + */ + getEntityFromMapObject: function (mapObject) { + const entityType = this.get("featureType"); + if (mapObject instanceof entityType) return mapObject; + if (mapObject.id instanceof entityType) return mapObject.id; + return null; + }, + + /** + * @inheritdoc + * @since 2.25.0 + */ + getFeatureAttributes: function (feature) { + feature = this.getEntityFromMapObject(feature); + return MapAsset.prototype.getFeatureAttributes.call(this, feature); + }, + + /** + * @inheritdoc + * @since 2.25.0 + */ + usesFeatureType: function (feature) { + // This method could be passed the entity directly, or the object + // returned from Cesium on a click event (where the entity is in the id + // property). + if (!feature) return false; + const baseMethod = MapAsset.prototype.usesFeatureType; + let result = baseMethod.call(this, feature); + if (result) return result; + result = baseMethod.call(this, feature.id); + return result; + }, + + /** + * Given a feature from a Cesium Vector Data source, returns any + * properties that are set on the feature, similar to an attributes table. + * @param {Cesium.Entity} feature A Cesium Entity + * @returns {Object} An object containing key-value mapping of property + * names to properties. + */ + getPropertiesFromFeature: function (feature) { + feature = this.getEntityFromMapObject(feature); + if (!feature) return null; + const featureProps = feature.properties; + let properties = {}; + if (featureProps) { + properties = feature.properties.getValue(new Date()); + } + properties = this.addCustomProperties(properties); + return properties; + }, + + /** + * Return the label for a feature from a DataSource model + * @param {Cesium.Entity} feature A Cesium Entity + * @returns {string} The label + */ + getLabelFromFeature: function (feature) { + feature = this.getEntityFromMapObject(feature); + if (!feature) return null; + return feature.name; + }, + + /** + * Return the DataSource model for a feature from a Cesium DataSource + * model + * @param {Cesium.Entity} feature A Cesium Entity + * @returns {Cesium.GeoJsonDataSource|Cesium.CzmlDataSource} The model + */ + getCesiumModelFromFeature: function (feature) { + feature = this.getEntityFromMapObject(feature); + if (!feature) return null; + return feature.entityCollection.owner; + }, + + /** + * Return the ID used by Cesium for a feature from a DataSource model + * @param {Cesium.Entity} feature A Cesium Entity + * @returns {string} The ID + */ + getIDFromFeature: function (feature) { + feature = this.getEntityFromMapObject(feature); + if (!feature) return null; + return feature.id; + }, + + /** + * Updates the styles set on the cesiumModel object based on the + * colorPalette and filters attributes. + */ + updateAppearance: function () { + try { + const model = this; + const cesiumModel = this.get("cesiumModel"); + this.set("displayReady", false); + + if (!cesiumModel) { + return; + } - const data = cesiumOptions.data; - delete cesiumOptions.data + const entities = cesiumModel.entities.values; - if (!dataSource) { - model.set('status', 'error') - model.set('statusDetails', 'Failed to create a Cesium DataSource model.') - return - } + // Suspending events while updating a large number of entities helps + // performance. + cesiumModel.entities.suspendEvents(); - dataSource.load(data, cesiumOptions) - .then(function (loadedData) { - model.set('cesiumModel', loadedData) - if (!recreate) { - model.setListeners() - } - model.updateFeatureVisibility() - model.updateAppearance() - model.set('status', 'ready') - - }) - .otherwise(function (error) { - // See https://cesium.com/learn/cesiumjs/ref-doc/RequestErrorEvent.html - let details = error; - // Write a helpful error message - switch (error.statusCode) { - case 404: - details = 'The resource was not found (error code 404).' - break; - case 500: - details = 'There was a server error (error code 500).' - break; - } - model.set('status', 'error'); - model.set('statusDetails', details) - }) - } else { - model.set('status', 'error') - model.set('statusDetails', type + ' is not a supported imagery type.') - } + // If the asset isn't visible, just hide all entities and update the + // visibility property to indicate that layer is hidden + if (!model.isVisible()) { + cesiumModel.entities.show = false; + if (model.get("opacity") === 0) model.set("visible", false); + } else { + cesiumModel.entities.show = true; + this.styleEntities(entities); } - catch (error) { - console.log('Failed to create a VectorData Cesium Model.', error); + + cesiumModel.entities.resumeEvents(); + this.runVisualizers(); + } catch (e) { + console.log("Failed to update CesiumVectorData model styles.", e); + } + }, + + runVisualizers: function () { + const dataSource = this.get("cesiumModel"); + const visualizers = dataSource._visualizers; + if (!visualizers || !visualizers.length) { + this.whenVisualizersReady(this.runVisualizers.bind(this)); + return; + } + const time = Cesium.JulianDate.now(); + let displayReadyNow = dataSource.update(time); + for (let x = 0; x < visualizers.length; x++) { + displayReadyNow = visualizers[x].update(time) && displayReadyNow; + } + this.set("displayReady", displayReadyNow); + this.trigger("appearanceChanged"); + }, + + /** + * Check for the existence of visualizers and run the callback when they + * are ready. This is useful for waiting to run code that depends on the + * visualizers being ready. It will attempt to run the callback every + * pingRate ms until the visualizers are ready, or until the maxPings is + * reached. + * @param {*} callBack + * @param {*} maxPings + */ + whenVisualizersReady: function (callBack, pingRate = 100, maxPings = 30) { + const model = this; + let pings = 0; + const interval = setInterval(function () { + pings++; + if (pings > maxPings) { + clearInterval(interval); + return; } - }, - - /** - * Set listeners that update the cesium model when the backbone model is updated. - */ - setListeners: function () { - try { - MapAsset.prototype.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) - const filters = this.get('filters'); - this.stopListening(filters, 'update') - this.listenTo(filters, 'update', this.updateFeatureVisibility) + const visualizers = model.get("cesiumModel")._visualizers; + if (visualizers && visualizers.length) { + clearInterval(interval); + callBack(); } - catch (error) { - console.log('Failed to set CesiumVectorData listeners.', error); + }, pingRate); + }, + + getEntityCollection: function () { + const model = this; + const dataSource = model.get("cesiumModel"); + return dataSource?.entities; + }, + + getEntities: function () { + return this.getEntityCollection()?.values || []; + }, + + suspendEvents: function () { + const entities = this.getEntityCollection(); + if (entities) entities.suspendEvents(); + }, + + resumeEvents: function () { + const entities = this.getEntityCollection(); + if (entities) entities.resumeEvents(); + }, + + addEntity: function (entity) { + try { + const entities = this.getEntityCollection(); + if (!entities) return false; + const newEntity = entities.add(entity); + this.styleEntities([newEntity]); + this.runVisualizers(); + return newEntity; + } catch (e) { + console.log("Failed to add an entity.", e); + } + }, + + removeEntity: function (entity) { + try { + const entities = this.getEntities(); + if (!entities) return false; + let removed = false; + // if entity is a string, remove by ID + if (typeof entity === "string") { + removed = entities.removeById(entity); + } else { + // Otherwise, assume it's an entity object + removed = entities.remove(entity); } - }, - - /** - * Checks that the map is ready to display this asset. The displayReady attribute - * is updated by the Cesium map when the dataSourceDisplay is updated. - * @returns {Promise} Returns a promise that resolves to this model when ready to - * be displayed. - */ - whenDisplayReady: function () { - return this.whenReady() - .then(function (model) { - return new Promise(function (resolve, reject) { - if (model.get('displayReady')) { - resolve(model) - return - } - model.stopListening(model, 'change:displayReady') - model.listenTo(model, 'change:displayReady', function () { - if (model.get('displayReady')) { - model.stopListening(model, 'change:displayReady') - resolve(model) - } - }) - }); - }) - }, - - /** - * Try to find Entity object that comes from an object passed from the - * Cesium map. This is useful when the map is clicked and the map - * returns an object that may or may not be an Entity. - * @param {Object} mapObject - An object returned from the Cesium map - * @returns {Cesium.Entity} - The Entity object if found, otherwise null. - * @since 2.25.0 - */ - getEntityFromMapObject: function(mapObject) { - const entityType = this.get("featureType") - if (mapObject instanceof entityType) return mapObject - if (mapObject.id instanceof entityType) return mapObject.id - return null - }, - - /** - * @inheritdoc - * @since 2.25.0 - */ - getFeatureAttributes: function (feature) { - feature = this.getEntityFromMapObject(feature) - return MapAsset.prototype.getFeatureAttributes.call(this, feature) - }, - - /** - * @inheritdoc - * @since 2.25.0 - */ - usesFeatureType: function (feature) { - // This method could be passed the entity directly, or the object - // returned from Cesium on a click event (where the entity is in the - // id property). - if(!feature) return false - const baseMethod = MapAsset.prototype.usesFeatureType - let result = baseMethod.call(this, feature) - if (result) return result - result = baseMethod.call(this, feature.id) - return result - }, - - /** - * Given a feature from a Cesium Vector Data source, returns any properties that are set - * on the feature, similar to an attributes table. - * @param {Cesium.Entity} feature A Cesium Entity - * @returns {Object} An object containing key-value mapping of property names to - * properties. - */ - getPropertiesFromFeature: function(feature) { - feature = this.getEntityFromMapObject(feature) - if (!feature) return null - const featureProps = feature.properties - let properties = {} - if (featureProps) { - properties = feature.properties.getValue(new Date()) + this.runVisualizers(); + return removed; + } catch (e) { + console.log("Failed to remove an entity.", e); + } + }, + + /** + * Update the styles for a set of entities + * @param {Array} entities - The entities to update + * @since 2.25.0 + */ + styleEntities: function (entities) { + // Map of entity types to style functions + const entityStyleMap = { + polygon: this.stylePolygon, + polyline: this.stylePolyline, + billboard: this.styleBillboard, + point: this.stylePoint, + }; + + entities.forEach((entity) => { + const styles = this.getStyles(entity); + if (!styles) { + entity.show = false; + return; } - properties = this.addCustomProperties(properties) - return properties - }, - - /** - * Return the label for a feature from a DataSource model - * @param {Cesium.Entity} feature A Cesium Entity - * @returns {string} The label - */ - getLabelFromFeature: function (feature) { - feature = this.getEntityFromMapObject(feature) - if (!feature) return null - return feature.name - }, - - /** - * Return the DataSource model for a feature from a Cesium DataSource - * model - * @param {Cesium.Entity} feature A Cesium Entity - * @returns {Cesium.GeoJsonDataSource|Cesium.CzmlDataSource} The model - */ - getCesiumModelFromFeature: function (feature) { - feature = this.getEntityFromMapObject(feature) - if (!feature) return null - return feature.entityCollection.owner - }, - - /** - * Return the ID used by Cesium for a feature from a DataSource model - * @param {Cesium.Entity} feature A Cesium Entity - * @returns {string} The ID - */ - getIDFromFeature: function (feature) { - feature = this.getEntityFromMapObject(feature) - if (!feature) return null - return feature.id - }, - - /** - * Updates the styles set on the cesiumModel object based on the colorPalette and - * filters attributes. - */ - updateAppearance: function () { - try { - - const model = this; - const cesiumModel = this.get('cesiumModel'); - - if (!cesiumModel) return - - const entities = cesiumModel.entities.values - - // Suspending events while updating a large number of entities helps - // performance. - cesiumModel.entities.suspendEvents() - - // If the asset isn't visible, just hide all entities and update the - // visibility property to indicate that layer is hidden - if (!model.isVisible()) { - cesiumModel.entities.show = false - if (model.get('opacity') === 0) model.set('visible', false); - } else { - cesiumModel.entities.show = true - this.styleEntities(entities) + entity.show = true; + for (const [type, styleFunction] of Object.entries(entityStyleMap)) { + if (entity[type]) { + styleFunction.call(this, entity, styles); } - - cesiumModel.entities.resumeEvents() - - // Let the map and/or other parent views know that a change has been - // made that requires the map to be re-rendered - model.trigger('appearanceChanged') - - } - catch (e) { - console.log('Failed to update CesiumVectorData model styles.', e); } - }, - - /** - * Update the styles for a set of entities - * @param {Array} entities - The entities to update - * @since 2.25.0 - */ - styleEntities: function (entities) { - - // Map of entity types to style functions - const entityStyleMap = { - polygon: this.stylePolygon, - polyline: this.stylePolyline, - billboard: this.styleBillboard, - point: this.stylePoint, - }; - - entities.forEach(entity => { - const styles = this.getStyles(entity); - if (!styles) { - entity.show = false; - return; + }, this); + }, + + /** + * Update the styles for a polygon entity + * @param {Cesium.Entity} entity - The entity to update + * @param {Object} styles - Styles to apply, as returned by getStyles + * @since 2.25.0 + */ + stylePolygon: function (entity, styles) { + entity.polygon.material = styles.color; + entity.polygon.outline = styles.outline; + entity.polygon.outlineColor = styles.outlineColor; + entity.polygon.outlineWidth = styles.outline ? 2 : 0; + }, + + /** + * Update the styles for a point entity + * @param {Cesium.Entity} entity - The entity to update + * @param {Object} styles - Styles to apply, as returned by getStyles + * @since 2.25.0 + */ + stylePoint: function (entity, styles) { + entity.point.color = styles.color; + entity.point.outlineColor = styles.outlineColor; + entity.point.outlineWidth = styles.outline ? 2 : 0; + entity.point.pixelSize = styles.pointSize; + }, + + /** + * Update the styles for a polyline entity + * @param {Cesium.Entity} entity - The entity to update + * @param {Object} styles - Styles to apply, as returned by getStyles + * @since 2.25.0 + */ + stylePolyline: function (entity, styles) { + entity.polyline.material = styles.color; + entity.polyline.width = styles.lineWidth; + }, + + /** + * Update the styles for a billboard entity + * @param {Cesium.Entity} entity - The entity to update + * @param {Object} styles - Styles to apply, as returned by getStyles + * @since 2.25.0 + */ + styleBillboard: function (entity, styles) { + if (!this.pinBuilder) { + this.pinBuilder = new Cesium.PinBuilder(); + } + entity.billboard.image = this.pinBuilder + .fromColor(styles.color, styles.markerSize) + .toDataURL(); + // To convert the automatically created billboards to points instead: + // entity.billboard = undefined; entity.point = new + // Cesium.PointGraphics(); + }, + + /** + * Update the styles for a label entity + * @param {Cesium.Entity} entity - The entity to update + * @param {Object} styles - Styles to apply, as returned by getStyles + * @since 2.25.0 + */ + styleLabel: function (entity, styles) { + // TODO... + }, + + /** + * Covert a Color model to a Cesium Color + * @param {Color} color A Color model + * @returns {Cesium.Color|null} A Cesium Color or null if the color is + * invalid + * @since 2.25.0 + */ + colorToCesiumColor: function (color) { + color = color?.get ? color.get("color") : color; + if (!color) return null; + return new Cesium.Color( + color.red, + color.green, + color.blue, + color.alpha + ); + }, + + /** + * Return the color for a feature based on the colorPalette and filters + * attributes. + * @param {Cesium.Entity} entity A Cesium Entity + * @returns {Cesium.Color|null} A Cesium Color or null if the color is + * invalid or alpha is 0 + * @since 2.25.0 + */ + colorForEntity: function (entity) { + const properties = this.getPropertiesFromFeature(entity); + const color = this.colorToCesiumColor(this.getColor(properties)); + const alpha = color.alpha * this.get("opacity"); + if (alpha === 0) return null; + color.alpha = alpha; + return this.colorToCesiumColor(color); + }, + + /** + * Return the styles for a selected feature + * @param {Cesium.Entity} entity A Cesium Entity + * @returns {Object} An object containing the styles for the feature + * @since 2.25.0 + */ + getSelectedStyles: function (entity) { + const highlightColor = this.colorToCesiumColor( + this.get("highlightColor") + ); + return { + color: highlightColor || this.colorForEntity(entity), + outlineColor: Cesium.Color.WHITE, + outline: true, + lineWidth: 7, + markerSize: 34, + pointSize: 17, + }; + }, + + /** + * Return the styles for a feature + * @param {Cesium.Entity} entity A Cesium Entity + * @returns {Object} An object containing the styles for the feature + * @since 2.25.0 + */ + getStyles: function (entity) { + 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( + this.get("outlineColor")?.get("color") + ); + return { + color: color, + outlineColor: outlineColor, + outline: outlineColor ? true : false, + lineWidth: 3, + markerSize: 25, + pointSize: 13, + }; + }, + + /** + * Shows or hides each feature from this Map Asset based on the filters. + */ + updateFeatureVisibility: function () { + try { + const model = this; + const entities = this.getEntities(); + const filters = model.get("filters"); + + if (!entities || !filters) return; + + // Suspending events while updating a large number of entities helps + // performance. + this.suspendEvents(); + + for (var i = 0; i < entities.length; i++) { + let visible = true; + const entity = entities[i]; + if (filters && filters.length) { + const properties = model.getPropertiesFromFeature(entity); + visible = model.featureIsVisible(properties); } - entity.show = true; - for (const [type, styleFunction] of Object.entries(entityStyleMap)) { - if (entity[type]) { - styleFunction.call(this, entity, styles); - } - } - }, this); - }, - - /** - * Update the styles for a polygon entity - * @param {Cesium.Entity} entity - The entity to update - * @param {Object} styles - Styles to apply, as returned by getStyles - * @since 2.25.0 - */ - stylePolygon: function (entity, styles) { - entity.polygon.material = styles.color - entity.polygon.outline = styles.outline; - entity.polygon.outlineColor = styles.outlineColor - entity.polygon.outlineWidth = styles.outline ? 2 : 0 - }, - - /** - * Update the styles for a point entity - * @param {Cesium.Entity} entity - The entity to update - * @param {Object} styles - Styles to apply, as returned by getStyles - * @since 2.25.0 - */ - stylePoint: function (entity, styles) { - entity.point.color = styles.color - entity.point.outlineColor = styles.outlineColor - entity.point.outlineWidth = styles.outline ? 2 : 0 - entity.point.pixelSize = styles.pointSize - }, - - /** - * Update the styles for a polyline entity - * @param {Cesium.Entity} entity - The entity to update - * @param {Object} styles - Styles to apply, as returned by getStyles - * @since 2.25.0 - */ - stylePolyline: function (entity, styles) { - entity.polyline.material = styles.color - entity.polyline.width = styles.lineWidth - }, - - /** - * Update the styles for a billboard entity - * @param {Cesium.Entity} entity - The entity to update - * @param {Object} styles - Styles to apply, as returned by getStyles - * @since 2.25.0 - */ - styleBillboard: function (entity, styles) { - if (!this.pinBuilder) { - this.pinBuilder = new Cesium.PinBuilder() - } - entity.billboard.image = this.pinBuilder.fromColor( - styles.color, styles.markerSize).toDataURL() - // To convert the automatically created billboards to points instead: - // entity.billboard = undefined; - // entity.point = new Cesium.PointGraphics(); - }, - - /** - * Update the styles for a label entity - * @param {Cesium.Entity} entity - The entity to update - * @param {Object} styles - Styles to apply, as returned by getStyles - * @since 2.25.0 - */ - styleLabel: function (entity, styles) { - // TODO... - }, - - /** - * Covert a Color model to a Cesium Color - * @param {Color} color A Color model - * @returns {Cesium.Color|null} A Cesium Color or null if the color is - * invalid - * @since 2.25.0 - */ - colorToCesiumColor: function (color) { - color = color?.get ? color.get("color") : color; - if(!color) return null - return new Cesium.Color( - color.red, color.green, color.blue, color.alpha - ) - }, - - /** - * Return the color for a feature based on the colorPalette and filters - * attributes. - * @param {Cesium.Entity} entity A Cesium Entity - * @returns {Cesium.Color|null} A Cesium Color or null if the color is - * invalid or alpha is 0 - * @since 2.25.0 - */ - colorForEntity: function (entity) { - const properties = this.getPropertiesFromFeature(entity); - const color = this.colorToCesiumColor(this.getColor(properties)); - const alpha = color.alpha * this.get("opacity"); - if (alpha === 0) return null; - color.alpha = alpha; - return this.colorToCesiumColor(color); - }, - - /** - * Return the styles for a selected feature - * @param {Cesium.Entity} entity A Cesium Entity - * @returns {Object} An object containing the styles for the feature - * @since 2.25.0 - */ - getSelectedStyles: function (entity) { - const highlightColor = this.colorToCesiumColor( - this.get("highlightColor") - ); - return { - "color": highlightColor || this.colorForEntity(entity), - "outlineColor": Cesium.Color.WHITE, - "outline": true, - "lineWidth": 7, - "markerSize": 34, - "pointSize": 17 - } - }, - - /** - * Return the styles for a feature - * @param {Cesium.Entity} entity A Cesium Entity - * @returns {Object} An object containing the styles for the feature - * @since 2.25.0 - */ - getStyles: function (entity) { - if (!entity) return null - entity = this.getEntityFromMapObject(entity) - if (this.featureIsSelected(entity)) { - return this.getSelectedStyles(entity) + entity.show = visible; } - const color = this.colorForEntity(entity); - if (!color) { return null } - const outlineColor = this.colorToCesiumColor( - this.get("outlineColor")?.get("color") - ); - return { - "color": color, - "outlineColor": outlineColor, - "outline": outlineColor ? true : false, - "lineWidth": 3, - "markerSize": 25, - "pointSize": 13, - } - }, - - /** - * Shows or hides each feature from this Map Asset based on the filters. - */ - updateFeatureVisibility: function () { - try { - const model = this; - const cesiumModel = model.get('cesiumModel') - const entities = cesiumModel.entities.values - const filters = model.get('filters') - - // Suspending events while updating a large number of entities helps - // performance. - cesiumModel.entities.suspendEvents() - - for (var i = 0; i < entities.length; i++) { - let visible = true - const entity = entities[i] - if (filters && filters.length) { - const properties = model.getPropertiesFromFeature(entity) - visible = model.featureIsVisible(properties) + + this.resumeEvents(); + model.runVisualizers(); + } catch (e) { + console.log("Failed to update CesiumVectorData model styles.", e); + } + }, + + /** + * Waits for the model to be ready to display, then gets a Cesium Bounding + * Sphere that can be used to navigate to view the full extent of the + * vector data. See + * {@link https://cesium.com/learn/cesiumjs/ref-doc/BoundingSphere.html}. + * @param {Cesium.DataSourceDisplay} dataSourceDisplay The data source + * display attached to the CesiumWidget scene that this bounding sphere is + * for. Required. + * @returns {Promise} Returns a promise that resolves to a Cesium Bounding + * Sphere when ready + */ + getBoundingSphere: function (dataSourceDisplay) { + return this.whenDisplayReady() + .then(function (model) { + const entities = model.getEntities(); // .slice(0)? + const boundingSpheres = []; + const boundingSphereScratch = new Cesium.BoundingSphere(); + for (let i = 0, len = entities.length; i < len; i++) { + let state = Cesium.BoundingSphereState.PENDING; + state = dataSourceDisplay.getBoundingSphere( + entities[i], + false, + boundingSphereScratch + ); + if (state === Cesium.BoundingSphereState.PENDING) { + return false; + } else if (state !== Cesium.BoundingSphereState.FAILED) { + boundingSpheres.push( + Cesium.BoundingSphere.clone(boundingSphereScratch) + ); } - entity.show = visible } - - cesiumModel.entities.resumeEvents() - - // Let the map and/or other parent views know that a change has been made that - // requires the map to be re-rendered - model.trigger('appearanceChanged') - } - catch (error) { + if (boundingSpheres.length) { + return Cesium.BoundingSphere.fromBoundingSpheres(boundingSpheres); + } + return false; + }) + .catch(function (error) { console.log( - 'There was an error updating CesiumVectorData feature visibility' + - '. Error details: ' + error + "Failed to get the bounding sphere for a CesiumVectorData model" + + ". Error details: " + + error ); - } - }, - - /** - * Waits for the model to be ready to display, then gets a Cesium Bounding Sphere - * that can be used to navigate to view the full extent of the vector data. See - * {@link https://cesium.com/learn/cesiumjs/ref-doc/BoundingSphere.html}. - * @param {Cesium.DataSourceDisplay} dataSourceDisplay The data source display - * attached to the CesiumWidget scene that this bounding sphere is for. Required. - * @returns {Promise} Returns a promise that resolves to a Cesium Bounding Sphere - * when ready - */ - getBoundingSphere: function (dataSourceDisplay) { - return this.whenDisplayReady() - .then(function (model) { - const entities = model.get('cesiumModel').entities.values.slice(0) - const boundingSpheres = []; - const boundingSphereScratch = new Cesium.BoundingSphere(); - for (let i = 0, len = entities.length; i < len; i++) { - let state = Cesium.BoundingSphereState.PENDING; - state = dataSourceDisplay.getBoundingSphere( - entities[i], false, boundingSphereScratch - ) - if (state === Cesium.BoundingSphereState.PENDING) { - return false; - } else if (state !== Cesium.BoundingSphereState.FAILED) { - boundingSpheres.push(Cesium.BoundingSphere.clone(boundingSphereScratch)); - } - } - if (boundingSpheres.length) { - return Cesium.BoundingSphere.fromBoundingSpheres(boundingSpheres); - } - return false - }).catch(function (error) { - console.log( - 'Failed to get the bounding sphere for a CesiumVectorData model' + - '. Error details: ' + error - ); - }) - }, - - }); - - return CesiumVectorData; + }); + }, + } + ); - } -); + return CesiumVectorData; +}); diff --git a/src/js/models/maps/assets/MapAsset.js b/src/js/models/maps/assets/MapAsset.js index f50d34bc9..e84fd5320 100644 --- a/src/js/models/maps/assets/MapAsset.js +++ b/src/js/models/maps/assets/MapAsset.js @@ -333,6 +333,37 @@ define([ } }, + /** + * Set an error status and message for this asset. + * @param {Object|String} error - An error object with a status code + * attribute or or string with details about the error. + * @since x.x.x + */ + setError: function (error) { + // See https://cesium.com/learn/cesiumjs/ref-doc/RequestErrorEvent.html + let details = error; + // Write a helpful error message + switch (error.statusCode) { + case 404: + details = 'The resource was not found (error code 404).' + break; + case 500: + details = 'There was a server error (error code 500).' + break; + } + this.set('status', 'error'); + this.set('statusDetails', details) + }, + + /** + * Set a ready status for this asset. + * @since x.x.x + */ + setReady: function () { + this.set('status', 'ready') + this.set('statusDetails', null) + }, + /** * When the asset can't be loaded, hide it from the map and show an error. * @since x.x.x diff --git a/src/js/views/maps/CesiumWidgetView.js b/src/js/views/maps/CesiumWidgetView.js index 641592e87..51b32a6c9 100644 --- a/src/js/views/maps/CesiumWidgetView.js +++ b/src/js/views/maps/CesiumWidgetView.js @@ -86,7 +86,7 @@ define([ removeFunction: "remove3DTileset", }, { - types: ["GeoJsonDataSource", "CzmlDataSource"], + types: ["GeoJsonDataSource", "CzmlDataSource", "CustomDataSource"], renderFunction: "addVectorData", removeFunction: "removeVectorData", }, @@ -240,8 +240,8 @@ define([ /** * Create a DataSourceDisplay and DataSourceCollection for the Cesium - * widget, and listen to the clock tick to update the display. This is - * required to display vector data (e.g. GeoJSON) on the map. + * widget. This is required to display vector data (e.g. GeoJSON) on the + * map. * @since x.x.x * @returns {Cesium.DataSourceDisplay} The Cesium DataSourceDisplay */ @@ -252,11 +252,6 @@ define([ scene: view.scene, dataSourceCollection: view.dataSourceCollection, }); - view.clock.onTick.removeEventListener( - view.updateDataSourceDisplay, - view - ); - view.clock.onTick.addEventListener(view.updateDataSourceDisplay, view); return view.dataSourceDisplay; }, @@ -285,64 +280,32 @@ define([ if (view.zoomTarget) { view.completeFlight(view.zoomTarget, view.zoomOptions); } + // The dataSourceDisplay must be set to 'ready' to get bounding + // spheres for dataSources + view.dataSourceDisplay._ready = true; } catch (e) { console.log("Error calling post render functions:", e); } }, /** - * Runs on every Cesium clock tick. Updates the display of the - * CesiumVectorData models in the scene. Similar to - * Cesium.DataSourceDisplay.update function, in that it runs update() on - * each DataSource and each DataSource's visualizer, except that it also - * updates each CesiumVectorData model's 'displayReady' attribute. (Sets - * to true when the asset is ready to be rendered in the map, false - * otherwise). Also re-renders the scene when the displayReady attribute - * changes. + * Run the update method and all visualizers for each data source. + * @since x.x.x */ - updateDataSourceDisplay: function () { - try { - const view = this; - const layers = view.model.get("layers"); - - var dataSources = view.dataSourceDisplay.dataSources; - if (!dataSources || !dataSources.length) { - return; - } - - let allReady = true; - const allReadyBefore = view.dataSourceDisplay._ready; - - for (let i = 0, len = dataSources.length; i < len; i++) { - const time = view.clock.currentTime; - const dataSource = dataSources.get(i); - const visualizers = dataSource._visualizers; - - const assetModel = layers.findWhere({ - cesiumModel: dataSource, - }); - let displayReadyNow = dataSource.update(time); - - for (let x = 0; x < visualizers.length; x++) { - displayReadyNow = visualizers[x].update(time) && displayReadyNow; - } - - assetModel.set("displayReady", displayReadyNow); - - allReady = displayReadyNow && allReady; - } - - // If any dataSource has switched display states, then re-render the - // scene. - if (allReady !== allReadyBefore) { - view.scene.requestRender(); - } - // The dataSourceDisplay must be set to 'ready' to get bounding - // spheres for dataSources - view.dataSourceDisplay._ready = allReady; - } catch (e) { - console.log("Error updating the data source display.", e); + updateAllDataSources: function () { + const view = this; + const dataSources = view.dataSourceDisplay.dataSources; + if (!dataSources || !dataSources.length) { + return; } + const time = view.clock.currentTime; + dataSources.forEach(function (dataSource) { + dataSource.update(view.clock.currentTime); + // for each visualizer, update it + dataSource._visualizers.forEach(function (visualizer) { + visualizer.update(time); + }); + }); }, /** @@ -554,10 +517,18 @@ define([ let newPosition = null; if (cartesian) { newPosition = view.getDegreesFromCartesian(cartesian); + newPosition.mapWidgetCoords = cartesian; } view.interactions.setMousePosition(newPosition); }, + /** + * Record the feature hovered over by the mouse based on position. + * @param {Object} position - The position of the mouse on the map + * @param {number} [delay=200] - The minimum number of milliseconds that + * must pass between calls to this function. + * @since x.x.x + */ setHoveredFeatures: function (position, delay = 200) { const view = this; const lastCall = this.setHoveredFeaturesLastCall || 0; @@ -568,14 +539,15 @@ define([ view.interactions.setHoveredFeatures([pickedFeature]); }, + /** + * React when the user interacts with the map. + * @since x.x.x + */ setInteractionListeners: function () { - // TODO: unset listeners too const interactions = this.interactions; const hoveredFeatures = interactions.get("hoveredFeatures"); + this.stopListening(hoveredFeatures, "change update"); this.listenTo(hoveredFeatures, "change update", this.updateCursor); - // this.listenTo( interactions, "change update", - // this.handleClickedFeatures - // ); }, /** diff --git a/src/js/views/maps/DrawToolView.js b/src/js/views/maps/DrawToolView.js index 75fa7dc17..768f4499c 100644 --- a/src/js/views/maps/DrawToolView.js +++ b/src/js/views/maps/DrawToolView.js @@ -1,8 +1,10 @@ "use strict"; -define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( +define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connectors/GeoPoints-CesiumPoints", "collections/maps/GeoPoints"], function ( Backbone, - GeoPointsVectorData + GeoPointsVectorData, + GeoPointsCesiumPoints, + GeoPoints ) { /** * @class DrawTool @@ -107,6 +109,19 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( */ points: undefined, + /** + * The color of the polygon that is being drawn as a hex string. + * @type {string} + */ + color: "#a31840", + + /** + * The initial opacity of the polygon that is being drawn. A number + * between 0 and 1. + * @type {number} + */ + opacity: 0.8, + /** * Initializes the DrawTool * @param {Object} options - A literal object with options to pass to the @@ -126,7 +141,7 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( // points, and originalAction properties to this view this.setUpMapModel(); this.setUpLayer(); - this.setUpConnector(); + this.setUpConnectors(); }, /** @@ -148,21 +163,20 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( */ setUpLayer: function () { this.layer = this.mapModel.addAsset({ - type: "CzmlDataSource", + type: "CustomDataSource", label: "Your Polygon", description: "The polygon that you are drawing on the map", hideInLayerList: true, // TODO: Hide in LayerList, doc in mapConfig - outlineColor: "#FF3E41", // TODO - opacity: 0.55, // TODO + outlineColor: this.color, + opacity: this.opacity, colorPalette: { colors: [ { - color: "#FF3E41", // TODO + color: this.color, }, ], }, - }); - return this.layer; + }) }, /** @@ -171,12 +185,18 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( * this view. * @returns {GeoPointsVectorData} The connector */ - setUpConnector: function () { - this.connector = new GeoPointsVectorData({ - vectorLayer: this.layer, + setUpConnectors: function () { + const points = this.points = new GeoPoints(); + this.polygonConnector = new GeoPointsVectorData({ + layer: this.layer, + geoPoints: points, + }); + this.pointsConnector = new GeoPointsCesiumPoints({ + layer: this.layer, + geoPoints: points, }); - this.points = this.connector.get("points"); - this.connector.connect(); + this.polygonConnector.connect(); + this.pointsConnector.connect(); return this.connector; }, @@ -203,6 +223,7 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( */ removeLayer: function () { if (!this.mapModel || !this.layer) return; + // TODO this.connector.disconnect(); this.connector.set("vectorLayer", null); this.mapModel.removeAsset(this.layer); @@ -386,49 +407,12 @@ define(["backbone", "models/connectors/GeoPoints-VectorData"], function ( this.addPoint({ latitude: point.get("latitude"), longitude: point.get("longitude"), + height: point.get("height"), + mapWidgetCoords: point.get("mapWidgetCoords"), }); } }, - /** - * The action to perform when the mode is "draw" and the user clicks on - * the map. - */ - handleDrawClick: function () { - if (!this.mode === "draw") return - const point = this.interactions.get("clickedPosition"); - if(!point) return - this.addPoint({ - latitude: point.get("latitude"), - longitude: point.get("longitude"), - }); - }, - - /** - * The action to perform when the mode is "move" and the user clicks on - * the map. - */ - handleMoveClick: function () { - if (!this.mode === "move") return - const feature = this.interactions.get("clickedFeature"); - if (!feature) return - // TODO: Set a listener to update the point feature and coords - // when it is clicked and dragged - }, - - /** - * The action to perform when the mode is "remove" and the user clicks on - * the map. - */ - handleRemoveClick: function () { - if (!this.mode === "remove") return - const feature = this.interactions.get("clickedFeature"); - if (!feature) return - // TODO: Get the coords of the clicked feature and remove the point - // from the polygon - console.log("remove feature", feature); - }, - /** * Clears the polygon that is being drawn */ From 6ff2d852793891af9bd66f8e72854b06bdce1c03 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Fri, 13 Oct 2023 16:35:41 -0400 Subject: [PATCH 13/43] Rerun visualizers until async processes complete - In CesiumVectorData, in the new runVisualizers method, re-run in cases where visualizers are waiting for an async process to complete. - Fixes issue with Cesium Geohashes not showing up - Add some missing JSDocs to CesiumVectorData Issue #2180 --- src/js/models/maps/assets/CesiumVectorData.js | 62 +++++++++++++++++-- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/src/js/models/maps/assets/CesiumVectorData.js b/src/js/models/maps/assets/CesiumVectorData.js index e95ec7ab6..2efc337db 100644 --- a/src/js/models/maps/assets/CesiumVectorData.js +++ b/src/js/models/maps/assets/CesiumVectorData.js @@ -371,7 +371,7 @@ define([ // Suspending events while updating a large number of entities helps // performance. - cesiumModel.entities.suspendEvents(); + model.suspendEvents(); // If the asset isn't visible, just hide all entities and update the // visibility property to indicate that layer is hidden @@ -383,13 +383,20 @@ define([ this.styleEntities(entities); } - cesiumModel.entities.resumeEvents(); + model.resumeEvents(); this.runVisualizers(); } catch (e) { console.log("Failed to update CesiumVectorData model styles.", e); } }, + /** + * Run the Cesium visualizers for this asset. Visualizers render data + * associated with DataSource instances. Visualizers must be run after + * changes are made to the data or the appearance of the data. + * @since x.x.x + * @see {@link https://cesium.com/learn/cesiumjs/ref-doc/Visualizer.html} + */ runVisualizers: function () { const dataSource = this.get("cesiumModel"); const visualizers = dataSource._visualizers; @@ -403,6 +410,12 @@ define([ displayReadyNow = visualizers[x].update(time) && displayReadyNow; } this.set("displayReady", displayReadyNow); + if (!displayReadyNow) { + // If the display is not ready, try again. It means the visualizers + // are waiting for an asynchronous process to complete. + setTimeout(this.runVisualizers.bind(this), 10); + return + } this.trigger("appearanceChanged"); }, @@ -412,8 +425,12 @@ define([ * visualizers being ready. It will attempt to run the callback every * pingRate ms until the visualizers are ready, or until the maxPings is * reached. - * @param {*} callBack - * @param {*} maxPings + * @param {Function} callBack - The function to run when the visualizers + * are ready + * @param {Number} [pingRate=100] - The number of milliseconds to wait + * between pings - pings are used to check if the visualizers are ready + * @param {Number} [maxPings=30] - The maximum number of pings to wait + * before giving up */ whenVisualizersReady: function (callBack, pingRate = 100, maxPings = 30) { const model = this; @@ -432,26 +449,54 @@ define([ }, pingRate); }, + /** + * Get the Cesium EntityCollection for this asset + * @returns {Cesium.EntityCollection} The Cesium EntityCollection + * @since x.x.x + */ getEntityCollection: function () { const model = this; const dataSource = model.get("cesiumModel"); return dataSource?.entities; }, + /** + * Get the Cesium Entities for this asset + * @returns {Cesium.Entity[]} The Cesium Entities + * @since x.x.x + */ getEntities: function () { return this.getEntityCollection()?.values || []; }, + /** + * Suspend events on the Cesium EntityCollection. This will prevent + * visualizers from running until resumeEvents is called. + * @since x.x.x + */ suspendEvents: function () { const entities = this.getEntityCollection(); if (entities) entities.suspendEvents(); }, + /** + * Resume events on the Cesium EntityCollection. This will allow + * visualizers to run again. + * @since x.x.x + */ resumeEvents: function () { const entities = this.getEntityCollection(); if (entities) entities.resumeEvents(); }, + /** + * Manually an entity to the Cesium EntityCollection. + * @param {Object} entity - The ConstructorOptions with properties to pass + * to Cesium.EntityCollection.add. See + * {@link https://cesium.com/learn/cesiumjs/ref-doc/EntityCollection.html?classFilter=EntityCollection#add} + * @returns {Cesium.Entity} The Cesium Entity that was added + * @since x.x.x + */ addEntity: function (entity) { try { const entities = this.getEntityCollection(); @@ -465,6 +510,13 @@ define([ } }, + /** + * Manually remove an entity from the Cesium EntityCollection. + * @param {Cesium.Entity|string} entity - The entity or ID of the entity + * to remove + * @returns {Boolean} True if the entity was removed, false otherwise + * @since x.x.x + */ removeEntity: function (entity) { try { const entities = this.getEntities(); @@ -472,7 +524,7 @@ define([ let removed = false; // if entity is a string, remove by ID if (typeof entity === "string") { - removed = entities.removeById(entity); + removed = entities.removeById(entity); } else { // Otherwise, assume it's an entity object removed = entities.remove(entity); From 2926dec7bf9a6f5bc4d873a26b22cdeb5072e412 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Fri, 13 Oct 2023 18:52:20 -0400 Subject: [PATCH 14/43] Minor changes to draw tool UI - Style draw tool buttons - Hide edit & delete point buttons for now (not implemented) - Enable configuring hiding a layer in the layer list - Fix issue with removing entities in CesiumVectorData Issue #2180 --- src/css/map-view.css | 20 +++- src/js/models/maps/assets/CesiumVectorData.js | 2 +- src/js/models/maps/assets/MapAsset.js | 42 ++++--- src/js/views/maps/DrawToolView.js | 112 ++++++++++++------ src/js/views/maps/LayerListView.js | 7 +- 5 files changed, 127 insertions(+), 56 deletions(-) diff --git a/src/css/map-view.css b/src/css/map-view.css index ce9bddef6..d3c9439e8 100644 --- a/src/css/map-view.css +++ b/src/css/map-view.css @@ -120,6 +120,10 @@ font-size: 1rem; } +.map-view__button--active { + background-color: var(--map-col-highlight); +} + /* ---- BADGE ---- */ .map-view__badge{ @@ -953,4 +957,18 @@ other class: .ui-slider-range */ box-shadow: var(--map-shadow-md); /* imagery appears lighter on the map */ filter: brightness(1.75); -} \ No newline at end of file +} + +/***************************************************************************************** + * + * Draw Tool + * + * Panel for drawing polygons in the map + * + */ + +.draw-tool { + display: grid; + grid-auto-rows: min-content; + grid-gap: 1rem; +} diff --git a/src/js/models/maps/assets/CesiumVectorData.js b/src/js/models/maps/assets/CesiumVectorData.js index 2efc337db..69591079a 100644 --- a/src/js/models/maps/assets/CesiumVectorData.js +++ b/src/js/models/maps/assets/CesiumVectorData.js @@ -519,7 +519,7 @@ define([ */ removeEntity: function (entity) { try { - const entities = this.getEntities(); + const entities = this.getEntityCollection(); if (!entities) return false; let removed = false; // if entity is a string, remove by ID diff --git a/src/js/models/maps/assets/MapAsset.js b/src/js/models/maps/assets/MapAsset.js index e84fd5320..e02f5391c 100644 --- a/src/js/models/maps/assets/MapAsset.js +++ b/src/js/models/maps/assets/MapAsset.js @@ -84,6 +84,8 @@ define([ * 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. + * @property {Boolean} [hideInLayerList = false] Set to true to hide this asset + * from the layer list. */ defaults: function () { return { @@ -105,6 +107,7 @@ define([ notification: {}, status: null, statusDetails: null, + hideInLayerList: false, }; }, @@ -171,6 +174,8 @@ define([ * @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 {boolean} [hideInLayerList] - Set to true to hide this asset from the + * layer list. */ /** @@ -280,24 +285,25 @@ define([ */ /** - * 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. - */ + * 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; diff --git a/src/js/views/maps/DrawToolView.js b/src/js/views/maps/DrawToolView.js index 768f4499c..9a93bbda8 100644 --- a/src/js/views/maps/DrawToolView.js +++ b/src/js/views/maps/DrawToolView.js @@ -38,9 +38,28 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect */ buttonClass: "map-view__button", + /** + * Class to use for the active button + * @type {string} + */ + buttonClassActive: "map-view__button--active", + + /** + * @typedef {Object} DrawToolButtonOptions + * @property {string} name - The name of the button. This should be the + * same as the mode that the button will activate (if the button is + * supposed to activate a mode). + * @property {string} label - The label to display on the button. + * @property {string} icon - The name of the icon to display on the + * button. + * @property {string} [method] - The name of the method to call when the + * button is clicked. If this is not provided, the button will toggle the + * mode of the draw tool. + */ + /** * The buttons to display in the toolbar and their corresponding actions. - * TODO: Finish documenting this when more finalized. + * @type {DrawToolButtonOptions[]} */ buttons: [ { @@ -48,21 +67,21 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect label: "Draw Polygon", icon: "pencil", }, - { - name: "move", - label: "Move Point", - icon: "move", - }, - { - name: "remove", - label: "Remove Point", - icon: "eraser", - }, + // { + // name: "move", + // label: "Move Point", + // icon: "move", + // }, + // { + // name: "remove", + // label: "Remove Point", + // icon: "eraser", + // }, { name: "clear", label: "Clear Polygon", icon: "trash", - method: "clearPoints", + method: "reset", }, { name: "save", @@ -72,6 +91,12 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect }, ], + /** + * The buttons that have been rendered in the toolbar. Formatted as an + * object with the button name as the key and the button element as the + * value. + * @type {Object} + */ buttonEls: {}, /** @@ -120,7 +145,7 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect * between 0 and 1. * @type {number} */ - opacity: 0.8, + opacity: 0.5, /** * Initializes the DrawTool @@ -134,7 +159,7 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect initialize: function (options) { this.mapModel = options.model; if (!this.mapModel) { - this.handleNoMapModel(); + console.warn("No map model was provided."); return; } // Add models & collections and add interactions, layer, connector, @@ -166,7 +191,7 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect type: "CustomDataSource", label: "Your Polygon", description: "The polygon that you are drawing on the map", - hideInLayerList: true, // TODO: Hide in LayerList, doc in mapConfig + hideInLayerList: true, outlineColor: this.color, opacity: this.opacity, colorPalette: { @@ -217,15 +242,24 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect this.points?.reset(null); }, + /** + * Resets the draw tool to its initial state. + */ + reset: function () { + this.setMode(false); + this.clearPoints(); + this.removeClickListeners(); + }, + /** * Removes the polygon object from the map - * TODO: Test this */ removeLayer: function () { if (!this.mapModel || !this.layer) return; - // TODO - this.connector.disconnect(); - this.connector.set("vectorLayer", null); + this.polygonConnector.disconnect(); + this.polygonConnector.set("vectorLayer", null); + this.pointsConnector.disconnect(); + this.pointsConnector.set("vectorLayer", null); this.mapModel.removeAsset(this.layer); }, @@ -234,17 +268,23 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect * @returns {DrawTool} Returns the view */ render: function () { + if(!this.mapModel) { + this.showError("No map model was provided."); + return this; + } this.renderToolbar(); return this; }, /** - * What to do when this view doesn't have a map view to draw on + * Show an error message to the user if the map model is not available + * or any other error occurs. + * @param {string} [message] - The error message to show to the user. */ - handleNoMapModel: function () { - console.warn("No map model provided to DrawTool"); - // TODO: Add a message to the view to let the user know that the draw - // tool is not available + showError: function (message) { + const str = `` + + ` The draw tool is not available. ${message}`; + this.el.innerHTML = str; }, /** @@ -274,13 +314,14 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect /** * Sends the polygon coordinates to a callback function to do something * with them. - * TODO: This is a WIP. + * @param {Function} callback - The callback function to send the polygon + * coordinates to. */ - save: function () { + save: function (callback) { this.setMode(false); - this.removeClickListeners(); - console.log(this.points.toJSON()); - // TODO: Call a callback function to save the polygon + if(callback && typeof callback === "function") { + callback(this.points.toJSON()); + } }, /** @@ -321,7 +362,7 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect const buttonEl = this.buttonEls[buttonName + "Button"]; if(!buttonEl) return; this.resetButtonStyles(); - buttonEl.style.backgroundColor = "blue"; // TODO - create active style + buttonEl.classList.add(this.buttonClassActive); }, /** @@ -331,7 +372,10 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect resetButtonStyles: function () { // Iterate through the buttonEls object and reset the styles for (const button in this.buttonEls) { - this.buttonEls[button].style.backgroundColor = "grey"; // TODO - create default style + if (this.buttonEls.hasOwnProperty(button)) { + const buttonEl = this.buttonEls[button]; + buttonEl.classList.remove(this.buttonClassActive); + } } }, @@ -396,10 +440,10 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect */ handleClick: function (throttle = 50) { // Prevent double clicks - if (this.blockClick) return; - this.blockClick = true; + if (this.clickActionBlocked) return; + this.clickActionBlocked = true; setTimeout(() => { - this.blockClick = false; + this.clickActionBlocked = false; }, throttle); // Add the point to the polygon if (this.mode === "draw") { diff --git a/src/js/views/maps/LayerListView.js b/src/js/views/maps/LayerListView.js index f53e300c4..3cca479b7 100644 --- a/src/js/views/maps/LayerListView.js +++ b/src/js/views/maps/LayerListView.js @@ -107,8 +107,7 @@ define( setListeners: function () { try { if (this.collection) { - this.listenTo(this.collection, 'add', this.render); - this.listenTo(this.collection, 'remove', this.render); + this.listenTo(this.collection, 'add remove reset', this.render); } } catch (e) { console.log('Failed to set listeners:', e); @@ -138,6 +137,10 @@ define( // Render a layer item for each layer in the collection this.collection.forEach(function (layerModel) { + if(layerModel.get('hideInLayerList') === true){ + // skip this layer + return + } var layerItem = new LayerItemView({ model: layerModel }) From 516cd604854c23964a45cd873d062fa0107bec4d Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Fri, 13 Oct 2023 19:08:55 -0400 Subject: [PATCH 15/43] Fix opacity of draw tool polygon Issue #2180 --- src/js/views/maps/DrawToolView.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/views/maps/DrawToolView.js b/src/js/views/maps/DrawToolView.js index 9a93bbda8..8567538e7 100644 --- a/src/js/views/maps/DrawToolView.js +++ b/src/js/views/maps/DrawToolView.js @@ -145,7 +145,7 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect * between 0 and 1. * @type {number} */ - opacity: 0.5, + opacity: 0.3, /** * Initializes the DrawTool @@ -193,6 +193,7 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect description: "The polygon that you are drawing on the map", hideInLayerList: true, outlineColor: this.color, + highlightColor: this.color, opacity: this.opacity, colorPalette: { colors: [ From 7f4103b39ed4f84b27e4e4440d109c8f01baf811 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Mon, 16 Oct 2023 12:23:39 -0400 Subject: [PATCH 16/43] Update polygon on draw rather than add new one Add an ID to the polygon created when drawing on map so that it is updated when new points are added rather than creating a new one Issue #2180 --- src/js/models/connectors/GeoPoints-Cesium.js | 8 ++++++-- src/js/models/connectors/GeoPoints-CesiumPolygon.js | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/js/models/connectors/GeoPoints-Cesium.js b/src/js/models/connectors/GeoPoints-Cesium.js index 3fc55b160..f224c6d31 100644 --- a/src/js/models/connectors/GeoPoints-Cesium.js +++ b/src/js/models/connectors/GeoPoints-Cesium.js @@ -108,9 +108,12 @@ define([ */ connect: function () { try { + this.disconnect(); + // Listen for changes to the points collection and update the layer - let geoPoints = this.get("geoPoints"); + const geoPoints = this.get("geoPoints"); const events = ["update", "reset"]; + events.forEach((eventName) => { this.listenTo(geoPoints, eventName, function (...args) { this.handleCollectionChange(eventName, ...args); @@ -141,7 +144,8 @@ define([ * Stop listening for changes to the Points collection. */ disconnect: function () { - this.stopListening(this.get("geoPoints")); + const geoPoints = this.get("geoPoints"); + if (geoPoints) this.stopListening(geoPoints); this.set("isConnected", false); }, diff --git a/src/js/models/connectors/GeoPoints-CesiumPolygon.js b/src/js/models/connectors/GeoPoints-CesiumPolygon.js index 5d726d745..7a27854bd 100644 --- a/src/js/models/connectors/GeoPoints-CesiumPolygon.js +++ b/src/js/models/connectors/GeoPoints-CesiumPolygon.js @@ -48,9 +48,11 @@ define(["cesium", "models/connectors/GeoPoints-Cesium"], function ( * the CesiumVectorData model. */ addPolygon: function () { + const id = this.cid; const layer = this.get("layer") || this.setVectorLayer(); const geoPoints = this.get("geoPoints") || this.setPoints(); return layer.addEntity({ + id: id, // If entity with this ID already exists, it will be updated polygon: { height: null, // <- clamp to ground hierarchy: new Cesium.CallbackProperty(() => { From b245cca1ba6b17f5e60627a9e8098fd2a8d6f06a Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Tue, 17 Oct 2023 15:53:59 -0400 Subject: [PATCH 17/43] Fix geo tests & minor bugs found while testing - Set up new geo tests Issues #2180 and #2189 --- src/js/models/filters/SpatialFilter.js | 23 ++++++++- src/js/models/maps/Geohash.js | 4 +- src/js/models/maps/assets/CesiumGeohash.js | 2 +- test/config/tests.json | 6 +++ .../unit/collections/maps/GeoPoints.spec.js | 21 +++++++++ .../unit/collections/maps/Geohashes.spec.js | 47 ------------------- .../models/connectors/Filters-Map.spec.js | 4 +- .../connectors/GeoPoints-Cesium.spec.js | 21 +++++++++ .../connectors/GeoPoints-CesiumPoints.spec.js | 21 +++++++++ .../GeoPoints-CesiumPolygon.spec.js | 21 +++++++++ .../unit/models/maps/GeoBoundingBox.spec.js | 21 +++++++++ .../unit/models/maps/GeoUtilities.spec.js | 21 +++++++++ .../models/maps/assets/CesiumGeohash.spec.js | 9 ++-- .../models/maps/assets/CesiumImagery.spec.js | 6 +-- 14 files changed, 165 insertions(+), 62 deletions(-) create mode 100644 test/js/specs/unit/collections/maps/GeoPoints.spec.js create mode 100644 test/js/specs/unit/models/connectors/GeoPoints-Cesium.spec.js create mode 100644 test/js/specs/unit/models/connectors/GeoPoints-CesiumPoints.spec.js create mode 100644 test/js/specs/unit/models/connectors/GeoPoints-CesiumPolygon.spec.js create mode 100644 test/js/specs/unit/models/maps/GeoBoundingBox.spec.js create mode 100644 test/js/specs/unit/models/maps/GeoUtilities.spec.js diff --git a/src/js/models/filters/SpatialFilter.js b/src/js/models/filters/SpatialFilter.js index 6d2e9719e..c49573c52 100644 --- a/src/js/models/filters/SpatialFilter.js +++ b/src/js/models/filters/SpatialFilter.js @@ -101,15 +101,25 @@ define([ } }, + /** + * Remove the listeners. + * @since x.x.x + */ + removeListeners: function () { + const extentEvents = + "change:height change:north change:south change:east change:west"; + this.stopListening(this, extentEvents); + }, + /** * Set a listener that updates the filter when the coordinates & height * change * @since 2.25.0 */ setListeners: function () { + this.removeListeners(); const extentEvents = "change:height change:north change:south change:east change:west"; - this.stopListening(this, extentEvents); this.listenTo(this, extentEvents, this.updateFilterFromExtent); }, @@ -318,7 +328,13 @@ define([ * @inheritdoc */ resetValue: function () { - const df = this.defaults(); + // Need to stop listeners because otherwise changing the coords will + // update the filter values. This sometimes updates the values *after* + // the values are reset, preventing the reset from working. + this.removeListeners(); + + let df = this.defaults(); + this.set({ values: df.values, east: df.east, @@ -327,6 +343,9 @@ define([ south: df.south, height: df.height, }); + + // Reset the listeners + this.setListeners(); }, } ); diff --git a/src/js/models/maps/Geohash.js b/src/js/models/maps/Geohash.js index 4f9e865a2..cb213dc65 100644 --- a/src/js/models/maps/Geohash.js +++ b/src/js/models/maps/Geohash.js @@ -316,9 +316,9 @@ define([ }, properties: properties, }; - if (label) { + if (label && (properties[label] || properties[label] === 0)) { (feature["label"] = { - text: properties[label].toString(), + text: properties[label]?.toString(), show: true, fillColor: { rgba: [255, 255, 255, 255], diff --git a/src/js/models/maps/assets/CesiumGeohash.js b/src/js/models/maps/assets/CesiumGeohash.js index 499a716f5..ed4c0a4b6 100644 --- a/src/js/models/maps/assets/CesiumGeohash.js +++ b/src/js/models/maps/assets/CesiumGeohash.js @@ -154,7 +154,7 @@ define([ const limit = this.get("maxGeoHashes"); const geohashes = this.get("geohashes") const area = this.getViewExtent().getArea(); - return this.get("geohashes").getMaxPrecision(area, limit); + return geohashes.getMaxPrecision(area, limit); }, /** diff --git a/test/config/tests.json b/test/config/tests.json index a668b5fd9..e45233660 100644 --- a/test/config/tests.json +++ b/test/config/tests.json @@ -1,5 +1,11 @@ { "unit": [ + "./js/specs/unit/collections/maps/GeoPoints.spec.js", + "./js/specs/unit/models/connectors/GeoPoints-Cesium.spec.js", + "./js/specs/unit/models/connectors/GeoPoints-CesiumPoints.spec.js", + "./js/specs/unit/models/connectors/GeoPoints-CesiumPolygon.spec.js", + "./js/specs/unit/models/maps/GeoBoundingBox.spec.js", + "./js/specs/unit/models/maps/GeoUtilities.spec.js", "./js/specs/unit/collections/SolrResults.spec.js", "./js/specs/unit/models/Search.spec.js", "./js/specs/unit/models/filters/Filter.spec.js", diff --git a/test/js/specs/unit/collections/maps/GeoPoints.spec.js b/test/js/specs/unit/collections/maps/GeoPoints.spec.js new file mode 100644 index 000000000..17f5feba5 --- /dev/null +++ b/test/js/specs/unit/collections/maps/GeoPoints.spec.js @@ -0,0 +1,21 @@ +define([ + "../../../../../../../../src/js/collections/maps/GeoPoints", +], function (GeoPoints) { + // Configure the Chai assertion library + var should = chai.should(); + var expect = chai.expect; + + describe("GeoPoints Test Suite", function () { + /* Set up */ + beforeEach(function () {}); + + /* Tear down */ + afterEach(function () {}); + + describe("Initialization", function () { + it("should create a GeoPoints instance", function () { + new GeoPoints().should.be.instanceof(GeoPoints); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/js/specs/unit/collections/maps/Geohashes.spec.js b/test/js/specs/unit/collections/maps/Geohashes.spec.js index e711ed40b..63d1d82f6 100644 --- a/test/js/specs/unit/collections/maps/Geohashes.spec.js +++ b/test/js/specs/unit/collections/maps/Geohashes.spec.js @@ -58,44 +58,9 @@ define(["../../../../../../../../src/js/collections/maps/Geohashes"], function ( .validatePrecision([1, 2, 3]) .should.deep.equal([1, 2, 3]); }); - - it("should validate a valid bounding box", function () { - const bounds = { north: 80, south: -80, east: 170, west: 160 }; - this.geohashes.boundsAreValid(bounds).should.be.true; - }); - - it("should invalidate a bounding box with invalid bounds", function () { - const bounds = { north: 80, south: -80, east: 170, west: 190 }; - this.geohashes.boundsAreValid(bounds).should.be.false; - }); - - it("should invalidate a bounding box with missing bounds", function () { - const bounds = { north: 80, south: -80, east: 170 }; - this.geohashes.boundsAreValid(bounds).should.be.false; - }); - - it("should invalidate a bounding box with non-number bounds", function () { - const bounds = { north: 80, south: -80, east: 170, west: "west" }; - this.geohashes.boundsAreValid(bounds).should.be.false; - }); }); describe("Bounds", function () { - it("should split a bounding box that crosses the prime meridian", function () { - const bounds = { north: 80, south: -80, east: -170, west: 170 }; - const expected = [ - { north: 80, south: -80, east: 180, west: 170 }, - { north: 80, south: -80, east: -170, west: -180 }, - ]; - this.geohashes.splitBoundingBox(bounds).should.deep.equal(expected); - }); - - it("should not split a bounding box that does not cross the prime meridian", function () { - const bounds = { north: 80, south: -80, east: 170, west: 160 }; - const expected = [{ north: 80, south: -80, east: 170, west: 160 }]; - this.geohashes.splitBoundingBox(bounds).should.deep.equal(expected); - }); - it("should get the area of a geohash tile", function () { const precision = 5; const expected = 0.0019311904907226562; @@ -117,18 +82,6 @@ define(["../../../../../../../../src/js/collections/maps/Geohashes"], function ( .getGeohashAreas(minPrecision, maxPrecision) .should.deep.equal(expected); }); - - it("should get the area of the world", function () { - const bounds = { north: 90, south: -90, east: 180, west: -180 }; - const expected = 360 * 180; - this.geohashes.getBoundingBoxArea(bounds).should.equal(expected); - }); - - it("should get the area of a small bounding box", function () { - const bounds = { north: 45, south: 44, east: 45, west: 44 }; - const expected = 1; - this.geohashes.getBoundingBoxArea(bounds).should.equal(expected); - }); }); describe("Precision", function () { diff --git a/test/js/specs/unit/models/connectors/Filters-Map.spec.js b/test/js/specs/unit/models/connectors/Filters-Map.spec.js index bd14a86dc..9c97a3475 100644 --- a/test/js/specs/unit/models/connectors/Filters-Map.spec.js +++ b/test/js/specs/unit/models/connectors/Filters-Map.spec.js @@ -52,7 +52,7 @@ define([ const map = this.filtersMap.get("map"); const spatialFilters = this.filtersMap.get("spatialFilters"); const extent = { north: 1, south: 2, east: 3, west: 4 }; - map.set("currentViewExtent", extent); + map.get("interactions").setViewExtent(extent); this.filtersMap.updateSpatialFilters(); spatialFilters[0].get("north").should.equal(1); spatialFilters[0].get("south").should.equal(2); @@ -80,6 +80,6 @@ define([ const spatialFilters = this.filtersMap.get("spatialFilters"); spatialFilters[0].get("values").should.deep.equal([]); }); - }); + }); }); }); diff --git a/test/js/specs/unit/models/connectors/GeoPoints-Cesium.spec.js b/test/js/specs/unit/models/connectors/GeoPoints-Cesium.spec.js new file mode 100644 index 000000000..5901a1c82 --- /dev/null +++ b/test/js/specs/unit/models/connectors/GeoPoints-Cesium.spec.js @@ -0,0 +1,21 @@ +define([ + "../../../../../../../../src/js/models/connectors/GeoPoints-Cesium", +], function (GeoPointsCesium) { + // Configure the Chai assertion library + var should = chai.should(); + var expect = chai.expect; + + describe("GeoPointsCesium Test Suite", function () { + /* Set up */ + beforeEach(function () {}); + + /* Tear down */ + afterEach(function () {}); + + describe("Initialization", function () { + it("should create a GeoPointsCesium instance", function () { + new GeoPointsCesium().should.be.instanceof(GeoPointsCesium); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/js/specs/unit/models/connectors/GeoPoints-CesiumPoints.spec.js b/test/js/specs/unit/models/connectors/GeoPoints-CesiumPoints.spec.js new file mode 100644 index 000000000..b948dd58b --- /dev/null +++ b/test/js/specs/unit/models/connectors/GeoPoints-CesiumPoints.spec.js @@ -0,0 +1,21 @@ +define([ + "../../../../../../../../src/js/models/connectors/GeoPoints-CesiumPoints", +], function (GeoPointsCesiumPoints) { + // Configure the Chai assertion library + var should = chai.should(); + var expect = chai.expect; + + describe("GeoPointsCesiumPoints Test Suite", function () { + /* Set up */ + beforeEach(function () {}); + + /* Tear down */ + afterEach(function () {}); + + describe("Initialization", function () { + it("should create a GeoPointsCesiumPoints instance", function () { + new GeoPointsCesiumPoints().should.be.instanceof(GeoPointsCesiumPoints); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/js/specs/unit/models/connectors/GeoPoints-CesiumPolygon.spec.js b/test/js/specs/unit/models/connectors/GeoPoints-CesiumPolygon.spec.js new file mode 100644 index 000000000..f5ae3e006 --- /dev/null +++ b/test/js/specs/unit/models/connectors/GeoPoints-CesiumPolygon.spec.js @@ -0,0 +1,21 @@ +define([ + "../../../../../../../../src/js/models/connectors/GeoPoints-CesiumPolygon", +], function (GeoPointsCesiumPolygon) { + // Configure the Chai assertion library + var should = chai.should(); + var expect = chai.expect; + + describe("GeoPointsCesiumPolygon Test Suite", function () { + /* Set up */ + beforeEach(function () {}); + + /* Tear down */ + afterEach(function () {}); + + describe("Initialization", function () { + it("should create a GeoPointsCesiumPolygon instance", function () { + new GeoPointsCesiumPolygon().should.be.instanceof(GeoPointsCesiumPolygon); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/js/specs/unit/models/maps/GeoBoundingBox.spec.js b/test/js/specs/unit/models/maps/GeoBoundingBox.spec.js new file mode 100644 index 000000000..d9553d8d2 --- /dev/null +++ b/test/js/specs/unit/models/maps/GeoBoundingBox.spec.js @@ -0,0 +1,21 @@ +define([ + "../../../../../../../../src/js/models/maps/GeoBoundingBox", +], function (GeoBoundingBox) { + // Configure the Chai assertion library + var should = chai.should(); + var expect = chai.expect; + + describe("GeoBoundingBox Test Suite", function () { + /* Set up */ + beforeEach(function () {}); + + /* Tear down */ + afterEach(function () {}); + + describe("Initialization", function () { + it("should create a GeoBoundingBox instance", function () { + new GeoBoundingBox().should.be.instanceof(GeoBoundingBox); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/js/specs/unit/models/maps/GeoUtilities.spec.js b/test/js/specs/unit/models/maps/GeoUtilities.spec.js new file mode 100644 index 000000000..a643471f4 --- /dev/null +++ b/test/js/specs/unit/models/maps/GeoUtilities.spec.js @@ -0,0 +1,21 @@ +define([ + "../../../../../../../../src/js/models/maps/GeoUtilities", +], function (GeoUtilities) { + // Configure the Chai assertion library + var should = chai.should(); + var expect = chai.expect; + + describe("GeoUtilities Test Suite", function () { + /* Set up */ + beforeEach(function () {}); + + /* Tear down */ + afterEach(function () {}); + + describe("Initialization", function () { + it("should create a GeoUtilities instance", function () { + new GeoUtilities().should.be.instanceof(GeoUtilities); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/js/specs/unit/models/maps/assets/CesiumGeohash.spec.js b/test/js/specs/unit/models/maps/assets/CesiumGeohash.spec.js index 9eab3db57..5cf58e9dd 100644 --- a/test/js/specs/unit/models/maps/assets/CesiumGeohash.spec.js +++ b/test/js/specs/unit/models/maps/assets/CesiumGeohash.spec.js @@ -1,8 +1,7 @@ define([ "../../../../../../../../src/js/models/maps/assets/CesiumGeohash", - "../../../../../../../../src/js/collections/maps/Geohashes", "../../../../../../../../src/js/models/maps/Map", -], function (CesiumGeohash, Geohashes) { +], function (CesiumGeohash, MapModel) { // Configure the Chai assertion library var should = chai.should(); var expect = chai.expect; @@ -10,7 +9,7 @@ define([ describe("CesiumGeohash Test Suite", function () { /* Set up */ beforeEach(function () { - this.map = new Map(); + this.map = new MapModel(); this.model = new CesiumGeohash(); this.model.set("mapModel", this.map); }); @@ -83,7 +82,9 @@ define([ it("should get the precision", function () { this.model.replaceGeohashes(); this.model.set("maxGeoHashes", 32); - this.map.set("currentViewExtent", { + console.log(this.map.attributes); + console.log(this.map.get("interactions")); + this.map.get("interactions").setViewExtent({ north: 90, south: -90, east: 180, diff --git a/test/js/specs/unit/models/maps/assets/CesiumImagery.spec.js b/test/js/specs/unit/models/maps/assets/CesiumImagery.spec.js index 2ddc8931e..8b6bf00b6 100644 --- a/test/js/specs/unit/models/maps/assets/CesiumImagery.spec.js +++ b/test/js/specs/unit/models/maps/assets/CesiumImagery.spec.js @@ -38,11 +38,9 @@ define([ describe("Creating the Cesium Model", function () { it("should convert list of degrees to a Cesium rectangle", function (done) { - const expectedRect = Cesium.Rectangle.fromDegrees(...boundingBox) imagery.whenReady().then(function (model) { - const rect = model.get("cesiumOptions").rectangle - const rectsEqual = Cesium.Rectangle.equals(rect, expectedRect) - rectsEqual.should.be.true + const rect = model.get("cesiumModel").rectangle + expect(rect.constructor.name).to.equal("Rectangle") done() }, function (error) { done(error) From f7d6ddfc87e234cf8a4cb6ca2ac91e32496b5267 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 18 Oct 2023 16:27:38 -0400 Subject: [PATCH 18/43] Fix Cesium datasource issues & minor draw tool bug - Properly switch the maps "click action" back to original setting after draw tool turned off - Fixed delayed rendering of datasources after update (esp. Geohashes) - Fix zooming to features on click (issue with getting bounding sphere of a feature) Issues #2180 and #2189 --- src/js/models/maps/assets/CesiumVectorData.js | 18 +-- src/js/views/maps/CesiumWidgetView.js | 121 ++++++++++++++---- src/js/views/maps/DrawToolView.js | 3 +- 3 files changed, 101 insertions(+), 41 deletions(-) diff --git a/src/js/models/maps/assets/CesiumVectorData.js b/src/js/models/maps/assets/CesiumVectorData.js index 69591079a..9eaf66bf2 100644 --- a/src/js/models/maps/assets/CesiumVectorData.js +++ b/src/js/models/maps/assets/CesiumVectorData.js @@ -405,16 +405,14 @@ define([ return; } const time = Cesium.JulianDate.now(); - let displayReadyNow = dataSource.update(time); + let displayReadyNow = true for (let x = 0; x < visualizers.length; x++) { displayReadyNow = visualizers[x].update(time) && displayReadyNow; } - this.set("displayReady", displayReadyNow); if (!displayReadyNow) { - // If the display is not ready, try again. It means the visualizers - // are waiting for an asynchronous process to complete. - setTimeout(this.runVisualizers.bind(this), 10); - return + setTimeout(this.runVisualizers.bind(this), 300); + } else { + this.set("displayReady", true); } this.trigger("appearanceChanged"); }, @@ -783,12 +781,8 @@ define([ } return false; }) - .catch(function (error) { - console.log( - "Failed to get the bounding sphere for a CesiumVectorData model" + - ". Error details: " + - error - ); + .catch(function (e) { + console.log("Error getting bounding sphere.", e); }); }, } diff --git a/src/js/views/maps/CesiumWidgetView.js b/src/js/views/maps/CesiumWidgetView.js index 51b32a6c9..f168dda1c 100644 --- a/src/js/views/maps/CesiumWidgetView.js +++ b/src/js/views/maps/CesiumWidgetView.js @@ -280,9 +280,6 @@ define([ if (view.zoomTarget) { view.completeFlight(view.zoomTarget, view.zoomOptions); } - // The dataSourceDisplay must be set to 'ready' to get bounding - // spheres for dataSources - view.dataSourceDisplay._ready = true; } catch (e) { console.log("Error calling post render functions:", e); } @@ -290,6 +287,8 @@ define([ /** * Run the update method and all visualizers for each data source. + * @return {boolean} Returns true if all data sources are ready to be + * displayed. * @since x.x.x */ updateAllDataSources: function () { @@ -299,13 +298,17 @@ define([ return; } const time = view.clock.currentTime; - dataSources.forEach(function (dataSource) { + let displayReady = true; + for (let i = 0; i < dataSources.length; i++) { + const dataSource = dataSources.get(i); dataSource.update(view.clock.currentTime); // for each visualizer, update it dataSource._visualizers.forEach(function (visualizer) { - visualizer.update(time); + displayReady = displayReady && visualizer.update(time); }); - }); + } + view.dataSourceDisplay._ready = displayReady; + return displayReady; }, /** @@ -370,11 +373,11 @@ define([ // Listen for addition or removal of layers TODO: Add similar listeners // for terrain - if(layers){ + if (layers) { view.stopListening(layers); view.listenTo(layers, "add", view.addAsset); view.listenTo(layers, "remove", view.removeAsset); - + // Each layer fires 'appearanceChanged' whenever the color, opacity, // etc. has been updated. Re-render the scene when this happens. view.listenTo(layers, "appearanceChanged", view.requestRender); @@ -595,7 +598,7 @@ define([ const layersReverse = layers.last(layers.length).reverse(); layersReverse.forEach(function (layer) { view.addAsset(layer); - }) + }); } // The Cesium Widget will support just one terrain option to start. @@ -662,19 +665,18 @@ define([ */ completeFlight: function (target, options) { try { - const view = this; - if (typeof options !== "object") options = {}; // A target is required - if (!target) { - return; - } + if (!target) return; + + const view = this; + if (typeof options !== "object") options = {}; + view.resetZoomTarget(); // If the target is a Bounding Sphere, use the camera's built-in // function if (target instanceof Cesium.BoundingSphere) { view.camera.flyToBoundingSphere(target, options); - view.resetZoomTarget(); return; } @@ -713,27 +715,31 @@ define([ // If the object saved in the Feature is an Entity, then this // function will get the bounding sphere for the entity on the next // run. - setTimeout(() => { - // TODO check if needed - view.flyTo(target.get("featureObject"), options); - }, 0); + // check if the layer is displayReady + const layer = target.get("mapAsset"); + const displayReady = layer.get("displayReady"); + if (!displayReady) { + // Must wait for layer to be rendered in via the dataSourceDisplay + // before we can get the bounding sphere for the feature. + view.listenToOnce(layer, "change:displayReady", function () { + view.flyTo(target, options); + }); + return + } + view.flyTo(target.get("featureObject"), options); return; } // If the target is a Cesium Entity, then get the bounding sphere for // the entity and call this function again. const entity = target instanceof Cesium.Entity ? target : target.id; + if (entity instanceof Cesium.Entity) { - let entityBoundingSphere = new Cesium.BoundingSphere(); - view.dataSourceDisplay.getBoundingSphere( - entity, - false, - entityBoundingSphere - ); - setTimeout(() => { - // TODO check if needed + + view.dataSourceDisplay._ready = true + view.getBoundingSphereFromEntity(entity).then(function (entityBoundingSphere) { view.flyTo(entityBoundingSphere, options); - }, 0); + }); return; } @@ -770,6 +776,65 @@ define([ } }, + getBoundingSphereFromEntity: function (entity) { + const view = this + const entityBoundingSphere = new Cesium.BoundingSphere(); + const readyState = Cesium.BoundingSphereState.DONE; + function getBS() { + return view.dataSourceDisplay.getBoundingSphere( + entity, + false, + entityBoundingSphere + ); + } + // Return a promise that resolves to bounding box when it's ready. + // Keep running getBS at intervals until it's ready. + return new Promise(function (resolve, reject) { + let attempts = 0; + const maxAttempts = 100; + const interval = setInterval(function () { + attempts++; + const state = getBS(); + if (state !== readyState) { + // Search for the entity again in case it was removed and + // re-added to the data source display. + entity = view.getEntityById(entity.id, entity.entityCollection); + if(!entity) { + clearInterval(interval); + reject("Failed to get bounding sphere for entity, entity not found."); + } + + } else { + clearInterval(interval); + resolve(entityBoundingSphere); + } + if (attempts >= maxAttempts) { + clearInterval(interval); + reject("Failed to get bounding sphere for entity."); + } + }, 100); + }) + }, + + /** + * Search an entity collection for an entity with a given id. + * @param {string} id - The id of the entity to find. + * @param {Cesium.EntityCollection} collection - The collection to search. + * @returns {Cesium.Entity} The entity with the given id, or null if no + * entity with that id exists in the collection. + * @since x.x.x + */ + getEntityById: function (id, collection) { + const entities = collection.values; + for (let i = 0; i < entities.length; i++) { + const entity = entities[i]; + if (entity.id === id) { + return entity; + } + } + return null; + }, + resetZoomTarget: function () { const view = this; view.zoomTarget = null; diff --git a/src/js/views/maps/DrawToolView.js b/src/js/views/maps/DrawToolView.js index 8567538e7..368117680 100644 --- a/src/js/views/maps/DrawToolView.js +++ b/src/js/views/maps/DrawToolView.js @@ -386,12 +386,13 @@ define(["backbone", "models/connectors/GeoPoints-CesiumPolygon", "models/connect */ removeClickListeners: function () { const handler = this.clickHandler; + const originalAction = this.originalAction; if (handler) { handler.stopListening(); handler.clear(); this.clickHandler = null; } - this.mapModel.set("clickFeatureAction", this.originalClickAction); + this.mapModel.set("clickFeatureAction", originalAction); this.listeningForClicks = false; }, From e24b8c67341eacfeac3de656baf70e8143a8f2c7 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 18 Oct 2023 17:49:14 -0400 Subject: [PATCH 19/43] Complete unit tests for new geo models & coll'ns Issues #2180 and #2189 --- src/js/collections/maps/GeoPoints.js | 11 +- test/config/tests.json | 12 +- .../unit/collections/maps/GeoPoints.spec.js | 387 +++++++++++++++++- .../connectors/GeoPoints-Cesium.spec.js | 29 +- .../connectors/GeoPoints-CesiumPoints.spec.js | 28 +- .../GeoPoints-CesiumPolygon.spec.js | 9 +- .../unit/models/maps/GeoBoundingBox.spec.js | 92 ++++- .../unit/models/maps/GeoUtilities.spec.js | 12 + 8 files changed, 556 insertions(+), 24 deletions(-) diff --git a/src/js/collections/maps/GeoPoints.js b/src/js/collections/maps/GeoPoints.js index d225ee0be..566a33ef2 100644 --- a/src/js/collections/maps/GeoPoints.js +++ b/src/js/collections/maps/GeoPoints.js @@ -71,9 +71,12 @@ define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) { */ removePoint(indexOrPoint) { if (typeof indexOrPoint === "number") { - this.removePointByIndex(indexOrPoint); + return this.removePointByIndex(indexOrPoint); } else if (Array.isArray(indexOrPoint)) { - this.removePointByAttr(indexOrPoint); + return this.removePointByAttr(indexOrPoint); + } else { + // try just removing the point + return this.remove(indexOrPoint); } }, @@ -157,10 +160,10 @@ define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) { if (!forceAsPolygon && geometryType === "Polygon" && this.length < 3) { geometryType = this.length === 1 ? "Point" : "LineString"; } - const czml = [this.getCZMLHeader()]; + let czml = [this.getCZMLHeader()]; switch (geometryType) { case "Point": - czml.concat(this.toCZMLPoints()); + czml = czml.concat(this.toCZMLPoints()); break; case "LineString": czml.push(this.getCZMLLineString()); diff --git a/test/config/tests.json b/test/config/tests.json index e45233660..712ea5fa6 100644 --- a/test/config/tests.json +++ b/test/config/tests.json @@ -1,11 +1,5 @@ { "unit": [ - "./js/specs/unit/collections/maps/GeoPoints.spec.js", - "./js/specs/unit/models/connectors/GeoPoints-Cesium.spec.js", - "./js/specs/unit/models/connectors/GeoPoints-CesiumPoints.spec.js", - "./js/specs/unit/models/connectors/GeoPoints-CesiumPolygon.spec.js", - "./js/specs/unit/models/maps/GeoBoundingBox.spec.js", - "./js/specs/unit/models/maps/GeoUtilities.spec.js", "./js/specs/unit/collections/SolrResults.spec.js", "./js/specs/unit/models/Search.spec.js", "./js/specs/unit/models/filters/Filter.spec.js", @@ -31,6 +25,12 @@ "./js/specs/unit/collections/maps/Geohashes.spec.js", "./js/specs/unit/models/maps/GeoPoint.spec.js", "./js/specs/unit/models/maps/GeoScale.spec.js", + "./js/specs/unit/collections/maps/GeoPoints.spec.js", + "./js/specs/unit/models/connectors/GeoPoints-Cesium.spec.js", + "./js/specs/unit/models/connectors/GeoPoints-CesiumPoints.spec.js", + "./js/specs/unit/models/connectors/GeoPoints-CesiumPolygon.spec.js", + "./js/specs/unit/models/maps/GeoBoundingBox.spec.js", + "./js/specs/unit/models/maps/GeoUtilities.spec.js", "./js/specs/unit/models/maps/MapInteraction.spec.js", "./js/specs/unit/models/connectors/Filters-Map.spec.js", "./js/specs/unit/models/connectors/Filters-Search.spec.js", diff --git a/test/js/specs/unit/collections/maps/GeoPoints.spec.js b/test/js/specs/unit/collections/maps/GeoPoints.spec.js index 17f5feba5..794c88501 100644 --- a/test/js/specs/unit/collections/maps/GeoPoints.spec.js +++ b/test/js/specs/unit/collections/maps/GeoPoints.spec.js @@ -1,21 +1,396 @@ -define([ - "../../../../../../../../src/js/collections/maps/GeoPoints", -], function (GeoPoints) { +// "use strict"; + +// define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) { +// /** +// * @class GeoPoints +// * @classdesc A group of ordered geographic points. +// * @class GeoPoints +// * @classcategory Collections/Maps +// * @extends Backbone.Collection +// * @since x.x.x +// * @constructor +// */ +// var GeoPoints = Backbone.Collection.extend( +// /** @lends GeoPoints.prototype */ { +// /** +// * The class/model that this collection contains. +// * @type {Backbone.Model} +// */ +// model: GeoPoint, + +// /** +// * Given a point in various formats, format it such that it can be used to +// * add to this collection. +// * @param {Array|Object|GeoPoint} point - Accepted formats are: +// * - An array of the form [longitude, latitude], with an optional third +// * element for height +// * - An object with a "longitude" and "latitude" property, and +// * optionally a "height" property +// * - A GeoPoint model +// * @returns {Object|GeoPoint} Returns an object with "longitude" and +// * "latitude" properties, and optionally a "height" property, or a +// * GeoPoint model. +// */ +// formatPoint: function (point) { +// let attributes = {}; +// if (Array.isArray(point) && point.length > 1) { +// attributes.longitude = point[0]; +// attributes.latitude = point[1]; +// if (point[2]) { +// attributes.height = point[2]; +// } +// } else if ( +// point instanceof GeoPoint || +// (point.latitude && point.longitude) +// ) { +// attributes = point; +// } +// return attributes; +// }, + +// /** +// * Add a point to the collection. Use this rather than the Backbone add +// * method to allow for different formats of points to be added. +// * @param {Array|Object|GeoPoint} point - See {@link formatPoint} for +// * accepted formats. +// * @returns {GeoPoint} Returns the GeoPoint model that was added. +// */ +// addPoint: function (point) { +// point = this.formatPoint(point); +// return this.add(point); +// }, + +// /** +// * Remove a specific point from the collection. Use this rather than the +// * Backbone remove method to allow for different formats of points to be +// * removed. +// * @param {Array|Object|GeoPoint|Number} indexOrPoint - The index of the +// * point to remove, or the point itself. See {@link formatPoint} for +// * accepted formats. +// * @returns {GeoPoint} Returns the GeoPoint model that was removed. +// */ +// removePoint(indexOrPoint) { +// if (typeof indexOrPoint === "number") { +// this.removePointByIndex(indexOrPoint); +// } else if (Array.isArray(indexOrPoint)) { +// this.removePointByAttr(indexOrPoint); +// } +// }, + +// /** +// * Remove a point from the collection based on its attributes. +// * @param {Array|Object|GeoPoint} point - Any format supported by +// * {@link formatPoint} is accepted. +// * @returns {GeoPoint} Returns the GeoPoint model that was removed. +// */ +// removePointByAttr: function (point) { +// point = this.formatPoint(point); +// const model = this.findWhere(point); +// return this.remove(model); +// }, + +// /** +// * Remove a point from the collection based on its index. +// * @param {Number} index - The index of the point to remove. +// * @returns {GeoPoint} Returns the GeoPoint model that was removed. +// */ +// removePointByIndex: function (index) { +// if (index < 0 || index >= this.length) { +// console.warn("Index out of bounds, GeoPoint not removed."); +// return; +// } +// const model = this.at(index); +// return this.remove(model); +// }, + +// /** +// * Convert the collection to a GeoJSON object. The output can be the +// * series of points as Point features, the points connected as a +// * LineString feature, or the points connected and closed as a Polygon. +// * +// * Note: For a "Polygon" geometry type, when there's only one point in the +// * collection, the output will be a "Point". If there are only two points, +// * the output will be a "LineString", unless `forceAsPolygon` is set to +// * true. +// * +// * @param {String} geometryType - The type of geometry to create. Can be +// * "Point", "LineString", or "Polygon". +// * @param {Boolean} [forceAsPolygon=false] - Set to true to enforce the +// * output as a polygon for the "Polygon" geometry type, regardless of the +// * number of points in the collection. +// * @returns {Object} Returns a GeoJSON object of type "Point", +// * "LineString", or "Polygon". +// */ +// toGeoJson: function (geometryType, forceAsPolygon = false) { +// if (!forceAsPolygon && geometryType === "Polygon" && this.length < 3) { +// geometryType = this.length === 1 ? "Point" : "LineString"; +// } +// return { +// type: "FeatureCollection", +// features: this.toGeoJsonFeatures(geometryType), +// }; +// }, + +// // TODO: Move this to a CZML model, use in GeoHash/es + +// /** +// * Get the header object for a CZML document. +// * @returns {Object} Returns a CZML header object. +// */ +// getCZMLHeader: function () { +// return { +// id: "document", +// version: "1.0", +// name: "GeoPoints", +// }; +// }, + +// /** +// * Convert the collection to a CZML document. +// * @param {String} geometryType - The type of geometry to create. +// * @param {Boolean} [forceAsPolygon=false] - Set to true to enforce the +// * output as a polygon for the "Polygon" geometry type, regardless of the +// * number of points in the collection. +// * @returns {Object[]} Returns an array of CZML objects. +// */ +// toCzml: function (geometryType, forceAsPolygon = false) { +// if (!forceAsPolygon && geometryType === "Polygon" && this.length < 3) { +// geometryType = this.length === 1 ? "Point" : "LineString"; +// } +// const czml = [this.getCZMLHeader()]; +// switch (geometryType) { +// case "Point": +// czml.concat(this.toCZMLPoints()); +// break; +// case "LineString": +// czml.push(this.getCZMLLineString()); +// break; +// case "Polygon": +// czml.push(this.getCZMLPolygon()); +// break; +// default: +// break; +// } +// return czml; +// }, + +// /** +// * Convert the collection to an array of CZML point objects. +// * @returns {Object[]} Returns an array of CZML point objects. +// */ +// toCZMLPoints: function () { +// return this.models.map((model) => { +// return model.toCZML(); +// }) +// }, + +// /** +// * Convert the collection to a CZML polygon object. +// * @returns {Object} Returns a CZML polygon object. +// */ +// getCZMLPolygon: function () { +// const coords = this.toECEFArray(); +// return { +// id: this.cid, +// name: "Polygon", +// polygon: { +// positions: { +// cartesian: coords, +// }, +// }, +// }; +// }, + +// /** +// * Convert the collection to a CZML line string object. +// * @returns {Object} Returns a CZML line string object. +// */ +// getCZMLLineString: function () { +// const coords = this.toECEFArray(); +// return { +// id: this.cid, +// name: "LineString", +// polyline: { +// positions: { +// cartesian: coords, +// }, +// }, +// }; +// }, + +// /** +// * Convert the collection to a GeoJSON object. The output can be the +// * series of points as Point features, the points connected as a +// * LineString feature, or the points connected and closed as a Polygon. +// * @param {"Point"|"LineString"|"Polygon"} geometryType - The type of +// * geometry to create. +// * @returns {Object[]} Returns an array of GeoJSON features. +// */ +// toGeoJsonFeatures: function (geometryType) { +// switch (geometryType) { +// case "Point": +// return this.toGeoJsonPointFeatures(); +// case "LineString": +// return [this.toGeoJsonLineStringFeature()]; +// case "Polygon": +// return [this.toGeoJsonPolygonFeature()]; +// default: +// return []; +// } +// }, + +// /** +// * Convert the collection to an array of GeoJSON point features. +// * @returns {Object[]} Returns an array of GeoJSON point features. +// */ +// toGeoJsonPointFeatures: function () { +// return this.models.map((model) => { +// return model.toGeoJsonFeature(); +// }); +// }, + +// /** +// * Convert the collection to a GeoJSON LineString feature. +// * @returns {Object} Returns a GeoJSON LineString feature. +// */ +// toGeoJsonLineStringFeature: function () { +// return { +// type: "Feature", +// geometry: { +// type: "LineString", +// coordinates: this.to2DArray(), +// }, +// properties: {}, +// }; +// }, + +// /** +// * Convert the collection to a GeoJSON Polygon feature. The polygon will +// * be closed if it isn't already. +// * @returns {Object} Returns a GeoJSON Polygon feature. +// */ +// toGeoJsonPolygonFeature: function () { +// const coordinates = this.to2DArray(); +// // Make sure the polygon is closed +// if (coordinates[0] != coordinates[coordinates.length - 1]) { +// coordinates.push(coordinates[0]); +// } +// return { +// type: "Feature", +// geometry: { +// type: "Polygon", +// coordinates: [coordinates], +// }, +// properties: {}, +// }; +// }, + +// /** +// * Convert the collection to an array of arrays, where each sub-array +// * contains the longitude and latitude of a point. +// * @returns {Array[]} Returns an array of arrays. +// */ +// to2DArray: function () { +// return this.models.map((model) => { +// return model.to2DArray(); +// }); +// }, + +// /** +// * Convert the collection to a cartesian array, where each every three +// * elements represents the x, y, and z coordinates of a vertex, e.g. +// * [x1, y1, z1, x2, y2, z2, ...]. +// * @returns {Array} Returns an array of numbers. +// */ +// toECEFArray: function () { +// return this.models.flatMap((model) => { +// return model.toECEFArray(); +// }); +// }, + +// /** +// * Convert the collection to an array of coordinates in the format +// * native to the map widget. For Cesium, this is an array of +// * Cartesian3 objects in ECEF coordinates. +// * @returns {Array} An array of coordinates that can be used by the map +// * widget. +// */ +// asMapWidgetCoords: function () { +// return this.models.map((model) => { +// return model.get("mapWidgetCoords"); +// }); +// }, +// } +// ); + +// return GeoPoints; +// }); + +define(["../../../../../../../../src/js/collections/maps/GeoPoints"], function ( + GeoPoints +) { // Configure the Chai assertion library var should = chai.should(); var expect = chai.expect; describe("GeoPoints Test Suite", function () { /* Set up */ - beforeEach(function () {}); + beforeEach(function () { + this.geoPoints = new GeoPoints(); + }); /* Tear down */ - afterEach(function () {}); + afterEach(function () { + this.geoPoints = null; + }); describe("Initialization", function () { it("should create a GeoPoints instance", function () { new GeoPoints().should.be.instanceof(GeoPoints); }); }); + + describe("Manipulating points", function () { + it("should add a point", function () { + this.geoPoints.addPoint([0, 0]); + this.geoPoints.length.should.equal(1); + }); + + it("should remove a point by index", function () { + this.geoPoints.addPoint([0, 0]); + this.geoPoints.removePointByIndex(0); + this.geoPoints.length.should.equal(0); + }); + + it("should remove a point by attribute", function () { + this.geoPoints.addPoint([0, 0]); + this.geoPoints.removePointByAttr(0, 0); + this.geoPoints.length.should.equal(0); + }); + + it("should remove a point by model", function () { + const that = this; + const model = this.geoPoints.addPoint([0, 0]); + this.geoPoints.removePoint(model); + this.geoPoints.length.should.equal(0); + }); + }); + + describe("Serialization", function () { + it("should convert to GeoJSON", function () { + this.geoPoints.addPoint([0, 0]); + const geoJson = this.geoPoints.toGeoJson("Point"); + geoJson.features.length.should.equal(1); + geoJson.features[0].geometry.type.should.equal("Point"); + }); + + it("should convert to CZML", function () { + this.geoPoints.addPoint([5, 5]); + const czml = this.geoPoints.toCzml("Point"); + czml.length.should.equal(2); + czml[1].position.cartesian.length.should.equal(3); + czml[1].point.should.be.instanceof(Object); + }); + }); }); -}); \ No newline at end of file +}); diff --git a/test/js/specs/unit/models/connectors/GeoPoints-Cesium.spec.js b/test/js/specs/unit/models/connectors/GeoPoints-Cesium.spec.js index 5901a1c82..9c62ee4f5 100644 --- a/test/js/specs/unit/models/connectors/GeoPoints-Cesium.spec.js +++ b/test/js/specs/unit/models/connectors/GeoPoints-Cesium.spec.js @@ -7,15 +7,40 @@ define([ describe("GeoPointsCesium Test Suite", function () { /* Set up */ - beforeEach(function () {}); + beforeEach(function () { + this.geoPointsCesium = new GeoPointsCesium(); + }); /* Tear down */ - afterEach(function () {}); + afterEach(function () { + this.geoPointsCesium = null; + }); describe("Initialization", function () { it("should create a GeoPointsCesium instance", function () { new GeoPointsCesium().should.be.instanceof(GeoPointsCesium); }); + + it("should set the GeoPoints collection", function () { + this.geoPointsCesium.get("geoPoints").models.should.be.empty; + }); + + it("should set the CesiumVectorData model", function () { + this.geoPointsCesium.get("layer").should.be.instanceof(Object) + }); + }); + + describe("Connect", function () { + it("should connect to the GeoPoints collection", function () { + this.geoPointsCesium.connect(); + this.geoPointsCesium.get("isConnected").should.equal(true); + }); + + it("should disconnect from the GeoPoints collection", function () { + this.geoPointsCesium.connect(); + this.geoPointsCesium.disconnect(); + this.geoPointsCesium.get("isConnected").should.equal(false); + }); }); }); }); \ No newline at end of file diff --git a/test/js/specs/unit/models/connectors/GeoPoints-CesiumPoints.spec.js b/test/js/specs/unit/models/connectors/GeoPoints-CesiumPoints.spec.js index b948dd58b..f9bd6996c 100644 --- a/test/js/specs/unit/models/connectors/GeoPoints-CesiumPoints.spec.js +++ b/test/js/specs/unit/models/connectors/GeoPoints-CesiumPoints.spec.js @@ -7,15 +7,39 @@ define([ describe("GeoPointsCesiumPoints Test Suite", function () { /* Set up */ - beforeEach(function () {}); + beforeEach(function () { + // Create a new GeoPointsCesiumPoints instance + this.geoPointsCesiumPoints = new GeoPointsCesiumPoints(); + }); /* Tear down */ - afterEach(function () {}); + afterEach(function () { + // Destroy the GeoPointsCesiumPoints instance + this.geoPointsCesiumPoints.destroy(); + }); describe("Initialization", function () { it("should create a GeoPointsCesiumPoints instance", function () { new GeoPointsCesiumPoints().should.be.instanceof(GeoPointsCesiumPoints); }); }); + + describe("Defaults", function () { + + it("should have a layerPoints array", function () { + this.geoPointsCesiumPoints.get("layerPoints").should.be.an("array"); + }); + }); + + describe("handleCollectionChange", function () { + it("should be a function", function () { + this.geoPointsCesiumPoints + .handleCollectionChange.should.be.a("function"); + }); + + }); + + + }); }); \ No newline at end of file diff --git a/test/js/specs/unit/models/connectors/GeoPoints-CesiumPolygon.spec.js b/test/js/specs/unit/models/connectors/GeoPoints-CesiumPolygon.spec.js index f5ae3e006..2ae22d56e 100644 --- a/test/js/specs/unit/models/connectors/GeoPoints-CesiumPolygon.spec.js +++ b/test/js/specs/unit/models/connectors/GeoPoints-CesiumPolygon.spec.js @@ -7,15 +7,20 @@ define([ describe("GeoPointsCesiumPolygon Test Suite", function () { /* Set up */ - beforeEach(function () {}); + beforeEach(function () { + this.geoPointsCesiumPolygon = new GeoPointsCesiumPolygon(); + }); /* Tear down */ - afterEach(function () {}); + afterEach(function () { + this.geoPointsCesiumPolygon.destroy(); + }); describe("Initialization", function () { it("should create a GeoPointsCesiumPolygon instance", function () { new GeoPointsCesiumPolygon().should.be.instanceof(GeoPointsCesiumPolygon); }); }); + }); }); \ No newline at end of file diff --git a/test/js/specs/unit/models/maps/GeoBoundingBox.spec.js b/test/js/specs/unit/models/maps/GeoBoundingBox.spec.js index d9553d8d2..15d70c087 100644 --- a/test/js/specs/unit/models/maps/GeoBoundingBox.spec.js +++ b/test/js/specs/unit/models/maps/GeoBoundingBox.spec.js @@ -1,3 +1,5 @@ + + define([ "../../../../../../../../src/js/models/maps/GeoBoundingBox", ], function (GeoBoundingBox) { @@ -7,15 +9,101 @@ define([ describe("GeoBoundingBox Test Suite", function () { /* Set up */ - beforeEach(function () {}); + beforeEach(function () { + this.geoBoundingBox = new GeoBoundingBox(); + }); /* Tear down */ - afterEach(function () {}); + afterEach(function () { + this.geoBoundingBox.destroy(); + }); describe("Initialization", function () { it("should create a GeoBoundingBox instance", function () { new GeoBoundingBox().should.be.instanceof(GeoBoundingBox); }); }); + + describe("Defaults", function () { + it("should have a north attribute", function () { + expect(this.geoBoundingBox.get("north")).to.equal(null); + }); + + it("should have a south attribute", function () { + expect(this.geoBoundingBox.get("south")).to.equal(null); + }); + + it("should have an east attribute", function () { + expect(this.geoBoundingBox.get("east")).to.equal(null); + }); + + it("should have a west attribute", function () { + expect(this.geoBoundingBox.get("west")).to.equal(null); + }); + + it("should have a height attribute", function () { + expect(this.geoBoundingBox.get("height")).to.equal(null); + }); + }); + + describe("Validation", function () { + it("should be valid with valid attributes", function () { + const valid = new GeoBoundingBox({ + north: 90, + south: -90, + east: 180, + west: -180, + }); + expect(valid.isValid()).to.equal(true); + }); + + it("should be invalid with invalid attributes", function () { + const invalid = new GeoBoundingBox({ + north: 91, + south: -91, + east: 181, + west: -181, + }); + expect(invalid.isValid()).to.equal(false); + }); + }); + + describe("methods", function () { + it("should split a bounding box that crosses the prime meridian", function () { + const bbox = new GeoBoundingBox({ + north: 90, + south: -90, + east: -180, + west: 180 + }); + const split = bbox.split(); + expect(split.length).to.equal(2); + expect(split[0].get("east")).to.equal(180); + expect(split[1].get("west")).to.equal(-180); + }); + + it("should not split a bounding box that does not cross the prime meridian", function () { + const bbox = new GeoBoundingBox({ + north: 90, + south: -90, + east: 10, + west: 0, + }); + const split = bbox.split(); + expect(split.length).to.equal(1); + expect(split[0].get("east")).to.equal(10); + expect(split[0].get("west")).to.equal(0); + }); + + it("should calculate area", function () { + const bbox = new GeoBoundingBox({ + north: 90, + south: -90, + east: 180, + west: -180, + }); + expect(bbox.getArea()).to.equal(360 * 180); + }); + }); }); }); \ No newline at end of file diff --git a/test/js/specs/unit/models/maps/GeoUtilities.spec.js b/test/js/specs/unit/models/maps/GeoUtilities.spec.js index a643471f4..367197a66 100644 --- a/test/js/specs/unit/models/maps/GeoUtilities.spec.js +++ b/test/js/specs/unit/models/maps/GeoUtilities.spec.js @@ -1,3 +1,4 @@ + define([ "../../../../../../../../src/js/models/maps/GeoUtilities", ], function (GeoUtilities) { @@ -17,5 +18,16 @@ define([ new GeoUtilities().should.be.instanceof(GeoUtilities); }); }); + + describe("geodeticToECEF", function () { + it("should convert geodetic coordinates to ECEF coordinates", function () { + const coord = [30, 40]; + const ecef = new GeoUtilities().geodeticToECEF(coord); + console.log(ecef); + ecef[0].should.be.closeTo(4243843, 1.0); + ecef[1].should.be.closeTo(2450184, 1.0); + ecef[2].should.be.closeTo(4084413, 1.0); + }); + }); }); }); \ No newline at end of file From b5e71f58a4eb3a5ae56b1144b9082e43e43a54c7 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 25 Oct 2023 15:09:28 -0400 Subject: [PATCH 20/43] Fix taxa persisting between editor sessions bug Fixes #2196 --- src/js/views/metadata/EML211View.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/views/metadata/EML211View.js b/src/js/views/metadata/EML211View.js index 7daf5571b..700595a1b 100644 --- a/src/js/views/metadata/EML211View.js +++ b/src/js/views/metadata/EML211View.js @@ -2347,8 +2347,8 @@ define(['underscore', 'jquery', 'backbone', if (taxonCoverages && taxonCoverages.length >= 1){ const taxonCoverage = taxonCoverages[0]; const classifications = taxonCoverage.get("taxonomicClassification"); - classifications.push(...newClassifications); - taxonCoverage.set("taxonomicClassification", classifications); + const allClass = classifications.concat(newClassifications); + taxonCoverage.set("taxonomicClassification", allClass); } else { // If there is no element for some reason, // create one and add the new taxon to its From aebacfb1fe04cf4554844f0d6ca771ff1b11c695 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 25 Oct 2023 16:11:26 -0400 Subject: [PATCH 21/43] Improve error handling of view service response Fixes #2144 --- src/js/views/MetadataIndexView.js | 147 ++++++++++++++++-------------- src/js/views/MetadataView.js | 62 +++++++------ 2 files changed, 111 insertions(+), 98 deletions(-) diff --git a/src/js/views/MetadataIndexView.js b/src/js/views/MetadataIndexView.js index 3d1bfc197..99910ff88 100644 --- a/src/js/views/MetadataIndexView.js +++ b/src/js/views/MetadataIndexView.js @@ -65,85 +65,92 @@ define(['jquery', encodeURIComponent(this.pid)+'")&rows=1&start=0&fl=*&wt=json'; var requestSettings = { url: MetacatUI.appModel.get('queryServiceUrl') + query, - success: function(data, textStatus, xhr){ + success: function (data, textStatus, xhr) { - if(data.response.numFound == 0){ + try { - if( view.parentView && view.parentView.model ){ + if (!data?.response?.numFound) { - //Show a "not indexed" message if there is system metadata but nothing in - // the index - if(view.parentView.model.get("systemMetadata")){ - view.showNotIndexed(); - } - //Show a "not found" message if there is no system metadata and no results in the index - else{ - view.parentView.model.set("notFound", true); - view.parentView.showNotFound(); - } - } + if (view.parentView && view.parentView.model) { - view.flagComplete(); - } - else{ - view.docs = data.response.docs; + //Show a "not indexed" message if there is system metadata but nothing in + // the index + if (view.parentView.model.get("systemMetadata")) { + view.showNotIndexed(); + } + //Show a "not found" message if there is no system metadata and no results in the index + else { + view.parentView.model.set("notFound", true); + view.parentView.showNotFound(); + } + } - _.each(data.response.docs, function(doc, i, list){ + view.flagComplete(); + } + else { + view.docs = data.response.docs; - //If this is a data object and there is a science metadata doc that describes it, then navigate to that Metadata View. - if((doc.formatType == "DATA") && (doc.isDocumentedBy && doc.isDocumentedBy.length)){ - view.onClose(); - MetacatUI.uiRouter.navigate("view/" + doc.isDocumentedBy[0], true); - return; - } + _.each(data.response.docs, function (doc, i, list) { - var metadataEl = $(document.createElement("section")).attr("id", "metadata-index-details"), - id = doc.id, - creator = doc.origin, - title = doc.title, - pubDate = doc.pubDate, - dateUploaded = doc.dateUploaded, - keys = Object.keys(doc), - docModel = new SolrResult(doc); - - //Extract General Info details that we want to list first - var generalInfoKeys = ["title", "id", "abstract", "pubDate", "keywords"]; - keys = _.difference(keys, generalInfoKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, generalInfoKeys, "General")); - - //Extract Spatial details - var spatialKeys = ["site", "southBoundCoord", "northBoundCoord", "westBoundCoord", "eastBoundCoord"]; - keys = _.difference(keys, spatialKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, spatialKeys, "Geographic Region")); - - //Extract Temporal Coverage details - var temporalKeys = ["beginDate", "endDate"]; - keys = _.difference(keys, temporalKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, temporalKeys, "Temporal Coverage")); - - //Extract Taxonomic Coverage details - var taxonKeys = ["order", "phylum", "family", "genus", "species", "scientificName"]; - keys = _.difference(keys, taxonKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, taxonKeys, "Taxonomic Coverage")); - - //Extract People details - var peopleKeys = ["origin", "investigator", "contactOrganization", "project"]; - keys = _.difference(keys, peopleKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, peopleKeys, "People and Associated Parties")); - - //Extract Access Control details - var accessKeys = ["isPublic", "submitter", "rightsHolder", "writePermission", "readPermission", "changePermission", "authoritativeMN"]; - keys = _.difference(keys, accessKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, accessKeys, "Access Control")); - - //Add the rest of the metadata - $(metadataEl).append(view.formatAttributeSection(docModel, keys, "Other")); - - view.$el.html(metadataEl); + //If this is a data object and there is a science metadata doc that describes it, then navigate to that Metadata View. + if ((doc.formatType == "DATA") && (doc.isDocumentedBy && doc.isDocumentedBy.length)) { + view.onClose(); + MetacatUI.uiRouter.navigate("view/" + doc.isDocumentedBy[0], true); + return; + } - view.flagComplete(); - }); + var metadataEl = $(document.createElement("section")).attr("id", "metadata-index-details"), + id = doc.id, + creator = doc.origin, + title = doc.title, + pubDate = doc.pubDate, + dateUploaded = doc.dateUploaded, + keys = Object.keys(doc), + docModel = new SolrResult(doc); + + //Extract General Info details that we want to list first + var generalInfoKeys = ["title", "id", "abstract", "pubDate", "keywords"]; + keys = _.difference(keys, generalInfoKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, generalInfoKeys, "General")); + + //Extract Spatial details + var spatialKeys = ["site", "southBoundCoord", "northBoundCoord", "westBoundCoord", "eastBoundCoord"]; + keys = _.difference(keys, spatialKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, spatialKeys, "Geographic Region")); + + //Extract Temporal Coverage details + var temporalKeys = ["beginDate", "endDate"]; + keys = _.difference(keys, temporalKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, temporalKeys, "Temporal Coverage")); + + //Extract Taxonomic Coverage details + var taxonKeys = ["order", "phylum", "family", "genus", "species", "scientificName"]; + keys = _.difference(keys, taxonKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, taxonKeys, "Taxonomic Coverage")); + + //Extract People details + var peopleKeys = ["origin", "investigator", "contactOrganization", "project"]; + keys = _.difference(keys, peopleKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, peopleKeys, "People and Associated Parties")); + + //Extract Access Control details + var accessKeys = ["isPublic", "submitter", "rightsHolder", "writePermission", "readPermission", "changePermission", "authoritativeMN"]; + keys = _.difference(keys, accessKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, accessKeys, "Access Control")); + + //Add the rest of the metadata + $(metadataEl).append(view.formatAttributeSection(docModel, keys, "Other")); + + view.$el.html(metadataEl); + + view.flagComplete(); + }); + } + } catch (e) { + console.log("Error parsing Solr response: " + e); + console.log("Solr response: " + data); + view.parentView.showNotFound(); } }, error: function(){ diff --git a/src/js/views/MetadataView.js b/src/js/views/MetadataView.js index 23a878855..2564a90c9 100644 --- a/src/js/views/MetadataView.js +++ b/src/js/views/MetadataView.js @@ -386,47 +386,53 @@ define(['jquery', var loadSettings = { url: endpoint, success: function (response, status, xhr) { + try { - //If the user has navigated away from the MetadataView, then don't render anything further - if (MetacatUI.appView.currentView != viewRef) - return; - - //Our fallback is to show the metadata details from the Solr index - if (status == "error") - viewRef.renderMetadataFromIndex(); - else { - //Check for a response that is a 200 OK status, but is an error msg - if ((response.length < 250) && (response.indexOf("Error transforming document") > -1) && viewRef.model.get("indexed")) { - viewRef.renderMetadataFromIndex(); + //If the user has navigated away from the MetadataView, then don't render anything further + if (MetacatUI.appView.currentView != viewRef) return; - } - //Mark this as a metadata doc with no stylesheet, or one that is at least different than usual EML and FGDC - else if ((response.indexOf('id="Metadata"') == -1)) { - viewRef.$el.addClass("container no-stylesheet"); - if (viewRef.model.get("indexed")) { + //Our fallback is to show the metadata details from the Solr index + if (status == "error" || !response || typeof response !== "string") + viewRef.renderMetadataFromIndex(); + else { + //Check for a response that is a 200 OK status, but is an error msg + if ((response.length < 250) && (response.indexOf("Error transforming document") > -1) && viewRef.model.get("indexed")) { viewRef.renderMetadataFromIndex(); return; } - } + //Mark this as a metadata doc with no stylesheet, or one that is at least different than usual EML and FGDC + else if ((response.indexOf('id="Metadata"') == -1)) { + viewRef.$el.addClass("container no-stylesheet"); + + if (viewRef.model.get("indexed")) { + viewRef.renderMetadataFromIndex(); + return; + } + } - //Now show the response from the view service - viewRef.$(viewRef.metadataContainer).html(response); + //Now show the response from the view service + viewRef.$(viewRef.metadataContainer).html(response); - //If there is no info from the index and there is no metadata doc rendered either, then display a message - if (viewRef.$el.is(".no-stylesheet") && viewRef.model.get("archived") && !viewRef.model.get("indexed")) - viewRef.$(viewRef.metadataContainer).prepend(viewRef.alertTemplate({ msg: "There is limited metadata about this dataset since it has been archived." })); + //If there is no info from the index and there is no metadata doc rendered either, then display a message + if (viewRef.$el.is(".no-stylesheet") && viewRef.model.get("archived") && !viewRef.model.get("indexed")) + viewRef.$(viewRef.metadataContainer).prepend(viewRef.alertTemplate({ msg: "There is limited metadata about this dataset since it has been archived." })); - viewRef.alterMarkup(); + viewRef.alterMarkup(); - viewRef.trigger("metadataLoaded"); + viewRef.trigger("metadataLoaded"); - //Add a map of the spatial coverage - if (gmaps) viewRef.insertSpatialCoverageMap(); + //Add a map of the spatial coverage + if (gmaps) viewRef.insertSpatialCoverageMap(); - // Injects Clipboard objects into DOM elements returned from the View Service - viewRef.insertCopiables(); + // Injects Clipboard objects into DOM elements returned from the View Service + viewRef.insertCopiables(); + } + } catch (e) { + console.log("Error rendering metadata from the view service", e); + console.log("Response from the view service: ", response); + viewRef.renderMetadataFromIndex(); } }, error: function (xhr, textStatus, errorThrown) { From acc05af65ebcbdac4a7f310c0d21ed4705e69208 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 26 Oct 2023 16:44:17 -0400 Subject: [PATCH 22/43] Prevent weird TOC placement in portals - Add min height to markdown sections with portals - Also rename methods from postRender to what they do, because backbone calls these methods automatically even though this isn't documented! fixes #2195 --- src/js/themes/dataone/css/metacatui.css | 4 ++++ src/js/views/MarkdownView.js | 22 ++++------------------ src/js/views/TOCView.js | 20 +++++++++++++------- src/js/views/portals/PortalSectionView.js | 4 +--- 4 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/js/themes/dataone/css/metacatui.css b/src/js/themes/dataone/css/metacatui.css index 625095593..90614feb4 100644 --- a/src/js/themes/dataone/css/metacatui.css +++ b/src/js/themes/dataone/css/metacatui.css @@ -479,6 +479,10 @@ width: 100%; color: var(--c-neutral-8); } +.toc-view + .markdown { + min-height: 90vh; +} + /* FORM/INPUT CUSTOMIZATIONS -------------------------------------------------- */ .depth { diff --git a/src/js/views/MarkdownView.js b/src/js/views/MarkdownView.js index ada6ecd33..41be210b3 100644 --- a/src/js/views/MarkdownView.js +++ b/src/js/views/MarkdownView.js @@ -88,7 +88,7 @@ define([ "jquery", "underscore", "backbone", * render - Renders the MarkdownView; converts markdown to HTML and * displays it. */ - render: function() { + render: function () { // Show a loading message while we render the markdown to HTML this.$el.html(this.loadingTemplate({ @@ -143,15 +143,10 @@ define([ "jquery", "underscore", "backbone", this.$el.html(this.template({ markdown: htmlFromMD })); if( this.showTOC ){ - this.listenToOnce(this, "TOCRendered", function(){ - this.trigger("mdRendered"); - this.postRender(); - }); this.renderTOC(); - } else { - this.trigger("mdRendered"); - this.postRender(); } + + this.trigger("mdRendered"); }); @@ -162,15 +157,6 @@ define([ "jquery", "underscore", "backbone", }, - postRender: function(){ - if(this.tocView){ - this.tocView.postRender(); - } else { - this.listenToOnce(this, "TOCRendered", function(){ - this.tocView.postRender(); - }); - } - }, /** * listRequiredExtensions - test which extensions are needed, then load @@ -378,7 +364,7 @@ define([ "jquery", "underscore", "backbone", view.$el.addClass("span9"); } - view.trigger("TOCRendered"); + view.tocView.setAffix(); }); diff --git a/src/js/views/TOCView.js b/src/js/views/TOCView.js index bcfeb4dca..1e68b66db 100644 --- a/src/js/views/TOCView.js +++ b/src/js/views/TOCView.js @@ -121,7 +121,6 @@ define(["jquery", } - var view = this; return this; }, @@ -278,11 +277,11 @@ define(["jquery", // Add scroll spy $("body").off("activate"); - $("body").on("activate", function(e){ + $("body").on("activate", function (e) { view.scrollSpyExtras(e); }); $(window).off("resize"); - $(window).on("resize", function(){ + $(window).on("resize", function () { $spy.scrollspy("refresh"); }); @@ -294,19 +293,26 @@ define(["jquery", /** - * affixTOC - description + * Adds and refreshes bootstrap's affix functionality. This function + * should be called after the DOM has been rendered or updated. Renamed + * from postRender to avoid it being called automatically by Backbone. + * @since x.x.x */ - postRender: function(){ + setAffix: function(){ try { var isVisible = this.$el.find(":visible").length > 0; - if(this.affix === true && isVisible){ + if(!isVisible || !this.$el.offset()){ + return; + } + + if (this.affix === true) { this.$el.affix({ offset: this.$el.offset().top }); } - if(this.addScrollspy && isVisible){ + if(this.addScrollspy){ this.renderScrollspy(); } diff --git a/src/js/views/portals/PortalSectionView.js b/src/js/views/portals/PortalSectionView.js index 38da5f54e..afb164607 100644 --- a/src/js/views/portals/PortalSectionView.js +++ b/src/js/views/portals/PortalSectionView.js @@ -144,9 +144,7 @@ define(["jquery", } }); - if(this.markdownView){ - this.markdownView.postRender(); - } + this.markdownView?.tocView?.setAffix(); }, /** From ea18e1100a7465c4704d7bb99216ea2b0e14b4fe Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 26 Oct 2023 18:17:15 -0400 Subject: [PATCH 23/43] Adjust height of feature info panel in Cesium map again after content has been loaded. Fixes #2192 --- src/js/views/maps/FeatureInfoView.js | 38 +++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/js/views/maps/FeatureInfoView.js b/src/js/views/maps/FeatureInfoView.js index 2e64ee8f6..6aa935474 100644 --- a/src/js/views/maps/FeatureInfoView.js +++ b/src/js/views/maps/FeatureInfoView.js @@ -256,6 +256,8 @@ define( try { + const view = this; + // Elements to update const title = this.getFeatureTitle() const iFrame = this.elements.iFrame @@ -275,12 +277,17 @@ define( this.elements.title.innerHTML = title // Update the iFrame content - iFrame.height = 0; this.getContent().then(function (html) { iFrameDiv.innerHTML = html; - const maxHeight = window.innerHeight - 275; - const scrollHeight = iFrame.contentWindow.document.body.scrollHeight + 5; - iFrame.height = scrollHeight > maxHeight ? maxHeight : scrollHeight; + view.updateIFrameHeight(); + // Not the ideal solution, but check the height of the iFrame + // again after some time to allow external content to load. This + // is necessary for content that loads asynchronously, like + // images. Difficult to set listeners for this, since the content + // may be from a different domain. + setTimeout(function () { + view.updateIFrameHeight(); + }, 850); }) // Show or hide the layer details button, update the text @@ -299,6 +306,29 @@ define( } }, + /** + * Update the height of the iFrame to match the height of the content + * within it. + * @param {number} [height] The height to set the iFrame to. If no + * height is provided, then the height of the content within the iFrame + * will be used. + * @param {boolean} [limit=true] Whether or not to limit the height of + * the iFrame to the height of the window, minus 275px. + * @since x.x.x + */ + updateIFrameHeight: function (height, limit = true) { + const iFrame = this.elements?.iFrame; + if (!iFrame) return; + if ((!height && height !== 0) || height < 0) { + height = iFrame.contentWindow.document.body.scrollHeight + 5; + } + if (limit) { + const maxHeight = window.innerHeight - 275; + height = height > maxHeight ? maxHeight : height; + } + iFrame.style.height = height + "px"; + }, + /** * Get the inner HTML content to insert into the iFrame. The content will vary * based on the feature and if there is a template set on the parent Map Asset From 0e7c60491b9fe14de56546300d3522682f680d5a Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 26 Oct 2023 18:22:57 -0400 Subject: [PATCH 24/43] Allow all users to set datasets to private on KNB Fixes #2215 --- src/js/themes/knb/config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/themes/knb/config.js b/src/js/themes/knb/config.js index 02a702cfc..9f81191fe 100644 --- a/src/js/themes/knb/config.js +++ b/src/js/themes/knb/config.js @@ -45,7 +45,6 @@ MetacatUI.AppConfig = Object.assign({ read: true }], hiddenSubjectsInAccessPolicy: ["CN=knb-data-admins,DC=dataone,DC=org"], - showDatasetPublicToggleForSubjects: ["CN=knb-data-admins,DC=dataone,DC=org"], allowChangeRightsHolder: false, enableMeasurementTypeView: true, From b8e7ba917457684bb70fb706cc0034c9d0aacb10 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 1 Nov 2023 14:03:36 -0400 Subject: [PATCH 25/43] Add moveStartAndCameraChanged event to map - Wait to trigger catalog search until camera is moving has moved enough to be considered 'changed' - Remove listeners in Map-Search-Filters connector - Update some documentation - Clean up some of the CesiumVectorData code Issues #2180 and #2189 --- src/js/models/connectors/Filters-Map.js | 4 +- .../models/connectors/Map-Search-Filters.js | 1 + src/js/models/connectors/Map-Search.js | 4 +- src/js/models/maps/MapInteraction.js | 75 +++++++++++-------- src/js/models/maps/assets/CesiumVectorData.js | 38 +++++----- src/js/views/maps/CesiumWidgetView.js | 4 +- 6 files changed, 69 insertions(+), 57 deletions(-) diff --git a/src/js/models/connectors/Filters-Map.js b/src/js/models/connectors/Filters-Map.js index b3afd7772..266b05073 100644 --- a/src/js/models/connectors/Filters-Map.js +++ b/src/js/models/connectors/Filters-Map.js @@ -185,7 +185,7 @@ define(["backbone", "collections/Filters", "models/maps/Map"], function ( } const interactions = this.get("map")?.get("interactions"); this.stopListening(this.get("filters"), "add remove"); - this.stopListening(interactions, "moveEnd moveStart"); + this.stopListening(interactions, "moveEnd moveStartAndChanged"); this.set("isConnected", false); } catch (e) { console.log("Error stopping Filter-Map listeners: ", e); @@ -207,7 +207,7 @@ define(["backbone", "collections/Filters", "models/maps/Map"], function ( this.updateSpatialFilters(); // Trigger a 'changing' event on the filters collection to // indicate that the spatial filter is being updated - this.listenTo(interactions, "moveStart", function () { + this.listenTo(interactions, "moveStartAndChanged", function () { this.get("filters").trigger("changing"); }); this.listenTo(interactions, "moveEnd", function () { diff --git a/src/js/models/connectors/Map-Search-Filters.js b/src/js/models/connectors/Map-Search-Filters.js index d37bed28d..7c411f55d 100644 --- a/src/js/models/connectors/Map-Search-Filters.js +++ b/src/js/models/connectors/Map-Search-Filters.js @@ -213,6 +213,7 @@ define([ * so that they work together. */ connect: function () { + this.disconnect(); this.coordinateMoveEndSearch(); this.getConnectors().forEach((connector) => connector.connect()); }, diff --git a/src/js/models/connectors/Map-Search.js b/src/js/models/connectors/Map-Search.js index ae7b5f266..7b9fd4483 100644 --- a/src/js/models/connectors/Map-Search.js +++ b/src/js/models/connectors/Map-Search.js @@ -179,7 +179,7 @@ define([ // When the user is panning/zooming in the map, hide the GeoHash layer // to indicate that the map is not up to date with the search results, // which are about to be updated. - this.listenTo(interactions, "moveStart", this.hideGeoHashLayer); + this.listenTo(interactions, "moveStartAndChanged", this.hideGeoHashLayer); // When the user is done panning/zooming in the map, show the GeoHash // layer again and update the search results (thereby updating the @@ -240,7 +240,7 @@ define([ const searchResults = this.get("searchResults"); this.stopListening(searchResults, "update reset"); this.stopListening(searchResults, "change:showOnMap"); - this.stopListening(interactions, "moveStart moveEnd"); + this.stopListening(interactions, "moveStartAndChanged moveEnd"); this.stopListening(searchResults, "request"); this.set("isConnected", false); }, diff --git a/src/js/models/maps/MapInteraction.js b/src/js/models/maps/MapInteraction.js index bf731a408..96c37726e 100644 --- a/src/js/models/maps/MapInteraction.js +++ b/src/js/models/maps/MapInteraction.js @@ -32,19 +32,25 @@ define([ * default attributes for the Map. * @returns {Object} The default attributes for the Map. * @property {GeoPoint} mousePosition - The current position of the mouse - * on the map. + * on the map. Updated by the map widget to show the longitude, latitude, + * and height (elevation) at the position of the mouse on the map. * @property {GeoPoint} clickedPosition - The position on the map that the * user last clicked. * @property {GeoScale} scale - The current scale of the map in - * pixels:meters. - * @property {GeoBoundingBox} viewExtent - The current extent of the map - * view. + * pixels:meters, i.e. The number of pixels on the screen that equal the + * number of meters on the map/globe. Updated by the map widget. + * @property {GeoBoundingBox} viewExtent - The extent of the currently + * visible area in the map widget. Updated by the map widget. * @property {Features} hoveredFeatures - The feature that the mouse is - * currently hovering over. + * currently hovering over. Updated by the map widget with a Feature model + * when a user hovers over a geographical feature on the map. * @property {Features} clickedFeatures - The feature that the user last - * clicked. - * @property {Features} selectedFeatures - The feature that is currently - * selected. + * clicked. Updated by the map widget with a Feature model when a user + * clicks on a geographical feature on the map. + * @property {Features} selectedFeatures - Features from one or more + * layers that are highlighted or selected on the map. Updated by the map + * widget with a Feature model when a user selects a geographical feature + * on the map (e.g. by clicking) * @property {Boolean} firstInteraction - Whether or not the user has * interacted with the map yet. This is set to true when the user has * clicked, hovered, panned, or zoomed the map. The only action that is @@ -58,28 +64,6 @@ define([ * this property and zoom to the specified feature or map asset when this * property is set. The property should be cleared after the map widget * has zoomed to the specified feature or map asset. - * - * TODO - * * @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, as well as the height/altitude in - * meters. - * - * * @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) */ defaults: function () { return { @@ -90,7 +74,7 @@ define([ hoveredFeatures: new Features(), clickedFeatures: new Features(), selectedFeatures: new Features(), - firstInteraction: false, // <- "hasInteracted"? + firstInteraction: false, previousAction: null, zoomTarget: null, }; @@ -115,6 +99,7 @@ define([ */ connectEvents: function () { this.listenForFirstInteraction(); + this.listenForMoveStartAndChange(); this.listenTo(this, "change:previousAction", this.handleClick); }, @@ -139,6 +124,34 @@ define([ ); }, + /** + * Expands the camera events that are passed to the MapInteraction model + * from the map widget by creating a 'moveStartAndChanged' event. This + * event is triggered after the camera starts moving if and only if the + * camera position changes enough to trigger a 'cameraChanged' event. This + * event is useful for triggering actions that should only occur after the + * camera has moved and the camera position has changed. + * @since x.x.x + */ + listenForMoveStartAndChange: function () { + if (this.moveStartChangeListener) { + this.moveStartChangeListener.destroy(); + } + const listener = new Backbone.Model(); + const model = this; + this.moveStartChangeListener = listener; + listener.stopListening(model, "moveStart"); + listener.listenTo(model, "moveStart", function () { + listener.listenToOnce(model, "cameraChanged", function () { + listener.stopListening(model, "moveEnd"); + model.trigger("moveStartAndChanged"); + }) + listener.listenToOnce(model, "moveEnd", function () { + listener.stopListening(model, "cameraChanged"); + }) + }) + }, + /** * Handles a mouse click on the map. If the user has clicked on a feature, * the feature is set as the 'clickedFeatures' attribute. If the map is diff --git a/src/js/models/maps/assets/CesiumVectorData.js b/src/js/models/maps/assets/CesiumVectorData.js index 9eaf66bf2..e596f1fe9 100644 --- a/src/js/models/maps/assets/CesiumVectorData.js +++ b/src/js/models/maps/assets/CesiumVectorData.js @@ -360,30 +360,26 @@ define([ updateAppearance: function () { try { const model = this; - const cesiumModel = this.get("cesiumModel"); + const entities = this.getEntities(); + const entityCollection = this.getEntityCollection(); this.set("displayReady", false); - if (!cesiumModel) { - return; - } - - const entities = cesiumModel.entities.values; - - // Suspending events while updating a large number of entities helps - // performance. - model.suspendEvents(); - - // If the asset isn't visible, just hide all entities and update the - // visibility property to indicate that layer is hidden - if (!model.isVisible()) { - cesiumModel.entities.show = false; - if (model.get("opacity") === 0) model.set("visible", false); - } else { - cesiumModel.entities.show = true; - this.styleEntities(entities); + if (entities && entities.length) { + if (model.isVisible()) { + // Suspending events while updating a large number of entities helps + // performance. + model.suspendEvents(); + entityCollection.show = true; + this.styleEntities(entities); + model.resumeEvents(); + } else { + // If the asset isn't visible, just hide all entities and update the + // visibility property to indicate that layer is hidden + entityCollection.show = false; + if (model.get("opacity") === 0) model.set("visible", false); + } } - model.resumeEvents(); this.runVisualizers(); } catch (e) { console.log("Failed to update CesiumVectorData model styles.", e); @@ -399,7 +395,7 @@ define([ */ runVisualizers: function () { const dataSource = this.get("cesiumModel"); - const visualizers = dataSource._visualizers; + const visualizers = dataSource?._visualizers; if (!visualizers || !visualizers.length) { this.whenVisualizersReady(this.runVisualizers.bind(this)); return; diff --git a/src/js/views/maps/CesiumWidgetView.js b/src/js/views/maps/CesiumWidgetView.js index f168dda1c..b62983725 100644 --- a/src/js/views/maps/CesiumWidgetView.js +++ b/src/js/views/maps/CesiumWidgetView.js @@ -455,7 +455,9 @@ define([ // model, and runs any functions configured above. Object.entries(cameraEvents).forEach(function ([label, functions]) { const callback = function () { - interactions.trigger(label); + // Rename because 'changed' is too similar to the Backbone event + const eventName = label === "changed" ? "cameraChanged" : label; + interactions.trigger(eventName); functions.forEach(function (func) { view[func].call(view); }); From 67be6beb6be1729d0a5170f8bbc818fa3b644974 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 25 Oct 2023 15:09:28 -0400 Subject: [PATCH 26/43] Fix taxa persisting between editor sessions bug Fixes #2196 --- src/js/views/metadata/EML211View.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/views/metadata/EML211View.js b/src/js/views/metadata/EML211View.js index 7daf5571b..700595a1b 100644 --- a/src/js/views/metadata/EML211View.js +++ b/src/js/views/metadata/EML211View.js @@ -2347,8 +2347,8 @@ define(['underscore', 'jquery', 'backbone', if (taxonCoverages && taxonCoverages.length >= 1){ const taxonCoverage = taxonCoverages[0]; const classifications = taxonCoverage.get("taxonomicClassification"); - classifications.push(...newClassifications); - taxonCoverage.set("taxonomicClassification", classifications); + const allClass = classifications.concat(newClassifications); + taxonCoverage.set("taxonomicClassification", allClass); } else { // If there is no element for some reason, // create one and add the new taxon to its From dc471e41a878af4157579c3c2955488e898fedd0 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 25 Oct 2023 16:11:26 -0400 Subject: [PATCH 27/43] Improve error handling of view service response Fixes #2144 --- src/js/views/MetadataIndexView.js | 147 ++++++++++++++++-------------- src/js/views/MetadataView.js | 62 +++++++------ 2 files changed, 111 insertions(+), 98 deletions(-) diff --git a/src/js/views/MetadataIndexView.js b/src/js/views/MetadataIndexView.js index 3d1bfc197..99910ff88 100644 --- a/src/js/views/MetadataIndexView.js +++ b/src/js/views/MetadataIndexView.js @@ -65,85 +65,92 @@ define(['jquery', encodeURIComponent(this.pid)+'")&rows=1&start=0&fl=*&wt=json'; var requestSettings = { url: MetacatUI.appModel.get('queryServiceUrl') + query, - success: function(data, textStatus, xhr){ + success: function (data, textStatus, xhr) { - if(data.response.numFound == 0){ + try { - if( view.parentView && view.parentView.model ){ + if (!data?.response?.numFound) { - //Show a "not indexed" message if there is system metadata but nothing in - // the index - if(view.parentView.model.get("systemMetadata")){ - view.showNotIndexed(); - } - //Show a "not found" message if there is no system metadata and no results in the index - else{ - view.parentView.model.set("notFound", true); - view.parentView.showNotFound(); - } - } + if (view.parentView && view.parentView.model) { - view.flagComplete(); - } - else{ - view.docs = data.response.docs; + //Show a "not indexed" message if there is system metadata but nothing in + // the index + if (view.parentView.model.get("systemMetadata")) { + view.showNotIndexed(); + } + //Show a "not found" message if there is no system metadata and no results in the index + else { + view.parentView.model.set("notFound", true); + view.parentView.showNotFound(); + } + } - _.each(data.response.docs, function(doc, i, list){ + view.flagComplete(); + } + else { + view.docs = data.response.docs; - //If this is a data object and there is a science metadata doc that describes it, then navigate to that Metadata View. - if((doc.formatType == "DATA") && (doc.isDocumentedBy && doc.isDocumentedBy.length)){ - view.onClose(); - MetacatUI.uiRouter.navigate("view/" + doc.isDocumentedBy[0], true); - return; - } + _.each(data.response.docs, function (doc, i, list) { - var metadataEl = $(document.createElement("section")).attr("id", "metadata-index-details"), - id = doc.id, - creator = doc.origin, - title = doc.title, - pubDate = doc.pubDate, - dateUploaded = doc.dateUploaded, - keys = Object.keys(doc), - docModel = new SolrResult(doc); - - //Extract General Info details that we want to list first - var generalInfoKeys = ["title", "id", "abstract", "pubDate", "keywords"]; - keys = _.difference(keys, generalInfoKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, generalInfoKeys, "General")); - - //Extract Spatial details - var spatialKeys = ["site", "southBoundCoord", "northBoundCoord", "westBoundCoord", "eastBoundCoord"]; - keys = _.difference(keys, spatialKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, spatialKeys, "Geographic Region")); - - //Extract Temporal Coverage details - var temporalKeys = ["beginDate", "endDate"]; - keys = _.difference(keys, temporalKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, temporalKeys, "Temporal Coverage")); - - //Extract Taxonomic Coverage details - var taxonKeys = ["order", "phylum", "family", "genus", "species", "scientificName"]; - keys = _.difference(keys, taxonKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, taxonKeys, "Taxonomic Coverage")); - - //Extract People details - var peopleKeys = ["origin", "investigator", "contactOrganization", "project"]; - keys = _.difference(keys, peopleKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, peopleKeys, "People and Associated Parties")); - - //Extract Access Control details - var accessKeys = ["isPublic", "submitter", "rightsHolder", "writePermission", "readPermission", "changePermission", "authoritativeMN"]; - keys = _.difference(keys, accessKeys); - $(metadataEl).append(view.formatAttributeSection(docModel, accessKeys, "Access Control")); - - //Add the rest of the metadata - $(metadataEl).append(view.formatAttributeSection(docModel, keys, "Other")); - - view.$el.html(metadataEl); + //If this is a data object and there is a science metadata doc that describes it, then navigate to that Metadata View. + if ((doc.formatType == "DATA") && (doc.isDocumentedBy && doc.isDocumentedBy.length)) { + view.onClose(); + MetacatUI.uiRouter.navigate("view/" + doc.isDocumentedBy[0], true); + return; + } - view.flagComplete(); - }); + var metadataEl = $(document.createElement("section")).attr("id", "metadata-index-details"), + id = doc.id, + creator = doc.origin, + title = doc.title, + pubDate = doc.pubDate, + dateUploaded = doc.dateUploaded, + keys = Object.keys(doc), + docModel = new SolrResult(doc); + + //Extract General Info details that we want to list first + var generalInfoKeys = ["title", "id", "abstract", "pubDate", "keywords"]; + keys = _.difference(keys, generalInfoKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, generalInfoKeys, "General")); + + //Extract Spatial details + var spatialKeys = ["site", "southBoundCoord", "northBoundCoord", "westBoundCoord", "eastBoundCoord"]; + keys = _.difference(keys, spatialKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, spatialKeys, "Geographic Region")); + + //Extract Temporal Coverage details + var temporalKeys = ["beginDate", "endDate"]; + keys = _.difference(keys, temporalKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, temporalKeys, "Temporal Coverage")); + + //Extract Taxonomic Coverage details + var taxonKeys = ["order", "phylum", "family", "genus", "species", "scientificName"]; + keys = _.difference(keys, taxonKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, taxonKeys, "Taxonomic Coverage")); + + //Extract People details + var peopleKeys = ["origin", "investigator", "contactOrganization", "project"]; + keys = _.difference(keys, peopleKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, peopleKeys, "People and Associated Parties")); + + //Extract Access Control details + var accessKeys = ["isPublic", "submitter", "rightsHolder", "writePermission", "readPermission", "changePermission", "authoritativeMN"]; + keys = _.difference(keys, accessKeys); + $(metadataEl).append(view.formatAttributeSection(docModel, accessKeys, "Access Control")); + + //Add the rest of the metadata + $(metadataEl).append(view.formatAttributeSection(docModel, keys, "Other")); + + view.$el.html(metadataEl); + + view.flagComplete(); + }); + } + } catch (e) { + console.log("Error parsing Solr response: " + e); + console.log("Solr response: " + data); + view.parentView.showNotFound(); } }, error: function(){ diff --git a/src/js/views/MetadataView.js b/src/js/views/MetadataView.js index 23a878855..2564a90c9 100644 --- a/src/js/views/MetadataView.js +++ b/src/js/views/MetadataView.js @@ -386,47 +386,53 @@ define(['jquery', var loadSettings = { url: endpoint, success: function (response, status, xhr) { + try { - //If the user has navigated away from the MetadataView, then don't render anything further - if (MetacatUI.appView.currentView != viewRef) - return; - - //Our fallback is to show the metadata details from the Solr index - if (status == "error") - viewRef.renderMetadataFromIndex(); - else { - //Check for a response that is a 200 OK status, but is an error msg - if ((response.length < 250) && (response.indexOf("Error transforming document") > -1) && viewRef.model.get("indexed")) { - viewRef.renderMetadataFromIndex(); + //If the user has navigated away from the MetadataView, then don't render anything further + if (MetacatUI.appView.currentView != viewRef) return; - } - //Mark this as a metadata doc with no stylesheet, or one that is at least different than usual EML and FGDC - else if ((response.indexOf('id="Metadata"') == -1)) { - viewRef.$el.addClass("container no-stylesheet"); - if (viewRef.model.get("indexed")) { + //Our fallback is to show the metadata details from the Solr index + if (status == "error" || !response || typeof response !== "string") + viewRef.renderMetadataFromIndex(); + else { + //Check for a response that is a 200 OK status, but is an error msg + if ((response.length < 250) && (response.indexOf("Error transforming document") > -1) && viewRef.model.get("indexed")) { viewRef.renderMetadataFromIndex(); return; } - } + //Mark this as a metadata doc with no stylesheet, or one that is at least different than usual EML and FGDC + else if ((response.indexOf('id="Metadata"') == -1)) { + viewRef.$el.addClass("container no-stylesheet"); + + if (viewRef.model.get("indexed")) { + viewRef.renderMetadataFromIndex(); + return; + } + } - //Now show the response from the view service - viewRef.$(viewRef.metadataContainer).html(response); + //Now show the response from the view service + viewRef.$(viewRef.metadataContainer).html(response); - //If there is no info from the index and there is no metadata doc rendered either, then display a message - if (viewRef.$el.is(".no-stylesheet") && viewRef.model.get("archived") && !viewRef.model.get("indexed")) - viewRef.$(viewRef.metadataContainer).prepend(viewRef.alertTemplate({ msg: "There is limited metadata about this dataset since it has been archived." })); + //If there is no info from the index and there is no metadata doc rendered either, then display a message + if (viewRef.$el.is(".no-stylesheet") && viewRef.model.get("archived") && !viewRef.model.get("indexed")) + viewRef.$(viewRef.metadataContainer).prepend(viewRef.alertTemplate({ msg: "There is limited metadata about this dataset since it has been archived." })); - viewRef.alterMarkup(); + viewRef.alterMarkup(); - viewRef.trigger("metadataLoaded"); + viewRef.trigger("metadataLoaded"); - //Add a map of the spatial coverage - if (gmaps) viewRef.insertSpatialCoverageMap(); + //Add a map of the spatial coverage + if (gmaps) viewRef.insertSpatialCoverageMap(); - // Injects Clipboard objects into DOM elements returned from the View Service - viewRef.insertCopiables(); + // Injects Clipboard objects into DOM elements returned from the View Service + viewRef.insertCopiables(); + } + } catch (e) { + console.log("Error rendering metadata from the view service", e); + console.log("Response from the view service: ", response); + viewRef.renderMetadataFromIndex(); } }, error: function (xhr, textStatus, errorThrown) { From 703883ee83cd6907c15a3235c8be2830c4be5287 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 26 Oct 2023 16:44:17 -0400 Subject: [PATCH 28/43] Prevent weird TOC placement in portals - Add min height to markdown sections with portals - Also rename methods from postRender to what they do, because backbone calls these methods automatically even though this isn't documented! fixes #2195 --- src/js/themes/dataone/css/metacatui.css | 4 ++++ src/js/views/MarkdownView.js | 22 ++++------------------ src/js/views/TOCView.js | 20 +++++++++++++------- src/js/views/portals/PortalSectionView.js | 4 +--- 4 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/js/themes/dataone/css/metacatui.css b/src/js/themes/dataone/css/metacatui.css index 625095593..90614feb4 100644 --- a/src/js/themes/dataone/css/metacatui.css +++ b/src/js/themes/dataone/css/metacatui.css @@ -479,6 +479,10 @@ width: 100%; color: var(--c-neutral-8); } +.toc-view + .markdown { + min-height: 90vh; +} + /* FORM/INPUT CUSTOMIZATIONS -------------------------------------------------- */ .depth { diff --git a/src/js/views/MarkdownView.js b/src/js/views/MarkdownView.js index ada6ecd33..41be210b3 100644 --- a/src/js/views/MarkdownView.js +++ b/src/js/views/MarkdownView.js @@ -88,7 +88,7 @@ define([ "jquery", "underscore", "backbone", * render - Renders the MarkdownView; converts markdown to HTML and * displays it. */ - render: function() { + render: function () { // Show a loading message while we render the markdown to HTML this.$el.html(this.loadingTemplate({ @@ -143,15 +143,10 @@ define([ "jquery", "underscore", "backbone", this.$el.html(this.template({ markdown: htmlFromMD })); if( this.showTOC ){ - this.listenToOnce(this, "TOCRendered", function(){ - this.trigger("mdRendered"); - this.postRender(); - }); this.renderTOC(); - } else { - this.trigger("mdRendered"); - this.postRender(); } + + this.trigger("mdRendered"); }); @@ -162,15 +157,6 @@ define([ "jquery", "underscore", "backbone", }, - postRender: function(){ - if(this.tocView){ - this.tocView.postRender(); - } else { - this.listenToOnce(this, "TOCRendered", function(){ - this.tocView.postRender(); - }); - } - }, /** * listRequiredExtensions - test which extensions are needed, then load @@ -378,7 +364,7 @@ define([ "jquery", "underscore", "backbone", view.$el.addClass("span9"); } - view.trigger("TOCRendered"); + view.tocView.setAffix(); }); diff --git a/src/js/views/TOCView.js b/src/js/views/TOCView.js index bcfeb4dca..1e68b66db 100644 --- a/src/js/views/TOCView.js +++ b/src/js/views/TOCView.js @@ -121,7 +121,6 @@ define(["jquery", } - var view = this; return this; }, @@ -278,11 +277,11 @@ define(["jquery", // Add scroll spy $("body").off("activate"); - $("body").on("activate", function(e){ + $("body").on("activate", function (e) { view.scrollSpyExtras(e); }); $(window).off("resize"); - $(window).on("resize", function(){ + $(window).on("resize", function () { $spy.scrollspy("refresh"); }); @@ -294,19 +293,26 @@ define(["jquery", /** - * affixTOC - description + * Adds and refreshes bootstrap's affix functionality. This function + * should be called after the DOM has been rendered or updated. Renamed + * from postRender to avoid it being called automatically by Backbone. + * @since x.x.x */ - postRender: function(){ + setAffix: function(){ try { var isVisible = this.$el.find(":visible").length > 0; - if(this.affix === true && isVisible){ + if(!isVisible || !this.$el.offset()){ + return; + } + + if (this.affix === true) { this.$el.affix({ offset: this.$el.offset().top }); } - if(this.addScrollspy && isVisible){ + if(this.addScrollspy){ this.renderScrollspy(); } diff --git a/src/js/views/portals/PortalSectionView.js b/src/js/views/portals/PortalSectionView.js index 38da5f54e..afb164607 100644 --- a/src/js/views/portals/PortalSectionView.js +++ b/src/js/views/portals/PortalSectionView.js @@ -144,9 +144,7 @@ define(["jquery", } }); - if(this.markdownView){ - this.markdownView.postRender(); - } + this.markdownView?.tocView?.setAffix(); }, /** From 713f5288cf6340ff40473b8182bce108b60b1bf1 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 26 Oct 2023 18:17:15 -0400 Subject: [PATCH 29/43] Adjust height of feature info panel in Cesium map again after content has been loaded. Fixes #2192 --- src/js/views/maps/FeatureInfoView.js | 38 +++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/js/views/maps/FeatureInfoView.js b/src/js/views/maps/FeatureInfoView.js index ada1774e6..7323aec67 100644 --- a/src/js/views/maps/FeatureInfoView.js +++ b/src/js/views/maps/FeatureInfoView.js @@ -256,6 +256,8 @@ define( try { + const view = this; + // Elements to update const title = this.getFeatureTitle() const iFrame = this.elements.iFrame @@ -275,12 +277,17 @@ define( this.elements.title.innerHTML = title // Update the iFrame content - iFrame.height = 0; this.getContent().then(function (html) { iFrameDiv.innerHTML = html; - const maxHeight = window.innerHeight - 275; - const scrollHeight = iFrame.contentWindow.document.body.scrollHeight + 5; - iFrame.height = scrollHeight > maxHeight ? maxHeight : scrollHeight; + view.updateIFrameHeight(); + // Not the ideal solution, but check the height of the iFrame + // again after some time to allow external content to load. This + // is necessary for content that loads asynchronously, like + // images. Difficult to set listeners for this, since the content + // may be from a different domain. + setTimeout(function () { + view.updateIFrameHeight(); + }, 850); }) // Show or hide the layer details button, update the text @@ -299,6 +306,29 @@ define( } }, + /** + * Update the height of the iFrame to match the height of the content + * within it. + * @param {number} [height] The height to set the iFrame to. If no + * height is provided, then the height of the content within the iFrame + * will be used. + * @param {boolean} [limit=true] Whether or not to limit the height of + * the iFrame to the height of the window, minus 275px. + * @since x.x.x + */ + updateIFrameHeight: function (height, limit = true) { + const iFrame = this.elements?.iFrame; + if (!iFrame) return; + if ((!height && height !== 0) || height < 0) { + height = iFrame.contentWindow.document.body.scrollHeight + 5; + } + if (limit) { + const maxHeight = window.innerHeight - 275; + height = height > maxHeight ? maxHeight : height; + } + iFrame.style.height = height + "px"; + }, + /** * Get the inner HTML content to insert into the iFrame. The content will vary * based on the feature and if there is a template set on the parent Map Asset From 187351c027f4b679f1e04b7f9eb414ed06adab5c Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 26 Oct 2023 18:22:57 -0400 Subject: [PATCH 30/43] Allow all users to set datasets to private on KNB Fixes #2215 --- src/js/themes/knb/config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/themes/knb/config.js b/src/js/themes/knb/config.js index 02a702cfc..9f81191fe 100644 --- a/src/js/themes/knb/config.js +++ b/src/js/themes/knb/config.js @@ -45,7 +45,6 @@ MetacatUI.AppConfig = Object.assign({ read: true }], hiddenSubjectsInAccessPolicy: ["CN=knb-data-admins,DC=dataone,DC=org"], - showDatasetPublicToggleForSubjects: ["CN=knb-data-admins,DC=dataone,DC=org"], allowChangeRightsHolder: false, enableMeasurementTypeView: true, From 938d0e9995a84d10b4bad24332c856e0a509778f Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 1 Nov 2023 16:12:24 -0400 Subject: [PATCH 31/43] Standardize spacing add docs to EMLGeoCoverageView Issue #2159 --- src/js/views/metadata/EMLGeoCoverageView.js | 572 +++++++++++--------- 1 file changed, 321 insertions(+), 251 deletions(-) diff --git a/src/js/views/metadata/EMLGeoCoverageView.js b/src/js/views/metadata/EMLGeoCoverageView.js index 6b404231a..ba5245566 100644 --- a/src/js/views/metadata/EMLGeoCoverageView.js +++ b/src/js/views/metadata/EMLGeoCoverageView.js @@ -1,253 +1,323 @@ /* global define */ -define(['underscore', 'jquery', 'backbone', - 'models/metadata/eml211/EMLGeoCoverage', - 'text!templates/metadata/EMLGeoCoverage.html'], - function (_, $, Backbone, EMLGeoCoverage, EMLGeoCoverageTemplate) { - - /** - * @class EMlGeoCoverageView - * @classdesc The EMLGeoCoverage renders the content of an EMLGeoCoverage model - * @classcategory Views/Metadata - * @extends Backbone.View - */ - var EMLGeoCoverageView = Backbone.View.extend( - /** @lends EMLGeoCoverageView.prototype */{ - - type: "EMLGeoCoverageView", - - tagName: "div", - - className: "row-fluid eml-geocoverage", - - attributes: { - "data-category": "geoCoverage" - }, - - editTemplate: _.template(EMLGeoCoverageTemplate), - - initialize: function (options) { - if (!options) - var options = {}; - - this.isNew = options.isNew || (options.model ? false : true); - this.model = options.model || new EMLGeoCoverage(); - this.edit = options.edit || false; - }, - - events: { - "change": "updateModel", - "mouseover .remove": "toggleRemoveClass", - "mouseout .remove": "toggleRemoveClass" - }, - - render: function (e) { - //Save the view and model on the element - this.$el.data({ - model: this.model, - view: this - }); - - this.$el.html(this.editTemplate({ - edit: this.edit, - model: this.model.toJSON() - })); - - if (this.isNew) { - this.$el.addClass("new"); - } - - return this; - }, - - /** - * Updates the model. - * If this is called from the user switching between latitude and longitude boxes, - * we check to see if the input was valid and display any errors if we need to. - * - * @param e The event +define([ + "underscore", + "jquery", + "backbone", + "models/metadata/eml211/EMLGeoCoverage", + "text!templates/metadata/EMLGeoCoverage.html", +], function (_, $, Backbone, EMLGeoCoverage, EMLGeoCoverageTemplate) { + /** + * @class EMlGeoCoverageView + * @classdesc The EMLGeoCoverage renders the content of an EMLGeoCoverage + * model + * @classcategory Views/Metadata + * @extends Backbone.View + */ + var EMLGeoCoverageView = Backbone.View.extend( + /** @lends EMLGeoCoverageView.prototype */ { + type: "EMLGeoCoverageView", + + /** + * The HTML tag name for this view element + * @type {string} + * @default "div" + */ + tagName: "div", + + /** + * The class names to add to this view's HTML element + */ + className: "row-fluid eml-geocoverage", + + /** + * Attributes for the HTML element. + */ + attributes: { + "data-category": "geoCoverage", + }, + + /** + * Events applied to this view's HTML elements by Backbone. + */ + events: { + change: "updateModel", // <- TODO: does this work? + "mouseover .remove": "toggleRemoveClass", + "mouseout .remove": "toggleRemoveClass", + }, + + /** + * The template to use for this view in edit mode + */ + editTemplate: _.template(EMLGeoCoverageTemplate), + + /** + * Initializes the EMLGeoCoverageView + * @param {Object} options - A literal object with options to pass to the + * view + * @param {EMLGeoCoverage} options.model - The EMLGeoCoverage model to + * render + * @param {boolean} options.edit - Flag to toggle whether this view is in + * edit mode + * @param {boolean} options.isNew - Flag to toggle whether this view is + * new + */ + initialize: function (options) { + if (!options) var options = {}; + + this.isNew = options.isNew || (options.model ? false : true); + this.model = options.model || new EMLGeoCoverage(); + this.edit = options.edit || false; + }, + + /** + * Renders the EMLGeoCoverageView + * @returns {EMLGeoCoverageView} Returns the view + */ + render: function () { + try { + // Save the view and model on the element + this.$el.data({ + model: this.model, + view: this, + }); + + this.$el.html( + this.editTemplate({ + edit: this.edit, + model: this.model.toJSON(), + }) + ); + + if (this.isNew) { + this.$el.addClass("new"); + } + + return this; + } catch (e) { + console.log("Error rendering EMLGeoCoverageView: ", e); + return this; + } + }, + + /** + * Updates the model. If this is called from the user switching between + * latitude and longitude boxes, we check to see if the input was valid + * and display any errors if we need to. + * + * @param {Event} e - The event that triggered this function + */ + updateModel: function (e) { + console.log("updateModel", e); + if (!e) return false; + + e.preventDefault(); + + //Get the attribute and value + var element = $(e.target), + value = element.val(), + attribute = element.attr("data-attribute"); + + //Get the attribute that was changed + if (!attribute) return false; + + var emlModel = this.model.getParentEML(); + if (emlModel) { + value = emlModel.cleanXMLText(value); + } + + //Are the NW and SE points the same? i.e. is this a single point and not + //a box? + var isSinglePoint = + this.model.get("north") != null && + this.model.get("north") == this.model.get("south") && + this.model.get("west") != null && + this.model.get("west") == this.model.get("east"), + hasEmptyInputs = + this.$("[data-attribute='north']").val() == "" || + this.$("[data-attribute='south']").val() == "" || + this.$("[data-attribute='west']").val() == "" || + this.$("[data-attribute='east']").val() == ""; + + //Update the model + if (value == "") this.model.set(attribute, null); + else this.model.set(attribute, value); + + //If the NW and SE points are the same point... + if (isSinglePoint && hasEmptyInputs) { + /* If the user updates one of the empty number inputs, then we can + * assume they do not want a single point and are attempting to + * enter a second point. So we should empty the value from the model + * for the corresponding coordinate For example, if the UI shows a + * lat,long pair of NW: [10] [30] SE: [ ] [ ] then the model values + * would be N: 10, W: 30, S: 10, E: 30 if the user updates that to: + * NW: [10] [30] SE: [5] [ ] then we want to remove the "east" value + * of "30", so the model would be: N: 10, W: 30, S: 5, E: null + */ + if ( + attribute == "north" && + this.$("[data-attribute='west']").val() == "" + ) + this.model.set("west", null); + else if ( + attribute == "south" && + this.$("[data-attribute='east']").val() == "" + ) + this.model.set("east", null); + else if ( + attribute == "east" && + this.$("[data-attribute='south']").val() == "" + ) + this.model.set("south", null); + else if ( + attribute == "west" && + this.$("[data-attribute='north']").val() == "" + ) + this.model.set("north", null); + /* + * If the user removes one of the latitude or longitude values, reset + * the opposite point + */ else if ( + ((attribute == "north" && this.model.get("north") == null) || + (attribute == "west" && this.model.get("west") == null)) && + this.$("[data-attribute='south']").val() == "" && + this.$("[data-attribute='east']").val() == "" + ) { + this.model.set("south", null); + this.model.set("east", null); + } else if ( + ((attribute == "south" && this.model.get("south") == null) || + (attribute == "east" && this.model.get("east") == null)) && + this.$("[data-attribute='north']").val() == "" && + this.$("[data-attribute='west']").val() == "" + ) { + this.model.set("north", null); + this.model.set("west", null); + } else if (attribute == "north" && this.model.get("north") != null) + /* Otherwise, if the non-empty number inputs are updated, we simply + * update the corresponding value in the other point */ - updateModel: function (e) { - if (!e) return false; - - e.preventDefault(); - - //Get the attribute and value - var element = $(e.target), - value = element.val(), - attribute = element.attr("data-attribute"); - - //Get the attribute that was changed - if (!attribute) return false; - - var emlModel = this.model.getParentEML(); - if(emlModel){ - value = emlModel.cleanXMLText(value); - } - - //Are the NW and SE points the same? i.e. is this a single point and not a box? - var isSinglePoint = (this.model.get("north") != null && this.model.get("north") == this.model.get("south")) && - (this.model.get("west") != null && this.model.get("west") == this.model.get("east")), - hasEmptyInputs = this.$("[data-attribute='north']").val() == "" || - this.$("[data-attribute='south']").val() == "" || - this.$("[data-attribute='west']").val() == "" || - this.$("[data-attribute='east']").val() == ""; - - //Update the model - if (value == "") - this.model.set(attribute, null); - else - this.model.set(attribute, value); - - //If the NW and SE points are the same point... - if (isSinglePoint && hasEmptyInputs) { - /* If the user updates one of the empty number inputs, then we can assume they do not - * want a single point and are attempting to enter a second point. So we should empty the - * value from the model for the corresponding coordinate - * For example, if the UI shows a lat,long pair of NW: [10] [30] SE: [ ] [ ] then the model - * values would be N: 10, W: 30, S: 10, E: 30 - * if the user updates that to: NW: [10] [30] SE: [5] [ ] - * then we want to remove the "east" value of "30", so the model would be: N: 10, W: 30, S: 5, E: null - */ - if (attribute == "north" && this.$("[data-attribute='west']").val() == "") - this.model.set("west", null); - else if (attribute == "south" && this.$("[data-attribute='east']").val() == "") - this.model.set("east", null); - else if (attribute == "east" && this.$("[data-attribute='south']").val() == "") - this.model.set("south", null); - else if (attribute == "west" && this.$("[data-attribute='north']").val() == "") - this.model.set("north", null); - - /* - * If the user removes one of the latitude or longitude values, reset the opposite point - */ - else if (((attribute == "north" && this.model.get("north") == null) || - (attribute == "west" && this.model.get("west") == null)) && - (this.$("[data-attribute='south']").val() == "" && - this.$("[data-attribute='east']").val() == "")) { - this.model.set("south", null); - this.model.set("east", null); - } else if (((attribute == "south" && this.model.get("south") == null) || - (attribute == "east" && this.model.get("east") == null)) && - (this.$("[data-attribute='north']").val() == "" && this.$("[data-attribute='west']").val() == "")) { - this.model.set("north", null); - this.model.set("west", null); - } - /* Otherwise, if the non-empty number inputs are updated, - * we simply update the corresponding value in the other point - */ - else if (attribute == "north" && this.model.get("north") != null) - this.model.set("south", value); - else if (attribute == "south" && this.model.get("south") != null) - this.model.set("north", value); - else if (attribute == "west" && this.model.get("west") != null) - this.model.set("east", value); - else if (attribute == "east" && this.model.get("east") != null) - this.model.set("west", value); - } - else { - - //Find out if we are missing a complete NW or SE point - var isMissingNWPoint = (this.model.get("north") == null && this.model.get("west") == null), - isMissingSEPoint = (this.model.get("south") == null && this.model.get("east") == null); - - // If there is a full NW point but no SE point, we can assume the user wants a single point and - // so we will copy the NW values to the SE - if (this.model.get("north") != null && this.model.get("west") != null && isMissingSEPoint) { - this.model.set("south", this.model.get("north")); - this.model.set("east", this.model.get("west")); - } - // Same for when there is a SE point but no NW point - else if (this.model.get("south") != null && this.model.get("east") != null && isMissingNWPoint) { - this.model.set("north", this.model.get("south")); - this.model.set("west", this.model.get("east")); - } - } - - - // Validate the coordinate boxes - //this.validateCoordinates(e); - - //If this model is part of the EML inside the root data package, mark the package as changed - if (this.model.get("parentModel")) { - if (this.model.get("parentModel").type == "EML" && _.contains(MetacatUI.rootDataPackage.models, this.model.get("parentModel"))) { - MetacatUI.rootDataPackage.packageModel.set("changed", true); - } - } - - this.validate(); - }, - - /** - * Checks to see if any error messages need to be removed. If not, then it performs validation - * across the row and displays any errors. This id called when the user clicks out of an edit box - * on to the page. - * - * @param e The event - * @param options - */ - validate: function (e, options) { - - //Query for the EMlGeoCoverageView element that the user is actively interacting with - var activeGeoCovEl = $(document.activeElement).parents(".eml-geocoverage"); - - //If the user is not actively in this view, then exit - if (activeGeoCovEl.length && activeGeoCovEl[0] == this.el) - return; - - //If the model is valid, then remove error styling and exit - if( this.model.isValid() ) { - this.$(".error").removeClass("error"); - this.$el.removeClass("error"); - this.$(".notification").empty(); - this.model.trigger("valid"); - - return; - } - else{ - - this.showValidation(); - - } - - }, - - /* - * Resets the error messaging and displays the current error messages for this model - * This function is used by the EML211EditorView during the package validation process - */ - showValidation: function(){ - this.$(".error").removeClass("error"); - this.$el.removeClass("error"); - this.$(".notification").empty(); - - var errorMessages = ""; - - for( field in this.model.validationError ){ - this.$("[data-attribute='" + field + "']").addClass("error"); - - errorMessages += this.model.validationError[field] + " "; - } - - this.$(".notification").text(errorMessages).addClass("error"); - }, - - /** - * Highlight what will be removed when the remove icon is hovered over - * - */ - toggleRemoveClass: function () { - this.$el.toggleClass("remove-preview"); - }, - - /** - * Unmarks this view as new - * - */ - notNew: function () { - this.$el.removeClass("new"); - this.isNew = false; - } - }); - - return EMLGeoCoverageView; - }); + this.model.set("south", value); + else if (attribute == "south" && this.model.get("south") != null) + this.model.set("north", value); + else if (attribute == "west" && this.model.get("west") != null) + this.model.set("east", value); + else if (attribute == "east" && this.model.get("east") != null) + this.model.set("west", value); + } else { + //Find out if we are missing a complete NW or SE point + var isMissingNWPoint = + this.model.get("north") == null && this.model.get("west") == null, + isMissingSEPoint = + this.model.get("south") == null && this.model.get("east") == null; + + // If there is a full NW point but no SE point, we can assume the user + // wants a single point and so we will copy the NW values to the SE + if ( + this.model.get("north") != null && + this.model.get("west") != null && + isMissingSEPoint + ) { + this.model.set("south", this.model.get("north")); + this.model.set("east", this.model.get("west")); + } + // Same for when there is a SE point but no NW point + else if ( + this.model.get("south") != null && + this.model.get("east") != null && + isMissingNWPoint + ) { + this.model.set("north", this.model.get("south")); + this.model.set("west", this.model.get("east")); + } + } + + // Validate the coordinate boxes this.validateCoordinates(e); + + //If this model is part of the EML inside the root data package, mark + //the package as changed + if (this.model.get("parentModel")) { + if ( + this.model.get("parentModel").type == "EML" && + _.contains( + MetacatUI.rootDataPackage.models, + this.model.get("parentModel") + ) + ) { + MetacatUI.rootDataPackage.packageModel.set("changed", true); + } + } + + this.validate(); + }, + + /** + * Checks to see if any error messages need to be removed. If not, then it + * performs validation across the row and displays any errors. This id + * called when the user clicks out of an edit box on to the page. + * + * @param e The event + * @param options + */ + validate: function (e, options) { + //Query for the EMlGeoCoverageView element that the user is actively + //interacting with + var activeGeoCovEl = $(document.activeElement).parents( + ".eml-geocoverage" + ); + + //If the user is not actively in this view, then exit + if (activeGeoCovEl.length && activeGeoCovEl[0] == this.el) return; + + //If the model is valid, then remove error styling and exit + if (this.model.isValid()) { + this.$(".error").removeClass("error"); + this.$el.removeClass("error"); + this.$(".notification").empty(); + this.model.trigger("valid"); + + return; + } else { + this.showValidation(); + } + }, + + /* + * Resets the error messaging and displays the current error messages for + * this model This function is used by the EML211EditorView during the + * package validation process + */ + showValidation: function () { + this.$(".error").removeClass("error"); + this.$el.removeClass("error"); + this.$(".notification").empty(); + + var errorMessages = ""; + + for (field in this.model.validationError) { + this.$("[data-attribute='" + field + "']").addClass("error"); + + errorMessages += this.model.validationError[field] + " "; + } + + this.$(".notification").text(errorMessages).addClass("error"); + }, + + /** + * Highlight what will be removed when the remove icon is hovered over. + */ + toggleRemoveClass: function () { + this.$el.toggleClass("remove-preview"); + }, + + /** + * Unmark this view as news + */ + notNew: function () { + this.$el.removeClass("new"); + this.isNew = false; + }, + } + ); + + return EMLGeoCoverageView; +}); From e546ddae980ee0ead17f57071d272ad0928dd32e Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 1 Nov 2023 18:15:08 -0400 Subject: [PATCH 32/43] Standardize spacing & docs to EMLGeoCoverage model Issue #2159 --- .../models/metadata/eml211/EMLGeoCoverage.js | 953 ++++++++++-------- 1 file changed, 516 insertions(+), 437 deletions(-) diff --git a/src/js/models/metadata/eml211/EMLGeoCoverage.js b/src/js/models/metadata/eml211/EMLGeoCoverage.js index 192694470..f0b66f84e 100644 --- a/src/js/models/metadata/eml211/EMLGeoCoverage.js +++ b/src/js/models/metadata/eml211/EMLGeoCoverage.js @@ -1,440 +1,519 @@ /* global define */ -define(['jquery', 'underscore', 'backbone', 'models/DataONEObject'], - function ($, _, Backbone, DataONEObject) { - - /** - * @class EMLGeoCoverage - * @classdesc A description of geographic coverage of a dataset, per the EML 2.1.1 metadata standard - * @classcategory Models/Metadata/EML211 - * @extends Backbone.Model - * @constructor - */ - var EMLGeoCoverage = Backbone.Model.extend( - /** @lends EMLGeoCoverage.prototype */{ - - defaults: { - objectXML: null, - objectDOM: null, - parentModel: null, - description: null, - east: null, - north: null, - south: null, - west: null - }, - - initialize: function (attributes) { - if (attributes && attributes.objectDOM) this.set(this.parse(attributes.objectDOM)); - - //specific attributes to listen to - this.on("change:description " + - "change:east " + - "change:west " + - "change:south " + - "change:north", - this.trickleUpChange); - }, - - /* - * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML). - * Used during parse() and serialize() - */ - nodeNameMap: function () { - return { - "altitudemaximum": "altitudeMaximum", - "altitudeminimum": "altitudeMinimum", - "altitudeunits": "altitudeUnits", - "boundingaltitudes": "boundingAltitudes", - "boundingcoordinates": "boundingCoordinates", - "eastboundingcoordinate": "eastBoundingCoordinate", - "geographiccoverage": "geographicCoverage", - "geographicdescription": "geographicDescription", - "northboundingcoordinate": "northBoundingCoordinate", - "southboundingcoordinate": "southBoundingCoordinate", - "westboundingcoordinate": "westBoundingCoordinate" - } - }, - - /** Based on this example serialization - - Rhine-Main-Observatory - - 9.0005 - 9.0005 - 50.1600 - 50.1600 - - - **/ - parse: function (objectDOM) { - - var modelJSON = {}; - - if (!objectDOM) { - if (this.get("objectDOM")) - var objectDOM = this.get("objectDOM"); - else - return {}; - } - - //Create a jQuery object of the DOM - var $objectDOM = $(objectDOM); - - //Get the geographic description - modelJSON.description = $objectDOM.children('geographicdescription').text(); - - //Get the bounding coordinates - var boundingCoordinates = $objectDOM.children('boundingcoordinates'); - if (boundingCoordinates) { - modelJSON.east = boundingCoordinates.children('eastboundingcoordinate').text().replace("+", ""); - modelJSON.north = boundingCoordinates.children('northboundingcoordinate').text().replace("+", ""); - modelJSON.south = boundingCoordinates.children('southboundingcoordinate').text().replace("+", ""); - modelJSON.west = boundingCoordinates.children('westboundingcoordinate').text().replace("+", ""); - } - - return modelJSON; - }, - - serialize: function () { - var objectDOM = this.updateDOM(), - xmlString = objectDOM.outerHTML; - - //Camel-case the XML - xmlString = this.formatXML(xmlString); - - return xmlString; - }, - - /* - * Makes a copy of the original XML DOM and updates it with the new values from the model. - */ - updateDOM: function () { - var objectDOM; - - if (!this.isValid()) { - return ""; - } - - if (this.get("objectDOM")) { - objectDOM = $(this.get("objectDOM").cloneNode(true)); - } else { - objectDOM = $(document.createElement("geographiccoverage")); - } - - //If only one point is given, make sure both points are the same - if ((this.get("north") && this.get("west")) && (!this.get("south") && !this.get("east"))) { - this.set("south", this.get("north")); - this.set("east", this.get("west")); - } - else if ((this.get("south") && this.get("east")) && (!this.get("north") && !this.get("west"))) { - this.set("north", this.get("south")); - this.set("west", this.get("east")); - } - - // Description - if (!objectDOM.children("geographicdescription").length) - objectDOM.append($(document.createElement("geographicdescription")).text(this.get("description"))); - else - objectDOM.children("geographicdescription").text(this.get("description")); - - // Create the bounding coordinates element - var boundingCoordinates = objectDOM.find("boundingcoordinates"); - if (!boundingCoordinates.length) { - boundingCoordinates = document.createElement("boundingcoordinates"); - objectDOM.append(boundingCoordinates); - } - - //Empty out the coordinates first - $(boundingCoordinates).empty(); - - //Add the four coordinate values - $(boundingCoordinates).append($(document.createElement("westboundingcoordinate")).text(this.get("west")), - $(document.createElement("eastboundingcoordinate")).text(this.get("east")), - $(document.createElement("northboundingcoordinate")).text(this.get("north")), - $(document.createElement("southboundingcoordinate")).text(this.get("south"))); - - return objectDOM; - }, - - /** - * Sometimes we'll need to add a space between error messages, but only if an - * error has already been triggered. Use addSpace to accomplish this. - * - * @param {string} msg The string that will be appended - * @param {bool} front A flag that when set will append the whitespace to the front of 'msg' - * @return {string} The string that was passed in, 'msg', with whitespace appended - */ - addSpace: function (msg, front) { - if (typeof front === "undefined") { - front = false; - } - if (msg) { - if (front) { - return (" " + msg); - } - return msg += " "; - } - return msg; - }, - - /** - * Because the same error messages are used in a couple of different places, we centralize the strings - * and access here. - * - * @param {string} area Specifies the area that the error message belongs to. - * Browse through the switch statement to find the one you need. - * @return {string} The error message - */ - getErrorMessage: function (area) { - switch (area) { - case "north": - return "The Northwest latitude must be between -90 and 90."; - break; - case "east": - return "The Southeast longitude must be between -180 and 180."; - break; - case "south": - return "The Southeast latitude must be between -90 and 90."; - break; - case "west": - return "The Northwest longitude must be between -180 and 180."; - break; - case "missing": - return "Each coordinate must include a latitude AND longitude."; - break; - case "description": - return "Each location must have a description."; - break; - case "needPair": - return "Each location description must have at least one coordinate pair."; - break; - default: - return ""; - break; - } - }, - - /** - * Generates an object that describes the current state of each latitude - * and longitude box. The status includes whether there is a value and - * if the value is valid. - * - * @return {array} An array containing the current state of each coordinate box - */ - getCoordinateStatus: function () { - var north = this.get("north"), - east = this.get("east"), - south = this.get("south"), - west = this.get("west"); - - return { - 'north': { - isSet: typeof north !== "undefined" && north != null && north !== "", - isValid: this.validateCoordinate(north, -90, 90) - }, - 'east': { - isSet: typeof east !== "undefined" && east != null && east !== "", - isValid: this.validateCoordinate(east, -180, 180) - }, - 'south': { - isSet: typeof south !== "undefined" && south != null && south !== "", - isValid: this.validateCoordinate(south, -90, 90) - }, - 'west': { - isSet: typeof west !== "undefined" && west != null && west !== "", - isValid: this.validateCoordinate(west, -180, 180) - }, - } - }, - - /** - * Checks the status object for conditions that warrant an error message to the user. This is called - * during the validation processes (validate() and updateModel()) after the status object has been - * created by getCoordinateStatus(). - * - * @param status The status object, holding the state of the coordinates - * @return {string} Any errors that need to be displayed to the user - */ - generateStatusErrors: function (status) { - var errorMsg = ""; - - // Northwest Latitude - if (status.north.isSet && !status.north.isValid) { - errorMsg = this.addSpace(errorMsg); - errorMsg += this.getErrorMessage("north"); - } - // Northwest Longitude - if (status.west.isSet && !status.west.isValid) { - errorMsg = this.addSpace(errorMsg); - errorMsg += this.getErrorMessage("west"); - } - // Southeast Latitude - if (status.south.isSet && !status.south.isValid) { - errorMsg = this.addSpace(errorMsg); - errorMsg += this.getErrorMessage("south"); - } - // Southeast Longitude - if (status.east.isSet && !status.east.isValid) { - errorMsg = this.addSpace(errorMsg); - errorMsg += this.getErrorMessage("east"); - } - return errorMsg; - - }, - - /** - * This grabs the various location elements and validates the user input. In the case of an error, - * we append an error string (errMsg) so that we display all of the messages at the same time. This - * validates the entire location row by adding extra checks for a description and for coordinate pairs - * - * @return {string} The error messages that the user will see - */ - validate: function () { - var errors = {}; - - if (!this.get("description")) { - errors.description = this.getErrorMessage("description"); - } - - var pointStatuses = this.getCoordinateStatus(); -/* - if (!this.checkForPairs(pointStatuses)) { - errorMsg = this.addSpace(errorMsg); - errorMsg += this.getErrorMessage("needPair"); - } - - if( this.hasMissingPoint(pointStatuses) ) { - //errorMsg = this.addSpace(errorMsg); - errors += this.getErrorMessage("missing"); - } -*/ - // errorMsg += this.addSpace(this.generateStatusErrors(pointStatuses), true); - - if( !pointStatuses.north.isSet && !pointStatuses.south.isSet && - !pointStatuses.east.isSet && !pointStatuses.west.isSet){ - errors.north = this.getErrorMessage("needPair"); - errors.west = ""; - } - - //Check that all the values are correct - if( pointStatuses.north.isSet && !pointStatuses.north.isValid ) - errors.north = this.getErrorMessage("north"); - if( pointStatuses.south.isSet && !pointStatuses.south.isValid ) - errors.south = this.getErrorMessage("south"); - if( pointStatuses.east.isSet && !pointStatuses.east.isValid ) - errors.east = this.getErrorMessage("east"); - if( pointStatuses.west.isSet && !pointStatuses.west.isValid ) - errors.west = this.getErrorMessage("west"); - - if( pointStatuses.north.isSet && !pointStatuses.west.isSet ) - errors.west = this.getErrorMessage("missing"); - else if( !pointStatuses.north.isSet && pointStatuses.west.isSet ) - errors.north = this.getErrorMessage("missing"); - else if( pointStatuses.south.isSet && !pointStatuses.east.isSet ) - errors.east = this.getErrorMessage("missing"); - else if( !pointStatuses.south.isSet && pointStatuses.east.isSet ) - errors.south = this.getErrorMessage("missing"); - - if( Object.keys(errors).length ) - return errors; - else - return false; - }, - - /** - * Checks for any coordinates with missing counterparts. - * - * @param status The status of the coordinates - * @return {bool} True if there are missing coordinates, false otherwise - */ - hasMissingPoint: function (status) { - if ((status.north.isSet && !status.west.isSet) || - (!status.north.isSet && status.west.isSet)) { - return true - } else if ((status.south.isSet && !status.east.isSet) || - (!status.south.isSet && status.east.isSet)) { - return true; - } - - return false; - - }, - - /** - * Checks that there are either two or four coordinate values. If there aren't, - * it means that the user still needs to enter coordinates. - * - * @param status The current state of the coordinates - * @return {bool} True if there are pairs, false otherwise - */ - checkForPairs: function (status) { - var isSet = _.filter(status, function (coord) { return coord.isSet == true; }); - - if (isSet.length == 0) { - return false; - } - return true; - }, - - /** - * Validate a coordinate String by making sure it can be coerced into a number and - * is within the given bounds. - * Note: Min and max are inclusive - * - * @param value {string} The value of the edit area that will be validated - * @param min The minimum value that 'value' can be - * @param max The maximum value that 'value' can be - * @return {bool} True if the validation passed, otherwise false - */ - validateCoordinate: function (value, min, max) { - - if (typeof value === "undefined" || value === null || value === "" && isNaN(value)) { - return false; - } - - var parsed = Number(value); - - if (isNaN(parsed)) { - return false; - } - - if (parsed < min || parsed > max) { - return false; - } - - return true; - }, - - /* - * Climbs up the model heirarchy until it finds the EML model - * - * @return {EML211 or false} - Returns the EML 211 Model or false if not found - */ - getParentEML: function(){ - var emlModel = this.get("parentModel"), - tries = 0; - - while (emlModel.type !== "EML" && tries < 6){ - emlModel = emlModel.get("parentModel"); - tries++; - } - - if( emlModel && emlModel.type == "EML") - return emlModel; - else - return false; - - }, - - trickleUpChange: function () { - this.get("parentModel").trigger("change"); - this.get("parentModel").trigger("change:geoCoverage"); - }, - - formatXML: function (xmlString) { - return DataONEObject.prototype.formatXML.call(this, xmlString); - } +define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( + $, + _, + Backbone, + DataONEObject +) { + /** + * @class EMLGeoCoverage + * @classdesc A description of geographic coverage of a dataset, per the EML + * 2.1.1 metadata standard + * @classcategory Models/Metadata/EML211 + * @extends Backbone.Model + * @constructor + */ + var EMLGeoCoverage = Backbone.Model.extend( + /** @lends EMLGeoCoverage.prototype */ { + defaults: { + objectXML: null, + objectDOM: null, + parentModel: null, + description: null, + east: null, + north: null, + south: null, + west: null, + }, + + initialize: function (attributes) { + if (attributes && attributes.objectDOM) + this.set(this.parse(attributes.objectDOM)); + + //specific attributes to listen to + this.on( + "change:description " + + "change:east " + + "change:west " + + "change:south " + + "change:north", + this.trickleUpChange + ); + }, + + /* + * Maps the lower-case EML node names (valid in HTML DOM) to the + * camel-cased EML node names (valid in EML). Used during parse() and + * serialize() + */ + nodeNameMap: function () { + return { + altitudemaximum: "altitudeMaximum", + altitudeminimum: "altitudeMinimum", + altitudeunits: "altitudeUnits", + boundingaltitudes: "boundingAltitudes", + boundingcoordinates: "boundingCoordinates", + eastboundingcoordinate: "eastBoundingCoordinate", + geographiccoverage: "geographicCoverage", + geographicdescription: "geographicDescription", + northboundingcoordinate: "northBoundingCoordinate", + southboundingcoordinate: "southBoundingCoordinate", + westboundingcoordinate: "westBoundingCoordinate", + }; + }, + + /** + * Parses the objectDOM to populate this model with data. + * @param {Element} objectDOM - The EML object element + * @returns {Object} The EMLGeoCoverage data + * + * @example - Example input XML + * + * Rhine-Main-Observatory + * + * 9.0005 + * 9.0005 + * 50.1600 + * 50.1600 + * + * + */ + parse: function (objectDOM) { + var modelJSON = {}; + + if (!objectDOM) { + if (this.get("objectDOM")) var objectDOM = this.get("objectDOM"); + else return {}; + } + + //Create a jQuery object of the DOM + var $objectDOM = $(objectDOM); + + //Get the geographic description + modelJSON.description = $objectDOM + .children("geographicdescription") + .text(); + + //Get the bounding coordinates + var boundingCoordinates = $objectDOM.children("boundingcoordinates"); + if (boundingCoordinates) { + modelJSON.east = boundingCoordinates + .children("eastboundingcoordinate") + .text() + .replace("+", ""); + modelJSON.north = boundingCoordinates + .children("northboundingcoordinate") + .text() + .replace("+", ""); + modelJSON.south = boundingCoordinates + .children("southboundingcoordinate") + .text() + .replace("+", ""); + modelJSON.west = boundingCoordinates + .children("westboundingcoordinate") + .text() + .replace("+", ""); + } + + return modelJSON; + }, + + /** + * Converts this EMLGeoCoverage to XML + * @returns {string} The XML string + */ + serialize: function () { + + const objectDOM = this.updateDOM(); + let xmlString = objectDOM?.outerHTML; + if (!xmlString) xmlString = objectDOM?.[0]?.outerHTML; + + //Camel-case the XML + xmlString = this.formatXML(xmlString); + + return xmlString; + }, + + /* + * Makes a copy of the original XML DOM and updates it with the new values + * from the model. + */ + updateDOM: function () { + var objectDOM; + + if (!this.isValid()) { + return ""; + } + + if (this.get("objectDOM")) { + objectDOM = $(this.get("objectDOM").cloneNode(true)); + } else { + objectDOM = $(document.createElement("geographiccoverage")); + } + + //If only one point is given, make sure both points are the same + if ( + this.get("north") && + this.get("west") && + !this.get("south") && + !this.get("east") + ) { + this.set("south", this.get("north")); + this.set("east", this.get("west")); + } else if ( + this.get("south") && + this.get("east") && + !this.get("north") && + !this.get("west") + ) { + this.set("north", this.get("south")); + this.set("west", this.get("east")); + } + + // Description + if (!objectDOM.children("geographicdescription").length) + objectDOM.append( + $(document.createElement("geographicdescription")).text( + this.get("description") + ) + ); + else + objectDOM + .children("geographicdescription") + .text(this.get("description")); + + // Create the bounding coordinates element + var boundingCoordinates = objectDOM.find("boundingcoordinates"); + if (!boundingCoordinates.length) { + boundingCoordinates = document.createElement("boundingcoordinates"); + objectDOM.append(boundingCoordinates); + } + + //Empty out the coordinates first + $(boundingCoordinates).empty(); + + //Add the four coordinate values + $(boundingCoordinates).append( + $(document.createElement("westboundingcoordinate")).text( + this.get("west") + ), + $(document.createElement("eastboundingcoordinate")).text( + this.get("east") + ), + $(document.createElement("northboundingcoordinate")).text( + this.get("north") + ), + $(document.createElement("southboundingcoordinate")).text( + this.get("south") + ) + ); + + return objectDOM; + }, + + /** + * Sometimes we'll need to add a space between error messages, but only if + * an error has already been triggered. Use addSpace to accomplish this. + * + * @param {string} msg The string that will be appended + * @param {bool} front A flag that when set will append the whitespace to + * the front of 'msg' + * @return {string} The string that was passed in, 'msg', with whitespace + * appended + */ + addSpace: function (msg, front) { + if (typeof front === "undefined") { + front = false; + } + if (msg) { + if (front) { + return " " + msg; + } + return (msg += " "); + } + return msg; + }, + + /** + * Because the same error messages are used in a couple of different + * places, we centralize the strings and access here. + * + * @param {string} area Specifies the area that the error message belongs + * to. Browse through the switch statement to find the one you need. + * @return {string} The error message + */ + getErrorMessage: function (area) { + switch (area) { + case "north": + return "The Northwest latitude must be between -90 and 90."; + break; + case "east": + return "The Southeast longitude must be between -180 and 180."; + break; + case "south": + return "The Southeast latitude must be between -90 and 90."; + break; + case "west": + return "The Northwest longitude must be between -180 and 180."; + break; + case "missing": + return "Each coordinate must include a latitude AND longitude."; + break; + case "description": + return "Each location must have a description."; + break; + case "needPair": + return "Each location description must have at least one coordinate pair."; + break; + default: + return ""; + break; + } + }, + + /** + * Generates an object that describes the current state of each latitude + * and longitude box. The status includes whether there is a value and if + * the value is valid. + * + * @return {array} An array containing the current state of each + * coordinate box + */ + getCoordinateStatus: function () { + var north = this.get("north"), + east = this.get("east"), + south = this.get("south"), + west = this.get("west"); + + return { + north: { + isSet: + typeof north !== "undefined" && north != null && north !== "", + isValid: this.validateCoordinate(north, -90, 90), + }, + east: { + isSet: typeof east !== "undefined" && east != null && east !== "", + isValid: this.validateCoordinate(east, -180, 180), + }, + south: { + isSet: + typeof south !== "undefined" && south != null && south !== "", + isValid: this.validateCoordinate(south, -90, 90), + }, + west: { + isSet: typeof west !== "undefined" && west != null && west !== "", + isValid: this.validateCoordinate(west, -180, 180), + }, + }; + }, + + /** + * Checks the status object for conditions that warrant an error message + * to the user. This is called during the validation processes (validate() + * and updateModel()) after the status object has been created by + * getCoordinateStatus(). + * + * @param status The status object, holding the state of the coordinates + * @return {string} Any errors that need to be displayed to the user + */ + generateStatusErrors: function (status) { + var errorMsg = ""; + + // Northwest Latitude + if (status.north.isSet && !status.north.isValid) { + errorMsg = this.addSpace(errorMsg); + errorMsg += this.getErrorMessage("north"); + } + // Northwest Longitude + if (status.west.isSet && !status.west.isValid) { + errorMsg = this.addSpace(errorMsg); + errorMsg += this.getErrorMessage("west"); + } + // Southeast Latitude + if (status.south.isSet && !status.south.isValid) { + errorMsg = this.addSpace(errorMsg); + errorMsg += this.getErrorMessage("south"); + } + // Southeast Longitude + if (status.east.isSet && !status.east.isValid) { + errorMsg = this.addSpace(errorMsg); + errorMsg += this.getErrorMessage("east"); + } + return errorMsg; + }, + + /** + * This grabs the various location elements and validates the user input. + * In the case of an error, we append an error string (errMsg) so that we + * display all of the messages at the same time. This validates the entire + * location row by adding extra checks for a description and for + * coordinate pairs + * + * @return {string} The error messages that the user will see + */ + validate: function () { + var errors = {}; + + if (!this.get("description")) { + errors.description = this.getErrorMessage("description"); + } + + var pointStatuses = this.getCoordinateStatus(); + + // if (!this.checkForPairs(pointStatuses)) { + // errorMsg = this.addSpace(errorMsg); + // errorMsg += this.getErrorMessage("needPair"); + // } + + // if (this.hasMissingPoint(pointStatuses)) { + // errorMsg = this.addSpace(errorMsg); + // errors += this.getErrorMessage("missing"); + // } + + // errorMsg += this.addSpace( + // this.generateStatusErrors(pointStatuses), + // true + // ); + + if ( + !pointStatuses.north.isSet && + !pointStatuses.south.isSet && + !pointStatuses.east.isSet && + !pointStatuses.west.isSet + ) { + errors.north = this.getErrorMessage("needPair"); + errors.west = ""; + } + + //Check that all the values are correct + if (pointStatuses.north.isSet && !pointStatuses.north.isValid) + errors.north = this.getErrorMessage("north"); + if (pointStatuses.south.isSet && !pointStatuses.south.isValid) + errors.south = this.getErrorMessage("south"); + if (pointStatuses.east.isSet && !pointStatuses.east.isValid) + errors.east = this.getErrorMessage("east"); + if (pointStatuses.west.isSet && !pointStatuses.west.isValid) + errors.west = this.getErrorMessage("west"); + + if (pointStatuses.north.isSet && !pointStatuses.west.isSet) + errors.west = this.getErrorMessage("missing"); + else if (!pointStatuses.north.isSet && pointStatuses.west.isSet) + errors.north = this.getErrorMessage("missing"); + else if (pointStatuses.south.isSet && !pointStatuses.east.isSet) + errors.east = this.getErrorMessage("missing"); + else if (!pointStatuses.south.isSet && pointStatuses.east.isSet) + errors.south = this.getErrorMessage("missing"); + + if (Object.keys(errors).length) return errors; + else return false; + }, + + /** + * Checks for any coordinates with missing counterparts. + * + * @param status The status of the coordinates + * @return {bool} True if there are missing coordinates, false otherwise + */ + hasMissingPoint: function (status) { + if ( + (status.north.isSet && !status.west.isSet) || + (!status.north.isSet && status.west.isSet) + ) { + return true; + } else if ( + (status.south.isSet && !status.east.isSet) || + (!status.south.isSet && status.east.isSet) + ) { + return true; + } + + return false; + }, + + /** + * Checks that there are either two or four coordinate values. If there + * aren't, it means that the user still needs to enter coordinates. + * + * @param status The current state of the coordinates + * @return {bool} True if there are pairs, false otherwise + */ + checkForPairs: function (status) { + var isSet = _.filter(status, function (coord) { + return coord.isSet == true; }); - return EMLGeoCoverage; - }); + if (isSet.length == 0) { + return false; + } + return true; + }, + + /** + * Validate a coordinate String by making sure it can be coerced into a + * number and is within the given bounds. Note: Min and max are inclusive + * + * @param value {string} The value of the edit area that will be validated + * @param min The minimum value that 'value' can be + * @param max The maximum value that 'value' can be + * @return {bool} True if the validation passed, otherwise false + */ + validateCoordinate: function (value, min, max) { + if ( + typeof value === "undefined" || + value === null || + (value === "" && isNaN(value)) + ) { + return false; + } + + var parsed = Number(value); + + if (isNaN(parsed)) { + return false; + } + + if (parsed < min || parsed > max) { + return false; + } + + return true; + }, + + /** + * Climbs up the model hierarchy until it finds the EML model + * + * @return {EML211 or false} - Returns the EML 211 Model or false if not + * found + */ + getParentEML: function () { + var emlModel = this.get("parentModel"), + tries = 0; + + while (emlModel.type !== "EML" && tries < 6) { + emlModel = emlModel.get("parentModel"); + tries++; + } + + if (emlModel && emlModel.type == "EML") return emlModel; + else return false; + }, + + /** + * Apply the change event on the parent EML model + */ + trickleUpChange: function () { + this.get("parentModel").trigger("change"); + this.get("parentModel").trigger("change:geoCoverage"); + }, + + /** + * See DataONEObject.formatXML() + */ + formatXML: function (xmlString) { + return DataONEObject.prototype.formatXML.call(this, xmlString); + }, + } + ); + + return EMLGeoCoverage; +}); From 728c3da1700cee4d9d71303cd4abe95cb01b75c3 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 1 Nov 2023 19:34:17 -0400 Subject: [PATCH 33/43] Add tests for EMLGeoCoverage Issue #2159 --- .../models/metadata/eml211/EMLGeoCoverage.js | 6 +- src/js/views/metadata/EMLGeoCoverageView.js | 5 +- test/config/tests.json | 1 + .../metadata/eml211/EMLGeoCoverage.spec.js | 133 ++++++++++++++++++ 4 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js diff --git a/src/js/models/metadata/eml211/EMLGeoCoverage.js b/src/js/models/metadata/eml211/EMLGeoCoverage.js index f0b66f84e..c9b93a12e 100644 --- a/src/js/models/metadata/eml211/EMLGeoCoverage.js +++ b/src/js/models/metadata/eml211/EMLGeoCoverage.js @@ -502,8 +502,10 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( * Apply the change event on the parent EML model */ trickleUpChange: function () { - this.get("parentModel").trigger("change"); - this.get("parentModel").trigger("change:geoCoverage"); + const parentModel = this.get("parentModel"); + if (!parentModel) return; + parentModel.trigger("change"); + parentModel.trigger("change:geoCoverage"); }, /** diff --git a/src/js/views/metadata/EMLGeoCoverageView.js b/src/js/views/metadata/EMLGeoCoverageView.js index ba5245566..6c50c5ba1 100644 --- a/src/js/views/metadata/EMLGeoCoverageView.js +++ b/src/js/views/metadata/EMLGeoCoverageView.js @@ -107,7 +107,6 @@ define([ * @param {Event} e - The event that triggered this function */ updateModel: function (e) { - console.log("updateModel", e); if (!e) return false; e.preventDefault(); @@ -255,8 +254,8 @@ define([ * performs validation across the row and displays any errors. This id * called when the user clicks out of an edit box on to the page. * - * @param e The event - * @param options + * @param {Event} e - The event that triggered this function + * @param {Object} options - Validation options */ validate: function (e, options) { //Query for the EMlGeoCoverageView element that the user is actively diff --git a/test/config/tests.json b/test/config/tests.json index a558e0dec..842aa0e71 100644 --- a/test/config/tests.json +++ b/test/config/tests.json @@ -21,6 +21,7 @@ "./js/specs/unit/collections/metadata/eml/EMLMissingValueCodes.spec.js", "./js/specs/unit/models/metadata/eml211/EMLMissingValueCode.spec.js", "./js/specs/unit/models/metadata/eml211/EMLDistribution.spec.js", + "./js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js", "./js/specs/unit/models/maps/assets/CesiumImagery.spec.js", "./js/specs/unit/collections/maps/Geohashes.spec.js", "./js/specs/unit/models/connectors/Filters-Map.spec.js", diff --git a/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js b/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js new file mode 100644 index 000000000..69cf3e220 --- /dev/null +++ b/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js @@ -0,0 +1,133 @@ +define([ + "../../../../../../../../src/js/models/metadata/eml211/EMLGeoCoverage", +], function (EMLGeoCoverage) { + // Configure the Chai assertion library + var should = chai.should(); + var expect = chai.expect; + + describe("EMLGeoCoverage Test Suite", function () { + /* Set up */ + beforeEach(function () { + let testEML = ` + Rhine-Main-Observatory + + 9.0005 + 9.0005 + 50.1600 + 50.1600 + + `; + // remove ALL whitespace + testEML = testEML.replace(/\s/g, ""); + this.testEML = testEML; + }); + + /* Tear down */ + afterEach(function () { + delete this.testEML; + }); + + describe("Initialization", function () { + it("should create a EMLGeoCoverage instance", function () { + new EMLGeoCoverage().should.be.instanceof(EMLGeoCoverage); + }); + }); + + describe("parse()", function () { + it("should parse EML", function () { + + var emlGeoCoverage = new EMLGeoCoverage( + { objectDOM: this.testEML }, + { parse: true } + ); + + emlGeoCoverage + .get("description") + .should.equal("Rhine-Main-Observatory"); + emlGeoCoverage.get("east").should.equal("9.0005"); + emlGeoCoverage.get("north").should.equal("50.1600"); + emlGeoCoverage.get("south").should.equal("50.1600"); + emlGeoCoverage.get("west").should.equal("9.0005"); + }); + }); + + describe("serialize()", function () { + + it("should serialize to XML", function () { + var emlGeoCoverage = new EMLGeoCoverage( + { objectDOM: this.testEML }, + { parse: true } + ); + var xmlString = emlGeoCoverage.serialize(); + xmlString.should.equal(this.testEML); + }); + }); + + describe("validation", function () { + it("should get the status of the coordinates", function () { + var emlGeoCoverage = new EMLGeoCoverage( + { objectDOM: this.testEML }, + { parse: true } + ); + var status = emlGeoCoverage.getCoordinateStatus(); + status.north.isSet.should.equal(true); + status.north.isValid.should.equal(true); + status.east.isSet.should.equal(true); + status.east.isValid.should.equal(true); + status.south.isSet.should.equal(true); + status.south.isValid.should.equal(true); + status.west.isSet.should.equal(true); + status.west.isValid.should.equal(true); + }); + + it("should validate the coordinates", function () { + var emlGeoCoverage = new EMLGeoCoverage( + { objectDOM: this.testEML }, + { parse: true } + ); + var status = emlGeoCoverage.getCoordinateStatus(); + var errors = emlGeoCoverage.generateStatusErrors(status); + expect(errors).to.be.empty; + }); + + it("should give an error if the coordinates are invalid", function () { + var emlGeoCoverage = new EMLGeoCoverage( + { objectDOM: this.testEML }, + { parse: true } + ); + emlGeoCoverage.set("north", "100"); + var errors = emlGeoCoverage.validate(); + errors.north.should.equal( + "The Northwest latitude must be between -90 and 90." + ); + }); + + it("should give an error if the coordinates are missing", function () { + var emlGeoCoverage = new EMLGeoCoverage( + { objectDOM: this.testEML }, + { parse: true } + ); + emlGeoCoverage.set("north", ""); + var errors = emlGeoCoverage.validate(); + console.log(errors); + errors.north.should.equal("Each coordinate must include a latitude AND longitude."); + }); + + // it("should give an error if the north and south coordinates are reversed", function () { + // var emlGeoCoverage = new EMLGeoCoverage( + // { objectDOM: this.testEML }, + // { parse: true } + // ); + // emlGeoCoverage.set("north", "40"); + // emlGeoCoverage.set("south", "50"); + // var errors = emlGeoCoverage.validate(); + // errors.north.should.equal( + // "The Northwest latitude must be between -90 and 90." + // ); + // errors.south.should.equal( + // "The Southeast latitude must be between -90 and 90." + // ); + // }); + }); + }); +}); \ No newline at end of file From 1e5cf79ffff5fc5cf62b2842c036a1413c778d5d Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 1 Nov 2023 20:19:58 -0400 Subject: [PATCH 34/43] Check for reversed N & S coords in EMLGeoCoverage - Show validation error when N coord is less than S coord - Fix the HTML input pattern for decimal degrees (was throwing an error) - Add a test for reversed coords to the EMLGeoCoverage model spec Issue #2159 --- .../models/metadata/eml211/EMLGeoCoverage.js | 40 +++++++++++++++---- src/js/templates/metadata/EMLGeoCoverage.html | 8 ++-- src/js/views/metadata/EMLGeoCoverageView.js | 27 ++++++++----- .../metadata/eml211/EMLGeoCoverage.spec.js | 29 ++++++-------- 4 files changed, 66 insertions(+), 38 deletions(-) diff --git a/src/js/models/metadata/eml211/EMLGeoCoverage.js b/src/js/models/metadata/eml211/EMLGeoCoverage.js index c9b93a12e..b4753de23 100644 --- a/src/js/models/metadata/eml211/EMLGeoCoverage.js +++ b/src/js/models/metadata/eml211/EMLGeoCoverage.js @@ -265,6 +265,9 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( case "needPair": return "Each location description must have at least one coordinate pair."; break; + case "coordsReversed": + return "The North latitude must be greater than the South latitude."; + break; default: return ""; break; @@ -277,7 +280,9 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( * the value is valid. * * @return {array} An array containing the current state of each - * coordinate box + * coordinate box, including: value (the value of the coordinate converted + * to a number), isSet (whether the coordinate has a value), and isValid + * (whether the value is in the correct range) */ getCoordinateStatus: function () { var north = this.get("north"), @@ -285,23 +290,28 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( south = this.get("south"), west = this.get("west"); + const isDefined = (value) => + typeof value !== "undefined" && value != null && value !== ""; + return { north: { - isSet: - typeof north !== "undefined" && north != null && north !== "", + value: Number(north), + isSet: isDefined(north), isValid: this.validateCoordinate(north, -90, 90), }, east: { - isSet: typeof east !== "undefined" && east != null && east !== "", + value: Number(east), + isSet: isDefined(east), isValid: this.validateCoordinate(east, -180, 180), }, south: { - isSet: - typeof south !== "undefined" && south != null && south !== "", + value: Number(south), + isSet: isDefined(south), isValid: this.validateCoordinate(south, -90, 90), }, west: { - isSet: typeof west !== "undefined" && west != null && west !== "", + value: Number(west), + isSet: isDefined(west), isValid: this.validateCoordinate(west, -180, 180), }, }; @@ -404,6 +414,22 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( else if (!pointStatuses.south.isSet && pointStatuses.east.isSet) errors.south = this.getErrorMessage("missing"); + // Verify latitudes: north should be > south. For longitude, east and + // west can be in any order, depending on whether the location crosses + // the antimeridian + if ( + pointStatuses.north.isSet && + pointStatuses.south.isSet && + pointStatuses.north.isValid && + pointStatuses.south.isValid + ) { + if (pointStatuses.north.value < pointStatuses.south.value) { + const msg = this.getErrorMessage("coordsReversed"); + errors.north = msg; + errors.south = msg; + } + } + if (Object.keys(errors).length) return errors; else return false; }, diff --git a/src/js/templates/metadata/EMLGeoCoverage.html b/src/js/templates/metadata/EMLGeoCoverage.html index cec7cdb9c..dcfa0f9bd 100644 --- a/src/js/templates/metadata/EMLGeoCoverage.html +++ b/src/js/templates/metadata/EMLGeoCoverage.html @@ -9,13 +9,13 @@

- - + +
- - + +
diff --git a/src/js/views/metadata/EMLGeoCoverageView.js b/src/js/views/metadata/EMLGeoCoverageView.js index 6c50c5ba1..8b73c610e 100644 --- a/src/js/views/metadata/EMLGeoCoverageView.js +++ b/src/js/views/metadata/EMLGeoCoverageView.js @@ -127,10 +127,10 @@ define([ //Are the NW and SE points the same? i.e. is this a single point and not //a box? var isSinglePoint = - this.model.get("north") != null && - this.model.get("north") == this.model.get("south") && - this.model.get("west") != null && - this.model.get("west") == this.model.get("east"), + this.model.get("north") != null && + this.model.get("north") == this.model.get("south") && + this.model.get("west") != null && + this.model.get("west") == this.model.get("east"), hasEmptyInputs = this.$("[data-attribute='north']").val() == "" || this.$("[data-attribute='south']").val() == "" || @@ -205,7 +205,7 @@ define([ } else { //Find out if we are missing a complete NW or SE point var isMissingNWPoint = - this.model.get("north") == null && this.model.get("west") == null, + this.model.get("north") == null && this.model.get("west") == null, isMissingSEPoint = this.model.get("south") == null && this.model.get("east") == null; @@ -290,14 +290,19 @@ define([ this.$el.removeClass("error"); this.$(".notification").empty(); - var errorMessages = ""; + const errorObj = this.model.validationError; + // Get all of the field keys + const fields = Object.keys(errorObj); + // Get all of the error messages (values). Remove duplicates. + let errorMessages = [...new Set(Object.values(errorObj))]; + // Join the error messages into a single string + errorMessages = errorMessages.join(" "); - for (field in this.model.validationError) { + // Highlight the fields that need to be fixed + fields.forEach((field) => { this.$("[data-attribute='" + field + "']").addClass("error"); - - errorMessages += this.model.validationError[field] + " "; - } - + }) + // Show the combined error message this.$(".notification").text(errorMessages).addClass("error"); }, diff --git a/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js b/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js index 69cf3e220..51b71f2d9 100644 --- a/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js +++ b/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js @@ -109,25 +109,22 @@ define([ ); emlGeoCoverage.set("north", ""); var errors = emlGeoCoverage.validate(); - console.log(errors); errors.north.should.equal("Each coordinate must include a latitude AND longitude."); }); - // it("should give an error if the north and south coordinates are reversed", function () { - // var emlGeoCoverage = new EMLGeoCoverage( - // { objectDOM: this.testEML }, - // { parse: true } - // ); - // emlGeoCoverage.set("north", "40"); - // emlGeoCoverage.set("south", "50"); - // var errors = emlGeoCoverage.validate(); - // errors.north.should.equal( - // "The Northwest latitude must be between -90 and 90." - // ); - // errors.south.should.equal( - // "The Southeast latitude must be between -90 and 90." - // ); - // }); + it("should give an error if the north and south coordinates are reversed", function () { + var emlGeoCoverage = new EMLGeoCoverage( + { objectDOM: this.testEML }, + { parse: true } + ); + emlGeoCoverage.set("north", "40"); + emlGeoCoverage.set("south", "50"); + var errors = emlGeoCoverage.validate(); + const msg = "The North latitude must be greater than the South latitude."; + errors.north.should.equal(msg); + errors.south.should.equal(msg); + }); + }); }); }); \ No newline at end of file From 02d3e4b104bd91f253cb3f0c64a9e4db8e487e46 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Wed, 1 Nov 2023 20:42:23 -0400 Subject: [PATCH 35/43] Check for other problematic coords in GeoCoverage - Add validation rule to prevent bounding boxes from: containing the poles or crossing the anti-meridian - Add unit tests for the new validation rules Issue #2159 --- .../models/metadata/eml211/EMLGeoCoverage.js | 53 ++++++++++++------- .../metadata/eml211/EMLGeoCoverage.spec.js | 32 +++++++++++ 2 files changed, 65 insertions(+), 20 deletions(-) diff --git a/src/js/models/metadata/eml211/EMLGeoCoverage.js b/src/js/models/metadata/eml211/EMLGeoCoverage.js index b4753de23..c03ad5973 100644 --- a/src/js/models/metadata/eml211/EMLGeoCoverage.js +++ b/src/js/models/metadata/eml211/EMLGeoCoverage.js @@ -265,9 +265,14 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( case "needPair": return "Each location description must have at least one coordinate pair."; break; - case "coordsReversed": + case "northSouthReversed": return "The North latitude must be greater than the South latitude."; break; + case "crossesAntiMeridian": + return "The bounding box cannot cross the anti-meridian."; + break; + case "containsPole": + return "The bounding box cannot contain the North or South pole."; default: return ""; break; @@ -370,21 +375,6 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( var pointStatuses = this.getCoordinateStatus(); - // if (!this.checkForPairs(pointStatuses)) { - // errorMsg = this.addSpace(errorMsg); - // errorMsg += this.getErrorMessage("needPair"); - // } - - // if (this.hasMissingPoint(pointStatuses)) { - // errorMsg = this.addSpace(errorMsg); - // errors += this.getErrorMessage("missing"); - // } - - // errorMsg += this.addSpace( - // this.generateStatusErrors(pointStatuses), - // true - // ); - if ( !pointStatuses.north.isSet && !pointStatuses.south.isSet && @@ -414,9 +404,8 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( else if (!pointStatuses.south.isSet && pointStatuses.east.isSet) errors.south = this.getErrorMessage("missing"); - // Verify latitudes: north should be > south. For longitude, east and - // west can be in any order, depending on whether the location crosses - // the antimeridian + // Verify latitudes: north should be > south. Don't allow bounding boxes + // to contain the north or south poles (doesn't really work) if ( pointStatuses.north.isSet && pointStatuses.south.isSet && @@ -424,12 +413,36 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( pointStatuses.south.isValid ) { if (pointStatuses.north.value < pointStatuses.south.value) { - const msg = this.getErrorMessage("coordsReversed"); + const msg = this.getErrorMessage("northSouthReversed"); + errors.north = msg; + errors.south = msg; + } + if ( + pointStatuses.north.value == 90 || + pointStatuses.south.value == -90 + ) { + const msg = this.getErrorMessage("containsPole"); errors.north = msg; errors.south = msg; } } + // For longitudes, don't allow bounding boxes that attempt to traverse + // the anti-meridian + if ( + pointStatuses.east.isSet && + pointStatuses.west.isSet && + pointStatuses.east.isValid && + pointStatuses.west.isValid + ) { + if (pointStatuses.east.value < pointStatuses.west.value) { + const msg = this.getErrorMessage("crossesAntiMeridian"); + errors.east = msg; + errors.west = msg; + } + } + + if (Object.keys(errors).length) return errors; else return false; }, diff --git a/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js b/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js index 51b71f2d9..de3717d30 100644 --- a/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js +++ b/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js @@ -125,6 +125,38 @@ define([ errors.south.should.equal(msg); }); + it("should give an error if the bounds cross the anti-meridian", function () { + var emlGeoCoverage = new EMLGeoCoverage( + { objectDOM: this.testEML }, + { parse: true } + ); + emlGeoCoverage.set("west", "170"); + emlGeoCoverage.set("east", "-170"); + var errors = emlGeoCoverage.validate(); + errors.west.should.equal("The bounding box cannot cross the anti-meridian."); + errors.east.should.equal("The bounding box cannot cross the anti-meridian."); + }); + + it("should give an error if the bounds contain the north pole", function () { + var emlGeoCoverage = new EMLGeoCoverage( + { objectDOM: this.testEML }, + { parse: true } + ); + emlGeoCoverage.set("north", "90"); + var errors = emlGeoCoverage.validate(); + errors.north.should.equal("The bounding box cannot contain the North or South pole."); + }); + + it("should give an error if the bounds contain the south pole", function () { + var emlGeoCoverage = new EMLGeoCoverage( + { objectDOM: this.testEML }, + { parse: true } + ); + emlGeoCoverage.set("south", "-90"); + var errors = emlGeoCoverage.validate(); + errors.south.should.equal("The bounding box cannot contain the North or South pole."); + }); + }); }); }); \ No newline at end of file From 53934d46330dd8b0c587148c5439f932392b2cf9 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 2 Nov 2023 09:15:56 -0400 Subject: [PATCH 36/43] Improve EMLGeoCoverage validation messages - For each error type, provide solutions for invalid geo coverages - Move error messages to an object in the model (rather than a switch statement) - Update tests to get error messages from the model Issue #2159 --- .../models/metadata/eml211/EMLGeoCoverage.js | 75 ++++++++++--------- .../metadata/eml211/EMLGeoCoverage.spec.js | 46 ++++++------ 2 files changed, 62 insertions(+), 59 deletions(-) diff --git a/src/js/models/metadata/eml211/EMLGeoCoverage.js b/src/js/models/metadata/eml211/EMLGeoCoverage.js index c03ad5973..7410ca5f9 100644 --- a/src/js/models/metadata/eml211/EMLGeoCoverage.js +++ b/src/js/models/metadata/eml211/EMLGeoCoverage.js @@ -62,6 +62,44 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( }; }, + /** + * The map of error keys to human-readable error messages to use for + * validation issues. + * @type {Object} + * @property {string} default - When the error is not in this list + * @property {string} north - When the Northwest latitude is not in the + * valid range + * @property {string} east - When the Southeast longitude is not in the + * valid range + * @property {string} south - When the Southeast latitude is not in the + * valid range + * @property {string} west - When the Northwest longitude is not in the + * valid range + * @property {string} missing - When a long or lat coordinate is missing + * @property {string} description - When a description is missing + * @property {string} needPair - When there are no coordinate pairs + * @property {string} northSouthReversed - When the North latitude is less + * than the South latitude + * @property {string} crossesAntiMeridian - When the bounding box crosses + * the anti-meridian + * @property {string} containsPole - When the bounding box contains the + * North or South pole + * @since x.x.x + */ + errorMessages: { + "default": "Please correct the geographic coverage.", + "north": "Northwest latitude out of range, must be >-90 and <90. Please correct the latitude.", + "east": "Southeast longitude out of range (-180 to 180). Please adjust the longitude.", + "south": "Southeast latitude out of range, must be >-90 and <90. Please correct the latitude.", + "west": "Northwest longitude out of range (-180 to 180). Check and correct the longitude.", + "missing": "Latitude and longitude are required for each coordinate. Please complete all fields.", + "description": "Missing location description. Please add a brief description.", + "needPair": "Location requires at least one coordinate pair. Please add coordinates.", + "northSouthReversed": "North latitude should be greater than South. Please swap the values.", + "crossesAntiMeridian": "Bounding box crosses the anti-meridian. Please use multiple boxes that meet at the anti-meridian instead.", + "containsPole": "Coordinates include a pole. Latitudes should be >-90 and <90." + }, + /** * Parses the objectDOM to populate this model with data. * @param {Element} objectDOM - The EML object element @@ -123,7 +161,6 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( * @returns {string} The XML string */ serialize: function () { - const objectDOM = this.updateDOM(); let xmlString = objectDOM?.outerHTML; if (!xmlString) xmlString = objectDOM?.[0]?.outerHTML; @@ -243,40 +280,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( * @return {string} The error message */ getErrorMessage: function (area) { - switch (area) { - case "north": - return "The Northwest latitude must be between -90 and 90."; - break; - case "east": - return "The Southeast longitude must be between -180 and 180."; - break; - case "south": - return "The Southeast latitude must be between -90 and 90."; - break; - case "west": - return "The Northwest longitude must be between -180 and 180."; - break; - case "missing": - return "Each coordinate must include a latitude AND longitude."; - break; - case "description": - return "Each location must have a description."; - break; - case "needPair": - return "Each location description must have at least one coordinate pair."; - break; - case "northSouthReversed": - return "The North latitude must be greater than the South latitude."; - break; - case "crossesAntiMeridian": - return "The bounding box cannot cross the anti-meridian."; - break; - case "containsPole": - return "The bounding box cannot contain the North or South pole."; - default: - return ""; - break; - } + return this.errorMessages[area] || this.errorMessages.default; }, /** @@ -442,7 +446,6 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function ( } } - if (Object.keys(errors).length) return errors; else return false; }, diff --git a/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js b/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js index de3717d30..a846e738b 100644 --- a/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js +++ b/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js @@ -17,7 +17,7 @@ define([ 50.1600 `; - // remove ALL whitespace + // remove ALL whitespace testEML = testEML.replace(/\s/g, ""); this.testEML = testEML; }); @@ -35,7 +35,6 @@ define([ describe("parse()", function () { it("should parse EML", function () { - var emlGeoCoverage = new EMLGeoCoverage( { objectDOM: this.testEML }, { parse: true } @@ -52,7 +51,6 @@ define([ }); describe("serialize()", function () { - it("should serialize to XML", function () { var emlGeoCoverage = new EMLGeoCoverage( { objectDOM: this.testEML }, @@ -86,7 +84,7 @@ define([ { parse: true } ); var status = emlGeoCoverage.getCoordinateStatus(); - var errors = emlGeoCoverage.generateStatusErrors(status); + const errors = emlGeoCoverage.generateStatusErrors(status); expect(errors).to.be.empty; }); @@ -96,10 +94,9 @@ define([ { parse: true } ); emlGeoCoverage.set("north", "100"); - var errors = emlGeoCoverage.validate(); - errors.north.should.equal( - "The Northwest latitude must be between -90 and 90." - ); + const errors = emlGeoCoverage.validate(); + const expectedMsg = emlGeoCoverage.errorMessages.north; + errors.north.should.equal(expectedMsg); }); it("should give an error if the coordinates are missing", function () { @@ -108,8 +105,9 @@ define([ { parse: true } ); emlGeoCoverage.set("north", ""); - var errors = emlGeoCoverage.validate(); - errors.north.should.equal("Each coordinate must include a latitude AND longitude."); + const errors = emlGeoCoverage.validate(); + const expectedMsg = emlGeoCoverage.errorMessages.missing; + errors.north.should.equal(expectedMsg); }); it("should give an error if the north and south coordinates are reversed", function () { @@ -119,10 +117,10 @@ define([ ); emlGeoCoverage.set("north", "40"); emlGeoCoverage.set("south", "50"); - var errors = emlGeoCoverage.validate(); - const msg = "The North latitude must be greater than the South latitude."; - errors.north.should.equal(msg); - errors.south.should.equal(msg); + const errors = emlGeoCoverage.validate(); + const expectedMsg = emlGeoCoverage.errorMessages.northSouthReversed; + errors.north.should.equal(expectedMsg); + errors.south.should.equal(expectedMsg); }); it("should give an error if the bounds cross the anti-meridian", function () { @@ -132,9 +130,10 @@ define([ ); emlGeoCoverage.set("west", "170"); emlGeoCoverage.set("east", "-170"); - var errors = emlGeoCoverage.validate(); - errors.west.should.equal("The bounding box cannot cross the anti-meridian."); - errors.east.should.equal("The bounding box cannot cross the anti-meridian."); + const errors = emlGeoCoverage.validate(); + const expectedMsg = emlGeoCoverage.errorMessages.crossesAntiMeridian; + errors.west.should.equal(expectedMsg); + errors.east.should.equal(expectedMsg); }); it("should give an error if the bounds contain the north pole", function () { @@ -143,8 +142,9 @@ define([ { parse: true } ); emlGeoCoverage.set("north", "90"); - var errors = emlGeoCoverage.validate(); - errors.north.should.equal("The bounding box cannot contain the North or South pole."); + const errors = emlGeoCoverage.validate(); + const expectedMsg = emlGeoCoverage.errorMessages.containsPole; + errors.north.should.equal(expectedMsg); }); it("should give an error if the bounds contain the south pole", function () { @@ -153,10 +153,10 @@ define([ { parse: true } ); emlGeoCoverage.set("south", "-90"); - var errors = emlGeoCoverage.validate(); - errors.south.should.equal("The bounding box cannot contain the North or South pole."); + const errors = emlGeoCoverage.validate(); + const expectedMsg = emlGeoCoverage.errorMessages.containsPole; + errors.south.should.equal(expectedMsg); }); - }); }); -}); \ No newline at end of file +}); From de40dcc5a32638115d2bc58184a0f7961f4af8d4 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 2 Nov 2023 15:07:27 -0400 Subject: [PATCH 37/43] Show cesium map information/help section - Optionally show a help panel in the ToolbarView - The help panel can display navigation instructions and feedback text - All of this is configurable in the map config Issue #2173 --- src/css/map-view.css | 110 ++++++++- src/js/models/maps/Map.js | 24 +- src/js/templates/maps/cesium-nav-help.html | 107 +++++++++ src/js/views/maps/HelpPanelView.js | 256 +++++++++++++++++++++ src/js/views/maps/ToolbarView.js | 32 ++- 5 files changed, 514 insertions(+), 15 deletions(-) create mode 100644 src/js/templates/maps/cesium-nav-help.html create mode 100644 src/js/views/maps/HelpPanelView.js diff --git a/src/css/map-view.css b/src/css/map-view.css index d3c9439e8..1812de6fe 100644 --- a/src/css/map-view.css +++ b/src/css/map-view.css @@ -126,7 +126,7 @@ /* ---- BADGE ---- */ -.map-view__badge{ +.map-view__badge { padding: 0.4em 0.5em 0.3em 0.55em; margin: 0 -0.2rem 0 0.3rem; font-size: 0.62rem; @@ -140,24 +140,25 @@ font-weight: 500; } -.map-view__badge--blue{ +.map-view__badge--blue { background-color: var(--map-col-blue); filter: none; } -.map-view__badge--green{ +.map-view__badge--green { background-color: var(--map-col-green); filter: none; } -.map-view__badge--yellow{ +.map-view__badge--yellow { background-color: var(--map-col-yellow); color: var(--map-col-bkg-lighter); filter: none; font-weight: 600; opacity: 0.9; } -.map-view__badge--contrast{ + +.map-view__badge--contrast { background-color: var(--map-col-text); color: var(--map-col-bkg); opacity: 0.8; @@ -612,23 +613,24 @@ represents 1 unit of the given distance measurement. */ border-radius: var(--map-border-radius); } -.layer-details__notification--blue{ +.layer-details__notification--blue { background-color: var(--map-col-blue); filter: none; } -.layer-details__notification--green{ +.layer-details__notification--green { background-color: var(--map-col-green); filter: none; } -.layer-details__notification--yellow{ +.layer-details__notification--yellow { background-color: var(--map-col-yellow); color: var(--map-col-bkg-lighter); filter: none; opacity: 0.9; } -.layer-details__notification--contrast{ + +.layer-details__notification--contrast { background-color: var(--map-col-text); color: var(--map-col-bkg); opacity: 0.95; @@ -972,3 +974,93 @@ other class: .ui-slider-range */ grid-auto-rows: min-content; grid-gap: 1rem; } + +/***************************************************************************************** + * + * Help panel + * + * Panel that shows navigation and other help information + * + */ + +.map-help-panel{ + width: 100%; +} + +.nav-help { + background-color: var(--map-col-bkg-lighter); + border-radius: var(--map-border-radius); +} + +.nav-help .map-view__button { + background-color: var(--map-col-bkg-lighter); + padding: 0.65rem 0.9rem; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + opacity: 0.8; +} + +.nav-help .map-view__button--active { + background-color: var(--map-col-bkg-lightest); + opacity: 1; +} + +.nav-help__img { + height: 48px; + width: 70px; +} + +.nav-help__instructions { + display: grid; + width: 100%; + gap: 0.8rem; + padding: 0.8rem 0.4rem; + background-color: var(--map-col-bkg-lightest); + border-radius: var(--map-border-radius); + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.nav-help__instructions.hidden { + display: none; +} + +.cesium-navigation-help-pan { + color: #66ccff; + font-weight: bold; +} + +.cesium-navigation-help-zoom { + color: #65fd00; + font-weight: bold; +} + +.cesium-navigation-help-rotate { + color: #ffd800; + font-weight: bold; +} + +.cesium-navigation-help-tilt { + color: #d800d8; + font-weight: bold; +} + +.nav-help__instruction { + display: grid; + grid-template-columns: 70px auto; + gap: 0.5rem; + align-items: center; +} + +.map-help-panel__title{ + text-transform: uppercase; + font-size: 0.95rem; + font-weight: 600; + letter-spacing: 0.06em; + margin: 0 0 0.8rem 0; + line-height: normal; +} + +.map-help-panel__section:not(:first-child) { + margin-top: 2.5rem; +} \ No newline at end of file diff --git a/src/js/models/maps/Map.js b/src/js/models/maps/Map.js index f15cfe4a5..b832d4a8f 100644 --- a/src/js/models/maps/Map.js +++ b/src/js/models/maps/Map.js @@ -42,8 +42,8 @@ define([ * 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}. + * 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} [toolbarOpen=false] - Whether or not the toolbar is @@ -56,6 +56,17 @@ define([ * 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. * * @example * { @@ -159,6 +170,12 @@ define([ * 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. */ defaults: function () { return { @@ -184,6 +201,9 @@ define([ showScaleBar: true, showFeatureInfo: true, clickFeatureAction: "showDetails", + showNavHelp: true, + showFeedback: false, + feedbackText: null }; }, diff --git a/src/js/templates/maps/cesium-nav-help.html b/src/js/templates/maps/cesium-nav-help.html new file mode 100644 index 000000000..34025b34e --- /dev/null +++ b/src/js/templates/maps/cesium-nav-help.html @@ -0,0 +1,107 @@ + diff --git a/src/js/views/maps/HelpPanelView.js b/src/js/views/maps/HelpPanelView.js new file mode 100644 index 000000000..8483958c0 --- /dev/null +++ b/src/js/views/maps/HelpPanelView.js @@ -0,0 +1,256 @@ +"use strict"; + +define(["backbone", "text!templates/maps/cesium-nav-help.html"], function ( + Backbone, + NavHelpTemplate +) { + /** + * @class MapHelpPanel + * @classdesc The MapHelpPanel view displays navigation instructions and other + * help information for the map. + * @classcategory Views/Maps + * @name MapHelpPanel + * @extends Backbone.View + * @screenshot views/maps/MapHelpPanel.png + * @since x.x.x + * @constructs MapHelpPanel + */ + var MapHelpPanel = Backbone.View.extend( + /** @lends MapHelpPanel.prototype */ { + /** + * The type of View this is + * @type {string} + */ + type: "MapHelpPanel", + + /** + * The HTML classes to use for this view's element + * @type {string} + */ + className: "map-help-panel", + + /** + * Initializes the MapHelpPanel + * @param {Object} options - A literal object with options to pass to the + * view + * @param {boolean} [options.showFeedback=true] - Set to false to hide the + * feedback section + * @param {boolean} [options.showNavHelp=true] - Set to false to hide the + * navigation instructions section + * @param {string} [options.feedbackText] - Text to show in the feedback + * section + */ + initialize: function (options) { + if (!options) options = {}; + + const validOptions = ["showFeedback", "feedbackText", "showNavHelp"]; + validOptions.forEach((option) => { + if (options.hasOwnProperty(option)) { + this[option] = options[option]; + } + }); + + }, + + /** + * The template to use to show the navigation instructions + * @type {function} + */ + navHelpTemplate: _.template(NavHelpTemplate), + + /** + * Set to false to hide the feedback section + * @type {boolean} + * @default true + */ + showFeedback: true, + + /** + * Set to false to hide the navigation instructions section + * @type {boolean} + * @default true + */ + showNavHelp: true, + + /** + * Text to show in the feedback section + * @type {string} + */ + feedbackText: `Please contact the administrator of this map for help.`, + + /** + * The sections to show in the help panel + * @type {Object[]} + * @property {string} id - The id of the section + * @property {string} title - The title of the section + * @property {string} render - The name of the method to call to render + * the section. The method will be passed the container within which to + * render the section. It should return the container element with the + * section added. Methods will be called with the view as the context. + */ + sections: [ + { + id: "nav-help", + title: "Navigation Instructions", + render: "renderNavHelp", + }, + { + id: "feedback", + title: "Feedback", + render: "renderFeedback", + }, + ], + + /** + * Renders the MapHelpPanel + * @returns {MapHelpPanel} Returns the view + */ + render: function () { + const view = this; + + let sections = JSON.parse(JSON.stringify(view.sections)); + + if (view.showFeedback === false) { + sections = sections.filter((section) => { + return section.id !== "feedback"; + }); + } + + if (view.showNavHelp === false) { + sections = sections.filter((section) => { + return section.id !== "nav-help"; + }); + } + + sections.forEach((section) => { + view.renderSection(section); + }); + + return view; + }, + + /** + * Renders a section of the help panel + * @param {Object} section - The options for the section, see + * {@link MapHelpPanel#sections} + */ + renderSection: function (section) { + try { + const view = this; + const renderMethod = view[section.render] || null; + if (!renderMethod) return; + + const contentContainerClass = "map-help-panel__content"; + + const sectionEl = document.createElement("section"); + sectionEl.classList.add("map-help-panel__section"); + sectionEl.innerHTML = `

${section.title}

+
`; + const contentEl = sectionEl.querySelector( + "." + contentContainerClass + ); + renderMethod.call(view, contentEl); + + view.el.appendChild(sectionEl); + + return sectionEl; + } catch (e) { + console.log("Error rendering a help panel section", e); + } + }, + + /** + * Renders the navigation instructions + * @param {HTMLElement} containerEl - The element to render the navigation + * instructions within + * @returns {HTMLElement} Returns the container element with the + * navigation instructions added + */ + renderNavHelp: function (containerEl) { + const view = this; + const cid = this.cid; + const cesiumUrl = + "https://cesium.com/downloads/cesiumjs/releases/1.91/Build/Cesium/"; + + const mouseButtonId = "nav-help-mouse-" + cid; + const touchButtonId = "nav-help-touch-" + cid; + const mouseSectionId = "nav-help-mouse-section-" + cid; + const touchSectionId = "nav-help-touch-section-" + cid; + + // Create the HTML and add it to the container + const navHelpHTML = this.navHelpTemplate({ + mouseButtonId, + touchButtonId, + mouseSectionId, + touchSectionId, + cesiumUrl, + }); + containerEl.innerHTML = navHelpHTML; + + // Select the elements + const mouseButtonEl = containerEl.querySelector("#" + mouseButtonId); + const touchButtonEl = containerEl.querySelector("#" + touchButtonId); + const mouseSectionEl = containerEl.querySelector("#" + mouseSectionId); + const touchSectionEl = containerEl.querySelector("#" + touchSectionId); + + // Add listeners to the buttons to toggle the sections + mouseButtonEl.addEventListener("click", () => { + view.showSection(mouseSectionEl, mouseButtonEl); + view.hideSection(touchSectionEl, touchButtonEl); + }); + touchButtonEl.addEventListener("click", () => { + view.showSection(touchSectionEl, touchButtonEl); + view.hideSection(mouseSectionEl, mouseButtonEl); + }); + + // Show only the mouse section by default + view.hideSection(touchSectionEl, touchButtonEl); + view.showSection(mouseSectionEl, mouseButtonEl); + + return containerEl; + }, + + /** + * Renders the feedback section + * @param {HTMLElement} containerEl - The element to render the feedback + * section within + * @returns {HTMLElement} Returns the container element with the feedback + * section added + */ + renderFeedback: function (containerEl) { + containerEl.innerHTML = this.feedbackText; + return containerEl; + }, + + /** + * Hides a section by adding the hidden class, and removes the active + * class from the button if one is provided + * @param {HTMLElement} sectionEl - The section element to hide + * @param {HTMLElement} [buttonEl] - The button element to remove the + * active class from + */ + hideSection: function (sectionEl, buttonEl) { + if (!sectionEl) return; + sectionEl.classList.add("hidden"); + if (!buttonEl) return; + buttonEl.classList.remove("map-view__button--active"); + }, + + /** + * Shows a section by removing the hidden class, and adds the active class + * to the button if one is provided + * @param {HTMLElement} sectionEl - The section element to show + * @param {HTMLElement} [buttonEl] - The button element to add the active + * class to + */ + showSection: function (sectionEl, buttonEl) { + if (!sectionEl) return; + sectionEl.classList.remove("hidden"); + if (!buttonEl) return; + buttonEl.classList.add("map-view__button--active"); + }, + } + ); + + return MapHelpPanel; +}); diff --git a/src/js/views/maps/ToolbarView.js b/src/js/views/maps/ToolbarView.js index e8eac5e69..b9b8aa320 100644 --- a/src/js/views/maps/ToolbarView.js +++ b/src/js/views/maps/ToolbarView.js @@ -9,7 +9,8 @@ define( 'models/maps/Map', // Sub-views - TODO: import these as needed 'views/maps/LayerListView', - 'views/maps/DrawToolView' + 'views/maps/DrawToolView', + 'views/maps/HelpPanelView' ], function ( $, @@ -19,7 +20,8 @@ define( Map, // Sub-views LayerListView, - DrawTool + DrawTool, + HelpPanel ) { /** @@ -157,7 +159,7 @@ define( * * @type {SectionOption[]} */ - sections: [ + sectionOptions: [ { label: 'Layers', icon: '', @@ -179,6 +181,16 @@ define( icon: 'pencil', view: DrawTool, viewOptions: {} + }, + { + label: 'Help', + icon: 'question-sign', + view: HelpPanel, + viewOptions: { + showFeedback: 'model.showFeedback', + feedbackText: 'model.feedbackText', + showNavHelp: 'model.showNavHelp', + } } ], @@ -205,9 +217,16 @@ define( if (!this.model || !(this.model instanceof Map)) { this.model = new Map(); } + if(this.model.get('toolbarOpen') === true) { this.isOpen = true; } + + + // Deep clone the section options so that the original array is not + // modified + this.sections = _.map(this.sectionOptions, _.clone); + if (this.model.get("showLayerList") === false) { this.sections = this.sections.filter( (section) => section.label !== "Layers" @@ -218,8 +237,13 @@ define( (section) => section.label !== "Home" ); } + if (!this.model.get("showNavHelp") && !this.model.get("showFeedback")) { + this.sections = this.sections.filter( + (section) => section.label !== "Help" + ); + } } catch (e) { - console.log('A ToolbarView failed to initialize. Error message: ' + e); + console.log('Error initializing a ToolbarView', e); } }, From f0245268ba05445200d36e833d7729aceb663299 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 2 Nov 2023 15:17:58 -0400 Subject: [PATCH 38/43] Add screenshot of new map help panel for docs Issue #2173 --- docs/screenshots/views/maps/MapHelpPanel.png | Bin 0 -> 631637 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/screenshots/views/maps/MapHelpPanel.png diff --git a/docs/screenshots/views/maps/MapHelpPanel.png b/docs/screenshots/views/maps/MapHelpPanel.png new file mode 100644 index 0000000000000000000000000000000000000000..7c277f4bbe0d67974b0ad2297c798f7ec4dd1ec0 GIT binary patch literal 631637 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%0Abrl1>6jMoPoo|Y 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 om8U1x-(X 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�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λzOxdlNOuVw%ueQ@Z9FK3`=^mS*N=BD74TB8O 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
3h1%ZPG%V! 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

T+n8OT5W`0mlCI7|r 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

qR>mKTc|IzTK4GcWeD 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 Tw2CrA00000NkvXXu0mjfUP0eg literal 0 HcmV?d00001 From c2837d76664651f3b04addfe3ad96e6e0877ff05 Mon Sep 17 00:00:00 2001 From: Robyn Thiessen-Bock Date: Thu, 2 Nov 2023 15:34:00 -0400 Subject: [PATCH 39/43] Hide map DrawTool for now, add screenshot for docs Issue #2180 --- docs/screenshots/views/maps/DrawTool.png | Bin 0 -> 373557 bytes src/js/views/maps/ToolbarView.js | 13 +++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 docs/screenshots/views/maps/DrawTool.png diff --git a/docs/screenshots/views/maps/DrawTool.png b/docs/screenshots/views/maps/DrawTool.png new file mode 100644 index 0000000000000000000000000000000000000000..03bd223cc9f2ba8407b1cc10980df4f4c9dba4b1 GIT binary patch literal 373557 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�{#? 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

tBz7T8S6U;3c1=W7d_q!j}|wq07>XF_~|Dw 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

-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