当前位置: 首页 > news >正文

深入解析 KMZ 文件的处理与可视化:从数据提取到地图展示项目实战

文章目录

      • 1. KMZ 文件与 KML 文件简介
        • 1.1 KMZ 文件
        • 1.2 KML 文件
      • 2. Python 环境配置与依赖安装
      • 3. 代码实现详解
        • 3.1 查找 KMZ 文件
        • 3.2 解压 KMZ 文件
        • 3.3 解析 KML 文件
        • 3.4 可视化 KMZ 数据
      • 4. 项目实战
        • 4.1. 数据采集
        • 4.2. 项目完整代码
      • 5. 项目运行与结果展示
      • 6. 总结与展望

在处理地理空间数据时,KMZ 文件是一种常见的格式,用于存储地图和地理信息数据。KMZ 文件是 KML 文件的压缩版本,其中 KML(Keyhole Markup Language)用于描述地理数据的格式。本文将详细介绍如何使用 Python 处理 KMZ 文件,提取其中的地理数据,并将其可视化到地图上。本文的核心代码将涉及文件的解压、KML 文件的解析、GPS 数据的提取以及如何使用 Folium 库将数据展示到地图上。

1. KMZ 文件与 KML 文件简介

1.1 KMZ 文件

KMZ 文件是 KML 文件的压缩版本,通常用于存储 Google Earth 或 Google Maps 中使用的地理信息数据。KMZ 文件可以包含一个或多个 KML 文件以及其他资源文件(如图片、图标等)。KML 文件是基于 XML 的格式,用于描述地理数据的标记、路径、区域等信息。

1.2 KML 文件

KML 文件由 XML 构成,用于存储地理数据,如地点标记、线条、区域、图像叠加等。KML 的基本结构包括:

  • Placemark:标记点
  • Point:点类型
  • LineString:线条
  • Polygon:多边形
  • TimeStamp:时间戳

2. Python 环境配置与依赖安装

在开始之前,确保你的 Python 环境中已经安装了以下依赖:

  • folium:用于地图可视化
  • xml.etree.ElementTree:用于解析 XML 文件
  • zipfile:用于解压 KMZ 文件
  • glob:用于文件路径匹配

可以使用以下命令安装所需的库:

pip install folium

3. 代码实现详解

3.1 查找 KMZ 文件
import os
import globdef find_kmz_files(directory):# 使用 glob 模块查找指定目录下的所有 .kmz 文件kmz_files = glob.glob(os.path.join(directory, '*.kmz'))return kmz_files
  • 功能:遍历指定目录,查找所有以 .kmz 结尾的文件。
  • 实现:使用 glob 模块和通配符模式来匹配所有 KMZ 文件。
3.2 解压 KMZ 文件
import zipfiledef extract_kml_from_kmz(kmz_file_path):# 解压 KMZ 文件with zipfile.ZipFile(kmz_file_path, 'r') as kmz:# 查找 KML 文件kml_files = [name for name in kmz.namelist() if name.lower().endswith('.kml')]if kml_files:kml_file_path = kml_files[0]kmz.extract(kml_file_path, os.path.dirname(kmz_file_path))return os.path.join(os.path.dirname(kmz_file_path), kml_file_path)return None
  • 功能:解压 KMZ 文件,并提取其中的 KML 文件。
  • 实现:使用 zipfile 模块打开 KMZ 文件,查找并解压 KML 文件。
3.3 解析 KML 文件
import xml.etree.ElementTree as ETdef parse_kml(kml_file_path):gps_data = []tree = ET.parse(kml_file_path)root = tree.getroot()# KML 的 XML namespacenamespace = {'kml': 'http://earth.google.com/kml/2.2'}print(f"Root element: {root.tag}")# 查找所有 Placemark 元素for placemark in root.findall('.//kml:Placemark', namespace):coordinates = placemark.find('.//kml:Point/kml:coordinates', namespace)if coordinates is not None:coords = coordinates.text.strip().split(',')if len(coords) >= 3:try:longitude = float(coords[0])latitude = float(coords[1])altitude = float(coords[2])gps_data.append({'latitude': latitude,'longitude': longitude,'altitude': altitude})except ValueError as e:print(f"Error parsing coordinates: {e}")return gps_data
  • 功能:解析 KML 文件,提取 GPS 数据(经纬度和高度)。
  • 实现:使用 xml.etree.ElementTree 解析 XML 格式的 KML 文件,通过查找 Placemark 元素和 coordinates 元素来获取地理数据。
