112 lines
3.7 KiB
TypeScript
112 lines
3.7 KiB
TypeScript
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
|
|
};
|
|
}
|