光伏发电园区管理系统 - Three.js + Django 实现方案

发布于:2025-07-06 ⋅ 阅读:(15) ⋅ 点赞:(0)

光伏发电园区管理系统 - Three.js + Django 实现方案

我将设计一个基于Three.js和Django的光伏发电园区管理系统,包含3D可视化、实时监控和数据分析功能。

系统架构设计

API请求
数据存储
数据存储
数据存储
获取
获取
前端 - Three.js
Django后端
数据库
外部API
光伏设备数据
气象数据
发电数据
实时天气
电价信息

技术栈与依赖

  • 前端

    • Three.js (r128) - 3D渲染
    • Chart.js - 数据可视化
    • Bootstrap 5 - UI框架
    • WebSocket - 实时数据更新
  • 后端

    • Django 4.0
    • Django REST Framework
    • Django Channels (WebSocket支持)
    • PostgreSQL (带PostGIS扩展)
    • Celery (任务队列)
  • 其他依赖

    • pandas - 数据分析
    • numpy - 数值计算
    • requests - 外部API调用

实现步骤

1. Django 项目设置

# 创建虚拟环境
python -m venv env
source env/bin/activate

# 安装依赖
pip install django djangorestframework django-channels channels-redis celery pandas numpy requests psycopg2

# 创建项目
django-admin startproject pv_management
cd pv_management
django-admin startapp dashboard

2. 数据库模型设计 (dashboard/models.py)

from django.contrib.gis.db import models
from django.contrib.postgres.fields import JSONField

class SolarPark(models.Model):
    name = models.CharField(max_length=100)
    location = models.PointField()
    capacity = models.FloatField()  # 总容量 (MW)
    installation_date = models.DateField()
    
    def __str__(self):
        return self.name

class PanelArray(models.Model):
    park = models.ForeignKey(SolarPark, on_delete=models.CASCADE, related_name='arrays')
    array_id = models.CharField(max_length=20)
    panel_type = models.CharField(max_length=50)
    panel_count = models.IntegerField()
    orientation = models.FloatField()  # 方位角 (0-360)
    tilt = models.FloatField()  # 倾斜角 (0-90)
    position = models.PointField()
    installation_date = models.DateField()
    
    def __str__(self):
        return f"{self.park.name} - Array {self.array_id}"

class PanelData(models.Model):
    array = models.ForeignKey(PanelArray, on_delete=models.CASCADE, related_name='data')
    timestamp = models.DateTimeField(auto_now_add=True)
    temperature = models.FloatField()  # 面板温度 (°C)
    voltage = models.FloatField()  # 输出电压 (V)
    current = models.FloatField()  # 输出电流 (A)
    power = models.FloatField()  # 输出功率 (W)
    efficiency = models.FloatField()  # 转换效率 (%)
    status = models.CharField(max_length=20, choices=[
        ('normal', '正常'),
        ('degraded', '性能下降'),
        ('fault', '故障'),
        ('maintenance', '维护中')
    ])
    weather_data = JSONField(null=True, blank=True)  # 存储天气数据
    
    @property
    def energy(self):
        # 计算发电量 (kWh)
        return (self.power * 0.001)  # 假设1小时数据
    
    def __str__(self):
        return f"{self.array} @ {self.timestamp}"

class MaintenanceLog(models.Model):
    array = models.ForeignKey(PanelArray, on_delete=models.CASCADE)
    timestamp = models.DateTimeField(auto_now_add=True)
    description = models.TextField()
    technician = models.CharField(max_length=100)
    resolved = models.BooleanField(default=False)
    
    def __str__(self):
        return f"维护记录 - {self.array} @ {self.timestamp}"

3. Django 视图与API (dashboard/views.py)

from rest_framework import viewsets, generics
from rest_framework.response import Response
from .models import SolarPark, PanelArray, PanelData, MaintenanceLog
from .serializers import (
    SolarParkSerializer, 
    PanelArraySerializer, 
    PanelDataSerializer, 
    MaintenanceLogSerializer
)
from django.contrib.gis.geos import Point
from django.utils import timezone
from datetime import timedelta
import pandas as pd

class SolarParkViewSet(viewsets.ModelViewSet):
    queryset = SolarPark.objects.all()
    serializer_class = SolarParkSerializer

class PanelArrayViewSet(viewsets.ModelViewSet):
    queryset = PanelArray.objects.all()
    serializer_class = PanelArraySerializer