3.4 可视化 KMZ 数据
import folium
from folium.features import CustomIcondef visualize_multiple_kmz_data(kmz_data_list):if not kmz_data_list:print("No GPS data available to visualize.")return# 计算所有经纬度的平均值,作为地图的中心all_latitudes = []all_longitudes = []for kmz_data in kmz_data_list:latitudes = [data['latitude'] for data in kmz_data['gps_data']]longitudes = [data['longitude'] for data in kmz_data['gps_data']]all_latitudes.extend(latitudes)all_longitudes.extend(longitudes)avg_latitude = sum(all_latitudes) / len(all_latitudes)avg_longitude = sum(all_longitudes) / len(all_longitudes)map_center = [avg_latitude, avg_longitude]gps_map = folium.Map(location=map_center, zoom_start=14, tiles='OpenStreetMap')folium.TileLayer(tiles='https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',name='Google Satellite',attr='© Google').add_to(gps_map)folium.LayerControl().add_to(gps_map)# 为每个 KMZ 文件使用不同的颜色colors = ['red', 'blue', 'green', 'purple', 'orange', 'darkred', 'lightred', 'beige', 'darkblue', 'darkgreen', 'cadetblue', 'darkpurple', 'white', 'pink', 'lightblue', 'lightgreen', 'gray', 'black', 'lightgray']for idx, kmz_data in enumerate(kmz_data_list):color = colors[idx % len(colors)]for data in kmz_data['gps_data']:folium.CircleMarker([data['latitude'], data['longitude']],radius=0.5,  # 半径大小color=color,  # 边框颜色fill=True,fill_color=color,  # 填充颜色fill_opacity=0.8).add_to(gps_map)# 绘制路径线并添加箭头for i in range(1, len(kmz_data['gps_data'])):start_point = kmz_data['gps_data'][i-1]end_point = kmz_data['gps_data'][i]# 绘制线条folium.PolyLine(locations=[(start_point['latitude'], start_point['longitude']),(end_point['latitude'], end_point['longitude'])],color=color,weight=2).add_to(gps_map)# 添加箭头folium.Marker(location=[(start_point['latitude'] + end_point['latitude']) / 2,(start_point['longitude'] + end_point['longitude']) / 2],icon=CustomIcon('https://upload.wikimedia.org/wikipedia/commons/e/e5/Black_triangle_pointing_right.svg',icon_size=(10, 10), icon_anchor=(5, 5))).add_to(gps_map)gps_map.save('multiple_kmz_map.html')print("GPS map saved as 'multiple_kmz_map.html'.")
  • 功能:将多个 KMZ 文件的数据可视化到一个地图上,使用不同的颜色表示不同的 KMZ 文件。
  • 实现
    • 计算所有点的平均经纬度作为地图的中心。
    • 使用 folium.Map 创建地图,并添加地图图层。
    • 对每个 KMZ 文件使用不同的颜色,并将其 GPS 数据以 CircleMarker 的形式添加到地图上。

绘制路径线,并在路径中添加箭头指示方向。

4. 项目实战

4.1. 数据采集

两个kmz文件:
在这里插入图片描述
其中一个kmz文件解压,会看到有一个kml文件:
在这里插入图片描述
kml文件打开,会看到一些关键信息,以下是部分信息截图:
在这里插入图片描述

