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

【开源解析】基于PyQt5+Folium的谷歌地图应用开发:从入门到实战

🌐【开源解析】基于PyQt5+Folium的谷歌地图应用开发:从入门到实战请添加图片描述

🌈 个人主页:创客白泽 - CSDN博客
🔥 系列专栏:🐍《Python开源项目实战》
💡 热爱不止于代码,热情源自每一个灵感闪现的夜晚。愿以开源之火,点亮前行之路。
👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦

请添加图片描述

在这里插入图片描述

📌 概述

在当今数据可视化与地理信息系统的交叉领域,交互式地图应用已成为不可或缺的工具。本文将深入剖析一个基于Python技术栈(PyQt5+Folium+Geopy)开发的**"谷歌地图"桌面应用**,它集成了地址解析、地图标注、距离测量等实用功能,并提供了三种不同的地图样式选择。

相较于传统Web地图应用,本项目的创新点在于:

  • 桌面端集成:通过PyQt5实现原生应用体验
  • 混合渲染技术:结合Folium的HTML生成与QWebEngineView的嵌入式渲染
  • 跨框架通信:实现Python与JavaScript的双向交互
  • 轻量级架构:无需复杂GIS系统即可实现核心功能

🛠️ 功能特性

核心功能矩阵

功能模块实现技术特色说明
地理编码Geopy/Nominatim支持全球地址解析
地图渲染Folium+Leaflet三种专业地图样式
距离测量Geodesic算法高精度大圆距离计算
交互界面PyQt5响应式桌面UI
地图导出HTML5可独立运行的网页地图

特色功能详解

  1. 智能地址解析:基于OpenStreetMap的Nominatim服务,支持模糊地址匹配
  2. 实时距离测量:选择两个标记点即可显示精确的球面距离
  3. 动态标记高亮:可视化连线辅助空间关系分析
  4. 多地图样式:街道图、卫星图、地形图一键切换
  5. 跨平台运行:生成的HTML地图可在任何浏览器查看

🎨 效果展示

街道地图

在这里插入图片描述

卫星地图

在这里插入图片描述
在这里插入图片描述

地形图

在这里插入图片描述
在这里插入图片描述

距离测量演示

在这里插入图片描述

🧩 实现步骤详解

1. 环境搭建

pip install PyQt5 folium geopy PyQtWebEngine

2. 核心架构设计

在这里插入图片描述

3. 关键技术实现

3.1 混合地图渲染
def initialize_map(self):# 加载Leaflet库html = """<!DOCTYPE html><html><head><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script></head><body><div id="map"></div><script>// JavaScript地图控制逻辑var map = L.map('map').setView([39.9042, 116.4074], 4);</script></body></html>"""self.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(f"addMarker({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/lyrs=s&x={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 = f"""map.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_agent="simple_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/lyrs=s&x={x}&y={y}&z={z}","⛰️ 地形图": "https://mt1.google.com/vt/lyrs=p&x={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, name=style_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, stretch=1)def get_empty_html(self):"""返回初始空白HTML"""return """<!DOCTYPE html><html><head><title>地图预览</title><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"></head><body><div id="map" style="height:100%;width:100%;"></div></body></html>"""def initialize_map(self):"""初始化地图,确保Leaflet库正确加载"""html = """<!DOCTYPE html><html><head><title>地图预览</title><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="crossorigin=""/><style>body { margin: 0; padding: 0; }#map { height: 100vh; width: 100%; }</style></head><body><div id="map"></div><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="crossorigin=""></script><script>var 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 href="https://www.openstreetmap.org/copyright">OpenStreetMap</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 + '</b><br>' + 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></html>"""self.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 = f"highlightMarkers({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 = f"""var newLayer = L.tileLayer('{tiles}', {{attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</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 = f"""setView({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_start=12)# 根据当前选择的样式设置地图瓦片for btn in self.style_buttons:if btn.isChecked():style_name = btn.text()tiles = self.map_styles[style_name]breakif style_name == "🌍 街道地图":tiles = "OpenStreetMap"attr = Noneelif style_name == "🛰️ 卫星地图":tiles = "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"attr = "Google Satellite"elif style_name == "⛰️ 地形图":tiles = "https://mt1.google.com/vt/lyrs=p&x={x}&y={y}&z={z}"attr = "Google Terrain"if attr:folium.TileLayer(tiles=tiles, attr=attr, name=style_name).add_to(map_obj)else:folium.TileLayer(tiles=tiles, name=style_name).add_to(map_obj)# 添加所有位置标记for loc in self.locations:folium.Marker(location=[loc['latitude'], loc['longitude']],popup=f"<b>{loc['name']}</b><br>{loc['address']}",tooltip=loc['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']]],color='red',weight=3,opacity=0.7,dash_array='10, 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(location=midpoint,icon=folium.DivIcon(icon_size=(150, 36),icon_anchor=(75, 18),html=f'<div style="font-size: 12pt; color: red; background: white; padding: 2px; border-radius: 3px;">{distance:.2f} km</div>'),tooltip=f"直线距离: {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(f"setView({first_loc['latitude']}, {first_loc['longitude']}, 12);")# 添加标记for loc in self.locations:js = f"""addMarker({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()

