最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python+Folium模擬實(shí)現(xiàn)谷歌地圖應(yīng)用效果

 更新時(shí)間:2025年05月29日 08:14:03   作者:創(chuàng)客白澤  
在當(dāng)今數(shù)據(jù)可視化與地理信息系統(tǒng)的交叉領(lǐng)域,交互式地圖應(yīng)用已成為不可或缺的工具,本文將深入剖析一個(gè)基于Python開發(fā)的谷歌地圖桌面應(yīng)用,感興趣的可以了解下

概述

在當(dāng)今數(shù)據(jù)可視化與地理信息系統(tǒng)的交叉領(lǐng)域,交互式地圖應(yīng)用已成為不可或缺的工具。本文將深入剖析一個(gè)基于Python技術(shù)棧(PyQt5+Folium+Geopy)開發(fā)的**"谷歌地圖"桌面應(yīng)用**,它集成了地址解析、地圖標(biāo)注、距離測(cè)量等實(shí)用功能,并提供了三種不同的地圖樣式選擇。

相較于傳統(tǒng)Web地圖應(yīng)用,本項(xiàng)目的創(chuàng)新點(diǎn)在于:

  • 桌面端集成:通過(guò)PyQt5實(shí)現(xiàn)原生應(yīng)用體驗(yàn)
  • 混合渲染技術(shù):結(jié)合Folium的HTML生成與QWebEngineView的嵌入式渲染
  • 跨框架通信:實(shí)現(xiàn)Python與JavaScript的雙向交互
  • 輕量級(jí)架構(gòu):無(wú)需復(fù)雜GIS系統(tǒng)即可實(shí)現(xiàn)核心功能

功能特性

核心功能矩陣

功能模塊實(shí)現(xiàn)技術(shù)特色說(shuō)明
地理編碼Geopy/Nominatim支持全球地址解析
地圖渲染Folium+Leaflet三種專業(yè)地圖樣式
距離測(cè)量Geodesic算法高精度大圓距離計(jì)算
交互界面PyQt5響應(yīng)式桌面UI
地圖導(dǎo)出HTML5可獨(dú)立運(yùn)行的網(wǎng)頁(yè)地圖

特色功能詳解

智能地址解析:基于OpenStreetMap的Nominatim服務(wù),支持模糊地址匹配

實(shí)時(shí)距離測(cè)量:選擇兩個(gè)標(biāo)記點(diǎn)即可顯示精確的球面距離

動(dòng)態(tài)標(biāo)記高亮:可視化連線輔助空間關(guān)系分析

多地圖樣式:街道圖、衛(wèi)星圖、地形圖一鍵切換

跨平臺(tái)運(yùn)行:生成的HTML地圖可在任何瀏覽器查看

效果展示

街道地圖

衛(wèi)星地圖

地形圖

距離測(cè)量演示

實(shí)現(xiàn)步驟詳解

1. 環(huán)境搭建

pip install PyQt5 folium geopy PyQtWebEngine

2. 核心架構(gòu)設(shè)計(jì)

3. 關(guān)鍵技術(shù)實(shí)現(xiàn)

3.1 混合地圖渲染

def initialize_map(self):
    # 加載Leaflet庫(kù)
    html = """
    <!DOCTYPE html>
    <html>
    <head>
        <link rel="stylesheet"  rel="external nofollow"  rel="external nofollow" />
        <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 跨語(yǔ)言通信

# Python調(diào)用JavaScript
self.map_view.page().runJavaScript("addMarker(39.9, 116.4, '北京', '中國(guó)首都');")

# JavaScript回調(diào)Python
self.map_view.page().runJavaScript("""
    map.on('click', function(e) {
        console.log(e.latlng);
    });
