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

国科联创网站建设嘉兴市平湖市建设局网站

国科联创网站建设,嘉兴市平湖市建设局网站,网站建设运营方案 团队,怎么自己网站搜不到#x1f310;【开源解析】基于PyQt5Folium的谷歌地图应用开发#xff1a;从入门到实战 #x1f308; 个人主页#xff1a;创客白泽 - CSDN博客 #x1f525; 系列专栏#xff1a;#x1f40d;《Python开源项目实战》 #x1f4a1; 热爱不止于代码#xff0c;热情源自每…【开源解析】基于PyQt5Folium的谷歌地图应用开发从入门到实战 个人主页创客白泽 - CSDN博客 系列专栏《Python开源项目实战》 热爱不止于代码热情源自每一个灵感闪现的夜晚。愿以开源之火点亮前行之路。 如果觉得这篇文章有帮助欢迎您一键三连分享给更多人哦 概述 在当今数据可视化与地理信息系统的交叉领域交互式地图应用已成为不可或缺的工具。本文将深入剖析一个基于Python技术栈PyQt5FoliumGeopy开发的**谷歌地图桌面应用**它集成了地址解析、地图标注、距离测量等实用功能并提供了三种不同的地图样式选择。 相较于传统Web地图应用本项目的创新点在于 桌面端集成通过PyQt5实现原生应用体验混合渲染技术结合Folium的HTML生成与QWebEngineView的嵌入式渲染跨框架通信实现Python与JavaScript的双向交互轻量级架构无需复杂GIS系统即可实现核心功能 ️ 功能特性 核心功能矩阵 功能模块实现技术特色说明地理编码Geopy/Nominatim支持全球地址解析地图渲染FoliumLeaflet三种专业地图样式距离测量Geodesic算法高精度大圆距离计算交互界面PyQt5响应式桌面UI地图导出HTML5可独立运行的网页地图 特色功能详解 智能地址解析基于OpenStreetMap的Nominatim服务支持模糊地址匹配实时距离测量选择两个标记点即可显示精确的球面距离动态标记高亮可视化连线辅助空间关系分析多地图样式街道图、卫星图、地形图一键切换跨平台运行生成的HTML地图可在任何浏览器查看 效果展示 街道地图 卫星地图 地形图 距离测量演示 实现步骤详解 1. 环境搭建 pip install PyQt5 folium geopy PyQtWebEngine2. 核心架构设计 3. 关键技术实现 3.1 混合地图渲染 def initialize_map(self):# 加载Leaflet库html !DOCTYPE htmlhtmlheadlink relstylesheet hrefhttps://unpkg.com/leaflet1.9.4/dist/leaflet.css/script srchttps://unpkg.com/leaflet1.9.4/dist/leaflet.js/script/headbodydiv idmap/divscript// JavaScript地图控制逻辑var map L.map(map).setView([39.9042, 116.4074], 4);/script/body/htmlself.map_view.setHtml(html)3.2 跨语言通信 # Python调用JavaScript self.map_view.page().runJavaScript(addMarker(39.9, 116.4, 北京, 中国首都);)# JavaScript回调Python self.map_view.page().runJavaScript(map.on(click, function(e) {console.log(e.latlng);}); )3.3 距离测量算法 from geopy.distance import geodesicdef calculate_distance(loc1, loc2):使用Vincenty公式计算球面距离return geodesic((loc1[latitude], loc1[longitude]),(loc2[latitude], loc2[longitude])).kilometers代码深度解析 1. 地理编码服务封装 def geocode_location(self, address):try:location self.geolocator.geocode(address)if location:return (location.latitude, location.longitude)return Noneexcept (GeocoderTimedOut, GeocoderServiceError) as e:# 实现自动重试机制time.sleep(0.5)return self.geocode_location(address)优化点增加了异常处理和自动重试机制提高服务稳定性 2. 动态标记管理 def update_embedded_map(self):# 使用JS批量操作DOM元素js_clear clearMarkers();js_add_markers []for loc in self.locations:js_add_markers.append(faddMarker({loc[latitude]}, {loc[longitude]}, f{loc[name]}, {loc[address]});)self.map_view.page().runJavaScript(js_clear .join(js_add_markers))性能优化减少Python-JS通信次数使用批量操作提升渲染效率 3. 地图样式热切换 self.map_styles { 街道地图: {url: https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png,attr: OpenStreetMap},️ 卫星地图: {url: https://mt1.google.com/vt/lyrssx{x}y{y}z{z},attr: Google} }def update_map_style(self):style next(s for s in self.map_styles if self.style_buttons[s].isChecked())js fmap.eachLayer(layer {{if (layer instanceof L.TileLayer) {{map.removeLayer(layer);}}}});L.tileLayer({self.map_styles[style][url]}, {{attribution: {self.map_styles[style][attr]}}}).addTo(map);self.map_view.page().runJavaScript(js)源码下载 import folium from geopy.geocoders import Nominatim from geopy.distance import geodesic # 添加距离计算功能 from geopy.exc import GeocoderTimedOut, GeocoderServiceError from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTreeWidget, QTreeWidgetItem,QRadioButton, QGroupBox, QFileDialog, QMessageBox, QScrollArea) from PyQt5.QtCore import Qt, QUrl, QTimer from PyQt5.QtWebEngineWidgets import QWebEngineView from PyQt5.QtGui import QIcon import sys import webbrowser import os import timeclass SimpleMapViewerApp(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle(谷歌桌面地图)self.setGeometry(100, 100, 1200, 800)self.geolocator Nominatim(user_agentsimple_map_viewer)self.locations []self.current_map_file os.path.join(os.path.expanduser(~), map.html)self.map_view Noneself.map_initialized Falseself.selected_markers [] # 存储选中的标记用于距离计算# 地图样式选项self.map_styles { 街道地图: https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png,️ 卫星地图: https://mt1.google.com/vt/lyrssx{x}y{y}z{z},⛰️ 地形图: https://mt1.google.com/vt/lyrspx{x}y{y}z{z}}# 创建UIself.create_widgets()# 延迟初始化地图确保WebEngineView完全加载QTimer.singleShot(500, self.initialize_map)def create_widgets(self):# 主窗口布局main_widget QWidget()self.setCentralWidget(main_widget)main_layout QHBoxLayout(main_widget)# 左侧控制面板control_panel QWidget()control_panel.setMinimumWidth(350)control_panel.setMaximumWidth(400)control_layout QVBoxLayout(control_panel)# 地图样式选择style_group QGroupBox(️ 地图样式)style_layout QVBoxLayout()self.style_buttons []for style_name in self.map_styles:btn QRadioButton(style_name)btn.toggled.connect(lambda checked, namestyle_name: self.update_map_style() if checked else None)style_layout.addWidget(btn)self.style_buttons.append(btn)self.style_buttons[0].setChecked(True)style_group.setLayout(style_layout)control_layout.addWidget(style_group)# 搜索框search_group QGroupBox( 位置搜索)search_layout QVBoxLayout()self.search_entry QLineEdit()self.search_entry.setPlaceholderText(输入地址或地名...)search_layout.addWidget(self.search_entry)search_btn QPushButton(搜索)search_btn.setIcon(QIcon.fromTheme(edit-find))search_btn.clicked.connect(self.search_location)search_layout.addWidget(search_btn)search_group.setLayout(search_layout)control_layout.addWidget(search_group)# 位置列表list_group QGroupBox( 位置列表)list_layout QVBoxLayout()self.location_list QTreeWidget()self.location_list.setHeaderLabels([名称, 地址])self.location_list.setColumnWidth(0, 150)self.location_list.setSelectionMode(QTreeWidget.ExtendedSelection)self.location_list.itemSelectionChanged.connect(self.on_location_selection_changed) # 添加选择变化事件list_layout.addWidget(self.location_list)# 距离显示标签self.distance_label QLabel(两地距离: 未选择)self.distance_label.setAlignment(Qt.AlignCenter)self.distance_label.setStyleSheet(font-weight: bold; color: #2c3e50;)list_layout.addWidget(self.distance_label)# 列表操作按钮list_btn_layout QHBoxLayout()remove_btn QPushButton(️ 删除选中)remove_btn.clicked.connect(self.remove_location)list_btn_layout.addWidget(remove_btn)clear_btn QPushButton( 清空列表)clear_btn.clicked.connect(self.clear_locations)list_btn_layout.addWidget(clear_btn)list_layout.addLayout(list_btn_layout)list_group.setLayout(list_layout)control_layout.addWidget(list_group)# 添加位置表单form_group QGroupBox(➕ 添加位置)form_layout QVBoxLayout()name_layout QHBoxLayout()name_layout.addWidget(QLabel(名称:))self.name_entry QLineEdit()name_layout.addWidget(self.name_entry)form_layout.addLayout(name_layout)addr_layout QHBoxLayout()addr_layout.addWidget(QLabel(地址:))self.address_entry QLineEdit()addr_layout.addWidget(self.address_entry)form_layout.addLayout(addr_layout)add_btn QPushButton(➕ 添加位置)add_btn.clicked.connect(self.add_location)form_layout.addWidget(add_btn)form_group.setLayout(form_layout)control_layout.addWidget(form_group)# 地图操作按钮map_btn_group QGroupBox(️ 地图操作)map_btn_layout QHBoxLayout()create_btn QPushButton(️ 生成地图)create_btn.clicked.connect(self.create_map)map_btn_layout.addWidget(create_btn)show_btn QPushButton( 查看地图)show_btn.clicked.connect(self.show_map)map_btn_layout.addWidget(show_btn)map_btn_group.setLayout(map_btn_layout)control_layout.addWidget(map_btn_group)control_layout.addStretch()# 右侧地图预览self.map_view QWebEngineView()self.map_view.setHtml(self.get_empty_html())# 添加到主布局main_layout.addWidget(control_panel)main_layout.addWidget(self.map_view, stretch1)def get_empty_html(self):返回初始空白HTMLreturn !DOCTYPE htmlhtmlheadtitle地图预览/titlemeta charsetutf-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0/headbodydiv idmap styleheight:100%;width:100%;/div/body/htmldef initialize_map(self):初始化地图确保Leaflet库正确加载html !DOCTYPE htmlhtmlheadtitle地图预览/titlemeta charsetutf-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0link relstylesheet hrefhttps://unpkg.com/leaflet1.9.4/dist/leaflet.cssintegritysha256-p4NxAoJBhIINhmNHrzRCf9tD/miZyoHS5obTRR9BMYcrossorigin/stylebody { margin: 0; padding: 0; }#map { height: 100vh; width: 100%; }/style/headbodydiv idmap/divscript srchttps://unpkg.com/leaflet1.9.4/dist/leaflet.jsintegritysha256-20nQCchB9co0qIjJZRGuk2/Z9VMkNiyxNV1lvTlZBocrossorigin/scriptscriptvar map L.map(map).setView([39.9042, 116.4074], 4);var osmLayer L.tileLayer(https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png, {attribution: copy; a hrefhttps://www.openstreetmap.org/copyrightOpenStreetMap/a contributors});osmLayer.addTo(map);// 存储标记的数组var markers [];var selectedMarkers [];var line null;function clearMarkers() {for (var i 0; i markers.length; i) {map.removeLayer(markers[i]);}markers [];if (line) {map.removeLayer(line);line null;}}function addMarker(lat, lng, name, address) {var marker L.marker([lat, lng]).addTo(map).bindPopup(b name /bbr address).bindTooltip(name);markers.push(marker);return marker;}function setView(lat, lng, zoom) {map.setView([lat, lng], zoom);}function highlightMarkers(markerIndices) {// 重置所有标记样式for (var i 0; i markers.length; i) {markers[i].setIcon(L.icon({iconUrl: https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png,iconSize: [25, 41],iconAnchor: [12, 41],popupAnchor: [1, -34],shadowUrl: https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png,shadowSize: [41, 41]}));}// 清除之前的线if (line) {map.removeLayer(line);line null;}// 高亮选中的标记selectedMarkers [];for (var i 0; i markerIndices.length; i) {var idx markerIndices[i];if (idx 0 idx markers.length) {markers[idx].setIcon(L.icon({iconUrl: https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png,iconSize: [25, 41],iconAnchor: [12, 41],popupAnchor: [1, -34],shadowUrl: https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png,shadowSize: [41, 41]}));selectedMarkers.push(markers[idx]);}}// 如果选中了两个点绘制连线if (selectedMarkers.length 2) {var latlngs [selectedMarkers[0].getLatLng(),selectedMarkers[1].getLatLng()];line L.polyline(latlngs, {color: red,weight: 3,opacity: 0.7,dashArray: 10, 10}).addTo(map);}}/script/body/htmlself.map_view.setHtml(html)self.map_initialized TrueQTimer.singleShot(500, self.update_map_style)def on_location_selection_changed(self):当位置列表选择变化时触发selected_items self.location_list.selectedItems()selected_indices [self.location_list.indexOfTopLevelItem(item) for item in selected_items]# 更新地图上的高亮标记if self.map_initialized:js fhighlightMarkers({selected_indices});self.map_view.page().runJavaScript(js)# 计算并显示距离if len(selected_indices) 2:loc1 self.locations[selected_indices[0]]loc2 self.locations[selected_indices[1]]# 使用geodesic计算两点间距离point1 (loc1[latitude], loc1[longitude])point2 (loc2[latitude], loc2[longitude])distance geodesic(point1, point2).kilometersself.distance_label.setText(f两地距离: {distance:.2f} 公里)else:self.distance_label.setText(两地距离: 请选择两个地点)def update_map_style(self):更新地图样式if not self.map_initialized:returnfor btn in self.style_buttons:if btn.isChecked():style_name btn.text()tiles self.map_styles[style_name]breakjs fvar newLayer L.tileLayer({tiles}, {{attribution: copy; a hrefhttps://www.openstreetmap.org/copyrightOpenStreetMap/a contributors}});// 先清除所有瓦片图层map.eachLayer(function(layer) {{if (layer instanceof L.TileLayer) {{map.removeLayer(layer);}}}});// 添加新图层newLayer.addTo(map);self.map_view.page().runJavaScript(js)self.update_embedded_map()def geocode_location(self, address):将地址转换为经纬度try:location self.geolocator.geocode(address)if location:return (location.latitude, location.longitude)return Noneexcept (GeocoderTimedOut, GeocoderServiceError) as e:QMessageBox.critical(self, 错误, f地理编码错误: {e})return Nonedef add_location(self):添加位置到列表name self.name_entry.text().strip()address self.address_entry.text().strip()if not name or not address:QMessageBox.warning(self, 警告, 请填写名称和地址)returncoords self.geocode_location(address)if coords:self.locations.append({name: name,address: address,latitude: coords[0],longitude: coords[1]})item QTreeWidgetItem([name, address])self.location_list.addTopLevelItem(item)self.name_entry.clear()self.address_entry.clear()self.update_embedded_map()else:QMessageBox.critical(self, 错误, f无法找到地址: {address})def remove_location(self):删除选中的位置selected self.location_list.selectedItems()if not selected:QMessageBox.warning(self, 警告, 请先选择要删除的位置)returnfor item in selected:index self.location_list.indexOfTopLevelItem(item)if 0 index len(self.locations):del self.locations[index]self.location_list.takeTopLevelItem(index)self.update_embedded_map()self.distance_label.setText(两地距离: 未选择) # 清除距离显示def clear_locations(self):清空所有位置if not self.locations:returnreply QMessageBox.question(self, 确认, 确定要清空所有位置吗?, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)if reply QMessageBox.Yes:self.locations.clear()self.location_list.clear()self.update_embedded_map()self.distance_label.setText(两地距离: 未选择) # 清除距离显示def search_location(self):搜索位置并定位query self.search_entry.text().strip()if not query:QMessageBox.warning(self, 警告, 请输入搜索内容)returntry:location self.geolocator.geocode(query)if location:js fsetView({location.latitude}, {location.longitude}, 15);addMarker({location.latitude}, {location.longitude}, {query.replace(, \\)}, );self.map_view.page().runJavaScript(js)else:QMessageBox.information(self, 提示, 未找到匹配的位置)except Exception as e:QMessageBox.critical(self, 搜索错误, str(e))def create_map(self):创建地图并添加所有位置标记if not self.locations:QMessageBox.warning(self, 警告, 没有可显示的位置)return# 使用folium创建地图first_loc self.locations[0]map_obj folium.Map(location[first_loc[latitude], first_loc[longitude]],zoom_start12)# 根据当前选择的样式设置地图瓦片for btn in self.style_buttons:if btn.isChecked():style_name btn.text()tiles self.map_styles[style_name]breakif style_name 街道地图:tiles OpenStreetMapattr Noneelif style_name ️ 卫星地图:tiles https://mt1.google.com/vt/lyrssx{x}y{y}z{z}attr Google Satelliteelif style_name ⛰️ 地形图:tiles https://mt1.google.com/vt/lyrspx{x}y{y}z{z}attr Google Terrainif attr:folium.TileLayer(tilestiles, attrattr, namestyle_name).add_to(map_obj)else:folium.TileLayer(tilestiles, namestyle_name).add_to(map_obj)# 添加所有位置标记for loc in self.locations:folium.Marker(location[loc[latitude], loc[longitude]],popupfb{loc[name]}/bbr{loc[address]},tooltiploc[name]).add_to(map_obj)# 如果有两个点被选中添加连线selected_items self.location_list.selectedItems()if len(selected_items) 2:loc1 self.locations[self.location_list.indexOfTopLevelItem(selected_items[0])]loc2 self.locations[self.location_list.indexOfTopLevelItem(selected_items[1])]# 添加两点间连线folium.PolyLine(locations[[loc1[latitude], loc1[longitude]],[loc2[latitude], loc2[longitude]]],colorred,weight3,opacity0.7,dash_array10, 10).add_to(map_obj)# 计算并显示距离distance geodesic((loc1[latitude], loc1[longitude]),(loc2[latitude], loc2[longitude])).kilometers# 在两点中间添加距离标签midpoint [(loc1[latitude] loc2[latitude]) / 2,(loc1[longitude] loc2[longitude]) / 2]folium.Marker(locationmidpoint,iconfolium.DivIcon(icon_size(150, 36),icon_anchor(75, 18),htmlfdiv stylefont-size: 12pt; color: red; background: white; padding: 2px; border-radius: 3px;{distance:.2f} km/div),tooltipf直线距离: {distance:.2f} 公里).add_to(map_obj)# 保存地图file_path, _ QFileDialog.getSaveFileName(self, 保存地图, self.current_map_file, HTML Files (*.html))if file_path:self.current_map_file file_pathmap_obj.save(self.current_map_file)QMessageBox.information(self, 成功, f地图已生成: {self.current_map_file})def show_map(self):在浏览器中显示地图if not os.path.exists(self.current_map_file):QMessageBox.critical(self, 错误, 请先生成地图)returnwebbrowser.open(file:// os.path.realpath(self.current_map_file))def update_embedded_map(self):更新内嵌地图视图if not self.map_initialized or not hasattr(self, map_view):return# 清除所有标记self.map_view.page().runJavaScript(clearMarkers();)if not self.locations:# 如果没有位置重置到默认视图self.map_view.page().runJavaScript(setView(39.9042, 116.4074, 4);)return# 设置地图中心和缩放级别first_loc self.locations[0]self.map_view.page().runJavaScript(fsetView({first_loc[latitude]}, {first_loc[longitude]}, 12);)# 添加标记for loc in self.locations:js faddMarker({loc[latitude]}, {loc[longitude]}, {loc[name].replace(, \\)}, {loc[address].replace(, \\)});self.map_view.page().runJavaScript(js)def main():app QApplication(sys.argv)app.setStyle(Fusion) # 使用Fusion风格使UI更现代window SimpleMapViewerApp()window.show()sys.exit(app.exec_())if __name__ __main__:main()扩展方向 数据持久化集成SQLite存储位置数据轨迹绘制支持路径规划和导航功能POI搜索接入更多地理编码服务提供商3D视图集成Cesium实现三维可视化插件系统支持功能模块动态加载 总结 本文详细剖析了基于PyQt5和Folium的地图应用开发全流程关键技术点包括 混合渲染架构巧妙结合桌面应用的性能优势和Web地图的灵活性精确距离测量使用geodesic算法实现专业级距离计算响应式UI设计通过PyQt5构建美观易用的交互界面跨框架通信实现Python逻辑与JavaScript渲染的无缝衔接 该解决方案特别适合以下场景 企业内网GIS系统科研数据可视化物流路径规划教学演示工具 未来展望随着WebAssembly技术的发展这类混合架构应用将获得更接近原生应用的性能表现在地理信息领域具有广阔的应用前景。 版权声明本文采用CC BY-NC-SA 4.0协议转载请注明出处。商业转载请联系作者授权。
http://www.zqtcl.cn/news/725738/