class PanelDataViewSet(viewsets.ModelViewSet):
    queryset = PanelData.objects.all()
    serializer_class = PanelDataSerializer

class MaintenanceLogViewSet(viewsets.ModelViewSet):
    queryset = MaintenanceLog.objects.all()
    serializer_class = MaintenanceLogSerializer

class ParkSummaryAPI(generics.RetrieveAPIView):
    def get(self, request, park_id):
        park = SolarPark.objects.get(id=park_id)
        arrays = park.arrays.all()
        
        # 当前发电数据
        current_data = PanelData.objects.filter(
            array__park=park, 
            timestamp__gte=timezone.now()-timedelta(minutes=5)
        ).order_by('-timestamp')
        
        # 计算总发电量
        total_power = sum([d.power for d in current_data])
        total_energy = sum([d.energy for d in current_data])
        
        # 效率分析
        efficiencies = [d.efficiency for d in current_data]
        avg_efficiency = sum(efficiencies) / len(efficiencies) if efficiencies else 0
        
        # 状态统计
        status_counts = current_data.values('status').annotate(count=models.Count('id'))
        
        return Response({
            'park': SolarParkSerializer(park).data,
            'total_power': total_power,
            'total_energy': total_energy,
            'avg_efficiency': avg_efficiency,
            'status_distribution': status_counts,
            'arrays': PanelArraySerializer(arrays, many=True).data
        })

class HistoricalDataAPI(generics.RetrieveAPIView):
    def get(self, request, array_id):
        time_range = request.GET.get('range', '24h')  # 默认24小时
        
        if time_range == '24h':
            delta = timedelta(hours=24)
        elif time_range == '7d':
            delta = timedelta(days=7)
        elif time_range == '30d':
            delta = timedelta(days=30)
        else:
            delta = timedelta(hours=24)
        
        end_time = timezone.now()
        start_time = end_time - delta
        
        data = PanelData.objects.filter(
            array_id=array_id,
            timestamp__range=(start_time, end_time)
        ).order_by('timestamp')
        
        # 使用Pandas进行数据处理
        df = pd.DataFrame.from_records(data.values(
            'timestamp', 'power', 'voltage', 'current', 'efficiency', 'temperature'
        ))
        
        # 按小时聚合
        df['timestamp'] = pd.to_datetime(df['timestamp'])
        df.set_index('timestamp', inplace=True)
        hourly = df.resample('H').mean()
        
        return Response({
            'raw_data': PanelDataSerializer(data, many=True).data,
            'hourly_data': hourly.reset_index().to_dict(orient='records')
        })

4. Three.js 光伏园区可视化 (frontend/js/solar_visualization.js)

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';

class SolarParkVisualization {
  constructor(containerId, parkData) {
    this.container = document.getElementById(containerId);
    this.parkData = parkData;
    
    // 场景设置
    this.scene = new THREE.Scene();
    this.scene.background = new THREE.Color(0x87CEEB); // 天空蓝
    
    // 相机设置
    this.camera = new THREE.PerspectiveCamera(
      60, 
      this.container.clientWidth / this.container.clientHeight, 
      0.1, 
      10000
    );
    this.camera.position.set(0, 200, 300);
    
    // 渲染器
    this.renderer = new THREE.WebGLRenderer({ antialias: true });
    this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.container.appendChild(this.renderer.domElement);
    
    // 控制器
    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    this.controls.enableDamping = true;
    this.controls.dampingFactor = 0.05;
    
    // 添加灯光
    this.addLights();
    
    // 加载环境
    this.loadEnvironment();
    
    // 创建光伏阵列
    this.createSolarArrays();
    
    // 添加后期处理
    this.setupPostProcessing();
    
    // 事件监听
    window.addEventListener('resize', this.onWindowResize.bind(this));
    this.renderer.domElement.addEventListener('click', this.onCanvasClick.bind(this));
    
    // 动画循环
    this.animate();
  }
  
  addLights() {
    // 环境光
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
    this.scene.add(ambientLight);
    
    // 方向光(模拟太阳)
    this.sunLight = new THREE.DirectionalLight(0xffffff, 1.0);
    this.sunLight.position.set(100, 200, 100);
    this.sunLight.castShadow = true;
    this.sunLight.shadow.mapSize.width = 2048;
    this.sunLight.shadow.mapSize.height = 2048;
    this.scene.add(this.sunLight);
    
    // 辅助光
    const fillLight = new THREE.DirectionalLight(0xffffff, 0.5);
    fillLight.position.set(-100, 100, -100);
    this.scene.add(fillLight);
  }
  
