地理特征类可视化实验报告

地理特征类可视化实验报告

一、实验目的

本次实验旨在通过蜂窝热力地图、变形地图、关联地图、气泡地图等可视化工具,分析地理数据的空间分布、关联关系及多维特征,掌握不同图表的适用场景、技术实现流程及结果解读方法。

二、实验环境

  • 工具库
    • geopandas/contextily:地理数据处理与地图背景绘制
    • matplotlib:基础可视化
    • plotly:交互式地图绘制
  • 数据:模拟地震数据、亚洲国家经济指标、城市坐标与航线网络

三、可视化图表分析

(一)蜂窝热力地图(Heat Map)

图表特点
  • 核心原理:通过颜色渐变(如红色系)或透明度变化,映射点数据的密度或强度,基于核密度估计(KDE)将离散点转化为连续热力面。
  • 关键特征
    • 空间聚集性:直观展示高值区域(如地震高频带、城市热点)。
    • 密度抽象:通过热力层叠加地图背景,增强地理空间关联。
应用场景
  • 自然灾害分析:定位地震震级分布(如喜马拉雅地震带高震级区域)。
  • 城市规划:分析人口密度、交通流量热点。
实现过程(以地震数据为例)
  1. 数据生成
    • 模拟中国5大主要地震带的随机震点,包含震级、破坏程度(震级平方)等属性。
  2. 热力绘制
    • 使用geopandas绘制震点,column='magnitude'映射颜色,markersize='damage'映射破坏程度。
    • 通过contextily添加CartoDB地图背景,增强地理参照。
  3. 结果展示
    • 深红色区域为高震级聚集带,点大小反映破坏程度差异。
import geopandas as gpd
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os
from shapely.geometry import Point
import contextily as ctx  # 用于添加地图背景
import sys

# 设置中文字体
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示问题


def generate_earthquake_data(n=50):
    """生成模拟地震数据

    Args:
        n: 地震点数量

    Returns:
        GeoDataFrame包含地震数据
    """
    # 中国主要地震带区域
    earthquake_zones = [
        # 格式: [区域名称, 中心点经度, 中心点纬度, 区域半径, 平均震级]
        ["喜马拉雅地震带", 88, 28, 5, 7.5],
        ["青藏高原地震带", 95, 33, 8, 6.5],
        ["华北地震带", 116, 38, 4, 6.0],
        ["台湾地震带", 121, 23.5, 2, 7.0],
        ["新疆地震带", 85, 40, 6, 6.0]
    ]

    data = []

    # 从每个地震带生成随机地震点
    for zone in earthquake_zones:
        zone_name, center_lon, center_lat, radius, avg_magnitude = zone

        # 生成该区域内的随机地震点
        zone_points = int(n * (radius / sum(z[3] for z in earthquake_zones)))

        for _ in range(zone_points):
            # 在中心点周围随机分布
            lon = center_lon + np.random.uniform(-radius, radius)
            lat = center_lat + np.random.uniform(-radius, radius)

            # 震级在平均震级附近波动
            magnitude = np.random.normal(avg_magnitude, 1.0)
            magnitude = max(3.0, min(9.0, magnitude))  # 限制震级范围

            data.append({
                'location': zone_name,
                'longitude': lon,
                'latitude': lat,
                'magnitude': magnitude,
                'damage': magnitude ** 2  # 模拟破坏程度,与震级平方成正比
            })

    # 创建GeoDataFrame
    gdf = gpd.GeoDataFrame(
        data,
        geometry=[Point(row['longitude'], row['latitude']) for row in data],
        crs="EPSG:4326"
    )

    return gdf