相关文章:

  • 商标设计网站猪八戒网站建设与设计教程
  • 网站建设积分wordpress添加右侧菜单
  • 网站策划资料方案天津优化公司
  • 做网站推广哪家公司好成都最正规的装修公司
  • 菜鸟建网站如何制作推广网站
  • 无锡企业建站系统广州品牌网站建设
  • 什么网站能免费做公众号封面wordpress主题打不开
  • 扬州外贸网站建设制作广告的软件
  • 一个主机怎么做两个网站百度上的网站怎么做
  • 济南建设工程业绩公示的网站wordpress载入等待
  • seo公司名字太原百度seo排名软件
  • 安徽省城乡建设厅网站拼多多关键词排名在哪里看
  • 素材下载网站开发wordpress微信付款插件
  • 网站有什么用河北廊坊建筑模板厂家
  • 永康住房和城乡建设部网站做网站 万户
  • 可信赖的常州网站建设做直播券的网站有多少
  • 网络营销案例分析pptseo策略是什么意思
  • 论坛网站建设视频青岛网站设计软件
  • 租用网站服务器价格清远医院网站建设方案
  • 房地产网站建设方案书福田所有车型
  • 网站功能描述高清视频网络服务器免费
  • 天台做网站微博推广效果怎么样
  • 苏州专门网站网站站长统计怎么做
  • 社交网站开发注意事项call_user_func_array() wordpress
  • 泉州企业免费建站个人网站设计与开发
  • 网站建设流程书籍互联网行业黑话
  • 山亭 网站建设wordpress 添加头像
  • 龙南县建设局网站新手如何做网络推广
  • 网站开发建设赚钱吗巩义旅游网站建设公司
  • 网站建设代码介绍网站顶部导航代码