基于 Mapbox 实现 PyQt5上的离线地图

本文最后更新于:June 1, 2023 9:58 PM

懒得看的话可以直接 clone 我的 repo:Offline-Mapbox-in-PyQt5 记得给个小星星🧐

到底是谁先流行的 Folium??一点也不好用!😭

我一共试了 Folium, Google Map 和 Mapbox,Folium 不能动态更新 marker,Google Map 不能离线,只有 Mapbox 还行。。还没试 Leaflet,谁有空谁试吧😇(记得告诉我可不可以)

获取 Maptile

我用的是 OpenStreetMapMaperitive

  1. 用 OpenStreetMap 获取需要地图的.osm文件
  2. 用 Maperitive 把.osm转换成.png

Web 部分

创建一个 html,可以叫map.html

获取需要的文件

  1. 建议自己注册一个 mapbox 账户,输入 token 然后按照步骤嵌入地图到 html
  2. 用浏览器打开 html 后打开 Developer Tool,在 source 里找到需要的js, css文件和其他文件(如sprite等)下载下来
  3. html 里引入需要文件,如
    1
    2
    3
    <script src='./api/assets/mapbox-gl.js'></script>
    <link href='./api/assets/mapbox-gl.css' rel='stylesheet'/>
    <script src="jquery-3.6.4.min.js"></script>
    我写一个字符的 JavaScript 就想用 jQuery, 所以我这里也引入了 jQuery

Host Files on Local Server

把文件放进对应路径后写一个server.py用来 host 文件

1
2
3
4
5
6
7
8
9
import http.server
import socketserver

PORT = 8888

Handler = http.server.SimpleHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
httpd.serve_forever()

Setup Config

在 html 里按自己要求设置一下 Mapbox 的参数,比如我是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<script>
var map = new mapboxgl.Map({
container: 'map',
style: {
"version": 8,
"name": "Mapbox Streets",
"sprite": "http://localhost:8888/mapbox_build/sprite/sprite",
// "glyphs": "http://localhost:8888/mapbox_build/fonts/{fontstack}/{range}.pbf",
"sources": {
"osm-tiles": {
"type": "raster",
'tiles': [
"http://localhost:8888/tiles/{z}/{x}/{y}.png"
],

}
},
"layers": [{
"id": "123",
"type": "raster",
"source": "osm-tiles",
"source-layer": "osmtiles"
}]
},
center: [-71.6516848, 48.5107057],
zoom: 15
});
</script>

现在这里所有东西都是本地文件,已经可以离线用了

嵌入 PyQt5

用 PyQt 的 webengin 嵌入 html,新建.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtWebEngineWidgets import QWebEngineView # pip install PyQtWebEngine
from PyQt5 import QtCore
from PyQt5.QtCore import *


"""
Mapbox in PyQt5
"""


class MyApp(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('Mapbox in PyQt Example')
self.window_width, self.window_height = 600, 400
self.setMinimumSize(self.window_width, self.window_height)

layout = QVBoxLayout()
self.setLayout(layout)

webView = QWebEngineView()
webView.load(QUrl('http://localhost:8888/map.html'))
layout.addWidget(webView)

if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet('''
QWidget {
font-size: 35px;
}
''')

myApp.show()

try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')

Python 和 Html 的交互

因为我需要实时更改 mapbox marker 的位置,所以我用了一个 json 来存 coordinate 的参数

json:

1
{"coordinate": [-71.65108479999981, 48.51130570000019]}

js:

1
2
3
4
5
6
7
8
9
let marker = new mapboxgl.Marker();

setInterval(function() {
$.getJSON("map.json", function (json) {
console.log(json);
marker.setLngLat(json.coordinate);
.addTo(map);
});
},10);

python:

写入 json 的 function

1
2
3
4
5
6
7
8
def reloadMap():
with open("map.json", "r") as jsonFile:
data = json.load(jsonFile)

data["coordinate"] = [data["coordinate"][0]+0.00001,data["coordinate"][1]+0.00001]

with open("map.json", "w") as jsonFile:
json.dump(data, jsonFile)

用 PyQt 自带的 QTimer 在新的线程上重复调用reloadMap()
1
2
3
4
timer = QTimer()
timer.timeout.connect(reloadMap) # execute `reloadMap`
timer.setInterval(50) # 1000ms = 1s
timer.start()

撒花

有问题的话欢迎评论呀!