def plot_earthquake_deformation_map(gdf, title="地震震级变形地图", output_file=None):
    """绘制地震震级变形地图

    Args:
        gdf: GeoDataFrame数据
        title: 图表标题
        output_file: 输出文件名,None则不保存
    """
    # 创建图表
    fig, ax = plt.subplots(figsize=(14, 10))

    # 提取原始坐标
    x = gdf.geometry.x
    y = gdf.geometry.y
    magnitudes = gdf['magnitude']

    # 计算变形因子 - 基于震级
    # 震级越高,变形越明显
    max_magnitude = magnitudes.max()
    deformation_factor = (magnitudes / max_magnitude) * 1.5  # 最大变形量为1.5度

    # 应用变形 - 震级越高,偏移越大
    x_deformed = x + np.random.normal(0, deformation_factor)
    y_deformed = y + np.random.normal(0, deformation_factor)

    # 创建变形后的GeoDataFrame
    deformed_gdf = gpd.GeoDataFrame(
        gdf.drop(columns=['geometry']),
        geometry=[Point(x, y) for x, y in zip(x_deformed, y_deformed)],
        crs="EPSG:4326"
    )

    # 将数据转换为Web Mercator投影 (EPSG:3857),与地图背景匹配
    deformed_gdf = deformed_gdf.to_crs(epsg=3857)
    original_gdf = gdf.to_crs(epsg=3857)

    # 绘制地图背景
    ctx.add_basemap(
        ax,
        crs=deformed_gdf.crs.to_string(),
        source=ctx.providers.CartoDB.Positron,
        attribution_size=8
    )

    # 绘制变形后的地震点
    scatter = deformed_gdf.plot(
        ax=ax,
        markersize=deformed_gdf['damage'] * 10,  # 点大小基于破坏程度
        column='magnitude',
        cmap='Reds',  # 红颜色表示更危险
        alpha=0.7,
        edgecolors='black',
        linewidths=0.5,
        legend=True,
        legend_kwds={
            'label': "地震震级 (Richter)",
            'orientation': "vertical",
            'shrink': 0.5
        }
    )

    # 绘制原始位置的浅色点作为参考
    original_gdf.plot(
        ax=ax,
        markersize=10,
        color='lightblue',
        alpha=0.5
    )

    # 添加地震带名称标注
    for location in deformed_gdf['location'].unique():
        loc_points = deformed_gdf[deformed_gdf['location'] == location]
        if len(loc_points) > 0:
            # 计算区域中心点
            center_x = loc_points.geometry.x.mean()
            center_y = loc_points.geometry.y.mean()

            # 添加标注
            ax.annotate(
                location,
                xy=(center_x, center_y),
                xytext=(0, 15),
                textcoords="offset points",
                ha='center',
                va='bottom',
                fontsize=12,
                fontweight='bold',
                bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="gray", alpha=0.8)
            )

    # 设置标题和坐标轴
    ax.set_title(title, fontsize=18)
    ax.set_xlabel('经度', fontsize=12)
    ax.set_ylabel('纬度', fontsize=12)

    # 设置中国大致范围 (Web Mercator投影)
    xlim = (7000000, 14000000)
    ylim = (2000000, 6000000)
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)

    # 添加网格线
    ax.grid(True, linestyle='--', alpha=0.6)

    # 添加图例说明点大小含义
    for size in [4, 6, 8]:
        ax.scatter([], [], s=size ** 2 * 10, c='red', alpha=0.7, label=f'{size}级地震')

    ax.legend(title='地震震级', scatterpoints=1, frameon=True, loc='lower right')

    # 保存图表
    if output_file:
        plt.savefig(output_file, dpi=300, bbox_inches='tight')
        print(f"图表已保存至: {output_file}")

    plt.show()


def get_script_directory():
    """获取当前脚本所在目录"""
    if getattr(sys, 'frozen', False):
        # 如果是打包后的可执行文件
        return os.path.dirname(os.path.abspath(sys.executable))
    else:
        # 如果是脚本文件
        return os.path.dirname(os.path.abspath(__file__))


if __name__ == "__main__":
    # 确保安装了必要的库
    try:
        import contextily as ctx
    except ImportError:
        print("缺少contextily库,正在尝试安装...")
        os.system("pip install contextily")
        import contextily as ctx

    # 获取当前脚本所在目录
    script_dir = get_script_directory()

    # 创建输出目录(在当前脚本所在目录下)
    output_dir = os.path.join(script_dir, "geo_visualizations")
    os.makedirs(output_dir, exist_ok=True)

    # 生成地震数据
    earthquake_data = generate_earthquake_data(n=100)

    # 绘制地震震级变形地图
    plot_earthquake_deformation_map(
        earthquake_data,
        "中国主要地震带震级变形地图",
        os.path.join(output_dir, "earthquake_deformation_map_with_background.png")
    )

实验结果:
在这里插入图片描述

(二)变形地图(Morph Map)

图表特点
  • 核心原理:通过几何变形(如坐标偏移、区域缩放)突出数据差异,原始位置与变形后位置对比展示特征。
  • 关键特征
    • 属性-几何映射:数据值越大,变形量越大(如震级高则偏移距离远)。
    • 视觉对比:结合颜色编码(如震级)与变形量,强化多维度信息。
应用场景
  • 区域数据对比:国家经济指标(GDP)、人口增长差异。
  • 动态模拟:冰川消融、污染物扩散路径。
实现过程(以亚洲国家数据为例)
  1. 数据准备
    • 生成亚洲国家随机数据值,匹配ISO代码(如中国CHN、日本JPN)。
  2. 地图绘制
    • 使用plotlyChoropleth组件,z=data_values映射颜色,scope='asia'聚焦亚洲区域。
    • 墨卡托投影调整经纬度范围,突出东亚与东南亚。
  3. 结果展示
    • 颜色越深表示数据值越高,东南亚部分国家呈现高值色块,中亚为低值区域。

实验代码:

import plotly.graph_objects as go

# 亚洲国家ISO 3166-1 alpha-3代码列表
asian_countries = [
    "CHN", "JPN", "KOR", "PRK", "MNG",  # 东亚
    "MMR", "THA", "LAO", "KHM", "VNM", "MYS", "SGP", "IDN", "BRN", "PHL", "TLS",  # 东南亚
    "IND", "PAK", "BGD", "NPL", "BTN", "LKA", "MDV",  # 南亚
    "AFG", "IRN", "IRQ", "SYR", "JOR", "ISR", "PSE", "SAU", "KWT", "BHR", "QAT", "ARE", "OMN", "YEM", "TUR", "CYP",  # 西亚
    "KAZ", "KGZ", "TJK", "UZB", "TKM"  # 中亚
]

# 为亚洲国家生成随机数据值
import numpy as np
data_values = np.random.randint(10, 100, len(asian_countries)).tolist()

# 创建亚洲变形地图
fig = go.Figure(go.Choropleth(
    locations=asian_countries,
    z=data_values,
    locationmode="ISO-3",  # 使用ISO 3166-1 alpha-3代码
    colorscale="Viridis",
    colorbar_title="数据值",
    marker_line_color='white',  # 国家边界线颜色
    marker_line_width=0.5,  # 国家边界线宽度
))

# 自定义亚洲区域显示
fig.update_layout(
    title="东亚变形地图",
    geo=dict(
        scope="asia",  # 聚焦亚洲区域
        showland=True,
        landcolor="LightGray",
        showcountries=True,
        countrycolor="Black",
        showocean=True,
        oceancolor="LightBlue",
        lonaxis_range=[30, 180],  # 调整经度范围以更好展示亚洲
        lataxis_range=[-10, 60],   # 调整纬度范围
        projection_type="mercator",  # 使用墨卡托投影(适合区域地图)
        # projection_scale=1.2,  # 可调整缩放比例
    )
)

fig.show()

实验结果:
在这里插入图片描述

(三)关联地图(Association Map)

图表特点
  • 核心原理:通过线段、箭头连接地理实体(如城市),展示空间关联关系(如航线、贸易路线)。
  • 关键特征
    • 关系可视化:线段颜色区分关系类型,箭头方向表示流向。
    • 网络结构:突出枢纽节点(如国际航空中心)。
应用场景
  • 交通网络分析:航线、高铁线路分布(如亚太城市航线网络)。
  • 物流与贸易:供应链节点连接、人口迁移路径。
实现过程(以亚太城市航线为例)
  1. 数据准备
    • 定义北京、东京、新加坡等城市坐标,及航线起点、终点、颜色。
  2. 地图绘制
    • 使用plotlyScattergeo绘制城市标记(红色圆点)与航线线段,箭头通过方位角计算方向。
    • 配置地图范围为亚洲,隐藏边框以聚焦数据。
  3. 结果展示
    • 北京、上海、新加坡为核心枢纽,航线覆盖东亚、东南亚及大洋洲,箭头清晰显示飞行方向。

实验代码:

import numpy as np
import plotly.graph_objects as go

# 城市坐标(北京、上海、东京、新加坡、悉尼、迪拜)
cities = {
    "北京": (116.403874, 39.914885),
    "上海": (121.473701, 31.230446),
    "东京": (139.691706, 35.689487),
    "新加坡": (103.850085, 1.289977),
    "悉尼": (151.2093, -33.8688),
    "迪拜": (55.3781, 25.2048)
}