""")

3.3 距離測(cè)量算法

from geopy.distance import geodesic

def calculate_distance(loc1, loc2):
    """使用Vincenty公式計(jì)算球面距離"""
    return geodesic(
        (loc1['latitude'], loc1['longitude']),
        (loc2['latitude'], loc2['longitude'])
    ).kilometers

代碼深度解析

1. 地理編碼服務(wù)封裝

def geocode_location(self, address):
    try:
        location = self.geolocator.geocode(address)
        if location:
            return (location.latitude, location.longitude)
        return None
    except (GeocoderTimedOut, GeocoderServiceError) as e:
        # 實(shí)現(xiàn)自動(dòng)重試機(jī)制
        time.sleep(0.5)
        return self.geocode_location(address)

優(yōu)化點(diǎn):增加了異常處理和自動(dòng)重試機(jī)制,提高服務(wù)穩(wěn)定性

2. 動(dòng)態(tài)標(biāo)記管理

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))

性能優(yōu)化:減少Python-JS通信次數(shù),使用批量操作提升渲染效率

3. 地圖樣式熱切換

self.map_styles = {
    "?? 街道地圖": {
        "url": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
        "attr": "OpenStreetMap"
    },
    "??? 衛(wèi)星地圖": {
        "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  # 添加距離計(jì)算功能
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 time

class 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 = None
        self.map_initialized = False
        self.selected_markers = []  # 存儲(chǔ)選中的標(biāo)記用于距離計(jì)算
        
        # 地圖樣式選項(xiàng)
        self.map_styles = {
            "?? 街道地圖": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
            "??? 衛(wèi)星地圖": "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}"
        }
        
        # 創(chuàng)建UI
        self.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)
        
        # 左側(cè)控制面板
        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)
        
        # 距離顯示標(biāo)簽
        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()
        
        # 右側(cè)地圖預(yù)覽
        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>地圖預(yù)覽</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庫(kù)正確加載"""
        html = """
        <!DOCTYPE html>
        <html>
        <head>
            <title>地圖預(yù)覽</title>
            <meta charset="utf-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <link rel="stylesheet"  rel="external nofollow"  rel="external nofollow" 
                  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  rel="external nofollow"  rel="external nofollow" >OpenStreetMap</a> contributors'
                });
                osmLayer.addTo(map);
                
                // 存儲(chǔ)標(biāo)記的數(shù)組
                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) {
                    // 重置所有標(biāo)記樣式
                    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;
                    }
                    
                    // 高亮選中的標(biāo)記
                    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]);
                        }
                    }
                    
                    // 如果選中了兩個(gè)點(diǎn),繪制連線
                    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 = True
        QTimer.singleShot(500, self.update_map_style)
    
    def on_location_selection_changed(self):
        """當(dāng)位置列表選擇變化時(shí)觸發(fā)"""
        selected_items = self.location_list.selectedItems()
        selected_indices = [self.location_list.indexOfTopLevelItem(item) for item in selected_items]
        
        # 更新地圖上的高亮標(biāo)記
        if self.map_initialized:
            js = f"highlightMarkers({selected_indices});"
            self.map_view.page().runJavaScript(js)
        
        # 計(jì)算并顯示距離
        if len(selected_indices) == 2:
            loc1 = self.locations[selected_indices[0]]
            loc2 = self.locations[selected_indices[1]]
            
            # 使用geodesic計(jì)算兩點(diǎn)間距離
            point1 = (loc1['latitude'], loc1['longitude'])
            point2 = (loc2['latitude'], loc2['longitude'])
            distance = geodesic(point1, point2).kilometers
            
            self.distance_label.setText(f"兩地距離: {distance:.2f} 公里")
        else:
            self.distance_label.setText("兩地距離: 請(qǐng)選擇兩個(gè)地點(diǎn)")
    
    def update_map_style(self):
        """更新地圖樣式"""
        if not self.map_initialized:
            return
            
        for btn in self.style_buttons:
            if btn.isChecked():
                style_name = btn.text()
                tiles = self.map_styles[style_name]
                break
        
        js = f"""
        var newLayer = L.tileLayer('{tiles}', {{
            attribution: '&copy; <a  rel="external nofollow"  rel="external nofollow" >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):
        """將地址轉(zhuǎn)換為經(jīng)緯度"""
        try:
            location = self.geolocator.geocode(address)
            if location:
                return (location.latitude, location.longitude)
            return None
        except (GeocoderTimedOut, GeocoderServiceError) as e:
            QMessageBox.critical(self, "錯(cuò)誤", f"地理編碼錯(cuò)誤: {e}")
            return None
    
    def add_location(self):
        """添加位置到列表"""
        name = self.name_entry.text().strip()
        address = self.address_entry.text().strip()
        
        if not name or not address:
            QMessageBox.warning(self, "警告", "請(qǐng)?zhí)顚懨Q和地址")
            return
        
        coords = 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, "錯(cuò)誤", f"無(wú)法找到地址: {address}")
    
    def remove_location(self):
        """刪除選中的位置"""
        selected = self.location_list.selectedItems()
        if not selected:
            QMessageBox.warning(self, "警告", "請(qǐng)先選擇要?jiǎng)h除的位置")
            return
        
        for 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:
            return
            
        reply = QMessageBox.question(
            self, '確認(rèn)', 
            '確定要清空所有位置嗎?', 
            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, "警告", "請(qǐng)輸入搜索內(nèi)容")
            return
        
        try:
            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, "搜索錯(cuò)誤", str(e))
    
    def create_map(self):
        """創(chuàng)建地圖并添加所有位置標(biāo)記"""
        if not self.locations:
            QMessageBox.warning(self, "警告", "沒有可顯示的位置")
            return
        
        # 使用folium創(chuàng)建地圖
        first_loc = self.locations[0]
        map_obj = folium.Map(
            location=[first_loc['latitude'], first_loc['longitude']],
            zoom_start=12
        )
        
        # 根據(jù)當(dāng)前選擇的樣式設(shè)置地圖瓦片
        for btn in self.style_buttons:
            if btn.isChecked():
                style_name = btn.text()
                tiles = self.map_styles[style_name]
                break
        
        if style_name == "?? 街道地圖":
            tiles = "OpenStreetMap"
            attr = None
        elif style_name == "??? 衛(wèi)星地圖":
            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)
        
        # 添加所有位置標(biāo)記
        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)
        
        # 如果有兩個(gè)點(diǎn)被選中,添加連線
        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])]
            
            # 添加兩點(diǎn)間連線
            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)
            
            # 計(jì)算并顯示距離
            distance = geodesic(
                (loc1['latitude'], loc1['longitude']),
                (loc2['latitude'], loc2['longitude'])
            ).kilometers
            
            # 在兩點(diǎn)中間添加距離標(biāo)簽
            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_path
            map_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, "錯(cuò)誤", "請(qǐng)先生成地圖")
            return
        
        webbrowser.open('file://' + os.path.realpath(self.current_map_file))
    
    def update_embedded_map(self):
        """更新內(nèi)嵌地圖視圖"""
        if not self.map_initialized or not hasattr(self, 'map_view'):
            return
            
        # 清除所有標(biāo)記
        self.map_view.page().runJavaScript("clearMarkers();")
        
        if not self.locations:
            # 如果沒有位置,重置到默認(rèn)視圖
            self.map_view.page().runJavaScript("setView(39.9042, 116.4074, 4);")
            return
        
        # 設(shè)置地圖中心和縮放級(jí)別
        first_loc = self.locations[0]
        self.map_view.page().runJavaScript(f"setView({first_loc['latitude']}, {first_loc['longitude']}, 12);")
        
        # 添加標(biāo)記
        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風(fēng)格使UI更現(xiàn)代
    
    window = SimpleMapViewerApp()
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

