#!/usr/bin/env python3
"""
GPS监测可视化 - 散点图 + 直方图（左右分布）
散点图：每天的GPS测量点相对于基准点的位移偏差
直方图：每个点的误差分布（每5mm一个区间）
"""

import json
import plotly
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import math
import os
import colorsys

AUTO_REFRESH_SECONDS = 600  # 10分钟

# 读取数据
with open('assets/data.json', 'r', encoding='utf-8') as f:
    data = json.load(f)

baseline = data['baseline']
base_lat = baseline['lat']
base_lng = baseline['lng']

# 获取所有.pos文件
pos_files = sorted([f for f in os.listdir('.') if f.endswith('.pos')])


def generate_distinct_colors(n):
    """按数量生成互不重复的可视化颜色。"""
    if n <= 0:
        return []
    result = []
    for i in range(n):
        hue = i / n
        saturation = 0.72
        value = 0.92
        r, g, b = colorsys.hsv_to_rgb(hue, saturation, value)
        result.append(f'rgb({int(r * 255)}, {int(g * 255)}, {int(b * 255)})')
    return result


colors = generate_distinct_colors(len(pos_files))


def calc_within_5mm_ratio(points_x, points_y):
    """计算点落在半径 5mm 圆内的占比。"""
    total = len(points_x)
    if total == 0:
        return 0.0
    within = sum(1 for x, y in zip(points_x, points_y) if (x * x + y * y) <= 25)
    return within * 100.0 / total

# 创建画布 - 1行2列（左右分布）
fig = make_subplots(
    rows=1, cols=2,  # 1行2列 → 左右布局
    subplot_titles=('GPS位移偏差散点图', 'GPS位移密度热图'),
    column_widths=[0.42, 0.42],
    horizontal_spacing=0.22  # 缩小两图间距
)

# ========== 散点图（左边）==========
all_points_x = []
all_points_y = []
daily_heatmap_data = []
scatter_trace_indices = []

for day_idx, pos_file in enumerate(pos_files): # 遍历每一天的 .pos 文件
    points_x = []
    points_y = []

    with open(pos_file, 'r', errors='ignore') as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith('%'):
                continue
            parts = line.split()
            if len(parts) < 5:
                continue
            try:
                lat = float(parts[2])
                lng = float(parts[3])
                Q = int(parts[5])
                if Q == 1:
                    dy = (lat - base_lat) * 111320 * 1000
                    dx = (lng - base_lng) * 96400 * 1000
                    points_x.append(dx)
                    points_y.append(dy)
                    all_points_x.append(dx)
                    all_points_y.append(dy)
            except:
                continue

    date = pos_file.split('_')[3]
    date_formatted = f"{date[:4]}/{date[4:6]}/{date[6:]}" # 提取日期，转化为年月日

    fig.add_trace(go.Scatter(
        x=points_x,
        y=points_y,
        mode='markers',
        marker=dict(size=4, color=colors[day_idx % len(colors)], opacity=0.6),
        name=f'散点 {date_formatted} ({len(points_x)}点)',
        hovertemplate=f'{date_formatted}<br>X: %{{x:.2f}} mm<br>Y: %{{y:.2f}} mm<extra></extra>', # 鼠标悬浮内容
        showlegend=True,
        legendrank=10 + day_idx
    ), row=1, col=1)  # 散点图 → 第1行第1列（左边）
    scatter_trace_indices.append(len(fig.data) - 1)

    daily_heatmap_data.append({
        'date_formatted': date_formatted,
        'x': points_x,
        'y': points_y
    })

# 添加5mm精度圆
theta = [i * 0.1 for i in range(0, 629)]
circle_x = [5 * math.cos(t) for t in theta]
circle_y = [5 * math.sin(t) for t in theta]
fig.add_trace(go.Scatter(
    x=circle_x, y=circle_y,
    mode='lines',
    line=dict(color='red', width=2, dash='dash'),
    name='5mm精度圆',
    legendrank=200
), row=1, col=1)
circle_trace_index = len(fig.data) - 1

# 基准点
fig.add_trace(go.Scatter(
    x=[0], y=[0],
    mode='markers+text',
    marker=dict(size=15, color='yellow', symbol='star'),
    text='基准点', textposition='top center',
    name='基准点',
    legendrank=201
), row=1, col=1)
baseline_trace_index = len(fig.data) - 1

# ========== 密度热图（右边）==========
heatmap_trace_indices = []

