OpenLayers地图基础功能
前言
接触到openlayers地图的起因是,一些客户设备性能差,使用高德地图非常卡,完全拖不动,又不接受聚合的呈现。试过降低高德地图的版本,2.0.0和1.4.15都用过,对于车辆多轨迹点多的情况还是会很卡,体验感不好。最后讨论决定换成openlayers地图,流畅度体验感确实好很多。当看到openlayers的英文文档内心是崩溃的,但是还好写着写着感觉很顺手,结构也比较清晰。 这里分享一下用到的openlayers的基础功能。
OpenLayers 是一个开源的 JavaScript 地图库,用于在网页中展示交互式地图。它支持多种地图源(如Google Maps、Bing Maps、OSM等)和地理数据格式(如GeoJSON、WMS、WFS等),适用于构建专业的GIS(地理信息系统)应用。
1、安装
sh
npm i ol -S
2、openlayers组成
📦 Map 地图实例 └── 🧩 Layers 图层(瓦片图层TileLayer-用于地图底图、矢量图层VectorLayer-用于绘制点线面) └── 🧮 Source 数据源(OSM、Vector、XYZ 等) └── 📍 Feature 地图要素(点、线、面) └── 🔷 Geometry 几何对象(Point, LineString, Polygon) └── 🎨 Style 样式系统(Style、Stroke、Fill、Text、Icon) └── 🧭 View 地图视图(中心、缩放) └── 🧰 Controls 控件(缩放、比例尺、全屏等) └── 🤏 Interactions 交互(拖动、绘制、选择)
3、创建地图
(1)这里封装成一个通用方法,方便创建多个实例
javascript
/**
* 创建 OpenLayers 地图实例,图层独立,避免相互影响
* @param mapID 地图挂载的容器ID
* @param options 视图参数配置
*/
export async function createOlMap(mapID: string, options = {}) {
try {
const map = new Map({
target: mapID,
view: new View({
center: fromLonLat(defaultMapCenter), //中心点坐标
zoom: defaultMapZoom, //默认10
minZoom: 4,
maxZoom: 18,
projection: 'EPSG:3857',
...options,
}),
layers: [standardLayer, satelliteLayer, trafficLayer, satelliteLabelLayer], //图层数组
});
// 添加比例尺控件
const scaleLineControl = new ScaleLine({
units: 'metric',
className: 'custom-scale-line',
});
map.addControl(scaleLineControl);
return map;
} catch (err) {
return Promise.reject('init map error: ' + err);
}
}
(2)使用高德地图的瓦片图层 · 路况图层需要叠在标准图层上使用 · 卫星标注图层要和卫星影像图层一起使用,才会有地点信息
javascript
/**工厂函数 - 创建高德标准地图图层 */
function createStandardLayer() {
return new TileLayer({
source: new XYZ({
url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&z={z}&x={x}&y={y}',
crossOrigin: 'anonymous',
}),
visible: true, //控制图层是否展示
});
}
/**工厂函数 - 创建高德卫星影像图层 */
function createSatelliteLayer() {
return new TileLayer({
source: new XYZ({
url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
crossOrigin: 'anonymous',
}),
visible: false,
});
}
/**工厂函数 - 创建高德卫星标注图层 */
function createSatelliteLabelLayer() {
return new TileLayer({
source: new XYZ({
url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}',
crossOrigin: 'anonymous',
}),
visible: false,
});
}
/**工厂函数 - 创建高德实时路况图层 */
function createTrafficLayer() {
return new TileLayer({
source: new XYZ({
url: `http://tm.amap.com/trafficengine/mapabc/traffictile?v=1.0&=&=&t=1&t=${Date.now()}&x={x}&y={y}&z={z}`,
tileSize: 256,
}),
visible: false,
});
}
4、给地图添加标记点
有两种方法,一种是使用dom元素标记点用Overlay实现,另一种是矢量图形用Feature,视情况而定。
(1)使用overlay实现(dom元素) 在 OpenLayers 中,Overlay(覆盖物) 是一种将 HTML/DOM 元素 绑定到地图特定坐标位置的机制。它不参与地图的实际渲染,而是独立浮动在地图容器之上,适合实现弹窗、自定义标记、测量工具提示等需要复杂 UI 或精准屏幕定位的场景。 以给地图添加车辆标记点为例:
javascript
const addMarkers = (truck)=>{
const dom = getMarkerContentDOM(getMarkerContent(truck));
// 创建overlay
const overlay = new Overlay({
element: dom,
positioning: 'center-center',
position: truck.position,
stopEvent: false
});
mapInstance.value!.addOverlay(overlay);
}
(2)使用Feature实现
javascript
const addMarker = (item: any)=>{
const icon = getMarkIcon(item);
const marker = new Feature({
geometry: new Point(fromLonLat(item.position)), //fromLonLat是用来转坐标系的,不一定需要
})
marker.setStyle(new Style({
image: new Icon({
anchor: [0.5, 0.5], // 图标锚点在底部中间
src: icon, // 图标地址
scale: getMarkScale(item), // 可调整缩放
rotation: item.Part=== 1 ? 0 : (item.Direction || 0) * Math.PI / 180 // 角度转弧度
})
}))
const markerLayer = new VectorLayer({
source: new VectorSource({
features:[marker]
}),
zIndex: 200
});
mapInstance.addLayer(markerLayer);
}
5、给地图添加路线
openlayers地图的架构感觉比较清晰,就是点线面,往上叠图层就行
javascript
const renderRoutes = ()=>{
if(!trackPath.value?.length) return;
if(routesLayer){
routesLayer?.getSource()?.clear();
}
const feature = new Feature({
geometry: new LineString(trackPath.value as any)
})
feature.setStyle(new Style({
stroke: new Stroke({
color: "#335CFF",
width: 3,
})
}))
routesLayer = new VectorLayer({
source: new VectorSource({
features:[feature]
}),
zIndex: 100
})
mapInstance.addLayer(routesLayer);
routesExtent.value = routesLayer.getSource()?.getExtent();
// 调整视图范围
mapInstance.getView().fit(routesExtent.value, { padding: [50, 50, 50, 50] });
}
6、绘制多边形图形
javascript
const startDrawingPolygon = ()=>{
// 确保清除之前的绘制交互
cleanupDrawing();
const source = polygonLayers.get(polygonType.efence)!.getSource();
drawInteraction.value = new Draw({
source: source,
type: 'Polygon',
style: new Style({
fill: new Fill({
color: '#f1870b40' // 对应 fillOpacity: 0.1
}),
stroke: new Stroke({
color: efenceColor, // 对应 strokeColor
width: 3, // 对应 strokeWeight
lineDash: [10, 5] // 虚线效果,对应 strokeStyle: "dashed"
})
})
})
// 添加绘制完成回调
drawInteraction.value.on('drawend', (event) => {
drawFeature = event.feature;
drawFeature.setStyle(new Style({
fill: new Fill({ color: efenceColor + 'CC' }), // 40表示透明度
stroke: new Stroke({
color: efenceColor,
width: 2
}),
text: new Text({
font: '14px Calibri,sans-serif',
stroke: new Stroke({
color: '#fff',
width: 3
})
})
}))
const geometry = drawFeature.getGeometry() as OlPolygon;
// 获取坐标(EPSG:3857),如需转为经纬度需逆转投影
const coordinates = geometry.getCoordinates()[0]; // [0] 是外环
// 转换为经纬度坐标
const lngLatCoords = coordinates.map(coord => {
const [lon, lat] = toLonLat(coord);
return {
Latitude: Math.round(lat * 1_000_000),
Longitude: Math.round(lon * 1_000_000)
};
});
});
mapInstance.value?.addInteraction(drawInteraction.value as Draw);
}
7、实现动态轨迹回放
静态轨迹回放,就是标记点+路线,这里只展示动态轨迹回放写法
javascript
let timer:any;
let lineLayer: VectorLayer | null = null;
let index = 0; //全局播放索引
/** 动态回放 */
const dynamicPlayback = (trigger?: string)=>{
if(!trackPath.value?.length) return;
if(!lineLayer){
lineLayer = new VectorLayer({
source: new VectorSource(),
zIndex: 100
})
mapInstance.addLayer(lineLayer);
}
if (!markerLayer) {
markerLayer = new VectorLayer({
source: new VectorSource(),
zIndex: 200
});
mapInstance.addLayer(markerLayer);
}
//获取轨迹中心点范围
mapInstance.getView().fit(routesExtent.value, { padding: [50, 50, 50, 50] });
timer = setInterval(()=>{
if (index >= locationData.value!.length) return clearInterval(timer);
const item = locationData.value[index]
const coord = fromLonLat(locationData.value[index].position);
const { startFeature, endFeature } = getStartEndMarker(trackPath.value);
// 添加起点
if(index === 0 ){
markerLayer?.getSource()?.addFeature(startFeature);
}
// 添加线
if(index > 0){
const prevCoord = fromLonLat(locationData.value[index-1].position);
const lineFeature = new Feature({
geometry: new LineString([prevCoord,coord])
})
lineFeature.setStyle(new Style({
stroke: new Stroke({
color: "#335CFF",
width: 3,
})
}))
lineLayer?.getSource()?.addFeature(lineFeature);
}
// 添加轨迹点
const icon = getMarkIcon(item);
const marker = new Feature({
geometry: new Point(fromLonLat(item.position)),
data: item // 存储原始数据以便交互
})
marker.setStyle(new Style({
image: new Icon({
anchor: [0.5, 0.5], // 图标锚点在底部中间
src: icon, // 图标地址
scale: getMarkScale(item), // 可调整缩放
rotation: item.Part=== 1 ? 0 : (item.Direction || 0) * Math.PI / 180 // 角度转弧度
})
}))
markerLayer?.getSource()?.addFeature(marker);
// 添加终点
if(index === locationData.value!.length - 1){
markerLayer?.getSource()?.addFeature(endFeature);
}
index++;
},100)
}
/**暂停 */
function pause() {
isPaused.value = true;
if (timer) {
clearInterval(timer);
timer = null;
}
}
/**继续 */
function resume() {
isPaused.value = false;
dynamicPlayback();
}
8、坐标系转换
地球坐标系和火星坐标系互相转换
javascript
//wgs84 to gcj02 地球坐标系 转 火星坐标系
export function wgs84_to_gcj02(lng, lat) {
var dlat = transformlat(lng - 105.0, lat - 35.0);
var dlng = transformlng(lng - 105.0, lat - 35.0);
var radlat = (lat / 180.0) * PI;
var magic = Math.sin(radlat);
magic = 1 - ee * magic * magic;
var sqrtmagic = Math.sqrt(magic);
dlat =
(dlat * 180.0) /
(((a * (1 - ee)) / (magic * sqrtmagic)) * PI);
dlng =
(dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI);
var mglat = lat + dlat;
var mglng = lng + dlng;
return [mglng, mglat];
}
/**
* GCJ-02 转 WGS84(火星坐标系 → 地球坐标系)
* @param {number} gcjLng - 火星经度
* @param {number} gcjLat - 火星纬度
* @returns {[number, number]} WGS84坐标 [经度, 纬度]
*/
export function gcj02_to_wgs84(lng, lat) {
const originalLngSign = Math.sign(lng);
const originalLatSign = Math.sign(lat);
lat = Math.abs(lat);
lng = Math.abs(lng);
let dlat = transformlat(lng - 105.0, lat - 35.0)
let dlng = transformlng(lng - 105.0, lat - 35.0)
let radlat = lat / 180.0 * PI
let magic = Math.sin(radlat)
magic = 1 - ee * magic * magic
let sqrtmagic = Math.sqrt(magic)
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI)
dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI)
let mglat = lat + dlat
let mglng = lng + dlng
let lngs = lng * 2 - mglng
let lats = lat * 2 - mglat
let finalLng = originalLngSign * lngs;
let finalLat = originalLatSign * lats;
return [finalLng, finalLat];
}
9、其他
还有一些功能,比如算轨迹的总里程、POI地点搜索、两点间路线规划等我是借助高德的api结合openlayers地图实现的。