擴(kuò)展方向

數(shù)據(jù)持久化:集成SQLite存儲(chǔ)位置數(shù)據(jù)

軌跡繪制:支持路徑規(guī)劃和導(dǎo)航功能

POI搜索:接入更多地理編碼服務(wù)提供商

3D視圖:集成Cesium實(shí)現(xiàn)三維可視化

插件系統(tǒng):支持功能模塊動(dòng)態(tài)加載

總結(jié)

本文詳細(xì)剖析了基于PyQt5和Folium的地圖應(yīng)用開發(fā)全流程,關(guān)鍵技術(shù)點(diǎn)包括:

  • 混合渲染架構(gòu):巧妙結(jié)合桌面應(yīng)用的性能優(yōu)勢(shì)和Web地圖的靈活性
  • 精確距離測(cè)量:使用geodesic算法實(shí)現(xiàn)專業(yè)級(jí)距離計(jì)算
  • 響應(yīng)式UI設(shè)計(jì):通過(guò)PyQt5構(gòu)建美觀易用的交互界面
  • 跨框架通信:實(shí)現(xiàn)Python邏輯與JavaScript渲染的無(wú)縫銜接

該解決方案特別適合以下場(chǎng)景:

  • 企業(yè)內(nèi)網(wǎng)GIS系統(tǒng)
  • 科研數(shù)據(jù)可視化
  • 物流路徑規(guī)劃
  • 教學(xué)演示工具

