743 lines
22 KiB
Python
743 lines
22 KiB
Python
"""
|
|
天气和季节系统工具类
|
|
提供各种实用工具函数和辅助方法
|
|
"""
|
|
|
|
import time
|
|
import random
|
|
from typing import Dict, Any, List, Optional, Tuple
|
|
import math
|
|
import json
|
|
import os
|
|
|
|
class WeatherUtils:
|
|
"""
|
|
天气和季节系统工具类
|
|
提供各种实用工具函数和辅助方法
|
|
"""
|
|
|
|
def __init__(self, plugin):
|
|
"""
|
|
初始化工具类
|
|
|
|
Args:
|
|
plugin: 天气和季节系统插件实例
|
|
"""
|
|
self.plugin = plugin
|
|
self.enabled = False
|
|
self.initialized = False
|
|
|
|
# 配置文件路径
|
|
self.config_directory = "config/weather"
|
|
self.preset_directory = "presets/weather"
|
|
|
|
# 数据缓存
|
|
self.data_cache = {}
|
|
self.cache_expiry = 300 # 缓存过期时间(秒)
|
|
|
|
# 日志配置
|
|
self.log_enabled = True
|
|
self.log_level = "INFO"
|
|
self.log_file = "weather_system.log"
|
|
|
|
# 性能监控
|
|
self.performance_stats = {
|
|
'function_calls': {},
|
|
'execution_times': {},
|
|
'memory_usage': 0.0
|
|
}
|
|
|
|
print("✓ 天气和季节系统工具类已创建")
|
|
|
|
def initialize(self) -> bool:
|
|
"""
|
|
初始化工具类
|
|
|
|
Returns:
|
|
是否初始化成功
|
|
"""
|
|
try:
|
|
# 创建必要的目录
|
|
directories = [self.config_directory, self.preset_directory]
|
|
for directory in directories:
|
|
if not os.path.exists(directory):
|
|
os.makedirs(directory)
|
|
|
|
self.initialized = True
|
|
print("✓ 天气和季节系统工具类初始化完成")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"✗ 天气和季节系统工具类初始化失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
def enable(self) -> bool:
|
|
"""
|
|
启用工具类
|
|
|
|
Returns:
|
|
是否启用成功
|
|
"""
|
|
try:
|
|
if not self.initialized:
|
|
print("✗ 工具类未初始化")
|
|
return False
|
|
|
|
self.enabled = True
|
|
print("✓ 天气和季节系统工具类已启用")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"✗ 天气和季节系统工具类启用失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
def disable(self):
|
|
"""禁用工具类"""
|
|
try:
|
|
self.enabled = False
|
|
print("✓ 天气和季节系统工具类已禁用")
|
|
|
|
except Exception as e:
|
|
print(f"✗ 天气和季节系统工具类禁用失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def finalize(self):
|
|
"""清理工具类资源"""
|
|
try:
|
|
self.disable()
|
|
self._save_cache()
|
|
self.initialized = False
|
|
print("✓ 天气和季节系统工具类资源已清理")
|
|
|
|
except Exception as e:
|
|
print(f"✗ 天气和季节系统工具类资源清理失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def update(self, dt: float):
|
|
"""
|
|
更新工具类状态
|
|
|
|
Args:
|
|
dt: 时间增量
|
|
"""
|
|
try:
|
|
if not self.enabled:
|
|
return
|
|
|
|
# 清理过期缓存
|
|
self._cleanup_expired_cache()
|
|
|
|
except Exception as e:
|
|
print(f"✗ 天气和季节系统工具类更新失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def _cleanup_expired_cache(self):
|
|
"""清理过期缓存"""
|
|
try:
|
|
current_time = time.time()
|
|
expired_keys = []
|
|
|
|
for key, (timestamp, _) in self.data_cache.items():
|
|
if current_time - timestamp > self.cache_expiry:
|
|
expired_keys.append(key)
|
|
|
|
for key in expired_keys:
|
|
del self.data_cache[key]
|
|
|
|
except Exception as e:
|
|
if self.log_enabled and self.log_level == "DEBUG":
|
|
print(f"✗ 缓存清理失败: {e}")
|
|
|
|
def _save_cache(self):
|
|
"""保存缓存到文件"""
|
|
try:
|
|
cache_file = os.path.join(self.config_directory, "cache.json")
|
|
cache_data = {}
|
|
|
|
# 只保存可序列化的数据
|
|
for key, (timestamp, value) in self.data_cache.items():
|
|
try:
|
|
json.dumps(value) # 测试是否可序列化
|
|
cache_data[key] = (timestamp, value)
|
|
except (TypeError, ValueError):
|
|
continue # 跳过不可序列化的数据
|
|
|
|
with open(cache_file, 'w', encoding='utf-8') as f:
|
|
json.dump(cache_data, f, ensure_ascii=False, indent=2)
|
|
|
|
except Exception as e:
|
|
if self.log_enabled and self.log_level == "DEBUG":
|
|
print(f"✗ 缓存保存失败: {e}")
|
|
|
|
def load_cache(self):
|
|
"""从文件加载缓存"""
|
|
try:
|
|
cache_file = os.path.join(self.config_directory, "cache.json")
|
|
if os.path.exists(cache_file):
|
|
with open(cache_file, 'r', encoding='utf-8') as f:
|
|
cache_data = json.load(f)
|
|
self.data_cache.update(cache_data)
|
|
|
|
except Exception as e:
|
|
if self.log_enabled and self.log_level == "DEBUG":
|
|
print(f"✗ 缓存加载失败: {e}")
|
|
|
|
def log_message(self, level: str, message: str):
|
|
"""
|
|
记录日志消息
|
|
|
|
Args:
|
|
level: 日志级别
|
|
message: 日志消息
|
|
"""
|
|
try:
|
|
if not self.log_enabled:
|
|
return
|
|
|
|
levels = ["DEBUG", "INFO", "WARNING", "ERROR"]
|
|
if levels.index(level) < levels.index(self.log_level):
|
|
return
|
|
|
|
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
|
log_entry = f"[{timestamp}] {level}: {message}\n"
|
|
|
|
print(log_entry.strip())
|
|
|
|
# 写入日志文件
|
|
try:
|
|
with open(self.log_file, 'a', encoding='utf-8') as f:
|
|
f.write(log_entry)
|
|
except Exception:
|
|
pass # 忽略文件写入错误
|
|
|
|
except Exception as e:
|
|
pass # 忽略日志记录错误
|
|
|
|
def performance_monitor(self, func_name: str):
|
|
"""
|
|
性能监控装饰器
|
|
|
|
Args:
|
|
func_name: 函数名称
|
|
"""
|
|
def decorator(func):
|
|
def wrapper(*args, **kwargs):
|
|
start_time = time.time()
|
|
|
|
try:
|
|
result = func(*args, **kwargs)
|
|
end_time = time.time()
|
|
|
|
# 记录执行时间
|
|
execution_time = end_time - start_time
|
|
if func_name not in self.performance_stats['function_calls']:
|
|
self.performance_stats['function_calls'][func_name] = 0
|
|
self.performance_stats['execution_times'][func_name] = 0.0
|
|
|
|
self.performance_stats['function_calls'][func_name] += 1
|
|
self.performance_stats['execution_times'][func_name] += execution_time
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
end_time = time.time()
|
|
execution_time = end_time - start_time
|
|
|
|
self.log_message("ERROR", f"函数 {func_name} 执行失败: {e}")
|
|
raise
|
|
|
|
return wrapper
|
|
return decorator
|
|
|
|
def get_weather_icon(self, weather_type: str) -> str:
|
|
"""
|
|
获取天气图标
|
|
|
|
Args:
|
|
weather_type: 天气类型
|
|
|
|
Returns:
|
|
天气图标字符
|
|
"""
|
|
icons = {
|
|
'clear': '☀️',
|
|
'cloudy': '☁️',
|
|
'rain': '🌧️',
|
|
'storm': '⛈️',
|
|
'snow': '❄️',
|
|
'fog': '🌫️',
|
|
'windy': '💨',
|
|
'hail': '🌨️'
|
|
}
|
|
|
|
return icons.get(weather_type, '🌈')
|
|
|
|
def get_season_icon(self, season: str) -> str:
|
|
"""
|
|
获取季节图标
|
|
|
|
Args:
|
|
season: 季节名称
|
|
|
|
Returns:
|
|
季节图标字符
|
|
"""
|
|
icons = {
|
|
'spring': '🌸',
|
|
'summer': '☀️',
|
|
'autumn': '🍂',
|
|
'winter': '❄️'
|
|
}
|
|
|
|
return icons.get(season, '🌍')
|
|
|
|
def temperature_to_string(self, temperature: float) -> str:
|
|
"""
|
|
将温度转换为字符串
|
|
|
|
Args:
|
|
temperature: 温度值(摄氏度)
|
|
|
|
Returns:
|
|
温度字符串
|
|
"""
|
|
return f"{temperature:.1f}°C"
|
|
|
|
def wind_speed_to_string(self, wind_speed: float) -> str:
|
|
"""
|
|
将风速转换为字符串
|
|
|
|
Args:
|
|
wind_speed: 风速(m/s)
|
|
|
|
Returns:
|
|
风速字符串
|
|
"""
|
|
return f"{wind_speed:.1f} m/s"
|
|
|
|
def precipitation_to_string(self, precipitation: float) -> str:
|
|
"""
|
|
将降水强度转换为字符串
|
|
|
|
Args:
|
|
precipitation: 降水强度(0-1)
|
|
|
|
Returns:
|
|
降水强度字符串
|
|
"""
|
|
if precipitation <= 0:
|
|
return "无降水"
|
|
elif precipitation < 0.3:
|
|
return "小雨"
|
|
elif precipitation < 0.6:
|
|
return "中雨"
|
|
elif precipitation < 0.9:
|
|
return "大雨"
|
|
else:
|
|
return "暴雨"
|
|
|
|
def visibility_to_string(self, visibility: float) -> str:
|
|
"""
|
|
将能见度转换为字符串
|
|
|
|
Args:
|
|
visibility: 能见度(米)
|
|
|
|
Returns:
|
|
能见度字符串
|
|
"""
|
|
if visibility >= 5000:
|
|
return "良好"
|
|
elif visibility >= 1000:
|
|
return "一般"
|
|
elif visibility >= 500:
|
|
return "较差"
|
|
else:
|
|
return "极差"
|
|
|
|
def humidity_to_string(self, humidity: float) -> str:
|
|
"""
|
|
将湿度转换为字符串
|
|
|
|
Args:
|
|
humidity: 湿度(%)
|
|
|
|
Returns:
|
|
湿度字符串
|
|
"""
|
|
return f"{humidity:.0f}%"
|
|
|
|
def pressure_to_string(self, pressure: float) -> str:
|
|
"""
|
|
将气压转换为字符串
|
|
|
|
Args:
|
|
pressure: 气压(hPa)
|
|
|
|
Returns:
|
|
气压字符串
|
|
"""
|
|
return f"{pressure:.1f} hPa"
|
|
|
|
def uv_index_to_string(self, uv_index: float) -> str:
|
|
"""
|
|
将UV指数转换为字符串
|
|
|
|
Args:
|
|
uv_index: UV指数
|
|
|
|
Returns:
|
|
UV指数字符串
|
|
"""
|
|
if uv_index <= 2:
|
|
return "低"
|
|
elif uv_index <= 5:
|
|
return "中等"
|
|
elif uv_index <= 7:
|
|
return "高"
|
|
elif uv_index <= 10:
|
|
return "很高"
|
|
else:
|
|
return "极高"
|
|
|
|
def get_weather_description(self, weather_type: str) -> str:
|
|
"""
|
|
获取天气描述
|
|
|
|
Args:
|
|
weather_type: 天气类型
|
|
|
|
Returns:
|
|
天气描述
|
|
"""
|
|
descriptions = {
|
|
'clear': '晴朗无云',
|
|
'cloudy': '多云天气',
|
|
'rain': '正在下雨',
|
|
'storm': '雷暴天气',
|
|
'snow': '正在下雪',
|
|
'fog': '大雾天气',
|
|
'windy': '风力强劲',
|
|
'hail': '正在下冰雹'
|
|
}
|
|
|
|
return descriptions.get(weather_type, '未知天气')
|
|
|
|
def get_season_description(self, season: str) -> str:
|
|
"""
|
|
获取季节描述
|
|
|
|
Args:
|
|
season: 季节名称
|
|
|
|
Returns:
|
|
季节描述
|
|
"""
|
|
descriptions = {
|
|
'spring': '万物复苏的春季',
|
|
'summer': '炎热的夏季',
|
|
'autumn': '收获的秋季',
|
|
'winter': '寒冷的冬季'
|
|
}
|
|
|
|
return descriptions.get(season, '未知季节')
|
|
|
|
def calculate_weather_severity(self, conditions: Dict[str, Any]) -> float:
|
|
"""
|
|
计算天气严重程度
|
|
|
|
Args:
|
|
conditions: 天气条件字典
|
|
|
|
Returns:
|
|
严重程度(0-1)
|
|
"""
|
|
try:
|
|
severity = 0.0
|
|
|
|
# 降水影响
|
|
precipitation = conditions.get('precipitation', 0.0)
|
|
severity += precipitation * 0.3
|
|
|
|
# 风速影响
|
|
wind_speed = conditions.get('wind_speed', 0.0)
|
|
severity += min(1.0, wind_speed / 20.0) * 0.2
|
|
|
|
# 温度影响(极端温度)
|
|
temperature = conditions.get('temperature', 20.0)
|
|
if temperature > 35 or temperature < 0:
|
|
severity += 0.2
|
|
|
|
# 能见度影响
|
|
visibility = conditions.get('visibility', 10000.0)
|
|
severity += (1.0 - min(1.0, visibility / 10000.0)) * 0.3
|
|
|
|
return min(1.0, severity)
|
|
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"天气严重程度计算失败: {e}")
|
|
return 0.0
|
|
|
|
def interpolate_values(self, start_value: float, end_value: float, progress: float) -> float:
|
|
"""
|
|
插值计算
|
|
|
|
Args:
|
|
start_value: 起始值
|
|
end_value: 结束值
|
|
progress: 进度(0-1)
|
|
|
|
Returns:
|
|
插值结果
|
|
"""
|
|
return start_value + (end_value - start_value) * progress
|
|
|
|
def clamp_value(self, value: float, min_value: float, max_value: float) -> float:
|
|
"""
|
|
限制值范围
|
|
|
|
Args:
|
|
value: 值
|
|
min_value: 最小值
|
|
max_value: 最大值
|
|
|
|
Returns:
|
|
限制后的值
|
|
"""
|
|
return max(min_value, min(max_value, value))
|
|
|
|
def random_range(self, min_value: float, max_value: float) -> float:
|
|
"""
|
|
生成随机范围值
|
|
|
|
Args:
|
|
min_value: 最小值
|
|
max_value: 最大值
|
|
|
|
Returns:
|
|
随机值
|
|
"""
|
|
return random.uniform(min_value, max_value)
|
|
|
|
def normalize_vector(self, vector: List[float]) -> List[float]:
|
|
"""
|
|
归一化向量
|
|
|
|
Args:
|
|
vector: 向量
|
|
|
|
Returns:
|
|
归一化后的向量
|
|
"""
|
|
try:
|
|
magnitude = math.sqrt(sum(x * x for x in vector))
|
|
if magnitude > 0:
|
|
return [x / magnitude for x in vector]
|
|
return [0.0] * len(vector)
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"向量归一化失败: {e}")
|
|
return vector
|
|
|
|
def distance_3d(self, pos1: Tuple[float, float, float], pos2: Tuple[float, float, float]) -> float:
|
|
"""
|
|
计算3D空间距离
|
|
|
|
Args:
|
|
pos1: 位置1
|
|
pos2: 位置2
|
|
|
|
Returns:
|
|
距离
|
|
"""
|
|
try:
|
|
dx = pos1[0] - pos2[0]
|
|
dy = pos1[1] - pos2[1]
|
|
dz = pos1[2] - pos2[2]
|
|
return math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"距离计算失败: {e}")
|
|
return 0.0
|
|
|
|
def angle_between_vectors(self, v1: List[float], v2: List[float]) -> float:
|
|
"""
|
|
计算两个向量之间的夹角
|
|
|
|
Args:
|
|
v1: 向量1
|
|
v2: 向量2
|
|
|
|
Returns:
|
|
夹角(弧度)
|
|
"""
|
|
try:
|
|
dot_product = sum(a * b for a, b in zip(v1, v2))
|
|
magnitude1 = math.sqrt(sum(a * a for a in v1))
|
|
magnitude2 = math.sqrt(sum(b * b for b in v2))
|
|
|
|
if magnitude1 > 0 and magnitude2 > 0:
|
|
cos_angle = dot_product / (magnitude1 * magnitude2)
|
|
cos_angle = max(-1.0, min(1.0, cos_angle)) # 限制在[-1, 1]范围内
|
|
return math.acos(cos_angle)
|
|
return 0.0
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"向量夹角计算失败: {e}")
|
|
return 0.0
|
|
|
|
def save_to_json(self, data: Dict[str, Any], filename: str) -> bool:
|
|
"""
|
|
保存数据到JSON文件
|
|
|
|
Args:
|
|
data: 数据字典
|
|
filename: 文件名
|
|
|
|
Returns:
|
|
是否保存成功
|
|
"""
|
|
try:
|
|
with open(filename, 'w', encoding='utf-8') as f:
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
return True
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"数据保存失败: {e}")
|
|
return False
|
|
|
|
def load_from_json(self, filename: str) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
从JSON文件加载数据
|
|
|
|
Args:
|
|
filename: 文件名
|
|
|
|
Returns:
|
|
数据字典或None
|
|
"""
|
|
try:
|
|
if os.path.exists(filename):
|
|
with open(filename, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
return None
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"数据加载失败: {e}")
|
|
return None
|
|
|
|
def get_cache(self, key: str) -> Any:
|
|
"""
|
|
获取缓存数据
|
|
|
|
Args:
|
|
key: 缓存键
|
|
|
|
Returns:
|
|
缓存数据或None
|
|
"""
|
|
try:
|
|
if key in self.data_cache:
|
|
timestamp, value = self.data_cache[key]
|
|
if time.time() - timestamp <= self.cache_expiry:
|
|
return value
|
|
else:
|
|
del self.data_cache[key]
|
|
return None
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"缓存获取失败: {e}")
|
|
return None
|
|
|
|
def set_cache(self, key: str, value: Any):
|
|
"""
|
|
设置缓存数据
|
|
|
|
Args:
|
|
key: 缓存键
|
|
value: 缓存值
|
|
"""
|
|
try:
|
|
self.data_cache[key] = (time.time(), value)
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"缓存设置失败: {e}")
|
|
|
|
def clear_cache(self):
|
|
"""清空缓存"""
|
|
try:
|
|
self.data_cache.clear()
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"缓存清空失败: {e}")
|
|
|
|
def get_performance_stats(self) -> Dict[str, Any]:
|
|
"""
|
|
获取性能统计信息
|
|
|
|
Returns:
|
|
性能统计信息字典
|
|
"""
|
|
try:
|
|
stats = self.performance_stats.copy()
|
|
|
|
# 计算平均执行时间
|
|
average_times = {}
|
|
for func_name, total_time in stats['execution_times'].items():
|
|
calls = stats['function_calls'][func_name]
|
|
if calls > 0:
|
|
average_times[func_name] = total_time / calls
|
|
|
|
stats['average_times'] = average_times
|
|
return stats
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"性能统计获取失败: {e}")
|
|
return {}
|
|
|
|
def reset_performance_stats(self):
|
|
"""重置性能统计信息"""
|
|
try:
|
|
self.performance_stats = {
|
|
'function_calls': {},
|
|
'execution_times': {},
|
|
'memory_usage': 0.0
|
|
}
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"性能统计重置失败: {e}")
|
|
|
|
def format_time(self, seconds: float) -> str:
|
|
"""
|
|
格式化时间
|
|
|
|
Args:
|
|
seconds: 秒数
|
|
|
|
Returns:
|
|
格式化的时间字符串
|
|
"""
|
|
try:
|
|
if seconds < 60:
|
|
return f"{seconds:.1f}秒"
|
|
elif seconds < 3600:
|
|
minutes = seconds / 60
|
|
return f"{minutes:.1f}分钟"
|
|
else:
|
|
hours = seconds / 3600
|
|
return f"{hours:.1f}小时"
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"时间格式化失败: {e}")
|
|
return f"{seconds:.1f}秒"
|
|
|
|
def generate_unique_id(self) -> str:
|
|
"""
|
|
生成唯一ID
|
|
|
|
Returns:
|
|
唯一ID字符串
|
|
"""
|
|
try:
|
|
timestamp = int(time.time() * 1000000) # 微秒时间戳
|
|
random_part = random.randint(1000, 9999)
|
|
return f"{timestamp}_{random_part}"
|
|
except Exception as e:
|
|
self.log_message("ERROR", f"唯一ID生成失败: {e}")
|
|
return str(random.randint(10000000, 99999999)) |