高考后的去向分布图——使用Python语言绘制

发布于:2025-06-24 ⋅ 阅读:(18) ⋅ 点赞:(0)

前言

在之前代码的基础上进行了优化,考虑到高考后对计算机操作还不是很熟练,读取数据从json格式换成了Excel表格,当Excel表格不存在的时候可以随机生成150条数据。

使用Python绘制毕业生城市分布图-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/148822546?spm=1001.2014.3001.5501Python绘图库及图像类型之基础图表_arrowprops=dict(arrowstyle='->', lw=1.5)-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/148433762?spm=1001.2014.3001.5502Python绘图库及图像类型之高级可视化-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/148450750?spm=1001.2014.3001.5502Python绘图库及图像类型之特殊领域可视化-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/148450970?spm=1001.2014.3001.5502Python绘制三十六计_import matplotlib.pyplot as pltfrom matplotlib.pat-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/148498880?spm=1001.2014.3001.5502使用Python语言进行函数作画绘制芙莉莲&勇者-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/148412637?spm=1001.2014.3001.5502使用Python进行函数作画-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/148383559?spm=1001.2014.3001.5502

代码

本程序的功能和前面是毕业生分布是一致的,提供了搜索功能,方便找到自己心仪同学的位置和联系方式。

import folium
import pandas as pd
import json
from folium.plugins import MarkerCluster
import os
import random
import requests

print("=" * 50)
print("湖南省高校分布交互式地图生成器")
print("湖南师范大学树达学院2021级计算机科学与技术专业刘健到此一游")
print("=" * 50)