到此這篇關(guān)于Python+Folium模擬實(shí)現(xiàn)谷歌地圖應(yīng)用效果的文章就介紹到這了,更多相關(guān)Python Folium模擬谷歌地圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用Eclipse如何開發(fā)python腳本

    使用Eclipse如何開發(fā)python腳本

    這篇文章主要為大家詳細(xì)介紹了使用Eclipse開發(fā)python腳本的相關(guān)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • python爬蟲系列網(wǎng)絡(luò)請(qǐng)求案例詳解

    python爬蟲系列網(wǎng)絡(luò)請(qǐng)求案例詳解

    這篇文章主要介紹了【Python從零到壹】python爬蟲系列-網(wǎng)絡(luò)請(qǐng)求,從零開始學(xué)習(xí)Python網(wǎng)絡(luò)爬蟲,如何從中獲取需要的數(shù)據(jù)信息,現(xiàn)整理出零基礎(chǔ)如何學(xué)爬蟲技術(shù)以供學(xué)習(xí)
    2021-04-04
  • python隨機(jī)在一張圖像上截取任意大小圖片的方法

    python隨機(jī)在一張圖像上截取任意大小圖片的方法

    今天小編就為大家分享一篇python隨機(jī)在一張圖像上截取任意大小圖片的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-01-01
  • 使用Python輕松實(shí)現(xiàn)隱藏、顯示和修改Excel網(wǎng)格線

    使用Python輕松實(shí)現(xiàn)隱藏、顯示和修改Excel網(wǎng)格線

    在數(shù)據(jù)處理和報(bào)表制作中,Excel 是最常用的工具之一,而網(wǎng)格線(Gridlines)作為 Excel 默認(rèn)的背景輔助線,本文將介紹如何使用 Spire.XLS for Python 庫(kù),通過(guò)簡(jiǎn)單的代碼實(shí)現(xiàn) Excel 網(wǎng)格線的隱藏/顯示以及顏色修改快跟隨小編一起學(xué)習(xí)一下吧
    2026-03-03
  • Python 訪問(wèn)限制 private public的詳細(xì)介紹

    Python 訪問(wèn)限制 private public的詳細(xì)介紹

    在一個(gè)模塊中,我們可能會(huì)定義很多函數(shù)和變量。這篇文章主要介紹了Python 訪問(wèn)限制 private public的詳細(xì)介紹,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-10-10
  • 教你使用Python寫一個(gè)簡(jiǎn)單的JSONParser

    教你使用Python寫一個(gè)簡(jiǎn)單的JSONParser

    這篇文章主要介紹了教你使用Python寫一個(gè)簡(jiǎn)單的JSONParser,它的整個(gè)效果,有點(diǎn)類似于 python 標(biāo)準(zhǔn)庫(kù) json 的 json.load() 方法,需要的朋友可以參考下
    2023-04-04
  • python自動(dòng)格式化json文件的方法

    python自動(dòng)格式化json文件的方法

    這篇文章主要介紹了python自動(dòng)格式化json文件的方法,可實(shí)現(xiàn)將json文件格式化的功能,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-03-03
  • 利用numpy實(shí)現(xiàn)一、二維數(shù)組的拼接簡(jiǎn)單代碼示例

    利用numpy實(shí)現(xiàn)一、二維數(shù)組的拼接簡(jiǎn)單代碼示例

    這篇文章主要介紹了利用numpy實(shí)現(xiàn)一、二維數(shù)組的拼接簡(jiǎn)單代碼示例,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-12-12
  • python中加背景音樂如何操作

    python中加背景音樂如何操作

    在本篇文章里小編給大家整理了關(guān)于在python中加背景音樂的方法,需要的朋友們可以參考下。
    2020-07-07
  • 使用selenium模擬動(dòng)態(tài)登錄百度頁(yè)面的實(shí)現(xiàn)

    使用selenium模擬動(dòng)態(tài)登錄百度頁(yè)面的實(shí)現(xiàn)

    本文主要介紹了使用selenium模擬動(dòng)態(tài)登錄百度頁(yè)面,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05

最新評(píng)論

潞城市| 瑞昌市| 毕节市| 万安县| 楚雄市| 垣曲县| 偏关县| 顺昌县| 保康县| 荔浦县| 万安县| 天镇县| 柳河县| 集安市| 汨罗市| 汶上县| 宁都县| 井冈山市| 宁阳县| 正镶白旗| 禹州市| 阜阳市| 德惠市| 丰都县| 建宁县| 宁阳县| 阜康市| 班玛县| 宜君县| 蒲江县| 三江| 和顺县| 嘉定区| 彭州市| 肥乡县| 双柏县| 临汾市| 天台县| 烟台市| 永川市| 桂阳县|