refactor(frontend): align TJWater Next integrations
This commit is contained in:
@@ -45,6 +45,7 @@ interface TimelineProps {
|
||||
timeRange?: { start: Date; end: Date };
|
||||
disableDateSelection?: boolean;
|
||||
schemeName?: string;
|
||||
schemeRunId?: string;
|
||||
schemeType?: string;
|
||||
}
|
||||
|
||||
@@ -80,6 +81,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
timeRange,
|
||||
disableDateSelection = false,
|
||||
schemeName = "",
|
||||
schemeRunId = "",
|
||||
schemeType = "burst_analysis",
|
||||
}) => {
|
||||
const data = useData();
|
||||
@@ -207,6 +209,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
sourceType,
|
||||
target,
|
||||
schemeName,
|
||||
schemeRunId,
|
||||
schemeType,
|
||||
signal,
|
||||
}: {
|
||||
@@ -216,6 +219,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
sourceType: "scheme" | "realtime";
|
||||
target: "primary" | "compare";
|
||||
schemeName?: string;
|
||||
schemeRunId?: string;
|
||||
schemeType?: string;
|
||||
signal?: AbortSignal;
|
||||
}) => {
|
||||
@@ -232,16 +236,16 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
junctionProperties,
|
||||
sourceType,
|
||||
"node",
|
||||
schemeName || "",
|
||||
schemeRunId || schemeName || "",
|
||||
schemeType || ""
|
||||
);
|
||||
if (nodeCacheRef.current.has(nodeCacheKey)) {
|
||||
nodeRecords = nodeCacheRef.current.get(nodeCacheKey)!;
|
||||
} else {
|
||||
nodePromise =
|
||||
sourceType === "scheme" && schemeName
|
||||
sourceType === "scheme" && schemeRunId
|
||||
? apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/schemes/records?scheme_type=${schemeType}&scheme_name=${schemeName}&query_time=${query_time}&type=node&property=${junctionProperties}`,
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/analysis/runs/${encodeURIComponent(schemeRunId)}/values?result_time=${encodeURIComponent(query_time)}&element_type=node&field=${encodeURIComponent(junctionProperties)}`,
|
||||
{ signal },
|
||||
)
|
||||
: apiFetch(
|
||||
@@ -261,16 +265,16 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
normalizedPipeProperties,
|
||||
sourceType,
|
||||
"link",
|
||||
schemeName || "",
|
||||
schemeRunId || schemeName || "",
|
||||
schemeType || ""
|
||||
);
|
||||
if (linkCacheRef.current.has(linkCacheKey)) {
|
||||
linkRecords = linkCacheRef.current.get(linkCacheKey)!;
|
||||
} else {
|
||||
linkPromise =
|
||||
sourceType === "scheme" && schemeName
|
||||
sourceType === "scheme" && schemeRunId
|
||||
? apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/schemes/records?scheme_type=${schemeType}&scheme_name=${schemeName}&query_time=${query_time}&type=link&property=${normalizedPipeProperties}`,
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/analysis/runs/${encodeURIComponent(schemeRunId)}/values?result_time=${encodeURIComponent(query_time)}&element_type=link&field=${encodeURIComponent(normalizedPipeProperties)}`,
|
||||
{ signal },
|
||||
)
|
||||
: apiFetch(
|
||||
@@ -288,14 +292,22 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
if (!nodeResponse.ok) {
|
||||
throw new Error(`Node fetch failed: ${nodeResponse.status}`);
|
||||
}
|
||||
nodeRecords = await nodeResponse.json();
|
||||
const payload = await nodeResponse.json();
|
||||
nodeRecords = sourceType === "scheme"
|
||||
? {
|
||||
results: Object.entries(payload ?? {}).map(([ID, value]) => ({
|
||||
ID,
|
||||
value,
|
||||
})),
|
||||
}
|
||||
: payload;
|
||||
nodeCacheRef.current.set(
|
||||
buildCacheKey(
|
||||
query_time,
|
||||
junctionProperties,
|
||||
sourceType,
|
||||
"node",
|
||||
schemeName || "",
|
||||
schemeRunId || schemeName || "",
|
||||
schemeType || ""
|
||||
),
|
||||
nodeRecords || []
|
||||
@@ -307,14 +319,22 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
if (!linkResponse.ok) {
|
||||
throw new Error(`Link fetch failed: ${linkResponse.status}`);
|
||||
}
|
||||
linkRecords = await linkResponse.json();
|
||||
const payload = await linkResponse.json();
|
||||
linkRecords = sourceType === "scheme"
|
||||
? {
|
||||
results: Object.entries(payload ?? {}).map(([ID, value]) => ({
|
||||
ID,
|
||||
value,
|
||||
})),
|
||||
}
|
||||
: payload;
|
||||
linkCacheRef.current.set(
|
||||
buildCacheKey(
|
||||
query_time,
|
||||
normalizedPipeProperties,
|
||||
sourceType,
|
||||
"link",
|
||||
schemeName || "",
|
||||
schemeRunId || schemeName || "",
|
||||
schemeType || ""
|
||||
),
|
||||
linkRecords || []
|
||||
@@ -336,6 +356,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
junctionProperties: string,
|
||||
pipeProperties: string,
|
||||
schemeName: string,
|
||||
schemeRunId: string,
|
||||
schemeType: string
|
||||
) => {
|
||||
const revision = frameRequestRevisionRef.current + 1;
|
||||
@@ -344,7 +365,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
const abortController = new AbortController();
|
||||
frameAbortControllerRef.current = abortController;
|
||||
const primarySourceType =
|
||||
disableDateSelection && schemeName ? "scheme" : "realtime";
|
||||
disableDateSelection && schemeRunId ? "scheme" : "realtime";
|
||||
const tasks = [
|
||||
fetchDataBySource({
|
||||
queryTime,
|
||||
@@ -353,12 +374,13 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
sourceType: primarySourceType,
|
||||
target: "primary",
|
||||
schemeName,
|
||||
schemeRunId,
|
||||
schemeType,
|
||||
signal: abortController.signal,
|
||||
}),
|
||||
];
|
||||
|
||||
if (isCompareMode && disableDateSelection && schemeName) {
|
||||
if (isCompareMode && disableDateSelection && schemeRunId) {
|
||||
tasks.push(
|
||||
fetchDataBySource({
|
||||
queryTime,
|
||||
@@ -600,6 +622,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
junctionText,
|
||||
pipeText,
|
||||
schemeName,
|
||||
schemeRunId,
|
||||
schemeType,
|
||||
);
|
||||
}
|
||||
@@ -612,6 +635,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
timelineCurrentTime,
|
||||
selectedDate,
|
||||
schemeName,
|
||||
schemeRunId,
|
||||
schemeType,
|
||||
]);
|
||||
|
||||
@@ -687,6 +711,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
junctionText,
|
||||
pipeText,
|
||||
schemeName,
|
||||
schemeRunId,
|
||||
schemeType,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
import { config, NETWORK_NAME } from "@/config/config";
|
||||
import { useProject } from "@/contexts/ProjectContext";
|
||||
import { apiFetch } from "@/lib/apiFetch";
|
||||
import { getAnalysisScheme } from "@/lib/analysisRuns";
|
||||
import { permissionCodes } from "@/lib/permissions";
|
||||
import { useAccessStore } from "@/store/accessStore";
|
||||
|
||||
@@ -65,8 +66,7 @@ type ActiveSchemeDetail = {
|
||||
valve_opening?: Record<string, number> | null;
|
||||
};
|
||||
|
||||
const isValveLayer = (layerId: string | undefined) =>
|
||||
layerId === "geo_valves_mat" || layerId === "geo_valves";
|
||||
const isValveLayer = (layerId: string | undefined) => layerId === "valves";
|
||||
|
||||
const Toolbar: React.FC<ToolbarProps> = ({
|
||||
hiddenButtons,
|
||||
@@ -93,11 +93,12 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
const currentTime = data?.currentTime;
|
||||
const selectedDate = data?.selectedDate;
|
||||
const schemeName = data?.schemeName;
|
||||
const schemeRunId = data?.schemeRunId;
|
||||
const networkName = project?.networkName || NETWORK_NAME;
|
||||
const isCompareMode = data?.isCompareMode ?? false;
|
||||
const toggleCompareMode = data?.toggleCompareMode;
|
||||
const canToggleCompare = Boolean(
|
||||
enableCompare && (isCompareMode || (queryType === "scheme" && schemeName)),
|
||||
enableCompare && (isCompareMode || (queryType === "scheme" && schemeRunId)),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -406,7 +407,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
if (
|
||||
queryType !== "scheme" ||
|
||||
schemeType !== "flushing_analysis" ||
|
||||
!schemeName ||
|
||||
!schemeRunId ||
|
||||
!selectedValveId
|
||||
) {
|
||||
setActiveSchemeDetail(null);
|
||||
@@ -418,15 +419,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
|
||||
const querySchemeDetail = async () => {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (schemeType) params.set("scheme_type", schemeType);
|
||||
const response = await apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/schemes/${encodeURIComponent(schemeName)}?${params.toString()}`,
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`getschemedetail failed: ${response.status}`);
|
||||
}
|
||||
const payload = await response.json();
|
||||
const payload = await getAnalysisScheme(schemeRunId);
|
||||
if (!cancelled) {
|
||||
setActiveSchemeDetail(payload?.scheme_detail ?? null);
|
||||
}
|
||||
@@ -446,7 +439,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [open, queryType, schemeName, schemeType, selectedValveId]);
|
||||
}, [open, queryType, schemeRunId, schemeType, selectedValveId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedValveId || isSimulationDataActive) {
|
||||
@@ -710,39 +703,59 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
dateObj.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0);
|
||||
// 转为 UTC ISO 字符串
|
||||
const querytime = dateObj.toISOString(); // 例如 "2025-09-16T16:30:00.000Z"
|
||||
let response: Response;
|
||||
if (queryType === "scheme") {
|
||||
const params = new URLSearchParams({
|
||||
scheme_type: schemeType ?? "",
|
||||
scheme_name: schemeName ?? "",
|
||||
id: String(id),
|
||||
type,
|
||||
query_time: querytime,
|
||||
});
|
||||
response = await apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/schemes/simulation-results?${params.toString()}`,
|
||||
);
|
||||
} else {
|
||||
const params = new URLSearchParams({
|
||||
id: String(id),
|
||||
type,
|
||||
query_time: querytime,
|
||||
});
|
||||
response = await apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/realtime/simulation-results?${params.toString()}`,
|
||||
if (!schemeRunId) {
|
||||
throw new Error("Analysis run ID is missing");
|
||||
}
|
||||
const fields = type === "node"
|
||||
? ["actual_demand", "total_head", "pressure", "quality"]
|
||||
: [
|
||||
"flow",
|
||||
"friction",
|
||||
"headloss",
|
||||
"quality",
|
||||
"reaction",
|
||||
"setting",
|
||||
"status",
|
||||
"velocity",
|
||||
];
|
||||
const values = await Promise.all(
|
||||
fields.map(async (field) => {
|
||||
const params = new URLSearchParams({
|
||||
result_time: querytime,
|
||||
element_type: type,
|
||||
field,
|
||||
});
|
||||
const response = await apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/analysis/runs/${encodeURIComponent(schemeRunId)}/values?${params.toString()}`,
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Analysis value fetch failed: ${response.status}`);
|
||||
}
|
||||
const payload = await response.json();
|
||||
return [field, payload?.[String(id)]] as const;
|
||||
}),
|
||||
);
|
||||
if (!cancelled) {
|
||||
setComputedProperties(
|
||||
Object.fromEntries(values.filter(([, value]) => value !== undefined)),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error("API request failed");
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
id: String(id),
|
||||
type,
|
||||
query_time: querytime,
|
||||
});
|
||||
const response = await apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/realtime/simulation-results?${params.toString()}`,
|
||||
);
|
||||
if (!response.ok) throw new Error("API request failed");
|
||||
const data = await response.json();
|
||||
if (cancelled) return;
|
||||
if (!data.result || data.result.length === 0) {
|
||||
setComputedProperties({});
|
||||
} else {
|
||||
setComputedProperties(data.result[0] || {});
|
||||
// console.log("查询到的计算属性:", data.result[0]);
|
||||
}
|
||||
setComputedProperties(data.result?.[0] || {});
|
||||
} catch (error) {
|
||||
console.error("Error querying computed properties:", error);
|
||||
if (!cancelled) {
|
||||
@@ -759,7 +772,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [highlightFeatures, currentTime, open, selectedDate, queryType, schemeName, schemeType, showPropertyPanel]);
|
||||
}, [highlightFeatures, currentTime, open, selectedDate, queryType, schemeName, schemeRunId, showPropertyPanel]);
|
||||
|
||||
const displayedComputedProperties = useMemo(() => {
|
||||
if (!isSimulationDataActive || !selectedValveId) {
|
||||
|
||||
@@ -22,7 +22,7 @@ const createValveFeature = () => {
|
||||
minor_loss: 0,
|
||||
};
|
||||
return {
|
||||
getId: () => "geo_valves.V1",
|
||||
getId: () => "valves.V1",
|
||||
getProperties: () => properties,
|
||||
} as unknown as Feature;
|
||||
};
|
||||
@@ -161,7 +161,7 @@ describe("getSimulationElementType", () => {
|
||||
|
||||
it("keeps point-rendered pumps on the same hydraulic-link path", () => {
|
||||
const pump = {
|
||||
getId: () => "geo_pumps.P1",
|
||||
getId: () => "pumps.P1",
|
||||
getProperties: () => ({
|
||||
id: "P1",
|
||||
geometry: { getType: () => "Point" },
|
||||
@@ -174,7 +174,7 @@ describe("getSimulationElementType", () => {
|
||||
|
||||
describe("buildFeatureProperties simulation values by hydraulic type", () => {
|
||||
it("shows link simulation results for a point-rendered pump", () => {
|
||||
const pump = createFeature("geo_pumps", "P1", {
|
||||
const pump = createFeature("pumps", "P1", {
|
||||
node1: "J1",
|
||||
node2: "J2",
|
||||
geometry: { getType: () => "Point" },
|
||||
@@ -196,8 +196,8 @@ describe("buildFeatureProperties simulation values by hydraulic type", () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
["geo_tanks", "T1", "水池"],
|
||||
["geo_reservoirs", "R1", "水库"],
|
||||
["tanks", "T1", "水池"],
|
||||
["reservoirs", "R1", "水库"],
|
||||
])("shows node simulation results for %s", (layer, id, type) => {
|
||||
const feature = createFeature(layer, id, {
|
||||
geometry: { getType: () => "Point" },
|
||||
|
||||
@@ -236,13 +236,13 @@ export const buildFeatureProperties = (
|
||||
});
|
||||
};
|
||||
|
||||
if (layer === "geo_pipes_mat" || layer === "geo_pipes") {
|
||||
if (layer === "pipes") {
|
||||
const result: ToolbarPropertyPanelData = {
|
||||
id: properties.id,
|
||||
type: "管道",
|
||||
properties: [
|
||||
{ label: "起始节点ID", value: properties.node1 },
|
||||
{ label: "终点节点ID", value: properties.node2 },
|
||||
{ label: "起始节点ID", value: properties.start_node_id },
|
||||
{ label: "终点节点ID", value: properties.end_node_id },
|
||||
{ label: "长度", value: properties.length?.toFixed?.(1), unit: "m" },
|
||||
{
|
||||
label: "管径",
|
||||
@@ -260,7 +260,7 @@ export const buildFeatureProperties = (
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layer === "geo_junctions_mat" || layer === "geo_junctions") {
|
||||
if (layer === "junctions") {
|
||||
const result: ToolbarPropertyPanelData = {
|
||||
id: properties.id,
|
||||
type: "节点",
|
||||
@@ -271,27 +271,15 @@ export const buildFeatureProperties = (
|
||||
unit: "m",
|
||||
},
|
||||
{
|
||||
type: "table",
|
||||
label: "基本需水量",
|
||||
columns: ["demand", "pattern"],
|
||||
rows: Array.from({ length: 5 }, (_, i) => i + 1)
|
||||
.map((idx) => {
|
||||
let demand = properties?.[`demand${idx}`];
|
||||
const pattern = properties?.[`pattern${idx}`];
|
||||
if (
|
||||
demand !== undefined &&
|
||||
demand !== null &&
|
||||
demand !== ""
|
||||
) {
|
||||
demand = toM3h(Number(demand), "lps");
|
||||
return [
|
||||
typeof demand === "number" ? demand.toFixed(3) : demand,
|
||||
pattern ?? "-",
|
||||
];
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean) as (string | number)[][],
|
||||
value: Number.isFinite(Number(properties.base_demand))
|
||||
? toM3h(Number(properties.base_demand), "lps").toFixed(3)
|
||||
: properties.base_demand,
|
||||
unit: "m³/h",
|
||||
},
|
||||
{
|
||||
label: "需水配置",
|
||||
value: properties.demands,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -301,7 +289,7 @@ export const buildFeatureProperties = (
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layer === "geo_tanks_mat" || layer === "geo_tanks") {
|
||||
if (layer === "tanks") {
|
||||
const result: ToolbarPropertyPanelData = {
|
||||
id: properties.id,
|
||||
type: "水池",
|
||||
@@ -313,17 +301,17 @@ export const buildFeatureProperties = (
|
||||
},
|
||||
{
|
||||
label: "初始水位",
|
||||
value: properties.init_level?.toFixed?.(1),
|
||||
value: properties.initial_level?.toFixed?.(1),
|
||||
unit: "m",
|
||||
},
|
||||
{
|
||||
label: "最低水位",
|
||||
value: properties.min_level?.toFixed?.(1),
|
||||
value: properties.minimum_level?.toFixed?.(1),
|
||||
unit: "m",
|
||||
},
|
||||
{
|
||||
label: "最高水位",
|
||||
value: properties.max_level?.toFixed?.(1),
|
||||
value: properties.maximum_level?.toFixed?.(1),
|
||||
unit: "m",
|
||||
},
|
||||
{
|
||||
@@ -333,7 +321,7 @@ export const buildFeatureProperties = (
|
||||
},
|
||||
{
|
||||
label: "最小容积",
|
||||
value: properties.min_vol?.toFixed?.(1),
|
||||
value: properties.minimum_volume?.toFixed?.(1),
|
||||
unit: "m³",
|
||||
},
|
||||
{
|
||||
@@ -346,7 +334,7 @@ export const buildFeatureProperties = (
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layer === "geo_reservoirs_mat" || layer === "geo_reservoirs") {
|
||||
if (layer === "reservoirs") {
|
||||
const result: ToolbarPropertyPanelData = {
|
||||
id: properties.id,
|
||||
type: "水库",
|
||||
@@ -356,37 +344,37 @@ export const buildFeatureProperties = (
|
||||
value: properties.head?.toFixed?.(1),
|
||||
unit: "m",
|
||||
},
|
||||
{ label: "模式", value: properties.pattern_id },
|
||||
],
|
||||
};
|
||||
appendNodeComputedProperties(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layer === "geo_pumps_mat" || layer === "geo_pumps") {
|
||||
if (layer === "pumps") {
|
||||
const result: ToolbarPropertyPanelData = {
|
||||
id: properties.id,
|
||||
type: "水泵",
|
||||
properties: [
|
||||
{ label: "起始节点 ID", value: properties.node1 },
|
||||
{ label: "终点节点 ID", value: properties.node2 },
|
||||
{ label: "起始节点 ID", value: properties.start_node_id },
|
||||
{ label: "终点节点 ID", value: properties.end_node_id },
|
||||
{
|
||||
label: "功率",
|
||||
value: properties.power?.toFixed?.(1),
|
||||
unit: "kW",
|
||||
},
|
||||
{
|
||||
label: "扬程",
|
||||
value: properties.head?.toFixed?.(1),
|
||||
unit: "m",
|
||||
label: "扬程曲线",
|
||||
value: properties.head_curve_id,
|
||||
},
|
||||
{
|
||||
label: "转速",
|
||||
value: properties.speed?.toFixed?.(1),
|
||||
unit: "rpm",
|
||||
unit: "倍",
|
||||
},
|
||||
{
|
||||
label: "模式",
|
||||
value: properties.pattern,
|
||||
value: properties.pattern_id,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -394,8 +382,8 @@ export const buildFeatureProperties = (
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layer === "geo_valves_mat" || layer === "geo_valves") {
|
||||
const valveType = valveSetting?.vType ?? properties.v_type;
|
||||
if (layer === "valves") {
|
||||
const valveType = valveSetting?.vType ?? properties.valve_type;
|
||||
const hasSimulationValues =
|
||||
Object.hasOwn(computedProperties, "status") ||
|
||||
Object.hasOwn(computedProperties, "setting");
|
||||
@@ -409,8 +397,8 @@ export const buildFeatureProperties = (
|
||||
id: properties.id,
|
||||
type: "阀门",
|
||||
properties: [
|
||||
{ label: "起始节点 ID", value: properties.node1 },
|
||||
{ label: "终点节点 ID", value: properties.node2 },
|
||||
{ label: "起始节点 ID", value: properties.start_node_id },
|
||||
{ label: "终点节点 ID", value: properties.end_node_id },
|
||||
{
|
||||
label: "直径",
|
||||
value: properties.diameter?.toFixed?.(1),
|
||||
@@ -519,7 +507,7 @@ export const buildFeatureProperties = (
|
||||
}
|
||||
};
|
||||
|
||||
if (layer === "geo_scada_mat" || layer === "geo_scada") {
|
||||
if (layer === "scada_devices") {
|
||||
return {
|
||||
id: properties.id,
|
||||
type: "SCADA设备",
|
||||
@@ -527,11 +515,13 @@ export const buildFeatureProperties = (
|
||||
{
|
||||
label: "类型",
|
||||
value:
|
||||
properties.type === "pipe_flow" ? "流量传感器" : "压力传感器",
|
||||
properties.device_type === "pipe_flow"
|
||||
? "流量传感器"
|
||||
: "压力传感器",
|
||||
},
|
||||
{
|
||||
label: "关联节点 ID",
|
||||
value: properties.associated_element_id,
|
||||
value: properties.node_id ?? properties.link_id,
|
||||
},
|
||||
{
|
||||
label: "传输模式",
|
||||
|
||||
@@ -81,6 +81,8 @@ interface DataContextType {
|
||||
selectedDate?: Date; // 选择的日期
|
||||
schemeName?: string; // 当前方案名称
|
||||
setSchemeName?: React.Dispatch<React.SetStateAction<string>>;
|
||||
schemeRunId?: string; // 当前分析运行 ID
|
||||
setSchemeRunId?: React.Dispatch<React.SetStateAction<string>>;
|
||||
setSelectedDate?: React.Dispatch<React.SetStateAction<Date>>;
|
||||
currentJunctionCalData?: any[]; // 当前计算结果
|
||||
setCurrentJunctionCalData?: React.Dispatch<React.SetStateAction<any[]>>;
|
||||
@@ -226,6 +228,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
// const [selectedDate, setSelectedDate] = useState<Date>(new Date("2025-9-17"));
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(new Date()); // 默认今天
|
||||
const [schemeName, setSchemeName] = useState<string>(""); // 当前方案名称
|
||||
const [schemeRunId, setSchemeRunId] = useState<string>("");
|
||||
// 记录 id、对应属性的计算值
|
||||
const [currentJunctionCalData, setCurrentJunctionCalData] = useState<any[]>(
|
||||
[],
|
||||
@@ -791,6 +794,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
);
|
||||
setSelectedDate(new Date());
|
||||
setSchemeName("");
|
||||
setSchemeRunId("");
|
||||
setCurrentJunctionCalData([]);
|
||||
setCurrentPipeCalData([]);
|
||||
setCompareJunctionCalData([]);
|
||||
@@ -1118,6 +1122,8 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
setSelectedDate,
|
||||
schemeName,
|
||||
setSchemeName,
|
||||
schemeRunId,
|
||||
setSchemeRunId,
|
||||
currentJunctionCalData,
|
||||
setCurrentJunctionCalData,
|
||||
currentPipeCalData,
|
||||
|
||||
@@ -63,6 +63,35 @@ import {
|
||||
} from "./operationalLayers";
|
||||
|
||||
describe("operational map resources", () => {
|
||||
it("requests the published tjwater_next layer names", () => {
|
||||
const sources = createOperationalMapSources({
|
||||
mapUrl: "https://maps.example.test/geoserver",
|
||||
workspace: "tjwater_next",
|
||||
});
|
||||
|
||||
expect((sources.junctions as any).options.url).toContain(
|
||||
"tjwater_next:junctions@WebMercatorQuad@pbf",
|
||||
);
|
||||
expect((sources.pipes as any).options.url).toContain(
|
||||
"tjwater_next:pipes@WebMercatorQuad@pbf",
|
||||
);
|
||||
expect((sources.valves as any).options.url).toContain(
|
||||
"tjwater_next:valves@WebMercatorQuad@pbf",
|
||||
);
|
||||
expect((sources.reservoirs as any).options.url).toContain(
|
||||
"typeName=tjwater_next:reservoirs",
|
||||
);
|
||||
expect((sources.pumps as any).options.url).toContain(
|
||||
"typeName=tjwater_next:pumps",
|
||||
);
|
||||
expect((sources.tanks as any).options.url).toContain(
|
||||
"typeName=tjwater_next:tanks",
|
||||
);
|
||||
expect((sources.scada as any).options.url).toContain(
|
||||
"typeName=tjwater_next:scada_devices",
|
||||
);
|
||||
});
|
||||
|
||||
it("shares sources while keeping per-map layer instances independent", () => {
|
||||
const options = {
|
||||
mapUrl: "https://maps.example.test/geoserver",
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { config } from "@/config/config";
|
||||
import { along, lineString, length, toMercator } from "@turf/turf";
|
||||
import type { FeatureLike } from "ol/Feature";
|
||||
import MVT from "ol/format/MVT";
|
||||
import { Point } from "ol/geom";
|
||||
import type BaseLayer from "ol/layer/Base";
|
||||
import VectorLayer from "ol/layer/Vector";
|
||||
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
|
||||
import { toLonLat } from "ol/proj";
|
||||
import GeoJSON from "ol/format/GeoJSON";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
import VectorTileSource from "ol/source/VectorTile";
|
||||
@@ -49,34 +46,12 @@ const createIconStyle = (src: string, scale = 0.1) =>
|
||||
|
||||
const scadaStyle = (feature: FeatureLike) =>
|
||||
createIconStyle(
|
||||
feature.get("type") === "pipe_flow"
|
||||
feature.get("device_type") === "pipe_flow"
|
||||
? "/icons/scada_flow.svg"
|
||||
: "/icons/scada_pressure.svg",
|
||||
);
|
||||
|
||||
const pumpStyle = (feature: FeatureLike) => {
|
||||
const geometry = feature.getGeometry();
|
||||
if (!geometry || geometry.getType() !== "LineString") return [];
|
||||
|
||||
const coordinates = (geometry as any)
|
||||
.getCoordinates()
|
||||
.map((coordinate: number[]) => toLonLat(coordinate));
|
||||
if (coordinates.length < 2) return [];
|
||||
|
||||
const featureLine = lineString(coordinates);
|
||||
const midpoint = along(featureLine, length(featureLine) / 2).geometry
|
||||
.coordinates;
|
||||
return [
|
||||
new Style({
|
||||
geometry: new Point(toMercator(midpoint)),
|
||||
image: new Icon({
|
||||
src: "/icons/pump.svg",
|
||||
scale: 0.12,
|
||||
anchor: [0.5, 0.5],
|
||||
}),
|
||||
}),
|
||||
];
|
||||
};
|
||||
const pumpStyle = () => createIconStyle("/icons/pump.svg", 0.12);
|
||||
|
||||
const pointProperties = [
|
||||
{ name: "高程", value: "elevation" },
|
||||
@@ -110,34 +85,34 @@ export const createOperationalMapSources = ({
|
||||
|
||||
return {
|
||||
junctions: new VectorTileSource({
|
||||
url: vectorTileUrl("geo_junctions"),
|
||||
url: vectorTileUrl("junctions"),
|
||||
format: new MVT(),
|
||||
projection: "EPSG:3857",
|
||||
}),
|
||||
pipes: new VectorTileSource({
|
||||
url: vectorTileUrl("geo_pipes"),
|
||||
url: vectorTileUrl("pipes"),
|
||||
format: new MVT(),
|
||||
projection: "EPSG:3857",
|
||||
}),
|
||||
valves: new VectorTileSource({
|
||||
url: vectorTileUrl("geo_valves"),
|
||||
url: vectorTileUrl("valves"),
|
||||
format: new MVT(),
|
||||
projection: "EPSG:3857",
|
||||
}),
|
||||
reservoirs: new VectorSource({
|
||||
url: vectorUrl("geo_reservoirs"),
|
||||
url: vectorUrl("reservoirs"),
|
||||
format: new GeoJSON(),
|
||||
}),
|
||||
pumps: new VectorSource({
|
||||
url: vectorUrl("geo_pumps"),
|
||||
url: vectorUrl("pumps"),
|
||||
format: new GeoJSON(),
|
||||
}),
|
||||
tanks: new VectorSource({
|
||||
url: vectorUrl("geo_tanks"),
|
||||
url: vectorUrl("tanks"),
|
||||
format: new GeoJSON(),
|
||||
}),
|
||||
scada: new VectorSource({
|
||||
url: vectorUrl("geo_scada"),
|
||||
url: vectorUrl("scada_devices"),
|
||||
format: new GeoJSON(),
|
||||
}),
|
||||
};
|
||||
@@ -187,7 +162,7 @@ export const createOperationalMapResources = ({
|
||||
properties: {
|
||||
name: "阀门",
|
||||
value: "valves",
|
||||
type: "linestring",
|
||||
type: "point",
|
||||
properties: [],
|
||||
},
|
||||
}),
|
||||
@@ -213,7 +188,7 @@ export const createOperationalMapResources = ({
|
||||
properties: {
|
||||
name: "水泵",
|
||||
value: "pumps",
|
||||
type: "linestring",
|
||||
type: "point",
|
||||
properties: [],
|
||||
},
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user