资讯详情

资讯详情

ECharts地图如何隐藏南海诸岛?GeoJSON数据过滤与配置方案全解析

做数据可视化大屏的同行尤其是常跟 ECharts 地图打交道的应该都碰到过这个场景中国地图注册好了数据也 setOption 上去了整体效果不错偏偏右下角吊着一块小区域上面标着“南海诸岛”四个字。业务方过来看了一眼直接问“这块能去掉吗跟咱们的数据没关系摆那儿还占地方”。第一次遇到这需求我翻了半天 ECharts 官方配置心想是不是有个 show: false 之类的开关结果发现根本没有。后来把 china.json 拉下来逐条看了数据结构才彻底搞明白问题出在哪儿。这篇文章就把这个问题彻底拆开从 GeoJSON 的数据结构说起给出几种能落地的隐藏方案再把隐藏之后地图漂移、缩放比例异常、tooltip 残留这几个连带问题一起解决掉。适合正在做地图可视化、大屏项目或者单纯被 ECharts 地图细节坑过的人阅读。1. 为什么右下角会跟着一个“南海诸岛”1.1 GeoJSON 里其实藏了一个独立区域ECharts 本身不内置中国地图数据需要开发者通过echarts.registerMap(china, geoJson)手动注册。而常见的 china.json 里features数组并不只是 34 个省级行政区很多版本的数据里额外塞了一些特殊区域其中就包括一个name为“南海诸岛”的 feature。可以把这个 GeoJSON 想象成一本画册每一页是一个区域封面上写着“中国地图”但翻到最后一页里面夹了一张独立的小插图。ECharts 拿到画册后做的事情很简单——逐页去画所有features数组里的对象都会渲染到 Canvas 上。它不会判断这张插图要不要放在主图右下角也不关心这个区域有没有跟主区域连在一起反正画就完了。为什么这个 feature 偏偏出现在右下角因为南海诸岛的经纬度范围落在我国南部和东南部海域换算到平面坐标后正好落在地图整体边界框的右下区域。所以它并不是“被放在右下角”而是它本身的地理坐标决定了它只能出现在那里。1.2 ECharts 渲染逻辑有多少 feature 画多少注册完地图后无论你用的是geo组件还是series-map系列ECharts 都会遍历 GeoJSON 的features数组每个 feature 就是一个可绘制、可绑定数据、可响应事件的区域。区域的样式、名称、数据映射全都围绕 feature 展开。弄明白这条逻辑就清楚为什么官方配置项里没有“隐藏南海诸岛”这个按钮了——ECharts 压根没有针对单个 feature 提供显隐开关。如果只是在series.data里没给“南海诸岛”传数据地图默认还是会画出区域轮廓只是没有数据值时显示成空白色块。所以想让右下角彻底消失要么从数据层面把这块区域摘掉要么用regions配置强行把它的视觉样式清掉没有第三条捷径。这里还要提醒一句不同来源的 china.json这个区域的名字不一定都一样。有些叫“南海诸岛”有些叫“中国南海诸岛”还有些版本把九段线单独拆成了一个 feature。排查的时候要打开 JSON 看一眼实际的name值别想当然。2. 方案一数据层过滤从根上解决2.1 用 Node 脚本预处理地图 JSON我最推荐的做法是在项目构建之前就把地图数据处理好把这份“有问题”的 feature 从 GeoJSON 里过滤掉生成一份干净的地图数据文件。这样前端代码里不需要写任何过滤逻辑运行时性能也完全不受影响。先看一个最基础的 Node 脚本const fs require(fs); // 读取原始地图 JSON const chinaGeo JSON.parse( fs.readFileSync(./china.json, utf-8) ); // 过滤掉名称为“南海诸岛”的 feature chinaGeo.features chinaGeo.features.filter( (feature) feature.properties.name ! 南海诸岛 ); // 输出新的地图 JSON fs.writeFileSync( ./china-clean.json, JSON.stringify(chinaGeo) ); console.log(处理完成剩余 feature 数, chinaGeo.features.length);这段代码里最关键的就是filter这一行。很多朋友图省事会写成includes(南海)结果把名字里带“南海”的其他区域也误删了。我自己就吃过一次亏。所以这里建议用精确匹配或者至少做成一个名单数组把各种来源的命名变体都放进去const hiddenNames [南海诸岛, 中国南海诸岛, 十段线, 九段线]; chinaGeo.features chinaGeo.features.filter( (feature) !hiddenNames.includes(feature.properties.name) );脚本跑完之后生成一个china-clean.json前端直接引用这个文件一步到位。2.2 在页面加载时动态过滤如果地图数据不是本地静态文件而是从接口或 CDN 动态拉取的不方便提前预处理那就在前端拿到数据之后、注册地图之前做一次过滤效果一样。fetch(./china.json) .then((res) res.json()) .then((geoJson) { // 在 registerMap 之前过滤数据 const filteredGeo { ...geoJson, features: geoJson.features.filter( (item) item.properties.name ! 南海诸岛 ) }; echarts.registerMap(china, filteredGeo); renderMap(); });这种做法灵活数据源随时可以换不用维护构建脚本。但要注意别在registerMap已经执行之后再改features数组那样 ECharts 内部已经存下了引用改了不一定生效。务必在注册之前完成过滤。2.3 过滤后必须重设的地图参数数据过滤只是第一步。真正隐蔽的坑在这里原始 china.json 里因为有南海诸岛整个地图的边界范围bounding box会被向右下角撑大。一旦去掉这块区域包围盒明显变小ECharts 默认会把地图重新适配到整个容器里——表现就是地图突然变大或者整体位置偏移甚至比例看起来不对劲。解决方法是给geo或series-map显式指定layoutCenter和layoutSize把地图主体固定在你想要的位置和大小。我常用的配置如下const option { geo: { map: china, roam: true, // 以容器中心为基准 layoutCenter: [50%, 50%], // 控制地图占容器的比例 layoutSize: 90%, itemStyle: { areaColor: #11204d, borderColor: #3d7eff }, emphasis: { itemStyle: { areaColor: #2a5fd0 } } } };layoutSize的值我一般从 100% 往低调观察地图在屏幕上的大小调到合适为止。如果是省级地图或者只需要展示部分区域可以改用center和zoom来控制视角。注意如果同时设置了layoutCenter和centerECharts 的坐标系会以layoutCenter为准。两者不要混用否则定位会出现诡异偏差。3. 方案二regions 配置隐藏适合临时应急3.1 geo 组件和 series-map 的 regions 写法如果不想改动地图数据ECharts 也提供了一种视觉层面的隐藏手段通过regions配置单独控制某个区域的样式。这个办法适合临时应急比如图表已经上线了不改数据文件只在前端配置里快速处理一下。geo组件里的写法const option { geo: { map: china, roam: true, regions: [ { name: 南海诸岛, itemStyle: { areaColor: transparent, borderColor: transparent }, label: { show: false }, emphasis: { itemStyle: { areaColor: transparent, borderColor: transparent }, label: { show: false } } } ] } };series-map里的写法几乎一样只是regions直接放在 series 里const option { series: [ { type: map, map: china, regions: [ { name: 南海诸岛, itemStyle: { areaColor: transparent, borderColor: transparent } } ] } ] };这套配置的核心思路是区域还在数据里但所有可见的样式都变成透明包括悬浮高亮的状态也一并处理掉。这样视觉上就看不到右下角那块了。3.2 视觉隐藏方案的两个典型坑这个方案看着方便实际用的时候有两个坑我必须说在前面。第一个坑areaColor透明了但区域还是实实在在存在于画布上。如果用户的鼠标刚好移到右下角那片空白区域ECharts 仍然会触发鼠标事件某些版本里还会出现高亮色的区域轮廓。要杜绝这种情况需要把emphasis状态也覆盖成透明并且最好在geo上把silent设为true。但silent会影响所有区域的事件响应如果其他省份还需要点击、悬浮效果这个方法就不合适了。第二个坑数据层面的占位问题没有解决。比如地图要绑定series.data数据里虽然没给南海诸岛传值但区域的边界仍然占用了包围盒。这就意味着 map 的缩放级别、中心点计算仍然会把这块区域算进去最终地图整体显示范围还是会被它影响。说到底这只是把颜色变透明并没有真正“去掉”这块区域。所以我对这个方案的态度很明确只适合调试、只适合临时看效果不建议放进生产环境长期跑。4. 方案三换一份不含该区域的地图数据4.1 按需拼接省市边界数据除了在原有数据上做减法和视觉隐藏还有一种思路是直接从源头上换数据。国内不少公开地图数据源会按省份分别提供 GeoJSON这种拆分后的数据本身就不存在“南海诸岛”这个独立 feature。常见做法是循环请求各省份的边界 GeoJSON然后把所有features拼成一个数组再注册成一张地图。代码大致长这样const adcodeList [ 110000, 120000, 130000 // 这里放所有需要的省级行政区划代码 ]; const features await Promise.all( adcodeList.map((code) fetch(https://geo.datav.aliyun.com/areas_v3/bound/${code}.json) .then((res) res.json()) .then((json) json.features[0]) ) ); const mergedGeo { type: FeatureCollection, features }; echarts.registerMap(china, mergedGeo);这种方式的优点是特别干净不需要额外过滤而且拼接哪个省、不放哪个省完全由你自己控制。缺点也明显请求次数多前端要处理异步加载顺序数据总大小比单份 JSON 更大。如果你的页面走的是 HTTP/2并且有 loading 状态兜底体验上问题不大但如果是纯静态页面、没有任何加载态要慎重。4.2 数据源与量级的取舍建议选择地图数据源时要考虑项目本身的定位。如果只做省级地图展示按需拼接是最灵活的如果要做全国下钻到市级的完整地图一次性加载全国 GeoJSON 仍然是更高效的选择这时候数据过滤方案就更合适。还有一种常见组合基础底图用一份不带南海诸岛的全国简化数据下钻到省的时候再单独请求省市级数据。下钻数据都是从官方或公开渠道拉的不存在右下角区域问题。这样既避免了初始加载时出现多余区域也不影响地图交互的下钻体验。我做过一个需要下钻到区县的项目就是用这个思路落地的整体很顺。5. 隐藏之后的地图优化与常见问题排查5.1 地图整体漂移、缩放比例不对怎么办这是隐藏“南海诸岛”之后最常遇到的问题。原因前面已经讲过原始地图的包围盒因为包含南海区域范围很大。去掉之后ECharts 默认重新按新包围盒适配画布导致地图视觉上变大、偏移。处理方式有两个思路。一个是用layoutCenter和layoutSize固定地图的位置和大小这也是大屏项目里最常用的方式。另一个思路是手动指定center和zoom比如const option { geo: { map: china, center: [104.0, 35.0], zoom: 1.2 } };center是一个经纬度数组zoom是缩放级别。这种方式适合你知道地图要聚焦在什么位置的情况但它跟用户的roam操作会有一些交互上的纠缠需要自己调几次才能找准手感。相比之下layoutCenter配合layoutSize对大屏来说更可控百分比定位也更适配不同分辨率的屏幕。5.2 过滤后地图仍显示旧区域多半是缓存遇到过一种很迷惑的现象代码里明明已经过滤掉了南海诸岛刷新页面后地图右下角还是有那块区域。排查半天最后发现是浏览器缓存了旧的china.json。尤其是本地开发用的静态服务器或者 CDN默认缓存策略比较宽松地图数据这种不常变的文件很容易被缓存住。解决思路很直接开发阶段在请求地址后面拼一个随机参数比如china.json?_tDate.now()生产环境通过构建工具给文件名加 hash。另外还有一个容易忽略的点就是registerMap多次注册同名的地图。如果项目里通过路由切换页面每次进入都重新执行registerMap后一次注册会把前一次的覆盖掉但图表实例可能还保留旧的地图数据。这时候需要chart.clear()或者chart.dispose()之后重新初始化。5.3 过滤时误删了其他区域怎么办过滤逻辑如果写得粗糙很容易误伤。最常见的是用includes(南海)去匹配结果把所有名称带“南海”的 feature 全部删掉比如某个版本数据里把“南海诸岛”和“十段线”做成了两个 feature甚至还有仅包含边界线段的独立 feature。这些名称里都带“南海”或“诸岛”全删了倒是省事但可能会把边界线也删掉导致地图外轮廓出现缺口。更稳妥的做法是维护一个明确的名单同时对 feature 的geometry.type做一次判断。Polygon和MultiPolygon是常规的区域可以过滤LineString或MultiLineString是边界线段是否要删取决于业务需求。如果只想隐藏填充区域但保留右下角那几段边界线可以让线段类 feature 继续保留如果是想完全去掉右下角的视觉元素就把它们一并过滤。这个判断逻辑写在前面给的 Node 脚本里只需要扩展几行。const shouldRemove (name, geometryType) { if (geometryType LineString || geometryType MultiLineString) { // 如果连边界线也想隐藏取消下面一行的注释 return true; // return false; // 保留边界线的情况 } return [南海诸岛, 中国南海诸岛].includes(name); }; chinaGeo.features chinaGeo.features.filter((feature) { const name feature.properties.name; const geometryType feature.geometry.type; return !shouldRemove(name, geometryType); });5.4 tooltip、高亮状态与数据联动问题用regions做视觉隐藏时右下角空白区域还可能出现 tooltip 残留鼠标移过去会显示一块空白浮层。这种情况只能靠把emphasis样式也设置透明同时针对那个区域把tooltip的formatter判断掉。但说实话这属于补丁式写法补一个漏一个还是回到数据过滤才是最稳的。如果用了数据过滤方案还需要注意数据联动的问题。比如series-map的data数组里如果还有name: 南海诸岛的数据项而地图里已经没有这个 feature 了控制台会报警告图表顶多忽略掉这个数据项不影响整体渲染。但如果你的数据是从后端动态生成、并且按照地图所有区域自动填充的那就要在后端或者前端统一过滤掉这个 name避免多余数据项。我自己现在的习惯是在后端生成地图统计数据时就对“南海诸岛”这个 key 做排除前端拿到的就是干净的数据。这样不止 ECharts 地图包括其他可视化组件、表格展示都不会再出现这个多余区域一套逻辑多处复用。最后再分享一个小技巧不管用哪个数据源、哪个方案我建议在地图数据处理的脚本里统一维护一份“隐藏名单”和“过滤规则”而不是每次临时写一句! 南海诸岛。因为地图数据源不应该在业务代码里散落得到处都是一旦以后换了数据源或者又要过滤澳门、香港等特殊区域直接改名单就行不用翻遍整个项目找过滤逻辑。地图这种基础组件维护成本低一点比什么花活都重要。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →