terriajs
Version:
Geospatial data visualization platform.
1,366 lines (1,206 loc) • 64.3 kB
text/typescript
import { reaction, runInAction } from "mobx";
import { http, HttpResponse } from "msw";
import { GeomType, LineSymbolizer, PolygonSymbolizer } from "protomaps-leaflet";
import Cartesian2 from "terriajs-cesium/Source/Core/Cartesian2";
import Cartesian3 from "terriajs-cesium/Source/Core/Cartesian3";
import Color from "terriajs-cesium/Source/Core/Color";
import Iso8601 from "terriajs-cesium/Source/Core/Iso8601";
import JulianDate from "terriajs-cesium/Source/Core/JulianDate";
import createGuid from "terriajs-cesium/Source/Core/createGuid";
import CustomDataSource from "terriajs-cesium/Source/DataSources/CustomDataSource";
import Entity from "terriajs-cesium/Source/DataSources/Entity";
import GeoJsonDataSource from "terriajs-cesium/Source/DataSources/GeoJsonDataSource";
import HeightReference from "terriajs-cesium/Source/Scene/HeightReference";
import StandardCssColors from "../../../../lib/Core/StandardCssColors";
import ContinuousColorMap from "../../../../lib/Map/ColorMap/ContinuousColorMap";
import ProtomapsImageryProvider from "../../../../lib/Map/ImageryProvider/ProtomapsImageryProvider";
import {
GEOJSON_SOURCE_LAYER_NAME,
ProtomapsGeojsonSource
} from "../../../../lib/Map/Vector/Protomaps/ProtomapsGeojsonSource";
import {
FEATURE_ID_PROP,
getColor
} from "../../../../lib/ModelMixins/GeojsonMixin";
import {
ImageryParts,
isDataSource
} from "../../../../lib/ModelMixins/MappableMixin";
import GeoJsonCatalogItem from "../../../../lib/Models/Catalog/CatalogItems/GeoJsonCatalogItem";
import SplitItemReference from "../../../../lib/Models/Catalog/CatalogReferences/SplitItemReference";
import CommonStrata from "../../../../lib/Models/Definition/CommonStrata";
import updateModelFromJson from "../../../../lib/Models/Definition/updateModelFromJson";
import TerriaFeature from "../../../../lib/Models/Feature/Feature";
import {
isTerriaFeatureData,
TerriaFeatureData
} from "../../../../lib/Models/Feature/FeatureData";
import Terria from "../../../../lib/Models/Terria";
import { worker } from "../../../mocks/browser";
import verysimpleCzml from "../../../../wwwroot/test/CZML/verysimple.czml" with { type: "json" };
import apiListGeojson from "../../../../wwwroot/test/GeoJSON/api-list.geojson" with { type: "json" };
import apiGeojson from "../../../../wwwroot/test/GeoJSON/api.geojson" with { type: "json" };
import bikeRacksGeojson from "../../../../wwwroot/test/GeoJSON/bike_racks.geojson" with { type: "json" };
import cemeteriesGeojson from "../../../../wwwroot/test/GeoJSON/cemeteries.geojson" with { type: "json" };
import emptyGeomsGeojson from "../../../../wwwroot/test/GeoJSON/empty-geoms.geojson" with { type: "json" };
import gmeGeojson from "../../../../wwwroot/test/GeoJSON/gme.geojson" with { type: "json" };
import heightGeojson from "../../../../wwwroot/test/GeoJSON/height.geojson" with { type: "json" };
import multipointGeojson from "../../../../wwwroot/test/GeoJSON/multipoint.geojson" with { type: "json" };
import multipolygonGeojson from "../../../../wwwroot/test/GeoJSON/multipolygon.geojson" with { type: "json" };
import pointsGeojson from "../../../../wwwroot/test/GeoJSON/points.geojson" with { type: "json" };
import polygonGeojson from "../../../../wwwroot/test/GeoJSON/polygon.geojson" with { type: "json" };
import polylineGeojson from "../../../../wwwroot/test/GeoJSON/polyline.geojson" with { type: "json" };
import timeBasedAutomaticStylesGeojson from "../../../../wwwroot/test/GeoJSON/time-based-automatic-styles.geojson" with { type: "json" };
import timeBasedGeojson from "../../../../wwwroot/test/GeoJSON/time-based.geojson" with { type: "json" };
describe("GeoJsonCatalogItemSpec", () => {
beforeEach(() => {
worker.use(
http.get("test/GeoJSON/bike_racks.geojson", () =>
HttpResponse.json(bikeRacksGeojson)
),
http.get("test/GeoJSON/cemeteries.geojson", () =>
HttpResponse.json(cemeteriesGeojson)
),
http.get("test/GeoJSON/gme.geojson", () => HttpResponse.json(gmeGeojson)),
http.get("test/CZML/verysimple.czml", () =>
HttpResponse.json(verysimpleCzml)
),
http.get("test/GeoJSON/time-based.geojson", () =>
HttpResponse.json(timeBasedGeojson)
),
http.get("test/GeoJSON/time-based-automatic-styles.geojson", () =>
HttpResponse.json(timeBasedAutomaticStylesGeojson)
),
http.get("test/GeoJSON/height.geojson", () =>
HttpResponse.json(heightGeojson)
),
http.get("test/GeoJSON/polyline.geojson", () =>
HttpResponse.json(polylineGeojson)
),
http.get("test/GeoJSON/polygon.geojson", () =>
HttpResponse.json(polygonGeojson)
),
http.get("test/GeoJSON/api.geojson", () => HttpResponse.json(apiGeojson)),
http.get("test/GeoJSON/api-list.geojson", () =>
HttpResponse.json(apiListGeojson)
),
http.get("test/GeoJSON/points.geojson", () =>
HttpResponse.json(pointsGeojson)
),
http.get("test/GeoJSON/multipoint.geojson", () =>
HttpResponse.json(multipointGeojson)
),
http.get("test/GeoJSON/multipolygon.geojson", () =>
HttpResponse.json(multipolygonGeojson)
),
http.get("test/GeoJSON/empty-geoms.geojson", () =>
HttpResponse.json(emptyGeomsGeojson)
),
http.get("test/KML/vic_police.kml", () =>
HttpResponse.text("this is not json")
)
);
});
describe("- with cesium primitives", function () {
let terria: Terria;
let geojson: GeoJsonCatalogItem;
beforeEach(function () {
terria = new Terria({
baseUrl: "./"
});
geojson = new GeoJsonCatalogItem("test-geojson", terria);
geojson.setTrait(CommonStrata.user, "forceCesiumPrimitives", true);
});
describe("GeoJsonCatalogItem", function () {
it("handles features with null geom", async () => {
geojson.setTrait(CommonStrata.user, "geoJsonData", {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {
LGA_CODE19: "19499",
LGA_NAME19: "No usual address (NSW)",
STE_CODE16: "1",
STE_NAME16: "New South Wales",
AREASQKM19: 0.0
},
geometry: null
},
{
type: "Feature",
properties: {},
geometry: {
type: "Polygon",
coordinates: [
[
[144.806671142578125, -32.96258644191746],
[145.008544921875, -33.19273094190691],
[145.557861328125, -32.659031913817685],
[145.042877197265625, -32.375322284319346],
[144.7998046875, -32.96719522935591],
[144.806671142578125, -32.96258644191746]
]
]
}
}
]
});
await geojson.loadMapItems();
expect(geojson.readyData?.features.length).toBe(1);
});
it("reloads when the URL is changed", async function () {
geojson.setTrait(
CommonStrata.user,
"url",
"data:application/json;base64,eyJ0eXBlIjoiRmVhdHVyZUNvbGxlY3Rpb24iLCJmZWF0dXJlcyI6W3sidHlwZSI6IkZlYXR1cmUiLCJwcm9wZXJ0aWVzIjp7fSwiZ2VvbWV0cnkiOnsidHlwZSI6IlBvaW50IiwiY29vcmRpbmF0ZXMiOlsxNDgsLTMxLjNdfX1dfQ=="
);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
?.getValue(JulianDate.now())
?.equalsEpsilon(Cartesian3.fromDegrees(148.0, -31.3), 0.0001)
).toBeTruthy("Doesn't match first location");
geojson.setTrait(
CommonStrata.user,
"url",
"data:application/json;base64,eyJ0eXBlIjoiRmVhdHVyZUNvbGxlY3Rpb24iLCJmZWF0dXJlcyI6W3sidHlwZSI6IkZlYXR1cmUiLCJwcm9wZXJ0aWVzIjp7fSwiZ2VvbWV0cnkiOnsidHlwZSI6IlBvaW50IiwiY29vcmRpbmF0ZXMiOlsxNTEsLTMzLjhdfX1dfQ=="
);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
?.getValue(JulianDate.now())
?.equalsEpsilon(Cartesian3.fromDegrees(151.0, -33.8), 0.0001)
).toBeTruthy("Doesn't match updated location");
});
});
describe("loading in EPSG:28356", function () {
it("works by URL", async function () {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/bike_racks.geojson"
);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
it("works by string", async function () {
geojson.setTrait(
CommonStrata.user,
"geoJsonString",
JSON.stringify(bikeRacksGeojson)
);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
it("works by data object", async function () {
geojson.setTrait(CommonStrata.user, "geoJsonData", bikeRacksGeojson);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
it("works with zip", async function () {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/bike_racks.zip"
);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
/* it("have default dataUrl and dataUrlType", function() {
geojson.updateFromJson({
url: "test/GeoJSON/bike_racks.geojson"
});
expect(geojson.dataUrl).toBe("test/GeoJSON/bike_racks.geojson");
expect(geojson.dataUrlType).toBe("direct");
})
it("use provided dataUrl", async function() {
geojson.url = "test/GeoJSON/bike_racks.geojson";
geojson.dataUrl = "test/test.html";
geojson.dataUrlType = "fake type";
await geojson.load();
expect(geojson.dataSource.entities.values.length).toBeGreaterThan(0);
expect(geojson.dataUrl).toBe("test/test.html");
expect(geojson.dataUrlType).toBe("fake type");
})
it("works by blob", async function() {
const blob = await loadBlob("test/GeoJSON/bike_racks.geojson");
geojson.data = blob;
geojson.dataSourceUrl = "anything.geojson";
await geojson.load();
expect(geojson.dataSource.entities.values.length).toBeGreaterThan(0);
}); */
});
describe("loading in CRS:84", function () {
it("works by URL", async function () {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/cemeteries.geojson"
);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
it("works by string", async function () {
geojson.setTrait(
CommonStrata.user,
"geoJsonString",
JSON.stringify(cemeteriesGeojson)
);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
it("works by blob", async function () {
geojson.setTrait(CommonStrata.user, "geoJsonData", cemeteriesGeojson);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
it("works with zip", async function () {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/cemeteries.zip"
);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
});
describe("loading without specified CRS (assumes EPSG:4326)", function () {
it("works by URL", async function () {
geojson.setTrait(CommonStrata.user, "url", "test/GeoJSON/gme.geojson");
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
it("works by string", async function () {
geojson.setTrait(
CommonStrata.user,
"geoJsonString",
JSON.stringify(gmeGeojson)
);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
it("works by blob", async function () {
geojson.setTrait(CommonStrata.user, "geoJsonData", gmeGeojson);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
it("works with zip", async function () {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/cemeteries.zip"
);
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values.length
).toBeGreaterThan(0);
expect(
(geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position
).toBeDefined();
});
});
// describe('loading Esri-style GeoJSON with an "envelope"', function() {
// it("works by URL", async function() {
// geojson.setTrait(
// CommonStrata.user,
// "url",
// "test/GeoJSON/EsriEnvelope.geojson"
// );
// await geojson.loadMapItems();
// expect(geojson.mapItems.length).toEqual(1);
// expect((geojson.mapItems[0] as GeoJsonDataSource).entities.values.length).toBeGreaterThan(0);
// expect((geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position).toBeDefined();
// });
//
// it("works by string", async function() {
// const geojsonString = await loadText("test/GeoJSON/EsriEnvelope.geojson");
// geojson.setTrait(CommonStrata.user, "geoJsonString", geojsonString);
// await geojson.loadMapItems();
// expect(geojson.mapItems.length).toEqual(1);
// expect((geojson.mapItems[0] as GeoJsonDataSource).entities.values.length).toBeGreaterThan(0);
// expect((geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position).toBeDefined();
// });
//
// it("works by blob", async function() {
// const blob = await loadJson("test/GeoJSON/EsriEnvelope.geojson");
// geojson.setTrait(CommonStrata.user, "geoJsonData", <JsonObject>blob);
// await geojson.loadMapItems();
// expect(geojson.mapItems.length).toEqual(1);
// expect((geojson.mapItems[0] as GeoJsonDataSource).entities.values.length).toBeGreaterThan(0);
// expect((geojson.mapItems[0] as GeoJsonDataSource).entities.values[0].position).toBeDefined();
// });
// });
describe("error handling", function () {
it("fails gracefully when the data at a URL is not JSON", async () => {
geojson.setTrait(CommonStrata.user, "url", "test/KML/vic_police.kml");
const error = (await geojson.loadMapItems()).error;
expect(error).toBeDefined("Load should not succeed");
});
it("fails gracefully when the provided string is not JSON", async () => {
geojson.setTrait(
CommonStrata.user,
"geoJsonString",
"this is not json"
);
const error = (await geojson.loadMapItems()).error;
expect(error).toBeDefined("Load should not succeed");
});
it("fails gracefully when the data at a URL is JSON but not GeoJSON", async () => {
geojson.setTrait(CommonStrata.user, "url", "test/CZML/verysimple.czml");
const error = (await geojson.loadMapItems()).error;
expect(error).toBeDefined("Load should not succeed");
});
it("fails gracefully when the provided string is JSON but not GeoJSON", async () => {
geojson.setTrait(
CommonStrata.user,
"geoJsonString",
JSON.stringify(verysimpleCzml)
);
const error = (await geojson.loadMapItems()).error;
expect(error).toBeDefined("Load should not succeed");
});
/*
it("fails gracefully when the provided blob is JSON but not GeoJSON", function(done) {
loadBlob("test/CZML/verysimple.czml").then(function(blob) {
geojson.data = blob;
geojson.dataSourceUrl = "anything.czml";
geojson
.load()
.then(function() {
done.fail("Load should not succeed.");
})
.catch(function(e) {
expect(e instanceof TerriaError).toBe(true);
done();
});
});
}); */
});
// describe("Adding and removing attribution", function() {
// var currentViewer;
// beforeEach(function() {
// currentViewer = geojson.terria.currentViewer;
// geojson.url = "test/GeoJSON/polygon.topojson";
// terria.disclaimerListener = function(member, callback) {
// callback();
// };
// geojson.isEnabled = true;
// });
// it("can add attribution", async function() {
// spyOn(currentViewer, "addAttribution");
// geojson.load();
// await geojson._loadForEnablePromise;
// expect(currentViewer.addAttribution).toHaveBeenCalled();
// });
// it("can remove attribution", async function() {
// spyOn(currentViewer, "removeAttribution");
// geojson.load();
// await geojson._loadForEnablePromise;
// geojson.isEnabled = false;
// expect(currentViewer.removeAttribution).toHaveBeenCalled();
// });
// });
describe("Support for time-varying geojson", () => {
it("Associates features with discrete times", async () => {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/time-based.geojson"
);
geojson.setTrait(CommonStrata.user, "timeProperty", "year");
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
const entities = (geojson.mapItems[0] as GeoJsonDataSource).entities
.values;
expect(entities.length).toEqual(2);
const entity1 = entities[0];
expect(
entity1.availability?.start.equals(
JulianDate.fromDate(new Date("2021"))
)
).toBeTruthy();
expect(
entity1.availability?.stop.equals(Iso8601.MAXIMUM_VALUE)
).toBeTruthy();
const entity2 = entities[1];
expect(
entity2.availability?.start.equals(
JulianDate.fromDate(new Date("2019"))
)
).toBeTruthy();
expect(
entity2.availability?.stop.equals(
JulianDate.fromDate(new Date("2021"))
)
).toBeTruthy();
});
});
describe("Support for filterByProperties", () => {
it("Filters correct features", async () => {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/time-based.geojson"
);
geojson.setTrait(CommonStrata.user, "filterByProperties", {
year: 2019
});
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
const entities = (geojson.mapItems[0] as GeoJsonDataSource).entities
.values;
expect(entities.length).toEqual(1);
const entity1 = entities[0];
expect(
entity1.properties?.getValue(terria.timelineClock.currentTime).year
).toBe(2019);
});
});
describe("Support for geojson with extruded heights", () => {
it("Sets polygon height properties correctly", async () => {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/height.geojson"
);
geojson.setTrait(CommonStrata.user, "heightProperty", "someProperty");
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
const entities = (geojson.mapItems[0] as GeoJsonDataSource).entities
.values;
expect(entities.length).toEqual(2);
const entity1 = entities[0];
expect(
entity1.polygon?.extrudedHeight?.getValue(
terria.timelineClock.currentTime
)
).toBe(10);
expect(
entity1.polygon?.heightReference?.getValue(
terria.timelineClock.currentTime
)
).toBe(HeightReference.CLAMP_TO_GROUND);
const entity2 = entities[1];
expect(
entity2.polygon?.extrudedHeight?.getValue(
terria.timelineClock.currentTime
)
).toBe(20);
expect(
entity2.polygon?.heightReference?.getValue(
terria.timelineClock.currentTime
)
).toBe(HeightReference.CLAMP_TO_GROUND);
expect(
entity2.polygon?.extrudedHeightReference?.getValue(
terria.timelineClock.currentTime
)
).toBe(HeightReference.RELATIVE_TO_GROUND);
});
});
describe("Per features styling", function () {
it("Applies styles to features according to their properties, respecting string case", async function () {
const name = "PETER FAGANS GRAVE";
const fill = "#0000ff";
runInAction(() => {
updateModelFromJson(geojson, CommonStrata.override, {
name: "test",
type: "geojson",
url: "test/GeoJSON/cemeteries.geojson",
perPropertyStyles: [
{
properties: { NAME: name },
style: {
fill: fill
},
caseSensitive: true
}
]
});
});
await geojson.loadMapItems();
const entity = (
geojson.mapItems[0] as GeoJsonDataSource
).entities.values.find(
(e) => e.properties?.getValue(JulianDate.now()).NAME === name
);
expect(entity).toBeDefined();
expect(entity?.properties?.getValue(JulianDate.now())?.fill).toEqual(
fill
);
});
});
});
describe("- with CZML template", function () {
let terria: Terria;
let geojson: GeoJsonCatalogItem;
beforeEach(function () {
terria = new Terria({
baseUrl: "./"
});
geojson = new GeoJsonCatalogItem("test-geojson", terria);
});
describe("Support for czml templates", () => {
it("supports points", async () => {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/points.geojson"
);
geojson.setTrait(CommonStrata.user, "czmlTemplate", {
cylinder: {
length: {
reference: "#properties.height"
},
topRadius: {
reference: "#properties.radius"
},
bottomRadius: {
reference: "#properties.radius"
},
material: {
solidColor: {
color: {
rgba: [0, 200, 0, 20]
}
}
}
}
});
await geojson.loadMapItems();
const entities = (geojson.mapItems[0] as GeoJsonDataSource).entities
.values;
expect(entities.length).toEqual(5);
const entity1 = entities[0];
expect(
entity1.cylinder?.length?.getValue(terria.timelineClock.currentTime)
).toBe("10");
expect(
entity1.cylinder?.bottomRadius?.getValue(
terria.timelineClock.currentTime
)
).toBe("10");
expect(entity1.properties?.someOtherProp?.getValue()).toBe("what");
const entity2 = entities[1];
expect(
entity2.cylinder?.length?.getValue(terria.timelineClock.currentTime)
).toBe("20");
expect(
entity2.cylinder?.bottomRadius?.getValue(
terria.timelineClock.currentTime
)
).toBe("5");
expect(entity2.properties?.someOtherProp?.getValue()).toBe("ok");
});
it("supports polygons", async () => {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/polygon.geojson"
);
geojson.setTrait(CommonStrata.user, "czmlTemplate", {
polygon: {
height: 10,
material: {
solidColor: {
color: {
rgba: [0, 200, 0, 20]
}
}
}
}
});
await geojson.loadMapItems();
const entities = (geojson.mapItems[0] as GeoJsonDataSource).entities
.values;
expect(entities.length).toEqual(1);
const entity1 = entities[0];
expect(
entity1.polygon?.height?.getValue(terria.timelineClock.currentTime)
).toBe(10);
expect(entity1.properties?.foo?.getValue()).toBe("hi");
expect(entity1.properties?.bar?.getValue()).toBe("bye");
});
});
});
describe("- tablestyling - with geojson-vt and protomaps", function () {
let terria: Terria;
let geojson: GeoJsonCatalogItem;
beforeEach(function () {
terria = new Terria({
baseUrl: "./"
});
geojson = new GeoJsonCatalogItem("test-geojson", terria);
});
it("Creates ProtomapsImageryProvider - with table styles", async () => {
geojson.setTrait(CommonStrata.user, "url", "test/GeoJSON/height.geojson");
await geojson.loadMapItems();
const mapItem = geojson.mapItems[0];
expect(
"imageryProvider" in mapItem &&
mapItem.imageryProvider instanceof ProtomapsImageryProvider
).toBeTruthy();
const protomaps =
"imageryProvider" in mapItem
? (mapItem.imageryProvider as ProtomapsImageryProvider)
: undefined;
if (!protomaps) throw "protomaps should be defined";
expect(geojson.activeStyle).toBe("someProperty");
expect(geojson.activeTableStyle.colorColumn?.name).toBe("someProperty");
expect(geojson.activeTableStyle.tableColorMap.minimumValue).toBe(10);
expect(geojson.activeTableStyle.tableColorMap.maximumValue).toBe(20);
expect(
geojson.activeTableStyle.tableColorMap.colorMap instanceof
ContinuousColorMap
).toBeTruthy();
const polygonSymbol = protomaps.paintRules[0]
.symbolizer as PolygonSymbolizer;
const polylineSymbol = protomaps.paintRules[1]
.symbolizer as LineSymbolizer;
const testFeature = {
props: {},
geomType: GeomType.Polygon,
numVertices: 0,
geom: [],
bbox: { minX: 0, minY: 0, maxX: 0, maxY: 0 }
};
const rowIdToColor: [number, string][] = [
[0, "rgb(255,245,240)"],
[1, "rgb(103,0,13)"]
];
rowIdToColor.forEach(([rowId, col]) => {
expect(
geojson.activeTableStyle.colorMap
.mapValueToColor(
geojson.activeTableStyle.colorColumn?.valuesForType?.[rowId]
)
?.toCssColorString()
).toBe(col);
expect(
polygonSymbol.fill.get(1, {
...testFeature,
geomType: GeomType.Polygon,
props: { _id_: rowId }
})
).toBe(col);
expect(
polygonSymbol.stroke.get(1, {
...testFeature,
geomType: GeomType.Polygon,
props: { _id_: rowId }
})
).toBe(getColor(terria.baseMapContrastColor).toCssHexString());
expect(
polygonSymbol.width.get(1, {
...testFeature,
geomType: GeomType.Polygon,
props: { _id_: rowId }
})
).toBe(
geojson.activeTableStyle.outlineStyleMap.traitValues.null.width ??
Infinity
);
expect(
polylineSymbol.color.get(1, {
...testFeature,
geomType: GeomType.Line,
props: { _id_: rowId }
})
).toBe(col);
});
});
it("Creates table features for points", async () => {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/bike_racks.geojson"
);
await geojson.loadMapItems();
const mapItem = geojson.mapItems[0] as CustomDataSource;
expect(isDataSource(mapItem)).toBeTruthy();
expect(geojson.activeStyle).toBe("number_of_");
expect(geojson.activeTableStyle.colorColumn?.name).toBe("number_of_");
expect(geojson.activeTableStyle.tableColorMap.minimumValue).toBe(0);
expect(geojson.activeTableStyle.tableColorMap.maximumValue).toBe(20);
expect(
geojson.activeTableStyle.tableColorMap.colorMap instanceof
ContinuousColorMap
).toBeTruthy();
// Test some colors
expect(
mapItem.entities.values[10].point?.color
?.getValue(terria.timelineClock.currentTime)
?.toCssColorString()
).toBe("rgb(254,227,214)");
expect(
mapItem.entities.values[20].point?.color
?.getValue(terria.timelineClock.currentTime)
?.toCssColorString()
).toBe("rgb(103,0,13)");
expect(geojson.disableSplitter).toBeTruthy();
});
it("Supports LegendOwnerTraits to override TableMixin.legends", async () => {
geojson.setTrait(CommonStrata.user, "url", "test/GeoJSON/height.geojson");
await geojson.loadMapItems();
expect(
"imageryProvider" in geojson.mapItems[0] &&
geojson.mapItems[0].imageryProvider instanceof
ProtomapsImageryProvider
).toBeTruthy();
expect(geojson.legends.length).toBe(1);
expect(geojson.legends[0].items.map((i) => i.color)).toEqual([
"rgb(103,0,13)",
"rgb(176,18,24)",
"rgb(226,48,40)",
"rgb(249,105,76)",
"rgb(252,160,130)",
"rgb(253,211,193)",
"rgb(255,245,240)"
]);
runInAction(() =>
updateModelFromJson(geojson, CommonStrata.definition, {
legends: [
{
url: "some-url"
}
]
})
);
expect(geojson.legends.length).toBe(1);
expect(geojson.legends[0].url).toBe("some-url");
});
it("Supports LegendOwnerTraits to override TableMixin.legends - with style disabled", async () => {
geojson.setTrait(CommonStrata.user, "url", "test/GeoJSON/height.geojson");
await geojson.loadMapItems();
runInAction(() => {
expect(
"imageryProvider" in geojson.mapItems[0] &&
geojson.mapItems[0].imageryProvider instanceof
ProtomapsImageryProvider
).toBeTruthy();
geojson.setTrait("user", "activeStyle", "");
expect(geojson.legends.length).toBe(1);
expect(geojson.legends[0].items.length).toBe(1);
expect(
geojson.legends[0].items.map(
(i) =>
// Look through default colors for disabled styles
// This can change, as `createColorForIdTransformer` will use the least used color (to avoid clashes)
!!StandardCssColors.modifiedBrewer8ClassSet2.find(
(c) =>
Color.fromCssColorString(c).toCssColorString() === i.color
)
)
).toEqual([true]);
updateModelFromJson(geojson, CommonStrata.definition, {
legends: [
{
url: "some-url"
}
]
});
expect(geojson.legends.length).toBe(1);
expect(geojson.legends[0].url).toBe("some-url");
});
});
it("correctly builds `Feature` from picked Entity", function () {
const picked = new Entity();
const feature = geojson.buildFeatureFromPickResult(
Cartesian2.ZERO,
picked
);
expect(feature).toBeDefined();
if (feature) {
expect(feature.cesiumEntity).toBe(picked);
}
});
});
describe("Disables protomaps (mvt) if geoJson simple styling is detected", () => {
let terria: Terria;
let geojson: GeoJsonCatalogItem;
beforeEach(function () {
terria = new Terria({
baseUrl: "./"
});
geojson = new GeoJsonCatalogItem("test-geojson", terria);
});
it("Unchanged - less than 50% features detected", async () => {
geojson.setTrait(CommonStrata.user, "url", "test/GeoJSON/api.geojson");
geojson.setTrait(
CommonStrata.user,
"geoJsonString",
`{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"stroke":"#555555","stroke-width":2,"stroke-opacity":1,"fill":"#ff0051","fill-opacity":0.5},"geometry":{"type":"Polygon","coordinates":[[[35.859375,53.54030739150022],[11.25,40.17887331434696],[15.1171875,14.604847155053898],[53.4375,44.84029065139799],[35.859375,53.54030739150022]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[85.4296875,66.93006025862448],[53.4375,43.83452678223682],[89.296875,34.88593094075317],[91.40625,50.958426723359935],[85.4296875,66.93006025862448]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[119.17968749999999,66.79190947341796],[100.1953125,53.74871079689897],[109.3359375,47.517200697839414],[119.17968749999999,66.79190947341796]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[71.015625,-2.811371193331128],[99.49218749999999,-2.811371193331128],[99.49218749999999,18.646245142670608],[71.015625,18.646245142670608],[71.015625,-2.811371193331128]]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[140.9765625,19.642587534013032],[134.296875,-17.978733095556155],[88.9453125,-36.597889133070204],[119.53125,15.961329081596647],[130.078125,27.371767300523047]]}}]}`
);
await geojson.loadMapItems();
expect(geojson.mapItems[0] instanceof GeoJsonDataSource).toBeFalsy();
expect(geojson.useTableStylingAndProtomaps).toBeTruthy();
expect(geojson.legends.length).toBe(1);
expect(geojson.disableSplitter).toBeFalsy();
});
it("Disabled protomaps - More than 50% features detected", async () => {
geojson.setTrait(CommonStrata.user, "url", "test/GeoJSON/api.geojson");
geojson.setTrait(
CommonStrata.user,
"geoJsonString",
`{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"stroke":"#555555","stroke-width":2,"stroke-opacity":1,"fill":"#ff0051","fill-opacity":0.5},"geometry":{"type":"Polygon","coordinates":[[[35.859375,53.54030739150022],[11.25,40.17887331434696],[15.1171875,14.604847155053898],[53.4375,44.84029065139799],[35.859375,53.54030739150022]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[85.4296875,66.93006025862448],[53.4375,43.83452678223682],[89.296875,34.88593094075317],[91.40625,50.958426723359935],[85.4296875,66.93006025862448]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[119.17968749999999,66.79190947341796],[100.1953125,53.74871079689897],[109.3359375,47.517200697839414],[119.17968749999999,66.79190947341796]]]}},{"type":"Feature","properties":{"marker-color":"#e000ff","marker-size":"large"},"geometry":{"type":"Point","coordinates":[30.585937499999996,-2.108898659243126]}},{"type":"Feature","properties":{"stroke":"#feff00","stroke-width":4,"stroke-opacity":1,"fill":"#fcffff","fill-opacity":0.5},"geometry":{"type":"Polygon","coordinates":[[[71.015625,-2.811371193331128],[99.49218749999999,-2.811371193331128],[99.49218749999999,18.646245142670608],[71.015625,18.646245142670608],[71.015625,-2.811371193331128]]]}},{"type":"Feature","properties":{"stroke":"#00ff02","stroke-width":2,"stroke-opacity":1},"geometry":{"type":"LineString","coordinates":[[140.9765625,19.642587534013032],[134.296875,-17.978733095556155],[88.9453125,-36.597889133070204],[119.53125,15.961329081596647],[130.078125,27.371767300523047]]}}]}`
);
await geojson.loadMapItems();
expect(geojson.mapItems[0] instanceof GeoJsonDataSource).toBeTruthy();
expect(geojson.useTableStylingAndProtomaps).toBeFalsy();
expect(geojson.legends.length).toBe(0);
expect(geojson.disableSplitter).toBeTruthy();
});
it("correctly matches feature _id_ with table rowId - with features with empty geoms", async () => {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/empty-geoms.geojson"
);
await geojson.loadMapItems();
expect(geojson.readyData?.features.length).toBe(4);
// Check _id_ vs rowIds
expect(
geojson.readyData?.features.map((f) => f.properties?.[FEATURE_ID_PROP])
).toEqual(geojson.rowIds);
// Check "someOtherProp" column
expect(
geojson.readyData?.features.map((f) => f.properties?.someOtherProp)
).toEqual(
geojson.tableColumns.find((c) => c.name === "someOtherProp")
?.values as string[]
);
});
it("correctly matches feature _id_ with table rowId - with filterByProperties", async () => {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/time-based.geojson"
);
geojson.setTrait(CommonStrata.user, "filterByProperties", {
year: 2019
});
await geojson.loadMapItems();
expect(geojson.readyData?.features.length).toBe(1);
expect(
geojson.readyData?.features.map((f) => f.properties?.[FEATURE_ID_PROP])
).toEqual(geojson.rowIds);
});
it("supports time", async function () {
geojson.setTrait(
CommonStrata.definition,
"url",
"test/GeoJSON/time-based-automatic-styles.geojson"
);
updateModelFromJson(geojson, CommonStrata.definition, {
currentTime: "2018-01-01",
defaultStyle: {
time: { timeColumn: "date", idColumns: ["idProperty"] }
}
});
const observeMapItems = reaction(
() => [geojson.mapItems],
() => {}
);
(await geojson.loadMapItems()).throwIfError();
expect(geojson.activeTableStyle.timeColumn?.name).toBe("date");
const firstProtomapsImageryProvider =
"imageryProvider" in geojson.mapItems[0]
? (geojson.mapItems[0].imageryProvider as ProtomapsImageryProvider)
: undefined;
if (!firstProtomapsImageryProvider) throw "protomaps should be defined";
const testFeature = {
props: {},
geomType: GeomType.Polygon,
numVertices: 0,
geom: [],
bbox: { minX: 0, minY: 0, maxX: 0, maxY: 0 }
};
const firstFilter = firstProtomapsImageryProvider.paintRules[0].filter;
if (!firstFilter) {
throw "filter should be defined";
}
// Current time is 2018-01-01
// First feature maps to 2018-01-01
testFeature.props = { [FEATURE_ID_PROP]: 0 };
expect(firstFilter(0, testFeature)).toBeTruthy();
// Second feature maps to 2019-01-01
testFeature.props = { [FEATURE_ID_PROP]: 1 };
expect(firstFilter(0, testFeature)).toBeFalsy();
// Change time to 2019-01-01
geojson.setTrait(CommonStrata.definition, "currentTime", "2019-01-01");
// Check new imagery provider
const nextProtomapsImageryProvider =
"imageryProvider" in geojson.mapItems[0]
? (geojson.mapItems[0].imageryProvider as ProtomapsImageryProvider)
: undefined;
if (!nextProtomapsImageryProvider) throw "protomaps should be defined";
const nextFilter = nextProtomapsImageryProvider.paintRules[0].filter;
if (!nextFilter) {
throw "filter should be defined";
}
testFeature.props = { [FEATURE_ID_PROP]: 0 };
expect(nextFilter(0, testFeature)).toBeFalsy();
testFeature.props = { [FEATURE_ID_PROP]: 1 };
expect(nextFilter(0, testFeature)).toBeTruthy();
expect(
firstProtomapsImageryProvider === nextProtomapsImageryProvider
).toBeFalsy();
// Now change the currentTime to 2019- g01-02 - this should not trigger a new imagery provider - as it within the current time interval
geojson.setTrait(CommonStrata.definition, "currentTime", "2019-01-02");
// Check new imagery provider
const lastProtomapsImageryProvider =
"imageryProvider" in geojson.mapItems[0]
? (geojson.mapItems[0].imageryProvider as ProtomapsImageryProvider)
: undefined;
if (!lastProtomapsImageryProvider) throw "protomaps should be defined";
expect(
nextProtomapsImageryProvider === lastProtomapsImageryProvider
).toBeTruthy();
observeMapItems();
});
});
describe("Support geojson through apis", () => {
let terria: Terria;
let geojson: GeoJsonCatalogItem;
beforeEach(function () {
terria = new Terria({
baseUrl: "./"
});
geojson = new GeoJsonCatalogItem("test-geojson", terria);
});
it("Extracts geojson nested in a json object", async () => {
geojson.setTrait(CommonStrata.user, "url", "test/GeoJSON/api.geojson");
geojson.setTrait(CommonStrata.user, "responseDataPath", "nested.data");
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
});
it("Extracts geojson from an array of json objects", async () => {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/api-list.geojson"
);
geojson.setTrait(CommonStrata.user, "responseGeoJsonPath", "nested.data");
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
});
});
describe("When given multiple URLs", function () {
let terria: Terria;
let geojson: GeoJsonCatalogItem;
beforeEach(function () {
terria = new Terria({
baseUrl: "./"
});
geojson = new GeoJsonCatalogItem("test-geojson", terria);
geojson.setTrait(CommonStrata.user, "forceCesiumPrimitives", true);
});
it("fetches and merges the responses as a single geojson feature collection", async function () {
updateModelFromJson(geojson, CommonStrata.user, {
urls: [
{ url: "test/GeoJSON/api.geojson", responseDataPath: "nested.data" },
{
url: "test/GeoJSON/points.geojson"
}
]
});
await geojson.loadMapItems();
expect(geojson.mapItems.length).toEqual(1);
const mapItem = geojson.mapItems[0] as CustomDataSource;
expect(isDataSource(mapItem)).toBeTruthy();
expect(mapItem.entities.values.length).toBe(7);
});
});
describe("handling MultiPoint features", function () {
let terria: Terria;
let geojson: GeoJsonCatalogItem;
beforeEach(function () {
terria = new Terria({
baseUrl: "./"
});
geojson = new GeoJsonCatalogItem("test-geojson", terria);
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/multipoint.geojson"
);
});
it("explodes multipoints as points", async function () {
await geojson.loadMapItems();
const points = geojson.mapItems[0] as CustomDataSource;
expect(points).toBeDefined();
expect(isDataSource(points)).toBeTruthy();
expect(points.entities.values.length).toEqual(5);
});
});
describe("handling MultiPolygon features", function () {
let terria: Terria;
let geojson: GeoJsonCatalogItem;
beforeEach(function () {
terria = new Terria({
baseUrl: "./"
});
geojson = new GeoJsonCatalogItem("test-geojson", terria);
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/multipolygon.geojson"
);
});
it("creates multi-polygon cesium primitives features", async function () {
runInAction(() =>
geojson.setTrait(CommonStrata.user, "forceCesiumPrimitives", true)
);
await geojson.loadMapItems();
const features = geojson.mapItems[0] as CustomDataSource;
expect(features).toBeDefined();
expect(isDataSource(features)).toBeTruthy();
expect(features.entities.values.length).toEqual(5);
expect(features.entities.values[0].polygon).toBeDefined();
expect(features.entities.values[1].polygon).toBeDefined();
expect(features.entities.values[2].polygon).toBeDefined();
expect(features.entities.values[3].polyline).toBeDefined();
expect(features.entities.values[4].polyline).toBeDefined();
});
});
describe("ProtomapsGeojsonSource", function () {
let terria: Terria;
let geojson: GeoJsonCatalogItem;
beforeEach(function () {
terria = new Terria({
baseUrl: "./"
});
geojson = new GeoJsonCatalogItem("test-geojson", terria);
});
it("creates multi-polygon protomaps features", async function () {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/multipolygon.geojson"
);
geojson.setTrait(CommonStrata.user, "forceCesiumPrimitives", false);
await geojson.loadMapItems();
const imageryParts = geojson.mapItems[0] as ImageryParts;
expect(imageryParts).toBeDefined();
expect(ImageryParts.is(imageryParts)).toBeTruthy();
const imageryProvider =
imageryParts.imageryProvider as ProtomapsImageryProvider;
expect(imageryProvider instanceof ProtomapsImageryProvider).toBeTruthy();
const source = imageryProvider.source as ProtomapsGeojsonSource;
expect(source instanceof ProtomapsGeojsonSource).toBeTruthy();
const features = await source.get(
{
x: 23,
y: 35,
z: 6
},
256
);
expect(features.get(GEOJSON_SOURCE_LAYER_NAME)?.length).toEqual(1);
const feature = features.get(GEOJSON_SOURCE_LAYER_NAME)?.[0];
expect(feature?.geom.length).toEqual(4);
expect(feature?.bbox).toEqual({
maxX: 288,
maxY: 204.3125,
minX: -32,
minY: -32
});
});
it("creates polygon protomaps features", async function () {
geojson.setTrait(
CommonStrata.user,
"url",
"test/GeoJSON/polygon.geojson"
);
await geojson.loadMapItems();
const imageryParts = geojson.mapItems[0] as ImageryParts;
expect(imageryParts).toBeDefined();
expect(ImageryParts.is(imageryParts)).toBeTruthy();
const imageryProvider =
imageryParts.imageryProvider as ProtomapsImageryProvider;
expect(imageryProvider instanceof ProtomapsImageryProvider).toBeTruthy();
const source = imageryProvider.source as ProtomapsGeojsonSource;
expect(source instanceof ProtomapsGeojsonSource).toBeTruthy();
const features = await sour