據(jù)轉(zhuǎn)換與區(qū)縣下鉆實戰(zhàn))
簡介這份資源面向需要在 Web 頁面中展示重慶區(qū)域地理數(shù)據(jù)的開發(fā)者提供基于 ECharts 實現(xiàn)重慶地圖可視化的基礎(chǔ)素材適合數(shù)據(jù)可視化入門與地圖交互開發(fā)場景。壓縮包共 2 個文件包含 1 個 JSON 與 1 個 HTML整體約 18KBJSON 文件承載重慶各行政區(qū)劃的名稱與經(jīng)緯度坐標(biāo)是繪制地圖邊界與形狀的數(shù)據(jù)基礎(chǔ)HTML 文件則給出引入 ECharts、創(chuàng)建容器、初始化實例并通過 setOption 加載地圖配置的完整頁面模板兩者配合即可快速跑通地圖渲染流程。目前已有 2425 人學(xué)習(xí)下載說明該素材在同類需求中具備一定參考價值。讀者可據(jù)此掌握 map 類型 series 的配置方式并在此基礎(chǔ)上擴展區(qū)域著色、懸浮提示、點擊事件與縮放平移等交互或替換數(shù)據(jù)源實現(xiàn)自定義熱點展示為后續(xù)地理信息可視化項目提供可復(fù)用的起點。1. Echarts重慶地圖從行政區(qū)劃到可視化的落地路徑做過數(shù)據(jù)大屏的工程師大概率都遇到過這個需求業(yè)務(wù)方甩過來一份帶區(qū)縣字段的表格要求在地圖上按區(qū)域著色最好還能下鉆到街道。Echarts 本身不內(nèi)置重慶的行政區(qū)劃邊界官方地圖倉庫里也沒有現(xiàn)成的重慶 GeoJSON所以“Echarts重慶地圖”這件事的核心不是寫配置項而是先把地理數(shù)據(jù)這條鏈路打通。它適合三類人做大屏前端但沒碰過地理數(shù)據(jù)的、需要按區(qū)縣做業(yè)務(wù)指標(biāo)可視化的、以及想給現(xiàn)有系統(tǒng)加一張區(qū)域熱力圖的后端轉(zhuǎn)全棧。整條鏈路拆開就是四步拿到合規(guī)的邊界數(shù)據(jù)、轉(zhuǎn)成 Echarts 認(rèn)的 GeoJSON、注冊地圖并配置 series、最后處理下鉆和性能。下面按這個順序講透每一步都給可復(fù)現(xiàn)的命令和代碼。2. 重慶地圖數(shù)據(jù)從哪來邊界獲取與格式轉(zhuǎn)換2.1 為什么不能直接用官方地圖包Echarts 5 之后官方不再隨包提供地圖 JSONecharts/map目錄已經(jīng)移除。很多人第一反應(yīng)是去翻舊版本或者找第三方 CDN 上的chongqing.js這條路有兩個坑一是舊數(shù)據(jù)邊界可能還是幾年前的行政區(qū)劃重慶這些年有過區(qū)縣調(diào)整二是第三方 CDN 的文件來源不可控生產(chǎn)環(huán)境引外鏈有可用性風(fēng)險。常見做法是從公開的行政區(qū)劃數(shù)據(jù)源獲取自己轉(zhuǎn)成 GeoJSON 后本地托管。我一般會優(yōu)先用帶adcode字段的數(shù)據(jù)源因為重慶的區(qū)縣代碼是穩(wěn)定的后續(xù)做數(shù)據(jù)匹配時比中文名可靠得多。獲取渠道上公開的地理數(shù)據(jù)平臺會提供按省下載的 GeoJSON下載時注意選“區(qū)縣級”精度而不是“市級”否則整個重慶只有一個外輪廓沒法按區(qū)著色。文件下來之后先別急著用用命令行看一眼結(jié)構(gòu)確認(rèn)features數(shù)組里每個元素的properties有沒有name和adcode。# 查看下載的 GeoJSON 里有多少個區(qū)縣要素 python -c import json with open(chongqing_raw.geojson, r, encodingutf-8) as f: data json.load(f) print(要素數(shù)量:, len(data[features])) # 打印前三個要素的屬性確認(rèn)字段名 for feat in data[features][:3]: print(feat[properties]) 這段腳本的作用是快速體檢數(shù)據(jù)。features的數(shù)量應(yīng)該接近重慶的區(qū)縣總數(shù)38 個左右含自治縣如果只有 1 個說明下錯了精度層級。properties里如果字段名是NAME或fullname而不是name后面注冊地圖時 Echarts 匹配不上需要先做字段重命名。參數(shù)上沒什么可調(diào)的重點是看輸出字段名不對就在轉(zhuǎn)換腳本里統(tǒng)一改掉。2.2 用 Python 把邊界數(shù)據(jù)轉(zhuǎn)成 Echarts 認(rèn)的 GeoJSONEcharts 的registerMap接受標(biāo)準(zhǔn) GeoJSON但有幾個隱性要求坐標(biāo)系必須是 WGS84 或 GCJ02 且全圖統(tǒng)一geometry.type支持Polygon和MultiPolygonproperties.name必須存在且和 series 里的name完全一致。公開數(shù)據(jù)源有時會帶一堆用不上的屬性字段文件體積大加載慢轉(zhuǎn)換時順手精簡掉。import json def convert_to_echarts_geojson(src_path, dst_path): with open(src_path, r, encodingutf-8) as f: data json.load(f) features [] for feat in data[features]: props feat[properties] # 統(tǒng)一取 name 字段兼容不同數(shù)據(jù)源的命名 name props.get(name) or props.get(NAME) or props.get(fullname) adcode props.get(adcode) or props.get(ADCODE) if not name: continue # 沒有名稱的要素直接丟棄否則地圖上會出現(xiàn)無名區(qū)域 features.append({ type: Feature, properties: { name: name, adcode: str(adcode) if adcode else }, geometry: feat[geometry] }) out {type: FeatureCollection, features: features} with open(dst_path, w, encodingutf-8) as f: # ensure_asciiFalse 保證中文名不被轉(zhuǎn)義成 \uXXXX json.dump(out, f, ensure_asciiFalse) convert_to_echarts_geojson(chongqing_raw.geojson, chongqing.json)邏輯說明遍歷原始要素把可能叫name/NAME/fullname的字段統(tǒng)一成name這是 Echarts 匹配區(qū)域的關(guān)鍵。adcode保留成字符串方便后續(xù)和業(yè)務(wù)數(shù)據(jù)里的區(qū)縣代碼對齊。ensure_asciiFalse這個參數(shù)很容易被忽略不加的話中文區(qū)縣名會變成 Unicode 轉(zhuǎn)義雖然 Echarts 也能解析但調(diào)試時肉眼沒法看。轉(zhuǎn)換完檢查一下輸出文件大小精簡后通常在幾百 KB 到 1MB 之間如果超過 2MB 說明屬性沒清干凈或者精度過高可以考慮用工具做坐標(biāo)抽稀。2.3 注冊地圖與最小可運行配置數(shù)據(jù)準(zhǔn)備好之后前端側(cè)的核心就兩個 APIecharts.registerMap注冊地圖然后在geo或series-map里引用。很多人卡在注冊了地圖但頁面空白八成是registerMap的第二個參數(shù)傳的是文件路徑字符串而不是解析后的對象或者注冊名和map配置項不一致。import * as echarts from echarts; import chongqingJson from ./chongqing.json; // 打包工具會解析成對象 // 注冊地圖第一個參數(shù)是自定義地圖名后續(xù)配置項要用同一個名字 echarts.registerMap(chongqing, chongqingJson); const chart echarts.init(document.getElementById(map)); chart.setOption({ tooltip: { trigger: item, formatter: }, series: [{ type: map, map: chongqing, // 必須和 registerMap 的第一個參數(shù)一致 roam: true, // 允許縮放和拖拽 label: { show: true, fontSize: 10 }, itemStyle: { areaColor: #f0f2f5, borderColor: #999 }, emphasis: { itemStyle: { areaColor: #ffd666 } } }] });參數(shù)說明map的值是注冊時的名字不是文件名這里最容易翻車。roam: true開啟縮放拖拽大屏場景建議開但要注意和頁面滾動的沖突移動端可能需要關(guān)掉。label.show在區(qū)縣數(shù)量多的時候會顯得擁擠重慶 38 個區(qū)縣全顯示標(biāo)簽會糊成一片建議只在emphasis里顯示或者按縮放級別動態(tài)控制。到這一步一張能交互的重慶地圖就跑起來了接下來才是把業(yè)務(wù)數(shù)據(jù)接進(jìn)去。3. 把業(yè)務(wù)數(shù)據(jù)映射到區(qū)縣series 配置與視覺編碼3.1 數(shù)據(jù)匹配的三種方式與踩坑地圖畫出來只是殼真正有價值的是把指標(biāo)數(shù)據(jù)映射到區(qū)域顏色上。Echarts 的series-map通過data數(shù)組里的name和 GeoJSON 的properties.name做匹配匹配不上就是灰色。實際項目里數(shù)據(jù)來源五花八門區(qū)縣名可能有“渝北區(qū)”和“渝北”兩種寫法或者帶空格直接匹配必然有一部分區(qū)域是灰的。常見做法有三種一是數(shù)據(jù)側(cè)統(tǒng)一清洗成和 GeoJSON 完全一致的名稱二是用nameMap做映射三是改用adcode匹配。我一般推薦第一種加第三種組合名稱用于展示adcode 用于匹配。如果數(shù)據(jù)里只有名稱那就老老實實做一次名稱對齊把“XX自治縣”簡寫成“XX縣”這類差異在 ETL 階段處理掉別指望前端兜底。// 假設(shè)后端返回的數(shù)據(jù)里區(qū)縣名可能帶“區(qū)”字差異 const rawData [ { region: 渝北, value: 320 }, { region: 沙坪壩區(qū), value: 280 }, { region: 九龍坡, value: 260 } ]; // 名稱歸一化統(tǒng)一補全或去掉后綴和 GeoJSON 對齊 function normalizeName(name) { // 這里以 GeoJSON 里是“渝北區(qū)”這種全稱為例 if (!name.endsWith(區(qū)) !name.endsWith(縣)) { return name 區(qū); } return name; } const seriesData rawData.map(item ({ name: normalizeName(item.region), value: item.value }));邏輯說明normalizeName是個兜底函數(shù)真實項目里名稱差異遠(yuǎn)不止補個“區(qū)”字還有“兩江新區(qū)”這種不屬于標(biāo)準(zhǔn)行政區(qū)劃但業(yè)務(wù)上要單獨統(tǒng)計的情況。遇到這種要么在 GeoJSON 里手動補一個虛擬區(qū)域要么在業(yè)務(wù)側(cè)歸并到所屬區(qū)縣。參數(shù)上value決定顏色深淺如果數(shù)據(jù)里有 0 和 null 的區(qū)別null 的區(qū)域會顯示為默認(rèn)色0 會顯示為最小值色視覺上要區(qū)分開。3.2 visualMap 分段與連續(xù)兩種模式的選型顏色映射靠visualMap控制分連續(xù)型和分段型。連續(xù)型適合展示趨勢顏色漸變平滑分段型適合做等級劃分比如把指標(biāo)分成“高/中/低”三檔業(yè)務(wù)方一眼能看懂。選哪個取決于數(shù)據(jù)分布如果數(shù)據(jù)是長尾分布連續(xù)型會導(dǎo)致大部分區(qū)域顏色接近看不出差異這時候分段型更實用。visualMap: { type: piecewise, // 分段型改成 continuous 就是連續(xù)型 pieces: [ { min: 300, label: 高 (≥300), color: #d73027 }, { min: 150, max: 299, label: 中 (150-299), color: #fee08b }, { max: 149, label: 低 (150), color: #1a9850 } ], left: 20, bottom: 20, textStyle: { color: #333 } }參數(shù)說明pieces數(shù)組的順序會影響圖例展示一般從高到低排。min/max的邊界要覆蓋數(shù)據(jù)全集否則超出范圍的區(qū)域會沒有顏色。分段型的一個坑是max: 149這種寫法如果數(shù)據(jù)里有小數(shù)比如 149.5會落到?jīng)]有匹配的區(qū)間建議用min: 0, max: 149明確閉合。連續(xù)型則要注意min和max不設(shè)的話 Echarts 會自動取數(shù)據(jù)極值但如果有 null 值可能算錯手動指定更穩(wěn)。3.3 區(qū)縣下鉆點擊事件與地圖切換大屏需求里十有八九要做下鉆點某個區(qū)縣切換到該區(qū)縣的街道級地圖。實現(xiàn)思路是監(jiān)聽click事件拿到區(qū)縣名或 adcode動態(tài)registerMap新的地圖數(shù)據(jù)再setOption。這里的關(guān)鍵是街道級數(shù)據(jù)要提前準(zhǔn)備好按 adcode 命名文件點擊時按需加載。chart.on(click, async (params) { if (params.componentType ! series) return; const adcode params.data?.adcode; // 需要 GeoJSON 里帶 adcode 并透傳到 data if (!adcode) return; // 動態(tài)加載對應(yīng)區(qū)縣的街道級 GeoJSON const res await fetch(/maps/${adcode}.json); const townJson await res.json(); echarts.registerMap(town_${adcode}, townJson); chart.setOption({ series: [{ type: map, map: town_${adcode}, data: [] // 街道級數(shù)據(jù)按需再請求 }] }); });邏輯說明params.data.adcode能拿到值的前提是 series 的 data 里帶了 adcode 字段而 data 又來自業(yè)務(wù)數(shù)據(jù)所以業(yè)務(wù)數(shù)據(jù)里必須包含 adcode。如果只有名稱就得在點擊時反查一張名稱到 adcode 的映射表。動態(tài)registerMap會不斷往內(nèi)存里加地圖下鉆層級多了要注意清理Echarts 沒有提供反注冊 API常見做法是控制下鉆深度或者用 iframe 隔離。返回上一級時重新注冊父級地圖即可但要注意setOption的合并策略地圖類型切換建議用notMerge: true避免殘留配置。4. 避坑與排查重慶地圖最常見的五類問題4.1 地圖顯示空白或只有背景色現(xiàn)象頁面初始化后容器有大小但地圖區(qū)域一片空白控制臺無報錯。原因通常是registerMap沒執(zhí)行成功或者map配置項的名字和注冊名不一致。排查時先在registerMap后面打印一下echarts.getMap(chongqing)如果返回 undefined 說明注冊失敗多半是 GeoJSON 對象結(jié)構(gòu)不對比如傳了字符串路徑或者 JSON 解析失敗。另一個隱蔽原因是容器高度為 0Echarts 初始化時讀不到尺寸給容器加個明確的height就能解決。4.2 部分區(qū)縣顯示為灰色現(xiàn)象地圖出來了但有幾個區(qū)縣是默認(rèn)灰色tooltip 也顯示不出數(shù)據(jù)。原因是數(shù)據(jù)里的名稱和 GeoJSON 的properties.name對不上。解決方法是把 GeoJSON 里所有name打印出來和業(yè)務(wù)數(shù)據(jù)的區(qū)縣字段做一次 diff差異項在數(shù)據(jù)側(cè)修正。重慶有幾個容易出問題的名稱“兩江新區(qū)”不是標(biāo)準(zhǔn)行政區(qū)劃“萬盛經(jīng)開區(qū)”在部分?jǐn)?shù)據(jù)源里歸在綦江這些要在需求階段就和業(yè)務(wù)方確認(rèn)歸屬。4.3 地圖變形或位置偏移現(xiàn)象地圖形狀看起來被壓扁或者拉伸。原因是 Echarts 默認(rèn)按容器寬高比渲染如果容器寬高比和地圖實際比例差太多就會變形。解決方法是給geo或series-map設(shè)置aspectScale默認(rèn) 0.75重慶的地圖可以調(diào)到 0.85 左右更接近真實比例。另外如果數(shù)據(jù)源坐標(biāo)系不統(tǒng)一比如部分要素是 GCJ02 部分是 WGS84會出現(xiàn)區(qū)域錯位這種只能回到數(shù)據(jù)源層面統(tǒng)一。4.4 大數(shù)據(jù)量下渲染卡頓現(xiàn)象區(qū)縣數(shù)量多或者疊加了散點、飛線后縮放拖拽明顯掉幀。原因是roam開啟后每次交互都觸發(fā)重繪加上label.show全開渲染壓力大。優(yōu)化手段關(guān)閉非必要的label用emphasis按需顯示散點用large: true開啟大數(shù)據(jù)模式如果還卡把地圖渲染成靜態(tài)圖片作為底圖交互層用 canvas 單獨畫。重慶主城區(qū)縣面積小、數(shù)量密標(biāo)簽重疊嚴(yán)重建議默認(rèn)不顯示標(biāo)簽。4.5 下鉆后返回上一級數(shù)據(jù)錯亂現(xiàn)象下鉆到區(qū)縣再返回地圖數(shù)據(jù)沒變或者顏色映射錯亂。原因是setOption默認(rèn)是合并模式上一次的visualMap和series配置殘留。解決方法是切換地圖時用chart.setOption(option, true)強制不合并或者每次切換前chart.clear()。但clear會清掉事件監(jiān)聽需要重新綁定所以更推薦notMerge參數(shù)。另外下鉆時visualMap的min/max要按新地圖的數(shù)據(jù)范圍重新計算否則顏色全擠在一檔。5. 進(jìn)階技巧讓重慶地圖在大屏里真正好用前面四章把從數(shù)據(jù)到渲染的主鏈路走通了這一章講幾個讓地圖從“能用”到“好用”的技巧。第一個是坐標(biāo)系的統(tǒng)一。如果地圖上要疊加散點表示門店位置散點的經(jīng)緯度必須和 GeoJSON 同一坐標(biāo)系。公開數(shù)據(jù)源多為 WGS84而業(yè)務(wù)系統(tǒng)里的地址解析結(jié)果可能是 GCJ02兩者直接混用會有幾百米偏移在區(qū)縣級別看不出來放大到街道就露餡。常見做法是統(tǒng)一轉(zhuǎn)成 GCJ02轉(zhuǎn)換算法網(wǎng)上有成熟實現(xiàn)但要注意轉(zhuǎn)換是非線性的不能簡單加偏移量。第二個是性能兜底。大屏通常 7x24 運行內(nèi)存泄漏是隱形殺手。每次下鉆registerMap都會新增一份地圖數(shù)據(jù)長時間運行內(nèi)存會漲。我的習(xí)慣是限制下鉆層級為兩級并且在返回時手動把不再使用的地圖數(shù)據(jù)置空雖然 Echarts 內(nèi)部還有引用但至少業(yè)務(wù)側(cè)不再持有。另外setInterval輪詢數(shù)據(jù)更新時如果地圖沒變化只更新series.data不要整個setOption重設(shè)前者只觸發(fā)數(shù)據(jù)重繪后者會重建整個圖表。第三個是視覺編碼的克制。很多大屏喜歡在地圖上堆特效飛線、漣漪、光暈全上結(jié)果業(yè)務(wù)方根本看不清哪個區(qū)縣指標(biāo)高。我的經(jīng)驗是顏色映射只用一個visualMap不要同時用itemStyle.areaColor和visualMap兩套顏色邏輯會互相覆蓋標(biāo)簽只在 hover 時顯示飛線數(shù)量控制在 10 條以內(nèi)多了就是一團(tuán)亂麻。重慶的地形是山地主城和區(qū)縣的數(shù)據(jù)分布往往極不均衡用分段型visualMap比連續(xù)型更能突出差異。最后一個技巧關(guān)于數(shù)據(jù)更新。如果地圖數(shù)據(jù)是實時刷新的series.data的更新頻率建議控制在 5 秒以上太快人眼也反應(yīng)不過來還增加渲染壓力。更新時用chart.setOption({ series: [{ data: newData }] })這種局部更新Echarts 會做 diff比全量setOption快很多。驗證更新是否生效可以在setOption后讀chart.getOption().series[0].data對比比肉眼看顏色靠譜。這套方案我在幾個大屏項目里反復(fù)用過最大的教訓(xùn)是地圖可視化的難點從來不在 Echarts 配置而在數(shù)據(jù)準(zhǔn)備和名稱對齊。前期花半小時把 GeoJSON 的name字段和業(yè)務(wù)數(shù)據(jù)的區(qū)縣字段做一次完整 diff能省掉后面幾小時的調(diào)試。希望幫到你。本文還有配套的精品資源點擊獲取