diff --git a/apps/examples/src/App.vue b/apps/examples/src/App.vue
index 1603fcd..bf7b8d5 100644
--- a/apps/examples/src/App.vue
+++ b/apps/examples/src/App.vue
@@ -15,6 +15,8 @@ import ExampleMaplibreRaw from '@/examples/Example-Maplibre.vue?raw'
import ExampleMaplibre from '@/examples/Example-Maplibre.vue'
import ExampleGeoTIFF from '@/examples/Example-GeoTIFF.vue'
import ExampleGeoTIFFRaw from '@/examples/Example-GeoTIFF.vue?raw'
+import ExampleMaplibreCOG from '@/examples/Example-MaplibreCOG.vue'
+import ExampleMaplibreCOGRaw from '@/examples/Example-MaplibreCOG.vue?raw'
import { onMounted, ref } from 'vue'
import hljs from 'highlight.js'
import '@geospatial-sdk/elements'
@@ -105,6 +107,13 @@ onMounted(() => {
>
+
+
+
diff --git a/apps/examples/src/examples/Example-MaplibreCOG.vue b/apps/examples/src/examples/Example-MaplibreCOG.vue
new file mode 100644
index 0000000..25a993a
--- /dev/null
+++ b/apps/examples/src/examples/Example-MaplibreCOG.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 6113154..7f09892 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2859,6 +2859,35 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@geomatico/maplibre-cog-protocol": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@geomatico/maplibre-cog-protocol/-/maplibre-cog-protocol-0.8.0.tgz",
+ "integrity": "sha512-q5Pg7pOp/fRB/UrAUEZ4AEF/B68XwvxMg0YRk0z+UwwHfOKQThkY7u91Kusx2AwIme6QOURFK6Fvw39ooj68RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@mapbox/sphericalmercator": "^1.2.0",
+ "d3-scale": "^4.0.2",
+ "geotiff": "^2.1.3",
+ "quick-lru": "^7.1.0"
+ },
+ "peerDependencies": {
+ "maplibre-gl": "^4.5.0 || ^5.0.0"
+ }
+ },
+ "node_modules/@geomatico/maplibre-cog-protocol/node_modules/quick-lru": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.3.0.tgz",
+ "integrity": "sha512-k9lSsjl36EJdK7I06v7APZCbyGT2vMTsYSRX1Q2nbYmnkBqgUhRkAuzH08Ciotteu/PLJmIF2+tti7o3C/ts2g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@geospatial-sdk/core": {
"resolved": "packages/core",
"link": true
@@ -3265,6 +3294,18 @@
"integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==",
"dev": true
},
+ "node_modules/@mapbox/sphericalmercator": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.2.0.tgz",
+ "integrity": "sha512-ZTOuuwGuMOJN+HEmG/68bSEw15HHaMWmQ5gdTsWdWsjDe56K1kGvLOK6bOSC8gWgIvEO0w6un/2Gvv1q5hJSkQ==",
+ "dev": true,
+ "bin": {
+ "bbox": "bin/bbox.js",
+ "to4326": "bin/to4326.js",
+ "to900913": "bin/to900913.js",
+ "xyz": "bin/xyz.js"
+ }
+ },
"node_modules/@mapbox/tiny-sdf": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz",
@@ -7016,6 +7057,95 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/dargs": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz",
@@ -9680,6 +9810,16 @@
"node": ">= 0.4"
}
},
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
@@ -17848,10 +17988,12 @@
"@geospatial-sdk/core": "^0.0.5-alpha.2"
},
"devDependencies": {
+ "@geomatico/maplibre-cog-protocol": "^0.8.0",
"maplibre-gl": "^5.19.0"
},
"peerDependencies": {
- "maplibre-gl": "^5.7.3"
+ "@geomatico/maplibre-cog-protocol": "^0.8.0",
+ "maplibre-gl": "^5.19.0"
}
},
"packages/openlayers": {
diff --git a/packages/maplibre/lib/map/create-map.test.ts b/packages/maplibre/lib/map/create-map.test.ts
index d7fc651..6143997 100644
--- a/packages/maplibre/lib/map/create-map.test.ts
+++ b/packages/maplibre/lib/map/create-map.test.ts
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import {
MAP_CTX_LAYER_GEOJSON_FIXTURE,
MAP_CTX_LAYER_GEOJSON_REMOTE_FIXTURE,
+ MAP_CTX_LAYER_GEOTIFF_FIXTURE,
MAP_CTX_LAYER_OGCAPI_FIXTURE,
MAP_CTX_LAYER_WFS_FIXTURE,
MAP_CTX_LAYER_WMS_FIXTURE,
@@ -370,6 +371,38 @@ describe("MapContextService", () => {
});
});
+ describe("GeoTIFF", () => {
+ beforeEach(async () => {
+ layerModel = MAP_CTX_LAYER_GEOTIFF_FIXTURE;
+ style = (await createLayer(layerModel)) as PartialStyleSpecification;
+ });
+ it("create a raster layer and source", () => {
+ const sourceId = "123456";
+ const sourcesIds = Object.keys(style.sources);
+ expect(sourcesIds.length).toBe(1);
+ expect(sourcesIds[0]).toBe(sourceId);
+
+ const source = style.sources[sourceId] as RasterSourceSpecification;
+ expect(source.type).toBe("raster");
+ expect(source.url).toBe(
+ "cog://https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/36/Q/WD/2020/7/S2A_36QWD_20200701_0_L2A/TCI.tif",
+ );
+ expect(source.tileSize).toBe(256);
+ });
+ it("create a layer with correct properties", () => {
+ expect(style.layers.length).toBe(1);
+ const layer = style.layers[0] as RasterLayerSpecification;
+ const metadata = layer.metadata as LayerMetadataSpecification;
+
+ expect(layer.id).toBe("123456");
+ expect(layer.type).toBe("raster");
+ expect(layer.source).toBe("123456");
+ expect(layer.paint?.["raster-opacity"]).toBe(1);
+ expect(layer.layout?.visibility).toBe("visible");
+ expect(metadata.layerHash).toBeTypeOf("string");
+ });
+ });
+
describe("WMTS", () => {
beforeEach(async () => {
layerModel = MAP_CTX_LAYER_WMTS_FIXTURE;
diff --git a/packages/maplibre/lib/map/create-map.ts b/packages/maplibre/lib/map/create-map.ts
index ca032bc..763d244 100644
--- a/packages/maplibre/lib/map/create-map.ts
+++ b/packages/maplibre/lib/map/create-map.ts
@@ -5,7 +5,8 @@ import {
ViewByZoomAndCenter,
} from "@geospatial-sdk/core";
-import { LayerSpecification, Map, MapOptions } from "maplibre-gl";
+import { addProtocol, LayerSpecification, Map, MapOptions } from "maplibre-gl";
+import { cogProtocol } from "@geomatico/maplibre-cog-protocol";
import { FeatureCollection, Geometry } from "geojson";
import {
OgcApiEndpoint,
@@ -22,6 +23,8 @@ import {
PartialStyleSpecification,
} from "../maplibre.models.js";
+let cogProtocolRegistered = false;
+
const featureCollection: FeatureCollection = {
type: "FeatureCollection",
features: [],
@@ -153,6 +156,36 @@ export async function createLayer(
],
};
}
+ case "geotiff": {
+ if (!cogProtocolRegistered) {
+ addProtocol("cog", cogProtocol);
+ cogProtocolRegistered = true;
+ }
+ const sourceId = layerId;
+ return {
+ sources: {
+ [sourceId]: {
+ type: "raster",
+ url: `cog://${layerModel.url}`,
+ tileSize: 256,
+ },
+ },
+ layers: [
+ {
+ id: layerId,
+ type: "raster",
+ source: sourceId,
+ paint: {
+ "raster-opacity": layerModel.opacity ?? 1,
+ },
+ layout: {
+ visibility: layerModel.visibility === false ? "none" : "visible",
+ },
+ metadata,
+ },
+ ],
+ };
+ }
case "wmts": {
console.warn(`WMTS layers are not yet supported in Maplibre`, layerModel);
return null;
diff --git a/packages/maplibre/package.json b/packages/maplibre/package.json
index 1f8330a..62e591c 100644
--- a/packages/maplibre/package.json
+++ b/packages/maplibre/package.json
@@ -27,10 +27,12 @@
"build": "tsc"
},
"devDependencies": {
- "maplibre-gl": "^5.19.0"
+ "maplibre-gl": "^5.19.0",
+ "@geomatico/maplibre-cog-protocol": "^0.8.0"
},
"peerDependencies": {
- "maplibre-gl": "^5.19.0"
+ "maplibre-gl": "^5.19.0",
+ "@geomatico/maplibre-cog-protocol": "^0.8.0"
},
"dependencies": {
"@geospatial-sdk/core": "^0.0.5-alpha.2"