1 Star 0 Fork 0

shanhe/PythonProject

Create your Gitee Account
Explore and code with more than 14 million developers,Free private repositories !:)
Sign up
文件
This repository doesn't specify license. Please pay attention to the specific project description and its upstream code dependency when using it.
Clone or Download
test.py 53.97 KB
Copy Edit Raw Blame History
山河 authored 2026-06-22 09:38 +08:00 . 实验四
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146
# -*- 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']}&current=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()
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/shanhe13/PythonProject.git
git@gitee.com:shanhe13/PythonProject.git
shanhe13
PythonProject
PythonProject
master

Search