  loadEnvironment() {
    // 添加地面
    const groundGeometry = new THREE.PlaneGeometry(1000, 1000);
    const groundMaterial = new THREE.MeshStandardMaterial({ 
      color: 0x8B4513,
      roughness: 0.9,
      metalness: 0.1
    });
    this.ground = new THREE.Mesh(groundGeometry, groundMaterial);
    this.ground.rotation.x = -Math.PI / 2;
    this.ground.receiveShadow = true;
    this.scene.add(this.ground);
    
    // 加载天空盒
    const loader = new THREE.CubeTextureLoader();
    const texture = loader.load([
      'textures/sky/px.jpg', 'textures/sky/nx.jpg',
      'textures/sky/py.jpg', 'textures/sky/ny.jpg',
      'textures/sky/pz.jpg', 'textures/sky/nz.jpg'
    ]);
    this.scene.background = texture;
    
    // 添加简单树木
    this.addVegetation();
  }
  
  addVegetation() {
    const treeGeometry = new THREE.ConeGeometry(5, 15, 8);
    const treeMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });
    
    for (let i = 0; i < 20; i++) {
      const tree = new THREE.Mesh(treeGeometry, treeMaterial);
      tree.position.set(
        Math.random() * 400 - 200,
        7.5,
        Math.random() * 400 - 200
      );
      tree.castShadow = true;
      this.scene.add(tree);
    }
  }
  
  createSolarArrays() {
    this.panelArrays = [];
    
    this.parkData.arrays.forEach(arrayData => {
      const arrayGroup = new THREE.Group();
      arrayGroup.name = `array-${arrayData.id}`;
      arrayGroup.userData = arrayData;
      
      // 根据方位和倾斜角计算方向
      const rotationY = THREE.MathUtils.degToRad(arrayData.orientation);
      const rotationX = THREE.MathUtils.degToRad(arrayData.tilt);
      
      // 计算位置
      const position = new THREE.Vector3(
        arrayData.position.x,
        1, // 离地高度
        arrayData.position.y
      );
      
      // 创建光伏板
      const rows = Math.ceil(Math.sqrt(arrayData.panel_count));
      const cols = Math.ceil(arrayData.panel_count / rows);
      
      const panelWidth = 1.0;
      const panelHeight = 1.7;
      const spacing = 0.1;
      
      const panelGeometry = new THREE.BoxGeometry(panelWidth, 0.05, panelHeight);
      const panelMaterial = new THREE.MeshPhysicalMaterial({
        color: 0x333333,
        metalness: 0.9,
        roughness: 0.1,
        transparent: true,
        opacity: 0.9,
        emissive: 0x111111
      });
      
      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
          const panelIndex = i * cols + j;
          if (panelIndex >= arrayData.panel_count) break;
          
          const panel = new THREE.Mesh(panelGeometry, panelMaterial);
          panel.castShadow = true;
          panel.receiveShadow = true;
          
          // 计算位置
          panel.position.x = (i - rows/2) * (panelWidth + spacing);
          panel.position.z = (j - cols/2) * (panelHeight + spacing);
          
          // 添加玻璃表面
          const glassGeometry = new THREE.PlaneGeometry(panelWidth * 0.95, panelHeight * 0.95);
          const glassMaterial = new THREE.MeshPhysicalMaterial({
            color: 0x00aaff,
            metalness: 0.1,
            roughness: 0.05,
            transparent: true,
            opacity: 0.3,
            side: THREE.DoubleSide
          });
          
          const glass = new THREE.Mesh(glassGeometry, glassMaterial);
          glass.position.y = 0.026; // 稍微高于面板
          glass.rotation.x = Math.PI / 2;
          panel.add(glass);
          
          arrayGroup.add(panel);
        }
      }
      
      // 设置阵列位置和旋转
      arrayGroup.position.copy(position);
      arrayGroup.rotation.y = rotationY;
      arrayGroup.rotation.x = rotationX;
      
      // 添加支撑结构
      const supportGeometry = new THREE.BoxGeometry(0.1, 1.5, 0.1);
      const supportMaterial = new THREE.MeshStandardMaterial({ color: 0xAAAAAA });
      
      const supportPositions = [
        new THREE.Vector3(-(rows/2 * (panelWidth+spacing)), -0.75, -(cols/2 * (panelHeight+spacing))),
        new THREE.Vector3( (rows/2 * (panelWidth+spacing)), -0.75, -(cols/2 * (panelHeight+spacing))),
        new THREE.Vector3(-(rows/2 * (panelWidth+spacing)), -0.75,  (cols/2 * (panelHeight+spacing))),
        new THREE.Vector3( (rows/2 * (panelWidth+spacing)), -0.75,  (cols/2 * (panelHeight+spacing)))
      ];
      
      supportPositions.forEach(pos => {
        const support = new THREE.Mesh(supportGeometry, supportMaterial);
        support.position.copy(pos);
        support.position.y = -0.75;
        arrayGroup.add(support);
      });
      
      this.scene.add(arrayGroup);
      this.panelArrays.push(arrayGroup);
    });
  }
  
  setupPostProcessing() {
    this.composer = new EffectComposer(this.renderer);
    
    const renderPass = new RenderPass(this.scene, this.camera);
    this.composer.addPass(renderPass);
    
    // 轮廓效果
    this.outlinePass = new OutlinePass(
      new THREE.Vector2(this.container.clientWidth, this.container.clientHeight),
      this.scene,
      this.camera
    );
    this.outlinePass.visibleEdgeColor.set(0x00ff00);
    this.outlinePass.hiddenEdgeColor.set(0x000000);
    this.outlinePass.edgeStrength = 3.0;
    this.outlinePass.edgeThickness = 1.0;
    this.outlinePass.edgeGlow = 0.5;
    
    this.composer.addPass(this.outlinePass);
  }
  
  onWindowResize() {
    this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
    this.composer.setSize(this.container.clientWidth, this.container.clientHeight);
  }
  
  onCanvasClick(event) {
    const mouse = new THREE.Vector2();
    mouse.x = (event.clientX / this.renderer.domElement.clientWidth) * 2 - 1;
    mouse.y = - (event.clientY / this.renderer.domElement.clientHeight) * 2 + 1;
    
    const raycaster = new THREE.Raycaster();
    raycaster.setFromCamera(mouse, this.camera);
    
    const intersects = raycaster.intersectObjects(this.panelArrays);
    
    if (intersects.length > 0) {
      const selectedArray = intersects[0].object.parent;
      this.outlinePass.selectedObjects = [selectedArray];
      
      // 触发自定义事件
      const arraySelected = new CustomEvent('arraySelected', {
        detail: { arrayData: selectedArray.userData }
      });
      document.dispatchEvent(arraySelected);
    } else {
      this.outlinePass.selectedObjects = [];
    }
  }
  
  updatePanelStatus(panelData) {
    this.panelArrays.forEach(arrayGroup => {
      const arrayData = arrayGroup.userData;
      
      // 查找该阵列的最新数据
      const data = panelData.find(d => d.array_id === arrayData.id);
      if (!data) return;
      
      // 根据状态更新颜色
      let color;
      switch(data.status) {
        case 'normal':
          color = 0x00ff00; // 绿色
          break;
        case 'degraded':
          color = 0xffff00; // 黄色
          break;
        case 'fault':
          color = 0xff0000; // 红色
          break;
        case 'maintenance':
          color = 0x0000ff; // 蓝色
          break;
        default:
          color = 0xffffff; // 白色
      }
      
      // 更新面板材质
      arrayGroup.traverse(child => {
        if (child.isMesh && child.material instanceof THREE.MeshPhysicalMaterial) {
          child.material.emissive = new THREE.Color(color);
          child.material.emissiveIntensity = data.status === 'normal' ? 0.1 : 0.5;
          
          // 对于玻璃部分
          if (child.children.length > 0) {
            const glass = child.children[0];
            if (data.status === 'normal') {
              glass.material.color.set(0x00aaff);
            } else {
              glass.material.color.set(0xff0000);
            }
          }
        }
      });
    });
  }
  
  animate() {
    requestAnimationFrame(this.animate.bind(this));
    
    // 更新控制器
    this.controls.update();
    
    // 更新太阳位置
    const time = Date.now() * 0.0001;
    this.sunLight.position.x = Math.sin(time) * 200;
    this.sunLight.position.z = Math.cos(time) * 200;
    
    // 渲染
    this.composer.render();
  }
}

