Skip to content

openlayers

是一个用于开发WebGIS客户端的JavaScript包,它是一个开源的项目,其设计之意是为互联网客户端提供强大的地图展示功能,包括地图数据显示与相关操作,并具有灵活的扩展机制。

架构

架构

  • Map:整个地图的容器
  • Layer:地图图层
  • Source:对应图层的数据源
  • Style:矢量图层的样式
  • View:地图表现相关的视图

坐标系

库默认使用EPSG:4326地理坐标系,EPSG:3857投影坐标系展示地图。

在创建视图时,若使用EPSG:4326坐标系覆盖默认的EPSG:3857投影,用于展示地图,在这种情况下,openLayers会将经纬度坐标直接映射到屏幕坐标系,而不会进行任何投影转换。

使用经纬度投影时,经度的范围为-180度至+180度,纬度的范围为-90度至+90度。较小纬度的区域(如赤道附近)会在屏幕上呈现较大的比例因子,而较高纬度的区域(如极地附近)会呈现较小的比例因子。

需要注意的是,使用经纬度投影时,地图上的直线不一定是直的,因为经纬度本身是曲线坐标系。如果需要在地图上绘制直线,或者进行测量和几何计算等操作,可能需要先将经纬度坐标转换为其他投影坐标系(如投影坐标系,如Web墨卡托投影 EPSG:3857),然后再进行相应的操作。

安装和使用

bash
npm i ol --save

MapView

mapol总的核心组件,初始化地图(map)时,至少需要一个可视化区域(view)、一个或多个图层(layer)、一个地图加载的目标html元素(target)

js
import Map from 'ol/map';
import View from 'ol/View';
import OSM from 'ol/source/OSM';
import Tile from 'ol/layer/Tile';

const map = new Map({
  target: document.getElementById('map'),
  layers: [
    new Tile({
      source: new OSM(),
    }),
  ],
  view: new View({
    zoom: 4,
    center: [108.358224, 22.904248],
  }),
});

坐标系注册和转换

若需要使用其他坐标系,可以进行注册,如下注册了一个GCJ02地理坐标系并添加到了库中:

js
const GCJ02 = new ol.proj.Projection({
  // 坐标系代码
  code: 'GCJ:02',
  // 坐标系范围
  extent: [-180, -90, 180, 90],
  // 坐标系分辨率使用的单位
  units: 'degrees',
  // 用于确定坐标系的坐标轴方向
  axisOrientation: 'enu',
});
// 添加到openlayers支持的坐标系
ol.proj.addProjection(GCJ02);

若该坐标系也使用EPSG:3857投影,则需要配置他们直接的转换关系:

js
// 定义GCJ02地理坐标系和web墨卡托投影坐标系之间的转换规则
ol.proj.addCoordinateTransforms(
  'GCJ:02',
  'EPSG:3857',
  function (coordinate) {
    // GCJ02火星坐标系转换为投影坐标系是使用经纬度直接转换,只有当前地理坐标系为WGS84时,经纬度可以被直接转换为EPSG:3857。
    // 若当前坐标系使用 GCJ02,算法会将GCJ02先转为WGS84,再转为EPSG:3857,所以这里将GCJ02视为WGS84进行直接转换
    return gcoord.transform(
      coordinate, // 经纬度坐标
      gcoord.WGS84, // 当前坐标系
      gcoord.EPSG3857 // 目标坐标系
    );
  },
  function (coordinate) {
    return gcoord.transform(
      coordinate, // 投影坐标
      gcoord.EPSG3857, // 当前坐标系
      gcoord.WGS84 // 目标坐标系
    );
  }
);

转换工具库:gcoord

之后可以使用gcoord工具转换,也可以使用openlayers自带的方法转换:

js
// 将经纬度坐标从 EPSG:4326 转换为 EPSG:3857 投影坐标系坐标,也可以反向转换
ol.proj.transform([lon, lat], 'EPSG:4326', 'EPSG:3857');

// 将经纬度视为 EPSG:4326 转换到指定坐标系
// 默认转换至 EPSG:3857
ol.proj.fromLonLat([lon, lat]);
// 指定转换至 EPSG:3857
ol.proj.fromLonLat([lon, lat], 'EPSG:3857');

