In my Angular 16 app, I'd like to display a WMS layer on a Google Map with Deck.gl - through a combination of TileLayer
s and BitmapLayer
s.
Here's my TileLayer
config:
import { TileLayer } from "@deck.gl/geo-layers/typed";
import { GeoBoundingBox } from "@deck.gl/geo-layers/typed/tileset-2d";
import { BitmapLayer } from "@deck.gl/layers/typed";
import { parse } from "@loaders.gl/core";
const wmsLayer = new TileLayer({
id: "xyz",
tileSize: 256,
opacity: 0.8,
renderSubLayers: (props: any) => {
const { west, south, east, north } = props.tile.bbox;
return new BitmapLayer(props, {
image: props.data,
transparentColor: [255, 255, 255, 0],
bounds: [west, south, east, north],
});
},
getTileData: async ({ bbox, signal }) => {
const { west, south, north, east } = bbox as GeoBoundingBox;
const params = [
"?Request=GetMap",
"&Service=WMS",
"&crs=EPSG:4326",
"&width=256",
"&height=256",
"&dpi=300",
"&format=image/png",
"&transparent=TRUE",
`&BBOX=${[west, south, east, north].join(",")}`,
];
const targetUrl = process.env.WMS_BASE + params.join("");
const data = await fetch(targetUrl, {
signal,
});
if (signal?.aborted) {
return null;
}
if (data.type === "cors") {
// Every data response seems to be of type `cors` 🤔🤔
return null;
}
return parse(data);
},
});
The issue is, getTileData
isn't handling CORS properly because it seems to only pass resolved requests whose response is of type: "cors"
:
Note that the initial preflight requests (OPTIONS
) plus the subsequent GET
s all resolve with 200s but only preflight fetch
is passed downstream:
How can I ignore preflight requests and let getTileData
only process the actual GET requests?
FYI I'm on v8.9.30 and here's my package.json :
"@deck.gl/aggregation-layers": "^8.9.30",
"@deck.gl/carto": "^8.9.30",
"@deck.gl/core": "^8.9.30",
"@deck.gl/experimental-layers": "^6.4.10",
"@deck.gl/extensions": "^8.9.30",
"@deck.gl/geo-layers": "^8.9.30",
"@deck.gl/google-maps": "^8.9.30",
"@deck.gl/layers": "^8.9.30",
"@deck.gl/mesh-layers": "^8.9.30",
"@loaders.gl/core": "^3.4.14",
"@luma.gl/core": "^8.5.21",
PS - I don't have a repro because the services are behind VPNs and auth walls.
TL;DR there's nothing wrong with getTileData
or fetch
. It's a matter of processing Blobs and/or array buffers.
Beginner's mistake: the fetch
response must be await
ed. Obtaining an image Blob
goes like this:
const resp = await fetch(...);
const blob = await resp.blob();
There's nothing wrong with a response of type: "cors"
– such responses indicate that they were received from a valid cross-origin request.
Blob's cannot easily be parsed by parse()
or load()
of @loaders.gl/core
. However, getTileData
may return a Blob
and pass it to renderSubLayers
.
renderSubLayers
's BitmapLayer
cannot load Blob
s directly. But you can pass Object URLs, namely: URL.createObjectURL(props.data)
.
Here's the working code:
import { GeoBoundingBox, TileLayer } from "@deck.gl/geo-layers/typed";
import { BitmapLayer } from "@deck.gl/layers/typed";
const wmsLayer = new TileLayer({
id: "xyz",
tileSize: 256,
opacity: 0.8,
renderSubLayers: (props: any) => {
const { west, south, east, north } = props.tile.bbox;
return new BitmapLayer(props, {
// ⬇️⬇️⬇️
image: URL.createObjectURL(props.data as Blob),
transparentColor: [255, 255, 255, 0],
bounds: [west, south, east, north],
});
},
getTileData: async ({ bbox, signal }) => {
const { west, south, north, east } = bbox as GeoBoundingBox;
const params = [
"...",
`&BBOX=${[west, south, east, north].join(",")}`,
];
const targetUrl = process.env.WMS_BASE + params.join("");
const resp = await fetch(targetUrl, { signal });
// ⬇️⬇️⬇️
const blob = resp?.blob;
if (signal?.aborted || !blob) {
return null;
}
// ⬇️⬇️⬇️
return blob;
},
});