export default SolarParkVisualization;

5. 前端主界面 (frontend/index.html)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>光伏发电园区管理系统</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
    <div class="container-fluid">
        <div class="row">
            <!-- 侧边导航 -->
            <div class="col-md-2 bg-dark text-white p-3">
                <h4 class="text-center mb-4">光伏园区管理</h4>
                <ul class="nav flex-column">
                    <li class="nav-item">
                        <a class="nav-link active" href="#">园区概览</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#">设备监控</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#">维护管理</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#">数据分析</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#">系统设置</a>
                    </li>
                </ul>
                
                <div class="mt-5">
                    <h5>园区选择</h5>
                    <select id="park-select" class="form-select">
                        <option value="1">园区1 - 50MW</option>
                        <option value="2">园区2 - 30MW</option>
                        <option value="3">园区3 - 20MW</option>
                    </select>
                </div>
                
                <div class="mt-4">
                    <h5>实时状态</h5>
                    <div class="d-flex justify-content-between">
                        <span>总发电量:</span>
                        <span id="total-power">0 kW</span>
                    </div>
                    <div class="d-flex justify-content-between">
                        <span>总效率:</span>
                        <span id="total-efficiency">0%</span>
                    </div>
                    <div class="mt-2">
                        <div class="progress">
                            <div id="capacity-bar" class="progress-bar" role="progressbar"></div>
                        </div>
                        <small class="text-muted">容量使用率</small>
                    </div>
                </div>
            </div>
            
            <!-- 主内容区 -->
            <div class="col-md-10 p-0">
                <div class="row">
                    <!-- 3D可视化区域 -->
                    <div class="col-md-8 p-0 position-relative">
                        <div id="solar-visualization" class="h-100"></div>
                        
                        <!-- 控制面板 -->
                        <div class="position-absolute top-0 end-0 m-3">
                            <div class="btn-group">
                                <button class="btn btn-light" id="reset-view">
                                    <i class="bi bi-arrow-clockwise"></i> 重置视图
                                </button>
                                <button class="btn btn-light" id="day-mode">
                                    <i class="bi bi-sun"></i> 白天模式
                                </button>
                                <button class="btn btn-dark" id="night-mode">
                                    <i class="bi bi-moon"></i> 夜间模式
                                </button>
                            </div>
                        </div>
                    </div>
                    
                    <!-- 数据面板 -->
                    <div class="col-md-4 p-3">
                        <div class="card mb-3">
                            <div class="card-header bg-primary text-white">
                                <h5 class="mb-0">选中的光伏阵列</h5>
                            </div>
                            <div class="card-body" id="array-details">
                                <p class="text-center text-muted">请选择光伏阵列查看详情</p>
                            </div>
                        </div>
                        
                        <div class="card mb-3">
                            <div class="card-header bg-success text-white">
                                <h5 class="mb-0">实时发电数据</h5>
                            </div>
                            <div class="card-body">
                                <canvas id="power-chart" height="200"></canvas>
                            </div>
                        </div>
                        
                        <div class="card">
                            <div class="card-header bg-info text-white">
                                <h5 class="mb-0">状态分布</h5>
                            </div>
                            <div class="card-body">
                                <canvas id="status-chart" height="200"></canvas>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- JavaScript 依赖 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.0/dist/chart.min.js"></script>
    <script src="{% static 'js/solar_visualization.js' %}"></script>
    <script src="{% static 'js/main.js' %}"></script>
