Support for both maplibre v3 and v4 addProtocol. (#339)

Adds support for MapLibre v4.x, which changed the addProtocol interface. This remains backwards-compatible with MapLibre v3.x and earlier.
This commit is contained in:
Brandon Liu
2024-01-31 12:38:34 +08:00
committed by GitHub
parent 6638d040a5
commit 84fb7a8671

View File

@@ -85,6 +85,19 @@ export const leafletRasterLayer = (source: PMTiles, options: unknown) => {
return new cls(options); return new cls(options);
}; };
type GetResourceResponse<T> = ExpiryData & {
data: T;
};
type AddProtocolAction = (
requestParameters: RequestParameters,
abortController: AbortController
) => Promise<GetResourceResponse<unknown>>;
type ExpiryData = {
cacheControl?: string | null;
expires?: string | null; // MapLibre can be a Date object
};
// copied from MapLibre /util/ajax.ts // copied from MapLibre /util/ajax.ts
type RequestParameters = { type RequestParameters = {
url: string; url: string;
@@ -96,15 +109,50 @@ type RequestParameters = {
collectResourceTiming?: boolean; collectResourceTiming?: boolean;
}; };
type ResponseCallback = ( // for legacy maplibre-3 interop
error?: Error | null, type ResponseCallbackV3 = (
data?: unknown | null, error?: Error | undefined,
cacheControl?: string | null, data?: unknown | undefined,
expires?: string | null cacheControl?: string | undefined,
expires?: string | undefined
) => void; ) => void;
type Cancelable = { type V3OrV4Protocol = <
cancel: () => void; T extends AbortController | ResponseCallbackV3,
R = T extends AbortController
? Promise<GetResourceResponse<unknown>>
: { cancel: () => void },
>(
requestParameters: RequestParameters,
arg2: T
) => R;
const v3compat =
(v4: AddProtocolAction): V3OrV4Protocol =>
(requestParameters, arg2) => {
if (arg2 instanceof AbortController) {
// biome-ignore lint: overloading return type not handled by compiler
return v4(requestParameters, arg2) as any;
}
const abortController = new AbortController();
v4(requestParameters, abortController)
.then(
(result) => {
return arg2(
undefined,
result.data,
result.cacheControl || "",
result.expires || ""
);
},
(err) => {
return arg2(err);
}
)
.catch((e) => {
return arg2(e);
});
return { cancel: () => abortController.abort() };
}; };
export class Protocol { export class Protocol {
@@ -122,10 +170,10 @@ export class Protocol {
return this.tiles.get(url); return this.tiles.get(url);
} }
tile = ( tilev4 = async (
params: RequestParameters, params: RequestParameters,
callback: ResponseCallback abortController: AbortController
): Cancelable => { ) => {
if (params.type === "json") { if (params.type === "json") {
const pmtilesUrl = params.url.substr(10); const pmtilesUrl = params.url.substr(10);
let instance = this.tiles.get(pmtilesUrl); let instance = this.tiles.get(pmtilesUrl);
@@ -134,23 +182,15 @@ export class Protocol {
this.tiles.set(pmtilesUrl, instance); this.tiles.set(pmtilesUrl, instance);
} }
instance const h = await instance.getHeader();
.getHeader()
.then((h) => { return {
const tilejson = { data: {
tiles: [`${params.url}/{z}/{x}/{y}`], tiles: [`${params.url}/{z}/{x}/{y}`],
minzoom: h.minZoom, minzoom: h.minZoom,
maxzoom: h.maxZoom, maxzoom: h.maxZoom,
bounds: [h.minLon, h.minLat, h.maxLon, h.maxLat], bounds: [h.minLon, h.minLat, h.maxLon, h.maxLat],
}; },
callback(null, tilejson, null, null);
})
.catch((e) => {
callback(e, null, null, null);
});
return {
cancel: () => {},
}; };
} }
const re = new RegExp(/pmtiles:\/\/(.+)\/(\d+)\/(\d+)\/(\d+)/); const re = new RegExp(/pmtiles:\/\/(.+)\/(\d+)\/(\d+)\/(\d+)/);
@@ -169,39 +209,20 @@ export class Protocol {
const x = result[3]; const x = result[3];
const y = result[4]; const y = result[4];
const controller = new AbortController(); const header = await instance.getHeader();
const signal = controller.signal; const resp = await instance?.getZxy(+z, +x, +y, abortController.signal);
const cancel = () => { if (resp) {
controller.abort(); return {
data: new Uint8Array(resp.data),
cacheControl: resp.cacheControl,
expires: resp.expires,
};
}
if (header.tileType === TileType.Mvt) {
return { data: new Uint8Array() };
}
return { data: null };
}; };
instance.getHeader().then((header) => { tile = v3compat(this.tilev4);
instance
?.getZxy(+z, +x, +y, signal)
.then((resp) => {
if (resp) {
callback(
null,
new Uint8Array(resp.data),
resp.cacheControl,
resp.expires
);
} else {
if (header.tileType === TileType.Mvt) {
callback(null, new Uint8Array(), null, null);
} else {
callback(null, null, null, null);
}
}
})
.catch((e) => {
if ((e as Error).name !== "AbortError") {
callback(e, null, null, null);
}
});
});
return {
cancel: cancel,
};
};
} }