ECharts自定义地图加载与错误处理

10 次阅读

ECharts 的地图可视化并不局限于内置地图类型。项目中遇到行政区划、园区、校园、商圈、厂区等特殊区域时,通常需要加载 GeoJSON 数据并注册为自定义地图。真正容易出问题的地方,也往往不是图表配置本身,而是地图数据格式、名称匹配、资源路径、坐标体系以及异步加载流程。

掌握自定义地图的加载方式,同时建立一套针对常见报错的排查思路,可以明显降低地图项目的调试成本。

一、ECharts自定义地图的基本原理

ECharts 的地图组件本质上依赖地图数据。对于自定义区域,常见方案是准备一份 GeoJSON 文件,然后通过 JavaScript 获取数据,再调用 echarts.registerMap() 注册地图。

基本流程可以概括为:

GeoJSON地图文件
      ↓
异步加载地图数据
      ↓
JSON解析
      ↓
echarts.registerMap()
      ↓
series.type = 'map'
      ↓
地图渲染

例如准备 custom-map.json:

JSON
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "name": "区域A"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [120.1, 30.1],
            [120.2, 30.1],
            [120.2, 30.2],
            [120.1, 30.2],
            [120.1, 30.1]
          ]
        ]
      }
    }
  ]
}

然后加载并注册:

JavaScript
fetch('/maps/custom-map.json')
  .then(response => response.json())
  .then(geoJson => {
    echarts.registerMap('customMap', geoJson);

    const option = {
      series: [
        {
          type: 'map',
          map: 'customMap',
          data: [
            { name: '区域A', value: 100 }
          ]
        }
      ]
    };

    chart.setOption(option);
  });

这里的关键点是,registerMap() 使用的地图名称必须与 series.map 中的名称一致。

二、为什么推荐使用GeoJSON加载自定义地图

GeoJSON 是目前前端地图数据中非常常见的一种格式,结构清晰,也比较适合与 ECharts 配合。

一个标准的 GeoJSON 地图通常包含:

JSON
{
  "type": "FeatureCollection",
  "features": []
}

每个区域通常对应一个 Feature:

JSON
{
  "type": "Feature",
  "properties": {
    "name": "区域A"
  },
  "geometry": {
    "type": "Polygon",
    "coordinates": []
  }
}

其中:

  • properties.name 通常用于区域名称匹配;

  • geometry.type 可以是 Polygon 或 MultiPolygon;

  • coordinates 保存具体的地理边界;

  • FeatureCollection 保存多个区域。

对于行政区地图,一个地区可能包含多个不连续区域,此时使用 MultiPolygon 比单纯的 Polygon 更合适。

三、使用axios加载自定义地图

如果项目已经使用 Axios,也可以直接通过 Axios 获取 GeoJSON。

JavaScript
import axios from 'axios';

axios.get('/maps/custom-map.json')
  .then(response => {
    const geoJson = response.data;

    echarts.registerMap('customMap', geoJson);

    chart.setOption({
      series: [
        {
          type: 'map',
          map: 'customMap'
        }
      ]
    });
  })
  .catch(error => {
    console.error('地图加载失败:', error);
  });

异步请求方式最大的好处是地图文件可以独立维护,不需要把大量 GeoJSON 数据直接写进 JavaScript 文件。

对于较大的地图文件,这种方式也更利于缓存和资源管理。

四、地图加载成功但无法显示的常见原因

“请求成功”并不意味着“地图一定能够正常显示”。

例如:

JavaScript
axios.get('/maps/custom-map.json')

返回 HTTP 200,只能说明服务器成功返回了文件。GeoJSON 是否符合预期,还需要进一步检查。

可以先输出数据:

JavaScript
console.log(response.data);

正常情况下应该看到:

JavaScript
{
  type: "FeatureCollection",
  features: [...]
}

如果返回的是:

HTML


...

