踐:從URL規(guī)則到內(nèi)網(wǎng)部署全解析)
去年有個(gè)項(xiàng)目讓我頭疼了整整一周。客戶的內(nèi)網(wǎng)系統(tǒng)里要嵌入一張城市地圖頁(yè)面早就做好地圖容器也畫(huà)出來(lái)了但打開(kāi)之后整張底圖全是灰的連一條道路都看不見(jiàn)。我當(dāng)時(shí)第一反應(yīng)是JS代碼報(bào)錯(cuò)查了半小時(shí)也沒(méi)查出問(wèn)題。后來(lái)打開(kāi)開(kāi)發(fā)者工具才發(fā)現(xiàn)瀏覽器一直在向高德的瓦片服務(wù)器發(fā)請(qǐng)求全部超時(shí)失敗。原因很直白內(nèi)網(wǎng)訪問(wèn)不了外網(wǎng)而地圖底圖本身就是一張張動(dòng)態(tài)請(qǐng)求下來(lái)的瓦片圖片。這次我不打算只貼一段下載代碼而是把“高德地圖瓦片”從URL規(guī)則、下載器編寫(xiě)、內(nèi)網(wǎng)服務(wù)搭建到前端接入這條鏈路完整拆開(kāi)講。讀完之后你至少能解決三件事搞懂高德瓦片URL的規(guī)律寫(xiě)一個(gè)能斷點(diǎn)續(xù)傳的瓦片下載器把瓦片用Nginx托管起來(lái)并讓Leaflet或高德JS API正常加載。適合正在做內(nèi)網(wǎng)GIS、數(shù)據(jù)大屏、離線地圖項(xiàng)目的開(kāi)發(fā)者參考。1. 為什么內(nèi)網(wǎng)里地圖總是一片空白瓦片加載機(jī)制與隔離網(wǎng)絡(luò)困境1.1 地圖瓦片到底是什么地圖上那套連續(xù)光滑的畫(huà)面其實(shí)被切成了固定大小的小方塊每個(gè)小方塊就是一張瓦片最常見(jiàn)的規(guī)格是256x256像素高清屏有512x512。所有瓦片按縮放級(jí)別組織級(jí)別越高地圖范圍越精細(xì)瓦片數(shù)量越多同一個(gè)級(jí)別下瓦片按x列號(hào)和y行號(hào)排列從左上角開(kāi)始計(jì)數(shù)。我經(jīng)常用一個(gè)拼圖比喻來(lái)解釋縮放級(jí)別是拼圖的精細(xì)度x和y是拼圖塊的行列坐標(biāo)z是整套拼圖的頁(yè)碼。瀏覽器里的高德地圖本質(zhì)上就是根據(jù)當(dāng)前視野范圍、中心點(diǎn)和縮放級(jí)別動(dòng)態(tài)計(jì)算需要哪幾頁(yè)拼圖的哪些塊然后向服務(wù)器把對(duì)應(yīng)圖片一張張取回來(lái)拼到屏幕對(duì)應(yīng)的位置上。沒(méi)有這些瓦片圖片底圖就什么都畫(huà)不出來(lái)。1.2 高德JS API加載底圖的完整鏈路高德JS API在頁(yè)面上初始化Map對(duì)象之后會(huì)自動(dòng)維護(hù)視圖狀態(tài)包括中心點(diǎn)經(jīng)緯度、縮放級(jí)別、DOM容器尺寸等。只要視圖有變化不管是拖動(dòng)、縮放還是窗口尺寸變化它都會(huì)基于瓦片編號(hào)算法算出當(dāng)前視野覆蓋的瓦片集合生成一組圖片URL把圖片元素插入到地圖容器里。這些URL指向的是高德的瓦片服務(wù)地址結(jié)構(gòu)大致如下https://webrd0{1-4}.is.autonavi.com/appmaptile?x{x}y{y}z{z}langzh_cnsize1scale1style8瀏覽器向這個(gè)地址發(fā)起HTTP請(qǐng)求高德服務(wù)器返回對(duì)應(yīng)的PNG圖片。整個(gè)過(guò)程對(duì)前端使用者是透明的你只看到地圖“畫(huà)”出來(lái)了背后其實(shí)是幾十張圖片在快速加載和拼接。只要中間任何一條鏈路走不通底圖就會(huì)空白。內(nèi)網(wǎng)環(huán)境最典型的問(wèn)題就是域名解析不到外網(wǎng)或者外網(wǎng)請(qǐng)求直接被防火墻攔截。1.3 內(nèi)網(wǎng)環(huán)境下的三種典型需求我實(shí)際接觸過(guò)的內(nèi)網(wǎng)地圖需求大概分三類出發(fā)點(diǎn)各不相同但最終都落到同一個(gè)技術(shù)動(dòng)作上業(yè)務(wù)系統(tǒng)地圖展示內(nèi)部管理平臺(tái)需要在地圖上疊加資產(chǎn)點(diǎn)位、人員軌跡、區(qū)域邊界底圖必須放在內(nèi)網(wǎng)這是最基礎(chǔ)的要求。數(shù)據(jù)可視化大屏大屏項(xiàng)目普遍追求高德風(fēng)格的街道圖或衛(wèi)星圖效果但又不能在演示現(xiàn)場(chǎng)賭外網(wǎng)帶寬瓦片本地化成了唯一靠譜方案。應(yīng)急離線地圖一些現(xiàn)場(chǎng)環(huán)境網(wǎng)絡(luò)狀態(tài)不穩(wěn)定提前把目標(biāo)區(qū)域瓦片下載到本地現(xiàn)場(chǎng)直接用靜態(tài)服務(wù)撐住整個(gè)演示方便又省心。這三類需求落到技術(shù)動(dòng)作上完全一致先把瓦片下載到內(nèi)網(wǎng)再讓前端從內(nèi)網(wǎng)加載。下面我從瓦片URL規(guī)則開(kāi)始拆解。2. 拆解高德瓦片URL規(guī)律z/x/y與坐標(biāo)系里藏著的門(mén)道2.1 從瀏覽器開(kāi)發(fā)者工具抓一個(gè)瓦片請(qǐng)求寫(xiě)下載器之前先得把瓦片URL的規(guī)律摸清楚。方法很簡(jiǎn)單打開(kāi)一個(gè)使用高德地圖的網(wǎng)頁(yè)按F12切到Network面板過(guò)濾Image類型請(qǐng)求拖動(dòng)一下地圖就能看到瓦片請(qǐng)求像瀑布一樣刷下來(lái)。一個(gè)典型的高德街道圖瓦片請(qǐng)求是這樣的https://webrd01.is.autonavi.com/appmaptile?langzh_cnsize1scale1style8x227y95z8其中webrd01是瓦片服務(wù)器編號(hào)高德做了負(fù)載均衡webrd02、webrd03、webrd04也能取到同樣的瓦片。z是縮放級(jí)別x和y是瓦片行列號(hào)style8表示街道圖scale1是普通分辨率返回256x256scale2返回512x512也就是高清瓦片。把這些參數(shù)拆清楚之后下載器的URL模板就可以直接構(gòu)造了。2.2 經(jīng)緯度與瓦片行列號(hào)的換算公式瓦片行列號(hào)不是隨機(jī)生成的它基于Web Mercator投影EPSG:3857做了全球劃分。把世界地圖看成一個(gè)矩形平面最左上角是(0,0)向右x增大向下y增大。在縮放級(jí)別z下全世界橫向和縱向都分成2的z次方份所以第z級(jí)總共有2^z乘以2^z張瓦片。已知經(jīng)緯度轉(zhuǎn)瓦片行列號(hào)的公式用Python可以寫(xiě)成這樣import math def lonlat_to_tile(lon, lat, z): n 2 ** z x int((lon 180.0) / 360.0 * n) rad math.radians(lat) y int((1.0 - math.asinh(math.tan(rad)) / math.pi) / 2.0 * n) return x, y這里asinh(tan(rad))和常用的ln(tan(rad) 1/cos(rad))是等價(jià)的只是寫(xiě)起來(lái)更簡(jiǎn)潔。Web Mercator在縱坐標(biāo)上用了非線性變換所以緯度越高相鄰?fù)咂淼膶?shí)際面積越小這也解釋了為什么高緯度地區(qū)在地圖上看起來(lái)被明顯放大了。2.3 GCJ-02坐標(biāo)系差異為什么用WGS-84坐標(biāo)算出來(lái)的瓦片會(huì)偏移高德瓦片最容易踩的隱性坑就是坐標(biāo)系。高德的坐標(biāo)體系是GCJ-02俗稱火星坐標(biāo)而GPS設(shè)備、第三方矢量數(shù)據(jù)常用的卻是WGS-84。瓦片行列號(hào)本身基于投影平面計(jì)算但在高德體系內(nèi)這個(gè)平面坐標(biāo)對(duì)應(yīng)的經(jīng)緯度基準(zhǔn)是GCJ-02不是WGS-84。如果你拿著一批GPS采集的WGS-84經(jīng)緯度坐標(biāo)直接套前面的公式算x/y去請(qǐng)求高德瓦片邊界框整體會(huì)偏移幾百米甚至上千米。結(jié)果就是你想下載A區(qū)域的瓦片實(shí)際拿到的卻是A區(qū)域偏移后的范圍。解決辦法是先做WGS-84到GCJ-02的坐標(biāo)轉(zhuǎn)換再參與行列號(hào)計(jì)算。如果項(xiàng)目里的坐標(biāo)本來(lái)就來(lái)自高德JS API或者高德地圖拾取器那它們已經(jīng)在GCJ-02體系內(nèi)直接用即可。坐標(biāo)轉(zhuǎn)換不建議自己造輪子用現(xiàn)成的coordtransform庫(kù)或者找一份成熟的七參數(shù)轉(zhuǎn)換實(shí)現(xiàn)都可以。這里不展開(kāi)但它確實(shí)是整個(gè)鏈路里最容易出問(wèn)題的一環(huán)后面第5章我會(huì)再提排查思路。2.4 瓦片規(guī)格普通屏與高清屏的差異高德瓦片接口的scale參數(shù)直接影響顯示效果和存儲(chǔ)成本。scale1返回256x256單張大約10-20KBscale2返回512x512單張大約30-60KB。同樣的范圍高清瓦片占用的存儲(chǔ)空間通常翻三到四倍。做普通PC端Web系統(tǒng)scale1完全夠用。做數(shù)字大屏或者需要截圖輸出的場(chǎng)景我建議直接用scale2否則畫(huà)面放大后全是鋸齒。關(guān)鍵點(diǎn)是下載時(shí)用了什么scale前端URL模板就要保持一致混用會(huì)導(dǎo)致瓦片物理尺寸不一致拼接出來(lái)非常難看。這個(gè)我在第5章的踩坑記錄里還會(huì)再講。3. 寫(xiě)一個(gè)瓦片下載器從邊界框計(jì)算到并發(fā)斷點(diǎn)續(xù)傳3.1 規(guī)劃下載范圍用經(jīng)緯度邊界框確定瓦片數(shù)量下載瓦片的第一步不是寫(xiě)循環(huán)而是先明確下載范圍。范圍一般用經(jīng)緯度矩形邊界框描述也就是最小經(jīng)度、最小緯度、最大經(jīng)度、最大緯度。縮放級(jí)別范圍取決于業(yè)務(wù)城區(qū)大屏項(xiàng)目通常15到17級(jí)就夠18級(jí)以上瓦片數(shù)量會(huì)暴漲下載時(shí)間和磁盤(pán)占用都不劃算。我習(xí)慣先估算瓦片數(shù)量再動(dòng)手。以北京城區(qū)大約1.5度經(jīng)度跨度、1.2度緯度跨度為例在17級(jí)下的數(shù)量大概是這樣的縮放級(jí)別2^z示例城市橫向瓦片數(shù)示例城市縱向瓦片數(shù)總瓦片數(shù)約15327681372182.9萬(wàn)166553627343711.9萬(wàn)1713107254687447.7萬(wàn)1826214410921748190.9萬(wàn)47萬(wàn)張256x256的PNG按平均15KB算大約7GB這個(gè)量級(jí)在單機(jī)磁盤(pán)上可以接受。但如果盲目把18級(jí)也加進(jìn)來(lái)量級(jí)立刻變成一百多萬(wàn)張存儲(chǔ)和時(shí)間成本都高一個(gè)量級(jí)。先算賬再動(dòng)手能省掉很多溝通成本。3.2 用邊界框換算出瓦片行列號(hào)集合有了邊界框就可以按級(jí)別生成行列號(hào)列表。這里有個(gè)新手容易搞反的地方Web Mercator中緯度越大的地方y(tǒng)值越小所以北邊界的y數(shù)比南邊界小。生成列表時(shí)需要對(duì)y方向取最小到最大不然范圍會(huì)亂。def tile_range(min_lon, min_lat, max_lon, max_lat, z): x0, y0 lonlat_to_tile(min_lon, max_lat, z) # 左上角 x1, y1 lonlat_to_tile(max_lon, min_lat, z) # 右下角 for x in range(x0, x1 1): for y in range(min(y0, y1), max(y0, y1) 1): yield x, y這個(gè)生成器給出某一級(jí)別下的行列號(hào)集合。再套一層級(jí)別循環(huán)就能得到完整任務(wù)列表。任務(wù)列表可以提前序列化到本地留檔方便后面斷點(diǎn)續(xù)傳時(shí)做對(duì)比也方便你分級(jí)別分批次執(zhí)行下載。3.3 編寫(xiě)下載函數(shù)單線程先跑通把坑都踩清楚我習(xí)慣先用單線程把一條鏈路跑通。這個(gè)版本不用考慮效率和優(yōu)雅重點(diǎn)是確定目錄結(jié)構(gòu)、請(qǐng)求頭、文件命名這些基礎(chǔ)約定。import os import requests def download_tile(z, x, y, url_template, save_root): save_dir os.path.join(save_root, str(z), str(x)) os.makedirs(save_dir, exist_okTrue) save_path os.path.join(save_dir, f{y}.png) url url_template.format(zz, xx, yy) resp requests.get(url, timeout10, headers{User-Agent: Mozilla/5.0}) if resp.status_code 200: with open(save_path, wb) as f: f.write(resp.content)目錄結(jié)構(gòu)是save_root/{z}/{x}/{y}.png這個(gè)結(jié)構(gòu)正好對(duì)應(yīng)Nginx靜態(tài)服務(wù)的URL路徑前端模板直接拼{z}/{x}/{y}.png就能用。單線程跑通的好處是能提前發(fā)現(xiàn)偶發(fā)超時(shí)、403、空文件這類問(wèn)題這些問(wèn)題在并發(fā)場(chǎng)景里會(huì)被無(wú)限放大后面排查起來(lái)更痛苦。3.4 并發(fā)下載與失敗重試單線程跑太慢幾萬(wàn)張瓦片能跑到地老天荒。我一般用ThreadPoolExecutor開(kāi)8個(gè)線程再配合重試機(jī)制和403/429退避邏輯。高德瓦片服務(wù)器對(duì)單IP的請(qǐng)求頻率有限制8個(gè)線程實(shí)測(cè)比較安全再往上就容易觸發(fā)風(fēng)控。import time import requests from concurrent.futures import ThreadPoolExecutor, as_completed def download_tile_safe(z, x, y, url_template, save_root, retries3): save_dir os.path.join(save_root, str(z), str(x)) os.makedirs(save_dir, exist_okTrue) save_path os.path.join(save_dir, f{y}.png) if os.path.exists(save_path) and os.path.getsize(save_path) 0: return True url url_template.format(zz, xx, yy) for attempt in range(retries): try: resp requests.get(url, timeout10, headers{ User-Agent: Mozilla/5.0, Referer: https://www.amap.com/ }) if resp.status_code 200: with open(save_path, wb) as f: f.write(resp.content) return True elif resp.status_code in (403, 429): time.sleep(2 * (attempt 1)) else: time.sleep(0.5) except Exception: time.sleep(1) return False def run_download(tasks, url_template, save_root, workers8): with ThreadPoolExecutor(max_workersworkers) as pool: futures [pool.submit(download_tile_safe, z, x, y, url_template, save_root) for z, x, y in tasks] for future in as_completed(futures): future.result()有個(gè)細(xì)節(jié)務(wù)必注意Referer頭一定要帶。瓦片服務(wù)器會(huì)校驗(yàn)請(qǐng)求來(lái)源沒(méi)有Referer的請(qǐng)求很容易直接403。退避間隔從2秒開(kāi)始成倍增長(zhǎng)如果重試三次都失敗就把這條任務(wù)記到日志文件里最后統(tǒng)一補(bǔ)下。3.5 斷點(diǎn)續(xù)傳中斷了不用從頭再來(lái)幾十萬(wàn)張瓦片的下載過(guò)程中網(wǎng)絡(luò)抖動(dòng)、服務(wù)器斷開(kāi)都可能導(dǎo)致任務(wù)中斷。斷點(diǎn)續(xù)傳的實(shí)現(xiàn)非常簡(jiǎn)單下載前判斷目標(biāo)文件是否存在存在且大小大于0就直接跳過(guò)。上面代碼里第一段判斷就是在做這件事。這個(gè)設(shè)計(jì)帶來(lái)的好處是任務(wù)中斷后重新跑一遍腳本已完成的瓦片全部跳過(guò)只補(bǔ)剩余部分。我還會(huì)把失敗任務(wù)單獨(dú)記錄全部跑完后統(tǒng)一用同樣的腳本重跑一次基本能把失敗率壓到極低。實(shí)測(cè)下來(lái)50萬(wàn)張瓦片的一次性下載成功率大概在99.5%左右剩下0.5%靠續(xù)傳補(bǔ)上整個(gè)流程很可靠。4. 內(nèi)網(wǎng)瓦片服務(wù)搭建Nginx托管與前端接入4.1 目錄結(jié)構(gòu)讓文件路徑與URL一一對(duì)應(yīng)下載完的瓦片目錄天然就是靜態(tài)文件結(jié)構(gòu)/data/map_tiles/ ├── 8/ │ ├── 226/ │ │ ├── 94.png │ │ └── 95.png │ └── 227/ │ ├── 94.png │ └── 95.png ├── 9/ │ └── ... └── 17/ └── ...HTTP請(qǐng)求/tiles/8/227/95.png可以直接映射到/data/map_tiles/8/227/95.png。只要Nginx配置里把URL前綴和磁盤(pán)路徑對(duì)應(yīng)好不需要任何路由改寫(xiě)前端模板地址就能直接命中物理文件。這個(gè)目錄結(jié)構(gòu)在下載器里就定好了所以下載和托管之間不會(huì)出現(xiàn)路徑斷層的麻煩。4.2 Nginx靜態(tài)托管配置我習(xí)慣單獨(dú)開(kāi)一個(gè)server塊專門(mén)服務(wù)瓦片不跟業(yè)務(wù)接口混在一起避免日志刷屏和路徑?jīng)_突。server { listen 8080; server_name _; location /tiles/ { alias /data/map_tiles/; autoindex off; expires 30d; add_header Cache-Control public, max-age2592000; try_files $uri 404; } }這里要特別注意alias和root的區(qū)別用了alias /data/map_tiles/URL里的/tiles/前綴會(huì)被去掉剩下的路徑直接對(duì)應(yīng)到/data/map_tiles/下。如果誤寫(xiě)成root /data/map_tiles/Nginx會(huì)去找/data/map_tiles/tiles/8/227/95.png路徑多了一層tiles立刻404。expires 30d讓瀏覽器把瓦片緩存一個(gè)月內(nèi)網(wǎng)環(huán)境下第二次拖動(dòng)地圖會(huì)特別流暢。4.3 Leaflet接入本地瓦片內(nèi)網(wǎng)項(xiàng)目如果不需要高德JS API的復(fù)雜交互能力用Leaflet加載本地瓦片是最省事的路線。L.tileLayer原生支持URL模板{z}、{x}、{y}三個(gè)變量會(huì)自動(dòng)替換成請(qǐng)求時(shí)的行列號(hào)。var map L.map(map, { center: [39.909, 116.397], zoom: 15, minZoom: 8, maxZoom: 17 }); L.tileLayer(http://192.168.1.100:8080/tiles/{z}/{x}/{y}.png, { maxZoom: 17, minZoom: 8, attribution: Map data ? AutoNavi }).addTo(map);這里有個(gè)坐標(biāo)基準(zhǔn)問(wèn)題高德瓦片是GCJ-02體系Leaflet本身并不關(guān)心坐標(biāo)系它只是把瓦片按位置貼到地圖上。中心點(diǎn)必須用GCJ-02坐標(biāo)才能和瓦片對(duì)齊如果直接用GPS采集的WGS-84坐標(biāo)地圖中心位置會(huì)出現(xiàn)偏移。在這個(gè)底圖上疊加WGS-84的GPS軌跡或POI數(shù)據(jù)同樣要先把數(shù)據(jù)坐標(biāo)轉(zhuǎn)成GCJ-02再疊加否則會(huì)整面偏移。4.4 高德JS API的內(nèi)網(wǎng)加載策略有些項(xiàng)目必須保留高德JS API的交互體驗(yàn)比如縮放控件、標(biāo)記動(dòng)畫(huà)、軌跡播放能力。這種情況下可以把高德JS API的JavaScript文件下載到內(nèi)網(wǎng)用內(nèi)網(wǎng)地址引用再通過(guò)自定義瓦片圖層把底圖URL指到內(nèi)網(wǎng)。高德JS API支持自定義AMap.TileLayer核心是重寫(xiě)getTileUrl方法var localLayer new AMap.TileLayer({ getTileUrl: function(x, y, z) { return http://192.168.1.100:8080/tiles/ z / x / y .png; }, zIndex: 100 }); var map new AMap.Map(container, { center: [116.397, 39.909], zoom: 15, layers: [localLayer] });這樣頁(yè)面看起來(lái)還是高德地圖底圖卻完全走內(nèi)網(wǎng)。需要提醒的是高德JS API內(nèi)網(wǎng)化要注意安全密鑰和域名白名單的配置這塊取決于項(xiàng)目申請(qǐng)API Key時(shí)的授權(quán)范圍。另外JS API里依賴在線服務(wù)的功能比如實(shí)時(shí)路況、搜索建議、路線規(guī)劃在內(nèi)網(wǎng)環(huán)境依然不可用需要用自建服務(wù)或者離線算法兜底。5. 實(shí)際項(xiàng)目中的坑偏移、白邊、限流與范圍規(guī)劃5.1 瓦片對(duì)不上號(hào)坐標(biāo)偏移的排查思路瓦片下載完成后最常遇到的癥狀是底圖出來(lái)了但業(yè)務(wù)數(shù)據(jù)整體偏了幾百米。遇到這種情況先別急著調(diào)前端按順序排查三件事第一確認(rèn)業(yè)務(wù)數(shù)據(jù)到底是什么坐標(biāo)系第二確認(rèn)瓦片行列號(hào)是基于什么坐標(biāo)系計(jì)算的第三確認(rèn)前端中心點(diǎn)和疊加數(shù)據(jù)用的坐標(biāo)基準(zhǔn)是否一致。高德體系內(nèi)默認(rèn)是GCJ-02GPS手持機(jī)、第三方矢量數(shù)據(jù)默認(rèn)是WGS-84。兩者在經(jīng)緯度數(shù)值上相差一點(diǎn)投影到地圖上就是幾百米的平移。我的解決習(xí)慣是項(xiàng)目一開(kāi)始就定好規(guī)范所有入庫(kù)坐標(biāo)統(tǒng)一轉(zhuǎn)成GCJ-02所有瓦片行列號(hào)用GCJ-02計(jì)算前端中心點(diǎn)直接用GCJ-02。統(tǒng)一基準(zhǔn)之后這個(gè)坑基本就不會(huì)再出現(xiàn)了。5.2 瓦片拼接處出現(xiàn)白線內(nèi)網(wǎng)加載本地瓦片后偶爾會(huì)在瓦片拼接縫看到一條細(xì)細(xì)的白線尤其在地圖縮放動(dòng)畫(huà)過(guò)程中最明顯。這不是瓦片沒(méi)下全而是瀏覽器在縮放或位移時(shí)對(duì)瓦片做了采樣相鄰?fù)咂g露出背景色看起來(lái)就是一條半透明的縫隙。處理辦法有幾個(gè)層次最簡(jiǎn)單的是把地圖容器背景色設(shè)成和地圖主色調(diào)接近的顏色比如淺灰或淺綠白線視覺(jué)上就不明顯了。如果追求更干凈的拼接可以給瓦片圖層加一點(diǎn)負(fù)邊距讓相鄰?fù)咂晕⒅丿B。另一個(gè)容易被忽略的原因是沒(méi)有統(tǒng)一scale參數(shù)混用256和512的瓦片會(huì)導(dǎo)致物理尺寸不一致拼接處自然對(duì)不齊。下載參數(shù)保持一致是底線。5.3 并發(fā)過(guò)高被限流403與429的處理下載腳本跑了一陣突然成片失敗日志里全是403或429說(shuō)明瓦片服務(wù)器已經(jīng)識(shí)別到請(qǐng)求頻率異常臨時(shí)把請(qǐng)求拒掉了。處理手段有三層一是把并發(fā)線程數(shù)降下來(lái)單IP 8個(gè)線程是相對(duì)安全的值超過(guò)10就容易被盯上二是給每個(gè)請(qǐng)求帶Referer: https://www.amap.com/模擬瀏覽器來(lái)源三是做好重試退避遇到403/429先等幾秒再重試不要反復(fù)快速請(qǐng)求。還有個(gè)實(shí)戰(zhàn)小技巧任務(wù)順序不要完全固定可以在任務(wù)列表里隨機(jī)打亂一下。太規(guī)律的并發(fā)模式容易被識(shí)別成批量程序稍微加點(diǎn)隨機(jī)性被限流的概率會(huì)低很多。我早期做下載器時(shí)沒(méi)注意這點(diǎn)連續(xù)兩次被限流后來(lái)加了個(gè)隨機(jī)shuffle再?zèng)]遇到過(guò)。5.4 瓦片數(shù)量估算與范圍規(guī)劃下載前先算瓦片數(shù)量這個(gè)習(xí)慣能救你很多次。我遇到過(guò)一個(gè)需求方張口就要全市所有級(jí)別的瓦片一算數(shù)據(jù)量幾十TB顯然不現(xiàn)實(shí)。后來(lái)把方案改成分級(jí)分區(qū)域下載主城區(qū)15到17級(jí)遠(yuǎn)郊區(qū)16到17級(jí)核心重點(diǎn)區(qū)域單獨(dú)補(bǔ)18級(jí)。這樣既滿足業(yè)務(wù)要求磁盤(pán)和帶寬也可控。判斷經(jīng)驗(yàn)一句話局域網(wǎng)內(nèi)網(wǎng)靜態(tài)服務(wù)加載瓦片非??炱款i從來(lái)不是帶寬而是瓦片準(zhǔn)備得夠不夠全、夠不夠細(xì)。寧可把高一級(jí)的瓦片范圍縮小也不要為了省事只下低級(jí)別等業(yè)務(wù)真的需要縮放時(shí)再補(bǔ)返工成本更高。5.5 合規(guī)邊界與瓦片更新從一個(gè)方案評(píng)審細(xì)節(jié)說(shuō)起最后說(shuō)一個(gè)原則性問(wèn)題。瓦片數(shù)據(jù)是高德投入成本生產(chǎn)的地圖數(shù)據(jù)受版權(quán)和服務(wù)條款保護(hù)。下載和內(nèi)網(wǎng)本地化使用適用于企業(yè)內(nèi)部系統(tǒng)、隔離網(wǎng)絡(luò)下的技術(shù)方案、已經(jīng)獲得授權(quán)的項(xiàng)目等場(chǎng)景。不要把下載下來(lái)的瓦片二次分發(fā)、轉(zhuǎn)售或者包裝成商業(yè)服務(wù)對(duì)外提供。動(dòng)手之前先確認(rèn)你的使用場(chǎng)景在授權(quán)范圍內(nèi)技術(shù)手段本身是中性的但使用邊界要自己把握清楚。我的個(gè)人習(xí)慣是在方案評(píng)審階段就把“瓦片來(lái)源、授權(quán)情況、更新機(jī)制”寫(xiě)進(jìn)技術(shù)文檔讓所有參與方心里有數(shù)。內(nèi)網(wǎng)地圖的核心問(wèn)題從來(lái)不只是怎么下載而是你有沒(méi)有一套可維護(hù)的瓦片更新和校驗(yàn)流程。先把本次范圍下好跑通鏈路后續(xù)需要增量更新時(shí)只要把邊界框和級(jí)別參數(shù)改一改重新跑一遍下載器就行。整套流程越簡(jiǎn)單越不容易出錯(cuò)。這套方法在后來(lái)幾個(gè)內(nèi)網(wǎng)大屏項(xiàng)目里反復(fù)用每次都是最省心的那一塊。