手写一个地图引擎(三)

605 阅读3分钟

地图拖动实现原理相对比较简单,就是地图面板容器随着鼠标移动的过程;地图图层及其他要素监听移地图动事件,进行更新状态操作。例如地图移动过程中瓦片图层需要加载当前视图范围未加载的瓦片,需要销毁超出当前范围的瓦片。

1 Event事件类设计

Event事件类通常会使用观察者设计模式,观察者模式定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。这种设计模式应用非常广泛,大家熟悉的Vue、leaflet、tomcat、Netty等等都有观察者模式的身影。js-event-bus是javascript开源的事件总线,大家可以拿来直接用,本文给大家简单写一个。

//事件类

class Event{
    constructor() {
        this.listener = [];
    }

    //注册事件
    on(type, fn, ctx) {
        this.listener.push({
            type,
            fn,
            ctx
        });
    }

    //触发事件
    fire(type, object) {
        this.listener.filter(item => {
            return item.type === type;
        }).forEach(item => {
            let {type, fn, ctx} = item;
            ctx = ctx || this;
            fn && fn.call(ctx, object);
        });
    }
}

2 让地图类、图层类继承Event

使地图和图层等实例具有发布、订阅事件的能力,之后的几乎所有功能都离不开事件。鼠标拖动地图是触发move事件,瓦片、marker等图层及其他叠加物监听地图move事件,做出相应的更新。

//地图类
class Map extends Event{
  ...
}

//图层类
class Layer extends Event{
  ...  
}

3 MouseHandler类,处理地图容器鼠标事件

在地图初始化的时候创建该实例,mouseHandler监听地图面板容器的鼠标事件,封装实现鼠标拖动、鼠标滚轮缩放等事件。

//处理鼠标事件类

class MouseHandler extends Event{

    constructor(el) {
        super();
        this.el = el;
        this.lastX = void 0;
        this.lastY = void 0;
    }

    isPc() {
        let userAgentInfo = navigator.userAgent;
        let Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPod", "iPad"];
        let flag = true;
        for (let i = 0; i < Agents.length; i++) {
            if (userAgentInfo.indexOf(Agents[i]) > 0) {
                flag = false;
                break;
            }
        }
        return flag;
    }

    handlerDrag() {
        let el = this.el;
        let me = this;
        if(this.isPc()) {
            el.onmousedown = function(event) {
                me.fire("movestart", event);
                el.onmousemove =  function(event1) {
                    me.fire("move", event1);
                    event1.preventDefault();
                }
            }

            el.onmouseup = function(event) {
                el.onmousemove =  function(event1) {
                    return false;
                }
                me.fire("moveend", event);
            }
        }else {
            el.ontouchstart = function(event) {
                let thevent = event.touches[0];
                me.lastX = thevent.pageX;
                me.lastY = thevent.pageY;
                me.fire("movestart", thevent);
                el.ontouchmove =  function(event1) {
                    let thevent1 = event1.touches[0];
                    thevent1.movementX = thevent1.pageX - me.lastX;
                    thevent1.movementY = thevent1.pageY - me.lastY;
                    me.lastX = thevent1.pageX;
                    me.lastY = thevent1.pageY;
                    me.fire("move", thevent1);
                    event1.preventDefault();
                }
            }

            el.ontouchend = function(event) {
                el.ontouchmove =  function(event1) {
                    return false;
                }
                me.lastX = void 0;
                me.lastY = void 0;
                me.fire("moveend", event);
            }
        }
        return me;
    }
    
    handlerWheel() {
        let el = this.el;
        let me = this;
        el.onwheel = function(event) {
            me.fire("wheel", event);
        }
        return me;
    }
}

4 实现地图移动方法move(event)

class Map extends Event{
     //…
     move(event) {//地图移动方法
        let {movementX, movementY} = event;
        let {zoom, center, resolutions, project} = this;
        //获取地图面板位置
        let offset = this.getMapPos();
        //计算并设定地图移动后位置
        let offsetPoint = new Point(offset[0] + movementX, offset[1] + movementY).round();
        this.mapPane.style["transform"] = `translate(${offsetPoint.x}px, ${offsetPoint.y}px)`;
        //更新地图中心点经纬度 
        let resolution = resolutions[zoom];
        let centerMeter = project.project(center);
        let lastCenterMeter = centerMeter.add(-movementX * resolution, movementY * resolution);
        this.center = project.unproject(lastCenterMeter);
        //触发地图移动事件 
        this.fire("move", event);
     }
}

5 Tile类监听地图move事件,更新瓦片

class Tile extends Layer{
     onAdd(map) {
        this.map = map;
        this.tiles = [];
        this.render();
        this.map.on("move", this.onUpdate, this);
     }
     
     onUpdate() {
       //更新瓦片方法总体思路是
       //1 加载地图移动后需要加载的瓦片,
       //2 移除超出地图范围的瓦片,
       //这部分代码在历史文章中已经写过了,
       //这里只做了一些修改,如需要请到github上自行查看。
        //Github地址在本文底部。
     }
}

6 初始化地图

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);

7 效果图

http://180.76.171.45:8080/gisdaily/page/pan.html

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