</body>
</html>

6. 前端主逻辑 (frontend/js/main.js)

// 初始化变量
let solarViz;
let powerChart;
let statusChart;
let selectedArray = null;
const SOCKET_URL = `ws://${window.location.host}/ws/dashboard/`;

// 初始化页面
document.addEventListener('DOMContentLoaded', function() {
    // 加载园区数据
    loadParkData(1);
    
    // 设置事件监听器
    document.getElementById('park-select').addEventListener('change', function() {
        loadParkData(this.value);
    });
    
    document.addEventListener('arraySelected', function(e) {
        selectedArray = e.detail.arrayData;
        updateArrayDetails();
        loadArrayData(selectedArray.id);
    });
    
    document.getElementById('reset-view').addEventListener('click', function() {
        if (solarViz) {
            solarViz.camera.position.set(0, 200, 300);
            solarViz.camera.lookAt(0, 0, 0);
            solarViz.controls.reset();
        }
    });
    
    document.getElementById('day-mode').addEventListener('click', function() {
        if (solarViz) {
            solarViz.scene.background = new THREE.Color(0x87CEEB);
        }
    });
    
    document.getElementById('night-mode').addEventListener('click', function() {
        if (solarViz) {
            solarViz.scene.background = new THREE.Color(0x0a0a2a);
        }
    });
    
    // 初始化WebSocket连接
    initWebSocket();
});

