electron中添加百度地图并框选范围

更新时间: 2025-07-03 10:05:34

# 修改index.html

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Electron</title>
   
  </head>

  <body>
    <div id="app"></div>
	<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=5Vd9Kn9tCkBfHuZ8aUM82krQfDIXD5gF"></script>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 设置宽松的Content-Security-Policy(CSP)规则

// main/index.js
//...
mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => {
  callback({
    responseHeaders: {
      ...details.responseHeaders,
      'Content-Security-Policy': [
        "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;"
        + " script-src * 'unsafe-inline' 'unsafe-eval' data: blob:;"
        + " connect-src * data: blob:;"
        + " img-src * data: blob:;"
        + " style-src * 'unsafe-inline' data: blob:;"
        + " font-src * data: blob:;"
      ]
    }
  })
})
//...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

解决Electron加载第三方网站时的跨域限制,这里加上之后百度地图api就可以被正确加载了

# 绘制选择下载范围选框

我给百度地图添加了click事件和mousemove事件,记录下第一个和第二个在地图上被点击的点,并计算出相应矩形的四个点来:

 import { onMounted, onUnmounted, ref } from 'vue'

  let map
  // 当前有没有激活选择范围按钮
  const drawRect = ref(false)
  // 第一个被点击的点
  let point1 = null
  // 第二个被点击的点  
  let point2 = null
  // 鼠标移动时经过的点
  let pointTemp = null

  onMounted(() => {
    initMap()
  })

  function initMap() {
    // 百度地图API功能
	map = new BMap.Map("map");  // 创建Map实例
    map.enableScrollWheelZoom();
    map.disableDoubleClickZoom()
    map.centerAndZoom(new BMap.Point(108.752287,35.84405),5)

    map.addEventListener("click", function(e){    
        map.clearOverlays()
        if(drawRect.value) {
            if(!point1){
                point1 = [e.point.lng ,e.point.lat]; 
                point2 = null
            }else{
                if(!point2) {
                    point2 = [e.point.lng ,e.point.lat]; 
                    map.clearOverlays()
                    map.addOverlay(new BMap.Polygon(getRectPoints(point1, point2),{strokeColor:"blue", strokeWeight:2, strokeOpacity:0.5}))
                    drawRect.value = false
                    pointTemp = false
                }
            }
        }
    })

    map.addEventListener("mousemove", function(e){    
        if(drawRect.value && point1 && !point2) {
            pointTemp = [e.point.lng, e.point.lat]; 
            map.clearOverlays()
            map.addOverlay(new BMap.Polygon(getRectPoints(point1, pointTemp),{strokeColor:"blue", strokeWeight:2, strokeOpacity:0.5}))
        }
    })
  }

  function getRectPoints(point1, point2) {
    // 找到最大最小的经纬度坐标
    let maxLng,minLng,maxLat,minLat 
    if(point1[0] > point2[0]) {
        maxLng = point1[0]
        minLng = point2[0]
    }else{
        maxLng = point2[0]
        minLng = point1[0]
    }

    if(point1[1] > point2[1]) {
        maxLat = point1[1]
        minLat = point2[1]
    }else{
        maxLat = point2[1]
        minLat = point1[1]
    }

    return [
        //左上角
        new BMap.Point(minLng, maxLat),
        //右上角
        new BMap.Point(maxLng, maxLat),
        //右下角
        new BMap.Point(maxLng, minLat),
        //左下角
        new BMap.Point(minLng, minLat)
    ]
  }
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

# 根据经纬度坐标找到对应图块编号

百度地图的瓦片是被分成一块一块,每块大小为256*256像素的图片,我们需要知道对应的层级下选择的范围对应的图块编号范围

因此我们需要了解百度地图的地图坐标系,并学会根据经纬度转换为图块编号

  • 经纬度球面坐标系
    经纬度是一种利用三维空间的球面来定义地球上的空间的球面坐标系,它能够标示地球上任何一个位置。 通过伦敦格林尼治天文台原址的经线为0度经线,从0度经线向东、向西各分180度。赤道为0度纬线,赤道以北的纬线称为北纬、以南的称为南纬。

在百度地图中,东经和北纬用正数表示,西经和南纬用负数表示。例如北京的位置大约是北纬39.9度,东经116.4度,那么用数值标示就是经度116.6,纬度39.9。

在百度地图中,习惯经度在前,纬度在后,例如:

var point = new BMap.Point(116.404, 39.915);  // 创建点坐标,经度在前,纬度在后
1
  • 墨卡托平面坐标系统
    由于百度地图是显示在平面上的,因此在地图内部系统中需要将球面坐标转换为平面坐标,这个转换过程称为投影。 百度地图使用的是墨卡托投影。墨卡托平面坐标如下图所示,平面坐标与经纬度坐标系的原点是重合的。

  • 图块编号系统
    百度地图在每一个级别将整个地图划分成若干个图块,通过编号系统将整个图块整合在一起以便显示完整的地图。当地图被拖动或者级别发生变化时,地图API将会根据平面坐标计算出当前视野内所需显示的图块的编号。 百度地图图块编号规则如下图所示:

    从平面坐标原点开始的右上方向的图块编号为0,0,以此类推。在最低的缩放级别(级别 1)中,整个地球由4张图块组成。随着级别的增长,地图所使用的图块个数也随之增多。