🚀 扩展方向

  1. 数据持久化:集成SQLite存储位置数据
  2. 轨迹绘制:支持路径规划和导航功能
  3. POI搜索:接入更多地理编码服务提供商
  4. 3D视图:集成Cesium实现三维可视化
  5. 插件系统:支持功能模块动态加载

💡 总结

本文详细剖析了基于PyQt5和Folium的地图应用开发全流程,关键技术点包括:

  1. 混合渲染架构:巧妙结合桌面应用的性能优势和Web地图的灵活性
  2. 精确距离测量:使用geodesic算法实现专业级距离计算
  3. 响应式UI设计:通过PyQt5构建美观易用的交互界面
  4. 跨框架通信:实现Python逻辑与JavaScript渲染的无缝衔接

该解决方案特别适合以下场景:

  • 企业内网GIS系统
  • 科研数据可视化
  • 物流路径规划
  • 教学演示工具

未来展望:随着WebAssembly技术的发展,这类混合架构应用将获得更接近原生应用的性能表现,在地理信息领域具有广阔的应用前景。


版权声明:本文采用CC BY-NC-SA 4.0协议,转载请注明出处。商业转载请联系作者授权。

相关文章:

【开源解析】基于PyQt5+Folium的谷歌地图应用开发:从入门到实战

&#x1f310;【开源解析】基于PyQt5Folium的谷歌地图应用开发&#xff1a;从入门到实战 &#x1f308; 个人主页&#xff1a;创客白泽 - CSDN博客 &#x1f525; 系列专栏&#xff1a;&#x1f40d;《Python开源项目实战》 &#x1f4a1; 热爱不止于代码&#xff0c;热情源自每…...

在 Ubuntu 22.04 LTS 上离线安装 Docker

在 Ubuntu 22.04 LTS 上离线安装 Docker 一、准备工作 1.1 获取目标系统信息 在目标 Ubuntu 22.04 LTS 系统上&#xff0c;先执行以下命令确认架构信息&#xff1a; uname -m lsb_release -a一般返回如下信息&#xff1a; 1.2 需要一台可联网的机器 准备一台可以连接互联网…...

python调用langchain实现RAG

一、安装langchain 安装依赖 python -m venv env.\env\Scripts\activatepip3 install langchainpip3 install langchain-corepip3 install langchain-openaipip3 install langchain-communitypip3 install dashscopepip3 install langchain_postgrespip3 install "psyc…...

Qt 中的 d-pointer 与 p-pointer小结

Qt 中的 d-pointer 与 p-pointer&#xff1a; PIMPL 惯用法解析 在 Qt 库中&#xff0c;尤其是在其核心类和模块中&#xff0c;广泛使用了 PIMPL (Pointer to IMPLementation&#xff0c;指向实现的指针) 的编程惯用法。这种模式通过一对指针来实现&#xff1a;d-pointer (d_p…...

冷库耗电高的一种重要原因分析,以及一种降低冷库电费≥20%的方法

1.冷库耗电高的原因分析 1.1化霜不及时 固定周期化霜&#xff1a;传统定时化霜模式&#xff08;如每日2次&#xff09;未考虑实际结霜量&#xff0c;导致无效化霜&#xff08;霜层薄时仍启动化霜&#xff09;&#xff0c;浪费大量电能。 化霜时间过长&#xff1a;化霜加热器…...

理解 Redis 事务-21(使用事务实现原子操)

使用事务实现原子操作 Redis 事务是一种在单个步骤中执行一组命令的机制。"要么全部&#xff0c;要么全部不"的方法确保了数据的一致性和完整性&#xff0c;尤其是在需要对相关数据进行多个操作时。没有事务&#xff0c;并发操作可能会导致竞争条件和不一致的数据状…...

神经网络加上注意力机制,精度反而下降,为什么会这样呢?注意力机制的本质是什么?如何正确使用注意力机制?注意力机制 | 深度学习

在深度学习的发展中,注意力机制的引入曾被誉为一次划时代的技术飞跃。无论是在自然语言处理领域产生Transformer架构,还是在图像识别、语音识别和推荐系统等多个方向取得显著成效,注意力机制的价值似乎毋庸置疑。然而,在一些实际应用场景中,研究人员和工程师却发现:在传统…...