那么问题通常不在 ECharts,而在资源路径。

例如:

JavaScript
axios.get('/maps/custom-map.json')

请求路径写错后,开发服务器可能返回 index.html。Axios 如果尝试按照 JSON 解析,就可能出现类似 JSON 解析错误。

因此,浏览器开发者工具中的 Network 面板非常重要。

五、检查自定义地图文件路径

地图资源路径是实际项目中非常容易忽略的问题。

假设项目结构如下:

public/
├── maps/
│   └── custom-map.json
└── index.html

src/
├── App.vue
└── main.js

对于 Vite 项目,通常可以直接:

JavaScript
fetch('/maps/custom-map.json')

因为 public 下的文件会作为静态资源直接提供。

如果地图文件放在:

src/assets/maps/custom-map.json

则更适合通过模块方式引入:

JavaScript
import customMap from './assets/maps/custom-map.json';

echarts.registerMap('customMap', customMap);

不同构建工具对静态资源的处理方式有所区别,因此不要简单地把一个项目中的路径写法复制到另一个项目。

六、处理“Map xxx not exists”错误

如果控制台出现类似:

Map customMap not exists

通常意味着 ECharts 在执行:

JavaScript
series: {
  type: 'map',
  map: 'customMap'
}

时,没有找到名为 customMap 的已注册地图。

首先确认是否执行了:

JavaScript
echarts.registerMap('customMap', geoJson);

其次检查名称是否完全一致:

JavaScript
echarts.registerMap('customMap', geoJson);

对应:

JavaScript
map: 'customMap'

下面这种写法就会出问题:

JavaScript
echarts.registerMap('customMap', geoJson);

const option = {
  series: [
    {
      type: 'map',
      map: 'CustomMap'
    }
  ]
};

地图名称是区分大小写的。

另外,还需要特别注意异步执行顺序。错误示例:

JavaScript
axios.get('/maps/custom-map.json').then(response => {
  echarts.registerMap('customMap', response.data);
});

chart.setOption({
  series: [
    {
      type: 'map',
      map: 'customMap'
    }
  ]
});

这里 setOption() 可能先于地图注册执行。

更安全的方式是:

JavaScript
axios.get('/maps/custom-map.json')
  .then(response => {
    echarts.registerMap('customMap', response.data);

    chart.setOption({
      series: [
        {
          type: 'map',
          map: 'customMap'
        }
      ]
    });
  });

七、解决地图名称与数据名称不匹配

地图能够正常显示,但 data 中的数据没有对应区域颜色,这是另一类非常常见的问题。

例如 GeoJSON:

JSON
{
  "properties": {
    "name": "北京市"
  }
}

而业务数据写成:

JavaScript
data: [
  {
    name: '北京',
    value: 100
  }
]

这两个名称并不相同,ECharts 无法自动判断它们属于同一个区域。

应该保持名称一致:

JavaScript
data: [
  {
    name: '北京市',
    value: 100
  }
]

对于大量业务数据,可以建立名称映射:

JavaScript
const nameMap = {
  '北京': '北京市',
  '上海': '上海市',
  '广州': '广州市'
};

然后在处理数据时统一转换:

JavaScript
const mapData = sourceData.map(item => ({
  name: nameMap[item.name] || item.name,
  value: item.value
}));

这比在大量业务代码中到处修改名称更加容易维护。

八、检查GeoJSON坐标数据

地图空白还有一个重要原因:GeoJSON 坐标数据本身存在问题。

例如坐标数组结构错误:

JSON
"coordinates": [
  [120.1, 30.1]
]

对于 Polygon,通常需要符合相应的嵌套结构:

JSON
"coordinates": [
  [
    [120.1, 30.1],
    [120.2, 30.1],
    [120.2, 30.2],
    [120.1, 30.2],
    [120.1, 30.1]
  ]
]

Polygon 的环通常还需要闭合,即最后一个坐标与第一个坐标相同。