university_coords = {
    # 本科院校 (修正了所有重复坐标)
    '中南大学': [28.1628, 112.9353],
    '湖南大学': [28.1792, 112.9438],
    '湖南师范大学': [28.1845, 112.9506],
    '湖南师范大学树达学院': [28.1900, 112.9400],  # 新增
    '湘潭大学': [27.8962, 112.9513],
    '湖南农业大学': [28.1811, 113.0836],
    '长沙理工大学': [28.0721, 113.0102],
    '中南林业科技大学': [28.1338, 113.0387],
    '湖南科技大学': [27.9150, 112.9440],  # 修正
    '南华大学': [26.8932, 112.8520],  # 修正
    '湖南工业大学': [27.8297, 113.1338],
    '吉首大学': [28.3119, 109.7389],
    '湖南工商大学': [28.2161, 113.0312],
    '湖南理工学院': [29.3572, 113.1289],
    '衡阳师范学院': [26.9030, 112.6330],  # 修正
    '湖南文理学院': [29.0316, 111.6991],
    '湖南工程学院': [27.8550, 112.9420],  # 修正
    '湖南城市学院': [28.5539, 112.3552],
    '湖南科技学院': [26.4203, 111.6122],
    '湖南人文科技学院': [27.7000, 111.9964],
    '湖南第一师范学院': [28.1212, 113.0009],
    '长沙学院': [28.2458, 113.0812],
    '湖南财政经济学院': [28.2100, 112.9100],  # 修正
    '湖南警察学院': [28.1449, 113.0363],
    '湖南女子学院': [28.1020, 113.0170],  # 修正
    '湖南医药学院': [27.5600, 110.0000],  # 修正
    '怀化学院': [27.5540, 109.9985],

    # 专科院校 (全部独立坐标)
    '湖南工业职业技术学院': [28.1260, 112.8810],
    '长沙民政职业技术学院': [28.1478, 112.9235],
    '湖南铁道职业技术学院': [27.8700, 113.1000],
    '湖南交通职业技术学院': [28.1650, 113.1100],
    '湖南大众传媒职业技术学院': [28.2380, 113.0850],
    '湖南科技职业学院': [28.1000, 113.0200],
    '湖南生物机电职业技术学院': [28.1870, 113.0900],
    '湖南商务职业技术学院': [28.2300, 112.9200],
    '湖南工程职业技术学院': [28.2700, 113.0500],
    '长沙航空职业技术学院': [28.0700, 113.0500],
    '湖南汽车工程职业学院': [27.9500, 113.1500],
    '湖南化工职业技术学院': [27.9400, 113.1400],
    '湖南城建职业技术学院': [27.8600, 112.9500],
    '湖南环境生物职业技术学院': [26.9300, 112.6000],
    '湖南机电职业技术学院': [28.2800, 113.0500],
    '湖南工艺美术职业学院': [28.5600, 112.3500],
    '湖南石油化工职业技术学院': [29.4500, 113.1500],
    '湖南国防工业职业技术学院': [27.8500, 112.8500],
    '长沙商贸旅游职业技术学院': [28.1000, 113.0500],
    '湖南网络工程职业学院': [28.1000, 112.9900],
    '湖南司法警官职业学院': [28.2200, 113.1000],
    '长沙环境保护职业技术学院': [28.1300, 113.0000],
    '湖南现代物流职业技术学院': [28.3000, 113.1000],
    '湖南安全技术职业学院': [28.2500, 113.0800],
    '湖南外国语职业学院': [28.3500, 112.8000],
    '湖南电子科技职业学院': [28.3300, 112.8200],
    '湖南都市职业学院': [28.2400, 113.2000],
    '湖南三一工业职业技术学院': [28.1700, 113.1500],
    '长沙电力职业技术学院': [28.2000, 113.0800],
    '湖南水利水电职业技术学院': [28.2300, 113.1100],
    '湖南信息职业技术学院': [28.3400, 112.8300],
    '湖南食品药品职业学院': [28.2100, 112.8700],
    '湖南劳动人事职业学院': [28.3100, 113.1100],
    '湖南有色金属职业技术学院': [27.9450, 113.1450],
    '湖南九嶷职业技术学院': [26.4500, 111.6000],
    '潇湘职业学院': [27.7200, 111.9900],
    '湖南软件职业学院': [27.9200, 112.9400],
    '湘西民族职业技术学院': [28.3200, 109.7500],
    '张家界航空工业职业技术学院': [29.1171, 110.4792],
    '益阳职业技术学院': [28.6000, 112.3500],
    '郴州职业技术学院': [25.7705, 113.0147],
    '娄底职业技术学院': [27.7300, 111.9800],
    '怀化职业技术学院': [27.5600, 109.9900],
    '永州职业技术学院': [26.4500, 111.6200],
    '常德职业技术学院': [29.0500, 111.7000],
    '岳阳职业技术学院': [29.3500, 113.1500],
    '邵阳职业技术学院': [27.2389, 111.4672],
    '株洲师范高等专科学校': [27.8300, 113.1300],
    '益阳师范高等专科学校': [28.5700, 112.3600],
    '湖南幼儿师范高等专科学校': [29.0400, 111.6800],
    '湘南幼儿师范高等专科学校': [25.7800, 113.0200],
    '怀化师范高等专科学校': [27.4000, 110.0000],
    '永州师范高等专科学校': [26.4300, 111.6200],
    '衡阳幼儿师范高等专科学校': [26.8800, 112.6100],
    '长沙幼儿师范高等专科学校': [28.0900, 113.0000],
    '株洲幼儿师范学校': [27.9450, 113.1420]
}

# 定义Excel文件路径
excel_file = "universities.xlsx"