// 将坐标信息转换为经纬度,固定转换至 EPSG:4326
// 默认坐标系 EPSG:3857
ol.proj.toLonLat([lon, lat]);
// 指定坐标系
ol.proj.toLonLat([lon, lat], 'EPSG:3857');

demo

以下demo均为百度地图的demo,初始化地图的代码init-bmap.js如下:

Details
js
/*定义百度地图分辨率与瓦片网格*/
const resolutions = [];
for (var i = 0; i <= 19; i++) {
  resolutions[i] = Math.pow(2, 18 - i);
}

// 创建百度地理坐标系,
const projBD09 = new ol.proj.Projection({
  // 坐标系代码
  code: 'BD:09',
  // 坐标系范围
  extent: [-180, -90, 180, 90],
  // 坐标系分辨率使用的单位
  units: 'degrees',
  // 用于确定坐标系的坐标轴方向
  axisOrientation: 'enu',
});
// 添加到openlayers支持的坐标系
ol.proj.addProjection(projBD09);
// 创建百度投影坐标系
const projBD09Meter = new ol.proj.Projection({
  // 坐标系代码
  code: 'BD:09-Meter',
  // 坐标系范围
  extent: [-20037726.37, -11708041.66, 20037726.37, 12474104.17],
  // 坐标系分辨率使用的单位
  units: 'm',
  // 用于确定坐标系的坐标轴方向
  axisOrientation: 'neu',
});
// 添加到openlayers支持的坐标系
ol.proj.addProjection(projBD09Meter);
// 定义百度投影坐标系和 BD:09 坐标系之间的转换规则
ol.proj.addCoordinateTransforms(
  'BD:09',
  'BD:09-Meter',
  function (coordinate) {
    return gcoord.transform(
      coordinate, // 经纬度坐标
      gcoord.BD09, // 当前坐标系
      gcoord.BD09Meter // 目标坐标系
    );
  },
  function (coordinate) {
    return gcoord.transform(
      coordinate, // 投影坐标
      gcoord.BD09Meter, // 当前坐标系
      gcoord.BD09 // 目标坐标系
    );
  }
);

/*加载百度地图离线瓦片不能用ol.source.XYZ,ol.source.XYZ针对谷歌地图(注意:是谷歌地图)而设计,而百度地图与谷歌地图使用了不同的投影、分辨率和瓦片网格。因此这里使用ol.source.TileImage来自行指定投影、分辨率、瓦片网格。*/
const tileGrid = new ol.tilegrid.TileGrid({
  origin: [0, 0],
  resolutions: resolutions,
});
// 创建百度坐标系切片资源
const bMapSources = new ol.source.TileImage({
  projection: 'BD:09-Meter',
  tileGrid: tileGrid,
  tileUrlFunction: function (tileCoord, pixelRatio, proj) {
    let z = tileCoord[0];
    let x = tileCoord[1];
    let y = -tileCoord[2] - 1;
    if (x < 0) x = 'M' + -x;
    if (y < 0) y = 'M' + -y;
    // return `http://111.12.91.15:28802/map/bmap/${z}/${x}/${y}.png`;
    return `https://maponline3.bdimg.com/tile/?qt=vtile&z=${z}&x=${x}&y=${y}&styles=pl&scaler=1&udt=20230616&from=jsapi3_0`;
  },
});
// 创建切片图层
const bdMapLayers = new ol.layer.Tile({
  source: bMapSources,
});
export default () => {
  const view = new ol.View({
    // 在百度地图jsapi中为了方便开发者,在底层将经纬度自动转换为墨卡托投影坐标,所以可以直接使用经纬度进行定位
    // 在使用openlayers的时候,需要手动对经纬度坐标进行转换,转换为百度的墨卡托投影坐标才能正确显示
    center: ol.proj.transform(
      [108.34171638707808, 22.818581259540537],
      'BD:09',
      'BD:09-Meter'
    ),
    zoom: 10, // 缩放
    maxZoom: 19,
    projection: 'BD:09-Meter',
    // 使用和百度切片资源设置的分辨率
    resolutions: bMapSources.getTileGrid().getResolutions(),
    // 设置缩放级别为整数
    constrainResolution: true,
    // 关闭无极缩放地图
    smoothResolutionConstraint: false,
  });
  return new ol.Map({
    target: 'map',
    layers: [bdMapLayers],
    view: view,
    controls: ol.control.defaults.defaults({
      zoom: true,
      rotate: true,
      attribution: true,
    }),
  });
};