// 加载园区数据
function loadParkData(parkId) {
    fetch(`/api/parks/${parkId}/summary/`)
        .then(response => response.json())
        .then(data => {
            // 更新UI
            document.getElementById('total-power').textContent = 
                `${(data.total_power / 1000).toFixed(1)} MW`;
            document.getElementById('total-efficiency').textContent = 
                `${data.avg_efficiency.toFixed(1)}%`;
                
            const capacityPercent = (data.total_power / (data.park.capacity * 1000000)) * 100;
            document.getElementById('capacity-bar').style.width = `${capacityPercent}%`;
            
            // 初始化3D可视化
            initSolarVisualization(data);
            
            // 初始化图表
            initCharts(data);
        });
}

// 初始化3D可视化
function initSolarVisualization(parkData) {
    const container = document.getElementById('solar-visualization');
    if (solarViz) {
        container.innerHTML = '';
    }
    solarViz = new SolarParkVisualization('solar-visualization', parkData);
}

// 初始化图表
function initCharts(parkData) {
    const powerCtx = document.getElementById('power-chart').getContext('2d');
    
    // 销毁现有图表
    if (powerChart) {
        powerChart.destroy();
    }
    
    // 创建功率图表
    powerChart = new Chart(powerCtx, {
        type: 'line',
        data: {
            labels: [],
            datasets: [{
                label: '总功率 (kW)',
                data: [],
                borderColor: 'rgba(75, 192, 192, 1)',
                backgroundColor: 'rgba(75, 192, 192, 0.2)',
                tension: 0.4,
                fill: true
            }]
        },
        options: {
            responsive: true,
            scales: {
                y: {
                    beginAtZero: true
                }
            }
        }
    });
    
    // 创建状态图表
    const statusCtx = document.getElementById('status-chart').getContext('2d');
    if (statusChart) {
        statusChart.destroy();
    }
    
    const statusData = parkData.status_distribution.map(s => s.count);
    const statusLabels = parkData.status_distribution.map(s => {
        switch(s.status) {
            case 'normal': return '正常';
            case 'degraded': return '性能下降';
            case 'fault': return '故障';
            case 'maintenance': return '维护中';
            default: return s.status;
        }
    });
    
    statusChart = new Chart(statusCtx, {
        type: 'doughnut',
        data: {
            labels: statusLabels,
            datasets: [{
                data: statusData,
                backgroundColor: [
                    'rgba(75, 192, 192, 0.8)',
                    'rgba(255, 206, 86, 0.8)',
                    'rgba(255, 99, 132, 0.8)',
                    'rgba(54, 162, 235, 0.8)'
                ]
            }]
        },
        options: {
            responsive: true,
            plugins: {
                legend: {
                    position: 'bottom'
                }
            }
        }
    });
}

// 更新阵列详情
function updateArrayDetails() {
    if (!selectedArray) return;
    
    const detailsDiv = document.getElementById('array-details');
    detailsDiv.innerHTML = `
        <h6>${selectedArray.park.name} - 阵列 ${selectedArray.array_id}</h6>
        <hr>
        <div class="row">
            <div class="col-6">
                <p><strong>面板类型:</strong></p>
                <p>${selectedArray.panel_type}</p>
            </div>
            <div class="col-6">
                <p><strong>面板数量:</strong></p>
                <p>${selectedArray.panel_count}</p>
            </div>
        </div>
        <div class="row mt-2">
            <div class="col-6">
                <p><strong>方位角:</strong></p>
                <p>${selectedArray.orientation}°</p>
            </div>
            <div class="col-6">
                <p><strong>倾斜角:</strong></p>
                <p>${selectedArray.tilt}°</p>
            </div>
        </div>
        <div class="mt-3">
            <button class="btn btn-warning btn-sm">查看详情</button>
            <button class="btn btn-danger btn-sm">报告问题</button>
        </div>
    `;
}