如果 GeoJSON 来自第三方地图数据,还应检查:

  • geometry 是否为空;

  • coordinates 是否完整;

  • Polygon 是否闭合;

  • MultiPolygon 嵌套层级是否正确;

  • 是否存在非法坐标;

  • 是否混入了异常 Feature。

地图数据越复杂,越不能只通过 ECharts 控制台报错判断问题。

九、处理地图坐标范围异常

有时候地图并不是完全不显示,而是出现缩放异常,甚至整个容器看起来是空白。

这可能与坐标范围有关。

例如地图数据的坐标应该位于:

120.x, 30.x

结果某个 Feature 却出现:

0, 0

或者:

12000000, 3000000

那么地图整体范围可能被异常坐标拉大。

可以简单检查:

JavaScript
console.log(geoJson.features);

重点查看:

JavaScript
feature.geometry.coordinates

如果地图数据来源经过坐标转换,还需要确认不同数据是否使用了相同的坐标体系。

十、注意不同坐标体系之间的差异

真实项目中的地图数据可能来自不同来源,例如 WGS84、GCJ-02、BD-09 等。

这些坐标体系并不是简单地把经纬度数字复制过去就能完全重合。

如果底图和 GeoJSON 使用不同坐标体系,就可能出现:

  • 区域边界错位;

  • 标记点偏移;

  • 路径与区域无法对齐;

  • 地图看起来整体偏移。

因此,自定义地图项目在数据准备阶段就应该明确坐标来源,而不是等页面渲染之后才排查偏移问题。

十一、处理异步加载失败

地图文件可能因为网络异常、服务器错误或者部署路径问题加载失败。

建议对 HTTP 状态进行检查:

JavaScript
fetch('/maps/custom-map.json')
  .then(response => {
    if (!response.ok) {
      throw new Error(`地图请求失败:HTTP ${response.status}`);
    }

    return response.json();
  })
  .then(geoJson => {
    echarts.registerMap('customMap', geoJson);

    chart.setOption({
      series: [
        {
          type: 'map',
          map: 'customMap'
        }
      ]
    });
  })
  .catch(error => {
    console.error(error);
  });

相比直接:

JavaScript
fetch('/maps/custom-map.json')
  .then(response => response.json())

增加状态判断后,更容易定位 404、403、500 等服务器问题。

十二、给地图加载增加加载状态

地图文件较大时,用户可能会在短时间内看到空白区域。

可以增加加载提示:

JavaScript
chart.showLoading();

fetch('/maps/custom-map.json')
  .then(response => {
    if (!response.ok) {
      throw new Error('地图数据请求失败');
    }

    return response.json();
  })
  .then(geoJson => {
    echarts.registerMap('customMap', geoJson);

    chart.setOption({
      series: [
        {
          type: 'map',
          map: 'customMap',
          roam: true
        }
      ]
    });
  })
  .catch(error => {
    console.error(error);
  })
  .finally(() => {
    chart.hideLoading();
  });

这样用户能够明确知道地图正在加载,而不是误以为页面出现故障。

十三、Vue项目中如何避免重复注册

Vue组件可能因为路由切换、组件重新挂载等原因重复执行地图初始化逻辑。

可以将地图加载与注册过程封装:

JavaScript
async function loadCustomMap() {
  const response = await fetch('/maps/custom-map.json');

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  const geoJson = await response.json();

  echarts.registerMap('customMap', geoJson);

  return geoJson;
}

组件中:

JavaScript
onMounted(async () => {
  try {
    await loadCustomMap();

    chart.setOption({
      series: [
        {
          type: 'map',
          map: 'customMap'
        }
      ]
    });
  } catch (error) {
    console.error('自定义地图初始化失败', error);
  }
});

如果多个页面都使用同一份地图数据,还可以进一步把地图加载逻辑放到公共模块中,避免重复请求。

十四、React项目中的加载思路