触控精灵 ADB运行模式填写电脑端IP教程

•ADB模式&#xff0c;如果你手机已经root则可以直接运行&#xff0c;无需安装电脑端。 •ADB模式&#xff0c;如果你手机没有root&#xff0c;那你可以windows电脑下载【极限投屏】软件&#xff0c;然后你的手机和电脑的网络要同一个wifi&#xff0c;然后把你电脑的ip地址填写…...

uniapp|实现多端图片上传、拍照上传自定义插入水印内容及拖拽自定义水印位置,实现水印相机、图片下载保存等功能

本文以基础视角,详细讲解如何在uni-app中实现图片上传→水印动态编辑→图片下载的全流程功能。 目录 引言应用场景分析(社交媒体、内容保护、企业素材管理等)uniapp跨平台开发优势核心功能实现​图片上传模块多来源支持:相册选择(`uni.chooseImage`)与拍照(`sourceType:…...

linux有效裁剪视频的方式(基于ffmpeg,不改变分辨率,帧率,视频质量,不需要三方软件)

就是在Linux上使用OBS Studio录制一个讲座或者其他视频&#xff0c;可能总有些时候会多录制一段时间&#xff0c;但是如果使用剪映或者PR这样的工具在导出的时候总需要烦恼导出的格式和参数&#xff0c;比如剪映就不支持mkv格式的导出&#xff0c;导出成mp4格式的视频就会变得很…...

服务器密码安全运维解决新思路:凭据管理SMS+双因素SLA认证结合的方案

引言&#xff1a;云服务器安全成本困局 在云计算渗透率突破60%的今天&#xff0c;中小企业正面临严峻的安全悖论&#xff1a;某权威机构数据显示&#xff0c;72%的云上数据泄露事件源于凭据管理不当&#xff0c;而传统安全解决方案的采购成本往往超过中小企业年利润的8%。这种…...

论文阅读笔记——In-Context Edit

ICEdit 论文阅读笔记 指令图像编辑现有方法的局限&#xff1a; 微调类方法&#xff08;InstructPix2Pix、Emu Edit、 Ultra Edit&#xff09;&#xff1a;需要大规模数据和算力、精度高但效率低且泛化性低&#xff1b;免训练方法&#xff08;Prompt-to-Prompt、 StableFlow&am…...

Debian 系统 Python 开发全解析:从环境搭建到项目实战

Debian 系统 Python 开发全解析:从环境搭建到项目实战 在当今数字化时代,Python 凭借其简洁易读的语法和强大的功能,成为了最受欢迎的编程语言之一。Debian 作为一款稳定、安全且开源的 Linux 操作系统,为 Python 开发提供了理想的环境。本文将详细介绍在 Debian 系统上进…...

Next.js 15 与 Apollo Client 的现代集成及性能优化

Next.js 15 与 Apollo Client 的现代集成及性能优化 目录 技术演进集成实践性能优化应用案例未来趋势 技术演进 Next.js 15 核心特性对开发模式的革新 Next.js 15 通过引入 App Router、服务器组件&#xff08;Server Components&#xff09;和客户端组件&#xff08;Clie…...

【后端高阶面经:MongoDB篇】41、MongoDB 是怎么做到高可用的?

一、MongoDB高可用核心架构&#xff1a;副本集&#xff08;Replica Set&#xff09;设计 &#xff08;一&#xff09;副本集角色与拓扑结构 1. 三大核心角色 角色职责描述资源占用选举权重数据存储Primary唯一接收写请求的节点&#xff0c;将操作日志&#xff08;Oplog&…...

IO Vs NIO

一、IO(传统阻塞式) 全称‌&#xff1a;Input/Output(输入/输出) 定义‌&#xff1a;Java 1.0 引入的基础 I/O 模型&#xff0c;基于流&#xff08;Stream&#xff09;的同步阻塞操作&#xff0c;线程在读写数据时会阻塞直到操作完成。 二、NIO(新式非阻塞式) ‌全…...

offset 家族和 client 家族

在前端开发中&#xff0c;offset 家族和 client 家族是用于获取元素尺寸和位置的重要属性集合。以下是对这两个家族相关知识点的系统总结&#xff1a; 一、offset 家族 核心属性 offsetWidth / offsetHeight 含义&#xff1a;元素的总尺寸&#xff0c;包含内容区、内边距、边…...

DMBOK对比知识点整理(4)

1.常见数据质量维度 常见数据质量维度(DMBOK-P353)质量维度...

day12 leetcode-hot100-21(矩阵4)

240. 搜索二维矩阵 II - 力扣&#xff08;LeetCode&#xff09; 1.暴力法O(m*n) 思路&#xff1a;两层for循环即可。 2.二分查找O(m*logn) 思路&#xff1a;每行都用二分查找,因为每行都是排好序的 class Solution {public boolean searchMatrix(int[][] matrix, int targe…...

Java基础 Day24

一、进程和线程 1、进程 &#xff08;1&#xff09;概念 进程 (Process) 是计算机中的程序关于某数据集合上的一次运行活动 是系统进行资源分配的基本单位 简单理解&#xff1a;程序的执行过程&#xff08;正在运行的应用程序&#xff09; &#xff08;2&#xff09;特性…...

提问:鲜羊奶是解决育儿Bug的补丁吗?

在育儿这个"系统工程"中&#xff0c;过度提醒就像冗余代码&#xff1a;"快写作业"&#xff08;重复调用&#xff09;、"多穿衣服"&#xff08;异常捕获&#xff09;、"别玩手机"&#xff08;进程阻断&#xff09;。羊大师技术育儿实验…...

关于数据仓库、数据湖、数据平台、数据中台和湖仓一体的概念和区别

我们谈论数据中台之前&#xff0c; 我们也听到过数据平台、数据仓库、数据湖、湖仓一体的相关概念&#xff0c;它们都与数据有关系&#xff0c;但他们和数据中台有什么样的区别&#xff0c; 下面我们将围绕数据平台、数据仓库、数据湖和数据中台的区别进行介绍。 一、相关概念…...

Hive 分桶(Bucketing)深度解析:原理、实战与核心概念对比

一、分桶的意义&#xff1a;比分区更细的粒度管理 1.1 解决分区数据不均匀问题 分区的局限性&#xff1a;分区基于表外字段&#xff08;如时间字段&#xff09;划分数据&#xff0c;但可能导致部分分区数据量过大&#xff0c;部分过小&#xff0c;无法进一步细化。 分桶的定…...

网络协议DHCP

DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09;是一种网络协议&#xff0c;用于自动给网络中的设备分配 IP 地址、子网掩码、默认网关、DNS 服务器等网络配置参数。 ✅ 一、DHCP 的作用 自动为客户端分配网络信息&#xff0c;…...

什么是可重组机器人?

可重组机器人是一种具有高度灵活性和适应性的新型机器人系统&#xff0c;能够根据不同任务需求&#xff0c;快速改变自身结构和功能。下面我从概念、结构、特点、应用领域、发展趋势等方面&#xff0c;为你详细介绍&#xff1a; 概念&#xff1a;可重组机器人是由多个标准化、模…...

4、docker compose

1、介绍 Docker Compose 是 Docker 官方提供的容器编排工具&#xff0c;用于简化多容器应用的开发、部署和管理。它通过声明式配置文件&#xff08;YAML格式&#xff09;定义容器化应用的服务、网络、存储等组件及其依赖关系&#xff0c;使用户能够通过单一命令快速启动、停止…...

Node.js全局对象详解:console、process与核心功能

在Node.js开发中&#xff0c;全局对象是一类无需引入即可直接访问的对象&#xff0c;它们为开发者提供了与运行时环境、系统交互和调试相关的核心功能。本文将深入解析Node.js中两个最常用的全局对象 console 和 process&#xff0c;并简要介绍其他全局对象的作用。通过代码示例…...

测试策略:AI模型接口的单元测试与稳定性测试

测试策略:AI模型接口的单元测试与稳定性测试 在构建支持AI能力的系统中,开发者不仅要关注业务逻辑的正确性,也必须保障AI模型接口在各种环境下都能稳定运行。这就要求我们在开发阶段制定清晰的测试策略,从功能验证到性能保障,逐步推进系统可用性、可维护性与可扩展性的提…...

SQL里几种JOIN连接

数据信息&#xff1a; 员工表EMP 部门表DEPT 一、INNER JOIN&#xff08;内连接&#xff09; 作用&#xff1a;只返回两个表中完全匹配的行&#xff0c;相当于取交集。 场景&#xff1a;查询「有部门的员工信息」。 示例&#xff1a; SELECT 员工.姓名, 部门.部门名称 FR…...

基于通义千问的儿童陪伴学习和成长的智能应用架构。

1.整体架构概览 我们的儿童聊天助手将采用典型的语音交互系统架构,结合大模型能力和外部知识库: 2. 技术方案分解 2.1. 前端应用/设备 选择: 移动App(iOS/Android)、Web应用,或者集成到智能音箱/平板等硬件设备中。技术栈: 移动App: React Native / Flutter (跨平台…...