1

这次以前做的一个项目又双叒叕加辣新需求

自然而然又有了新问题



首先如下图所示,需要两个地图

image.png

左侧的是子组件中的地图,右侧的是父组件中的地图,子组件中传入了父组件的this对象。

在子组件中的ngOnInit生命周期钩子中初始化地图的话会出现
image.png
或者地图已经被初始化过的错误,所以放在ngAfterViewInit中初始化就没有问题了

然后遇到的第一个问题就是,地图显示不全,如下图所示
image.png

可以看到地图只显示很小的一片区域,移动的时候也不会扩大显示区域。这时候一般在正确的地方写上代码this.map.invalidateSize(true)就行了,官方文档里对这个方法的描述是
image.png
简而言之就是根据容器大小动态更新地图。 之所以说一般情况下这样写就对了,因为还有其他的情况

第二种情况也会导致地图显示不全
就是img的样式问题,如下图
image.png
这里在父组件中定义了map的预设样式。

子组件中也同样,应用到了这个样式,我更改了leaflet中的mark,标记用的不是点,用的是img图片代替,我在样式中使用了样式穿透
:host ::ng-deep .divlayer .divcontent img 就会造成上面的地图显示不全。 解决办法就是更具体到img中为图标的那一类
也就是:host ::ng-deep .divlayer .divcontent img.leaflet-marker-icon 这样问题就解决了,皆大欢喜

scss和less在angular中使用的不同目前发现的一点就是

less中可以使用:host /deep/ xxx{}不会报错,而在scss中/deep/会报错,可以用::ng-deep替代。

    • -

2021-02-22更新

image.png

如图,在leaflet中实现连线功能

下面开始讲如何实现

首先需要新建一个图层,画的线就画在这个图层上。

this.testLayerGroup = new L.layerGroup()//初始化图层
this.testLayerGroup.addTo(this.map)//添加图层到海图

上面两行代码需要写在ngOnInit生命周期中,并且要在地图创建完成以后

