手写一个地图引擎(二)

854 阅读2分钟

上篇文章完成了地图核心类设计、编码和加载栅格瓦片的功能开发。今天和大家一起研究下Marker是如何实现的;Marker是地图最基本的要素,是由图标、文字及其他html片段构成的,通常用来表示一些poi点,相信大家都不陌生,Marker的用途不再详细介绍;本文是从API底层实现的角度剖析Marker。由于本文依赖往期大部分核心代码,建议先阅读往期相关文章。

1 经纬度转换屏幕坐标函数

经纬度转换屏幕坐标函数是地图引擎的核心,无论是二维还是三维地图,需要显示在计算机屏幕上就是地理坐标或者投影坐标转换到屏幕坐标的过程,针对二维地图有两种情况,一种是经纬度直投坐标系,比如WGS84,是直接将经纬度转换为屏幕坐标;另一种是投影坐标系,如WEB墨卡托投影坐标系,需要将经纬度按照投影算法投影到平面坐标系,然后再变换到屏幕坐标系。下边例子就是经纬度转WEB墨卡托投影后变换屏幕坐标的函数。

//地图类

class Map { 
    constructor(options) {}
    setCenter(centerLonLat, zoom) {}
    addTileLayer(options) {}
    addLayer(layer) {}
    /** 经纬度转换屏幕坐标*/
    lonlat2pixls (lonlat) {
        let {center, zoom, project, resolutions, size} = this;
        let projectCenter = project.project(center),
            projectLonlat = project.project(lonlat),
            resolution = resolutions[zoom];
        //1 计算目标点和地图中心点地图单位(默认米)的差值
        let dMeter = projectLonlat.sub(projectCenter.x, projectCenter.y);
        //2 计算目标点和地图中心点像素差值
        let dPixel = dMeter.divide(resolution, -resolution);
        //3 计算地图中心点屏幕坐标
        let centerPixel = size.divide(2, 2);
        //4 计算目标经纬度屏幕坐标
        let pixel = dPixel.add(centerPixel.x, centerPixel.y);
        return pixel;
    }
}

2 Layer,MarkerLayer,Marker类设计

Layer是地图叠加物的基类,主要有onAdd,onUpdate方法,分别是当被添加到地图时调用,和需要更新时调用,用于创建、更新叠加物状态;MarkerLayer继承Layer,用于管理Marker;Marker同样继承Layer,代表具体的点要素。

//图层类

class Layer{
    constructor(options) {
        this.options = options;
        this.id = Utiil.createId();
        this.map = null;
    }

    onAdd(map) {
        //Overwrite
    }

    onUpdate(map) {
        //Overwrite
    }
}

//Marker图层,用于管理Marker

class MarkerLayer extends Layer{
    constructor(options) {
        options = Object.assign({zIndex: 100}, options);
        super(options);
        this.type = "markerLayer";
        let pane = this.pane = document.createElement("div");
        pane.className = "map-marker-pane";
        pane.style["z-index"] = this.options.zIndex;
        this.markers = options.markers.map(m => {
            return new Marker(m);
        }) || [];

    }

    onAdd(map) {
        this.map = map;
        map.mapPane.appendChild(this.pane);
        if(Array.isArray(this.markers)) {
            this.markers.forEach(m => {
                m.onAdd(map, this);
            });
        }
    }
}

//Marker类,多数情况下还会有个Icon类,本着代码简洁,阐述原理的目的省略了Icon类

class Marker extends Layer{
    constructor(options) {
        super(options);
        this.lonlat = options.lonlat;
        this.iconUrl = options.iconUrl;
        this.iconWidth = options.iconWidth;
        this.iconHeight = options.iconHeight;
        this.offset = options.offset || [0, 0];
    }

    onAdd(map, markerLayer) {
        this.map = map;
        this.markerLayer = markerLayer;
        this.init();
    }

    init() {
        this.markerBody = document.createElement("div");
        this.markerBody.className = "map-marker";
        let img = new Image();
        img.width = this.iconWidth;
        img.height = this.iconHeight;
        img.src = this.iconUrl;
        this.markerBody.appendChild(img);
        this.markerLayer.pane.appendChild(this.markerBody);
        this.render();
    }

    render() {
        let {map, markerBody, lonlat, iconWidth, iconHeight, offset} = this;
        let pixel = map.lonlat2pixls(lonlat);
        let pos = pixel.sub(iconWidth / 2, iconHeight / 2).add(offset[0], offset[1]);
        markerBody.style.left = pos.x + "px";
        markerBody.style.top = pos.y + "px";
    }
}

3 初始化和调用

let map = new Map({
    center: new LonLat(
        116.3,
        39.85
    ),
    zoom: 11
});
map.addTileLayer({
    id: "tile",
    url: "https://c.tile.openstreetmap.org/{z}/{x}/{y}.png"
});

let arr = [    [116.42,39.93], [116.37,39.92], [116.43,39.88], [116.35,39.87],
    [116.43,39.92], [116.28,39.85], [116.22,39.9],  [116.3,39.95],
    [116.1,39.93],  [116.13,39.75], [116.65,39.92], [116.65,40.13],
    [116.23,40.22], [116.33,39.73], [116.63,40.32], [117.12,40.13],
    [116.83,40.37], [115.97,40.45]
];
let markers = [];
arr.forEach(i => {
    let marker =  {
        lonlat: new LonLat(i[0], i[1]),
        iconWidth: 40,
        iconHeight: 40,
        offset:[0, -20],
        iconUrl: '../img/marker.png'
    };
    markers.push(marker);
})
let markerLayer = new MarkerLayer({
    markers: markers
});
map.addLayer(markerLayer);

4 效果图

demo:http://180.76.171.45:8080/gisdaily/page/marker.html

欢迎大家关注我的技术公众号

文艺公众号