# 定义航线数据(起点、终点、颜色)
routes = [
    ("北京", "上海", "blue"),
    ("北京", "东京", "green"),
    ("北京", "新加坡", "purple"),
    ("上海", "东京", "orange"),
    ("上海", "新加坡", "pink"),
    ("东京", "新加坡", "cyan"),
    ("北京", "悉尼", "red"),
    ("上海", "悉尼", "magenta"),
    ("迪拜", "新加坡", "gold"),
    ("迪拜", "北京", "brown")
]

fig = go.Figure()

# 添加城市标记
for city, (lon, lat) in cities.items():
    fig.add_trace(
        go.Scattergeo(
            lon=[lon],
            lat=[lat],
            mode='markers',
            name=city,
            marker=dict(
                size=14,
                color='red',
                symbol='circle-dot',
                line=dict(width=2, color='white'),  # 白色描边
                opacity=0.9
            )
        )
    )

# 添加航线及箭头
for start, end, color in routes:
    start_lon, start_lat = cities[start]
    end_lon, end_lat = cities[end]

    # 绘制航线线段
    fig.add_trace(
        go.Scattergeo(
            lon=[start_lon, end_lon],
            lat=[start_lat, end_lat],
            mode='lines',
            line=dict(
                width=2,
                color=color,
                dash='solid'
            ),
            showlegend=False  # 隐藏线段图例
        )
    )

    # 计算箭头位置(中点偏移10%距离以避免覆盖城市标记)
    delta_lon = end_lon - start_lon
    delta_lat = end_lat - start_lat
    ratio = 0.9  # 箭头位置比例(0.5为中点,0.9靠近终点)
    arrow_lon = start_lon + delta_lon * ratio
    arrow_lat = start_lat + delta_lat * ratio

    # 添加箭头符号(根据方向调整角度)
    bearing = np.degrees(np.arctan2(delta_lat, delta_lon))  # 计算方位角
    fig.add_trace(
        go.Scattergeo(
            lon=[arrow_lon],
            lat=[arrow_lat],
            mode='markers',
            name=f"{start}-{end}",
            marker=dict(
                size=12,
                color=color,
                symbol='triangle-right' if delta_lon >= 0 else 'triangle-left',  # 左右方向
                angle=bearing - 90,  # 调整箭头角度使其指向终点
                line=dict(width=1, color=color)
            )
        )
    )

# 地图布局设置
fig.update_layout(
    title=dict(
        text="亚太大区主要城市航线网络",
        x=0.5,
        xanchor='center',
        yanchor='top',
        font=dict(size=18, color='darkblue')
    ),
    geo=dict(
        scope='asia',  # 基础范围为亚洲
        projection_type='mercator',
        landcolor='LightGray',
        countrycolor='White',
        showland=True,
        showcountries=True,
        countrywidth=0.5,
        showocean=True,
        oceancolor='LightBlue',
        showlakes=True,
        lakecolor='LightBlue',
        showrivers=True,
        rivercolor='LightBlue',
        showsubunits=True,
        subunitcolor='LightGray',
        showframe=False,  # 隐藏地图边框
        bgcolor='white'
    ),
    margin=dict(l=0, r=0, t=50, b=0),  # 调整地图边距
    height=600,
    width=800
)

fig.show()

实验结果:
在这里插入图片描述

(四)气泡地图(Bubble Map)

图表特点
  • 核心原理:以气泡大小映射一维数据(如人口),颜色映射另一维数据(如GDP),实现双变量可视化。
  • 关键特征
    • 多维编码:位置(坐标)+ 大小(人口)+ 颜色(GDP),支持悬停交互查询详细数据。
    • 密度控制:通过透明度避免气泡重叠。
应用场景
  • 城市对比分析:人口与经济指标(如东京人口多且GDP高)。
  • 生态研究:物种密度与环境因子相关性。
实现过程(以亚洲城市数据为例)
  1. 数据准备
    • 收集北京、东京、新加坡等城市的人口、GDP数据。
  2. 地图绘制
    • 使用plotlyScattergeosize='pop'映射人口规模,color='gdp'映射经济总量,Viridis色系区分高低值。
    • 配置悬停提示展示城市名称、人口、GDP。
  3. 结果展示
    • 东京、上海、北京气泡最大(人口多),颜色最深(GDP高),呈现“人口-经济”正相关。

实验代码:

import plotly.graph_objects as go
import numpy as np

