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