demo-点

Details
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>点</title>
    <link rel="stylesheet" href="./style/ol.css">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #map {
            width: 100vw;
            height: 100vh
        }

        canvas {
            backface-visibility: hidden;
            transform: translate3d(0, 0, 0);
        }
    </style>
</head>

<body>

    <div id="map"></div>
    <script src="https://unpkg.com/gcoord/dist/gcoord.global.prod.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/animejs/3.2.1/anime.min.js"></script>
    <script src="./lib/ol.js"></script>
    <script type="module">
        import initMap from './lib/init-bmap.js';
        const { getVectorContext } = ol.render
        const Feature = ol.Feature
        const Point = ol.geom.Point
        const VectorSource = ol.source.Vector
        const { Tile, Vector } = ol.layer
        const { Style, Circle, Fill, Stroke, Icon } = ol.style
        const { Draw, Select, Modify } = ol.interaction

        const map = initMap()

        // 创建一个矢量资源组
        const makerSource = new VectorSource()

        // 创建一个矢量图层存放点
        const makerLayer = new Vector({
            source: makerSource,
        })
        map.addLayer(makerLayer)

        // 创建点图形特征
        const pointFeature = new Feature({
            geometry: new Point(ol.proj.transform(
                [108.34171638707808, 22.818581259540537],
                "BD:09",
                "BD:09-Meter"
            ))
        });

        // 设置样式
        const fill = new Fill({
            color: 'blue',
        });
        const stroke = new Stroke({
            color: 'skyblue',
            width: 3,
        });
        pointFeature.setStyle(new Style({
            image: new Circle({
                fill: fill,
                stroke: stroke,
                radius: 10,
            }),
            zIndex: 2
        }));

        // 添加到矢量要素资源组
        makerSource.addFeature(pointFeature)

        // 创建可选交互,图形可以被选中
        const selectInteraction = new Select({
            // 禁用多选
            multi: false,
            // 选中样式
            style: new Style({
                image: new Circle({
                    fill: new Fill({
                        color: 'red',
                    }),
                    stroke: new Stroke({
                        color: 'yellow',
                        width: 3,
                    }),
                    radius: 10,
                }),
                zIndex: 2
            })
        })

        // 选中或取消选中事件
        selectInteraction.on('select', (event) => {
            const selectedFeature = event.selected[0]
            if (selectedFeature) {
                // 选中图像坐标点:
                const coordinate = ol.proj.transform(
                    selectedFeature.getGeometry().getCoordinates(),
                    "BD:09-Meter",
                    "BD:09"
                )
                console.log(coordinate)
            } else {
                // 取消选中
            }
        })
        // 将交互添加至地图
        map.addInteraction(selectInteraction)
    </script>
</body>

</html>

demo-多边形样式绘制和编辑删除

Details
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>多边形</title>
    <link rel="stylesheet" href="./style/ol.css">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #map {
            width: 100vw;
            height: 100vh
        }

        .actions {
            position: absolute;
            top: 20px;
            right: 20px;
        }

        .actions button {
            padding: 5px 10px;
            margin-left: 10px;
        }
    </style>
</head>

<body>

    <div id="map"></div>

    <div class="actions">
        <button class="btn-delete" type="button" style="display: none;">删除选中多边形</button>
        <button class="btn-create" type="button">绘制多边形</button>
        <button class="btn-add" type="button">直接添加一个多边形</button>
    </div>
    <script src="https://unpkg.com/gcoord/dist/gcoord.global.prod.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/animejs/3.2.1/anime.min.js"></script>
    <script src="./lib/ol.js"></script>
    <script type="module">
        import initMap from './lib/init-bmap.js';
        const { getVectorContext } = ol.render
        const Feature = ol.Feature
        const Point = ol.geom.Point
        const VectorSource = ol.source.Vector
        const { Tile, Vector } = ol.layer
        const { Style, Circle, Fill, Stroke, Icon } = ol.style
        const { Draw, Select, Modify } = ol.interaction
        const Polygon = ol.geom.Polygon

        const map = initMap()

        // 创建一个矢量资源组
        const vectoSource = new VectorSource()

        // 创建一个矢量图层存放点
        const vectorLayer = new Vector({
            source: vectoSource,
            style: new Style({
                fill: new Fill({
                    color: 'rgba(255, 255, 255, 0.4)'
                }),
                stroke: new Stroke({
                    color: 'skyblue',
                    width: 2
                })
            })
        })
        map.addLayer(vectorLayer)

        // 操作按钮
        const btnDel = document.querySelector('.btn-delete')
        const btnCreate = document.querySelector('.btn-create')
        const btnAdd = document.querySelector('.btn-add')

        // 创建可选交互,图形可以被选中
        let selectedFeature
        const selectInteraction = new Select({
            // 禁用多选
            multi: false
        })
        // 选中时更新当前选中图形
        selectInteraction.on('select', (event) => {
            selectedFeature = event.selected[0]
            if (selectedFeature) {
                btnDel.style.display = 'inline-block'
            } else {
                btnDel.style.display = 'none'
            }
        })
        // 将交互添加至地图
        map.addInteraction(selectInteraction)

        // 创建可编辑交互,
        const modifyInteraction = new Modify({
            // 选中的图形可编辑
            features: selectInteraction.getFeatures()
        })
        // 监听 modifyend 事件,当结束编辑时获取多边形的坐标集合
        modifyInteraction.on('modifyend', function (event) {
            // 获取多边形的边界和内部边界集合
            const feature = event.features.getArray()[0];
            const polygon = feature.getGeometry();
            const coordinates = polygon.getCoordinates()[0].map(item => ol.proj.transform(
                item,
                "BD:09-Meter",
                "BD:09"
            ));
            console.log(coordinates);
        });

        // 将交互添加至地图
        map.addInteraction(modifyInteraction)

        // 开始多边形绘制
        const startDrawPolygon = (polyCoords) => {
            // 选择交互先禁用
            selectInteraction.setActive(false)

            // 定义绘制样式
            const style = new Style({
                fill: new Fill({
                    color: 'rgba(255, 255, 255, 0.4)'
                }),
                stroke: new Stroke({
                    color: '#ffcc33',
                    width: 2
                }),
                image: new Circle({
                    radius: 7,
                    fill: new Fill({
                        color: '#ffcc33'
                    })
                })
            })

            // 创建多边形绘制交互
            const drawInteraction = new Draw({
                source: vectorLayer.getSource(),
                type: 'Polygon',
                // 设置绘制完成后的样式
                style: new Style({
                    fill: new Fill({
                        color: 'rgba(255, 255, 255, 0.4)'
                    }),
                    stroke: new Stroke({
                        color: '#ffcc33',
                        width: 2
                    }),
                    image: new Circle({
                        radius: 7,
                        fill: new Fill({
                            color: '#ffcc33'
                        })
                    })
                })
            })
            // 添加交互至地图
            map.addInteraction(drawInteraction)

            // 绘制完成后事件
            drawInteraction.on('drawend', (event) => {
                // 关闭绘制交互
                drawInteraction.setActive(false)
                map.removeInteraction(drawInteraction)
                // 启用选择交互
                selectInteraction.setActive(true)

                // 获取多边形的坐标集合
                const feature = event.feature;
                const polygon = feature.getGeometry();
                const coordinates = polygon.getCoordinates()[0].map(item => ol.proj.transform(
                    item,
                    "BD:09-Meter",
                    "BD:09"
                ));
                console.log(coordinates);
            })

            if (polyCoords) {
                const polyFeature = new Feature({
                    geometry: new Polygon(polyCoords),
                })
                vectoSource.addFeature(polyFeature);

                // 关闭绘制交互
                drawInteraction.setActive(false)
                map.removeInteraction(drawInteraction)
                // 启用选择交互
                selectInteraction.setActive(true)
            }
        }

        // 删除选中的图形
        const delPolygon = () => {
            vectorLayer.getSource().removeFeature(selectedFeature)
            selectedFeature = null
            btnDel.style.display = 'none'
        }

        btnDel.addEventListener('click', delPolygon)
        btnCreate.addEventListener('click', () => {
            startDrawPolygon()
        })
        btnAdd.addEventListener('click', () => {
            const cordStr = '108.37928810485103,22.814679938509663|108.38124641086213,22.812781095654344|108.38289929299995,22.814346810120874|108.3841748868237,22.816262287101505|108.38455217513777,22.816822540109328|108.38449827680718,22.817611432792752|108.38368980184848,22.818494199816865|108.3827465810633,22.818971164222994|108.38153386862524,22.81899387669899|108.38025827480149,22.818960564964552|108.38006064758936,22.81679755590632|108.37928810485103,22.814679938509663'
            const polyCoords = [
                cordStr.split('|').map(item => item.split(',')).map(item => ol.proj.transform(
                    item,
                    "BD:09",
                    "BD:09-Meter"
                ))
            ]
            startDrawPolygon(polyCoords)
        })
    </script>
</body>

</html>

demo-圆形样式绘制和编辑删除

Details
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>圆形</title>
    <link rel="stylesheet" href="./style/ol.css">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #map {
            width: 100vw;
            height: 100vh
        }

        .actions {
            position: absolute;
            top: 20px;
            right: 20px;
        }

        .actions button {
            padding: 5px 10px;
            margin-left: 10px;
        }
    </style>
</head>

<body>

    <div id="map"></div>

    <div class="actions">
        <button class="btn-delete" type="button" style="display: none;">删除选中圆形</button>
        <button class="btn-create" type="button">绘制圆形</button>
        <button class="btn-add" type="button">直接添加一个圆形</button>
    </div>
    <script src="https://unpkg.com/gcoord/dist/gcoord.global.prod.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/animejs/3.2.1/anime.min.js"></script>
    <script src="./lib/ol.js"></script>
    <script type="module">
        import initMap from './lib/init-bmap.js';
        const { getVectorContext } = ol.render
        const Feature = ol.Feature
        const Point = ol.geom.Point
        const VectorSource = ol.source.Vector
        const { Tile, Vector } = ol.layer
        const { Style, Circle, Fill, Stroke, Icon } = ol.style
        const { Draw, Select, Modify } = ol.interaction
        const CircleShape = ol.geom.Circle

        const map = initMap()

        // 创建一个矢量资源组
        const vectoSource = new VectorSource()

        // 创建一个矢量图层存放点
        const vectorLayer = new Vector({
            source: vectoSource,
            style: new Style({
                fill: new Fill({
                    color: 'rgba(255, 255, 255, 0.4)'
                }),
                stroke: new Stroke({
                    color: 'skyblue',
                    width: 2
                })
            })
        })
        map.addLayer(vectorLayer)

        // 操作按钮
        const btnDel = document.querySelector('.btn-delete')
        const btnCreate = document.querySelector('.btn-create')
        const btnAdd = document.querySelector('.btn-add')

        // 创建可选交互,图形可以被选中
        let selectedFeature
        const selectInteraction = new Select({
            // 禁用多选
            multi: false
        })
        // 选中时更新当前选中图形
        selectInteraction.on('select', (event) => {
            selectedFeature = event.selected[0]
            if (selectedFeature) {
                btnDel.style.display = 'inline-block'
            } else {
                btnDel.style.display = 'none'
            }
        })
        // 将交互添加至地图
        map.addInteraction(selectInteraction)

        // 创建可编辑交互,
        const modifyInteraction = new Modify({
            // 选中的图形可编辑
            features: selectInteraction.getFeatures()
        })
        // 监听 modifyend 事件,当结束编辑时获取多边形的坐标集合
        modifyInteraction.on('modifyend', function (event) {
            const feature = event.features.getArray()[0];
            const circle = feature.getGeometry();

            // 获取圆形的圆心坐标和半径
            const center = ol.proj.transform(
                circle.getCenter(),
                "BD:09-Meter",
                "BD:09"
            );
            const radius = circle.getRadius();

            // 输出圆形的圆心坐标和半径
            console.log(center, radius);
        });

        // 将交互添加至地图
        map.addInteraction(modifyInteraction)

        // 开始多边形绘制
        const startDrawPolygon = (coords) => {
            // 选择交互先禁用
            selectInteraction.setActive(false)

            // 创建多边形绘制交互
            const drawInteraction = new Draw({
                source: vectorLayer.getSource(),
                type: 'Circle',
                // 设置绘制完成后的样式
                style: new Style({
                    fill: new Fill({
                        color: 'rgba(255, 255, 255, 0.4)'
                    }),
                    stroke: new Stroke({
                        color: '#ffcc33',
                        width: 2
                    }),
                    image: new Circle({
                        radius: 7,
                        fill: new Fill({
                            color: '#ffcc33'
                        })
                    })
                })
            })
            // 添加交互至地图
            map.addInteraction(drawInteraction)

            // 绘制完成后事件
            drawInteraction.on('drawend', (event) => {
                // 关闭绘制交互
                drawInteraction.setActive(false)
                map.removeInteraction(drawInteraction)
                // 启用选择交互
                selectInteraction.setActive(true)

                const feature = event.feature;
                const circle = feature.getGeometry();

                // 获取圆形的圆心坐标和半径
                const center = ol.proj.transform(
                    circle.getCenter(),
                    "BD:09-Meter",
                    "BD:09"
                );
                const radius = circle.getRadius();

                // 输出圆形的圆心坐标和半径
                console.log(center, radius);
            })

            if (coords) {
                const circleFeature = new Feature({
                    geometry: new CircleShape(coords.center, coords.radius),
                })
                vectoSource.addFeature(circleFeature);

                // 关闭绘制交互
                drawInteraction.setActive(false)
                map.removeInteraction(drawInteraction)
                // 启用选择交互
                selectInteraction.setActive(true)
            }
        }

        // 删除选中的图形
        const delPolygon = () => {
            vectorLayer.getSource().removeFeature(selectedFeature)
            selectedFeature = null
            btnDel.style.display = 'none'
        }

        btnDel.addEventListener('click', delPolygon)
        btnCreate.addEventListener('click', () => {
            startDrawPolygon()
        })
        btnAdd.addEventListener('click', () => {
            const circle = {
                center: ol.proj.transform(
                    [
                        108.457123841618,
                        23.167687666375514
                    ],
                    "BD:09",
                    "BD:09-Meter"
                ),
                radius: 5000
            }
            startDrawPolygon(circle)
        })
    </script>
</body>

</html>

demo-geojson

Details
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>绘制行政区域</title>
    <link rel="stylesheet" href="./style/ol.css">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #map {
            width: 100vw;
            height: 100vh
        }

        .actions {
            position: absolute;
            top: 20px;
            right: 20px;
        }

        .actions button {
            padding: 5px 10px;
            margin-left: 10px;
        }
    </style>
</head>

<body>

    <div id="map"></div>

    <div class="actions">
        <select class="select-address">
            <option value="" selected>请选择区域</option>
            <option value="450000000000" data-center="[108.358224, 23.404248]" data-level="2">广西</option>
            <option value="450100000000" data-center="[108.320004, 22.82402]" data-level="3">南宁</option>
            <option value="450200000000" data-center="[109.411703, 24.314617]" data-level="3">柳州</option>
        </select>
        <button class="btn-remove" type="button">清除</button>
    </div>
    <script src="https://unpkg.com/gcoord/dist/gcoord.global.prod.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/animejs/3.2.1/anime.min.js"></script>
    <script src="./lib/ol.js"></script>
    <script type="module">
        import initMap from './lib/init-bmap.js';
        const { getVectorContext } = ol.render
        const Feature = ol.Feature
        const Point = ol.geom.Point
        const VectorSource = ol.source.Vector
        const { Tile, Vector } = ol.layer
        const { Style, Circle, Fill, Stroke, Icon } = ol.style
        const GeoJSON = ol.format.GeoJSON

        const map = initMap()

        // 创建一个矢量资源组
        const vectorSource = new VectorSource()

        // 创建一个矢量图层存放点
        const vectorLayer = new Vector({
            source: vectorSource,
        })
        map.addLayer(vectorLayer)

        let areaFeatures
        const levelMap = {
            1: 5,
            2: 8,
            3: 9
        }
        const clearArea = () => {
            if (areaFeatures) {
                areaFeatures.forEach((feature) => {
                    vectorSource.removeFeature(feature)
                })
                areaFeatures.length = 0
            }
        }
        const drawArea = (code, center, level) => {
            fetch(`http://111.12.91.15:28802/map/geojson/${code}.json`, {
                headers: {
                    'Content-Type': 'application/json;charset=utf-8'
                },
                method: 'GET'
            }).then(res => {
                res.json().then(data => {
                    // 清除原边界
                    clearArea()
                    // 生成要素
                    const geojsonFormat = new GeoJSON();
                    data.features.forEach(function (feature) {
                        const coordinates = feature.geometry.coordinates;
                        const transformedCoordinates = coordinates.map(coordinateList => {
                            return coordinateList.map((coordinate) => {
                                return coordinate.map(item => {
                                    return ol.proj.transform(
                                        item,
                                        "BD:09",
                                        "BD:09-Meter"
                                    )
                                })
                            })
                        });
                        feature.geometry.coordinates = transformedCoordinates;
                    });
                    areaFeatures = geojsonFormat.readFeatures(data);
                    // 添加到资源组
                    vectorSource.addFeatures(areaFeatures);

                    // 设置中心点
                    const mapView = map.getView()
                    mapView.animate({ zoom: levelMap[level] }, { center });
                })
            })
        }
        document.querySelector('.btn-remove').addEventListener('click', clearArea)
        document.querySelector('.select-address').addEventListener('change', event => {
            const areaCode = event.target.value
            if (areaCode) {
                const options = Array.from(event.target.querySelectorAll('option'))
                const current = options.find(option => option.value === areaCode)
                drawArea(areaCode, ol.proj.transform(
                    JSON.parse(current.dataset.center),
                    "BD:09",
                    "BD:09-Meter"
                ), current.dataset.level)
            }
        })
    </script>
</body>

</html>

demo-窗口内地图范围坐标

Details
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>获取窗口内地图范围坐标</title>
    <link rel="stylesheet" href="./style/ol.css">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #map {
            width: 100vw;
            height: 100vh
        }

        .actions {
            position: absolute;
            top: 20px;
            right: 20px;
        }

        .actions button {
            padding: 5px 10px;
            margin-left: 10px;
        }
    </style>
</head>

<body>

    <div id="map"></div>
    <div class="actions">
        <button class="btn-compute" type="button">查询当前视口坐标</button>
    </div>
    <script src="https://unpkg.com/gcoord/dist/gcoord.global.prod.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/animejs/3.2.1/anime.min.js"></script>
    <script src="./lib/ol.js"></script>
    <script type="module">
        import initMap from './lib/init-bmap.js';
        const { getVectorContext } = ol.render
        const Feature = ol.Feature
        const Point = ol.geom.Point
        const VectorSource = ol.source.Vector
        const { Tile, Vector } = ol.layer
        const { Style, Circle, Fill, Stroke, Icon } = ol.style

        const map = initMap()

        document.querySelector('.btn-compute').addEventListener('click', () => {
            const extent = map.getView().calculateExtent(map.getSize());
            const formatExtent = [
                ...ol.proj.transform(
                    [extent[0], extent[1]],
                    "BD:09-Meter",
                    "BD:09"
                ),
                ...ol.proj.transform(
                    [extent[2], extent[3]],
                    "BD:09-Meter",
                    "BD:09"
                )
            ]
            alert(`[minx, miny, maxx, maxy]:${formatExtent}`)
        })
    </script>
</body>

</html>

demo-比例尺功能、平移缩放控件

Details
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>比例尺功能、平移缩放控件</title>
    <link rel="stylesheet" href="./style/ol.css">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #map {
            width: 100vw;
            height: 100vh
        }
    </style>
</head>

<body>

    <div id="map"></div>
    <script src="https://unpkg.com/gcoord/dist/gcoord.global.prod.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/animejs/3.2.1/anime.min.js"></script>
    <script src="./lib/ol.js"></script>
    <script type="module">
        import initMap from './lib/init-bmap.js';
        const { getVectorContext } = ol.render
        const Feature = ol.Feature
        const Point = ol.geom.Point
        const VectorSource = ol.source.Vector
        const { Tile, Vector } = ol.layer
        const { Style, Circle, Fill, Stroke, Icon } = ol.style
        const ScaleLine = ol.control.ScaleLine

        const map = initMap()

        // 创建一个 ScaleLine 控件实例
        const scaleLineControl = new ScaleLine({
            // 设置比例尺单位为度量制(metric)
            units: 'metric'
        });

        // 添加 ScaleLine 控件到地图中
        map.addControl(scaleLineControl);
    </script>
</body>

</html>

demo-图标和动画

Details
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图标和动画</title>
    <link rel="stylesheet" href="./style/ol.css">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #map {
            width: 100vw;
            height: 100vh
        }
    </style>
</head>

<body>

    <div id="map"></div>
    <script src="https://unpkg.com/gcoord/dist/gcoord.global.prod.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/animejs/3.2.1/anime.min.js"></script>
    <script src="./lib/ol.js"></script>
    <script type="module">
        import initMap from './lib/init-bmap.js';
        const { getVectorContext } = ol.render
        const Feature = ol.Feature
        const Point = ol.geom.Point
        const VectorSource = ol.source.Vector
        const { Tile, Vector } = ol.layer
        const { Style, Circle, Fill, Stroke, Icon } = ol.style

        const map = initMap()

        // 创建一个矢量资源组
        const makerSource = new VectorSource()

        // 创建一个矢量图层存放点
        const makerLayer = new Vector({
            source: makerSource,
        })
        map.addLayer(makerLayer)

        // 创建点图形特征
        const pointFeature = new Feature({
            geometry: new Point(ol.proj.transform(
                [108.34171638707808, 22.818581259540537],
                "BD:09",
                "BD:09-Meter"
            ))
        });
        // 创建图标
        const icon = new Icon({
            anchor: [0.5, 1],
            src: '../../images/location.png',
            width: 40,
            height: 40,
        })
        // 把图标添加到图形特征样式
        pointFeature.setStyle(new Style({
            image: icon,
            zIndex: 2,
        }));
        // 添加到矢量要素资源组
        makerSource.addFeature(pointFeature)

        // 添加动画
        const current = {
            value: 0
        }
        anime({
            targets: current,
            value: 1,
            direction: 'alternate',
            loop: true,
            duration: 500,
            easing: function (el, i, total) {
                return function (t) {
                    return Math.pow(Math.sin(t * (i + 1)), total);
                }
            },
            update: function () {
                const next = [0, 20 * current.value]
                // 更新图标的偏移量
                icon.setDisplacement(next)
                // 触发更新
                pointFeature.changed()
            }
        });
    </script>
</body>

</html>

demo-自定义覆盖物,显示html结构

Details
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>点</title>
    <link rel="stylesheet" href="./style/ol.css">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #map {
            width: 100vw;
            height: 100vh
        }

        .info-window {
            background-color: #fff;
            padding: 20px;
            border-radius: 20px;
            box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.2);
            color: plum;
            font-size: 20px;
        }
    </style>
</head>

<body>

    <div id="map"></div>
    <script src="https://unpkg.com/gcoord/dist/gcoord.global.prod.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/animejs/3.2.1/anime.min.js"></script>
    <script src="./lib/ol.js"></script>
    <script type="module">
        import initMap from './lib/init-bmap.js';
        const { getVectorContext } = ol.render
        const Feature = ol.Feature
        const Point = ol.geom.Point
        const VectorSource = ol.source.Vector
        const { Tile, Vector } = ol.layer
        const { Style, Circle, Fill, Stroke, Icon } = ol.style
        const Overlayer = ol.Overlay

        const map = initMap()

        const element = document.createElement('div')
        element.innerHTML = `<h1>hello world</h1><button type="button" class="btn-close" >点击关闭</button>`

        const infoWindow = new Overlayer({
            id: 'infoWindow',
            element,
            position: ol.proj.transform(
                [108.358224, 22.904248],
                "BD:09",
                "BD:09-Meter"
            ),
            // 坐标为该窗口的左下角
            positioning: 'bottom-left',
            autoPan: true,
            className: 'info-window'
        })

        map.addOverlay(infoWindow)
        document.querySelector('.btn-close').addEventListener('click', () => {
            map.removeOverlay(infoWindow)
        })
    </script>
</body>

</html>