然后写一个方法 drawLine(){//划线}

方法中

drawLine(){
    let end:boolean=false;//用于控制划线方法结束与否

    this.testLayerGroup.clearLayers()//清除图层
    this.testCoordinates = []//清空数组

    let layerId: any = null;
    let layerId2: any = null;
    //这两个id稍后用到的时候再解释是干什么用的

    this.map.on('mousedown',(e:any)=>{//当鼠标按下的时候触发 e的内容如图1所示
        let coodinate:any = [e.latlng.lat,e.latlng.lng]//用来存放坐标点 第一个是纬度(latitude),第二个是经度(longitude)
        let exist:boolean:any = false;//用于判定是否为同一个点
        for (let i = 0; i < this.testCoordinates.length; i++) {//如果找到了已存在的点(通常为双击的时候)就标记为已点过,从而不会进入到画点的方法中
            if (this.testCoordinates[i][0] == e.latlng.lat && this.testCoordinates[i][1] == e.latlng.lng) {
              exist = true;
              break;
            }
        }
        if(!exist){//如果非同样的点
            let startpoint = L.circleMarker(coodinate,{//点击的点位做成圆形标记比较好认
                radius:2 //这个是圆点的半径,还有其他很多的属性,在leaflet官网里面有详细的描述
            })
            this.testLayerGroup.addLayer(startpoint);//把起始点放进图层里
            this.testCoordinates.push([e.latlng.lat,e.latlng.lng]);//把点位加入到数组集合里
            let len:number = this.testCoordinates.length;
            if(len>=2){//有两个点的时候,增加距离文字提示
                let coordinate1:any = this.testCoordinates[len-2]//上一个点
                let coordinate2:any = this.testCoordinates[len-1]//这个点
                let line1 = L.polyline([coordinate1,coordinate2],{//画线需要两个点
                    weight:2 //线的粗细程度 其他属性官网由
                }).setText('▶',{ repeat: false, center: true, offset: [4, 4], attributes: { fill: 'red' } })//setText官网上没有相关的描述但是具体的作用是在线的中间添加文字性的描述,示例图中没有改变颜色,所以代码里是红色的,但是图里是黑色的。
                this.testLayerGroup.addLayer(line1)//把这个线加入到图层里
                let distance:any = (L.latLng(coordinate1).distanceTo(L.latLng(coordinate2))/1852).toFixed(2);//计算距离,海里是米除以1852,结果保留两位小数
                let tips = L.divIcon({//距离提示框的相关属性
                    html:`<p style="text-shadow:#aac39a 0.5px 0.5px, #aac39a -0.5px -0.5px;font-weight:bold">${distance}海里`,
                    iconSize:[100,30],
                    className:'divIcon'//类名 所以可以在上面的hmtl写上样式,也可以在样式文件中通过类名改变样式
                })
                let mark1:any = L.marker(coordinate2,{icon:tips});//把距离提示作为一个标记
                this.testLayerGroup.addLayer(mark1);//把标记插入到图层中
            }
            this.map.on('mousemove',(e1:any)=>{//当鼠标移动的时候
                if(!end){//非结束的时候
                    if (layerId != null) {这个id有的话
                        this.testLayerGroup.removeLayer(layerId);//清除掉这个id的图层
                        layerId = null;//id设为空
                    }
                    if (layerId2 != null) {//同上操作
                        this.testLayerGroup.removeLayer(layerId2);
                        layerId2 = null;
                    }
                    let coordinate2:any = [e1.latlng.lat,e1.latlng.lng];//最新的点位
                    let coordinate1:any = this.testCoordinates[len-1];//数组中最后一个点位
                    //这两个点位就是移动的时候划线用的,同时上面的id是为了时刻保持只有一条线起作用的,不然的话就会如图2一样
                    //下面的代码上面已经重复过了
                    let distance: number = Number((L.latLng(coordinate1).distanceTo(L.latLng(coordinate2)) / 1852).toFixed(2));
                    if (distance != 0) {
                        let line = L.polyline([coordinate1, coordinate2], {
                            weight: 2
                        }).setText('▶', { repeat: false, center: true, offset: [4, 4] });
                        this.testLayerGroup.addLayer(line)
                        layerId = this.testLayerGroup.getLayerId(line);//线的id设立一下
                        let tips = L.divIcon({
                            html: `<p style="ext-shadow: #000 1px 0 0, #000 0 1px 0, #000 -1px 0 0, #000 0 -1px 0;font-weight:bold">${distance}海里`,
                            iconSize: [100, 30],
                            className: 'divIcon'
                        })
                        let mark1: any = L.marker(coordinate2, { icon: tips });
                        this.testLayerGroup.addLayer(mark1);
                        layerId2 = this.testLayerGroup.getLayerId(mark1);//点的id设立一下
                    }
                }
            })
        }
    })
    
    this.map.on('dblclick',()=>{//双击以后的事件
        let len:number = this.testCoordinates.length;
        if(len>1){//数组长度大于1的时候
            this.map.off('mousedown');//结束点击事件
            this.map.off('dblclick');//结束双击事件
            end = true//划线方法状态改为结束
        }
        let distance:number = 0;
        for(let i = 1;i<len;i++){
            distance += Number((L.latLng(this.testCoordinates[i-1]).distanceTo(L.latLng(this.testCoordinates[i]))/1852).toFixed(2))
            //双击的时候是结束划线了,所以要把所有的距离都统计起来
        }
        let popup = L.popup();
        popup.setLatLng(this.testCoordinates[len-1]);//创建一个气泡弹出框,位置就是数组中最后一个坐标点,也就是放在结束的地方
        let table:any = L.DomUtil.create('table',"testTable");
        //在地图里创立一个dom对象,具体为是一个table标签
        let tr:any = L.DomUtil.create('tr','testtr',table);
        //同上,创立一个dom对象,具体是一个tr标签,父元素是上面创立的table标签
        let td:any = L.DomUtil.create('td','testtd',tr);
        //同上,td标签, 父元素为tr
        let text:any = L.DomUtil.create('span','testspan',td);
        //同上,span标签,父元素为td
        distance = Number(distance.toFixed(2))
        text.innerText = `总里程: ${distance} 海里`;//span标签的内容
        popup.setContent(table);//气泡弹出窗的内容
        this.testLayerGroup.eachLayer((layer: any) => {//防止点到其他地方然后把这个总里程弹窗覆盖掉
        layer.bindPopup(popup);
        });
        popup.openOn(this.map);
    })
}

image.png
图1

image.png
图2

这样就完成了一个划线的方法,具体的触发可以搞一个按钮,点一下就执行draw方法。


2021-3-10 17:05

li{
    transition:0.3s;
}
li:hover{
    left:0.4em;
}

样式中使用transition
hover的时候无法产生动效的原因其实很简单
就是在li中加上left:0px就行

li{
    left:0px;
    transition:0.4s
}

2021-4-15 17:10更新

最近有个需求,就是需要在leaflet里面用两个地图,结果遇到了
Map container is already initialized
这个问题,求助其他人以后发觉问题核心所在,就是leaflet不允许在map里面再创建一个map,也就是除非你的第二个map和第一个map是同级的才行
解决办法有两个,第一个办法就是在第二个map建立之前删除leaflet内部建立的id,这样不论怎样不论在什么位置,地图都能创立:document.getElementById('Track_Map')['_leaflet_id'] = undefined;
这样操作的副作用还不清楚,但是既然leaflet这样操作了,那肯定有他的道理吧。
第二种办法就是如一开始所说,不要在map内部创建另一个map,与之平级的地方创建另一个map。


2021-4-19 11:12
这次有个新需求就是需要在leaflet中两个点之间的连线上做上标记,效果如下
image.png

由于原生leaflet没有提供这个功能,所以需要安装插件使用
npm i leaflet-textpath -s
然后在需要用到的地方引入import "node_modules/leaflet-textpath/leaflet.textpath.js";
具体用法如下
line = L.polyline(...).setText('►',{{ repeat: false, center: true, offset: [4, 4], attributes: { fill: lineColor } }})

有一个很重要的一点就是,这个setText依赖于地图的svg属性,也就是在地图的设置中,如果加上了这preferCanvas: true段代码,地图将会优先用canvas渲染,polyline中就不会有_path属性,setText就会报错,报Cannot read property 'setAttribute' of undefined错.


2021-04-20 9:55
leaflet里面,图层layerclearLayers()方法与remove()方法是决然不同的
remove()是直接把这个layer直接清除掉了,而clearLayers()只是把这个layer里面的图层都给清除掉。

嵌套object的循环方法:

object{
   object1{
     x:1,
     y:2,
   },
   object2{
     x2:1,
     y2:2,
   }
}

上图所示的object对象是不可以用遍历数组的方式进行遍历的,例如forEach()方法就会直接报错,说不是可遍历对象,这时候就要用for(let k in object)方法了。


2021-4-21 11:50

目前有个需求就是需要在点击mark的时候,改变popup的层级,使之不能够被另外的东西覆盖
如下图

image.png

原本是这样的

image.png

点击以后的弹框被覆盖掉了
解决办法一开始想的是在创建弹窗的内容的时候,把内容的层级提高

image.png

给弹框的content加上了新的class,改变层级,后面发现没有作用,需要把容器的层级提高才行

image.png

也就是这一级,但是leaflet官方只给出了setContent方法,这个方法就是改变弹出框的内容用的,但是没有类似于setClassName方法。不过查询文档以后,发现了这个方法

image.png

打印以后发觉返回的就是这个容器本身的dom元素,那我直接对这个dom元素进行操作就易如反掌了

if(names.getElement().classList.remove("weather-name-container-on")){
    names.getElement().classList.remove("weather-name-container-on");
}
names.getElement().classList.add("weather-name-container-on");

munergs
30 声望8 粉丝

现在即是最好。