# 检查Excel文件是否存在
if not os.path.exists(excel_file):
    print(f"错误:未找到数据文件 '{excel_file}'")
    print("请创建包含高校数据的Excel文件,格式如下:")
    print("列名: '姓名', '大学', '专业', '电话'")

    # 创建50条示例数据
    universities = list(university_coords.keys())

    # 多样化的专业列表
    majors = [
        "计算机科学与技术", "软件工程", "人工智能", "数据科学", "网络安全",
        "物联网工程", "云计算", "大数据", "区块链",
        "电子信息工程", "通信工程", "自动化", "电气工程及其自动化",
        "机械工程", "材料科学与工程", "土木工程", "建筑学",
        "金融学", "会计学", "市场营销", "国际经济与贸易", "工商管理",
        "法学", "汉语言文学", "英语", "新闻学", "广告学",
        "数学与应用数学", "物理学", "化学", "生物科学",
        "临床医学", "口腔医学", "护理学", "药学",
        "艺术设计", "音乐表演", "舞蹈学", "戏剧影视文学",
        "体育教育", "运动训练", "社会体育指导与管理",
        "心理学", "教育学", "学前教育", "小学教育"
    ]

    # 姓氏和名字
    surnames = ["张", "王", "李", "赵", "陈", "刘", "杨", "黄", "周", "吴"]
    given_names = ["伟", "芳", "娜", "秀英", "敏", "静", "丽", "强", "磊", "军",
                   "洋", "勇", "艳", "杰", "涛", "明", "超", "秀兰", "霞", "平",
                   "刚", "桂英", "文", "兰", "红", "志强", "建国", "建华", "桂兰", "桂香"]

    sample_data = {
        '姓名': [],
        '大学': [],
        '专业': [],
        '电话': []
    }

    # 生成150条记录
    for i in range(150):
        # 随机生成姓名
        surname = random.choice(surnames)
        given_name = random.choice(given_names)
        name = surname + given_name

        # 随机选择大学和专业
        university = random.choice(universities)
        major = random.choice(majors)

        # 生成随机电话号码
        prefix = random.choice(['130', '131', '132', '133', '134', '135', '136', '137', '138', '139'])
        suffix = ''.join(random.choices('0123456789', k=8))
        phone = prefix + suffix

        sample_data['姓名'].append(name)
        sample_data['大学'].append(university)
        sample_data['专业'].append(major)
        sample_data['电话'].append(phone)

    # 创建DataFrame并保存为Excel
    df_sample = pd.DataFrame(sample_data)
    df_sample.to_excel(excel_file, index=False)
    print(f"已创建包含150条示例数据的文件 '{excel_file}',请修改后重新运行程序")
    exit()

# 从Excel文件读取数据
try:
    df = pd.read_excel(excel_file)
    print(f"成功从 '{excel_file}' 加载 {len(df)} 条高校数据")

    # 检查必要的列是否存在
    required_columns = ['姓名', '大学']
    missing_columns = [col for col in required_columns if col not in df.columns]
    if missing_columns:
        print(f"错误:Excel文件中缺少必要的列: {', '.join(missing_columns)}")
        exit()

    # 重命名列(如果使用不同的列名)
    column_mapping = {
        '姓名': '姓名',
        '大学': '大学',
        '专业': '专业',
        '电话': '电话',
        '学校': '大学',
        '名称': '姓名',
        '专业名称': '专业',
        '手机': '电话',
        '联系电话': '电话'
    }

    # 应用列名映射
    df = df.rename(columns=column_mapping)

    # 确保所有必要的列都存在
    if '专业' not in df.columns:
        df['专业'] = '未提供专业信息'
    if '电话' not in df.columns:
        df['电话'] = ''

    # 转换为列表字典
    student_data = df.to_dict('records')
except Exception as e:
    print(f"读取Excel文件时出错: {e}")
    exit()

# 验证数据格式
valid_data = []
for student in student_data:
    if pd.isna(student.get('姓名')) or pd.isna(student.get('大学')):
        print(f"警告:跳过无效记录 - {student}")
        continue

    university = student["大学"]
    if university not in university_coords:
        print(f"警告:大学 '{university}' 不在坐标字典中,已跳过记录: {student['姓名']}")
        continue

    # 处理电话号码 - 如果没有则生成随机号码
    phone = str(student.get("电话", ""))
    if not phone or phone.lower() in ['nan', 'null', 'none', '']:
        # 生成随机的中国大陆手机号码
        prefix = random.choice(['130', '131', '132', '133', '134', '135', '136', '137', '138', '139'])
        suffix = ''.join(random.choices('0123456789', k=8))
        phone = prefix + suffix
        print(f"提示:{student['姓名']} ({university}) 没有电话号码,已生成随机号码: {phone}")

    # 获取专业信息
    major = student.get("专业", "未提供专业信息")
    if pd.isna(major):
        major = "未提供专业信息"

    valid_data.append({
        '姓名': student['姓名'],
        '大学': university,
        '专业': major,
        '电话': phone,
        '坐标': university_coords[university]
    })

