-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmap_tiles.py
More file actions
90 lines (78 loc) · 3.73 KB
/
Copy pathmap_tiles.py
File metadata and controls
90 lines (78 loc) · 3.73 KB
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
81
82
83
84
85
86
87
88
89
90
"""
地图底图瓦片 (Web Mercator)
- 用 Web Mercator 把经纬度投影到全局像素坐标, 这样车站/线路能和地图瓦片精确对齐。
- 瓦片来自 CARTO 暗色底图 (基于 OpenStreetMap), 无需 key, 配暗色界面。
- 瓦片下载到本地缓存 tiles_cache/。下载失败时调用方可不显示底图(只画线路)。
注意: 网络请求(urllib)只应在 App 运行时调用; 纯数学函数可离线测试。
归属: © OpenStreetMap contributors © CARTO
"""
from __future__ import annotations
import math
import os
import urllib.request
_HERE = os.path.dirname(os.path.abspath(__file__))
# 缓存目录带样式名: 换底图样式后不会误用旧样式的缓存
_TILE_CACHE = os.path.join(_HERE, "tiles_amap")
TILE = 256
# 高德地图瓦片 (GCJ-02, 含中文标注; style=7 路网图)。{s}=子域 webrd01..04
_URL = "https://webrd{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}"
_SUBS = ("01", "02", "03", "04")
def lonlat_to_px(lat, lon, z):
"""经纬度 → 该 zoom 下的全局像素坐标 (x, y)。"""
n = 2 ** z
x = (lon + 180.0) / 360.0 * n * TILE
la = math.radians(max(-85.05112878, min(85.05112878, lat)))
y = (1.0 - math.log(math.tan(la) + 1.0 / math.cos(la)) / math.pi) / 2.0 * n * TILE
return x, y
def choose_zoom(min_lat, max_lat, min_lon, max_lon, target_px=860, zmax=16, zmin=3):
"""选一个 zoom, 使包围盒在该 zoom 下的像素跨度 <= target_px。"""
for z in range(zmax, zmin - 1, -1):
xa, ya = lonlat_to_px(max_lat, min_lon, z)
xb, yb = lonlat_to_px(min_lat, max_lon, z)
if abs(xb - xa) <= target_px and abs(yb - ya) <= target_px:
return z
return zmin
def tile_range(min_lat, max_lat, min_lon, max_lon, z, max_tiles=49):
"""返回覆盖包围盒的瓦片坐标列表 [(x, y), ...](含边缘各扩 1 圈)。"""
xa, ya = lonlat_to_px(max_lat, min_lon, z)
xb, yb = lonlat_to_px(min_lat, max_lon, z)
n = 2 ** z
tx0, tx1 = sorted([int(xa // TILE), int(xb // TILE)])
ty0, ty1 = sorted([int(ya // TILE), int(yb // TILE)])
tx0 = max(0, tx0 - 1); ty0 = max(0, ty0 - 1)
tx1 = min(n - 1, tx1 + 1); ty1 = min(n - 1, ty1 + 1)
tiles = [(x, y) for x in range(tx0, tx1 + 1) for y in range(ty0, ty1 + 1)]
return tiles[:max_tiles]
def fetch_tile(z, x, y, timeout=15):
"""下载(或读缓存)一块瓦片, 返回本地 PNG 路径; 失败返回 None。"""
os.makedirs(_TILE_CACHE, exist_ok=True)
path = os.path.join(_TILE_CACHE, f"{z}_{x}_{y}.png")
if os.path.exists(path) and os.path.getsize(path) > 0:
return path
sub = _SUBS[(x + y) % len(_SUBS)]
url = _URL.format(s=sub, z=z, x=x, y=y)
try:
req = urllib.request.Request(url, headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Referer": "https://www.amap.com/"})
with urllib.request.urlopen(req, timeout=timeout) as r:
data = r.read()
if not data:
return None
with open(path, "wb") as f:
f.write(data)
return path
except Exception:
return None
if __name__ == "__main__":
# 离线自测: 投影/zoom/瓦片范围
# 東京都心一小块: 大约 lat 35.66~35.74, lon 139.70~139.80
z = choose_zoom(35.66, 35.74, 139.70, 139.80)
print("choose_zoom (东京都心) =", z)
px = lonlat_to_px(35.6897, 139.7004, z) # 新宿
print("新宿 像素坐标 @z%d =" % z, (round(px[0], 1), round(px[1], 1)))
tiles = tile_range(35.66, 35.74, 139.70, 139.80, z)
print("瓦片数:", len(tiles), "示例:", tiles[:3])
# 单段近距离应得到更高 zoom
z2 = choose_zoom(35.689, 35.700, 139.700, 139.704)
print("choose_zoom (两近站) =", z2, "(应比上面大)")