# 转换方法

  1. 将经纬度转换为平面坐标
    在百度地图API中,平面坐标是以最大级别18级为基准的。就是说在18级下,平面坐标的一个单位就代表了屏幕上的1个像素。平面坐标与地图所展示的级别没有关系,也就是说在1级和18级下,天安门位置的平面坐标都是一致的。那么如何知道某个位置的平面坐标呢?可通过BMap.MercatorProjection类来完成,该类提供经纬度与平面坐标互相转换的方法。例如天安门的经纬度大约为116.404, 39.915,经过转换即可得到平面坐标:
map.getMapType().getProjection().lngLatToPoint(new BMap.Point(116.404, 39.915))
1


这个就是平面坐标。你可以这样理解它的含义:第18级下,天安门距离坐标原点的位置差为:12958175, 4825923.77,单位为像素。

  1. 将平面坐标转换为每一级下的像素坐标

在第18级下,我们直接将平面坐标向下取整就得到了像素坐标,而在其他级别下可以通过如下公式进行换算(这里取整为向下取整):

像素坐标 = 平面坐标 × Math.pow(2, zoom – 18)

  1. 将像素坐标转换为图块坐标
    百度地图API在展示地图时是将整个地图图片切割成若干图块来显示的,当地图初始化或是地图级别、中心点位置发生变化时,地图API会根据当前像素坐标计算出视野内需要的图块坐标(也叫图块编号),从而加载对应的图块用以显示地图。 百度地图的图块坐标原点与平面坐标一致,从原点向右上方开始编号为0, 0,如何知道某个位置的图块坐标呢?通过如下公式计算即可:

图块坐标 = Math.floor(像素坐标 ÷ 256)

256实际上是每个图块的宽度和高度,我们用像素坐标除以这个数就知道图块坐标了。还以天安门为例,在第4级下天安门所在的图块编号为:3, 1,而在第18级下,图块编号为:50617, 18851


  let point1 = null
  let point2 = null
  const minLevel = ref(1)
  const maxLevel = ref(20)

  //...

  function getRectPoints(point1, point2) {
    // 找到最大最小的经纬度坐标
    let maxLng,minLng,maxLat,minLat 
    if(point1[0] > point2[0]) {
        maxLng = point1[0]
        minLng = point2[0]
    }else{
        maxLng = point2[0]
        minLng = point1[0]
    }

    if(point1[1] > point2[1]) {
        maxLat = point1[1]
        minLat = point2[1]
    }else{
        maxLat = point2[1]
        minLat = point1[1]
    }

    return [
        //左上角
        new BMap.Point(minLng, maxLat),
        //右上角
        new BMap.Point(maxLng, maxLat),
        //右下角
        new BMap.Point(maxLng, minLat),
        //左下角
        new BMap.Point(minLng, minLat)
    ]
  }

  function startDraw() {
    drawRect.value = true
    point1 = null 
    point2 = null
  }

  function download() {
    let pointList = getRectPoints(point1, point2)
    const levelImgXYList = []
    for(let i = minLevel.value;i <= maxLevel.value; i++) {
        // 取右上角计算图块最大x,y 
        let [maxX,maxY] = pointToImgXY(pointList[1],i)
        // 取左下角计算图块最小x,y
        let [minX,minY] = pointToImgXY(pointList[3],i)
        levelImgXYList.push({
            level: i,
            minX,
            maxX,
            minY,
            maxY
        })
    }
  }

  function pointToImgXY(point,level) {
    var projection = map.getMapType().getProjection().lngLatToPoint(point)
    // 取得对应缩放层级的像素坐标  
    let pixelPoint = [projection.x*Math.pow(2, (level-18)), projection.y*Math.pow(2, (level-18))]
    // 获得图块编码  
    let imgPoint = [Math.floor(pixelPoint[0]/256), Math.floor(pixelPoint[1]/256)]
    return imgPoint
  }
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 选择下载目录

在 Electron 中,这可以通过 dialog.showOpenDialog 并设置 properties: ['openDirectory'] 实现。

  1. 主进程代码(main.js)
const { app, BrowserWindow, ipcMain, dialog } = require('electron')

ipcMain.handle('select-download-folder', async (event) => {
  const win = BrowserWindow.getFocusedWindow()
  const result = await dialog.showOpenDialog(win, {
    title: '选择下载文件夹',
    properties: ['openDirectory']
  })
  // result.canceled: 用户是否取消
  // result.filePaths: 用户选择的文件夹路径数组
  if (result.canceled || result.filePaths.length === 0) {
    return null
  } else {
    return result.filePaths[0] // 返回第一个选中的文件夹路径
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

  1. 预加载脚本(preload.js)
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('api', {
  selectDownloadFolder: () => ipcRenderer.invoke('select-download-folder')
})
1
2
3
4
5

  1. Vue 组件中调用
<template>
  <div>
    <button @click="chooseFolder">选择下载文件夹</button>
    <div v-if="folderPath">你选择的文件夹:{{ folderPath }}</div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const folderPath = ref('')

async function chooseFolder() {
  const result = await window.electronAPI.selectDownloadFolder()
  if (result) {
    folderPath.value = result
  } else {
    folderPath.value = '用户取消了选择'
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  1. 说明
  • 这样会弹出系统的“选择文件夹”对话框,用户只能选择文件夹,不能选文件。
  • 你可以用返回的文件夹路径进行后续的文件保存、下载等操作。
  1. 官方文档

界面写成如下的样子就可以开始编写python代码了,界面比较简单就不赘述了: