echarts地图自定义边界

0 阅读5分钟

想要效果

image.png

问题

没有这一级别的地图geojson数据

image.png

DataV.GeoAtlas地理小工具系列

网站上获取地图信息,到黄岩区这一级后,就没有下一级别了,但是实际的业务中又需要划分出区域出来

思路

1.手动划线,设置区域

2.将划线生成坐标点

3.按区域生成多个区域数据

第一步

显示黄岩地图

image.png

第二步 设置划线逻辑

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>地图画线工具</title>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            display: flex;
            height: 100vh;
            font-family: Arial, sans-serif;
        }
        #sidebar {
            width: 350px;
            background: #263238;
            color: white;
            padding: 20px;
            overflow-y: auto;
        }
        #map { flex: 1; background: #0d47a1; }
        h2 { margin-bottom: 20px; color: #4fc3f7; }
        .btn {
            width: 100%;
            padding: 12px;
            margin: 8px 0;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
        .btn-red { background: #f44336; color: white; }
        .btn-green { background: #4caf50; color: white; }
        .btn-blue { background: #2196f3; color: white; }
        .btn-orange { background: #ff9800; color: white; }
        #points { 
            max-height: 200px; 
            overflow-y: auto; 
            background: #1a1a1a; 
            padding: 10px; 
            margin: 10px 0;
            font-family: monospace;
            font-size: 12px;
            border-radius: 4px;
        }
        .point-row {
            padding: 5px;
            border-bottom: 1px solid #333;
            display: flex;
            justify-content: space-between;
        }
        textarea {
            width: 100%;
            height: 200px;
            margin-top: 10px;
            padding: 10px;
            background: #1a1a1a;
            color: #4caf50;
            border: 1px solid #455a64;
            font-family: monospace;
            font-size: 11px;
            resize: none;
        }
        .coord {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: rgba(0,0,0,0.9);
            color: #4fc3f7;
            padding: 15px 25px;
            border-radius: 8px;
            font-family: monospace;
            font-size: 14px;
            z-index: 1000;
        }
        .info {
            background: rgba(76, 175, 80, 0.2);
            padding: 10px;
            border-left: 4px solid #4caf50;
            margin: 10px 0;
            font-size: 13px;
        }
    </style>
</head>
<body>
    <div id="sidebar">
        <h2>🗺️ 地图画线工具</h2>
        
        <div class="info">
            <strong>操作说明:</strong><br>
            • 按住鼠标拖动:画线<br>
            • 松开鼠标:结束画线<br>
            • 画完后点击"生成坐标"
        </div>

        <button class="btn btn-orange" onclick="undo()">↩️ 撤销</button>
        <button class="btn btn-red" onclick="clearAll()">🗑️ 清空</button>
        
        <div style="margin: 15px 0;">
            <strong>已采集点位:</strong>
            <span id="count">0</span></div>
        
        <button class="btn btn-green" onclick="generate()">📋 生成 Polygon 坐标</button>
        <button class="btn btn-blue" onclick="copy()">📄 复制到剪贴板</button>
        
        <textarea id="output" placeholder="点击"生成坐标"后显示..."></textarea>
    </div>

    <div id="map"></div>
    <div class="coord" id="coord">经度: --, 纬度: --</div>

    <script>
        let points = [];
        let chart = null;
        let isDrawing = false;
        let drawMode = false;
        let tempLine = [];

        // 加载地图
        fetch('./黄岩区.geojson')
            .then(r => r.json())
            .then(data => {
                echarts.registerMap('huangyan', data);
                init();
            })
            .catch(e => alert('加载地图失败: ' + e.message));

        function init() {
            chart = echarts.init(document.getElementById('map'));
            
            chart.setOption({
                geo: {
                    map: 'huangyan',
                    roam: true,
                    layoutCenter: ['50%', '50%'],
                    layoutSize: '95%',
                    silent: true,
                    itemStyle: {
                        areaColor: 'rgba(25, 118, 210, 0.5)',
                        borderColor: '#4fc3f7',
                        borderWidth: 2
                    },
                    emphasis: {
                        disabled: false,
                        itemStyle: {
                            areaColor: 'rgba(79, 195, 247, 0.7)',
                            borderColor: '#fff',
                            borderWidth: 3
                        }
                    }
                },
                series: [
                    {
                        type: 'scatter',
                        coordinateSystem: 'geo',
                        data: [],
                        symbolSize: 15,
                        itemStyle: { color: '#ff5722', borderColor: '#fff', borderWidth: 2 },
                        label: { show: true, formatter: '{@index}', color: '#fff' },
                        zlevel: 10
                    },
                    {
                        type: 'lines',
                        coordinateSystem: 'geo',
                        polyline: true,
                        data: [],
                        lineStyle: { color: '#ff5722', width: 3 },
                        zlevel: 9
                    }
                ]
            });

            // 获取地理坐标的辅助函数
            function getGeoCoord(e) {
                // 使用 convertFromPixel 并指定 geo 坐标系
                const coord = chart.convertFromPixel({ seriesIndex: 0 }, [e.offsetX, e.offsetY]);
                if (!coord || isNaN(coord[0]) || isNaN(coord[1])) return null;
                // 验证坐标在黄岩区范围内(大致范围)
                if (coord[0] < 120.5 || coord[0] > 121.5 || coord[1] < 28.2 || coord[1] > 28.9) {
                    return null;
                }
                return coord;
            }

            // 鼠标坐标
            chart.getZr().on('mousemove', e => {
                const c = getGeoCoord(e);
                if (c) document.getElementById('coord').textContent = 
                    `经度: ${c[0].toFixed(6)}, 纬度: ${c[1].toFixed(6)}`;
            });

            // 当前正在画的轨迹点
            let drawingPoints = [];
            let lastDrawPoint = null;
            
            // 鼠标按下,开始画线
            chart.getZr().on('mousedown', e => {
                isDrawing = true;
                const coord = getGeoCoord(e);
                if (coord) {
                    drawingPoints = [coord]; // 开始新轨迹
                    lastDrawPoint = coord;
                    drawLine();
                }
            });

            // 鼠标移动,实时记录轨迹(间隔采集)
            chart.getZr().on('mousemove', e => {
                const c = getGeoCoord(e);
                if (c) {
                    document.getElementById('coord').textContent = 
                        `经度: ${c[0].toFixed(6)}, 纬度: ${c[1].toFixed(6)}`;
                    
                    if (isDrawing && lastDrawPoint) {
                        // 计算与上一个点的距离 
                        const dist = Math.sqrt(
                            Math.pow(c[0] - lastDrawPoint[0], 2) + 
                            Math.pow(c[1] - lastDrawPoint[1], 2)
                        );
                        // 距离超过0.005度(约500米)才采集
                        if (dist > 0.005) {
                            drawingPoints.push(c);
                            lastDrawPoint = c;
                            drawLine();
                        }
                    }
                }
            });

            // 鼠标松开,完成画线
            chart.getZr().on('mouseup', () => {
                if (!isDrawing) return;
                isDrawing = false;
                
                // 将轨迹点添加到总点集
                if (drawingPoints.length > 0) {
                    points = points.concat(drawingPoints);
                    drawingPoints = [];
                    lastDrawPoint = null;
                    update();
                }
            });
            
            // 实时画线
            function drawLine() {
                // 合并已有点和正在画的轨迹
                const allPoints = [...points, ...drawingPoints];
                
                if (chart) {
                    chart.setOption({
                        series: [
                            { 
                                data: allPoints.map((p, i) => ({ 
                                    value: p, 
                                    name: i < points.length ? i + 1 : '*' 
                                })) 
                            },
                            { 
                                data: allPoints.length > 1 ? [{ coords: allPoints }] : [] 
                            }
                        ]
                    });
                }
            }

            // 禁用地图拖动,但允许缩放
            chart.setOption({
                geo: {
                    roam: true
                }
            });

            // 快捷键:Ctrl+Z 撤销
            document.addEventListener('keydown', (e) => {
                if (e.ctrlKey && e.key === 'z') {
                    e.preventDefault();
                    undo();
                }
            });

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

        // 只更新地图,不更新侧边栏(画线时使用,避免卡顿)
        function updateMapOnly() {
            document.getElementById('count').textContent = points.length;
            if (chart) {
                chart.setOption({
                    series: [
                        { data: points.map((p, i) => ({ value: p, name: i + 1 })) },
                        { data: points.length > 1 ? [{ coords: points }] : [] }
                    ]
                });
            }
        }

        function update() {
            // 只更新计数和地图
            document.getElementById('count').textContent = points.length;
            if (chart) {
                chart.setOption({
                    series: [
                        { data: points.map((p, i) => ({ value: p, name: i + 1 })) },
                        { data: points.length > 1 ? [{ coords: points }] : [] }
                    ]
                });
            }
        }



        function undo() {
            points.pop();
            update();
        }

        function clearAll() {
            if (confirm('清空所有点?')) {
                points = [];
                update();
            }
        }

        function closePolygon() {
            if (points.length < 3) {
                alert('至少需要3个点');
                return;
            }
            // 如果首尾不相同,添加首点作为尾点
            const first = points[0];
            const last = points[points.length - 1];
            if (first[0] !== last[0] || first[1] !== last[1]) {
                points.push([...first]);
                update();
            }
            alert('已闭合!');
        }

        function generate() {
            if (points.length < 3) {
                alert('至少需要3个点');
                return;
            }
            
            // 生成格式化的坐标数组
            const coords = points.map(p => `            [${p[0].toFixed(6)}, ${p[1].toFixed(6)}]`).join(',\n');
            
            const output = `"coordinates": [
          [
${coords}
          ]
        ]`;
            
            document.getElementById('output').value = output;
        }

        function copy() {
            const out = document.getElementById('output');
            if (!out.value) return alert('请先生成坐标');
            out.select();
            document.execCommand('copy');
            alert('已复制!粘贴到 GeoJSON 的 coordinates 字段');
        }

        // 初始化显示
        update();
    </script>
</body>
</html>

划线测试

image.png

生成坐标点

"coordinates": [
          [
            [120.993998, 28.689548],
            [120.987912, 28.694942],
            [120.975741, 28.701996],
            [120.960804, 28.709465],
            [120.953059, 28.721082],
            [120.927057, 28.724402],
            [120.920418, 28.716933],
            [120.915439, 28.707805],
            [120.907694, 28.703656],
            [120.908247, 28.693283],
            [120.906588, 28.679175],
            [120.902162, 28.669632],
            [120.901608, 28.660089],
            [120.896629, 28.665068],
            [120.889991, 28.666727],
            [120.882799, 28.666727],
            [120.875053, 28.662578],
            [120.885011, 28.655939],
            [120.887778, 28.649716],
            [120.893863, 28.644737],
            [120.905481, 28.636023],
            [120.913780, 28.628140],
            [120.913780, 28.625235],
            [120.903821, 28.624405],
            [120.880586, 28.627725],
            [120.867861, 28.629799],
            [120.863989, 28.624405],
            [120.855690, 28.620671],
            [120.828582, 28.617352],
            [120.811985, 28.617352],
            [120.805899, 28.611128],
            [120.805346, 28.601170],
            [120.798154, 28.594116],
            [120.795388, 28.590382],
            [120.796495, 28.579179],
            [120.801474, 28.570465],
            [120.814198, 28.567561],
            [120.826369, 28.567146],
            [120.826369, 28.558433],
            [120.829135, 28.553453],
            [120.841859, 28.545155],
            [120.836880, 28.533122],
            [120.833561, 28.527728],
            [120.839093, 28.518185],
            [120.849605, 28.515281],
            [120.854031, 28.514451],
            [120.857350, 28.505737],
            [120.863989, 28.494949],
            [120.875607, 28.490385],
            [120.897736, 28.486236],
            [120.912673, 28.494119],
            [120.924291, 28.503248],
            [120.948080, 28.500343],
            [120.952506, 28.509057],
            [120.961911, 28.509887],
            [120.974635, 28.538516],
            [120.971316, 28.558847],
            [120.980167, 28.574200],
            [120.996764, 28.578349],
            [121.006722, 28.599925],
            [121.005616, 28.616522],
            [121.023319, 28.625235],
            [121.036597, 28.621916],
            [121.046002, 28.641417],
            [121.034384, 28.665898],
            [121.031618, 28.681665],
            [121.012808, 28.680005],
            [121.002850, 28.686644]
          ]
        ]

渲染

image.png

<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>地图显示</title>
  <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { height: 100vh; background: #0d47a1; }
    #map { width: 100%; height: 100%; }
    .color-picker {
      position: fixed; top: 20px; right: 20px;
      background: rgba(0,0,0,0.8); color: white;
      padding: 15px 20px; border-radius: 8px;
    }
    .color-picker input { width: 60px; height: 30px; border: none; }
  </style>
</head>
<body>
  <div id="map"></div>
  <div class="color-picker">
    <label>背景色:</label>
    <input type="color" id="bgColor" value="#0d47a1" />
  </div>

  <script>
    // 1. 定义颜色映射(和GeoJSON的name一一对应)
    const colorMap = {
      "宁溪所": "rgba(255, 87, 34, 0.6)",
      "北洋所": "rgba(76, 175, 80, 0.6)",
      "北新所": "rgba(33, 150, 243, 0.6)",
      "东江所": "rgba(255, 152, 0, 0.6)",
      "院桥所": "rgba(156, 39, 176, 0.6)",
      "西澄所": "rgba(0, 188, 212, 0.6)",
      "黄岩区": "rgba(25, 118, 210, 0.5)"
    };

    // 2. 加载GeoJSON并初始化地图
    fetch('./黄岩区.geojson')
      .then(r => {
        // 验证GeoJSON加载成功
        if (!r.ok) throw new Error('GeoJSON文件加载失败');
        return r.json();
      })
      .then(geoJson => {
        // 打印GeoJSON中的所有name,方便你核对
        console.log('GeoJSON中的区域名称:');
        geoJson.features.forEach(feature => {
          console.log('- ' + feature.properties.name);
        });

        // 注册地图
        echarts.registerMap('huangyan', geoJson);
        const chart = echarts.init(document.getElementById('map'));

        // 3. 构造地图数据(确保每个区域都有对应数据,并设置颜色)
        const mapData = Object.keys(colorMap).map(name => ({
          name: name,
          value: Math.random(), // 随便给个值
          itemStyle: {
            areaColor: colorMap[name] // 直接为每个区域设置颜色
          }
        }));

        // 4. 配置项(极简版,只保留核心)
        const option = {
          backgroundColor: '#0d47a1',
          series: [{
            type: 'map',
            map: 'huangyan',
            roam: true, // 支持缩放/拖拽
            data: mapData,
            // 区域样式
            itemStyle: {
              borderColor: '#4fc3f7',
              borderWidth: 2
            },
            // 高亮样式
            emphasis: {
              itemStyle: {
                borderColor: '#fff',
                borderWidth: 3,
                areaColor: function(params) {
                  // 高亮时颜色加深
                  const color = colorMap[params.name];
                  if (color) {
                    return color.replace('0.6)', '0.9)').replace('0.5)', '0.8)');
                  }
                  return '#ff9800';
                }
              }
            },
            // 文字标签
            label: {
              show: true,
              color: '#fff',
              fontSize: 14,
              fontWeight: 'bold'
            }
          }]
        };

        // 渲染地图
        chart.setOption(option);
        window.addEventListener('resize', () => chart.resize());

        // 背景色切换
        document.getElementById('bgColor').addEventListener('input', (e) => {
          chart.setOption({ backgroundColor: e.target.value });
          document.body.style.background = e.target.value;
        });

      })
      .catch(e => {
        alert('错误:' + e.message);
        console.error('详细错误:', e);
      });
  </script>
</body>
</html>

多次绘制实现最终效果