// 加载阵列数据
function loadArrayData(arrayId) {
    fetch(`/api/arrays/${arrayId}/data/?range=24h`)
        .then(response => response.json())
        .then(data => {
            // 更新图表
            updatePowerChart(data.hourly_data);
        });
}

// 更新功率图表
function updatePowerChart(hourlyData) {
    if (!powerChart) return;
    
    const labels = hourlyData.map(d => new Date(d.timestamp).toLocaleTimeString());
    const powerValues = hourlyData.map(d => d.power ? d.power / 1000 : 0);
    
    powerChart.data.labels = labels;
    powerChart.data.datasets[0].data = powerValues;
    powerChart.update();
}

// 初始化WebSocket
function initWebSocket() {
    const socket = new WebSocket(SOCKET_URL);
    
    socket.onopen = function() {
        console.log('WebSocket连接已建立');
    };
    
    socket.onmessage = function(event) {
        const data = JSON.parse(event.data);
        
        // 更新实时数据
        if (data.type === 'realtime_update') {
            document.getElementById('total-power').textContent = 
                `${(data.total_power / 1000).toFixed(1)} MW`;
            document.getElementById('total-efficiency').textContent = 
                `${data.avg_efficiency.toFixed(1)}%`;
                
            const capacityPercent = (data.total_power / (data.park_capacity * 1000000)) * 100;
            document.getElementById('capacity-bar').style.width = `${capacityPercent}%`;
            
            // 更新3D场景中的面板状态
            if (solarViz) {
                solarViz.updatePanelStatus(data.panel_data);
            }
            
            // 更新状态图表
            if (statusChart) {
                const statusData = data.status_distribution.map(s => s.count);
                statusChart.data.datasets[0].data = statusData;
                statusChart.update();
            }
        }
    };
    
    socket.onclose = function() {
        console.log('WebSocket连接已关闭,5秒后重试...');
        setTimeout(initWebSocket, 5000);
    };
}

系统功能亮点

  1. 三维光伏园区可视化

    • 真实感光伏板阵列渲染
    • 面板状态实时颜色编码(正常、故障、维护等)
    • 交互式相机控制
  2. 实时数据监控

    • WebSocket实时数据推送
    • 功率、效率、温度等关键指标监控
    • 状态分布饼图
  3. 数据分析功能

    • 历史发电数据分析
    • 效率趋势图表
    • 容量利用率监控
  4. 设备管理

    • 光伏阵列详细信息查看
    • 维护记录跟踪
    • 故障报告功能

安装与部署指南

1. 环境准备

# 安装PostgreSQL
sudo apt install postgresql postgresql-contrib postgis

# 创建数据库
sudo -u postgres createdb pv_management
sudo -u postgres psql -c "CREATE USER pvuser WITH PASSWORD 'password';"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pv_management TO pvuser;"
sudo -u postgres psql -d pv_management -c "CREATE EXTENSION postgis;"

2. 配置Django项目

# settings.py 配置

DATABASES = {
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'NAME': 'pv_management',
        'USER': 'pvuser',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

# 添加应用
INSTALLED_APPS = [
    ...
    'channels',
    'dashboard',
    'django.contrib.gis',
]

# 配置ASGI
ASGI_APPLICATION = 'pv_management.asgi.application'

# 配置Channels
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [("127.0.0.1", 6379)],
        },
    },
}

# 静态文件配置
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'frontend/static')]

3. 运行系统

# 数据库迁移
python manage.py makemigrations
python manage.py migrate

# 收集静态文件
python manage.py collectstatic

# 启动Django开发服务器
python manage.py runserver

# 启动Celery worker (在另一个终端)
celery -A pv_management worker -l info

# 启动Channels (在另一个终端)
daphne pv_management.asgi:application

未来扩展方向

  1. AI预测功能

    • 发电量预测
    • 故障预测
    • 清洁优化建议
  2. 无人机巡检集成

    • 自动巡检路径规划
    • 热成像故障检测
    • 基于图像的污垢分析
  3. AR移动应用

    • 现场设备信息叠加
    • 维护指导可视化
    • 远程专家协助
  4. 能源交易平台

    • 实时电价监控
    • 自动售电策略
    • 区块链能源交易

这个系统为光伏发电园区提供了全面的数字化管理解决方案,结合了Three.js的强大可视化能力和Django的灵活后端处理,实现了光伏发电园区的智能化管理。


网站公告

今日签到

点亮在社区的每一天
去签到