terriajs
Version:
Geospatial data visualization platform.
387 lines (346 loc) • 13.1 kB
text/typescript
import { action } from "mobx";
import { http, passthrough } from "msw";
import {
Filter,
GeomType,
LineSymbolizer,
PolygonSymbolizer
} from "protomaps-leaflet";
import ProtomapsImageryProvider from "../../../../lib/Map/ImageryProvider/ProtomapsImageryProvider";
import {
filterFn,
getFont,
numberFn,
numberOrFn
} from "../../../../lib/Map/Vector/Protomaps/mapboxStyleJsonToProtomaps";
import { ImageryParts } from "../../../../lib/ModelMixins/MappableMixin";
import MapboxVectorTileCatalogItem from "../../../../lib/Models/Catalog/CatalogItems/MapboxVectorTileCatalogItem";
import CommonStrata from "../../../../lib/Models/Definition/CommonStrata";
import updateModelFromJson from "../../../../lib/Models/Definition/updateModelFromJson";
import Terria from "../../../../lib/Models/Terria";
import { worker } from "../../../mocks/browser";
describe("MapboxVectorTileCatalogItem", function () {
let mvt: MapboxVectorTileCatalogItem;
beforeEach(function () {
mvt = new MapboxVectorTileCatalogItem("test", new Terria());
});
it("has a type", function () {
expect(MapboxVectorTileCatalogItem.type).toBe("mvt");
expect(mvt.type).toBe("mvt");
});
describe("imageryProvider", function () {
let imageryProvider: ProtomapsImageryProvider;
beforeEach(function () {
mvt.setTrait(CommonStrata.user, "url", "http://test");
mvt.setTrait(CommonStrata.user, "layer", "test-layer");
});
it("is an instance of ProtomapsImageryProvider", async function () {
await mvt.loadMapItems();
if (!ImageryParts.is(mvt.mapItems[0]))
throw new Error("Expected MapItem to be an ImageryParts");
imageryProvider = mvt.mapItems[0]
.imageryProvider as ProtomapsImageryProvider;
expect(imageryProvider instanceof ProtomapsImageryProvider).toBeTruthy();
});
it("sets min/max/native zoom properties", async function () {
mvt.setTrait(CommonStrata.user, "minimumZoom", 1);
mvt.setTrait(CommonStrata.user, "maximumZoom", 20);
mvt.setTrait(CommonStrata.user, "maximumNativeZoom", 10);
await mvt.loadMapItems();
if (!ImageryParts.is(mvt.mapItems[0]))
throw new Error("Expected MapItem to be an ImageryParts");
imageryProvider = mvt.mapItems[0]
.imageryProvider as ProtomapsImageryProvider;
// Note: ProtomapsImageryProvider uses softMinimumLevel instead of minimumLevel
expect(imageryProvider.minimumLevel).toBe(0);
expect(imageryProvider.softMinimumLevel).toBe(1);
expect(imageryProvider.maximumLevel).toBe(20);
expect(imageryProvider.maximumNativeZoom).toBe(10);
});
it("sets idProperty", async function () {
mvt.setTrait(CommonStrata.user, "idProperty", "id");
await mvt.loadMapItems();
if (!ImageryParts.is(mvt.mapItems[0]))
throw new Error("Expected MapItem to be an ImageryParts");
imageryProvider = mvt.mapItems[0]
.imageryProvider as ProtomapsImageryProvider;
expect(imageryProvider.idProperty).toBe("id");
});
});
describe("legends", function () {
it(
"constructs a default legend from the definition",
action(async function () {
mvt.setTrait(CommonStrata.user, "fillColor", "red");
mvt.setTrait(CommonStrata.user, "lineColor", "yellow");
mvt.setTrait(CommonStrata.user, "name", "Test");
await mvt.loadMapItems();
const legendItem = mvt.legends[0].items[0];
expect(legendItem.color).toBe("red");
expect(legendItem.outlineColor).toBe("yellow");
expect(legendItem.outlineWidth).toBe(1);
expect(legendItem.title).toBe("Test");
})
);
});
describe("paint rules", function () {
it(
"creates paint rules from simple styles",
action(function () {
mvt.setTrait(CommonStrata.user, "fillColor", "red");
mvt.setTrait(CommonStrata.user, "lineColor", "yellow");
mvt.setTrait(CommonStrata.user, "layer", "Test");
expect(mvt.paintRules.length).toBe(2);
expect(mvt.paintRules[0].dataLayer).toBe("Test");
expect(mvt.paintRules[1].dataLayer).toBe("Test");
expect(
mvt.paintRules[0].symbolizer instanceof PolygonSymbolizer
).toBeTruthy();
expect(
mvt.paintRules[1].symbolizer instanceof LineSymbolizer
).toBeTruthy();
})
);
});
describe("highlight features", function () {
let imageryProvider: ProtomapsImageryProvider;
beforeEach(async () => {
updateModelFromJson(mvt, CommonStrata.definition, {
name: "Mapbox vector tiles Test",
type: "mvt",
url: "/test/mvt/nsw-lga-mvt/{z}/{x}/{y}.pbf",
fillColor: "#ff0000",
lineColor: "#ffff00",
minimumZoom: 0,
maximumNativeZoom: 11,
maximumZoom: 28,
idProperty: "LGA_CODE13",
layer: "FID_LGA_2013_AUST"
});
await mvt.loadMapItems();
if (!ImageryParts.is(mvt.mapItems[0]))
throw new Error("Expected MapItem to be an ImageryParts");
imageryProvider = mvt.mapItems[0]
.imageryProvider as ProtomapsImageryProvider;
});
it("correctly picks features", async function () {
worker.use(
http.get("*.pbf", () => {
return passthrough();
})
);
// Get polygon
const polygon = await imageryProvider.pickFeatures(
1881,
1229,
11,
2.630470869072516,
-0.5932730847619763
);
expect(polygon.length).toBe(1);
expect(polygon[0]?.properties?.LGA_CODE13).toBe("11450");
// Select nothing
const nothingToSelect = await imageryProvider.pickFeatures(
1884,
1230,
11,
2.6387404164527286,
-0.5945282912087255
);
expect(nothingToSelect.length).toBe(0);
});
});
/** Mapbox style json tests are adapted from from https://github.com/protomaps/protomaps-leaflet/blob/a08304417ef36fef03679976cd3e5a971fec19a2/test/json_style.test.ts
* License: BSD-3-Clause
* Copyright 2021-2024 Protomaps LLC
* Full license https://github.com/protomaps/protomaps-leaflet/blob/main/LICENSE
*/
describe("mapbox style json", function () {
const emptyFeature = {
props: {},
geomType: GeomType.Point,
numVertices: 0,
geom: [],
bbox: { minX: 0, minY: 0, maxX: 0, maxY: 0 }
};
let f: Filter | undefined;
it("==", async () => {
f = filterFn(["==", "building", "yes"]);
expect(f(0, { ...emptyFeature, props: { building: "yes" } })).toBe(true);
});
it("!=", async () => {
f = filterFn(["!=", "building", "yes"]);
expect(!f(0, { ...emptyFeature, props: { building: "yes" } })).toBe(true);
expect(f(0, { ...emptyFeature, props: { building: "no" } })).toBe(true);
});
it("<", async () => {
f = filterFn(["<", "level", 3]);
expect(f(0, { ...emptyFeature, props: { level: 2 } })).toBe(true);
expect(!f(0, { ...emptyFeature, props: { level: 3 } })).toBe(true);
});
it("<=", async () => {
f = filterFn(["<=", "level", 3]);
expect(f(0, { ...emptyFeature, props: { level: 2 } })).toBe(true);
expect(f(0, { ...emptyFeature, props: { level: 3 } })).toBe(true);
});
it(">", async () => {
f = filterFn([">", "level", 3]);
expect(f(0, { ...emptyFeature, props: { level: 4 } })).toBe(true);
expect(!f(0, { ...emptyFeature, props: { level: 3 } })).toBe(true);
});
it(">=", async () => {
f = filterFn([">=", "level", 3]);
expect(f(0, { ...emptyFeature, props: { level: 4 } })).toBe(true);
expect(f(0, { ...emptyFeature, props: { level: 3 } })).toBe(true);
});
it("in", async () => {
f = filterFn(["in", "type", "foo", "bar"]);
expect(f(0, { ...emptyFeature, props: { type: "foo" } })).toBe(true);
expect(f(0, { ...emptyFeature, props: { type: "bar" } })).toBe(true);
expect(!f(0, { ...emptyFeature, props: { type: "baz" } })).toBe(true);
});
it("!in", async () => {
f = filterFn(["!in", "type", "foo", "bar"]);
expect(!f(0, { ...emptyFeature, props: { type: "bar" } })).toBe(true);
expect(f(0, { ...emptyFeature, props: { type: "baz" } })).toBe(true);
});
it("has", async () => {
f = filterFn(["has", "type"]);
expect(f(0, { ...emptyFeature, props: { type: "foo" } })).toBe(true);
expect(!f(0, { ...emptyFeature, props: {} })).toBe(true);
});
it("!has", async () => {
f = filterFn(["!has", "type"]);
expect(!f(0, { ...emptyFeature, props: { type: "foo" } })).toBe(true);
expect(f(0, { ...emptyFeature, props: {} })).toBe(true);
});
it("!", async () => {
f = filterFn(["!", ["has", "type"]]);
expect(!f(0, { ...emptyFeature, props: { type: "foo" } })).toBe(true);
expect(f(0, { ...emptyFeature, props: {} })).toBe(true);
});
it("all", async () => {
f = filterFn(["all", ["==", "building", "yes"], ["==", "type", "foo"]]);
expect(!f(0, { ...emptyFeature, props: { building: "yes" } })).toBe(true);
expect(!f(0, { ...emptyFeature, props: { type: "foo" } })).toBe(true);
expect(
f(0, { ...emptyFeature, props: { building: "yes", type: "foo" } })
).toBe(true);
});
it("any", async () => {
f = filterFn(["any", ["==", "building", "yes"], ["==", "type", "foo"]]);
expect(!f(0, { ...emptyFeature, props: {} })).toBe(true);
expect(f(0, { ...emptyFeature, props: { building: "yes" } })).toBe(true);
expect(f(0, { ...emptyFeature, props: { type: "foo" } })).toBe(true);
expect(
f(0, { ...emptyFeature, props: { building: "yes", type: "foo" } })
).toBe(true);
});
it("numberFn constant", async () => {
let n = numberOrFn(5);
expect(n).toEqual(5);
n = numberOrFn(undefined);
expect(n).toEqual(0);
});
it("numberFn function", async () => {
const n = numberFn({
base: 1,
stops: [
[14, 0],
[16, 2]
]
});
expect(n.length).toEqual(1);
expect(n(15)).toEqual(0);
expect(n(16)).toEqual(1);
expect(n(17)).toEqual(2);
});
it("numberFn interpolate", async () => {
const n = numberFn([
"interpolate",
["exponential", 1],
["zoom"],
14,
0,
16,
2
]);
expect(n.length).toEqual(1);
expect(n(15)).toEqual(0);
expect(n(16)).toEqual(1);
expect(n(17)).toEqual(2);
});
it("numberFn properties", async () => {
const n = numberFn(["step", ["get", "scalerank"], 0, 1, 2, 3, 4]);
expect(n.length).toEqual(2);
expect(n(14, { ...emptyFeature, props: { scalerank: 0 } })).toEqual(0);
expect(n(14, { ...emptyFeature, props: { scalerank: 1 } })).toEqual(2);
expect(n(14, { ...emptyFeature, props: { scalerank: 3 } })).toEqual(4);
expect(n(14, { ...emptyFeature, props: { scalerank: 4 } })).toEqual(4);
});
it("font", async () => {
let n = getFont({ "text-font": ["Noto"], "text-size": 14 }, {});
expect(n(1)).toEqual("14px sans-serif");
n = getFont({ "text-font": ["Noto"], "text-size": 15 }, {});
expect(n(1)).toEqual("15px sans-serif");
n = getFont(
{ "text-font": ["Noto"], "text-size": 15 },
{ Noto: { face: "serif" } }
);
expect(n(1)).toEqual("15px serif");
n = getFont(
{ "text-font": ["Boto", "Noto"], "text-size": 15 },
{ Noto: { face: "serif" }, Boto: { face: "Comic Sans" } }
);
expect(n(1)).toEqual("15px Comic Sans, serif");
});
it("font weight and style", async () => {
let n = getFont(
{ "text-font": ["Noto"], "text-size": 15 },
{ Noto: { face: "serif", weight: 100 } }
);
expect(n(1)).toEqual("100 15px serif");
n = getFont(
{ "text-font": ["Noto"], "text-size": 15 },
{ Noto: { face: "serif", style: "italic" } }
);
expect(n(1)).toEqual("italic 15px serif");
});
it("font size fn zoom", async () => {
const n = getFont(
{
"text-font": ["Noto"],
"text-size": {
base: 1,
stops: [
[14, 1],
[16, 3]
]
}
},
{}
);
expect(n(15)).toEqual("1px sans-serif");
expect(n(16)).toEqual("2px sans-serif");
expect(n(17)).toEqual("3px sans-serif");
});
it("font size fn zoom props", async () => {
const n = getFont(
{
"text-font": ["Noto"],
"text-size": ["step", ["get", "scalerank"], 0, 1, 12, 2, 10]
},
{}
);
expect(n(14, { ...emptyFeature, props: { scalerank: 0 } })).toEqual(
"0px sans-serif"
);
expect(n(14, { ...emptyFeature, props: { scalerank: 1 } })).toEqual(
"12px sans-serif"
);
expect(n(14, { ...emptyFeature, props: { scalerank: 2 } })).toEqual(
"10px sans-serif"
);
});
});
});