fix: hide unavailable GeoServer layers

This commit is contained in:
2026-09-11 16:40:24 +08:00
parent f9c71cc076
commit cf699d6207
15 changed files with 683 additions and 60 deletions
@@ -0,0 +1,111 @@
import { GEOSERVER_WORKSPACE, MAP_URL } from "./geoserver-config";
import {
GEOSERVER_WATER_NETWORK_SOURCE_IDS,
SOURCE_LAYERS,
SUPPLY_LAYER_CATALOG,
type GeoServerWaterNetworkSourceId
} from "./sources";
type GeoServerSupplyLayerCatalogItem = Extract<
(typeof SUPPLY_LAYER_CATALOG)[number],
{ sourceLayer: string }
>;
export type DataDependentGeoServerSourceId = Extract<
GeoServerSupplyLayerCatalogItem,
{ availability: "probe" }
>["id"];
export const DATA_DEPENDENT_GEOSERVER_SOURCE_IDS: DataDependentGeoServerSourceId[] = SUPPLY_LAYER_CATALOG.filter(
(layer): layer is Extract<GeoServerSupplyLayerCatalogItem, { availability: "probe" }> =>
layer.sourceLayer !== undefined && layer.availability === "probe"
).map((layer) => layer.id);
export type GeoServerLayerAvailability = {
availableSourceIds: GeoServerWaterNetworkSourceId[];
emptySourceIds: DataDependentGeoServerSourceId[];
failedSourceIds: DataDependentGeoServerSourceId[];
};
export const REQUIRED_GEOSERVER_SOURCE_IDS: GeoServerWaterNetworkSourceId[] = SUPPLY_LAYER_CATALOG.filter(
(layer): layer is Extract<GeoServerSupplyLayerCatalogItem, { availability: "required" }> =>
layer.sourceLayer !== undefined && layer.availability === "required"
).map((layer) => layer.id);
export function createGeoServerLayerHitsUrl(sourceId: DataDependentGeoServerSourceId) {
const params = new URLSearchParams({
service: "WFS",
version: "2.0.0",
request: "GetFeature",
typeNames: `${GEOSERVER_WORKSPACE}:${SOURCE_LAYERS[sourceId]}`,
resultType: "hits"
});
return new URL(`${MAP_URL}/${GEOSERVER_WORKSPACE}/ows?${params.toString()}`);
}
export async function fetchGeoServerLayerFeatureCount(
sourceId: DataDependentGeoServerSourceId,
signal?: AbortSignal
) {
const response = await fetch(createGeoServerLayerHitsUrl(sourceId), {
headers: { Accept: "application/xml, text/xml" },
signal
});
const payload = await response.text();
if (!response.ok) {
if (/Feature type\s+\S+\s+unknown/i.test(payload)) return 0;
throw new Error(`GeoServer ${sourceId} 图层探测失败:HTTP ${response.status}`);
}
const match = payload.match(/\bnumberMatched\s*=\s*["'](\d+|unknown)["']/i);
if (!match) {
throw new Error(`GeoServer ${sourceId} 图层探测响应缺少 numberMatched`);
}
if (match[1].toLowerCase() === "unknown") return 1;
const count = Number(match[1]);
if (!Number.isSafeInteger(count) || count < 0) {
throw new Error(`GeoServer ${sourceId} 图层探测返回无效数量`);
}
return count;
}
export async function resolveGeoServerLayerAvailability(
signal?: AbortSignal
): Promise<GeoServerLayerAvailability> {
const results = await Promise.allSettled(
DATA_DEPENDENT_GEOSERVER_SOURCE_IDS.map(async (sourceId) => ({
sourceId,
count: await fetchGeoServerLayerFeatureCount(sourceId, signal)
}))
);
const availableOptionalSourceIds: DataDependentGeoServerSourceId[] = [];
const emptySourceIds: DataDependentGeoServerSourceId[] = [];
const failedSourceIds: DataDependentGeoServerSourceId[] = [];
results.forEach((result, index) => {
const sourceId = DATA_DEPENDENT_GEOSERVER_SOURCE_IDS[index];
if (result.status === "rejected") {
failedSourceIds.push(sourceId);
} else if (result.value.count > 0) {
availableOptionalSourceIds.push(sourceId);
} else {
emptySourceIds.push(sourceId);
}
});
const availableSourceIdSet = new Set<GeoServerWaterNetworkSourceId>([
...REQUIRED_GEOSERVER_SOURCE_IDS,
...availableOptionalSourceIds
]);
return {
availableSourceIds: GEOSERVER_WATER_NETWORK_SOURCE_IDS.filter((sourceId) =>
availableSourceIdSet.has(sourceId)
),
emptySourceIds,
failedSourceIds
};
}