React 中通常可以结合 useEffect 处理地图初始化。

JavaScript
useEffect(() => {
  let chart;

  async function initMap() {
    try {
      chart = echarts.init(document.getElementById('map'));

      const response = await fetch('/maps/custom-map.json');

      if (!response.ok) {
        throw new Error(`地图加载失败:${response.status}`);
      }

      const geoJson = await response.json();

      echarts.registerMap('customMap', geoJson);

      chart.setOption({
        series: [
          {
            type: 'map',
            map: 'customMap',
            roam: true
          }
        ]
      });
    } catch (error) {
      console.error(error);
    }
  }

  initMap();

  return () => {
    chart?.dispose();
  };
}, []);

组件销毁时调用:

JavaScript
chart.dispose();

可以减少图表实例重复创建带来的内存占用和事件绑定问题。

十五、地图容器尺寸导致的空白问题

有些情况下,GeoJSON、注册过程和 setOption() 全部正常,但页面仍然没有地图。

这时应该检查 DOM 容器。

例如:

HTML
 id="map">

如果没有高度:

CSS
#map {
  width: 100%;
  height: 500px;
}

ECharts 初始化时可能无法获得合理的绘制区域。

尤其是在 Vue、React、弹窗、Tab 页等场景中,容器可能在初始化时处于隐藏状态。

这种情况下可以在容器真正显示之后执行:

JavaScript
chart.resize();

例如窗口变化时:

JavaScript
window.addEventListener('resize', () => {
  chart.resize();
});

组件销毁时则应移除监听器,避免产生无效事件。

十六、使用nameMap处理显示名称

如果 GeoJSON 中使用的是完整行政区名称,而页面希望展示简称,可以使用 nameMap。

例如:

JavaScript
const option = {
  series: [
    {
      type: 'map',
      map: 'customMap',
      nameMap: {
        '广东省': '广东',
        '浙江省': '浙江'
      }
    }
  ]
};

不过需要注意,nameMap 的作用与业务数据名称匹配并不完全相同。

如果业务数据:

JavaScript
{
  name: '广东',
  value: 90
}

而 GeoJSON:

JavaScript
{
  name: '广东省'
}

仍然应该从数据层面确认名称映射关系,而不能仅依赖显示名称配置解决所有匹配问题。

十七、自定义地图颜色与交互

地图注册成功后,可以继续通过 visualMap 实现数据分级:

JavaScript
const option = {
  visualMap: {
    min: 0,
    max: 1000,
    calculable: true
  },
  series: [
    {
      type: 'map',
      map: 'customMap',
      roam: true,
      data: [
        { name: '区域A', value: 100 },
        { name: '区域B', value: 500 },
        { name: '区域C', value: 900 }
      ]
    }
  ]
};

还可以设置选中状态:

JavaScript
series: [
  {
    type: 'map',
    map: 'customMap',
    selectedMode: 'single'
  }
]

通过 emphasis 可以强化鼠标悬停效果:

JavaScript
emphasis: {
  label: {
    show: true
  }
}

这样,自定义 GeoJSON 不仅可以作为静态地图展示,也能够承载数据分析和区域交互。

十八、推荐建立统一的错误排查顺序

遇到 ECharts 自定义地图加载失败时,不要一开始就修改大量 option 配置。按照固定顺序排查效率更高。

第一步检查网络请求:

Network → custom-map.json

确认是否:

200 OK

第二步检查响应内容:

JavaScript
console.log(response.data);

确认是不是合法 GeoJSON。

第三步检查注册:

JavaScript
echarts.registerMap('customMap', geoJson);

第四步检查地图名称:

JavaScript
map: 'customMap'

第五步检查业务数据:

JavaScript
data: [
  { name: '区域A', value: 100 }
]

确认 name 与 GeoJSON 中的区域名称一致。

第六步检查容器尺寸:

