據(jù)包實戰(zhàn):從加載到坐標轉(zhuǎn)換的完整指南)
簡介包含世界各國地理邊界與屬性數(shù)據(jù)的GeoJSON資源包面向GIS開發(fā)者、前端可視化工程師及地理信息學習者適用于交互式地圖展示、空間數(shù)據(jù)分析和GIS教學。資源共2000個文件以json/geojson格式為主壓縮包僅2.1MB體積小巧便于分發(fā)與加載。已有4731人學習下載是社區(qū)認可度較高的地理數(shù)據(jù)集合。數(shù)據(jù)遵循WGS84坐標系每個文件對應一個國家或地區(qū)的多邊形邊界并攜帶國家名稱、首都等屬性字段可直接在Leaflet、Mapbox GL JS、OpenLayers等地圖庫中渲染也可用于面積計算、區(qū)域查詢、地理編碼等多種開發(fā)任務。內(nèi)容包含完整的世界國家邊界數(shù)據(jù)目錄清晰覆蓋全球各主要國家與地區(qū)無論是快速構建世界地圖看板還是開展區(qū)域統(tǒng)計分析這套數(shù)據(jù)都能直接復用是GIS入門與地圖開發(fā)的實用資源也是教學與科研中常用的基礎數(shù)據(jù)集。1. 世界國家GeoJSON數(shù)據(jù)包離線地圖背后的“邊界檔案”做可視化的時候世界地圖永遠是最先想要的那張底圖但真正動手才發(fā)現(xiàn)GeoJSON 數(shù)據(jù)不是隨手能拿到的。在線地圖服務要配 token某些平臺請求頻率卡得死死的更別提內(nèi)網(wǎng)環(huán)境根本沒有外網(wǎng)請求通道。于是這份“世界地圖、世界國家 geojson、各國地圖 json 數(shù)據(jù)”的下載資源就成了剛需——一份包含全球各國邊界的 GeoJSON 集合解壓之后即可離線加載適合 ECharts、Leaflet、D3、Power BI 以及各類數(shù)據(jù)大屏的后端渲染場景。它的核心價值在于“可復用”和“不依賴外部請求”有了它世界地圖就不再是黑匣子邊界、國名、區(qū)域編碼都攤開在你眼前。適合正在做地理可視化、需要按國家聚合展示指標的開發(fā)者和數(shù)據(jù)分析師也適合學生用來做地圖相關的課程設計。2. 解析這份世界地圖數(shù)據(jù)目錄結(jié)構、坐標系與真實用途2.1 數(shù)據(jù)從哪來一眼認出“Natural Earth 系”特征拿到任何一份世界國家 GeoJSON 數(shù)據(jù)包第一件事不是解壓而是先判斷數(shù)據(jù)源頭。市面上流傳的“世界地圖 json 數(shù)據(jù)”絕大多數(shù)都脫胎于 Natural Earth 的 Admin 0 國家邊界數(shù)據(jù)集也就是“國家的行政 0 級邊界”。這份資源大概率也是這個體系每個國家一個 Polygon 或 MultiPolygonproperties 里帶著國名、ISO 代碼、大洲、人口估算值等字段。怎么判斷確實是 Natural Earth 系看 properties 字段。典型的特征是包含NAME、NAME_EN、CONTINENT、ISO_A3、POP_EST這類鍵名。ISO_A3是三字母國家代碼比如CHN、USA、BRA這是做數(shù)據(jù)匹配的黃金字段比中文名和英文名都穩(wěn)定。CONTINENT直接標了大洲歸屬后續(xù)做“按大洲聚合”或者“過濾出亞洲國家”的操作時這個字段比自己去數(shù)國家省事得多。還有一個隱藏特征Natural Earth 數(shù)據(jù)集的格陵蘭被劃在北美洲名下法屬圭亞那被劃在南美洲名下這與大多數(shù)人腦中的地理分區(qū)不完全一樣。后面做標注、做篩選時如果發(fā)現(xiàn)某些“奇怪”歸屬大概率不是資源錯了而是數(shù)據(jù)源本來就按這個標準劃分。2.2 解壓后先做什么核對目錄結(jié)構與字段清單我一般會把 GeoJSON 下載包解壓后先做一個“摸底三步”避免用到一半才發(fā)現(xiàn)字段對不上。第一步看文件目錄。常見的組織方式是三種一份全世界的單文件例如world.geojson按大洲拆分asia.geojson、europe.geojson這樣的命名以及按單個國家拆分china.geojson、japan.geojson。如果三種都有這份資源就算比較完整了。按大洲拆的文件適合做區(qū)域大屏單獨一個國家的文件適合做國家下鉆。第二步檢查 JSON 合法性。GeoJSON 本質(zhì)就是 JSON用 jq 或者 Python 都能快速做語法體檢。python -c import json; datajson.load(open(world.geojson,r,encodingutf-8)); print(len(data[features]), data[features][0][properties])這條命令會輸出兩樣東西要素總數(shù)和第一個要素的 properties。要素總數(shù)一般在 170 到 250 之間取決于把爭議地區(qū)、海外領地算作獨立要素還是合并進主國如果數(shù)字遠小于 100說明這份數(shù)據(jù)可能被過度簡化了不適合做精細的可視化。properties 的輸出用來確認字段名后面寫匹配邏輯全靠它。第三步確認坐標系。多數(shù)世界地圖 GeoJSON 都是 WGS-84 經(jīng)緯度坐標EPSG:4326但有些“好心”的處理者會把數(shù)據(jù)轉(zhuǎn)成墨卡托平面坐標導致前端加載出來完全是亂的。判斷方法很簡單看特征坐標絕對值如果經(jīng)度在 -180 到 180 之間、緯度在 -90 到 90 之間就是經(jīng)緯度如果坐標變成了幾百萬的數(shù)字那是投影坐標需要做轉(zhuǎn)換。2.3 什么場景真正需要這份JSON什么場景可以放棄這份 GeoJSON 數(shù)據(jù)包最適合的場景是“自己掌握渲染全鏈路”用原生 ECharts 或 Leaflet 實現(xiàn)的世界地圖按國家做數(shù)據(jù)下鉆、按大洲做聚合展示、或者是純離線環(huán)境下的大屏項目。它把地圖的“邊界數(shù)據(jù)”這個環(huán)節(jié)徹底本地化所有樣式都可以自己控制不再依賴第三方平臺是否開放地圖服務。反過來有些場景不建議用這份資源。比如只是要一個背景底圖不在乎國家邊界精確度那直接引用在線地圖服務既省流量又省心。再比如你的展示層級是“中國省級”那應該找中國省級 GeoJSON 而不是這份世界數(shù)據(jù)世界數(shù)據(jù)里省級邊界通常不包含硬拆只會得到粗糙結(jié)果。判斷標準就一條你需要的是“國家級別”的粒度還是“國家以下”的粒度。這份資源的邊界就到國家粒度再細就不夠了。3. 把GeoJSON喂給地圖組件ECharts與Pyecharts實戰(zhàn)3.1 ECharts 加載離線世界地圖從注冊地圖到數(shù)據(jù)對齊ECharts 在 4.x 時代還內(nèi)置著世界地圖5.x 開始就把地圖數(shù)據(jù)移除了要求開發(fā)者自己注冊。這一步反而讓本地 GeoJSON 數(shù)據(jù)的價值凸顯出來。用 ECharts 加載這份數(shù)據(jù)核心動作就是registerMap。fetch(world.geojson) .then(res res.json()) .then(worldJson { echarts.registerMap(world, worldJson); const chart echarts.init(document.getElementById(map)); chart.setOption({ series: [{ type: map, map: world, roam: true, label: { show: false }, data: [ { name: China, value: 140 }, { name: United States of America, value: 120 } ] }] }); });這里的registerMap(world, worldJson)是注冊動作第一個參數(shù)world是自定義的地圖名后面series里map: world必須和注冊名保持一致。data數(shù)組里的name字段是關鍵它要與 GeoJSON 中properties.NAME_EN完全一致差一個空格都匹配不上。上面例子里China和United States of America就是典型的標準寫法如果你的數(shù)據(jù)源字段帶縮寫或別名要先做一次字段清洗。如果 JSON 文件較大超過 10MB不建議用fetch一次性拉取而是把數(shù)據(jù)放到本地 JS 文件里用script srcworld.js/script的方式同步加載再把解析后的 JSON 傳給registerMap。這是地圖應用最常見的前端優(yōu)化手段。3.2 Pyecharts 自定義地圖繞開默認圖集Python 生態(tài)里做地圖圖表Pyecharts 是高頻選擇。但它自帶的世界地圖底圖和國內(nèi)常用的業(yè)務數(shù)據(jù)字段往往匹配不順暢而且內(nèi)置圖集更新緩慢。自己下載了 GeoJSON 之后用法是先把數(shù)據(jù)轉(zhuǎn)換成 Pyecharts 可識別的形式。import json from pyecharts import options as opts from pyecharts.charts import Map with open(world.geojson, r, encodingutf-8) as f: geo_data json.load(f) region_data [ {name: China, value: 140}, {name: United States, value: 120}, {name: Brazil, value: 90}, ] c Map() c.add(世界指標, region_data, maptypeworld, is_roamTrue) c.set_global_opts( title_optsopts.TitleOpts(title各國數(shù)據(jù)分布), visualmap_optsopts.VisualMapOpts(max_150), ) c.render(world_map.html)這里maptypeworld用的是 Pyecharts 默認注冊的世界地圖數(shù)據(jù)。如果你要改成自己下載的 GeoJSONPyecharts 提供的辦法是使用Map項的maptype配合自定義地圖注冊表或者干脆把 GeoJSON 轉(zhuǎn)成JS語言文件后手動加載。常見做法是找echarts-map-js這類封裝庫把world.geojson轉(zhuǎn)成world.js放入項目再由 Pyecharts 模板加載。我一般會建議如果你的 Pyecharts 環(huán)境版本較老0.5.x直接改maptype不如升級到新版用瀏覽器端registerMap更省事。3.3 讓業(yè)務字段與國名精確對上name匹配的硬規(guī)則地圖渲染后最陰間的現(xiàn)象是圖表出來了顏色塊也有但提示框里的國家名是英文業(yè)務數(shù)據(jù)里存的是中文名或者數(shù)據(jù)里寫的US和 GeoJSON 里的United States對不上。這個問題的根源是 data 數(shù)組的name必須嚴格與 GeoJSON properties 中某個字段等價ECharts 內(nèi)部的匹配邏輯是字符串全等。解決方式有兩種。一種是把業(yè)務數(shù)據(jù)統(tǒng)一改成 GeoJSON 的NAME_EN值這樣最直接但要求業(yè)務側(cè)配合。另一種是在傳數(shù)據(jù)前做一次映射常見做法是維護一張 ISO 代碼映射表用ISO_A3作為中間鍵。iso_to_name_en { CHN: China, USA: United States of America, BRA: Brazil, } def convert_business_data(raw_records): converted [] for rec in raw_records: iso3 rec.get(country_code) name_en iso_to_name_en.get(iso3) if name_en is None: print(f[WARN] unknown ISO code: {iso3}, skip) continue converted.append({name: name_en, value: rec.get(value)}) return converted這段腳本的核心思路是業(yè)務數(shù)據(jù)里不管存的是CN、中國還是CHN統(tǒng)一在進入圖表前轉(zhuǎn)成NAME_EN。輸出日志里如果出現(xiàn)unknown ISO code就要回頭檢查業(yè)務數(shù)據(jù)是不是真的有這個國家還是國家代碼用了舊標準。匹配邏輯做成獨立函數(shù)還有個好處后續(xù)數(shù)據(jù)源變化只需要維護映射表不用改渲染代碼。4. 坐標、格式與文件瘦身讓GeoJSON在前端不翻車4.1 經(jīng)緯度順序與GCJ-02“點位跑到海里”的兩大根源GeoJSON 的坐標數(shù)組順序是[經(jīng)度, 緯度]也就是[lon, lat]。這個順序和很多人的習慣相反日常說話都是“北緯 39.9、東經(jīng) 116.4”但寫進 GeoJSON 時必須反過來。一個典型的錯誤是把[39.9, 116.4]當成坐標結(jié)果中國地圖上的標注點直接瞬移到北冰洋附近。另一個大坑是坐標系混用。國內(nèi)在線地圖高德、騰訊、百度用的坐標系是 GCJ-02 甚至 BD-09與 GeoJSON 數(shù)據(jù)集的 WGS-84 存在偏移。如果把來自國內(nèi)地圖服務的標注點坐標直接用到世界地圖 GeoJSON 上點位會有幾百米到幾公里的漂移視覺上就是“點在國界外面”。import math def gcj02_to_wgs84(lng, lat): a 6378245.0 ee 0.006693421622965943 dlat _transform_lat(lng - 105.0, lat - 35.0) dlng _transform_lng(lng - 105.0, lat - 35.0) radlat lat / 180.0 * math.pi magic math.sin(radlat) magic 1 - ee * magic * magic sqrtmagic math.sqrt(magic) dlat (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * math.pi) dlng (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * math.pi) return lng dlng, lat dlat這是 GCJ-02 轉(zhuǎn) WGS-84 的通用算法核心參數(shù)ee是 WGS-84 橢球偏心率平方a是長半軸。如果你的業(yè)務點位來自國內(nèi)地圖服務統(tǒng)一在寫入圖表前做一次轉(zhuǎn)換。反過來也一樣把 GeoJSON 里的邊界坐標直接喂給高德地圖 JS API 時要轉(zhuǎn)換成 GCJ-02。我建議在項目里封裝一個坐標轉(zhuǎn)換模塊所有點位進出地圖組件都走這一個函數(shù)別在業(yè)務代碼里到處內(nèi)聯(lián)轉(zhuǎn)換邏輯。4.2 TopoJSON轉(zhuǎn)GeoJSON把壓縮格式變回通用格式有些下載包里提供的是.topojson后綴的文件大小往往只有 GeoJSON 的一半甚至更少因為它把相鄰多邊形的共享邊界存成“弧段”大幅去重。問題是 ECharts、D3 等通用渲染庫不直接吃 TopoJSON需要先轉(zhuǎn)回 GeoJSON。import topojson import json with open(world.topojson, r, encodingutf-8) as f: topology json.load(f) geo_data topojson.Topology(topology).to_geojson() with open(world_from_topojson.geojson, w, encodingutf-8) as f: json.dump(geo_data, f, ensure_asciiFalse)這段腳本把 TopoJSON 對象轉(zhuǎn)換為完整的 GeoJSON 結(jié)構。值得說的是topojson這個 Python 庫與 Node 端的topojson-client行為一致都會把arcs解算為坐標序列輸出MultiPolygon。如果你的環(huán)境沒有 Python也可以用 Node 命令npx topojson-client world.topojson -o world.geojson效果相同。注意轉(zhuǎn)換后的 GeoJSON 坐標精度通常是原始精度文件體積會變大很多。所以“TopoJSON 轉(zhuǎn) GeoJSON”之后最好接一個簡化步驟把邊界精度壓到可視化可接受的級別。4.3 簡化邊界降精度、降要素保住肉眼形狀世界地圖文件動輒三五十兆瀏覽器加載后交互掉幀是常事。核心優(yōu)化思路有兩個降坐標精度、減少要素數(shù)量。降精度最直接的辦法是用mapshaper它是在線免費的也支持命令行本地處理。mapshaper world.geojson -simplify 10% keep-shapes -o formatgeojson world_simplified.geojson-simplify 10%表示保留原始點數(shù)的 10%從幾何層面大幅減少坐標密度keep-shapes是保形參數(shù)讓簡化后的小島、海峽不消失。這條命令執(zhí)行完后文件體積通常能降到原來的 30% 以下。如果不想引入 Node 環(huán)境Python 里可以用shapely的simplify方法做類似操作但拓撲關系保持不如 mapshaper 穩(wěn)。我一般會把簡化分兩級低精度版本用于世界總覽高精度版本用于單個國家下鉆兩個文件分開存。這樣既保住了大屏第一眼的效果又不至于因為一個南太平洋小島拖慢整張地圖。4.4 用腳本做體檢壞幾何與重復國名一出生就攔下數(shù)據(jù)包拿到手先跑一遍體檢腳本能規(guī)避后續(xù)一半以上的渲染異常。體檢的核心檢查項有三個幾何合法性、要素是否重復、properties 字段是否完整。from shapely.geometry import shape import json with open(world.geojson, r, encodingutf-8) as f: data json.load(f) problem_list [] for idx, feat in enumerate(data[features]): geom shape(feat[geometry]) props feat[properties] if not geom.is_valid: problem_list.append((idx, props.get(NAME_EN), invalid geometry)) if ISO_A3 not in props or NAME_EN not in props: problem_list.append((idx, props.get(NAME_EN), missing fields)) seen set() for idx, feat in enumerate(data[features]): name feat[properties].get(NAME_EN, ) if name in seen: problem_list.append((idx, name, duplicate name)) seen.add(name) print(ftotal features: {len(data[features])}) print(fproblems: {len(problem_list)}) for p in problem_list: print(p)geom.is_valid是 shapely 自帶的拓撲校驗專門檢查多邊形自相交、環(huán)斷裂等問題。duplicate name的檢查尤其重要因為很多數(shù)據(jù)包把“法國本土”“法屬圭亞那”“留尼汪”等海外領地拆成多個要素但它們共享一個NAME_EN如果不處理渲染時會出現(xiàn)一個國家名對應多個多邊形提示框和點擊事件都會打架。體檢結(jié)果出來后根據(jù)具體需求決定是合并海外領地還是保留原樣。5. 使用世界地圖GeoJSON的五大坑現(xiàn)象、原因與解法5.1 地圖加載出來卻是“白板”registerMap時機與異步陷阱現(xiàn)象數(shù)據(jù)配好了setOption 也執(zhí)行了頁面上一片空白控制臺不報錯。原因registerMap是異步的fetch還沒返回數(shù)據(jù)時setOption已經(jīng)執(zhí)行此時 ECharts 內(nèi)部沒有注冊到對應地圖名渲染自然為空。解決把setOption放進fetch.then回調(diào)里確保地圖注冊完成后再設置配置。如果使用同步script引入地圖 JS 文件也要確認變量全局可用后再 init 圖表。從那以后我每次寫地圖之前都會先確認“注冊完成”這個事件是否已經(jīng)發(fā)生。5.2 國家名對不上數(shù)據(jù)字段與業(yè)務字段錯位現(xiàn)象部分國家顯示出來了部分國家顏色塊不出現(xiàn)但提示框里可以看到地圖自帶的國家英文名。原因業(yè)務數(shù)據(jù)的name與 GeoJSON 的NAME_EN不一致常見差異包括“United States” vs “United States of America”、“Russia” vs “Russian Federation”以及拼寫中含連字符的差異。解決不要靠肉眼匹配統(tǒng)一用 ISO_A3 映射。寫一節(jié)里提到的映射函數(shù)保證所有業(yè)務數(shù)據(jù)在進入圖表前被轉(zhuǎn)換成合法地圖名。或者反過來把 GeoJSON 里所有的 NAME_EN 導出為清單業(yè)務側(cè)按清單改數(shù)據(jù)。優(yōu)先前面那個方案改動集中在代碼里可維護性更好。5.3 國土區(qū)域“多一塊”或“少一塊”鄰國界與海外領地的處理現(xiàn)象渲染后某個國家區(qū)域明顯多了一塊小島或者某個國家在地圖上缺了一個角。原因這份世界地圖 GeoJSON 存在多處海外領地與主國分離的情況例如法國的海外大區(qū)在地理上屬于其它大洲但在properties.CONTINENT里仍標著 Europe。造成“多一塊”通常是把海外領地也畫在當前國家色塊下造成“少一塊”則是數(shù)據(jù)本身把某些爭議區(qū)域歸給了鄰國。解決根據(jù)實際業(yè)務定義決定處理策略。如果做的是“國家主體一色塊”的宏觀大屏就把海外領地要素合并到主國去如果做的是“精確到地區(qū)”的數(shù)據(jù)掛接反而應該保留獨立要素。合并時可以用shapely.ops.unary_union把同一國名的多邊形幾何合并成一個整體。5.4 涉及本國區(qū)域的展示官方數(shù)據(jù)源優(yōu)先現(xiàn)象中國范圍內(nèi)的可視化用這份數(shù)據(jù)渲染后周邊邊界線條與常規(guī)認知存在差異審核或領導評審階段被提出質(zhì)疑。原因國外公開的全球數(shù)據(jù)集屬于“通用教學級別”其國界與中國官方審圖標準在部分區(qū)域存在差別直接用于面向公眾的產(chǎn)品就會出問題。解決只要可視化結(jié)果涉及中國區(qū)域的邊界展示就不要繼續(xù)使用這份數(shù)據(jù)。國內(nèi)的做法是優(yōu)先采用具備官方審圖號的數(shù)據(jù)源或者使用各大國內(nèi)地圖服務平臺提供的標準 GeoJSON/行政區(qū)劃接口這份世界地圖 GeoJSON 只用于全球視野展示把中國區(qū)域單獨替換成官方數(shù)據(jù)源再合成。以后凡是涉中國區(qū)域的工程項目我都會第一時間確認底圖出處和審核要求不帶僥幸心理。5.5 簡化和合并過頭要素拓撲斷裂引發(fā)渲染異?,F(xiàn)象用 mapshaper 簡化后原來正常的國家邊界在海峽、峽灣區(qū)域出現(xiàn)“鋸齒缺角”放大后還能看到多邊形錯位。原因-simplify比率設得太激進或者沒有加keep-shapes參數(shù)導致細長型多邊形被過度抽點拓撲斷裂。解決簡化比率控制在 10%30%且務必加keep-shapes。簡化后的文件要重新跑 4.4 的體檢腳本對比簡化前后要素數(shù)量和幾何合法性。如果某個國家的形狀嚴重變形就單獨把該國要素從原始數(shù)據(jù)中提取出來不參與全局簡化。6. 把多維數(shù)據(jù)釘在國土上點歸屬匹配與地圖校驗腳本有了世界地圖 GeoJSON 之后最常做的進階操作不是畫地圖而是“判斷一個點落在哪個國家”。比如你有十萬條交易記錄每條帶經(jīng)緯度想把它們聚合成“國家維度的指標”這時候不需要去各個國家查表直接用 GeoJSON 的幾何做空間判斷即可。from shapely.geometry import Point, shape import json with open(world.geojson, r, encodingutf-8) as f: data json.load(f) country_polys [] for feat in data[features]: props feat[properties] geom shape(feat[geometry]) country_polys.append((props.get(NAME_EN), props.get(ISO_A3), geom)) def locate_country(lng, lat): p Point(lng, lat) for name, iso3, geom in country_polys: if geom.contains(p): return name, iso3 return None, None sample_point (116.4, 39.9) print(locate_country(*sample_point))這個腳本把 GeoJSON 里的多邊形一次性加載進 shapely 的空間索引之后每個點的判斷都是幾何包含關系計算。十萬條記錄跑下來在普通筆記本上也就幾秒鐘。性能上要注意如果點數(shù)量上百萬建議用rtree或geopandas.sjoin做空間連接不要逐條循環(huán)。這類空間操作的另一個衍生用途是“數(shù)據(jù)有效性驗證”——把業(yè)務數(shù)據(jù)里的異常點位比如在中國的邊界數(shù)據(jù)里出現(xiàn)了一個落在海中央的坐標直接揪出來。從那以后我每次拿到地圖數(shù)據(jù)都會強制走一遍“體檢腳本 點歸屬驗證”的流程確認邊界數(shù)據(jù)可用、點位歸屬符合常識再進入正式開發(fā)流程。希望這份世界地圖 GeoJSON 數(shù)據(jù)的使用經(jīng)驗能幫你在可視化項目里少踩幾個坑。本文還有配套的精品資源點擊獲取