if not valid_data:
    print("错误:没有有效数据可用于创建地图")
    exit()

print(f"有效数据记录: {len(valid_data)} 条")

# 创建数据框
df = pd.DataFrame(valid_data)


# 获取湖南省GeoJSON边界数据
def get_hunan_geojson():
    try:
        # 尝试从网络获取最新数据
        url = "https://geo.datav.aliyun.com/areas_v3/bound/430000_full.json"
        response = requests.get(url, timeout=10)
        response.raise_for_status()
        print("成功从网络获取湖南省边界数据")
        return response.json()
    except:
        # 如果网络请求失败,使用本地备份数据
        print("网络请求失败,使用本地备份的湖南省边界数据")
        return {
            "type": "FeatureCollection",
            "features": [
                {
                    "type": "Feature",
                    "properties": {
                        "adcode": 430000,
                        "name": "湖南省",
                        "center": [112.982279, 28.19409],
                        "centroid": [112.113889, 28.547812],
                        "childrenNum": 14,
                        "level": "province",
                        "parent": {"adcode": 100000},
                        "subFeatureIndex": 0,
                        "acroutes": [100000]
                    },
                    "geometry": {
                        "type": "MultiPolygon",
                        "coordinates": [[[
                            [109.423469, 29.117102], [109.423469, 29.117102],
                            [113.382312, 29.117102], [113.382312, 24.977642],
                            [109.423469, 24.977642], [109.423469, 29.117102]
                        ]]]
                    }
                }
            ]
        }


# 获取湖南省边界数据
hunan_geojson = get_hunan_geojson()

# 创建湖南省地图 - 使用OpenStreetMap中文版
print("正在创建地图...")
m = folium.Map(location=[27.5, 112.0],
               zoom_start=7,
               tiles='https://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
               attr='高德地图',
               control_scale=True)

# 添加湖南省精确边界
folium.GeoJson(
    hunan_geojson,
    name='湖南省边界',
    style_function=lambda feature: {
        'fillColor': '#f0f8ff',
        'color': '#1e90ff',
        'weight': 2,
        'fillOpacity': 0.2
    },
    tooltip=folium.GeoJsonTooltip(fields=['name'], aliases=['省份: '])
).add_to(m)

# 按大学分组学生
university_groups = df.groupby('大学')

# 创建自定义标记聚类 - 显示总人数而不是标记数量
marker_cluster = MarkerCluster(
    name="高校分布",
    icon_create_function='''function(cluster) {
        var markers = cluster.getAllChildMarkers();
        var totalStudents = 0;

        // 计算所有标记中的学生总数
        for (var i = 0; i < markers.length; i++) {
            totalStudents += markers[i].options.studentCount;
        }

        // 根据总人数确定图标大小和颜色
        var iconSize = 40;
        var iconColor = '#4CAF50'; // 绿色
        var fontSize = 16;

        if (totalStudents > 30) {
            iconSize = 55;
            iconColor = '#f44336'; // 红色
            fontSize = 18;
        } else if (totalStudents > 20) {
            iconSize = 50;
            iconColor = '#FF9800'; // 橙色
            fontSize = 17;
        } else if (totalStudents > 10) {
            iconSize = 45;
            iconColor = '#FFC107'; // 黄色
            fontSize = 16;
        } else if (totalStudents > 5) {
            iconSize = 40;
            iconColor = '#4CAF50'; // 绿色
            fontSize = 16;
        }

        // 创建圆形图标
        return L.divIcon({
            html: '<div style="background-color:' + iconColor + '; width: ' + iconSize + 'px; height: ' + iconSize + 'px; border-radius: 50%; border: 3px solid white; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: ' + fontSize + 'px; box-shadow: 0 0 10px rgba(0,0,0,0.3);">' + totalStudents + '</div>',
            className: 'marker-cluster-custom',
            iconSize: L.point(iconSize, iconSize),
            iconAnchor: [iconSize / 2, iconSize / 2]
        });
    }'''
).add_to(m)

# 为每个大学添加标记
print("正在添加高校标记...")
for university, group in university_groups:
    count = len(group)
    university_type = "本科" if "大学" in university or "学院" in university else "专科"

    # 创建弹出窗口内容 - 使用表格显示姓名和电话
    popup_html = f"""
    <div style="width:350px; font-family: 'Microsoft YaHei', sans-serif;">
        <h4 style="margin-top:0; color:#1e90ff; border-bottom:1px solid #eee; padding-bottom:5px;">
        {university}</h4>
        <div style="display:flex; justify-content:space-between; margin-bottom:10px;">
            <span style="background-color:#4CAF50; color:white; padding:3px 8px; border-radius:4px;">
                {university_type}院校
            </span>
            <span style="font-weight:bold; color:#333;">
                学生人数: {count}人
            </span>
        </div>
        <div style="max-height:300px; overflow-y:auto; border:1px solid #eee; padding:5px; margin-top:5px;">
            <table style="width:100%; border-collapse: collapse;">
                <thead>
                    <tr style="background-color: #f2f2f2;">
                        <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">姓名</th>
                        <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">专业</th>
                        <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">电话</th>
                    </tr>
                </thead>
                <tbody>
    """

    # 添加表格行
    for _, row in group.iterrows():
        popup_html += f"""
                    <tr>
                        <td style="border: 1px solid #ddd; padding: 8px;">{row['姓名']}</td>
                        <td style="border: 1px solid #ddd; padding: 8px;">{row['专业']}</td>
                        <td style="border: 1px solid #ddd; padding: 8px;">
                            <a href="tel:{row['电话']}">{row['电话']}</a>
                        </td>
                    </tr>
        """

    popup_html += """
                </tbody>
            </table>
        </div>
        <div style="margin-top:10px; font-size:12px; color:#888; text-align:center;">
            点击电话可直接拨打(在移动设备上)
        </div>
    </div>
    """

    # 根据人数确定图标大小
    if count > 7:
        icon_size = (60, 60)
        icon_color = '#8B0000'  # 深红
    elif count > 5:
        icon_size = (45, 45)
        icon_color = '#FF8C00'  # 深橙色
    elif count > 3:
        icon_size = (42, 42)
        icon_color = '#FFA500'  # 橙色
    elif count > 1:
        icon_size = (38, 38)
        icon_color = '#FFD700'  # 金色
    else:
        icon_size = (30, 30)
        icon_color = '#00CED1'  # 青蓝色

    # 创建自定义图标
    icon = folium.DivIcon(
        icon_size=icon_size,
        icon_anchor=(icon_size[0] // 2, icon_size[1] // 2),
        html=f"""
        <div style="
            background-color: {icon_color};
            width: {icon_size[0]}px;
            height: {icon_size[1]}px;
            border-radius: 50%;
            border: 2px solid white;
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            font-weight: bold;
            box-shadow: 0 0 10px rgba(0,0,0,0.3);
            font-family: 'Microsoft YaHei', sans-serif;
            font-size: {min(16, max(12, icon_size[0] // 2))}px;
        ">
            {count}
        </div>
        """
    )

    # 添加标记并设置学生人数属性
    marker = folium.Marker(
        location=university_coords[university],
        popup=folium.Popup(popup_html, max_width=400),
        icon=icon,
        tooltip=f"{university} ({count}名学生)"
    )

    # 添加学生人数属性,用于聚类计算
    marker.options['studentCount'] = count
    marker.add_to(marker_cluster)

# 添加标题
title_html = '''
     <h3 align="center" style="font-size:18px; background-color:rgba(255,255,255,0.8); 
             padding:10px; margin:10px; border-radius:5px; font-family: 'Microsoft YaHei', sans-serif;">
     <b>耒阳市正源学校2021级毕业生分布图</b></h3>
     '''
m.get_root().html.add_child(folium.Element(title_html))

# 添加搜索框的CSS样式
search_css = '''
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
    #search-container {
        position: fixed; 
        top: 100px; 
        right: 50px; 
        z-index:9999; 
        font-size:14px;
        background-color:rgba(255,255,255,0.95);
        padding: 15px; 
        border-radius: 5px; 
        box-shadow: 0 0 15px rgba(0,0,0,0.3);
        font-family: 'Microsoft YaHei', sans-serif;
    }

    .collapsible-toggle-btn {
        position: absolute;
        top: 5px;
        right: 5px;
        width: 30px;
        height: 30px;
        border-radius: 50%;
        background-color: #4CAF50;
        color: white;
        border: none;
        display: flex;
        justify-content: center;
        align-items: center;
        cursor: pointer;
        box-shadow: 0 2px 5px rgba(0,0,0,0.2);
        z-index: 10000;
    }

    .collapsible-content {
        transition: all 0.3s ease;
        overflow: hidden;
    }

    .collapsible-content.collapsed {
        max-height: 0;
        opacity: 0;
        padding: 0;
        margin: 0;
    }

    @media (max-width: 768px) {
        #search-container {
            width: 90%;
            right: 5%;
            top: 20px;
        }

        .collapsible-content {
            max-height: 400px;
            opacity: 1;
        }

        .collapsible-content.collapsed {
            max-height: 0;
            opacity: 0;
            padding: 0;
            margin: 0;
        }
    }

    #search-input {
        width: 280px; 
        padding: 10px; 
        border: 1px solid #ddd; 
        border-radius: 4px; 
        margin-bottom: 12px; 
        font-size:14px;
    }

    #search-results {
        margin-top:15px; 
        max-height:400px; 
        overflow-y:auto; 
        display:none; 
        border:1px solid #ddd; 
        border-radius:4px; 
        padding:5px;
    }
</style>
'''
m.get_root().html.add_child(folium.Element(search_css))

# 添加搜索框(带折叠功能)
search_html = '''
<div id="search-container">
    <button class="collapsible-toggle-btn" id="toggle-search" onclick="toggleElement('search-content')">
        <i class="fa fa-chevron-up" id="toggle-search-icon"></i>
    </button>

    <h4 style="margin-top:0; margin-bottom:10px; color:#333; border-bottom:1px solid #eee; padding-bottom:5px; position:relative;">
        <i class="fa fa-search" style="margin-right:5px;"></i>高校与学生搜索
    </h4>

    <div class="collapsible-content" id="search-content">
        <input type="text" id="search-input" placeholder="输入高校名称或学生姓名...">
        <div>
            <button onclick="searchGraduates()" 
                    style="padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight:bold;">
                <i class="fa fa-search" style="margin-right:5px;"></i>搜索
            </button>
            <button onclick="clearSearch()" 
                    style="padding: 10px 20px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; margin-left:8px; font-weight:bold;">
                <i class="fa fa-times" style="margin-right:5px;"></i>清除
            </button>
        </div>
        <div id="search-results"></div>
    </div>
</div>
'''
m.get_root().html.add_child(folium.Element(search_html))

# 获取地图的div ID
map_div_id = m.get_name()

# 添加JavaScript功能
search_js = f"""
<script>
// 存储所有毕业生数据
var allGraduates = {json.dumps(valid_data, ensure_ascii=False)};

// 切换元素状态函数
function toggleElement(elementId) {{
    var content = document.getElementById(elementId);
    var icon = document.getElementById('toggle-search-icon');

    if (content.classList.contains('collapsed')) {{
        content.classList.remove('collapsed');
        icon.className = 'fa fa-chevron-up';
    }} else {{
        content.classList.add('collapsed');
        icon.className = 'fa fa-chevron-down';
    }}
}}

// 搜索函数
function searchGraduates() {{
    var input = document.getElementById('search-input').value.trim().toLowerCase();
    var resultsContainer = document.getElementById('search-results');
    resultsContainer.innerHTML = '';
    resultsContainer.style.display = 'block';

    if (!input) {{
        resultsContainer.innerHTML = '<div style="color:#888; text-align:center; padding:20px;">请输入搜索内容</div>';
        return;
    }}

    var found = false;
    var resultsHtml = '<div style="font-size:14px;"><table style="width:100%; border-collapse: collapse; border-spacing:0;">';
    resultsHtml += '<thead><tr style="background-color:#f5f5f5; font-weight:bold;">';
    resultsHtml += '<th style="padding:10px; border-bottom:1px solid #ddd; text-align:left;">姓名</th>';
    resultsHtml += '<th style="padding:10px; border-bottom:1px solid #ddd; text-align:left;">大学</th>';
    resultsHtml += '<th style="padding:10px; border-bottom:1px solid #ddd; text-align:left;">专业</th>';
    resultsHtml += '<th style="padding:10px; border-bottom:1px solid #ddd; text-align:left;">电话</th>';
    resultsHtml += '</tr></thead><tbody>';

    var resultCount = 0;
    var firstResultLocation = null;

    for (var i = 0; i < allGraduates.length; i++) {{
        var graduate = allGraduates[i];
        var name = graduate.姓名.toLowerCase();
        var university = graduate.大学.toLowerCase();
        var major = graduate.专业.toLowerCase();
        var phone = graduate.电话;
        var location = graduate.坐标;

        if (name.includes(input) || university.includes(input) || major.includes(input)) {{
            found = true;
            resultCount++;

            if (resultCount === 1) {{
                firstResultLocation = location;
            }}

            var displayName = graduate.姓名;
            var displayUniversity = graduate.大学;
            var displayMajor = graduate.专业;

            if (name.includes(input)) {{
                var startIndex = name.indexOf(input);
                displayName = graduate.姓名.substring(0, startIndex) + 
                             '<span style="background-color:yellow;">' + 
                             graduate.姓名.substring(startIndex, startIndex + input.length) + 
                             '</span>' + 
                             graduate.姓名.substring(startIndex + input.length);
            }}

            if (university.includes(input)) {{
                var startIndex = university.indexOf(input);
                displayUniversity = graduate.大学.substring(0, startIndex) + 
                             '<span style="background-color:yellow;">' + 
                             graduate.大学.substring(startIndex, startIndex + input.length) + 
                             '</span>' + 
                             graduate.大学.substring(startIndex + input.length);
            }}

            if (major.includes(input)) {{
                var startIndex = major.indexOf(input);
                displayMajor = graduate.专业.substring(0, startIndex) + 
                             '<span style="background-color:yellow;">' + 
                             graduate.专业.substring(startIndex, startIndex + input.length) + 
                             '</span>' + 
                             graduate.专业.substring(startIndex + input.length);
            }}

            resultsHtml += '<tr style="border-bottom:1px solid #eee; cursor:pointer;" onclick="flyToLocation([' + location[0] + ',' + location[1] + '])">';
            resultsHtml += '<td style="padding:10px; border-bottom:1px solid #eee;">' + displayName + '</td>';
            resultsHtml += '<td style="padding:10px; border-bottom:1px solid #eee;">' + displayUniversity + '</td>';
            resultsHtml += '<td style="padding:10px; border-bottom:1px solid #eee;">' + displayMajor + '</td>';
            resultsHtml += '<td style="padding:10px; border-bottom:1px solid #eee;"><a href="tel:' + phone + '" style="color:#1e90ff; text-decoration:none;">' + 
                          '<i class="fa fa-phone" style="margin-right:5px;"></i>' + phone + '</a></td>';
            resultsHtml += '</tr>';
        }}
    }}

    resultsHtml += '</tbody></table>';

    if (found) {{
        resultsHtml += '<div style="padding:10px; background-color:#f9f9f9; text-align:center; font-size:13px; color:#666;">';
        resultsHtml += '共找到 ' + resultCount + ' 条匹配记录';
        resultsHtml += '<br><span style="color:#4CAF50;">点击任意结果可定位到地图位置</span>';
        resultsHtml += '</div>';

        if (firstResultLocation) {{
            flyToLocation(firstResultLocation);
        }}
    }} else {{
        resultsHtml = '<div style="color:#f44336; text-align:center; padding:30px; font-size:16px;">';
        resultsHtml += '<i class="fa fa-exclamation-triangle" style="font-size:24px; margin-bottom:10px; display:block;"></i>';
        resultsHtml += '未找到匹配的学生或高校';
        resultsHtml += '</div>';
    }}

    resultsHtml += '</div>';
    resultsContainer.innerHTML = resultsHtml;
}}

// 清除搜索函数
function clearSearch() {{
    document.getElementById('search-input').value = '';
    var resultsContainer = document.getElementById('search-results');
    resultsContainer.innerHTML = '';
    resultsContainer.style.display = 'none';
}}

// 定位函数
function flyToLocation(location) {{
    var lat = parseFloat(location[0]);
    var lng = parseFloat(location[1]);

    if (isNaN(lat) || isNaN(lng)) {{
        console.error("无效的坐标:", location);
        return;
    }}

    var mapElements = document.getElementsByClassName('folium-map');
    if (mapElements.length === 0) {{
        console.error("未找到地图元素");
        return;
    }}

    var mapDiv = mapElements[0];

    // 获取地图实例
    var map = null;
    if (typeof window[mapDiv.id] !== 'undefined') {{
        map = window[mapDiv.id];
    }} else if (typeof window['map_' + mapDiv.id] !== 'undefined') {{
        map = window['map_' + mapDiv.id];
    }} else {{
        var mapObjects = mapDiv.getElementsByClassName('leaflet-container');
        if (mapObjects.length > 0) {{
            map = mapObjects[0]._map;
        }}
    }}

    if (!map) {{
        console.error("无法获取地图实例");
        return;
    }}

    // 平滑移动地图
    map.flyTo([lat, lng], 12, {{
        animate: true,
        duration: 1.5
    }});

    // 添加高亮标记
    var marker = L.marker([lat, lng], {{
        icon: L.divIcon({{
            className: 'highlight-marker',
            html: '<div style="background-color:red; width:40px; height:40px; border-radius:50%; border:3px solid white; box-shadow:0 0 15px rgba(255,0,0,0.9); display:flex; align-items:center; justify-content:center; font-size:18px; font-weight:bold;">!</div>',
            iconSize: [40, 40],
            iconAnchor: [20, 20]
        }}),
        zIndexOffset: 1000
    }}).addTo(map);

    // 3秒后移除高亮标记
    setTimeout(function() {{
        if (marker && map.hasLayer(marker)) {{
            map.removeLayer(marker);
        }}
    }}, 3000);
}}

// 支持按Enter键搜索
document.getElementById('search-input').addEventListener('keypress', function(e) {{
    if (e.key === 'Enter') {{
        searchGraduates();
    }}
}});

// 移动端初始化:默认折叠搜索框
function initMobileView() {{
    if (window.innerWidth <= 768) {{
        toggleElement('search-content');
    }}
}}

// 页面加载完成后初始化
window.addEventListener('load', initMobileView);
</script>
"""
m.get_root().html.add_child(folium.Element(search_js))

# 添加全屏控件
folium.plugins.Fullscreen(
    position='topleft',
    title='全屏查看',
    title_cancel='退出全屏',
    force_separate_button=True
).add_to(m)

# 添加比例尺
folium.plugins.MousePosition(
    position='bottomright',
    separator=' : ',
    empty_string='坐标未获取',
    lng_first=False,
    num_digits=4
).add_to(m)

# 添加图层控制
folium.LayerControl().add_to(m)

# 保存为HTML文件
output_file = "hunan_universities.html"
m.save(output_file)

print("=" * 50)
print(f"地图已成功生成: {output_file}")
print("请用浏览器打开该文件查看交互式地图")
print("=" * 50)


网站公告

今日签到

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