百度离线地图 2023年1月7日

文章讲述了在纯局域网环境下,为实现设备定位,作者需要下载百度地图的离线瓦片。面对网上资源的不足,作者尝试并分析了“斑马鱼”工具的下载效率问题,发现主要瓶颈在于下载调度和网络速度。为解决这个问题,作者编写了一个Go语言程序,通过工作池方式提高了瓦片下载速度,最终在服务器上成功高效地完成了420万个瓦片的下载任务。
摘要由CSDN通过智能技术生成

引子

最近一个WEB项目需求在纯局域网下显示设备定位,以前用惯了百度地图JS版 现在没外网了只有搞离线版了。百度官网是没有离线的JS版本,网上搜索了一圈下了不少评论中说好用的版本其中还花了十几块Down了个号称好用的。 本想着应该是妥妥的了,结果欠差人意,花钱的ZIP包中只有2个JS文件,其他的zip包里各种自己封装的JS和一些零星的地图瓦片。用基本都能用,问题集中在地图瓦片的获取上,于是在网上又找了不少瓦片下载工具,要么是过期下不到,要么是需要充钱才能用。最后聚焦在“斑马鱼”这款工具,1.免费2.功能也基本满足需求。 框选了个南京市大概范围 4-19级地图瓦片一共420多万个,软件提供50个线程同时下载 貌似不错 弄到凌晨5点多开下,一觉睡醒下午1点半。才下载了120多万条数据感觉有点懵逼 这下载速度有点鸡肋!!这玩不转啊,才只是一个南京市的瓦片的下载就需要这么长时间,要是搞几个省的,等这项目开始部署了瓦片也下不完呀。想办法提高瓦片下载的效率吧。

开搞、提速

研究了下“斑马鱼”,它有2个程序BMapTool.exe、BMapDown.exe。

BMapTool用来显示地图,框选下载区域,生成下载任务。

BMapDown读取下载任务,下载瓦片数据。

WinHex 打开生成的下载任务文件“xxxxx.tiles” 发现是个SQLite3数据库 里面3张表 task、tiles、zoom,从名字上看就知道各个表里是什么数据了。 再打开抓包工具看下瓦片是从哪下的,一目了然 URL是http://online3.map.bdimg.com/tile/?qt=vtile&x=6&y=1&z=5&styles=pl&scaler=1 其中参数 x y z 正好对应 tiles 表中 x y z 3个字段 那flag字段估计就是下载成功与否的标志位了。这下简单了所有需要下载的瓦片的URL都有了接下来就是提高下载工作的效率了的事了。“斑马鱼”下载慢主要原因应该是它对下载工作的调度的问题,还有就是本地网络下载速度的问题。

原因1:我在虚拟机里(win7 64位 2核)跑BMapDown下载数还一直跳个不停(网上下的EXE程序总是要先扔到虚拟机里跑下,抓下网络包看看它到底访问了什么网络,Procmon看下读取了哪些本地资源,看明白了才敢放在真机环境下跑)真机环境(win10 64位 16核)下载数下半天才跳一下。真机环境下载竟然比虚拟机慢一万倍,日了鬼了。

原因2:书房于客厅隔了两道墙WIFI信号不是满格。

解决方案:把下载工作直接扔到服务器上搞了事。下面是go写的瓦片下载程序,代码没多少,全贴出来。

dltiles.go

package main

import (
    "bufio"
    "database/sql"
    "fmt"
    _ "github.com/mattn/go-sqlite3"
    "io"
    "net/http"
    "os"
    "time"
)

//下载参数
type Job struct {
    Id   int
    Url  string
    Path string
    File string
}

//下载结果参数
type Result struct {
    Id  int
    Err error
}

//下载文件
func downFile(url string, path string, fn string) error {
    err := os.MkdirAll(path, 0777)
    if err != nil {
        fmt.Printf("Mkdir Error:%v\n", err)
        return err
    }

    fmt.Printf("Start Download %s\n", url)
    res, err := http.Get(url)
    if err != nil {
        fmt.Printf("Download URL:%s\tFail\n", url)
        return err
    }
    defer res.Body.Close()

    reader := bufio.NewReaderSize(res.Body, 64*1024)
    file, err := os.Create(path + fn)
    if err != nil {
        fmt.Printf("Create File:%s Fail Err:%v\n", path+fn, err)
        return err
    }

    writer := bufio.NewWriter(file)
    _, err = io.Copy(writer, reader)
    if err != nil {
        fmt.Printf("Write File:%s Fail Err:%v\n", path+fn, err)
        return err
    }

    fmt.Printf("%s -> %s OK\n", url, path+fn)
    return nil
}

//------------------------------------------------------------------------------------

//下载工作池
func DownloadPool(size int, jobChan chan *Job, retChan chan *Result) {
    for i := 0; i < size; i++ {
        go func(jobChan chan *Job, retChan chan *Result) {
            for job := range jobChan {
                err := downFile(job.Url, job.Path, job.File)
                r := &Result{
                    Id:  job.Id,
                    Err: err,
                }
                retChan <- r
            }
        }(jobChan, retChan)
    }
}

//------------------------------------------------------------------------------------

func checkErr(err error) {
    if err != nil {
        panic(err)
    }
}

