Fetch the repository succeeded.
# -*- coding: utf-8 -*-
"""
天气数据采集与智能旅游决策系统
功能:当前天气(含湿度) / 7天预报 / 历史统计 / 温度图表 / 每日最佳推荐 / 综合出行指南 / 周边城市推荐 / 数据导出
覆盖城市:全国150+主要城市(含所有地级市)
"""
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import threading
import requests
import json
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib
matplotlib.use('TkAgg')
import csv
import os
import math
# ==================== 中文字体配置 ====================
import matplotlib.font_manager as fm
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC', 'Heiti TC']
plt.rcParams['axes.unicode_minus'] = False
# ==================== 微信推送配置(可选扩展) ====================
WEBHOOK_URL = "" # 替换为你的企业微信机器人 Webhook 地址
def send_wechat_message(webhook_url, content):
if not webhook_url:
return False
headers = {"Content-Type": "application/json"}
data = {"msgtype": "text", "text": {"content": content}}
try:
response = requests.post(webhook_url, headers=headers, data=json.dumps(data), timeout=10)
return response.status_code == 200
except:
return False
# ==================== 全国150+主要城市坐标 ====================
# 用于每日推荐的热门旅游城市
TOUR_CITIES = [
"北京", "上海", "广州", "深圳", "杭州", "成都", "重庆", "武汉", "南京", "西安",
"昆明", "三亚", "厦门", "青岛", "大连", "桂林", "张家界", "丽江", "拉萨", "乌鲁木齐",
"哈尔滨", "长春", "沈阳", "呼和浩特", "兰州", "西宁", "银川", "台北", "香港", "澳门",
"珠海", "佛山", "苏州", "无锡", "宁波", "温州", "泉州", "烟台", "威海", "扬州",
"大理", "西双版纳", "敦煌", "喀什", "林芝", "秦皇岛", "北海", "惠州", "洛阳", "开封"
]
# 所有城市(按省份分组,覆盖全国主要地级市)
ALL_CITIES = {
# ===== 直辖市 =====
"北京": {"lat": 39.9042, "lon": 116.4074, "province": "北京"},
"上海": {"lat": 31.2304, "lon": 121.4737, "province": "上海"},
"天津": {"lat": 39.0841, "lon": 117.2010, "province": "天津"},
"重庆": {"lat": 29.4316, "lon": 106.9123, "province": "重庆"},
# ===== 河北 =====
"石家庄": {"lat": 38.0423, "lon": 114.5149, "province": "河北"},
"唐山": {"lat": 39.6500, "lon": 118.1750, "province": "河北"},
"秦皇岛": {"lat": 39.9354, "lon": 119.5884, "province": "河北"},
"邯郸": {"lat": 36.6107, "lon": 114.4907, "province": "河北"},
"邢台": {"lat": 37.0695, "lon": 114.5611, "province": "河北"},
"保定": {"lat": 38.8677, "lon": 115.4850, "province": "河北"},
"张家口": {"lat": 40.7690, "lon": 114.8841, "province": "河北"},
"承德": {"lat": 40.9762, "lon": 117.9392, "province": "河北"},
# ===== 山西 =====
"太原": {"lat": 37.8706, "lon": 112.5489, "province": "山西"},
"大同": {"lat": 40.0768, "lon": 113.3001, "province": "山西"},
"阳泉": {"lat": 37.8576, "lon": 113.5833, "province": "山西"},
"长治": {"lat": 36.1941, "lon": 113.1160, "province": "山西"},
"晋城": {"lat": 35.4976, "lon": 112.8456, "province": "山西"},
"临汾": {"lat": 36.0880, "lon": 111.5190, "province": "山西"},
"运城": {"lat": 35.0269, "lon": 111.0029, "province": "山西"},
# ===== 内蒙古 =====
"呼和浩特": {"lat": 40.8424, "lon": 111.7492, "province": "内蒙古"},
"包头": {"lat": 40.6210, "lon": 109.8402, "province": "内蒙古"},
"乌海": {"lat": 39.6522, "lon": 106.7942, "province": "内蒙古"},
"赤峰": {"lat": 42.2671, "lon": 118.9580, "province": "内蒙古"},
"通辽": {"lat": 43.6523, "lon": 122.2649, "province": "内蒙古"},
"鄂尔多斯": {"lat": 39.6084, "lon": 109.7809, "province": "内蒙古"},
"呼伦贝尔": {"lat": 49.2118, "lon": 119.7658, "province": "内蒙古"},
# ===== 辽宁 =====
"沈阳": {"lat": 41.8057, "lon": 123.4315, "province": "辽宁"},
"大连": {"lat": 38.9140, "lon": 121.6147, "province": "辽宁"},
"鞍山": {"lat": 41.1078, "lon": 122.9956, "province": "辽宁"},
"抚顺": {"lat": 41.8681, "lon": 123.9258, "province": "辽宁"},
"本溪": {"lat": 41.2924, "lon": 123.7636, "province": "辽宁"},
"锦州": {"lat": 41.0966, "lon": 121.1312, "province": "辽宁"},
"丹东": {"lat": 40.0056, "lon": 124.3547, "province": "辽宁"},
"营口": {"lat": 40.6662, "lon": 122.2352, "province": "辽宁"},
"盘锦": {"lat": 41.1199, "lon": 122.0708, "province": "辽宁"},
# ===== 吉林 =====
"长春": {"lat": 43.8171, "lon": 125.3235, "province": "吉林"},
"吉林市": {"lat": 43.8378, "lon": 126.5495, "province": "吉林"},
"四平": {"lat": 43.1682, "lon": 124.3700, "province": "吉林"},
"辽源": {"lat": 42.9027, "lon": 125.1442, "province": "吉林"},
"通化": {"lat": 41.7291, "lon": 125.9369, "province": "吉林"},
"白山": {"lat": 41.9400, "lon": 126.4250, "province": "吉林"},
"松原": {"lat": 45.1411, "lon": 124.8232, "province": "吉林"},
"延边": {"lat": 42.8916, "lon": 129.5087, "province": "吉林"},
# ===== 黑龙江 =====
"哈尔滨": {"lat": 45.8038, "lon": 126.5350, "province": "黑龙江"},
"齐齐哈尔": {"lat": 47.3543, "lon": 123.9182, "province": "黑龙江"},
"鸡西": {"lat": 45.2951, "lon": 130.9691, "province": "黑龙江"},
"鹤岗": {"lat": 47.3499, "lon": 130.2900, "province": "黑龙江"},
"双鸭山": {"lat": 46.6466, "lon": 131.1540, "province": "黑龙江"},
"大庆": {"lat": 46.5907, "lon": 125.1122, "province": "黑龙江"},
"伊春": {"lat": 47.7266, "lon": 128.8420, "province": "黑龙江"},
"佳木斯": {"lat": 46.7995, "lon": 130.3155, "province": "黑龙江"},
"牡丹江": {"lat": 44.5533, "lon": 129.6195, "province": "黑龙江"},
"黑河": {"lat": 50.2403, "lon": 127.5288, "province": "黑龙江"},
"绥化": {"lat": 46.6522, "lon": 126.9700, "province": "黑龙江"},
# ===== 江苏 =====
"南京": {"lat": 32.0603, "lon": 118.7969, "province": "江苏"},
"苏州": {"lat": 31.2990, "lon": 120.5853, "province": "江苏"},
"无锡": {"lat": 31.4912, "lon": 120.3119, "province": "江苏"},
"常州": {"lat": 31.8107, "lon": 119.9741, "province": "江苏"},
"南通": {"lat": 32.0145, "lon": 120.8738, "province": "江苏"},
"徐州": {"lat": 34.2057, "lon": 117.2836, "province": "江苏"},
"扬州": {"lat": 32.3932, "lon": 119.4210, "province": "江苏"},
"镇江": {"lat": 32.2000, "lon": 119.4550, "province": "江苏"},
"泰州": {"lat": 32.4555, "lon": 119.9200, "province": "江苏"},
"盐城": {"lat": 33.3781, "lon": 120.1630, "province": "江苏"},
# ===== 浙江 =====
"杭州": {"lat": 30.2741, "lon": 120.1551, "province": "浙江"},
"宁波": {"lat": 29.8683, "lon": 121.5440, "province": "浙江"},
"温州": {"lat": 28.0000, "lon": 120.7000, "province": "浙江"},
"嘉兴": {"lat": 30.7460, "lon": 120.7580, "province": "浙江"},
"湖州": {"lat": 30.8940, "lon": 120.1020, "province": "浙江"},
"绍兴": {"lat": 30.0300, "lon": 120.5800, "province": "浙江"},
"金华": {"lat": 29.1030, "lon": 119.6470, "province": "浙江"},
"衢州": {"lat": 28.9360, "lon": 118.8760, "province": "浙江"},
"舟山": {"lat": 29.9900, "lon": 122.2050, "province": "浙江"},
"台州": {"lat": 28.6560, "lon": 121.4210, "province": "浙江"},
"丽水": {"lat": 28.4670, "lon": 119.9230, "province": "浙江"},
# ===== 安徽 =====
"合肥": {"lat": 31.8206, "lon": 117.2272, "province": "安徽"},
"芜湖": {"lat": 31.3527, "lon": 118.3760, "province": "安徽"},
"蚌埠": {"lat": 32.9394, "lon": 117.3600, "province": "安徽"},
"淮南": {"lat": 32.6260, "lon": 116.9990, "province": "安徽"},
"马鞍山": {"lat": 31.6700, "lon": 118.5000, "province": "安徽"},
"淮北": {"lat": 33.9600, "lon": 116.7980, "province": "安徽"},
"铜陵": {"lat": 30.9440, "lon": 117.8120, "province": "安徽"},
"安庆": {"lat": 30.5250, "lon": 117.0500, "province": "安徽"},
"黄山": {"lat": 29.7200, "lon": 118.3100, "province": "安徽"},
"滁州": {"lat": 32.3200, "lon": 118.3300, "province": "安徽"},
"阜阳": {"lat": 32.8900, "lon": 115.8100, "province": "安徽"},
# ===== 福建 =====
"福州": {"lat": 26.0745, "lon": 119.2965, "province": "福建"},
"厦门": {"lat": 24.4798, "lon": 118.0894, "province": "福建"},
"莆田": {"lat": 25.4540, "lon": 119.0070, "province": "福建"},
"泉州": {"lat": 24.8740, "lon": 118.6760, "province": "福建"},
"漳州": {"lat": 24.5090, "lon": 117.6470, "province": "福建"},
"龙岩": {"lat": 25.0750, "lon": 117.0170, "province": "福建"},
"宁德": {"lat": 26.6660, "lon": 119.5470, "province": "福建"},
"三明": {"lat": 26.2650, "lon": 117.6390, "province": "福建"},
"南平": {"lat": 26.6400, "lon": 118.1770, "province": "福建"},
# ===== 江西 =====
"南昌": {"lat": 28.6820, "lon": 115.8579, "province": "江西"},
"九江": {"lat": 29.7000, "lon": 116.0000, "province": "江西"},
"景德镇": {"lat": 29.2670, "lon": 117.1830, "province": "江西"},
"萍乡": {"lat": 27.6170, "lon": 113.8500, "province": "江西"},
"赣州": {"lat": 25.8300, "lon": 114.9300, "province": "江西"},
"吉安": {"lat": 27.1167, "lon": 114.9833, "province": "江西"},
"宜春": {"lat": 27.8000, "lon": 114.3830, "province": "江西"},
"上饶": {"lat": 28.4530, "lon": 117.9670, "province": "江西"},
# ===== 山东 =====
"济南": {"lat": 36.6512, "lon": 117.1201, "province": "山东"},
"青岛": {"lat": 36.0671, "lon": 120.3826, "province": "山东"},
"淄博": {"lat": 36.8130, "lon": 118.0550, "province": "山东"},
"枣庄": {"lat": 34.8100, "lon": 117.3200, "province": "山东"},
"东营": {"lat": 37.4300, "lon": 118.6700, "province": "山东"},
"烟台": {"lat": 37.4635, "lon": 121.4479, "province": "山东"},
"潍坊": {"lat": 36.7050, "lon": 119.1620, "province": "山东"},
"济宁": {"lat": 35.4000, "lon": 116.5800, "province": "山东"},
"泰安": {"lat": 36.2000, "lon": 117.0800, "province": "山东"},
"威海": {"lat": 37.5130, "lon": 122.1220, "province": "山东"},
"临沂": {"lat": 35.1050, "lon": 118.3500, "province": "山东"},
"德州": {"lat": 37.4500, "lon": 116.3000, "province": "山东"},
"聊城": {"lat": 36.4500, "lon": 116.0000, "province": "山东"},
"滨州": {"lat": 37.3830, "lon": 118.0330, "province": "山东"},
"菏泽": {"lat": 35.2400, "lon": 115.4300, "province": "山东"},
# ===== 河南 =====
"郑州": {"lat": 34.7566, "lon": 113.6253, "province": "河南"},
"开封": {"lat": 34.7973, "lon": 114.3073, "province": "河南"},
"洛阳": {"lat": 34.6181, "lon": 112.4540, "province": "河南"},
"平顶山": {"lat": 33.7350, "lon": 113.2930, "province": "河南"},
"安阳": {"lat": 36.1030, "lon": 114.3520, "province": "河南"},
"鹤壁": {"lat": 35.9000, "lon": 114.2000, "province": "河南"},
"新乡": {"lat": 35.3030, "lon": 113.9270, "province": "河南"},
"焦作": {"lat": 35.2400, "lon": 113.2400, "province": "河南"},
"濮阳": {"lat": 35.7600, "lon": 115.0700, "province": "河南"},
"许昌": {"lat": 34.0300, "lon": 113.8500, "province": "河南"},
"漯河": {"lat": 33.5800, "lon": 114.0200, "province": "河南"},
"三门峡": {"lat": 34.7700, "lon": 111.2000, "province": "河南"},
"南阳": {"lat": 32.9900, "lon": 112.5300, "province": "河南"},
"商丘": {"lat": 34.4200, "lon": 115.6500, "province": "河南"},
"信阳": {"lat": 32.1200, "lon": 114.0700, "province": "河南"},
"周口": {"lat": 33.6200, "lon": 114.6500, "province": "河南"},
"驻马店": {"lat": 32.9800, "lon": 114.0200, "province": "河南"},
# ===== 湖北 =====
"武汉": {"lat": 30.5928, "lon": 114.3055, "province": "湖北"},
"黄石": {"lat": 30.2200, "lon": 115.1000, "province": "湖北"},
"十堰": {"lat": 32.6300, "lon": 110.7800, "province": "湖北"},
"宜昌": {"lat": 30.6900, "lon": 111.2900, "province": "湖北"},
"襄阳": {"lat": 32.0400, "lon": 112.1400, "province": "湖北"},
"鄂州": {"lat": 30.4000, "lon": 114.8900, "province": "湖北"},
"荆门": {"lat": 31.0300, "lon": 112.2000, "province": "湖北"},
"孝感": {"lat": 30.9200, "lon": 113.9200, "province": "湖北"},
"荆州": {"lat": 30.3400, "lon": 112.2400, "province": "湖北"},
"黄冈": {"lat": 30.4500, "lon": 114.8800, "province": "湖北"},
"咸宁": {"lat": 29.8300, "lon": 114.3200, "province": "湖北"},
"随州": {"lat": 31.6900, "lon": 113.3700, "province": "湖北"},
"恩施": {"lat": 30.2900, "lon": 109.4800, "province": "湖北"},
# ===== 湖南 =====
"长沙": {"lat": 28.2282, "lon": 112.9388, "province": "湖南"},
"株洲": {"lat": 27.8300, "lon": 113.1500, "province": "湖南"},
"湘潭": {"lat": 27.8300, "lon": 112.9000, "province": "湖南"},
"衡阳": {"lat": 26.8900, "lon": 112.6100, "province": "湖南"},
"邵阳": {"lat": 27.2400, "lon": 111.4700, "province": "湖南"},
"岳阳": {"lat": 29.3700, "lon": 113.1000, "province": "湖南"},
"常德": {"lat": 29.0500, "lon": 111.6800, "province": "湖南"},
"张家界": {"lat": 29.1160, "lon": 110.4780, "province": "湖南"},
"益阳": {"lat": 28.5700, "lon": 112.3300, "province": "湖南"},
"郴州": {"lat": 25.7700, "lon": 113.0300, "province": "湖南"},
"永州": {"lat": 26.4300, "lon": 111.6200, "province": "湖南"},
"怀化": {"lat": 27.5700, "lon": 110.0000, "province": "湖南"},
"娄底": {"lat": 27.7300, "lon": 112.0000, "province": "湖南"},
# ===== 广东 =====
"广州": {"lat": 23.1291, "lon": 113.2644, "province": "广东"},
"韶关": {"lat": 24.8000, "lon": 113.6000, "province": "广东"},
"深圳": {"lat": 22.5431, "lon": 114.0579, "province": "广东"},
"珠海": {"lat": 22.2700, "lon": 113.5770, "province": "广东"},
"汕头": {"lat": 23.3700, "lon": 116.6800, "province": "广东"},
"佛山": {"lat": 23.0300, "lon": 113.1200, "province": "广东"},
"江门": {"lat": 22.5800, "lon": 113.0800, "province": "广东"},
"湛江": {"lat": 21.2700, "lon": 110.3500, "province": "广东"},
"茂名": {"lat": 21.6600, "lon": 110.9200, "province": "广东"},
"肇庆": {"lat": 23.0500, "lon": 112.4500, "province": "广东"},
"惠州": {"lat": 23.1100, "lon": 114.4100, "province": "广东"},
"梅州": {"lat": 24.2800, "lon": 116.1200, "province": "广东"},
"汕尾": {"lat": 22.7800, "lon": 115.3700, "province": "广东"},
"河源": {"lat": 23.7300, "lon": 114.6800, "province": "广东"},
"阳江": {"lat": 21.8500, "lon": 111.9800, "province": "广东"},
"清远": {"lat": 23.6800, "lon": 113.0200, "province": "广东"},
"东莞": {"lat": 23.0200, "lon": 113.7500, "province": "广东"},
"中山": {"lat": 22.5200, "lon": 113.3800, "province": "广东"},
"潮州": {"lat": 23.6700, "lon": 116.6300, "province": "广东"},
"揭阳": {"lat": 23.5500, "lon": 116.3700, "province": "广东"},
"云浮": {"lat": 22.9300, "lon": 112.0300, "province": "广东"},
# ===== 广西 =====
"南宁": {"lat": 22.8170, "lon": 108.3669, "province": "广西"},
"柳州": {"lat": 24.3100, "lon": 109.4000, "province": "广西"},
"桂林": {"lat": 25.2736, "lon": 110.2900, "province": "广西"},
"梧州": {"lat": 23.4800, "lon": 111.3200, "province": "广西"},
"北海": {"lat": 21.4800, "lon": 109.1200, "province": "广西"},
"防城港": {"lat": 21.6800, "lon": 108.3500, "province": "广西"},
"钦州": {"lat": 21.9500, "lon": 108.6200, "province": "广西"},
"贵港": {"lat": 23.1000, "lon": 109.6000, "province": "广西"},
"玉林": {"lat": 22.6300, "lon": 110.1500, "province": "广西"},
"百色": {"lat": 23.9000, "lon": 106.6200, "province": "广西"},
"贺州": {"lat": 24.4000, "lon": 111.5500, "province": "广西"},
"河池": {"lat": 24.7000, "lon": 108.0700, "province": "广西"},
"来宾": {"lat": 23.7300, "lon": 109.2300, "province": "广西"},
"崇左": {"lat": 22.4000, "lon": 107.3700, "province": "广西"},
# ===== 海南 =====
"海口": {"lat": 20.0440, "lon": 110.1999, "province": "海南"},
"三亚": {"lat": 18.2528, "lon": 109.5119, "province": "海南"},
"儋州": {"lat": 19.5200, "lon": 109.5800, "province": "海南"},
# ===== 四川 =====
"成都": {"lat": 30.5728, "lon": 104.0668, "province": "四川"},
"自贡": {"lat": 29.3500, "lon": 104.7700, "province": "四川"},
"攀枝花": {"lat": 26.5800, "lon": 101.7200, "province": "四川"},
"泸州": {"lat": 28.8800, "lon": 105.4300, "province": "四川"},
"德阳": {"lat": 31.1300, "lon": 104.3800, "province": "四川"},
"绵阳": {"lat": 31.4660, "lon": 104.6790, "province": "四川"},
"广元": {"lat": 32.4300, "lon": 105.8200, "province": "四川"},
"遂宁": {"lat": 30.5300, "lon": 105.5700, "province": "四川"},
"内江": {"lat": 29.5800, "lon": 105.0500, "province": "四川"},
"乐山": {"lat": 29.6000, "lon": 103.7600, "province": "四川"},
"南充": {"lat": 30.7900, "lon": 106.1100, "province": "四川"},
"眉山": {"lat": 30.0500, "lon": 103.8300, "province": "四川"},
"宜宾": {"lat": 28.7500, "lon": 104.6400, "province": "四川"},
"广安": {"lat": 30.4600, "lon": 106.6400, "province": "四川"},
"达州": {"lat": 31.2100, "lon": 107.4700, "province": "四川"},
"雅安": {"lat": 30.0100, "lon": 103.0400, "province": "四川"},
"巴中": {"lat": 31.8600, "lon": 106.7500, "province": "四川"},
"资阳": {"lat": 30.1200, "lon": 104.6300, "province": "四川"},
"阿坝": {"lat": 31.9000, "lon": 102.2200, "province": "四川"},
"甘孜": {"lat": 30.0500, "lon": 101.9600, "province": "四川"},
"凉山": {"lat": 27.8900, "lon": 102.2600, "province": "四川"},
# ===== 贵州 =====
"贵阳": {"lat": 26.6477, "lon": 106.6302, "province": "贵州"},
"六盘水": {"lat": 26.5900, "lon": 104.8300, "province": "贵州"},
"遵义": {"lat": 27.7256, "lon": 106.9370, "province": "贵州"},
"安顺": {"lat": 26.2500, "lon": 105.9300, "province": "贵州"},
"毕节": {"lat": 27.3000, "lon": 105.2900, "province": "贵州"},
"铜仁": {"lat": 27.7300, "lon": 109.1900, "province": "贵州"},
"黔东南": {"lat": 26.5800, "lon": 107.9800, "province": "贵州"},
"黔南": {"lat": 26.2500, "lon": 107.5200, "province": "贵州"},
"黔西南": {"lat": 25.0900, "lon": 104.9000, "province": "贵州"},
# ===== 云南 =====
"昆明": {"lat": 25.0409, "lon": 102.7123, "province": "云南"},
"曲靖": {"lat": 25.5000, "lon": 103.7900, "province": "云南"},
"玉溪": {"lat": 24.3500, "lon": 102.5400, "province": "云南"},
"保山": {"lat": 25.1100, "lon": 99.1600, "province": "云南"},
"昭通": {"lat": 27.3400, "lon": 103.7100, "province": "云南"},
"丽江": {"lat": 26.8721, "lon": 100.2330, "province": "云南"},
"普洱": {"lat": 22.7700, "lon": 100.9700, "province": "云南"},
"临沧": {"lat": 23.8800, "lon": 100.0800, "province": "云南"},
"大理": {"lat": 25.6065, "lon": 100.2670, "province": "云南"},
"楚雄": {"lat": 25.0400, "lon": 101.5400, "province": "云南"},
"红河": {"lat": 23.3700, "lon": 103.3900, "province": "云南"},
"文山": {"lat": 23.4000, "lon": 104.2300, "province": "云南"},
"西双版纳": {"lat": 21.9900, "lon": 100.7900, "province": "云南"},
"德宏": {"lat": 24.4300, "lon": 98.5800, "province": "云南"},
"怒江": {"lat": 25.8500, "lon": 98.8500, "province": "云南"},
"迪庆": {"lat": 27.8200, "lon": 99.7000, "province": "云南"},
# ===== 西藏 =====
"拉萨": {"lat": 29.6500, "lon": 91.1000, "province": "西藏"},
"日喀则": {"lat": 29.2780, "lon": 88.8820, "province": "西藏"},
"昌都": {"lat": 31.1300, "lon": 97.1700, "province": "西藏"},
"林芝": {"lat": 29.6500, "lon": 94.3500, "province": "西藏"},
"山南": {"lat": 29.2300, "lon": 91.7700, "province": "西藏"},
"那曲": {"lat": 31.4700, "lon": 92.0500, "province": "西藏"},
"阿里": {"lat": 30.4000, "lon": 81.1500, "province": "西藏"},
# ===== 陕西 =====
"西安": {"lat": 34.3416, "lon": 108.9398, "province": "陕西"},
"铜川": {"lat": 34.9000, "lon": 108.9500, "province": "陕西"},
"宝鸡": {"lat": 34.3600, "lon": 107.1500, "province": "陕西"},
"咸阳": {"lat": 34.3300, "lon": 108.7000, "province": "陕西"},
"渭南": {"lat": 34.5000, "lon": 109.5000, "province": "陕西"},
"延安": {"lat": 36.5855, "lon": 109.4900, "province": "陕西"},
"汉中": {"lat": 33.0600, "lon": 107.0300, "province": "陕西"},
"榆林": {"lat": 38.2800, "lon": 109.7300, "province": "陕西"},
"安康": {"lat": 32.6800, "lon": 109.0200, "province": "陕西"},
"商洛": {"lat": 33.8700, "lon": 109.9300, "province": "陕西"},
# ===== 甘肃 =====
"兰州": {"lat": 36.0611, "lon": 103.8343, "province": "甘肃"},
"嘉峪关": {"lat": 39.7900, "lon": 98.2800, "province": "甘肃"},
"金昌": {"lat": 38.5200, "lon": 102.1800, "province": "甘肃"},
"白银": {"lat": 36.5400, "lon": 104.1800, "province": "甘肃"},
"天水": {"lat": 34.5800, "lon": 105.7200, "province": "甘肃"},
"武威": {"lat": 37.9300, "lon": 102.6300, "province": "甘肃"},
"张掖": {"lat": 38.9300, "lon": 100.4500, "province": "甘肃"},
"平凉": {"lat": 35.5400, "lon": 106.6800, "province": "甘肃"},
"酒泉": {"lat": 39.7300, "lon": 98.5000, "province": "甘肃"},
"庆阳": {"lat": 35.7300, "lon": 107.6300, "province": "甘肃"},
"定西": {"lat": 35.5800, "lon": 104.6200, "province": "甘肃"},
"陇南": {"lat": 33.4000, "lon": 104.9200, "province": "甘肃"},
"临夏": {"lat": 35.6000, "lon": 103.2000, "province": "甘肃"},
"甘南": {"lat": 34.9800, "lon": 102.9000, "province": "甘肃"},
# ===== 青海 =====
"西宁": {"lat": 36.6171, "lon": 101.7785, "province": "青海"},
"海东": {"lat": 36.5000, "lon": 102.1000, "province": "青海"},
"海北": {"lat": 36.9500, "lon": 100.9000, "province": "青海"},
"黄南": {"lat": 35.5200, "lon": 102.0200, "province": "青海"},
"海南": {"lat": 36.2800, "lon": 100.6200, "province": "青海"},
"果洛": {"lat": 34.4700, "lon": 100.2300, "province": "青海"},
"玉树": {"lat": 33.0000, "lon": 97.0000, "province": "青海"},
"海西": {"lat": 37.3700, "lon": 97.3700, "province": "青海"},
"格尔木": {"lat": 36.4060, "lon": 94.9010, "province": "青海"},
# ===== 宁夏 =====
"银川": {"lat": 38.4710, "lon": 106.2580, "province": "宁夏"},
"石嘴山": {"lat": 39.0140, "lon": 106.3840, "province": "宁夏"},
"吴忠": {"lat": 38.0000, "lon": 106.2000, "province": "宁夏"},
"固原": {"lat": 36.0000, "lon": 106.2800, "province": "宁夏"},
"中卫": {"lat": 37.5100, "lon": 105.1800, "province": "宁夏"},
# ===== 新疆 =====
"乌鲁木齐": {"lat": 43.8266, "lon": 87.6168, "province": "新疆"},
"克拉玛依": {"lat": 45.5900, "lon": 84.8700, "province": "新疆"},
"吐鲁番": {"lat": 42.9500, "lon": 89.1800, "province": "新疆"},
"哈密": {"lat": 42.8200, "lon": 93.5200, "province": "新疆"},
"昌吉": {"lat": 44.0200, "lon": 87.2700, "province": "新疆"},
"博州": {"lat": 44.9000, "lon": 82.0700, "province": "新疆"},
"巴州": {"lat": 41.7300, "lon": 86.1500, "province": "新疆"},
"阿克苏": {"lat": 41.1700, "lon": 80.2600, "province": "新疆"},
"克州": {"lat": 39.7200, "lon": 76.1000, "province": "新疆"},
"喀什": {"lat": 39.4670, "lon": 75.9890, "province": "新疆"},
"和田": {"lat": 37.1200, "lon": 79.9200, "province": "新疆"},
"伊犁": {"lat": 43.9200, "lon": 81.3200, "province": "新疆"},
"塔城": {"lat": 46.7300, "lon": 83.0000, "province": "新疆"},
"阿勒泰": {"lat": 47.8300, "lon": 88.1300, "province": "新疆"},
"石河子": {"lat": 44.3000, "lon": 86.0300, "province": "新疆"},
# ===== 台湾 =====
"台北": {"lat": 25.0330, "lon": 121.5654, "province": "台湾"},
"高雄": {"lat": 22.6273, "lon": 120.3014, "province": "台湾"},
"台中": {"lat": 24.1470, "lon": 120.6730, "province": "台湾"},
"台南": {"lat": 22.9900, "lon": 120.2100, "province": "台湾"},
"基隆": {"lat": 25.1200, "lon": 121.7500, "province": "台湾"},
"新竹": {"lat": 24.8000, "lon": 120.9800, "province": "台湾"},
"嘉义": {"lat": 23.4800, "lon": 120.4500, "province": "台湾"},
# ===== 香港澳门 =====
"香港": {"lat": 22.3193, "lon": 114.1694, "province": "香港"},
"澳门": {"lat": 22.1987, "lon": 113.5439, "province": "澳门"},
}
# 省份分组
PROVINCES = sorted(set(c['province'] for c in ALL_CITIES.values()))
CITY_BY_PROVINCE = {}
for name, info in ALL_CITIES.items():
p = info['province']
if p not in CITY_BY_PROVINCE:
CITY_BY_PROVINCE[p] = []
CITY_BY_PROVINCE[p].append(name)
# ==================== API 调用 ====================
class WeatherAPI:
@staticmethod
def get_current_weather(city_name):
if city_name not in ALL_CITIES:
return None
coords = ALL_CITIES[city_name]
url = f"https://api.open-meteo.com/v1/forecast?latitude={coords['lat']}&longitude={coords['lon']}¤t=temperature_2m,relative_humidity_2m,windspeed_10m,winddirection_10m&timezone=Asia/Shanghai"
try:
resp = requests.get(url, timeout=10)
data = resp.json()
current = data.get('current', {})
if not current:
return None
return {
"城市": city_name,
"温度": current.get('temperature_2m', 'N/A'),
"湿度": current.get('relative_humidity_2m', 'N/A'),
"风速": current.get('windspeed_10m', 'N/A'),
"风向": current.get('winddirection_10m', 'N/A'),
"时间": current.get('time', 'N/A')
}
except Exception as e:
print(f"获取当前天气出错: {e}")
return None
@staticmethod
def get_forecast(city_name, days=7):
if city_name not in ALL_CITIES:
return None
coords = ALL_CITIES[city_name]
url = f"https://api.open-meteo.com/v1/forecast?latitude={coords['lat']}&longitude={coords['lon']}&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=Asia/Shanghai&forecast_days={days}"
try:
resp = requests.get(url, timeout=10)
data = resp.json()
daily = data.get('daily', {})
result = []
for i in range(len(daily.get('time', []))):
result.append({
"日期": daily['time'][i],
"最高温": daily['temperature_2m_max'][i],
"最低温": daily['temperature_2m_min'][i],
"天气代码": daily['weathercode'][i] if i < len(daily['weathercode']) else 0
})
return result
except:
return None
@staticmethod
def get_historical(city_name, days=30):
if city_name not in ALL_CITIES:
return None
coords = ALL_CITIES[city_name]
end_date = datetime.now().strftime('%Y-%m-%d')
start_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
url = f"https://archive-api.open-meteo.com/v1/archive?latitude={coords['lat']}&longitude={coords['lon']}&start_date={start_date}&end_date={end_date}&daily=temperature_2m_max,temperature_2m_min&timezone=Asia/Shanghai"
try:
resp = requests.get(url, timeout=10)
data = resp.json()
daily = data.get('daily', {})
result = []
for i in range(len(daily.get('time', []))):
result.append({
"日期": daily['time'][i],
"最高温": daily['temperature_2m_max'][i],
"最低温": daily['temperature_2m_min'][i]
})
return result
except:
return None
# ==================== 分析工具 ====================
def analyze_comfort(temperature, humidity=None):
try:
t = float(temperature)
if 18 <= t <= 25:
temp_score = 1.0
elif 15 <= t < 18:
temp_score = 0.7
elif 25 < t <= 30:
temp_score = 0.7
elif 10 <= t < 15:
temp_score = 0.4
elif 30 < t <= 35:
temp_score = 0.4
elif t < 10:
temp_score = 0.2
else:
temp_score = 0.2
if humidity is not None:
try:
h = float(humidity)
if 40 <= h <= 70:
hum_score = 1.0
elif 30 <= h < 40 or 70 < h <= 80:
hum_score = 0.6
elif h < 30 or h > 80:
hum_score = 0.3
else:
hum_score = 0.5
except:
hum_score = 0.5
else:
hum_score = 0.5
total_score = temp_score * 0.7 + hum_score * 0.3
if total_score >= 0.8:
label = "非常舒适"
elif total_score >= 0.6:
label = "舒适"
elif total_score >= 0.4:
label = "一般"
else:
label = "不舒适"
return label, total_score
except:
return "未知", 0.5
def get_comfort_score_from_forecast(high_temp, low_temp):
try:
avg_temp = (float(high_temp) + float(low_temp)) / 2
if 18 <= avg_temp <= 25:
return 1.0
elif 15 <= avg_temp < 18 or 25 < avg_temp <= 30:
return 0.7
elif 10 <= avg_temp < 15 or 30 < avg_temp <= 35:
return 0.4
else:
return 0.2
except:
return 0.5
# ==================== 距离计算与周边推荐 ====================
def calc_distance(lat1, lon1, lat2, lon2):
"""计算两个经纬度之间的直线距离(公里),使用Haversine公式"""
R = 6371 # 地球半径(公里)
dlat = math.radians(lat2 - lat1)
dlon = math.radians(lon2 - lon1)
a = math.sin(dlat/2)**2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon/2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
return R * c
def get_nearby_cities(base_city, radius_km=300):
"""获取距离基准城市一定半径内的所有城市"""
if base_city not in ALL_CITIES:
return []
base = ALL_CITIES[base_city]
nearby = []
for city, info in ALL_CITIES.items():
if city == base_city:
continue
dist = calc_distance(base['lat'], base['lon'], info['lat'], info['lon'])
if dist <= radius_km:
nearby.append({"城市": city, "距离": round(dist, 1)})
return sorted(nearby, key=lambda x: x['距离'])
def recommend_nearby_cities(base_city, radius_km=300):
"""推荐周边舒适度最高的Top 5城市"""
nearby = get_nearby_cities(base_city, radius_km)
if not nearby:
return []
results = []
for item in nearby:
city = item['城市']
current = WeatherAPI.get_current_weather(city)
if current:
comfort_label, score = analyze_comfort(current['温度'], current['湿度'])
results.append({
"城市": city,
"距离": item['距离'],
"温度": current['温度'],
"湿度": current['湿度'],
"风速": current['风速'],
"舒适标签": comfort_label,
"舒适分": score
})
results.sort(key=lambda x: x['舒适分'], reverse=True)
return results[:5]
# ==================== 综合出行指南 ====================
def generate_comprehensive_guide(city, current, forecast, history):
if not current:
return "数据不足,请先获取天气数据"
temp = float(current['温度'])
humidity = float(current['湿度']) if current['湿度'] != 'N/A' else None
wind = float(current['风速'])
# 出行指数
travel_score = 10
if temp < 0 or temp > 35:
travel_score -= 4
elif temp < 5 or temp > 30:
travel_score -= 2
if humidity is not None:
if humidity < 30 or humidity > 80:
travel_score -= 2
elif humidity < 40 or humidity > 70:
travel_score -= 1
if wind > 20:
travel_score -= 2
elif wind > 10:
travel_score -= 1
travel_score = max(0, min(10, travel_score))
travel_label = ["❌ 不建议出行", "⚠️ 谨慎出行", "✅ 适宜出行", "🌟 极佳出行"][min(3, travel_score // 3)]
# 穿衣建议
if temp >= 30:
dress = "👕 短袖+短裤,注意防晒"
elif temp >= 25:
dress = "👕 短袖+薄外套"
elif temp >= 18:
dress = "👔 长袖+薄外套"
elif temp >= 10:
dress = "🧥 毛衣+厚外套"
else:
dress = "🧥 羽绒服+围巾手套"
if humidity is not None and humidity > 70:
dress += ",湿度较大,建议携带雨具"
# 带伞提醒
rain_today = False
if forecast and len(forecast) > 0:
rain_today = forecast[0].get('天气代码', 0) in [51, 61, 71, 80, 81, 82]
umbrella = "☔ 今天有雨,记得带伞!" if rain_today else "☀️ 今天无雨,不用带伞"
# 异常预警
alerts = []
if temp >= 35:
alerts.append("🔥 高温预警!注意防暑降温")
elif temp <= 0:
alerts.append("❄️ 低温预警!注意防寒保暖")
if humidity is not None:
if humidity >= 80:
alerts.append("💧 湿度过高!体感闷热,注意防潮")
elif humidity <= 30:
alerts.append("💧 湿度过低!注意补水保湿")
if history and len(history) >= 3:
recent_temps = [h['最高温'] for h in history[-3:] if h['最高温'] is not None]
if recent_temps:
avg_temp = sum(recent_temps) / len(recent_temps)
diff = abs(temp - avg_temp)
if diff >= 10:
alerts.append(f"🌡️ 温度突变!今日比近3天均值 {'高' if temp > avg_temp else '低'} {diff:.1f}°C")
report = f"""
📋 {city} 综合出行指南
━━━━━━━━━━━━━━━━━━━━━━━━━━
🌡️ 温度:{temp}°C 💧 湿度:{humidity if humidity is not None else 'N/A'}%
💨 风速:{wind} km/h
🚶 出行指数:{travel_score}/10 {travel_label}
👔 穿衣建议:{dress}
{umbrella}
━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ 异常预警:
"""
if alerts:
report += "\n".join([" " + a for a in alerts])
else:
report += " ✅ 当前无异常天气,放心出行!"
report += "\n━━━━━━━━━━━━━━━━━━━━━━━━━━"
report += f"\n🕐 更新时间:{current['时间']}"
return report
def export_weather_data(city, history):
if not history:
return None
filename = f"{city}_天气数据_{datetime.now().strftime('%Y%m%d')}.csv"
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(["日期", "最高温(°C)", "最低温(°C)"])
for h in history:
writer.writerow([h['日期'], h['最高温'], h['最低温']])
return filename
# ==================== 未来7天每日最佳城市推荐 ====================
def get_weekly_best_cities(city_list=TOUR_CITIES):
all_forecasts = {}
for city in city_list:
forecast = WeatherAPI.get_forecast(city, 7)
if forecast:
all_forecasts[city] = forecast
if not all_forecasts:
return []
best_cities = []
for day_idx in range(7):
best_city = None
best_score = -1
best_high = best_low = None
date_str = ""
for city, fore in all_forecasts.items():
if len(fore) <= day_idx:
continue
day_data = fore[day_idx]
date_str = day_data['日期']
high = day_data['最高温']
low = day_data['最低温']
score = get_comfort_score_from_forecast(high, low)
if score > best_score:
best_score = score
best_city = city
best_high = high
best_low = low
if best_city:
best_cities.append({
"日期": date_str,
"城市": best_city,
"最高温": best_high,
"最低温": best_low,
"舒适分": round(best_score, 2)
})
return best_cities
# ==================== GUI 主程序 ====================
class WeatherAnalyzer:
def __init__(self):
self.root = tk.Tk()
self.root.title("🌤️ 天气数据采集与智能旅游决策系统")
self.root.geometry("1300x850")
self.root.configure(bg="#F5F5F5")
self._setup_ui()
self.current_data = {"current": None, "forecast": None, "history": None}
def _setup_ui(self):
title_frame = tk.Frame(self.root, bg="#2C3E50", height=60)
title_frame.pack(fill=tk.X)
title_frame.pack_propagate(False)
tk.Label(title_frame, text="🌤️ 天气数据采集与智能旅游决策系统",
font=("微软雅黑", 18, "bold"), fg="white", bg="#2C3E50").pack(pady=15)
main_frame = tk.Frame(self.root, bg="#F5F5F5")
main_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=10)
control_frame = tk.LabelFrame(main_frame, text="📍 查询与控制")
control_frame.pack(fill=tk.X, pady=(0, 10))
# 第一行:省份 + 城市 + 基本功能
row1 = tk.Frame(control_frame, bg="#F5F5F5")
row1.pack(fill=tk.X, padx=10, pady=5)
tk.Label(row1, text="省份:", font=("微软雅黑", 10)).pack(side=tk.LEFT)
self.province_var = tk.StringVar(value="全部")
province_list = ["全部"] + PROVINCES
self.province_combo = ttk.Combobox(row1, textvariable=self.province_var, values=province_list, width=12)
self.province_combo.pack(side=tk.LEFT, padx=5)
self.province_combo.bind('<<ComboboxSelected>>', self.on_province_change)
tk.Label(row1, text="城市:", font=("微软雅黑", 10)).pack(side=tk.LEFT, padx=(10, 0))
self.city_var = tk.StringVar(value="北京")
self.city_combo = ttk.Combobox(row1, textvariable=self.city_var, values=list(ALL_CITIES.keys()), width=14)
self.city_combo.pack(side=tk.LEFT, padx=5)
self.search_btn = tk.Button(row1, text="📊 获取数据", command=self.start_analysis)
self.search_btn.pack(side=tk.LEFT, padx=5)
self.guide_btn = tk.Button(row1, text="📋 出行指南", command=self.show_comprehensive_guide)
self.guide_btn.pack(side=tk.LEFT, padx=5)
self.export_btn = tk.Button(row1, text="📁 导出CSV", command=self.export_data)
self.export_btn.pack(side=tk.LEFT, padx=5)
self.weekly_btn = tk.Button(row1, text="🗓️ 全国最佳", command=self.show_weekly_best)
self.weekly_btn.pack(side=tk.LEFT, padx=5)
# 第二行:周边推荐
row2 = tk.Frame(control_frame, bg="#F5F5F5")
row2.pack(fill=tk.X, padx=10, pady=5)
tk.Label(row2, text="📍 我的位置:", font=("微软雅黑", 10)).pack(side=tk.LEFT)
self.base_city_var = tk.StringVar(value="北京")
self.base_city_combo = ttk.Combobox(row2, textvariable=self.base_city_var, values=list(ALL_CITIES.keys()), width=14)
self.base_city_combo.pack(side=tk.LEFT, padx=5)
tk.Label(row2, text="半径:", font=("微软雅黑", 10)).pack(side=tk.LEFT, padx=(10, 0))
self.radius_var = tk.StringVar(value="200km")
radius_options = ["50km", "100km", "200km", "300km", "500km", "800km"]
self.radius_combo = ttk.Combobox(row2, textvariable=self.radius_var, values=radius_options, width=8)
self.radius_combo.pack(side=tk.LEFT, padx=5)
self.nearby_btn = tk.Button(row2, text="🏠 周边推荐", command=self.show_nearby_recommend, bg="#27AE60", fg="white")
self.nearby_btn.pack(side=tk.LEFT, padx=5)
self.status_var = tk.StringVar(value="✅ 就绪 | 全国150+城市已加载 | 支持周边游推荐")
tk.Label(main_frame, textvariable=self.status_var, font=("微软雅黑", 9),
fg="#7F8C8D", anchor=tk.W).pack(fill=tk.X, pady=(0, 10))
paned = tk.PanedWindow(main_frame, orient=tk.HORIZONTAL, sashrelief=tk.RAISED)
paned.pack(fill=tk.BOTH, expand=True)
left_frame = tk.Frame(paned, bg="#F5F5F5")
paned.add(left_frame, width=600)
notebook = ttk.Notebook(left_frame)
notebook.pack(fill=tk.BOTH, expand=True)
self.tab_current = tk.Frame(notebook, bg="#F5F5F5")
notebook.add(self.tab_current, text="🌡️ 当前天气")
self.current_text = scrolledtext.ScrolledText(self.tab_current, wrap=tk.WORD, font=("微软雅黑", 10))
self.current_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.tab_forecast = tk.Frame(notebook, bg="#F5F5F5")
notebook.add(self.tab_forecast, text="📅 7天预报")
self.forecast_text = scrolledtext.ScrolledText(self.tab_forecast, wrap=tk.WORD, font=("微软雅黑", 10))
self.forecast_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.tab_history = tk.Frame(notebook, bg="#F5F5F5")
notebook.add(self.tab_history, text="📊 历史统计")
self.history_text = scrolledtext.ScrolledText(self.tab_history, wrap=tk.WORD, font=("微软雅黑", 10))
self.history_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
right_frame = tk.Frame(paned, bg="#F5F5F5")
paned.add(right_frame, width=500)
self.figure = plt.Figure(figsize=(5, 6), dpi=100)
self.canvas = FigureCanvasTkAgg(self.figure, right_frame)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
self._show_welcome()
def _show_welcome(self):
msg = """
🌤️ 欢迎使用智能旅游决策系统!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📌 覆盖城市:全国150+主要地级市
📌 数据来源:Open-Meteo 免费API
【核心功能】
1. 📊 获取天气 - 查询任意城市当前天气、7天预报、历史统计
2. 📋 出行指南 - 出行指数 + 穿衣建议 + 带伞提醒 + 异常预警
3. 🗓️ 全国最佳 - 未来7天全国温度舒适度最高的城市
4. 🏠 周边推荐 - 基于您的位置和半径,推荐周边最舒适城市(新功能!)
5. 📁 导出CSV - 导出历史温度数据
【操作提示】
- 第一行:选择省份/城市,查询单城市天气
- 第二行:设置「我的位置」和「半径」,点击「周边推荐」获取附近最佳出游地
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"""
self.current_text.insert(tk.END, msg)
self.current_text.config(state=tk.DISABLED)
def on_province_change(self, event):
province = self.province_var.get()
if province == "全部":
cities = list(ALL_CITIES.keys())
else:
cities = CITY_BY_PROVINCE.get(province, [])
self.city_combo['values'] = cities
if cities:
self.city_var.set(cities[0])
def start_analysis(self):
city = self.city_var.get().strip()
if city not in ALL_CITIES:
messagebox.showwarning("提示", "请选择有效城市")
return
self.search_btn.config(state=tk.DISABLED, text="加载中...")
self.status_var.set(f"📡 正在获取 {city} 天气数据...")
self.current_text.config(state=tk.NORMAL)
self.current_text.delete(1.0, tk.END)
self.forecast_text.config(state=tk.NORMAL)
self.forecast_text.delete(1.0, tk.END)
self.history_text.config(state=tk.NORMAL)
self.history_text.delete(1.0, tk.END)
def task():
current = WeatherAPI.get_current_weather(city)
forecast = WeatherAPI.get_forecast(city, 7)
history = WeatherAPI.get_historical(city, 30)
self.root.after(0, self._on_done, city, current, forecast, history)
threading.Thread(target=task, daemon=True).start()
def _on_done(self, city, current, forecast, history):
self.search_btn.config(state=tk.NORMAL, text="📊 获取数据")
self.current_data = {"current": current, "forecast": forecast, "history": history}
if not current:
self.status_var.set("❌ 数据获取失败,请检查网络")
return
self.status_var.set(f"✅ 数据获取成功!{city}")
comfort_label, _ = analyze_comfort(current['温度'], current['湿度'])
current_content = f"""
📍 {city} 当前天气
{'=' * 35}
🌡️ 温度:{current['温度']}°C
💧 湿度:{current['湿度']}%
💨 风速:{current['风速']} km/h
🧭 风向:{current['风向']}°
🕐 更新时间:{current['时间']}
😊 舒适度:{comfort_label}
"""
self.current_text.insert(tk.END, current_content)
self.current_text.config(state=tk.DISABLED)
if forecast:
forecast_content = f"📅 {city} 未来7天天气预报\n{'=' * 35}\n"
weather_map = {0: "☀️ 晴", 1: "🌤️ 少云", 2: "⛅ 多云", 3: "☁️ 阴", 45: "🌫️ 雾", 51: "🌧️ 小雨", 61: "🌧️ 中雨",
71: "🌨️ 小雪"}
for day in forecast:
desc = weather_map.get(day['天气代码'], "🌤️")
forecast_content += f"{day['日期']}: {desc} {day['最低温']}~{day['最高温']}°C\n"
self.forecast_text.insert(tk.END, forecast_content)
self.forecast_text.config(state=tk.DISABLED)
if history:
temps = [h['最高温'] for h in history if h['最高温'] is not None]
if temps:
history_content = f"📊 {city} 近{len(history)}天温度统计\n{'=' * 35}\n"
history_content += f"📈 最高温:{max(temps):.1f}°C\n"
history_content += f"📉 最低温:{min(temps):.1f}°C\n"
history_content += f"📊 平均温:{sum(temps) / len(temps):.1f}°C"
self.history_text.insert(tk.END, history_content)
self.history_text.config(state=tk.DISABLED)
self.figure.clear()
ax1 = self.figure.add_subplot(211)
if forecast:
dates = [d['日期'][5:] for d in forecast]
highs = [d['最高温'] for d in forecast]
lows = [d['最低温'] for d in forecast]
ax1.plot(dates, highs, 'r-o', label='最高温')
ax1.plot(dates, lows, 'b-o', label='最低温')
ax1.set_title(f'{city} 7天温度趋势')
ax1.set_ylabel('温度 (°C)')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax2 = self.figure.add_subplot(212)
if history:
hist_temps = [h['最高温'] for h in history if h['最高温'] is not None]
if hist_temps:
ax2.hist(hist_temps, bins=10, color='skyblue', edgecolor='black')
ax2.set_title(f'{city} 近{len(hist_temps)}天温度分布')
ax2.set_xlabel('温度 (°C)')
ax2.set_ylabel('天数')
ax2.grid(True, alpha=0.3)
self.figure.tight_layout()
self.canvas.draw()
def show_comprehensive_guide(self):
city = self.city_var.get().strip()
current = self.current_data.get("current")
forecast = self.current_data.get("forecast")
history = self.current_data.get("history")
if not current:
messagebox.showwarning("提示", "请先获取天气数据")
return
guide = generate_comprehensive_guide(city, current, forecast, history)
messagebox.showinfo("📋 综合出行指南", guide)
def export_data(self):
city = self.city_var.get().strip()
history = self.current_data.get("history")
if not history:
messagebox.showwarning("提示", "请先获取天气数据")
return
filename = export_weather_data(city, history)
if filename:
messagebox.showinfo("导出成功", f"数据已导出到:{os.path.abspath(filename)}")
def show_weekly_best(self):
self.status_var.set("⏳ 正在计算全国每日最佳城市...")
def task():
best_list = get_weekly_best_cities(TOUR_CITIES)
self.root.after(0, self._show_weekly_result, best_list)
threading.Thread(target=task, daemon=True).start()
def _show_weekly_result(self, best_list):
self.status_var.set("✅ 计算完成")
if not best_list:
messagebox.showinfo("全国每日最佳", "未能获取预报数据,请检查网络")
return
report = "🗓️ 未来7天全国每日最佳旅游城市推荐\n" + "=" * 50 + "\n"
report += f"{'日期':<12} {'城市':<8} {'温度范围':<12} {'舒适度':<8}\n"
report += "-" * 50 + "\n"
for item in best_list:
date_str = item['日期'][5:]
report += f"{date_str:<12} {item['城市']:<8} {item['最低温']}~{item['最高温']}°C {item['舒适分']:<8}\n"
report += "\n" + "=" * 50 + "\n"
report += "💡 评分基于温度舒适度(18-25°C为最佳),适用于长途旅行规划。"
messagebox.showinfo("🗓️ 全国每日最佳", report)
def show_nearby_recommend(self):
"""周边城市推荐"""
base_city = self.base_city_var.get().strip()
if base_city not in ALL_CITIES:
messagebox.showwarning("提示", "请选择有效的基准城市")
return
radius_text = self.radius_var.get().replace("km", "")
try:
radius = int(radius_text)
except:
radius = 200
self.status_var.set(f"⏳ 正在计算以 {base_city} 为中心 {radius}km 内的最佳城市...")
self.nearby_btn.config(state=tk.DISABLED, text="计算中...")
def task():
results = recommend_nearby_cities(base_city, radius)
self.root.after(0, self._show_nearby_result, results, base_city, radius)
threading.Thread(target=task, daemon=True).start()
def _show_nearby_result(self, results, base_city, radius):
self.nearby_btn.config(state=tk.NORMAL, text="🏠 周边推荐")
self.status_var.set("✅ 周边推荐计算完成")
if not results:
messagebox.showinfo("周边推荐", f"在以 {base_city} 为中心 {radius}km 范围内未找到其他城市数据")
return
report = f"🏠 以 {base_city} 为中心 {radius}km 内最舒适的 Top 5 城市\n" + "=" * 55 + "\n"
report += f"{'排名':<4} {'城市':<8} {'距离':<8} {'温度':<6} {'湿度':<6} {'舒适度':<10}\n"
report += "-" * 55 + "\n"
for i, item in enumerate(results, 1):
report += f"{i:<4} {item['城市']:<8} {item['距离']}km {item['温度']}°C {item['湿度']}% {item['舒适标签']:<6}\n"
report += "\n" + "=" * 55 + "\n"
if results:
best = results[0]
report += f"🏆 最佳周边游目的地:{best['城市']}({best['距离']}km,{best['舒适标签']},{best['温度']}°C)"
report += "\n💡 适用于周末短途出游规划"
messagebox.showinfo("🏠 周边推荐", report)
def run(self):
self.root.mainloop()
# ==================== 为微信端新增函数 ====================
def get_weather_text(city_name):
"""
根据城市名获取天气信息,并返回一段格式化的文字(用于微信回复/推送)
如果获取失败,返回错误提示。
"""
if city_name not in ALL_CITIES:
return f"❌ 未找到城市“{city_name}”,请检查名称。"
current = WeatherAPI.get_current_weather(city_name)
if not current:
return f"❌ 获取{city_name}天气失败,请稍后重试。"
forecast = WeatherAPI.get_forecast(city_name, 3) # 取3天预报
comfort_label, _ = analyze_comfort(current['温度'], current['湿度'])
# 生成今日天气描述
msg = f"🌤️ {city_name} 当前天气\n"
msg += f"温度:{current['温度']}°C\n"
msg += f"湿度:{current['湿度']}%\n"
msg += f"风速:{current['风速']} km/h\n"
msg += f"舒适度:{comfort_label}\n"
msg += f"更新时间:{current['时间']}\n"
# 加上未来三天简要预报
if forecast and len(forecast) >= 3:
msg += "\n📅 未来三天预报:\n"
weather_map = {0: "晴", 1: "少云", 2: "多云", 3: "阴", 45: "雾", 51: "小雨", 61: "中雨", 71: "小雪"}
for day in forecast[:3]:
desc = weather_map.get(day['天气代码'], "多云")
msg += f"{day['日期'][5:]}: {desc} {day['最低温']}~{day['最高温']}°C\n"
return msg
def get_morning_weather(city="北京"):
"""
专门用于早上6:10推送的简略天气(可包含穿衣建议)
"""
current = WeatherAPI.get_current_weather(city)
if not current:
return f"早安!今日{city}天气获取失败,请稍后查看。"
temp = current['温度']
# 简单穿衣建议
if float(temp) >= 30:
dress = "注意防暑,穿短袖短裤。"
elif float(temp) >= 18:
dress = "天气适宜,穿长袖薄外套。"
elif float(temp) >= 10:
dress = "偏凉,穿毛衣厚外套。"
else:
dress = "寒冷,请穿羽绒服!"
msg = f"☀️ 早安!{city}今日天气:\n"
msg += f"温度 {temp}°C,湿度 {current['湿度']}%\n"
msg += f"建议:{dress}"
return msg
if __name__ == "__main__":
print("=" * 60)
print("🌤️ 天气数据采集与智能旅游决策系统 启动中...")
print("覆盖城市:全国150+主要地级市 | 功能:天气查询/出行指南/全国最佳/周边推荐")
print("=" * 60)
app = WeatherAnalyzer()
app.run()
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。