# 模拟数据:城市、坐标、人口(万)、GDP(亿美元)
data = [
    {"city": "北京", "lon": 116.4039, "lat": 39.9149, "pop": 2154, "gdp": 5619},
    {"city": "上海", "lon": 121.4737, "lat": 31.2304, "pop": 2428, "gdp": 6342},
    {"city": "东京", "lon": 139.6917, "lat": 35.6895, "pop": 1396, "gdp": 10220},
    {"city": "新加坡", "lon": 103.8501, "lat": 1.2899, "pop": 570, "gdp": 3720},
    {"city": "悉尼", "lon": 151.2093, "lat": -33.8688, "pop": 531, "gdp": 3375},
    {"city": "迪拜", "lon": 55.3781, "lat": 25.2048, "pop": 330, "gdp": 1580},
    {"city": "孟买", "lon": 72.8777, "lat": 19.0760, "pop": 2135, "gdp": 2248},
    {"city": "首尔", "lon": 126.9780, "lat": 37.5665, "pop": 963, "gdp": 4520},
]

# 提取数据维度
lons = [d["lon"] for d in data]
lats = [d["lat"] for d in data]
pop = [d["pop"] for d in data]
gdp = [d["gdp"] for d in data]
city_names = [d["city"] for d in data]

# 创建气泡图
fig = go.Figure(
    go.Scattergeo(
        lon=lons,
        lat=lats,
        mode='markers',
        text=city_names,
        hovertext=[f"{d['city']}<br>人口: {d['pop']}万<br>GDP: {d['gdp']}亿美元" for d in data],
        marker=dict(
            size=pop,              # 气泡大小映射人口
            sizemin=8,             # 最小气泡尺寸(px)
            sizemode='area',       # 使用面积模式
            sizeref=max(pop) / 30**2,  # 缩放因子,控制最大气泡大小
            color=gdp,             # 颜色映射GDP
            colorscale='Viridis',  # 颜色渐变方案
            colorbar=dict(
                title="GDP(亿美元)",
                title_side="right",  # 颜色条标题位置
                thickness=25,        # 颜色条厚度
                len=0.6,             # 颜色条长度
                yanchor="top",       # 垂直锚点
                y=1.0                # 垂直位置
            ),
            line=dict(width=1, color='white'),  # 气泡描边
            opacity=0.8
        )
    )
)

# 布局设置
fig.update_layout(
    title=dict(
        text="亚洲主要城市人口与GDP气泡图",
        x=0.5,
        xanchor='center',
        yanchor='top',
        font=dict(size=18, color='darkblue')
    ),
    geo=dict(
        scope='asia',          # 显示亚洲区域
        projection_type='mercator',
        landcolor='LightGray',
        countrycolor='White',
        showland=True,
        showcountries=True,
        countrywidth=0.5,
        showocean=True,
        oceancolor='LightBlue',
        lonaxis_range=[40, 160],  # 调整经度范围
        lataxis_range=[-40, 50]   # 调整纬度范围
    ),
    hoverlabel=dict(
        bgcolor='white',
        font_size=12,
        bordercolor='black'
    )
)

# 显示图形
fig.show()

实验结果:
在这里插入图片描述

四、图表总结对比表

图表类型核心特征典型应用场景关键工具/库数据维度
蜂窝热力地图颜色渐变展示点数据密度,支持热力层叠加地震分布、城市热点分析geopandas + matplotlib空间坐标、单变量(震级)
变形地图区域几何变形突出数据差异,颜色编码单变量国家经济对比、动态趋势模拟plotly(Choropleth)区域编码、单变量(数据值)
关联地图线段/箭头刻画空间关联,支持流向与权重展示航线网络、贸易路线分析plotly(Scattergeo)起点/终点坐标、关系类型
气泡地图气泡大小+颜色双变量编码,支持交互查询城市多维指标对比、生态因子分析plotly(Scattergeo)空间坐标、双变量(人口/GDP)

五、实验总结

本次实验通过4类地理可视化图表,系统展示了空间数据的分布、关联与多维特征:

  • 蜂窝热力地图有效揭示了地震数据的空间聚集性;
  • 变形地图通过区域色块对比,直观呈现宏观数据差异;
  • 关联地图清晰刻画了城市间的航线网络结构;
  • 气泡地图通过双变量编码,实现了城市指标的多维对比。

未来可结合实时数据、三维可视化(如Deck.gl)或动态交互(如时间轴),进一步提升地理分析的深度与交互体验。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值