//用时统计
func timeCost(start time.Time) {
    tc := time.Since(start)
    fmt.Printf("Time total: %v\n", tc)
}

func main() {
    //命令行参数处理
    if len(os.Args) < 3 {
        fmt.Println("dltiles [sqlite-file] [save-path]")
        os.Exit(0)
    }
    dbfile := os.Args[1]
    fmt.Printf("SQLite:%s\n", dbfile)
    path := os.Args[2]
    fmt.Printf("Save Path:%s\n", path)

    //准备下载工作
    jobChan := make(chan *Job, 256)
    retChan := make(chan *Result, 256)
    doneChan := make(chan int)
    DownloadPool(128, jobChan, retChan)

    t := time.Now()
    fmt.Printf("Time start: %s\n", t.Format("2006-01-02 15:04:05"))
    defer timeCost(t)

    //打开数据库
    db, err := sql.Open("sqlite3", dbfile)
    checkErr(err)

    //获取需要下载的瓦片数
    rows, err := db.Query("select count(*) as num from tiles")
    var Total int
    rows.Next()
    rows.Scan(&Total)
    rows.Close()
    fmt.Printf("Total: %d\n", Total)

    //下载结果计数
    go func(retChan chan *Result, total int, done chan int) {
        var iDone int
        for result := range retChan {
            if result.Err == nil {
                fmt.Printf("Job:%v Done\n", result.Id)
            } else {
                fmt.Printf("Job:%v Fail\t Err:%v\n", result.Id, result.Err)
            }
            iDone++
            if iDone == total {
                done <- 1
            }
        }
    }(retChan, Total, doneChan)

    //从sqlite取下载URL
    rows, err = db.Query("select * from tiles")
    checkErr(err)
    for rows.Next() {
        var id int
        var flag int
        var z int
        var x int
        var y int
        err = rows.Scan(&id, &flag, &z, &x, &y)
        checkErr(err)
        url := fmt.Sprintf("http://online3.map.bdimg.com/tile/?qt=vtile&x=%d&y=%d&z=%d&styles=pl&scaler=1", x, y, z)
        spath := fmt.Sprintf("%s/%d/%d/", path, z, x)
        sfn := fmt.Sprintf("%d.png", y)
        job := &Job{
            Id:   id,
            Url:  url,
            Path: spath,
            File: sfn,
        }
        jobChan <- job
    }
    db.Close()
    <-doneChan
}

使用方法:

服务器先安装go

wget https://golang.google.cn/dl/go1.17.5.linux-amd64.tar.gz

tar zxvf go1.17.5.linux-amd64.tar.gz

mv go /var/golang.17.5

rm go1.17.5.linux-amd64.tar.gz -rf

cd /usr/local/bin/

ln -s /var/golang.17.5/bin/go go

ln -s /var/golang.17.5/bin/gofmt gofmt

go env -w GO111MODULE=auto

然后下载SQLite依赖包

go get github.com/mattn/go-sqlite3

编译使用

go build dltiles.go

./dltiles xxx.ltiles(斑马鱼的SQLite任务文件) /download/tiles/(瓦片保存路径) >> tiles.log

或 直接 go run dltiles.go xxx.dltiles(斑马鱼的SQLite任务文件) /download/tiles/(瓦片保存路径) >> tiles.log

现在下载效率比PC上搞,快的起飞了420万多个瓦片 54分钟 搞定。

相关JS EXE 资源文中无法给出 给个链接 需要的哥们去下载吧。https://github.com/cnxbb/BaiduOfflineMap 题外话:现在的CSDN和以前的CSDN千差万别。什么JB玩意,发个帖子为了方便有同样需求的哥们们。

离线地图Demo

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>Offline Map Demo</title>
<style type="text/css">
    body, html,#offlinemap, #tab, #mapfrm {width: 100%;height: 100%;overflow: hidden;margin:0;font-family:"微软雅黑";}
    #offlinemap {
        display:block;
        position:fixed;
        top:0px;
        left:0px;
        right:0px;
        bottom:0px;
    }
</style>
<script type="text/javascript" src="jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="baidumapv2/baidumap_offline_v2_load.js"></script>
<script language="JavaScript">
    var map;
    $(document).ready(function() {
        map = new BMap.Map("offlinemap");    // 创建Map实例
        map.centerAndZoom(new BMap.Point(116.404, 39.915), 5 );  // 初始化地图,设置中心点坐标和地图级别
        map.enableScrollWheelZoom(true);     //开启鼠标滚轮缩放
        map.addControl(new BMap.NavigationControl());   //缩放按钮
        map.setMinZoom(5);//设置最小缩放等级
        //添加个大头针
        var point = new BMap.Point( 116.404, 39.915 );
        var dicon = new BMap.Icon( "mapding_red.png", new BMap.Size( 22, 40 ) );
        var mk = new BMap.Marker( point, { icon: dicon, offset: new BMap.Size(0, -20 ) } );
        setTimeout( function() {
            map.panTo( point );
        }, 200 );
        map.addOverlay( mk );
    });
</script>
</head>
<body style="padding:0px;margin:0px;">
    <div id="offlinemap"></div>
</body>

</html>

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值