Bläddra i källkod

feat(app): 新增燃气管网地图组件

- 实现了一个交互式的燃气管网地图,展示了管道、监测设备、隐患点和风险区域- 使用 React Leaflet 库创建地图,并集成了 Ant Design 组件
- 提供了切换不同图层的控制选项,包括标准地图、卫星地图、管网管线、监测设备、隐患点位和风险区域
- 模拟了管道、监测设备、隐患点和风险区域的数据,并在地图上进行了可视化- 自定义了图标和颜色,以区分不同类型的监测设备和隐患点
nahida 9 månader sedan
förälder
incheckning
64ea6a9f53
1 ändrade filer med 336 tillägg och 0 borttagningar
  1. 336 0
      app/(other)/test8/components/gas-network-map.tsx

+ 336 - 0
app/(other)/test8/components/gas-network-map.tsx

@@ -0,0 +1,336 @@
+"use client"
+
+import {useState} from "react"
+import {Circle, LayersControl, MapContainer, Marker, Polyline, Popup, TileLayer} from "react-leaflet"
+import {Badge, Card, Descriptions, Divider, Switch} from "antd"
+import {Icon} from "leaflet"
+import "leaflet/dist/leaflet.css"
+
+// 修复 Leaflet 默认图标问题
+if (typeof window !== "undefined") {
+  delete (Icon.Default.prototype as any)._getIconUrl
+  Icon.Default.mergeOptions({
+    iconRetinaUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png",
+    iconUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png",
+    shadowUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png",
+  })
+}
+
+const mockPipelineData = [
+  {
+    id: "pipeline_001",
+    name: "五一大道主干管",
+    coordinates: [
+      [28.2282, 112.9388],
+      [28.2292, 112.9398],
+      [28.2302, 112.9408],
+      [28.2312, 112.9418],
+    ],
+    pressure: "中压",
+    material: "钢管",
+    diameter: "DN400",
+    status: "正常",
+  },
+  {
+    id: "pipeline_002",
+    name: "芙蓉路支管",
+    coordinates: [
+      [28.2312, 112.9418],
+      [28.2322, 112.9428],
+      [28.2332, 112.9438],
+    ],
+    pressure: "低压",
+    material: "PE管",
+    diameter: "DN200",
+    status: "维护中",
+  },
+  {
+    id: "pipeline_003",
+    name: "韶山路干管",
+    coordinates: [
+      [28.2202, 112.9308],
+      [28.2212, 112.9318],
+      [28.2222, 112.9328],
+      [28.2232, 112.9338],
+    ],
+    pressure: "高压",
+    material: "钢管",
+    diameter: "DN600",
+    status: "正常",
+  },
+]
+
+const mockMonitoringDevices = [
+  {
+    id: "device_CS001",
+    name: "压力监测点CS001",
+    position: [28.2282, 112.9388],
+    type: "压力监测",
+    status: "在线",
+    value: "0.28MPa",
+    lastUpdate: "2024-01-15 14:30:00",
+  },
+  {
+    id: "device_CS002",
+    name: "流量监测点CS002",
+    position: [28.2302, 112.9408],
+    type: "流量监测",
+    status: "在线",
+    value: "156m³/h",
+    lastUpdate: "2024-01-15 14:29:45",
+  },
+  {
+    id: "device_CS003",
+    name: "甲烷浓度监测CS003",
+    position: [28.2322, 112.9428],
+    type: "气体浓度",
+    status: "报警",
+    value: "0.6%LEL",
+    lastUpdate: "2024-01-15 14:31:12",
+  },
+  {
+    id: "device_CS004",
+    name: "温度监测点CS004",
+    position: [28.2222, 112.9328],
+    type: "温度监测",
+    status: "在线",
+    value: "15.2°C",
+    lastUpdate: "2024-01-15 14:30:30",
+  },
+]
+
+const mockHazardPoints = [
+  {
+    id: "hazard_CS001",
+    name: "管道老化隐患",
+    position: [28.2292, 112.9398],
+    level: "较大",
+    type: "设备老化",
+    description: "五一大道段管道使用年限超过20年,存在老化风险",
+    status: "待处理",
+  },
+  {
+    id: "hazard_CS002",
+    name: "地铁施工风险",
+    position: [28.2332, 112.9438],
+    level: "重大",
+    type: "外部威胁",
+    description: "芙蓉路地铁3号线施工,可能影响管道安全",
+    status: "处理中",
+  },
+  {
+    id: "hazard_CS003",
+    name: "道路改造影响",
+    position: [28.2212, 112.9318],
+    level: "一般",
+    type: "外部威胁",
+    description: "韶山路改造工程可能对管道造成影响",
+    status: "已处理",
+  },
+]
+
+const mockRiskAreas = [
+  {
+    id: "risk_CS001",
+    center: [28.2292, 112.9398],
+    radius: 200,
+    level: "较大风险",
+    color: "#faad14",
+  },
+  {
+    id: "risk_CS002",
+    center: [28.2332, 112.9438],
+    radius: 300,
+    level: "重大风险",
+    color: "#f5222d",
+  },
+  {
+    id: "risk_CS003",
+    center: [28.2212, 112.9318],
+    radius: 150,
+    level: "低风险",
+    color: "#52c41a",
+  },
+]
+
+// 自定义图标
+const createCustomIcon = (color: string, symbol: string) => {
+  return new Icon({
+    iconUrl: `data:image/svg+xml;base64,${btoa(`
+      <svg width="25" height="41" viewBox="0 0 25 41" xmlns="http://www.w3.org/2000/svg">
+        <path fill="${color}" stroke="#fff" strokeWidth="2" d="M12.5 0C5.6 0 0 5.6 0 12.5c0 12.5 12.5 28.5 12.5 28.5s12.5-16 12.5-28.5C25 5.6 19.4 0 12.5 0z"/>
+        <text x="12.5" y="17" textAnchor="middle" fill="white" fontSize="12" fontWeight="bold">${symbol}</text>
+      </svg>
+    `)}`,
+    iconSize: [25, 41],
+    iconAnchor: [12, 41],
+    popupAnchor: [1, -34],
+  })
+}
+
+const deviceIcon = createCustomIcon("#1890ff", "D")
+const hazardIcon = createCustomIcon("#faad14", "!")
+const criticalHazardIcon = createCustomIcon("#f5222d", "!!")
+
+interface GasNetworkMapProps {
+  height?: string
+}
+
+export default function GasNetworkMap({ height = "600px" }: GasNetworkMapProps) {
+  const [showPipelines, setShowPipelines] = useState(true)
+  const [showDevices, setShowDevices] = useState(true)
+  const [showHazards, setShowHazards] = useState(true)
+  const [showRiskAreas, setShowRiskAreas] = useState(true)
+
+  return (
+    <div className="w-full">
+      <Card className="mb-4">
+        <div className="flex flex-wrap gap-4 items-center">
+          <div className="flex items-center gap-2">
+            <Switch checked={showPipelines} onChange={setShowPipelines} size="small" />
+            <span>管网管线</span>
+          </div>
+          <div className="flex items-center gap-2">
+            <Switch checked={showDevices} onChange={setShowDevices} size="small" />
+            <span>监测设备</span>
+          </div>
+          <div className="flex items-center gap-2">
+            <Switch checked={showHazards} onChange={setShowHazards} size="small" />
+            <span>隐患点位</span>
+          </div>
+          <div className="flex items-center gap-2">
+            <Switch checked={showRiskAreas} onChange={setShowRiskAreas} size="small" />
+            <span>风险区域</span>
+          </div>
+        </div>
+      </Card>
+
+      <Card>
+        <div style={{ height }}>
+          <MapContainer center={[28.2282, 112.9388]} zoom={14} style={{ height: "100%", width: "100%" }}>
+            <LayersControl position="topright">
+              <LayersControl.BaseLayer checked name="标准地图">
+                <TileLayer
+                  attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+                  url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
+                />
+              </LayersControl.BaseLayer>
+
+              <LayersControl.BaseLayer name="卫星地图">
+                <TileLayer
+                  attribution='&copy; <a href="https://www.esri.com/">Esri</a>'
+                  url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
+                />
+              </LayersControl.BaseLayer>
+            </LayersControl>
+
+            {/* 管网管线 */}
+            {showPipelines &&
+              mockPipelineData.map((pipeline) => (
+                <Polyline
+                  key={pipeline.id}
+                  positions={pipeline.coordinates as [number, number][]}
+                  color={pipeline.status === "正常" ? "#52c41a" : "#faad14"}
+                  weight={6}
+                  opacity={0.8}
+                >
+                  <Popup>
+                    <div className="min-w-64">
+                      <h4 className="font-semibold mb-2">{pipeline.name}</h4>
+                      <Descriptions size="small" column={1}>
+                        <Descriptions.Item label="管道编号">{pipeline.id}</Descriptions.Item>
+                        <Descriptions.Item label="压力等级">{pipeline.pressure}</Descriptions.Item>
+                        <Descriptions.Item label="管材">{pipeline.material}</Descriptions.Item>
+                        <Descriptions.Item label="管径">{pipeline.diameter}</Descriptions.Item>
+                        <Descriptions.Item label="状态">
+                          <Badge status={pipeline.status === "正常" ? "success" : "warning"} text={pipeline.status} />
+                        </Descriptions.Item>
+                      </Descriptions>
+                    </div>
+                  </Popup>
+                </Polyline>
+              ))}
+
+            {/* 监测设备 */}
+            {showDevices &&
+              mockMonitoringDevices.map((device) => (
+                <Marker key={device.id} position={device.position as [number, number]} icon={deviceIcon}>
+                  <Popup>
+                    <div className="min-w-64">
+                      <h4 className="font-semibold mb-2">{device.name}</h4>
+                      <Descriptions size="small" column={1}>
+                        <Descriptions.Item label="设备编号">{device.id}</Descriptions.Item>
+                        <Descriptions.Item label="监测类型">{device.type}</Descriptions.Item>
+                        <Descriptions.Item label="当前值">{device.value}</Descriptions.Item>
+                        <Descriptions.Item label="状态">
+                          <Badge
+                            status={
+                              device.status === "在线" ? "success" : device.status === "报警" ? "error" : "default"
+                            }
+                            text={device.status}
+                          />
+                        </Descriptions.Item>
+                        <Descriptions.Item label="更新时间">{device.lastUpdate}</Descriptions.Item>
+                      </Descriptions>
+                    </div>
+                  </Popup>
+                </Marker>
+              ))}
+
+            {/* 隐患点位 */}
+            {showHazards &&
+              mockHazardPoints.map((hazard) => (
+                <Marker
+                  key={hazard.id}
+                  position={hazard.position as [number, number]}
+                  icon={hazard.level === "重大" ? criticalHazardIcon : hazardIcon}
+                >
+                  <Popup>
+                    <div className="min-w-64">
+                      <h4 className="font-semibold mb-2">{hazard.name}</h4>
+                      <Descriptions size="small" column={1}>
+                        <Descriptions.Item label="隐患编号">{hazard.id}</Descriptions.Item>
+                        <Descriptions.Item label="风险等级">
+                          <Badge color={hazard.level === "重大" ? "red" : "orange"} text={hazard.level} />
+                        </Descriptions.Item>
+                        <Descriptions.Item label="隐患类型">{hazard.type}</Descriptions.Item>
+                        <Descriptions.Item label="处理状态">
+                          <Badge status={hazard.status === "待处理" ? "warning" : "processing"} text={hazard.status} />
+                        </Descriptions.Item>
+                      </Descriptions>
+                      <Divider style={{ margin: "8px 0" }} />
+                      <p className="text-sm text-gray-600">{hazard.description}</p>
+                    </div>
+                  </Popup>
+                </Marker>
+              ))}
+
+            {/* 风险区域 */}
+            {showRiskAreas &&
+              mockRiskAreas.map((area) => (
+                <Circle
+                  key={area.id}
+                  center={area.center as [number, number]}
+                  radius={area.radius}
+                  fillColor={area.color}
+                  fillOpacity={0.2}
+                  color={area.color}
+                  weight={2}
+                >
+                  <Popup>
+                    <div>
+                      <h4 className="font-semibold mb-2">风险评估区域</h4>
+                      <p>
+                        风险等级: <Badge color={area.color} text={area.level} />
+                      </p>
+                      <p>影响半径: {area.radius}米</p>
+                    </div>
+                  </Popup>
+                </Circle>
+              ))}
+          </MapContainer>
+        </div>
+      </Card>
+    </div>
+  )
+}