# 默认先显示全部日期合并热图
fig.add_trace(go.Histogram2d(
    x=all_points_x,
    y=all_points_y,
    xbins=dict(start=-50, end=50, size=5),
    ybins=dict(start=-50, end=50, size=5),
    colorscale='Viridis',  # 颜色渐变
    colorbar=dict(title='点数'),
    hovertemplate='X: %{x:.2f} mm<br>Y: %{y:.2f} mm<br>点数: %{z}<extra></extra>', # 鼠标悬浮显示
    showscale=True,
    showlegend=False,
    visible=True
), row=1, col=2)
heatmap_trace_indices.append(len(fig.data) - 1)

# 每个日期单独一张热图，默认隐藏，通过下拉菜单切换
for day_data in daily_heatmap_data:
    fig.add_trace(go.Histogram2d(
        x=day_data['x'],
        y=day_data['y'],
        xbins=dict(start=-50, end=50, size=5),
        ybins=dict(start=-50, end=50, size=5),
        colorscale='Viridis',
        colorbar=dict(title='点数'),
        hovertemplate='X: %{x:.2f} mm<br>Y: %{y:.2f} mm<br>点数: %{z}<extra></extra>',
        showscale=True,
        showlegend=False,
        visible=False
    ), row=1, col=2)
    heatmap_trace_indices.append(len(fig.data) - 1)

# 右图叠加 5mm 精度圆
fig.add_trace(go.Scatter(
    x=circle_x, y=circle_y,
    mode='lines',
    line=dict(color='red', width=2, dash='dash'),
    showlegend=False,
    hoverinfo='skip'
), row=1, col=2)
right_circle_trace_index = len(fig.data) - 1

# 生成“按日期联动切换散点图+热图”的可见性配置
base_visibility = [False] * len(fig.data)
base_visibility[circle_trace_index] = True # 显示圆
base_visibility[baseline_trace_index] = True # 显示基准点
base_visibility[right_circle_trace_index] = True # 右图显示5mm圆

date_labels = ['全部日期'] + [item['date_formatted'] for item in daily_heatmap_data]
ratios = [calc_within_5mm_ratio(all_points_x, all_points_y)]
ratios.extend([calc_within_5mm_ratio(item['x'], item['y']) for item in daily_heatmap_data])

buttons = []
for i, label in enumerate(date_labels):
    visible = base_visibility.copy()
    if i == 0:
        for idx in scatter_trace_indices:
            visible[idx] = True
    else:
        visible[scatter_trace_indices[i - 1]] = True
    visible[heatmap_trace_indices[i]] = True
    buttons.append(dict(
        label=label,
        method='update',
        args=[
            {'visible': visible},
            {'title': f'GPS监测可视化 - 热图: {label} | 5mm内点占比: {ratios[i]:.2f}%'}
        ]
    ))

# 设置布局
fig.update_layout(
    title=f'GPS监测可视化 - 热图: 全部日期 | 5mm内点占比: {ratios[0]:.2f}%',
    width=1800, # 增加画布宽度，给中间图例和右图留出空间
    height=700, # 高度降低
    showlegend=True,
    legend=dict(
        yanchor="middle", y=0.5,
        xanchor="center", x=0.5,  # 图例放在两图中间内部
        bgcolor="rgba(255,255,255,0.75)"
    ),
    updatemenus=[
        dict(
            type='dropdown',
            direction='down',
            x=0.78,
            y=1.14,
            xanchor='left',
            yanchor='top',
            showactive=True,
            buttons=buttons
        )
    ]
)

# 散点图坐标范围
fig.update_xaxes(range=[-50, 50], row=1, col=1)
fig.update_yaxes(range=[-50, 50], row=1, col=1)
fig.update_xaxes(range=[-50, 50], row=1, col=2)
fig.update_yaxes(range=[-50, 50], row=1, col=2)

# 两个图的横坐标单位
fig.update_xaxes(title_text='位移偏差 (mm)', row=1, col=1)
fig.update_xaxes(title_text='X位移偏差 (mm)', row=1, col=2)
fig.update_yaxes(title_text='Y位移偏差 (mm)', row=1, col=2)

# 保存
fig.write_html('gps_plot.html')

# 给页面注入自动刷新脚本（用于接收新数据后自动看到最新图）
refresh_script = f"""
<script>
  setTimeout(function() {{
    window.location.reload();
  }}, {AUTO_REFRESH_SECONDS * 1000});
</script>
"""
with open('gps_plot.html', 'r', encoding='utf-8') as f:
    html_content = f.read()

if 'window.location.reload()' not in html_content:
    html_content = html_content.replace('</body>', refresh_script + '\n</body>')
    with open('gps_plot.html', 'w', encoding='utf-8') as f:
        f.write(html_content)

print("可视化已保存到 gps_plot.html")
