refactor(frontend): align TJWater Next integrations

This commit is contained in:
2026-08-26 12:26:29 +08:00
parent 6623f4f7fb
commit fee4fc6ce1
51 changed files with 3074 additions and 22280 deletions
+37 -12
View File
@@ -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,
);
};
+56 -43
View File
@@ -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",
+11 -36
View File
@@ -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: [],
},
}),