,
- "image" | "color" | "width" | "height" | "rotation" | "pixelOffset"
+ | "image"
+ | "color"
+ | "width"
+ | "height"
+ | "rotation"
+ | "pixelOffset"
+ | "scaleByDistance"
+ | "disableDepthTestDistance"
>;
export type SupportedPathGraphics = Pick<
@@ -69,6 +83,8 @@ export type SupportedLabelGraphics = Pick<
| "pixelOffset"
| "horizontalOrigin"
| "verticalOrigin"
+ | "scaleByDistance"
+ | "disableDepthTestDistance"
>;
/** For given TableStyle and rowId, return feature styling in a "cesium-friendly" format.
@@ -119,7 +135,9 @@ export function getFeatureStyle(style: TableStyle, rowId: number) {
| undefined = pointStyle
? {
color: color,
- pixelSize: pointSize ?? pointStyle?.height ?? pointStyle?.width
+ pixelSize: pointSize ?? pointStyle?.height ?? pointStyle?.width,
+ scaleByDistance: scaleByDistanceFromTraits(pointStyle?.scaleByDistance),
+ disableDepthTestDistance: pointStyle?.disableDepthTestDistance
}
: undefined;
@@ -153,7 +171,11 @@ export function getFeatureStyle(style: TableStyle, rowId: number) {
pixelOffset: new Cartesian2(
pointStyle.pixelOffset?.[0],
pointStyle.pixelOffset?.[1]
- )
+ ),
+ scaleByDistance: scaleByDistanceFromTraits(
+ pointStyle?.scaleByDistance
+ ),
+ disableDepthTestDistance: pointStyle?.disableDepthTestDistance
}
: undefined;
@@ -223,7 +245,9 @@ export function getFeatureStyle(style: TableStyle, rowId: number) {
? HorizontalOrigin.CENTER
: labelStyle.horizontalOrigin === "RIGHT"
? HorizontalOrigin.RIGHT
- : HorizontalOrigin.LEFT
+ : HorizontalOrigin.LEFT,
+ scaleByDistance: scaleByDistanceFromTraits(labelStyle?.scaleByDistance),
+ disableDepthTestDistance: labelStyle?.disableDepthTestDistance
}
: undefined;
@@ -240,3 +264,24 @@ export function getFeatureStyle(style: TableStyle, rowId: number) {
!pointStyle?.marker?.startsWith("data:image")
};
}
+
+/**
+ * Constructs a `NearFarScalar` instance from the ScaleByDistance traits or
+ * `undefined` if the settings is not meaningful.
+ */
+function scaleByDistanceFromTraits(
+ scaleByDistance: ScaleByDistanceTraits | undefined
+): NearFarScalar | undefined {
+ if (!scaleByDistance) {
+ return;
+ }
+
+ const { near, nearValue, far, farValue } = scaleByDistance;
+ if (nearValue === 1 && farValue === 1) {
+ // Return undefined as this value will have no effect when both near and
+ // far value is equal to 1.
+ return;
+ }
+
+ return new NearFarScalar(near, nearValue, far, farValue);
+}
diff --git a/lib/Traits/TraitsClasses/ApiRequestTraits.ts b/lib/Traits/TraitsClasses/ApiRequestTraits.ts
index ce6528bbca0..1b22710e9c1 100644
--- a/lib/Traits/TraitsClasses/ApiRequestTraits.ts
+++ b/lib/Traits/TraitsClasses/ApiRequestTraits.ts
@@ -63,7 +63,7 @@ export default class ApiRequestTraits extends mixTraits(UrlTraits) {
name: "Response data path",
type: "string",
description:
- "Path to relevent data in JSON response. eg: `some.user.name`, `some.users[0].name` or `some.users[].name`"
+ "Path to relevant data in JSON response. eg: `some.user.name`, `some.users[0].name` or `some.users[].name`"
})
responseDataPath?: string;
}
diff --git a/lib/Traits/TraitsClasses/ApiTableCatalogItemTraits.ts b/lib/Traits/TraitsClasses/ApiTableCatalogItemTraits.ts
index 12594eb9178..38c4ddd50c9 100644
--- a/lib/Traits/TraitsClasses/ApiTableCatalogItemTraits.ts
+++ b/lib/Traits/TraitsClasses/ApiTableCatalogItemTraits.ts
@@ -1,12 +1,13 @@
import objectArrayTrait from "../Decorators/objectArrayTrait";
+import primitiveArrayTrait from "../Decorators/primitiveArrayTrait";
import primitiveTrait from "../Decorators/primitiveTrait";
+import ModelTraits from "../ModelTraits";
import mixTraits from "../mixTraits";
-import ApiRequestTraits from "./ApiRequestTraits";
+import ApiRequestTraits, { QueryParamTraits } from "./ApiRequestTraits";
import AutoRefreshingTraits from "./AutoRefreshingTraits";
import CatalogMemberTraits from "./CatalogMemberTraits";
import LegendOwnerTraits from "./LegendOwnerTraits";
import TableTraits from "./Table/TableTraits";
-import primitiveArrayTrait from "../Decorators/primitiveArrayTrait";
export class ApiTableRequestTraits extends mixTraits(ApiRequestTraits) {
@primitiveTrait({
@@ -29,12 +30,28 @@ export class ApiTableRequestTraits extends mixTraits(ApiRequestTraits) {
columnMajorColumnNames?: string[] = ["value"];
}
+export class ApiTableColumnTraits extends ModelTraits {
+ @primitiveTrait({
+ name: "Name",
+ type: "string",
+ description: "Column name. This must match name in `columns`."
+ })
+ name?: string;
+
+ @primitiveTrait({
+ name: "Response data path",
+ type: "string",
+ description:
+ "Path to relevant data in JSON response. eg: `some.user.name`, `some.users[0].name` or `some.users[].name`. For data to be parsed correctly, it must return a primitive value (string, number, boolean)."
+ })
+ responseDataPath?: string;
+}
+
export default class ApiTableCatalogItemTraits extends mixTraits(
TableTraits,
CatalogMemberTraits,
LegendOwnerTraits,
- AutoRefreshingTraits,
- ApiRequestTraits
+ AutoRefreshingTraits
) {
@objectArrayTrait({
name: "APIs",
@@ -45,6 +62,15 @@ export default class ApiTableCatalogItemTraits extends mixTraits(
})
apis: ApiTableRequestTraits[] = [];
+ @objectArrayTrait({
+ name: "API Columns",
+ type: ApiTableColumnTraits,
+ description:
+ 'ApiTableCatalogItem specific column configuration. Note: you **must** define which columns to use from API response in the `columns` `TableColumnTraits` - for example `[{name:"some-key-in-api-response", ...}]`. This object only adds additional properties',
+ idProperty: "name"
+ })
+ apiColumns: ApiTableColumnTraits[] = [];
+
@primitiveTrait({
name: "Id key",
type: "string",
@@ -59,4 +85,22 @@ export default class ApiTableCatalogItemTraits extends mixTraits(
"When true, new data received through APIs will be appended to existing data. If false, new data will replace existing data."
})
shouldAppendNewData?: boolean = true;
+
+ @objectArrayTrait({
+ name: "Common query parameters",
+ type: QueryParamTraits,
+ description:
+ "Common query parameters to supply to all APIs. These are merged into the query parameters for each API.",
+ idProperty: "name"
+ })
+ queryParameters: QueryParamTraits[] = [];
+
+ @objectArrayTrait({
+ name: "Common query parameters for updates",
+ type: QueryParamTraits,
+ description:
+ "Common query parameters to supply to all APIs on subsequent calls after the first call. These are merged into the update query parameters for each API.",
+ idProperty: "name"
+ })
+ updateQueryParameters: QueryParamTraits[] = [];
}
diff --git a/lib/Traits/TraitsClasses/GeoJsonCatalogItemTraits.ts b/lib/Traits/TraitsClasses/GeoJsonCatalogItemTraits.ts
index 771a1a97001..f220da614ab 100644
--- a/lib/Traits/TraitsClasses/GeoJsonCatalogItemTraits.ts
+++ b/lib/Traits/TraitsClasses/GeoJsonCatalogItemTraits.ts
@@ -1,5 +1,6 @@
import { JsonObject } from "../../Core/Json";
import anyTrait from "../Decorators/anyTrait";
+import objectArrayTrait from "../Decorators/objectArrayTrait";
import primitiveTrait from "../Decorators/primitiveTrait";
import mixTraits from "../mixTraits";
import ApiRequestTraits from "./ApiRequestTraits";
@@ -9,6 +10,15 @@ export default class GeoJsonCatalogItemTraits extends mixTraits(
GeoJsonTraits,
ApiRequestTraits
) {
+ @objectArrayTrait({
+ type: ApiRequestTraits,
+ name: "URLs",
+ idProperty: "url",
+ description:
+ "Array of GeoJSON URLs to fetch. The GeoJSON features from the URL responses will be merged into one single FeatureCollection. When this trait is specified, the `url` trait is ignored."
+ })
+ urls?: ApiRequestTraits[];
+
@anyTrait({
name: "geoJsonData",
description: "A geojson data object"
diff --git a/lib/Traits/TraitsClasses/GeoJsonTraits.ts b/lib/Traits/TraitsClasses/GeoJsonTraits.ts
index 076d36b80ab..58c1e5d3996 100644
--- a/lib/Traits/TraitsClasses/GeoJsonTraits.ts
+++ b/lib/Traits/TraitsClasses/GeoJsonTraits.ts
@@ -155,4 +155,12 @@ export class GeoJsonTraits extends mixTraits(
- \`heightOffset: number\` to offset height values (in m)`
})
czmlTemplate?: JsonObject;
+
+ @primitiveTrait({
+ type: "boolean",
+ name: "Explode MultiPoints",
+ description:
+ "Replaces `MultiPoint` features with its equivalent `Point` features when `true`. This is useful for example when using Table mode which does not support `MultiPoint` features currently."
+ })
+ explodeMultiPoints = true;
}
diff --git a/lib/Traits/TraitsClasses/Table/LabelStyleTraits.ts b/lib/Traits/TraitsClasses/Table/LabelStyleTraits.ts
index f5c3b720f33..b06c3173809 100644
--- a/lib/Traits/TraitsClasses/Table/LabelStyleTraits.ts
+++ b/lib/Traits/TraitsClasses/Table/LabelStyleTraits.ts
@@ -4,6 +4,7 @@ import objectTrait from "../../Decorators/objectTrait";
import primitiveArrayTrait from "../../Decorators/primitiveArrayTrait";
import primitiveTrait from "../../Decorators/primitiveTrait";
import mixTraits from "../../mixTraits";
+import ScaleByDistanceTraits from "../ScaleByDistanceTraits";
import {
BinStyleTraits,
EnumStyleTraits,
@@ -60,6 +61,23 @@ export class LabelSymbolTraits extends mixTraits(TableStyleMapSymbolTraits) {
})
scale = 1;
+ @objectTrait({
+ name: "Scale by distance",
+ description:
+ "Scales a point, billboard or label feature by its distance from the camera.",
+ type: ScaleByDistanceTraits,
+ isNullable: true
+ })
+ scaleByDistance?: ScaleByDistanceTraits;
+
+ @primitiveTrait({
+ name: "Disable depth test distance",
+ description:
+ "The distance from camera at which to disable depth testing, for example to prevent clipping of features againts terrain. Set to a very large value like 99999999 to disable depth testing altogether. When not defined or set to 0, depth testing is always applied.",
+ type: "number"
+ })
+ disableDepthTestDistance?: number;
+
@primitiveTrait({
name: "Fill color",
description: "The fill color of the label.",
diff --git a/lib/Traits/TraitsClasses/Table/PointStyleTraits.ts b/lib/Traits/TraitsClasses/Table/PointStyleTraits.ts
index 891f76fa254..75aac6853d5 100644
--- a/lib/Traits/TraitsClasses/Table/PointStyleTraits.ts
+++ b/lib/Traits/TraitsClasses/Table/PointStyleTraits.ts
@@ -4,6 +4,7 @@ import objectTrait from "../../Decorators/objectTrait";
import primitiveArrayTrait from "../../Decorators/primitiveArrayTrait";
import primitiveTrait from "../../Decorators/primitiveTrait";
import mixTraits from "../../mixTraits";
+import ScaleByDistanceTraits from "../ScaleByDistanceTraits";
import {
BinStyleTraits,
EnumStyleTraits,
@@ -47,6 +48,23 @@ export class PointSymbolTraits extends mixTraits(TableStyleMapSymbolTraits) {
type: "number"
})
width?: number = 16;
+
+ @objectTrait({
+ name: "Scale by distance",
+ description:
+ "Scales a point, billboard or label feature by its distance from the camera.",
+ type: ScaleByDistanceTraits,
+ isNullable: true
+ })
+ scaleByDistance?: ScaleByDistanceTraits;
+
+ @primitiveTrait({
+ name: "Disable depth test distance",
+ description:
+ "The distance from camera at which to disable depth testing, for example to prevent clipping of features againts terrain. Set to a very large value like 99999999 to disable depth testing altogether. When not defined or set to 0, depth testing is always applied.",
+ type: "number"
+ })
+ disableDepthTestDistance?: number;
}
export class EnumPointSymbolTraits extends mixTraits(
diff --git a/package.json b/package.json
index 6bd2717d8d3..5a8efed8ea5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "terriajs",
- "version": "8.3.9",
+ "version": "8.4.1",
"description": "Geospatial data visualization platform.",
"license": "Apache-2.0",
"engines": {
@@ -12,16 +12,17 @@
},
"resolutions": {
"colors": "1.4.0",
- "@types/node": "^18.15.11"
+ "@types/node": "^18.15.11",
+ "@types/css-font-loading-module": "^0.0.9"
},
"dependencies": {
- "@babel/core": "^7.22.9",
- "@babel/parser": "^7.22.7",
+ "@babel/core": "^7.23.5",
+ "@babel/parser": "^7.23.5",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.22.7",
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
- "@babel/preset-env": "^7.22.9",
- "@babel/preset-react": "^7.22.5",
+ "@babel/preset-env": "^7.23.5",
+ "@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.22.5",
"@mapbox/geojson-merge": "^1.1.1",
"@mapbox/point-geometry": "^0.1.0",
@@ -178,20 +179,20 @@
"svg-sprite-loader": "4.1.3",
"terriajs-cesium": "1.92.0-tile-error-provider-fix-2",
"terriajs-html2canvas": "1.0.0-alpha.12-terriajs-1",
- "thredds-catalog-crawler": "0.0.5",
+ "thredds-catalog-crawler": "0.0.6",
"ts-essentials": "^5.0.0",
"ts-loader": "^5.3.3",
"ts-node": "^5.0.1",
- "typescript": "^4.9.5",
+ "typescript": "~5.2.0",
"urijs": "^1.18.12",
"url-loader": "^1.1.2",
- "webpack": "~4.46.0",
+ "webpack": "~4.47.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.1.14",
"worker-loader": "^2.0.0"
},
"devDependencies": {
- "@babel/eslint-parser": "^7.12.16",
+ "@babel/eslint-parser": "^7.23.3",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@types/dateformat": "^3.0.1",
"@types/node": "^18.15.11",
@@ -219,17 +220,17 @@
"karma-jasmine": "^1.1.0",
"karma-opera-launcher": "^1.0.0",
"karma-safari-launcher": "^1.0.0",
- "karma-spec-reporter": "^0.0.32",
+ "karma-spec-reporter": "^0.0.36",
"klaw-sync": "^4.0.0",
"minimist": "^1.2.8",
"node-notifier": "^5.1.2",
- "node-sass": "^6.0.1",
"plugin-error": "^1.0.1",
"prettier": "2.7.1",
"pretty-quick": "^1.10.0",
"react-shallow-testutils": "^3.0.0",
"react-test-renderer": "^16.3.2",
"regenerator-runtime": "^0.13.2",
+ "sass": "^1.66.1",
"terriajs-server": "^4.0.0",
"yaml": "^1.10.0"
},
diff --git a/test/ModelMixins/TableMixinSpec.ts b/test/ModelMixins/TableMixinSpec.ts
index f74c8d54f18..1cd76b0154d 100644
--- a/test/ModelMixins/TableMixinSpec.ts
+++ b/test/ModelMixins/TableMixinSpec.ts
@@ -34,6 +34,7 @@ import TableTrailStyleTraits, {
import HorizontalOrigin from "terriajs-cesium/Source/Scene/HorizontalOrigin";
import VerticalOrigin from "terriajs-cesium/Source/Scene/VerticalOrigin";
+import ScaleByDistanceTraits from "../../lib/Traits/TraitsClasses/ScaleByDistanceTraits";
const LatLonValCsv = require("raw-loader!../../wwwroot/test/csv/lat_lon_val.csv");
const LatLonEnumCsv = require("raw-loader!../../wwwroot/test/csv/lat_lon_enum.csv");
@@ -2024,6 +2025,64 @@ describe("TableMixin", function () {
});
});
+ it("correctly applies disableDepthTestDistance trait", async function () {
+ item.setTrait(CommonStrata.user, "csvString", LatLonValCsv);
+ item.setTrait(CommonStrata.user, "styles", [
+ createStratumInstance(TableStyleTraits, {
+ id: "test-style",
+ point: createStratumInstance(TablePointStyleTraits, {
+ null: createStratumInstance(PointSymbolTraits, {
+ disableDepthTestDistance: 42
+ })
+ })
+ })
+ ]);
+ item.setTrait(CommonStrata.user, "activeStyle", "test-style");
+ await item.loadMapItems();
+
+ const mapItem = item.mapItems[0] as CustomDataSource;
+ mapItem.entities.values.forEach((entity) =>
+ expect(
+ entity.point?.disableDepthTestDistance?.getValue(JulianDate.now())
+ ).toBe(42)
+ );
+ });
+
+ it("correctly applies scaleByDistance traits", async function () {
+ item.setTrait(CommonStrata.user, "csvString", LatLonValCsv);
+ item.setTrait(CommonStrata.user, "styles", [
+ createStratumInstance(TableStyleTraits, {
+ id: "test-style",
+ point: createStratumInstance(TablePointStyleTraits, {
+ null: createStratumInstance(PointSymbolTraits, {
+ scaleByDistance: createStratumInstance(ScaleByDistanceTraits, {
+ near: 0,
+ nearValue: 4,
+ far: 50000,
+ farValue: 10
+ })
+ })
+ })
+ })
+ ]);
+ item.setTrait(CommonStrata.user, "activeStyle", "test-style");
+ await item.loadMapItems();
+
+ const mapItem = item.mapItems[0] as CustomDataSource;
+ mapItem.entities.values.forEach((entity) =>
+ expect(
+ entity.point?.scaleByDistance?.getValue(JulianDate.now())
+ ).toEqual(
+ jasmine.objectContaining({
+ near: 0,
+ nearValue: 4,
+ far: 50000,
+ farValue: 10
+ })
+ )
+ );
+ });
+
it("doesn't pick hidden style as default activeStyle", async function () {
item.setTrait(CommonStrata.user, "csvString", ParkingSensorDataCsv);
diff --git a/test/Models/Catalog/CatalogItems/ApiTableCatalogItemSpec.ts b/test/Models/Catalog/CatalogItems/ApiTableCatalogItemSpec.ts
index b58ac6289ec..fc3862175b6 100644
--- a/test/Models/Catalog/CatalogItems/ApiTableCatalogItemSpec.ts
+++ b/test/Models/Catalog/CatalogItems/ApiTableCatalogItemSpec.ts
@@ -248,4 +248,94 @@ describe("ApiTableCatalogItem", function () {
expect(valueColumn).toEqual(["value", "8", "9", "7"]);
});
});
+
+ describe("supports apiColumns", function () {
+ beforeEach(function () {
+ jasmine.Ajax.stubRequest(
+ "build/TerriaJS/data/regionMapping.json"
+ ).andReturn({ responseText: regionMapping });
+
+ jasmine.Ajax.stubRequest(
+ proxyCatalogItemUrl(apiCatalogItem, "https://terria.io/position.json")
+ ).andReturn({
+ responseText: positionApiResponse
+ });
+
+ runInAction(() => {
+ updateModelFromJson(apiCatalogItem, CommonStrata.definition, {
+ idKey: "id",
+ apis: [
+ {
+ url: "https://terria.io/position.json"
+ }
+ ],
+ columns: [
+ {
+ name: "id"
+ },
+ {
+ name: "some embedded value"
+ },
+ {
+ name: "some other embedded value"
+ },
+ {
+ name: "some fake embedded value"
+ }
+ ],
+
+ apiColumns: [
+ {
+ name: "some embedded value",
+ responseDataPath: "some.embedded.0"
+ },
+ {
+ name: "some other embedded value",
+ responseDataPath: "some.embedded.1"
+ },
+ {
+ name: "some fake embedded value",
+ responseDataPath: "some.embedded.path.that[].does.not.exist.1"
+ }
+ ]
+ });
+ });
+ });
+
+ it("uses responseDataPath", async function () {
+ await apiCatalogItem.loadMapItems();
+ const table = apiCatalogItem.dataColumnMajor;
+ expect(table).toBeDefined();
+ let definedTable: string[][] = table!;
+ const embeddedColumn = definedTable.find(
+ ([name]) => name === "some embedded value"
+ );
+ expect(embeddedColumn).toEqual([
+ "some embedded value",
+ "first_element 1",
+ "first_element 2",
+ "first_element 3"
+ ]);
+
+ const otherEmbeddedColumn = definedTable.find(
+ ([name]) => name === "some other embedded value"
+ );
+ expect(otherEmbeddedColumn).toEqual([
+ "some other embedded value",
+ "second_element 1",
+ "second_element 2",
+ "second_element 3"
+ ]);
+
+ const fakeEmbeddedColumn = definedTable.find(
+ ([name]) => name === "some fake embedded value"
+ );
+ expect(fakeEmbeddedColumn).toEqual([
+ "some fake embedded value",
+ "",
+ "",
+ ""
+ ]);
+ });
+ });
});
diff --git a/test/Models/Catalog/CatalogItems/GeoJsonCatalogItemSpec.ts b/test/Models/Catalog/CatalogItems/GeoJsonCatalogItemSpec.ts
index 3326b0705d4..2c98dc04dd2 100644
--- a/test/Models/Catalog/CatalogItems/GeoJsonCatalogItemSpec.ts
+++ b/test/Models/Catalog/CatalogItems/GeoJsonCatalogItemSpec.ts
@@ -3,6 +3,7 @@ import { GeomType, LineSymbolizer, PolygonSymbolizer } from "protomaps";
import { CustomDataSource } from "terriajs-cesium";
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";
@@ -10,6 +11,7 @@ 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 { JsonObject } from "../../../../lib/Core/Json";
+import StandardCssColors from "../../../../lib/Core/StandardCssColors";
import loadJson from "../../../../lib/Core/loadJson";
import loadText from "../../../../lib/Core/loadText";
import ContinuousColorMap from "../../../../lib/Map/ColorMap/ContinuousColorMap";
@@ -883,9 +885,18 @@ describe("GeoJsonCatalogItemSpec", () => {
expect(geojson.legends.length).toBe(1);
expect(geojson.legends[0].items.length).toBe(1);
- expect(geojson.legends[0].items.map((i) => i.color)).toEqual([
- "rgb(102,194,165)"
- ]);
+
+ 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: [
@@ -1120,6 +1131,61 @@ describe("GeoJsonCatalogItemSpec", () => {
});
});
+ describe("When given multiple URLs", function () {
+ let terria: Terria;
+ let geojson: GeoJsonCatalogItem;
+
+ beforeEach(async 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("geojson can be split", function () {
let terria: Terria;
let geojson: GeoJsonCatalogItem;
diff --git a/test/Models/Catalog/esri/ArcGisFeatureServerCatalogItemSpec.ts b/test/Models/Catalog/esri/ArcGisFeatureServerCatalogItemSpec.ts
index 1e33f01b42a..d70989b6580 100644
--- a/test/Models/Catalog/esri/ArcGisFeatureServerCatalogItemSpec.ts
+++ b/test/Models/Catalog/esri/ArcGisFeatureServerCatalogItemSpec.ts
@@ -311,7 +311,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
pixelOffset: [0, 0],
width: 6,
height: 16,
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
});
expect(tableStyle.pointStyleMap.traitValues.enum).toEqual([
@@ -322,7 +323,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "1",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
},
{
legendTitle: "2",
@@ -331,7 +333,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "2",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
},
{
legendTitle: "3",
@@ -340,7 +343,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "3",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
},
{
legendTitle: "4",
@@ -349,7 +353,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "4",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
},
{
legendTitle: "5",
@@ -358,7 +363,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "5",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
},
{
legendTitle: "6",
@@ -367,7 +373,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "6",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
},
{
legendTitle: "7",
@@ -376,7 +383,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "7",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
},
{
legendTitle: "8",
@@ -385,7 +393,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "8",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
},
{
legendTitle: "9",
@@ -394,7 +403,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "9",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
},
{
legendTitle: "10",
@@ -403,7 +413,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "10",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
},
{
legendTitle: "11",
@@ -412,7 +423,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "11",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
},
{
legendTitle: "12",
@@ -421,7 +433,8 @@ describe("ArcGisFeatureServerCatalogItem", function () {
width: 2,
height: 16,
value: "12",
- rotation: 0
+ rotation: 0,
+ scaleByDistance: { near: 0, nearValue: 1, far: 1, farValue: 1 }
}
]);
diff --git a/test/Models/Catalog/esri/ArcGisMapServerCatalogItemSpec.ts b/test/Models/Catalog/esri/ArcGisMapServerCatalogItemSpec.ts
index be483fc3108..99b8d03e723 100644
--- a/test/Models/Catalog/esri/ArcGisMapServerCatalogItemSpec.ts
+++ b/test/Models/Catalog/esri/ArcGisMapServerCatalogItemSpec.ts
@@ -262,6 +262,62 @@ describe("ArcGisMapServerCatalogItem", function () {
imageryProvider.requestImage(0, 0, 100);
expect(item.scaleWorkbenchInfo).toBeDefined();
});
+
+ it("usePreCachedTilesIfAvailable = false if requesting specific layers", async function () {
+ runInAction(() => {
+ item = new ArcGisMapServerCatalogItem("test", new Terria());
+ item.setTrait(CommonStrata.definition, "url", mapServerUrl);
+ item.setTrait(CommonStrata.definition, "layers", "31,32");
+ });
+ await item.loadMapItems();
+
+ expect(item.layersArray.length).toBe(2);
+
+ imageryProvider = item.mapItems[0]
+ .imageryProvider as ArcGisMapServerImageryProvider;
+ expect((imageryProvider as any)._usePreCachedTilesIfAvailable).toBe(
+ false
+ );
+ });
+
+ it("usePreCachedTilesIfAvailable = true if not requesting specific layers", async function () {
+ runInAction(() => {
+ item = new ArcGisMapServerCatalogItem("test", new Terria());
+ item.setTrait(CommonStrata.definition, "url", mapServerUrl);
+ item.setTrait(CommonStrata.definition, "layers", undefined);
+ });
+ await item.loadMapItems();
+ expect(item.layersArray.length).toBe(74);
+
+ imageryProvider = item.mapItems[0]
+ .imageryProvider as ArcGisMapServerImageryProvider;
+ expect((imageryProvider as any)._usePreCachedTilesIfAvailable).toBe(
+ true
+ );
+ });
+
+ it("usePreCachedTilesIfAvailable = true if requesting all layers", async function () {
+ runInAction(() => {
+ item = new ArcGisMapServerCatalogItem("test", new Terria());
+ item.setTrait(CommonStrata.definition, "url", mapServerUrl);
+ item.setTrait(
+ CommonStrata.definition,
+ "layers",
+ new Array(74)
+ .fill(0)
+ .map((_, i) => i)
+ .join(",")
+ );
+ });
+ await item.loadMapItems();
+ expect(item.layersArray.length).toBe(74);
+
+ imageryProvider = item.mapItems[0]
+ .imageryProvider as ArcGisMapServerImageryProvider;
+ expect((imageryProvider as any)._usePreCachedTilesIfAvailable).toBe(
+ true
+ );
+ });
});
});
diff --git a/test/ReactViews/DimensionSelectorSectionSpec.tsx b/test/ReactViews/DimensionSelectorSectionSpec.tsx
index ccc2f9e185a..8093cee1663 100644
--- a/test/ReactViews/DimensionSelectorSectionSpec.tsx
+++ b/test/ReactViews/DimensionSelectorSectionSpec.tsx
@@ -13,6 +13,7 @@ import SelectableDimensions, {
SelectableDimension as SelectableDimensionModel
} from "../../lib/Models/SelectableDimensions/SelectableDimensions";
import Terria from "../../lib/Models/Terria";
+import Collapsible from "../../lib/ReactViews/Custom/Collapsible/Collapsible";
import { SelectableDimensionGroup } from "../../lib/ReactViews/SelectableDimensions/Group";
import SelectableDimension from "../../lib/ReactViews/SelectableDimensions/SelectableDimension";
import { terriaTheme } from "../../lib/ReactViews/StandardUserInterface";
@@ -361,9 +362,9 @@ describe("DimensionSelectorSection", function () {
const group = section.root.findByType(SelectableDimensionGroup);
expect(group.props.dim.type).toEqual("group");
- const collapsible = group.children[0];
-
- if (typeof collapsible === "string") throw "Invalid collapsible";
+ const collapsible: TestRenderer.ReactTestInstance = (
+ group.children[0] as any
+ ).children[0].children[0];
const button = collapsible.children[0];
diff --git a/tsconfig-node.json b/tsconfig-node.json
index 81d91c9ee3e..37123944db3 100644
--- a/tsconfig-node.json
+++ b/tsconfig-node.json
@@ -1,25 +1,9 @@
// This tsconfig is used to build the model layer only for use in a node.js app.
-// Build with ./node_modules/.bin/tsc -p tsconfig-models.json
+// Build with ./node_modules/.bin/tsc -p tsconfig-node.json
{
+ "extends": "./tsconfig.json",
"compilerOptions": {
- "target": "es2019",
- "experimentalDecorators": true,
- "module": "esNext",
- "moduleResolution": "node",
- "sourceMap": true,
- "strict": true,
- "allowJs": true,
- "jsx": "react",
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "useDefineForClassFields": true, // required for mobx6 - https://mobx.js.org/installation.html#use-spec-compliant-transpilation-for-class-properties
"outDir": "dist",
- "resolveJsonModule": true,
- "typeRoots": [
- "./lib/ThirdParty"
- //"./node_modules/@types",
- //"../node_modules/@types"
- ],
"types": [
"terriajs-cesium",
"mapbox__geojson-merge",
@@ -28,8 +12,8 @@
"pmtiles",
"terriajs-html2canvas",
"urijs",
- "styled-components" // eventually it will be required anyway for SSR.
- //"react"
+ "styled-components", // eventually it will be required anyway for SSR.
+ "react"
]
},
"include": [
diff --git a/tsconfig.json b/tsconfig.json
index cdf2ea1f355..a9c5877fa40 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,13 +7,23 @@
"sourceMap": true,
"strict": true,
"allowJs": true,
- "jsx": "preserve",
+ "jsx": "react-jsx",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"useDefineForClassFields": true, // required for mobx6 - https://mobx.js.org/installation.html#use-spec-compliant-transpilation-for-class-properties
"outDir": "ts-out",
"resolveJsonModule": true,
- "typeRoots": ["./lib/ThirdParty/**/*.d.ts"],
+ "typeRoots": [
+ "./lib/ThirdParty/",
+ "./node_modules/",
+ "./node_modules/@types/",
+ "../node_modules/",
+ "../node_modules/@types/",
+ "../../node_modules/",
+ "../../node_modules/@types/",
+ "../../../node_modules/",
+ "../../../node_modules/@types/"
+ ],
"types": ["terriajs-cesium"]
},
"include": [
diff --git a/wwwroot/languages/ca/translation.json b/wwwroot/languages/ca/translation.json
index 146a03d8b9a..6709c2c29d1 100644
--- a/wwwroot/languages/ca/translation.json
+++ b/wwwroot/languages/ca/translation.json
@@ -235,8 +235,9 @@
"title": "Comptar conjunts de dades",
"btnCount": "Comptar conjunts de dades",
"noMapDataEnabled": "No hi ha dades del mapa activades",
- "mapDataState": "{{count}} conjunt de dades actiu al mapa",
- "mapDataState_plural": "{{count}} conjunts de dades actius al mapa",
+ "mapDataState_0": "{{count}} conjunt de dades actiu al mapa",
+ "mapDataState_1": "{{count}} conjunts de dades actius al mapa",
+ "mapDataState_2": "{{count}} conjunts de dades actius al mapa",
"totals": "El catàleg conté {{items}} articles en {{groups}} grups.
"
},
"toolsPanel": {
@@ -374,8 +375,9 @@
"storyEditor": "Editor d'històries",
"delete": "Esborrar",
"message": "<2>Aquest és el vostre editor d'històries2><1>Creeu i compartiu històries interactives directament des del vostre mapa<1><0><0>0>Com començar 0>1>1 >",
- "removeAllStoriesDialog": "Esteu segur que voleu suprimir {{ count }} escena?",
- "removeAllStoriesDialog_plural": "Esteu segur que voleu suprimir {{ count }} escenes?",
+ "removeAllStoriesDialog_0": "Esteu segur que voleu suprimir {{ count }} escena?",
+ "removeAllStoriesDialog_1": "Esteu segur que voleu suprimir {{ count }} escenes?",
+ "removeAllStoriesDialog_2": "Esteu segur que voleu suprimir {{ count }} escenes?",
"captureScene": "Captura l'escena",
"promptHtml1": "<0>Podeu veure i crear històries en qualsevol moment fent clic aquí.0>",
"loadSceneErrorTitle": "Error carregant Story Scene",
@@ -630,7 +632,10 @@
"searchNoCatalogueItem": "Ho sentim, no hi ha cap article del catàleg que coincideixi amb la teva cerca.",
"inMultipleLocations": "En diverses ubicacions, com ara: ",
"inDataCatalogue": "Al Catàleg de Dades",
- "searchErrorOccurred": "S'ha produït un error durant la cerca. Comprova la teva connexió a Internet o torna-ho a provar més tard."
+ "searchErrorOccurred": "S'ha produït un error durant la cerca. Comprova la teva connexió a Internet o torna-ho a provar més tard.",
+ "searchMinCharacters_0": "Heu d'introduir un mínim de {{count}} caracter",
+ "searchMinCharacters_1": "Heu d'introduir un mínim de {{count}} caràcters",
+ "searchMinCharacters_2": "Heu d'introduir un mínim de {{count}} caràcters"
},
"terriaViewer": {
"slowWebGLAvailableMessageII": "{{appName}} funciona millor amb un navegador web que admeti {{webGL}}, incloses les darreres versions de $t(browsers.chrome), $t(browsers.firefox), $t(browsers.edge), i $t(browsers.safari). Sembla que el vostre navegador web no admet WebGL, de manera que veureu una experiència limitada només en 2D.",
@@ -2118,8 +2123,9 @@
"loadError": "Error en carregar els paràmetres de cerca. Comproveu la consola per trobar errors detallats.",
"noParameters": "No s'han trobat paràmetres cercables",
"searching": "Cercant",
- "resultsCount": "S'ha trobat {{count}} coincidència",
- "resultsCount_plural": "S'ha trobat {{count}} coincidències",
+ "resultsCount_0": "S'ha trobat {{count}} coincidència",
+ "resultsCount_1": "S'ha trobat {{count}} coincidències",
+ "resultsCount_2": "S'ha trobat {{count}} coincidències",
"searchError": "La cerca ha fallat. Comproveu la consola per trobar errors detallats.",
"resetBtnText": "Esborra l'entrada",
"searchBtnText": "Cerca"
@@ -2197,5 +2203,14 @@
"undefinedLabel": "Sense definir",
"invalid": "Invàlid",
"enabled": "Activat"
+ },
+ "searchProvider": {
+ "models": {
+ "idForMatchingErrorTitle": "Falta una propietat",
+ "idForMatchingErrorMessage": "Els objectes del model han de tenir una propietat `id`, `localId` o `name`.",
+ "unsupportedTypeMessage": "No s'ha pogut crear el tipus {{type}} de model desconegut.",
+ "unsupportedTypeTitle": "Tipus desconegut"
+ },
+ "noSearchProviders": "No hi ha proveïdors de cerca configurats"
}
}
diff --git a/wwwroot/languages/pt_BR/translation.json b/wwwroot/languages/pt_BR/translation.json
index c6f0327192c..42d4dbcbb78 100644
--- a/wwwroot/languages/pt_BR/translation.json
+++ b/wwwroot/languages/pt_BR/translation.json
@@ -57,7 +57,7 @@
"errorGettingLocation": "Erro ao obter localização",
"myLocation": "Minha Localização",
"location": "Localização",
- "originError": "Seu navegador só pode fornecer sua localização ao usar https. Você pode usar o {{secureUrl}} em seu lugar.",
+ "originError": "Seu navegador só pode fornecer sua localização ao usar HTTPS. Você pode usar o {{secureUrl}} em seu lugar.",
"centreMap": "Centralize o mapa na sua localização atual",
"browserCannotProvide": "Seu navegador não pode fornecer sua localização."
},
@@ -497,7 +497,7 @@
"resultsLabel": "Resultados da pesquisa",
"done": "Feito",
"data": "Dados",
- "searchInDataCatalog": "Pesquise <1> '{{locationSearchText}}' 1> no Catálogo de Dados",
+ "searchInDataCatalog": "Pesquise '{{locationSearchText}}' no Catálogo de Dados",
"search": "Pesquise '{{searchText}}' no Catálogo de Dados",
"viewLess": "Ver menos resultados de {{name}}"
},
@@ -731,7 +731,8 @@
"unverifiedExternalLink": {
"title": "Alerta: link não verificado",
"denyText": "Cancelar",
- "confirmText": "Abrir em uma nova aba"
+ "confirmText": "Abrir em uma nova aba",
+ "message": "Você está prestes a abrir: \n\n`{{url}}` \n\nEsse endereço não foi verificado e pode ser inseguro."
}
},
"analytics": {
@@ -818,7 +819,8 @@
"failedToObtain": "Falha ao obter o bloco de imagem X: {{x}} Y: {{y}} Nível: {{level}}.",
"notWebMercatorTilingScheme": "Este conjunto de dados não pode ser exibido no mapa 2D porque não suporta a projeção Web Mercator (EPSG: 3857).",
"unusalTilingScheme": "Este conjunto de dados não pode ser exibido no mapa 2D porque ele usa um esquema de tiling incomum que não é compatível.",
- "terrainServerErrorTitle": "Servidor Terrain não está respondendo"
+ "terrainServerErrorTitle": "Servidor Terrain não está respondendo",
+ "stoppedRenderingTitle": "Falha ao mostrar o mapa 3D"
},
"computeRingWindingOrder": {
"devError": "Pontos esperados do tipo {x: número, y: número}"
diff --git a/wwwroot/models/Box.glb b/wwwroot/models/Box.glb
deleted file mode 100644
index 95ec886b6b9..00000000000
Binary files a/wwwroot/models/Box.glb and /dev/null differ
diff --git a/wwwroot/test/GeoJSON/multipoint.geojson b/wwwroot/test/GeoJSON/multipoint.geojson
new file mode 100644
index 00000000000..42986797138
--- /dev/null
+++ b/wwwroot/test/GeoJSON/multipoint.geojson
@@ -0,0 +1,18 @@
+{
+ "type": "Feature",
+ "bbox": [-10.0, -10.0, 10.0, 10.0],
+ "properties": {
+ "foo": "hi",
+ "bar": "bye"
+ },
+ "geometry": {
+ "type": "MultiPoint",
+ "coordinates": [
+ [100.0, 0.0],
+ [101.0, 0.0],
+ [101.0, 1.0],
+ [100.0, 1.0],
+ [100.0, 0.0]
+ ]
+ }
+}
diff --git a/wwwroot/test/JSON-api/position_api_response.json b/wwwroot/test/JSON-api/position_api_response.json
index bed37b4abf7..f26a5978ae7 100644
--- a/wwwroot/test/JSON-api/position_api_response.json
+++ b/wwwroot/test/JSON-api/position_api_response.json
@@ -2,16 +2,19 @@
{
"id": 1,
"latitude": -37.25,
- "longitude": 145.25
+ "longitude": 145.25,
+ "some": { "embedded": ["first_element 1", "second_element 1"] }
},
{
"id": 2,
"latitude": -37,
- "longitude": 145.5
+ "longitude": 145.5,
+ "some": { "embedded": ["first_element 2", "second_element 2"] }
},
{
"id": 3,
"latitude": -37.1,
- "longitude": 145.7
+ "longitude": 145.7,
+ "some": { "embedded": ["first_element 3", "second_element 3"] }
}
]