4.2. 项目完整代码
import os
import glob
import folium
import zipfile
import xml.etree.ElementTree as ET
from folium.features import CustomIcondef find_kmz_files(directory):kmz_files = glob.glob(os.path.join(directory, '*.kmz'))return kmz_filesdef extract_kml_from_kmz(kmz_file_path):with zipfile.ZipFile(kmz_file_path, 'r') as kmz:kml_files = [name for name in kmz.namelist() if name.lower().endswith('.kml')]if kml_files:kml_file_path = kml_files[0]kmz.extract(kml_file_path, os.path.dirname(kmz_file_path))return os.path.join(os.path.dirname(kmz_file_path), kml_file_path)return Nonedef parse_kml(kml_file_path):gps_data = []tree = ET.parse(kml_file_path)root = tree.getroot()namespace = {'kml': 'http://earth.google.com/kml/2.2'}for placemark in root.findall('.//kml:Placemark', namespace):coordinates = placemark.find('.//kml:Point/kml:coordinates', namespace)if coordinates is not None:coords = coordinates.text.strip().split(',')if len(coords) >= 3:try:longitude = float(coords[0])latitude = float(coords[1])altitude = float(coords[2])gps_data.append({'latitude': latitude,'longitude': longitude,'altitude': altitude})except ValueError as e:print(f"Error parsing coordinates: {e}")return gps_datadef visualize_multiple_kmz_data(kmz_data_list):if not kmz_data_list:print("No GPS data available to visualize.")returnall_latitudes = []all_longitudes = []for kmz_data in kmz_data_list:latitudes = [data['latitude'] for data in kmz_data['gps_data']]longitudes = [data['longitude'] for data in kmz_data['gps_data']]all_latitudes.extend(latitudes)all_longitudes.extend(longitudes)avg_latitude = sum(all_latitudes) / len(all_latitudes)avg_longitude = sum(all_longitudes) / len(all_longitudes)map_center = [avg_latitude, avg_longitude]gps_map = folium.Map(location=map_center, zoom_start=14, tiles='OpenStreetMap')folium.TileLayer(tiles='https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',name='Google Satellite',attr='© Google').add_to(gps_map)folium.LayerControl().add_to(gps_map)colors = ['red', 'blue', 'green', 'purple', 'orange', 'darkred', 'lightred', 'beige', 'darkblue', 'darkgreen', 'cadetblue', 'darkpurple', 'white', 'pink', 'lightblue', 'lightgreen', 'gray', 'black', 'lightgray']for idx, kmz_data in enumerate(kmz_data_list):color = colors[idx % len(colors)]for data in kmz_data['gps_data']:folium.CircleMarker([data['latitude'], data['longitude']],radius=0.5,color=color,fill=True,fill_color=color,fill_opacity=0.8).add_to(gps_map)for i in range(1, len(kmz_data['gps_data'])):start_point = kmz_data['gps_data'][i-1]end_point = kmz_data['gps_data'][i]folium.PolyLine(locations=[(start_point['latitude'], start_point['longitude']),(end_point['latitude'], end_point['longitude'])],color=color,weight=2).add_to(gps_map)folium.Marker(location=[(start_point['latitude'] + end_point['latitude']) / 2,(start_point['longitude'] + end_point['longitude']) / 2],icon=CustomIcon('https://upload.wikimedia.org/wikipedia/commons/e/e5/Black_triangle_pointing_right.svg',icon_size=(10, 10), icon_anchor=(5, 5))).add_to(gps_map)gps_map.save('multiple_kmz_map.html')print("GPS map saved as 'multiple_kmz_map.html'.")if __name__ == '__main__':directory_path = "F:\\notebookComputer\\20240723"kmz_files = find_kmz_files(directory_path)if kmz_files:kmz_data_list = []for kmz_file_path in kmz_files:kml_file_path = extract_kml_from_kmz(kmz_file_path)if kml_file_path:parsed_gps_data = parse_kml(kml_file_path)print(f"Parsed GPS data: {parsed_gps_data}")kmz_data_list.append({'file_name': os.path.basename(kmz_file_path),'gps_data': parsed_gps_data})if kmz_data_list:visualize_multiple_kmz_data(kmz_data_list)else:print("No GPS data available to visualize.")else:print(f"No .kmz files found in directory: {directory_path}")

5. 项目运行与结果展示

在代码执行完毕后,将会生成一个名为 multiple_kmz_map.html 的文件,该文件可以用浏览器打开以查看地图上的标记点和路径。地图将会显示所有 KMZ 文件中提取的 GPS 数据,每个文件的标记点使用不同的颜色表示。
multiple_kmz_map.html文件不好截图如下:
在这里插入图片描述
浏览器打开multiple_kmz_map.html文件效果图如下:
在这里插入图片描述

6. 总结与展望

