地理信息分析 - ECharts 场景化最佳实践
场景描述: 基于地图的地理信息可视化,包括热力图、散点分布、区域聚合、路径规划、行政区划等功能,广泛应用于物流、零售、人口分析等领域。
📋 目录
场景概述
典型应用场景
- 物流配送: 配送路线规划、配送点分布、时效分析
- 零售选址: 门店分布、客流热力图、商圈分析
- 人口统计: 人口密度、迁移流动、城市化趋势
- 环境监测: 空气质量、温度分布、污染源定位
- 应急响应: 灾害范围、救援资源调度、疏散路线
核心挑战
核心功能
功能1: 地图注册与加载
步骤1: 获取GeoJSON数据
typescript
// 方式1: 从阿里云DataV获取
const chinaGeoJSON = await fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')
.then(r => r.json());
// 方式2: 从高德API获取
const cityGeoJSON = await fetch(`https://restapi.amap.com/v3/config/district?keywords=${cityName}&key=${apiKey}&subdistrict=0&extensions=all`)
.then(r => r.json());
// 方式3: 本地文件
import provinceGeoJSON from './geojson/provinces/zhejiang.json';1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
步骤2: 注册地图
typescript
// ❌ 错误: 未注册就使用
chart.setOption({
geo: { map: 'china' } // ⚠️ 白屏!
});
// ✅ 正确: 先注册后使用
echarts.registerMap('china', chinaGeoJSON);
chart.setOption({
geo: { map: 'china' }
});1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
步骤3: 批量注册
typescript
// 批量注册省级地图
async function registerProvinces() {
const provinces = ['zhejiang', 'jiangsu', 'guangdong'];
for (const province of provinces) {
const geoJSON = await fetch(`/geojson/provinces/${province}.json`)
.then(r => r.json());
echarts.registerMap(province, geoJSON);
}
}
await registerProvinces();1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
功能2: 散点分布图
基础散点图
typescript
// 门店分布
const storeData = [
{ name: '门店A', value: [120.153576, 30.287459, 100] }, // [经度, 纬度, 数值]
{ name: '门店B', value: [116.407526, 39.904030, 150] },
{ name: '门店C', value: [121.473701, 31.230416, 120] }
];
chart.setOption({
geo: {
map: 'china',
roam: true, // 允许缩放平移
zoom: 1.2,
label: { show: false },
itemStyle: {
areaColor: '#eee',
borderColor: '#999'
}
},
series: [{
name: '门店分布',
type: 'scatter',
coordinateSystem: 'geo', // ✅ 使用地理坐标系
data: storeData,
symbolSize: (value) => Math.sqrt(value[2]) / 5, // 根据数值调整大小
emphasis: {
label: { show: true, formatter: '{b}' }
},
itemStyle: {
color: '#c23531'
}
}]
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
大规模散点优化
typescript
// 10000+数据点的优化方案
const largeScatterData = generateRandomPoints(10000);
chart.setOption({
geo: { map: 'china' },
series: [{
type: 'scatter',
coordinateSystem: 'geo',
data: largeScatterData,
// ✅ 性能优化配置
progressive: 5000, // 渐进式渲染
progressiveThreshold: 2000, // 超过2000启用渐进式
large: true, // 大规模模式
largeThreshold: 1000, // 超过1000启用large模式
symbolSize: 4, // 减小符号大小
emphasis: { // 简化高亮效果
scale: false // 禁用缩放动画
},
animation: false // 禁用动画
}]
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
性能对比:
| 数据量 | 普通模式FPS | 优化后FPS | 提升 |
|---|---|---|---|
| 1000 | 55 | 60 | ↑ 9% |
| 5000 | 25 | 58 | ↑ 132% |
| 10000 | 12 | 55 | ↑ 358% |
| 50000 | 3 | 48 | ↑ 1500% |
功能3: 热力图
核密度估计热力图
typescript
// 人口密度热力图
const heatmapData = [
{ name: '杭州', value: [120.153576, 30.287459, 1000] },
{ name: '上海', value: [121.473701, 31.230416, 2500] },
{ name: '南京', value: [118.796877, 32.060255, 800] }
];
chart.setOption({
geo: { map: 'china' },
visualMap: {
min: 0,
max: 3000,
calculable: true,
inRange: {
color: ['#fff', '#ff0', '#f80', '#f00'] // 白→黄→橙→红
}
},
series: [{
name: '人口密度',
type: 'heatmap',
coordinateSystem: 'geo',
data: heatmapData,
pointSize: 20, // 点的大小
blurSize: 30 // 模糊程度
}]
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
功能4: 路径规划与飞线
飞线图(OD流向)
typescript
// 物流运输路线
const routes = [
{
coords: [
[120.153576, 30.287459], // 起点: 杭州
[116.407526, 39.904030] // 终点: 北京
],
value: 100 // 货运量
},
{
coords: [
[120.153576, 30.287459], // 起点: 杭州
[121.473701, 31.230416] // 终点: 上海
],
value: 150
}
];
chart.setOption({
geo: {
map: 'china',
roam: true,
itemStyle: {
areaColor: '#1a1a2e',
borderColor: '#16213e'
},
emphasis: {
itemStyle: { areaColor: '#0f3460' }
}
},
series: [
// 起点标记
{
name: '起点',
type: 'effectScatter',
coordinateSystem: 'geo',
data: [{ name: '杭州', value: [120.153576, 30.287459] }],
symbolSize: 15,
rippleEffect: {
brushType: 'stroke',
scale: 3
},
itemStyle: { color: '#00f5d4' }
},
// 终点标记
{
name: '终点',
type: 'scatter',
coordinateSystem: 'geo',
data: routes.map(route => ({
name: '终点',
value: route.coords[1]
})),
symbolSize: 10,
itemStyle: { color: '#fee440' }
},
// 飞线
{
name: '运输路线',
type: 'lines',
coordinateSystem: 'geo',
data: routes.map(route => ({
coords: route.coords,
value: route.value
})),
lineStyle: {
color: '#00f5d4',
width: 1,
opacity: 0.4,
curveness: 0.2 // 曲线程度
},
effect: {
show: true,
period: 6, // 动画周期
trailLength: 0.3, // 尾迹长度
color: '#fff', // 移动点颜色
symbol: 'arrow', // 箭头
symbolSize: 6
}
}
]
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
技术实现
坐标转换
常见坐标系说明
typescript
/**
* WGS84: GPS原始坐标(国际标准)
* GCJ02: 火星坐标(中国国测局标准,GPS需要加偏移)
* BD09: 百度坐标(在GCJ02基础上再次加密)
*/
// WGS84 → GCJ02
function wgs84ToGcj02(lng: number, lat: number): [number, number] {
const PI = 3.1415926535897932384626;
const a = 6378245.0;
const ee = 0.00669342162296594323;
let dLat = transformLat(lng - 105.0, lat - 35.0);
let dLng = transformLng(lng - 105.0, lat - 35.0);
const radLat = lat / 180.0 * PI;
let magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * PI);
dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * PI);
const mgLat = lat + dLat;
const mgLng = lng + dLng;
return [mgLng, mgLat];
}
function transformLat(lng: number, lat: number): number {
let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * Math.PI) + 40.0 * Math.sin(lat / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * Math.PI) + 320 * Math.sin(lat * Math.PI / 30.0)) * 2.0 / 3.0;
return ret;
}
function transformLng(lng: number, lat: number): number {
let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * Math.PI) + 40.0 * Math.sin(lng / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * Math.PI) + 300.0 * Math.sin(lng / 30.0 * Math.PI)) * 2.0 / 3.0;
return ret;
}
// GCJ02 → BD09
function gcj02ToBd09(lng: number, lat: number): [number, number] {
const z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * Math.PI);
const theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * Math.PI);
const bdLng = z * Math.cos(theta) + 0.0065;
const bdLat = z * Math.sin(theta) + 0.006;
return [bdLng, bdLat];
}
// 使用示例
const gpsCoords = [120.153576, 30.287459]; // WGS84
const gcjCoords = wgs84ToGcj02(gpsCoords[0], gpsCoords[1]); // 转火星坐标
const bdCoords = gcj02ToBd09(gcjCoords[0], gcjCoords[1]); // 转百度坐标
console.log('WGS84:', gpsCoords);
console.log('GCJ02:', gcjCoords);
console.log('BD09:', bdCoords);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
区域聚合(Clustering)
typescript
// 当有10000个点位时,需要聚合显示
class GeoCluster {
private points: Array<{ lng: number; lat: number; value: number }>;
constructor(points: any[]) {
this.points = points;
}
/**
* 网格聚合
*/
gridCluster(zoom: number): any[] {
const gridSize = this.getGridSize(zoom);
const clusters: Map<string, any[]> = new Map();
this.points.forEach(point => {
// 计算网格ID
const gridX = Math.floor(point.lng / gridSize);
const gridY = Math.floor(point.lat / gridSize);
const gridId = `${gridX}_${gridY}`;
if (!clusters.has(gridId)) {
clusters.set(gridId, []);
}
clusters.get(gridId)!.push(point);
});
// 生成聚合数据
const result: any[] = [];
clusters.forEach((points, gridId) => {
const center = this.calculateCenter(points);
const totalValue = points.reduce((sum, p) => sum + p.value, 0);
result.push({
name: `聚合点(${points.length})`,
value: [center.lng, center.lat, totalValue],
count: points.length,
points: points
});
});
return result;
}
private getGridSize(zoom: number): number {
// 缩放级别越高,网格越小
const baseSize = 0.5; // 基础网格0.5度
return baseSize / Math.pow(2, zoom - 4);
}
private calculateCenter(points: any[]): { lng: number; lat: number } {
const totalLng = points.reduce((sum, p) => sum + p.lng, 0);
const totalLat = points.reduce((sum, p) => sum + p.lat, 0);
return {
lng: totalLng / points.length,
lat: totalLat / points.length
};
}
}
// 使用
const cluster = new GeoCluster(largePoints);
const clusteredData = cluster.gridCluster(chartZoomLevel);
chart.setOption({
series: [{
type: 'scatter',
coordinateSystem: 'geo',
data: clusteredData,
symbolSize: (data) => Math.log(data[2]) * 5, // 根据总值调整大小
label: {
show: true,
formatter: (params) => params.data.count.toString() // 显示点数
}
}]
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
高级应用
应用1: 地图下钻
typescript
class MapDrillDown {
private chart: echarts.ECharts;
private currentLevel: 'country' | 'province' | 'city' = 'country';
private history: string[] = [];
constructor(chart: echarts.ECharts) {
this.chart = chart;
// 监听点击事件
this.chart.on('click', (params) => {
if (params.componentType === 'geo') {
this.drillDown(params.name);
}
});
}
async drillDown(regionName: string) {
console.log(`下钻到: ${regionName}`);
// 记录历史
this.history.push(regionName);
// 获取该地区的GeoJSON
const geoJSON = await this.loadGeoJSON(regionName);
// 注册地图
echarts.registerMap(regionName, geoJSON);
// 更新图表
this.chart.setOption({
geo: {
map: regionName,
zoom: 1
}
});
// 更新面包屑
this.updateBreadcrumb();
}
drillUp() {
if (this.history.length <= 1) {
console.log('已经是最顶层');
return;
}
this.history.pop();
const previousRegion = this.history[this.history.length - 1];
this.chart.setOption({
geo: { map: previousRegion }
});
this.updateBreadcrumb();
}
private updateBreadcrumb() {
const breadcrumb = document.getElementById('breadcrumb');
if (breadcrumb) {
breadcrumb.innerHTML = this.history.join(' > ');
}
}
private async loadGeoJSON(regionName: string): Promise<any> {
// 从服务器或本地加载
const response = await fetch(`/geojson/${regionName}.json`);
return response.json();
}
}
// 使用
const drillDown = new MapDrillDown(chart);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
性能优化
优化1: 懒加载GeoJSON
typescript
// ❌ 错误: 启动时加载所有地图
await Promise.all([
loadGeoJSON('china'),
loadGeoJSON('beijing'),
loadGeoJSON('shanghai'),
// ... 几百个省份城市
]);
// ✅ 正确: 按需加载
async function loadMapOnDemand(regionName: string) {
// 检查是否已缓存
if (echarts.getMap(regionName)) {
return;
}
// 动态加载
const geoJSON = await fetch(`/geojson/${regionName}.json`).then(r => r.json());
echarts.registerMap(regionName, geoJSON);
}
// 用户点击时才加载
chart.on('click', async (params) => {
await loadMapOnDemand(params.name);
chart.setOption({ geo: { map: params.name } });
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
优化2: Web Worker处理聚合
typescript
// worker.js
self.onmessage = function(e) {
const { points, zoom } = e.data;
// 复杂的聚合计算
const clusters = performClustering(points, zoom);
self.postMessage(clusters);
};
function performClustering(points: any[], zoom: number) {
// 密集计算...
return clusters;
}
// main.js
const clusterWorker = new Worker('cluster-worker.js');
// 缩放时触发聚合
chart.on('georoam', () => {
const zoom = chart.getOption().geo[0].zoom;
clusterWorker.postMessage({
points: allPoints,
zoom: zoom
});
});
clusterWorker.onmessage = (e) => {
const clusteredData = e.data;
chart.setOption({
series: [{ data: clusteredData }]
});
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
完整案例
全国物流监控大屏
typescript
class LogisticsDashboard {
private chart: echarts.ECharts;
private ws: WebSocket;
private orders: Map<string, any> = new Map();
constructor() {
this.chart = echarts.init(document.getElementById('logistics-map'));
this.ws = new WebSocket('wss://logistics.example.com/ws');
}
async init() {
// 1. 加载中国地图
const chinaGeoJSON = await fetch('/geojson/china.json').then(r => r.json());
echarts.registerMap('china', chinaGeoJSON);
// 2. 初始化地图
this.chart.setOption({
title: {
text: '全国物流实时监控',
left: 'center',
textStyle: { fontSize: 24, color: '#fff' }
},
backgroundColor: '#1a1a2e',
geo: {
map: 'china',
roam: true,
zoom: 1.2,
label: { show: false },
itemStyle: {
areaColor: '#16213e',
borderColor: '#0f3460'
},
emphasis: {
itemStyle: { areaColor: '#e94560' }
}
},
tooltip: {
trigger: 'item',
formatter: (params) => {
if (params.seriesType === 'lines') {
return `${params.data.from} → ${params.data.to}<br/>运输中`;
}
return `${params.name}<br/>${params.value[2]}单`;
}
}
});
// 3. 监听WebSocket
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleRealtimeData(data);
};
// 4. 加载历史订单
await this.loadHistoryOrders();
}
private handleRealtimeData(data: any) {
switch (data.type) {
case 'new_order':
this.addOrder(data.order);
break;
case 'update_location':
this.updateVehicleLocation(data.vehicle);
break;
case 'delivery':
this.markDelivered(data.orderId);
break;
}
this.render();
}
private addOrder(order: any) {
this.orders.set(order.id, order);
}
private render() {
// 聚合订单数据
const cityOrders = this.aggregateByCity();
// 生成飞线数据
const routes = this.generateRoutes();
this.chart.setOption({
series: [
// 订单热力图
{
name: '订单分布',
type: 'heatmap',
coordinateSystem: 'geo',
data: cityOrders,
pointSize: 15,
blurSize: 20
},
// 运输路线
{
name: '运输路线',
type: 'lines',
coordinateSystem: 'geo',
data: routes,
lineStyle: {
color: '#00f5d4',
width: 1,
opacity: 0.3,
curveness: 0.2
},
effect: {
show: true,
period: 6,
trailLength: 0.3,
color: '#fff',
symbol: 'arrow'
}
}
]
});
}
private aggregateByCity(): any[] {
const cityMap: Map<string, number> = new Map();
this.orders.forEach(order => {
const cityKey = `${order.cityLng},${order.cityLat}`;
cityMap.set(cityKey, (cityMap.get(cityKey) || 0) + 1);
});
return Array.from(cityMap.entries()).map(([coords, count]) => {
const [lng, lat] = coords.split(',').map(Number);
return { name: '订单', value: [lng, lat, count] };
});
}
private generateRoutes(): any[] {
const warehouse = { lng: 120.153576, lat: 30.287459 }; // 杭州仓库
return Array.from(this.orders.values())
.filter(order => order.status === 'delivering')
.map(order => ({
coords: [
[warehouse.lng, warehouse.lat],
[order.lng, order.lat]
],
from: '杭州',
to: order.cityName
}));
}
private async loadHistoryOrders() {
const orders = await fetch('/api/orders/today').then(r => r.json());
orders.forEach((order: any) => this.orders.set(order.id, order));
this.render();
}
}
// 启动
const dashboard = new LogisticsDashboard();
dashboard.init();1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
最佳实践总结
🎯 技术要点
| 功能 | 关键技术 | 注意事项 |
|---|---|---|
| 地图注册 | echarts.registerMap | 必须先注册后使用 |
| 大规模散点 | progressive + large | 超过2000点启用 |
| 坐标转换 | WGS84→GCJ02→BD09 | 注意API使用的坐标系 |
| 飞线效果 | lines系列+effect | 控制curveness曲线度 |
| 地图下钻 | 动态加载GeoJSON | 懒加载避免初始化过大 |
| 区域聚合 | 网格聚类算法 | 根据zoom动态调整粒度 |
📊 性能优化清单
- [ ] 超过1000点开启
large: true - [ ] 超过2000点开启
progressive - [ ] 禁用不必要的动画(
animation: false) - [ ] GeoJSON懒加载(按需加载)
- [ ] 复杂计算交给Web Worker
- [ ] 使用网格聚合减少渲染数量
- [ ] 减小symbolSize提升大数据量性能
🔧 常见问题
Q1: 地图白屏?
A: 检查是否正确注册:
typescript
echarts.registerMap('china', geoJSON);
chart.setOption({ geo: { map: 'china' } });1
2
2
Q2: 散点位置偏移?
A: 确认坐标系是否正确:
typescript
series: [{
coordinateSystem: 'geo', // 必须设置
data: [[lng, lat, value]]
}]1
2
3
4
2
3
4
Q3: 大数据量卡顿?
A: 启用优化配置:
typescript
{
progressive: 5000,
large: true,
animation: false
}1
2
3
4
5
2
3
4
5
延伸阅读
- ECharts 反模式: 地图未注册白屏
- ECharts 性能优化
- ECharts 坐标系系统
- GeoJSON规范
总结: 地理信息分析的核心是正确的地图注册、合理的性能优化和准确的坐标转换。通过渐进式渲染、区域聚合、懒加载等技术,可以实现万级数据点的流畅展示。记住:地图可视化的价值在于揭示空间规律,而不是简单地标注位置。