CSS
#map {
  width: 100%;
  height: 500px;
}

第七步检查坐标体系和 GeoJSON 几何结构。

按照这个顺序排查,通常可以快速区分到底是“资源没有加载”“数据格式错误”“地图没有注册”“名称无法匹配”,还是单纯的 DOM 尺寸问题。

十九、生产环境中的优化建议

自定义地图文件通常体积较大,尤其是高精度行政区边界。直接把原始 GeoJSON 放进生产环境,可能造成页面加载时间增加。

可以从几个方面优化。

1. 简化地图边界

如果地图只用于数据展示,没有精确测绘需求,可以适当降低 GeoJSON 精度。

过于密集的坐标点会增加:

2. 开启资源压缩

服务器可以对 JSON 等静态资源启用 Brotli 或 Gzip 压缩。

GeoJSON 属于文本格式,通常具有较高的压缩收益。

3. 合理缓存地图文件

地图边界变化频率通常低于业务数据,因此可以设置合理的 HTTP 缓存策略,让浏览器减少重复下载。

4. 按需加载

并非所有页面都需要地图,可以在进入地图页面时再加载 GeoJSON,而不是应用启动时一次性加载所有地图数据。

5. 复用已经加载的数据

同一页面存在多个图表时,没有必要重复请求同一个 GeoJSON 文件。可以将加载结果缓存起来:

JavaScript
let customMapPromise;

function loadCustomMap() {
  if (!customMapPromise) {
    customMapPromise = fetch('/maps/custom-map.json')
      .then(response => {
        if (!response.ok) {
          throw new Error('地图加载失败');
        }

        return response.json();
      });
  }

  return customMapPromise;
}

后续调用:

JavaScript
loadCustomMap().then(geoJson => {
  echarts.registerMap('customMap', geoJson);
});

这样多个调用方可以共享同一次请求。

二十、一个相对完整的自定义地图示例

下面将加载、注册、错误处理和图表配置整合起来:

JavaScript
import * as echarts from 'echarts';

async function initCustomMap(dom) {
  const chart = echarts.init(dom);

  chart.showLoading();

  try {
    const response = await fetch('/maps/custom-map.json');

    if (!response.ok) {
      throw new Error(
        `地图文件加载失败,HTTP状态码:${response.status}`
      );
    }

    const geoJson = await response.json();

    if (
      !geoJson ||
      geoJson.type !== 'FeatureCollection' ||
      !Array.isArray(geoJson.features)
    ) {
      throw new Error('地图文件不是有效的GeoJSON FeatureCollection');
    }

    echarts.registerMap('customMap', geoJson);

    chart.setOption({
      tooltip: {
        trigger: 'item'
      },
      visualMap: {
        min: 0,
        max: 1000,
        calculable: true
      },
      series: [
        {
          type: 'map',
          map: 'customMap',
          roam: true,
          data: [
            {
              name: '区域A',
              value: 300
            },
            {
              name: '区域B',
              value: 700
            }
          ]
        }
      ]
    });

    return chart;
  } catch (error) {
    console.error('ECharts自定义地图初始化失败:', error);
    throw error;
  } finally {
    chart.hideLoading();
  }
}

这个版本比单纯调用 registerMap() 更适合实际项目,因为它把资源请求、HTTP 状态检查、GeoJSON 基础校验、地图注册、图表初始化和异常处理串联起来了。

ECharts 自定义地图最核心的并不是某一个配置参数,而是保证“地图文件、地图注册、区域名称、坐标数据和渲染时机”彼此一致。遇到地图空白或 Map xxx not exists 一类错误时,应优先检查资源请求和注册流程,再检查 GeoJSON 结构、名称匹配、容器尺寸以及坐标体系。将地图加载封装成独立的异步模块后,再配合缓存、错误处理和数据校验,可以让自定义地图在 Vue、React 等前端项目中更加稳定,也方便后续维护和扩展。