本文详细介绍了如何使用 Python 处理 KMZ 文件,提取其中的 GPS 数据,并通过 Folium 库将其可视化。通过将 KMZ 文件中的地理数据转换为地图标记点和路径线,我们可以更直观地分析和展示地理数据。未来的工作可以包括支持更多的地理数据格式、添加更多的地图样式和功能、以及优化代码的性能和可读性。根据需求,文章可以继续扩展,以包含更多的技术细节、优化建议和实际应用场景的分析。

欢迎点赞|关注|收藏|评论,您的肯定是我创作的动力

在这里插入图片描述

相关文章:

深入解析 KMZ 文件的处理与可视化:从数据提取到地图展示项目实战

文章目录 1. KMZ 文件与 KML 文件简介1.1 KMZ 文件1.2 KML 文件 2. Python 环境配置与依赖安装3. 代码实现详解3.1 查找 KMZ 文件3.2 解压 KMZ 文件3.3 解析 KML 文件3.4 可视化 KMZ 数据 4. 项目实战4.1. 数据采集4.2. 项目完整代码 5. 项目运行与结果展示6. 总结与展望 在处理…...

YOLOv5轻量化改进 | backbone | 结合MobileNetV4(包含多个结构和使用方式)

YOLOv5轻量化改进 | backbone | 结合MobileNetV4(包含多个结构) 本文介绍论文原理介绍网络代码多种yaml设置网络测试及实验结果<!-- 这里放入论文图片 --> &emsp;;本文介绍 本文给大家带来的改进机制是结合MobileNetV4骨干网络,其中来自2024.5月发布的MobileNetV4…...

学习安卓开发遇到的问题

问题1&#xff1a;学习禁用与恢复按钮中&#xff1a; java代码报错&#xff1a;报错代码是 R.id.btn_enable;case R.id.btn_disable;case R.id.btn_test: 代码如下&#xff1a;&#xff08;实现功能在代码后面&#xff09; package com.example.apptest;import static java.…...

数学建模--禁忌搜索

目录 算法基本原理 关键要素 应用实例 实现细节 python代码示例 总结 禁忌搜索算法在解决哪些具体类型的组合优化问题中最有效&#xff1f; 禁忌搜索算法的邻域结构设计有哪些最佳实践或案例研究&#xff1f; 如何动态更新禁忌表以提高禁忌搜索算法的效率和性能&#…...

LeetCode 第136场双周赛个人题解

Q1. 求出胜利玩家的数目 原题链接 Q1. 求出胜利玩家的数目 思路分析 直接模拟 时间复杂度&#xff1a;O(N) AC代码 class Solution { public:int winningPlayerCount(int n, vector<vector<int>>& pick) {unordered_map<int, unordered_map<int, …...

The operation was rejected by your operating system. code CERT_HAS_EXPIRED报错解决

各种报错&#xff0c;试了清缓存&#xff0c;使用管理员权限打开命令行工具&#xff0c;更新npm&#xff0c;都不好使 最终解决&#xff1a;删除 c:/user/admin/ .npmrc...

[Git][基本操作]详细讲解

目录 1.创建本地仓库2.配置 Git3.添加文件1.添加文件2.提交文件3.其他 && 说明 4.删除文件5.跟踪修改文件6.版本回退7.撤销修改0.前言1.未add2.已add&#xff0c;未commit3.已add&#xff0c;已commit 1.创建本地仓库 创建⼀个Git本地仓库&#xff1a;git init运行该命…...

springMVC中从Excel文件中导入导出数据

目录 1. 数据库展示2. 导入依赖3. 写方法3.1 导入数据3.2 导出数据 4. 效果5. 不足6. 参考链接 1. 数据库展示 2. 导入依赖 pom.xml <!--文件上传处理--><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId>&…...

C++的STL简介(三)

目录 1.vector的模拟实现 1.1begin&#xff08;&#xff09; 1.2end&#xff08;&#xff09; 1.3打印信息 1.4 reserve&#xff08;&#xff09; 1.5 size&#xff08;&#xff09; 1.6 capacity&#xff08;&#xff09; 1.7 push_back() 1.8[ ] 1.9 pop_back() 1.10 insert&…...

BERT模型

BERT模型是由谷歌团队于2019年提出的 Encoder-only 的 语言模型&#xff0c;发表于NLP顶会ACL上。原文题目为&#xff1a;《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》链接 在前大模型时代&#xff0c;BERT模型可以算是一个参数量比…...

