第一期 Google 谷歌地图(高德地图)瓦片下载/缓存,使用Python实现(一)_谷歌瓦片地图-CSDN博客
第二期 Google 谷歌地图(高德地图)瓦片下载/缓存,使用Python实现(二)_谷歌瓦片失效-CSDN博客
第四期 Google 谷歌地图(高德地图)瓦片下载/缓存,使用Python实现、自定义上传地图瓦片资源(四)-CSDN博客
后续可能还会出第四期,请持续关注。
1、项目流程
上期解释了地图瓦片的大致规则并且使用python实现了请求瓦片的工具。那么现在我们来说说结合前端显示具体实现流程。先请看下面的流程图:
地图请求流程图
地图参数设置流程图
2、使用技术与实现
2.1 技术选型:
前端使用:原生HTML,CSS,JavaScript,jQuery 3.6.0 地图控件:arcgis.js 4.22版本
后端使用 python 3.9.4 ,flask 3.0.3,requests 2.31.0 ,Flask-SocketIO 5.3.6
2.2 界面展示
2.3 核心代码
前端html代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>地图缓存</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 600px;
width: 600px;
}
</style>
</head>
<body>
<div id="viewDiv"></div>
<div id="opt">
<div id="mapURLDiv">
<label for="mapUrl">地图源:</label>
<select id="mapUrl" name="mapUrl">
<option value="googleMap" >谷歌地图</option>
<option value="AMap" selected>高德地图</option>
</select>
</div>
<div id="mapURLChooseDiv">
<label for="mapType">地图类型:</label>
<select id="mapType" name="mapType">
<option value="1" >卫星图</option>
<option value="2" selected>路线图</option>
</select>
<br>
<label for="parameterVal">更多参数:</label>
<textarea id="parameterVal" name="parameterVal" rows="4" cols="50">
</textarea>
<br>
</div>
<input id="setMapProp" type="button" value="提交">
</div>
<!-- 导入arcgis -->
<link rel="stylesheet" href="https://js.arcgis.com/4.22/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.22/"></script>
<!-- 引入jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/static/index.js"></script>
</body>
</html>
JavaScript 核心代码
//使用arcgis.js
require([
"esri/Map",
"esri/views/SceneView",
"esri/views/MapView",
"esri/layers/TileLayer",
"esri/layers/BaseTileLayer",
"esri/Color",
"esri/request",
],
(Map, SceneView, MapView,TileLayer,BaseTileLayer, Color, esriRequest) => {
// *******************************************************
// 自定义图层类代码
// 创建一个BaseTileLayer的子类
// *******************************************************
const TintLayer = BaseTileLayer.createSubclass({
properties: {
urlTemplate: null,
tint: {
value: null,
type: Color,
},
},
//为LayerView提供的给定级别、行和列生成tile url
getTileUrl: function (level, row, col) {
return this.urlTemplate.replace("{z}", level).replace("{x}", col).replace("{y}", row);
},
// 此方法获取指定级别和大小的瓦片
// 重写此方法以处理从服务器返回的数据
fetchTile: function (level, row, col, options) {
// 调用getTileUrl()方法来构造tiles的URL
// 对于LayerView提供的给定级别、行和列
const url = this.getTileUrl(level, row, col);
// 基于生成的url请求平铺
// the signal option 确保废弃的请求被中止
return esriRequest(url, {
responseType: "image",
signal: options && options.signal,
}).then(
function (response) {
// esri请求解析成功时
// 从响应中获取图像
const image = response.data;
const width = this.tileInfo.size[0];
const height = this.tileInfo.size[0];
// 使用二维渲染上下文创建画布
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
// 将应用程序提供的色调应用于画布
if (this.tint) {
// 获取一个rgba形式的CSS颜色字符串,表示tint color实例.
context.fillStyle = this.tint.toCss();
context.fillRect(0, 0, width, height);
// 在画布和steman平铺之间应用“差异”混合操作。差值混合操作从顶层(瓦片)中减去底层(画布),或者反过来总是得到一个正值
context.globalCompositeOperation = "difference";
}
//将混合图像绘制到画布上。
context.drawImage(image, 0, 0, width, height);
return canvas;
}.bind(this)
);
},
});
let centerPoint = [116.404177,39.909651]; //大概是北京天安门的经纬度
// 创建一个瓦片图层实例
myTileLayer = new TintLayer({
// urlTemplate: "http://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
urlTemplate: "/mapImg/{z}/{x}/{y}",
title: "高德",
});
//底层
map = new Map({
layers : [myTileLayer]
});
//3D容器
view = new MapView({
container: "viewDiv", //与html元素绑定
map: map, //设置底层
zoom: 15, //初始地图距离大小
center: centerPoint //中心点(可以取小数) ,这里设置的北京
});
// 刷新瓦片
refreshMapTileFunction = function refreshMapTile(){
map.remove(myTileLayer)
myTileLayer = new TintLayer({
urlTemplate: "/mapImg/{z}/{x}/{y}",
title: "高德",
});
map.add(myTileLayer)
}
})
python 瓦片请求接口代码
@app.route("/mapImg/<z>/<x>/<y>")
def mapImg(x, y, z):
imgTypes = ["jpeg", "png", "jpg"]
mapURLValue = setting.global_mapURLValue
mapURLStyle = setting.global_mapURLStyle
LOCAL_IMAGE_CACHE_DIR = "./map/" + mapURLValue + "/" + mapURLStyle + "/" + str(z) + "/" + str(x) + "/"
# local_image_path = os.path.join(LOCAL_IMAGE_CACHE_DIR, str(y) + ".jpeg")
for imgType in imgTypes:
file_path = os.path.join(LOCAL_IMAGE_CACHE_DIR, str(y) + "." + imgType)
if os.path.isfile(file_path) and os.path.exists(file_path):
# 无论图片是从缓存读取还是刚下载完,都返回给客户端
with open(file_path, 'rb') as img_file:
img_data = img_file.read()
# 设置正确的MIME类型
mime_type = 'image/' + imgType # 根据实际情况设置正确的图片MIME类型
response = Response(img_data, mimetype=mime_type)
# 返回图片数据
return response
savePath = None
urlStr = None
contentType = None
mapUrl = DataCheckUtils.dataCheckNone(setting.global_mapURLValue)
httpParameter = DataCheckUtils.dataCheckNone(setting.global_mapURLHttpParameter)
httpProxies = setting.global_mapURLProxies
httpHeaders = setting.global_mapURLHeaders
# 下载地图前,参考用户设置的值
if mapUrl.__eq__("googleMap"):
lyrs = DataCheckUtils.dataCheckNone(setting.global_mapURLStyle,"s")
hl = DataCheckUtils.dataCheckNone(setting.global_mapURLhl,"zh-CN")
gl = DataCheckUtils.dataCheckNone(setting.global_mapURLgl,"cn")
# httpParameter = None
headers = httpHeaders
proxies = httpProxies
savePathGoogle, urlStrGoogle, contentTypeGoogle = MapDownloadUtils.googleMapDownload(x, y, z,lyrs=lyrs, hl=hl, gl=gl, httpParameter=httpParameter, headers=headers, proxies=proxies)
savePath = savePathGoogle
urlStr = urlStrGoogle
contentType = contentTypeGoogle
elif mapUrl.__eq__("AMap"):
style = DataCheckUtils.dataCheckNone(setting.global_mapURLStyle,"6")
headers = httpProxies
proxies = httpHeaders
savePathAMap, urlStrAMap, contentTypeAMap = MapDownloadUtils.AMapDownload(x, y, z,style=style, httpParameter=httpParameter, headers=headers, proxies=proxies)
savePath = savePathAMap
urlStr = urlStrAMap
contentType = contentTypeAMap
# 无论图片是从缓存读取还是刚下载完,都返回给客户端
with open(savePath, 'rb') as img_file:
img_data = img_file.read()
# 设置正确的MIME类型
mime_type = contentType # 根据实际情况设置正确的图片MIME类型
response = Response(img_data, mimetype=mime_type)
# 返回图片数据
return response
完整代码:
GitHub:
Crazy-GrowUp/flask-mapimg at v1.0.0 (github.com)
https://github.com/Crazy-GrowUp/flask-mapimg/tree/v1.0.0
Gitee:
flask-mapimg: 可自动缓存地图瓦片,保证项目下次加载更迅速 后端使用python flask 前端地图使用 arcgis.js - Gitee.com
https://gitee.com/jack_of_disco/flask-mapimg/tree/v1.0.0/
优化空间
1、地图中心位置可以提供
2、地图瓦片缓存可以更自动更深度下载
如请求瓦片地址是 z/x/y ==> 3/5/7 ,
那么我们可以提前向深层下载(z暂时理解为深度):
a) z+1/x * 2/y * 2 ==> 4/10/14
b) z+1/x * 2+1/y * 2 ==> 4/11/14
c) z+1/x * 2/y * 2+1 ==> 4/10/15
d) z+1/x * 2+1/y * 2+1 ==> 4/11/15
然后再深层一点 ,可以设置默认深入几层下载,这样算是提前预判用户的下一步操作吧,也可以使得地图加载的更快
3、地图资源所占磁盘大小空间
如果要下载全球的地图瓦片资源需要的空间大概是6.59TB,那服务器无限制的缓存,早晚会把硬盘填满。那么可以再写一个方法,每次下载资源前或者每天一次检查资源的大小,如果超过了就删除很久没用到资源,每次删除100MB或者自定义。所以这里还要记录瓦片的最近一次的使用时间(建议持久化)。
如果大家还有什么好的建议,可以打在评论区,欢迎大家来交流,后续可能还会更新此系列内容,也请大家多多关注、点赞、收藏。
3、参考链接
1、欢迎来到 Flask 的世界 — Flask中文文档(3.0.x) (dormousehole.readthedocs.io)
https://dormousehole.readthedocs.io/en/latest/
2、arcgis for javascript TileLayer 自定义高德地图图层_高德tilelayer-CSDN博客
https://blog.csdn.net/jieyucx/article/details/131083769
3、6.59TB 全球地名路网透明标签瓦片地图 (baidu.com)
https://baijiahao.baidu.com/s?id=1744453723688131827&wfr=spider&for=pc