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

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

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

概述

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

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

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

功能特性

核心功能矩陣

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

特色功能詳解

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

實時距離測量:選擇兩個標(biāo)記點即可顯示精確的球面距離

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

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

跨平臺運行:生成的HTML地圖可在任何瀏覽器查看

效果展示

街道地圖

衛(wèi)星地圖

地形圖

距離測量演示

實現(xiàn)步驟詳解

1. 環(huán)境搭建

pip install PyQt5 folium geopy PyQtWebEngine

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

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

3.1 混合地圖渲染

def initialize_map(self):
    # 加載Leaflet庫
    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 跨語言通信

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

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

3.3 距離測量算法

from geopy.distance import geodesic

def calculate_distance(loc1, loc2):
    """使用Vincenty公式計算球面距離"""
    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:
        # 實現(xiàn)自動重試機(jī)制
        time.sleep(0.5)
        return self.geocode_location(address)

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

2. 動態(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  # 添加距離計算功能
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 = []  # 存儲選中的標(biāo)記用于距離計算
        
        # 地圖樣式選項
        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庫正確加載"""
        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);
                
                // 存儲標(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]);
                        }
                    }
                    
                    // 如果選中了兩個點,繪制連線
                    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)位置列表選擇變化時觸發(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)
        
        # 計算并顯示距離
        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).kilometers
            
            self.distance_label.setText(f"兩地距離: {distance:.2f} 公里")
        else:
            self.distance_label.setText("兩地距離: 請選擇兩個地點")
    
    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, "錯誤", f"地理編碼錯誤: {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, "警告", "請?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, "錯誤", f"無法找到地址: {address}")
    
    def remove_location(self):
        """刪除選中的位置"""
        selected = self.location_list.selectedItems()
        if not selected:
            QMessageBox.warning(self, "警告", "請先選擇要刪除的位置")
            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, "警告", "請輸入搜索內(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, "搜索錯誤", 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)
        
        # 如果有兩個點被選中,添加連線
        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
            
            # 在兩點中間添加距離標(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, "錯誤", "請先生成地圖")
            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è)置地圖中心和縮放級別
        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存儲位置數(shù)據(jù)

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

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

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

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

總結(jié)

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

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

該解決方案特別適合以下場景:

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

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

相關(guān)文章

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

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

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

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

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

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

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

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

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

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

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

    教你使用Python寫一個簡單的JSONParser

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

    python自動格式化json文件的方法

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

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

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

    python中加背景音樂如何操作

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

    使用selenium模擬動態(tài)登錄百度頁面的實現(xiàn)

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

最新評論

加查县| 武定县| 西丰县| 若羌县| 施秉县| 鄯善县| 神农架林区| 齐河县| 波密县| 博野县| 大化| 商都县| 大名县| 疏附县| 博野县| 永平县| 哈密市| 韩城市| 城步| 嘉义县| 金川县| 驻马店市| 九台市| 台中县| 那坡县| 文水县| 陆良县| 江阴市| 辽源市| 荣昌县| 江源县| 宜黄县| 武强县| 宜兴市| 富顺县| 拜城县| 离岛区| 沂源县| 腾冲县| 海门市| 惠安县|