举例说明计算机视觉(CV)技术的优势和挑战

计算机视觉&#xff08;CV&#xff09;技术是通过计算机模拟和处理图像与视频数据来模拟人类视觉的能力。它可以带来许多优势&#xff0c;也面临一些挑战。 优势&#xff1a; 自动化&#xff1a;CV技术可以自动处理大量的图像和视频数据&#xff0c;从而提高工作效率和准确性。…...

Animate软件基础:关于补间动画中的图层

Animate 文档中的每一个场景都可以包含任意数量的时间轴图层。使用图层和图层文件夹可组织动画序列的内容和分隔动画对象。在图层和文件夹中组织它们可防止它们在重叠时相互擦除、连接或分段。若要创建一次包含多个元件或文本字段的补间移动的动画&#xff0c;请将每个对象放置…...

mac|安装hashcat(压缩包密码p解)

一、安装Macports&#xff08;如果有brew就不用这一步&#xff09; 根据官网文档&#xff1a;The MacPorts Project -- Download & Installation&#xff0c;安装步骤如下 1、下载MacPorts&#xff0c;这里我用的是tar.gz &#xff0c;可以通过keka&#xff08;keka安装在…...

【保姆级系列:锐捷模拟器的下载安装使用全套教程】

保姆级系列&#xff1a;锐捷模拟器的下载安装使用全套教程 1.介绍2.下载3.安装4.实践教程5.验证 1.介绍 锐捷目前可以通过EVE-NG来模拟自己家的路由器&#xff0c;交换机&#xff0c;防火墙。实现方式是把自己家的镜像导入到EVE-ng里面来运行。下面主要就是介绍如何下载镜像和…...

virtualbox7安装centos7.9配置静态ip

1.背景 我大概在一年之前安装virtualbox7centos7.9的环境&#xff0c;但看视频说用vagrant启动的窗口可以不用第三方工具(比如xshell、secure等)连接centos7.9&#xff0c;于是尝鲜试了下还可以&#xff0c;导致系统文件格式是vmdk了&#xff08;网上有vmdk转vdi的方法&#xf…...

结构型设计模式:桥接/组合/装饰/外观/享元

结构型设计模式&#xff1a;适配器/代理 (qq.com)...

vLLM初识(一)

vLLM初识&#xff08;一&#xff09; 前言 在LLM推理优化——KV Cache篇&#xff08;百倍提速&#xff09;中&#xff0c;我们已经介绍了KV Cache技术的原理&#xff0c;从中我们可以知道&#xff0c;KV Cache本质是空间换时间的技术&#xff0c;对于大型模型和长序列&#xf…...

【Apache Doris】周FAQ集锦:第 18 期

【Apache Doris】周FAQ集锦&#xff1a;第 18 期 SQL问题数据操作问题运维常见问题其它问题关于社区 欢迎查阅本周的 Apache Doris 社区 FAQ 栏目&#xff01; 在这个栏目中&#xff0c;每周将筛选社区反馈的热门问题和话题&#xff0c;重点回答并进行深入探讨。旨在为广大用户…...

docker部署可执行的jar

1.将项目打包&#xff0c;上传到服务器的指定目录 2.在该目录下创建Dockerfile文件 3.Dockerfile写入如下指令 # 基于哪个镜像 FROM java:8 # 拷贝文件到容器&#xff0c;也可以直接写成ADD xxxxx.jar /app.jar ADD springboot-file-0.0.1.jar file.jar RUN bash -c touch /…...

OpenCV||超详细的图像处理模块

一、颜色变换cvtColor dst cv2.cvtColor(src, code[, dstCn[, dst]]) src: 输入图像&#xff0c;即要进行颜色空间转换的原始图像。code: 转换代码&#xff0c;指定要执行的颜色空间转换类型。这是一个必需的参数&#xff0c;决定了源颜色空间到目标颜色空间的转换方式。dst…...

Seraphine:你的英雄联盟智能游戏伙伴,让每一局游戏都更从容

Seraphine&#xff1a;你的英雄联盟智能游戏伙伴&#xff0c;让每一局游戏都更从容 【免费下载链接】Seraphine 英雄联盟战绩查询工具 项目地址: https://gitcode.com/gh_mirrors/se/Seraphine 你是否曾在英雄联盟的BP阶段犹豫不决&#xff0c;错过了最佳选择&#xff1…...

5分钟在Mac上实现专业级无线直播:DistroAV NDI插件终极配置指南

5分钟在Mac上实现专业级无线直播&#xff1a;DistroAV NDI插件终极配置指南 【免费下载链接】obs-ndi DistroAV (formerly OBS-NDI): NDI integration for OBS Studio 项目地址: https://gitcode.com/gh_mirrors/ob/obs-ndi 还在为Mac电脑上的多机位直播设置而烦恼吗&am…...

CATIA二次开发—API高效查询与架构解析

1. CATIA二次开发入门&#xff1a;从V5到V6的跨越挑战 如果你是从CATIA V5转向V6开发的工程师&#xff0c;可能会遇到这样的困惑&#xff1a;为什么在V5中得心应手的API调用方式&#xff0c;到了V6就完全不管用了&#xff1f;这就像突然从手动挡汽车换成了自动驾驶电动车&#…...

从量子自旋到量子比特:原理、应用与工程实践全解析

1. 从“旋转的电子”到“内禀角动量”&#xff1a;自旋概念的祛魅如果你在大学里上过量子力学课&#xff0c;大概率在某个时刻被“自旋”这个概念迎面撞上。我记得当时教授在黑板上写下“电子自旋为1/2”&#xff0c;然后试图用一个小球绕自身轴旋转的经典图像来解释&#xff0…...

保姆级教程:在银河麒麟V10上为gcc编译的程序添加可执行权限(附kysec_set命令详解)

银河麒麟V10系统下gcc编译程序执行权限问题全解析 在银河麒麟V10操作系统中&#xff0c;许多开发者首次使用gcc编译程序后&#xff0c;会遇到一个看似简单却令人困惑的问题&#xff1a;明明已经为生成的可执行文件添加了传统Linux权限&#xff08;如chmod x&#xff09;&#…...

保姆级教程:SAP S/4HANA数据迁移,用LTMC从零导入会计科目(附模板避坑指南)

SAP S/4HANA会计科目迁移实战&#xff1a;LTMC工具全流程详解与避坑手册 当企业首次部署SAP S/4HANA时&#xff0c;会计科目主数据的迁移往往是财务模块实施的关键第一步。不同于传统ECC系统&#xff0c;S/4HANA的简化数据模型对会计科目结构提出了新要求&#xff0c;而Migrati…...

PyTorch模型保存加载避坑指南:从state_dict到checkpoint,这5种场景你都会了吗?

PyTorch模型保存加载避坑指南&#xff1a;从state_dict到checkpoint&#xff0c;这5种场景你都会了吗&#xff1f; 在深度学习项目的实际开发中&#xff0c;模型保存与加载看似简单&#xff0c;却隐藏着无数"坑点"。我曾见过团队因一个错误的map_location参数导致生…...

告别覆盖烦恼:手把手教你让OpenLayers专题图在Cesium三维地球中持续显示

告别覆盖烦恼&#xff1a;手把手教你让OpenLayers专题图在Cesium三维地球中持续显示 当二维地图与三维地球在同一个应用中切换时&#xff0c;最令人头疼的问题莫过于精心设计的专题图层突然消失。这种割裂体验不仅影响用户操作流畅性&#xff0c;更可能导致关键业务信息丢失。本…...

Origin 9.1 保姆级教程:从数据归一化到论文级图表导出(附避坑指南)

Origin 9.1 科研数据处理与图表输出全流程实战指南 科研数据的可视化呈现是论文写作中不可或缺的一环。作为一款功能强大的科学绘图软件&#xff0c;Origin 9.1在学术界有着广泛的应用。本文将系统性地介绍从数据预处理到高质量图表导出的完整工作流程&#xff0c;特别针对科研…...

Blender 3MF插件:终极指南 - 如何轻松实现3D打印设计一体化

Blender 3MF插件&#xff1a;终极指南 - 如何轻松实现3D打印设计一体化 【免费下载链接】Blender3mfFormat Blender add-on to import/export 3MF files 项目地址: https://gitcode.com/gh_mirrors/bl/Blender3mfFormat 你是否曾经在Blender中精心设计了3D模型&#xff…...