maptalks Development Manual - Advanced

foreword

In the previous article, I have already understood the basic functions of maptalks and how to draw maps. For small partners who have the ability to explore, they may have completed more advanced functions, but here, as a manual, I will slowly record the development. details.

The effects that customers need are varied, but functional changes cannot be escaped.

The following example is based on the previous example

Project code address (demo)

mark

Creation and elimination in practical applications

In practical applications, mark marks are displayed along with the type selected by the user, which involves the elimination and creation of marks.

  1. Let's define a page first, as follows, leave a toolbar above the map
<template>
  <div class="container">
    <div class="map-toolbar">
      <el-checkbox-group v-model="checkList" @change="handleCheckChange">
        <el-checkbox :label="item.label" v-bind:key="item.value" :value="item.value" v-for="item in chooseData"></el-checkbox>
      </el-checkbox-group>
    </div>
    <div
      id="map"
      class="map-container"
    />
  </div>
</template>

<style scoped lang="scss">
  html, body {
      
      
    margin: 0px;
    height: 100%;
    width: 100%;
  }
.container {
      
      
  width: 100%;
  height: 100%;
  position: absolute
}
.map-toolbar {
      
      
  position: relative;
  z-index: 1000;
  width: 500px;
  height: 50px;
  float: right;
  top:10px;
  right: 300px;
  background-color: #fff;
  border-radius:5px;
  padding: 10px;

}
  .map-container {
      
      
    width: 100%;
    height: 100%
  }
</style>

image-20211031113311804

  1. Definition method:

    // 定义两个常量(仅测试)   
    const schoolCoordinate = [
            {
          
          
                adcode: 510104,
                name: "小学校",
                center:  [118.13734735854666, 24.498801064931087],
                parent: {
          
          
                    adcode: 510100
                },
            },  {
          
          
                adcode: 510104,
                name: "大学校",
                center:  [118.11804966596969, 24.47633923130772],
                parent: {
          
          
                    adcode: 510100
                },
            }
        ]
    
        const hospitalCoordinate = [
            {
          
          
                adcode: 510104,
                name: "小医院",
                center:  [118.18303419164465, 24.51143878000599],
                parent: {
          
          
                    adcode: 510100
                },
            },{
          
          
                adcode: 510104,
                name: "大医院",
                center:  [118.19412188942488, 24.53586932160701],
                parent: {
          
          
                    adcode: 510100
                },
            }
    
        ]
    // data 里定义的变量:
    // 类型复选框
    checkList: [],
        chooseData: [{
          
          
            label: '学校',
            value: 'school'
        }, {
          
          
            label: '医院',
            value: 'hospital'
        }],
            // 保持选择的图层的名称
            checkLayers: []
    
    
    // methods定义的方法
    drawMark(centerPointList, layer) {
          
          
         if (!centerPointList) {
          
          
             console.log('无区域中心点数据')
             return
         }
         const info = {
          
           content: '', width: 150, minHeight: 100 }
         const result = []
         // 这里 d 的数据格式是数组,如:[-0.113049, 51.498568]
         centerPointList.forEach(d => {
          
          
             if (!d.info) {
          
          
                 d.info = info
             }
             // 设有高度、高亮的mark
             const mark = new maptalks.Marker(d.center, {
          
          
                 // 设置了这个属性,会替换默认的图标
                 // symbol: {
          
          
                 // markerFile: 'foo.png',
                 // textName: d.name
                 // },
                 properties: {
          
          
                     // 高度设置
                     altitude: 50
                 }
             }).addTo(layer)
             mark.setInfoWindow({
          
          
                 title: d.name,
                 content: '<div>' + d.adcode + '</div>',
                 // autoPan: true,
                 width: d.info.width,
                 minHeight: d.info.minHeight,
             })
    
             mark.setZIndex(1000)
             result.push(mark)
         })
         return result
     },
    
     // 复选框选中
         handleCheckChange(data) {
          
          
             console.log(data)
             const _t = this
             // 清楚图层
             this.checkLayers.forEach(l => {
          
          
                 l.clear()
             })
             const schoolMarkLayer = this.mapEngine.getLayer('schoolMark')
             const hospitalMarkLayer = this.mapEngine.getLayer('hospitalMark')
             data.forEach(d => {
          
          
                 if (d === '学校') {
          
          
                     _t.drawMark(schoolCoordinate, schoolMarkLayer)
                     _t.checkLayers.push('schoolMark')
                 }
                 if (d === '医院') {
          
          
                     _t.drawMark(hospitalCoordinate, hospitalMarkLayer)
                     _t.checkLayers.push('hospitalMark')
                 }
             })
         },
    

I don't know how to upload the video. It is possible to have all the codes, if there is no full post, I don’t want to take up too much space;

Let me talk about it here, some of its methods

  1. layer.clear() , he will clear everything on the layer;
  2. mark.remove() , you can use this to remove the mark, but this needs to be called by the mark object. If the front-end wants to achieve the above functions, then it is necessary to save the mark list. This is very unwise. When there are too many points, the front-end may I can't bear it; so each type of layer is cached here, and the layer is used for operation;

custom icon

Here is an example using the vue logo as a replacement icon

顶部require引入静态资源
const logo = require('../../assets/logo.png')


// 创建时,使用symbol替换默认的样式
const mark = new maptalks.Marker(d.center, {
    
    
                        symbol: {
    
    
                            // markerType: 'square',
                            markerFile: logo,
                            markerWidth: 40,
                            markerHeight: 40,
                            markerDx: 0,
                            markerDy: 0,
                            markerOpacity: 1
                        },
                        properties: {
    
    
                            // 高度设置
                            altitude: 0
                        }
                    }).addTo(layer)

Here markerFile is used to replace the icon, markerType, marker type, what kind of icon it represents, when you have the markerFile attribute, it cannot be overwritten, when there is no markerFile, it can be displayed with markerType, it has several types : ellipse cross x diamond bar square triangle pin pie, the effect can be set by yourself to see.

The effect is as follows:

image-20211106190724575

Add animation effects

After adding your own mark, you may require some visual effects. Mark also provides animatea method to set your own animation. Then we set an animation when the mark appears, and the horizontal expansion of the vue logo.

Add the following code on the basis of the above code, and then set the new maptalks.Markerdefault symbol.markerHeightsettings to 0 and 10, which means the initial height is 0, the initial width is 10, and it is transformed to a height of 40 and a width of 40.symbol.markerWidth

mark.animate({
    
    
    symbol: {
    
    
        markerWidth: 40,
        markerHeight: 40
    },
    properties: {
    
    
        altitude: 800
    }
}, {
    
    
    duration: 150,
}, function (frame) {
    
    
    if (frame.state.playState === 'finished') {
    
    
        console.log('animation finished');
    }
});

When we click on the school or hospital option, these sprinkles are automatically expanded, and monitoring can also be added, and some processing can be done at each animation stage.

Complete code:

   drawMark(centerPointList, layer) {
    
    
                if (!centerPointList) {
    
    
                    console.log('无区域中心点数据')
                    return
                }
                const info = {
    
     content: '', width: 150, minHeight: 100 }
                const result = []
                // 这里 d 的数据格式是数组,如:[-0.113049, 51.498568]
                centerPointList.forEach(d => {
    
    
                    if (!d.info) {
    
    
                        d.info = info
                    }
                    // 设有高度、高亮的mark
                    const mark = new maptalks.Marker(d.center, {
    
    
                        symbol: {
    
    
                            markerType: 'square',
                            markerFile: logo,
                            markerWidth: 10,
                            markerHeight: 0,
                            markerDx: 0,
                            markerDy: 0,
                            markerOpacity: 1
                        },
                        properties: {
    
    
                            // 高度设置
                            altitude: 0
                        }
                    }).addTo(layer)
                    mark.setInfoWindow({
    
    
                        title: d.name,
                        content: '<div>' + d.adcode + '</div>',
                        // autoPan: true,
                        width: d.info.width,
                        minHeight: d.info.minHeight,
                    })

                    mark.animate({
    
    
                        symbol: {
    
    
                             markerWidth: 40,
            				 markerHeight: 40
                        },
                        properties: {
    
    
                            altitude: 800
                        }
                    }, {
    
    
                        duration: 150,
                    }, function (frame) {
    
    
                        if (frame.state.playState === 'finished') {
    
    
                            console.log('animation finished');
                        }
                    });

                    mark.setZIndex(1000)
                    result.push(mark)
                })
                return result
            },

It can be seen that when creating the mark object at the beginning, the set width widthis only 10, which is the start width of the animation, and then the properties set in the animate method are the properties that need to be changed, and are also the final properties. By controlling the execution of the durationanimation time.

tool

Here are basically symbols, so I have to mention it again here, that is, symbolwe can check its documentation for this attribute, because it is a system, and it is unified and complete;

The first one of the following related attribute comments is relatively complete, and the latter ones are almost the same, so they will not be marked all

distance measuring tool

API: Class: DistanceTool (maptalks.org)

Calculate the distance, copy the official case, make a slight modification, add the end event,

    /**
       * 测距工具
       * @returns {*}
       */
    addDistanceTool () {
    
    
      return new maptalks.DistanceTool({
    
    
        symbol: {
    
    
          lineColor: '#34495e',
          lineWidth: 2
        },
        // 请看 symbol属性说明文档:https://github.com/maptalks/maptalks.js/wiki/Symbol-Reference
        // 绘制的线的样式
        vertexSymbol: {
    
    
          // 绘制的marker类型,也就是简单形状,支持:ellipse cross x diamond bar square triangle pin pie
          markerType: 'square',
          // 绘制的marker的填充色
          markerFill: '#1bbc9b',
          // 绘制的线的颜色
          markerLineColor: '#000',
          // 绘制的线的宽度
          markerLineWidth: 1,
          // 绘制的marker大小
          markerWidth: 10,
          markerHeight: 10
        },
		// 文本标签属性
        labelOptions: {
    
    
          textSymbol: {
    
    
            // 文本呈现的样式,默认是: monospace, 当你设置 textFont时会覆盖这个属性
            textFaceName: 'monospace',
            // 文本填充色(字体颜色)
            textFill: '#fff',
            // 行距
            textLineSpacing: 1,
            // 对齐方式
            textHorizontalAlignment: 'right',
            // 文本标签与marker的距离,也就是与打点的位置的距离
            textDx: 20,
            // 标签的线的颜色
            markerLineColor: '#b4b3b3',
            // 标签的填充色
            markerFill: '#000'
          },
          boxStyle: {
    
    
            // 标签的padding, 第一个值是左右的padding,第二个是上下的padding
            padding: [6, 5],
            symbol: {
    
    
              // 绘制的marker类型,也就是简单形状,支持:ellipse cross x diamond bar square triangle pin pie
              markerType: 'square',
              markerFill: '#000',
              markerFillOpacity: 0.9,
              markerLineColor: '#b4b3b3'
            }
          }
        },
        // 清楚按钮的symbol
        clearButtonSymbol: [{
    
    
          markerType: 'square',
          markerFill: '#000',
          markerLineColor: '#b4b3b3',
          markerLineWidth: 2,
          markerWidth: 15,
          markerHeight: 15,
          markerDx: 20
        }, {
    
    
          markerType: 'x',
          markerWidth: 10,
          markerHeight: 10,
          markerLineColor: '#fff',
          markerDx: 20
        }],
        language: 'zh-CN',
        once: true
      }).addTo(this.mapEngine)
        .on('drawend', p => {
    
    
          console.log('地图坐标:' + p.coordinate)
          console.log('界面坐标:' + p.containerPoint)
          console.log('测量距离:' + p.drawTool.getLastMeasure() + '米')
        })
    },

Add tool buttons:

Here we added the [Range Measurement] button, which activates surveying and mapping for Chinese New Year and holidays when clicked, and closes surveying and mapping when clicked again

    /**
       * 创建工具栏
       */
    addToolbar () {
    
    
      const _t = this
      const map = this.mapEngine
      new maptalks.control.Toolbar({
    
    
        items: [
          {
    
    
            item: '测距',
            click: () => {
    
    
              if (_t.distanceTool.isEnabled()) {
    
    
                _t.distanceTool.disable()
              } else {
    
    
                _t.distanceTool.enable()
              }
            }
          },
          {
    
    
            item: '放大',
            click: () => {
    
    
              map.setZoom(_t.zoom += 1)
            }
          },
          {
    
    
            item: '缩小',
            click: () => {
    
    
              map.setZoom(_t.zoom -= 1)
            }
          },
          {
    
    
            item: '旋转',
            click: () => {
    
    
              map.setBearing(_t.bearing -= 50)
            }
          },
          {
    
    
            item: '重置',
            click: () => {
    
    
              _t.mapDataReset(map)
            }
          },
          {
    
    
            item: '锁定',
            click: (t) => {
    
    
              if (t.target.item === '锁定') {
    
    
                map.setOptions({
    
    
                  // 可拖动
                  draggable: false,
                  // 平移
                  dragPan: false,
                  // 旋转
                  dragRotate: false,
                  // 间距
                  dragPitch: false,
                  // 滚动缩放
                  scrollWheelZoom: false,
                  // 点击 缩放
                  touchZoom: false,
                  // 双击缩放
                  doubleClickZoom: false
                })
                t.target.item = '取消锁定'
              } else {
    
    
                map.setOptions({
    
    
                  // 可拖动
                  draggable: true,
                  // 平移
                  dragPan: true,
                  // 旋转
                  dragRotate: true,
                  // 间距
                  dragPitch: true,
                  // 滚动缩放
                  scrollWheelZoom: true,
                  // 点击 缩放
                  touchZoom: true,
                  // 双击缩放
                  doubleClickZoom: true
                })
                t.target.item = '锁定'
              }
            }
          }
        ]
      }).addTo(map)
    },

Effect:

image-20220108163237187

The above function is to start the distance measurement mode after clicking [Range], but in some cases, click the button once and measure once. This is also achievable here. maptalks provides such an api, just need to be in options Add the "onece" attribute and set it to true.

    /**
     * 测距工具
     * @returns {*}
     */
    addDistanceTool () {
    
    
      return new maptalks.DistanceTool({
    
    
   // ... 省略
        language: 'zh-CN',
          // 只测绘一次
        once: true
      }).addTo(this.mapEngine)
        .on('drawend', p => {
    
    
          console.log('地图坐标:' + p.coordinate)
          console.log('界面坐标:' + p.containerPoint)
          console.log('测量距离:' + p.drawTool.getLastMeasure() + '米')
        })
    }

If configured as above, in addToolbarthis method, we do not need to modify, because the attribute of this onece is also disabled after a survey, so our addToolbarmethod can still be extended.

drawing tool

Similarly, you can directly take the official demo and modify it.


/**
       * 绘制工具
       */
    addDrawTool (layer) {
    
    
      const drawTool = new maptalks.DrawTool({
    
    
        mode: 'Point'
      }).addTo(this.mapEngine)
      // 默认是禁用的,当点击按钮后才能使用
        .disable()

      drawTool.on('drawend', function (param) {
    
    
        layer.addGeometry(param.geometry)
      })

      this.drawTool = drawTool
      return drawTool
    }

   /**
     *增加绘制工具栏
     */
    addDrawToolbar (layer) {
    
    
      const _t = this

      const items = [{
    
    code: 'Point', name: '点'},
        {
    
    code: 'LineString', name: '线'},
        {
    
    code: 'Polygon', name: '几何面'},
        {
    
    code: 'Circle', name: '圆'},
        {
    
    code: 'Ellipse', name: '椭圆'},
        {
    
    code: 'Rectangle', name: '矩形'},
        {
    
    code: 'FreeHandLineString', name: '自由绘制'},
        {
    
    code: 'FreeHandPolygon', name: '任意几何面'}]
        .map(function (value) {
    
    
          return {
    
    
            item: value.name,
            click: function () {
    
    
              _t.drawTool.setMode(value.code).enable()
            }
          }
        })
      new maptalks.control.Toolbar({
    
    
        position: {
    
    
          top: 100,
          right: 50
        },
        items: [
          {
    
    
            item: '绘制工具',
            children: items
          },
          {
    
    
            item: '禁用',
            click: function () {
    
    
              _t.drawTool.disable()
            }
          },
          {
    
    
            item: '清除',
            click: function () {
    
    
              layer.clear()
            }
          }
        ]
      }).addTo(this.mapEngine)
    },

Effect:

image-20220108210804183

Surface measurement tool

Another tool is also a more face-saving tool:

  /**
     * 测面工具
     */
    addAreaTool () {
    
    
      const areaTool = new maptalks.AreaTool({
    
    
        once: true,
        // 请看 symbol属性说明文档:https://github.com/maptalks/maptalks.js/wiki/Symbol-Reference
        symbol: {
    
    
          lineColor: '#1bbc9b',
          lineWidth: 2,
          polygonFill: '#fff',
          polygonOpacity: 0.3
        },
        vertexSymbol: {
    
    
          // 绘制的marker类型,也就是简单形状,支持:ellipse cross x diamond bar square triangle pin pie
          markerType: 'ellipse',
          // 绘制的marker的填充色
          markerFill: '#34495e',
          // 绘制的线的颜色
          markerLineColor: '#1bbc9b',
          // 绘制的线的宽度
          markerLineWidth: 3,
          // 绘制的marker大小
          markerWidth: 10,
          markerHeight: 10
        },
        // 文本标签属性
        labelOptions: {
    
    
          textSymbol: {
    
    
            // 文本呈现的样式,默认是: monospace, 当你设置 textFont时会覆盖这个属性
            textFaceName: 'monospace',
            // 文本填充色(字体颜色)
            textFill: '#fff',
            // 行距
            textLineSpacing: 1,
            // 对齐方式
            textHorizontalAlignment: 'right',
            // 文本标签与marker的距离,也就是与打点的位置的距离
            textDx: 15
          },
          boxStyle: {
    
    
            // 标签的padding, 第一个值是左右的padding,第二个是上下的padding
            padding: [6, 2],
            symbol: {
    
    
              markerType: 'square',
              markerFill: '#000',
              markerFillOpacity: 0.9,
              markerLineColor: '#b4b3b3'
            }
          }
        },
        clearButtonSymbol: [{
    
    
          markerType: 'square',
          markerFill: '#000',
          markerLineColor: '#b4b3b3',
          markerLineWidth: 2,
          markerWidth: 15,
          markerHeight: 15,
          markerDx: 22
        }, {
    
    
          markerType: 'x',
          markerWidth: 10,
          markerHeight: 10,
          markerLineColor: '#fff',
          markerDx: 22
        }],
        language: 'zh-CN'
      }).addTo(this.mapEngine)
      // 默认关闭
      areaTool.disable()
      this.areaTool = areaTool
      return areaTool
    }

In the drawing toolbar, add this menu;

 {
    
    
            item: '测面工具',
            click: function () {
    
    
              if (_t.areaTool.isEnabled()) {
    
    
                _t.areaTool.disable()
              } else {
    
    
                _t.areaTool.enable()
              }
            }
          },

Also add a menu in the drawing toolbar, and then it will have the following effect:

image-20220108212851554

map animation

If, as soon as your page is opened, the camera rotates slowly from top to bottom, rotates 360 degrees and then locates the designated as, and then the icon pops out, such an effect must be able to capture most of the hearts.

The api is also provided in maptalks, we just call it:

  /**
     * 地图动画
     */
    mapAnimate () {
    
    
      const _t = this
      _t.mapEngine.animateTo({
    
    
        center: [118.13245430046891, 24.495713873147764],
        zoom: 13,
        pitch: 0,
        bearing: 20
      }, {
    
    
        duration: 5000
      })
      setTimeout(function () {
    
    
        _t.mapEngine.animateTo({
    
    
          center: [118.087828, 24.462059],
          zoom: 14,
          pitch: 65,
          bearing: 360
        }, {
    
    
          duration: 7000
        })
      }, 5000)
    }

Our software can’t cut the animation, but just run the code for this;

Generally, markers are brought along, and markers are also animated, and the combination of the two is perfect.

So, here I take down the functions of the school and the hospital made above, and combine them:

 /**
     * 地图动画
     */
    mapAnimate () {
    
    
      const _t = this
      _t.mapEngine.animateTo({
    
    
        center: [118.13245430046891, 24.495713873147764],
        zoom: 13,
        pitch: 0,
        bearing: 20
      }, {
    
    
        duration: 5000
      })
      setTimeout(function () {
    
    
        _t.mapEngine.animateTo({
    
    
          center: [118.087828, 24.462059],
          zoom: 14,
          pitch: 65,
          bearing: 360
        }, {
    
    
          duration: 7000
        })
      }, 5000)
        // 在地图动画块结束时,撒点动画加入,并设置默认值
      setTimeout(function () {
    
    
        _t.checkList = []
        _t.checkList.push('学校')
        _t.handleCheckChange(_t.checkList)
      }, 9000)
    }

I added a play button, which can be played repeatedly, eh. No software, no gifs.

polymerization

The function of aggregation also has to be said, all map designs to zoom will have this function.

It is a bit difficult to rely on maptalks alone, but the advantage of using maptalks is that it has many plug-ins. We also use plug-ins to achieve all this aggregation function:

文档: GitHub - maptalks/maptalks.markercluster: A layer of maptalks to cluster markers.

Import the plugin:

npm install maptalks.markercluster

Use as follows;

import * as maptalks from 'maptalks'
import {
    
    ClusterLayer} from 'maptalks.markercluster'

Let's modify the marker page we wrote before:

The layer we created this time is a markercluster, which will be a little different. We only change the layer creation method, and I have added corresponding comments. It will be clearer when you look at the code.

I didn't write all the symbols here. One is to copy the official website directly, and the other is to be lazy. It is the same as other symbols and can be copied.

// 先创建图层
 // 创建学校和医院的mark图层
      // new maptalks.VectorLayer('schoolMark').addTo(_t.mapEngine)
      // new maptalks.VectorLayer('hospitalMark').addTo(_t.mapEngine)
      const symbol = {
    
    
        // 聚合最大半径
        maxClusterRadius: 160,
        // 聚合的最大缩放比例,也就是当缩放到某一个层级时,进行聚合
        maxClusterZoom: 19,
        // 是否开启动画,默认true(开启)
        animation: true,
        // 动画时长
        animationDuration: 30,
        // textSumProperty: '',
        // 这里的属性,可以看官网,都是统一的
        // symbol: {},
        textSymbol: {
    
    
          // 文本呈现的样式,默认是: monospace, 当你设置 textFont时会覆盖这个属性
          textFaceName: 'monospace',
          // 字体大小
          textSize: 16
        }
      }
      new ClusterLayer('schoolMark', symbol).addTo(this.mapEngine)
      new ClusterLayer('hospitalMark', symbol).addTo(this.mapEngine)

addMarkerMethod modification:

 drawMark (centerPointList, layer) {
    
    
      if (!centerPointList) {
    
    
        console.log('无区域中心点数据')
        return
      }
      const info = {
    
    content: '', width: 150, minHeight: 100}
      const result = []
      // 这里 d 的数据格式是数组,如:[-0.113049, 51.498568]
      centerPointList.forEach(d => {
    
    
        if (!d.info) {
    
    
          d.info = info
        }
        // 设有高度、高亮的mark
        const mark = new maptalks.Marker(d.center, {
    
    
          symbol: {
    
    
            markerType: 'square',
            markerFile: logo,
            markerWidth: 10,
            markerHeight: 0,
            markerDx: 0,
            markerDy: 0,
            markerOpacity: 1
          },
          properties: {
    
    
            // 高度设置
            altitude: 0
          }
        })
        mark.setInfoWindow({
    
    
          title: d.name,
          content: '<div>' + d.adcode + '</div>',
          // autoPan: true,
          width: d.info.width,
          minHeight: d.info.minHeight
        })

        mark.animate({
    
    
          symbol: {
    
    
            markerWidth: 40,
            markerHeight: 40
          },
          properties: {
    
    
            altitude: 800
          }
        }, {
    
    
          duration: 150
        }, function (frame) {
    
    
          if (frame.state.playState === 'finished') {
    
    
            console.log('animation finished')
          }
        })

        mark.setZIndex(1000)
        result.push(mark)
      })
      layer.addMarker(result)
      return result
    },

The effect is as follows:

image-20220110225340346

basemap style

The conventional map design is not good, it will make the viewer very tired, so you can change the style.

This is relatively simple, it is an attribute:cssFilter

  /**
       * 初始化地图
       */
    initMap () {
    
    
      const _t = this
      return new maptalks.Map('map', {
    
    
        // 默认中心点点位
        center: _t.center,
        // 缩放层级
        zoom: _t.zoom,
        // 倾斜度
        pitch: _t.pitch,
        // 最小缩放层级
        minZoom: 1,
        // 最大缩放层级
        maxZoom: 18,
        baseLayer: new maptalks.TileLayer('base', {
    
    
          // 电子地图图层
          urlTemplate: _t.urlTemplate,
          subdomains: _t.subdomains,
          attribution: _t.attribution,
          cssFilter: 'sepia(100%) invert(90%)'
        })
      })
    },

It provides two:

image-20220110230801619

Effect:

image-20220110230946871

heat map

Heat map, which is integrated with heatmap.js

Add thermal layer plugin

npm install maptalks.heatmap

The adding method is also one of its methods. Its data is an array of coordinates. Just add your own variables. Here we will do it

// 获取热力点
const h1 = []
for (let i = 0; i < simingAreaData.length; i++) {
    
    
    const s = simingAreaData[i]
    s.pointList.forEach(ss => {
    
    
        ss.forEach(sss => {
    
    
            sss.push(i + '100')
            h1.push(sss)
        })
    })
}
// 创建图层
new HeatLayer('heat', h1, {
    
    
    // 热力比例(我的理解是,在0-1之间,也就是0-100%,一个热力点,的热力程度是多少
    heatValueScale: 0.7,
    // 旋转时强制刷新
    forceRenderOnRotating: true,
    // 移动时强制刷新
    forceRenderOnMoving: true,
    // 模糊因子, 值越大,越平滑,默认值是15
    // blur: 20,
    // 每个数据点的半径(默认值25,也建议25,我建议不能小于20)
    // radius: 25,
    // 最小不透明读,越小越透明
    // minOpacity: 0.8,
    // 热力梯度,是热力点外围的颜色值,从外围到里,值是递增的,最大值就是中心的位置
    // gradient: {
    
    
    //   0.4: 'blue',
    //   0.6: 'cyan',
    //   0.7: 'lime',
    //   0.8: 'yellow',
    //   1.0: 'red'
    // }
}).addTo(this.mapEngine)

First of all, we need to understand how the heat is presented

  1. Gradient outward from the center of the point;
  2. Multiple points can be superimposed, or multiple points are gathered together to present a surface;
  3. The display of each point is the same, and there is a thermal gradient, which is a color change from outside to inside (from light to dark);

So it is different from the above geometry, the geometry requires more than 3 points, and the heat map only needs at least one:

The above code also marks the thermal properties:

  • heatValueScale: The role of this value is not well understood,
  • forceRenderOnRotating: force refresh when rotating
  • forceRenderOnMoving: force refresh when moving
  • blur: blur factor, the larger the value, the smoother, the default value is 15
  • radius: the radius of each data point (the default value is 25, and 25 is also recommended, and I recommend not less than 20)
  • minOpacity: the minimum opaque read, the smaller the more transparent
  • gradient: Thermal gradient, which is the color value of the periphery of the thermal point. From the periphery to the inside, the value is increasing, and the maximum value is the position of the center

The above values ​​have default configurations, which can be used directly or customized;

Interaction of heat map

In response to a question raised by a friend, due to the character limit, I added it to the article

The heat map itself is rendered by point drawing, and there is no open interactive interface (I haven't found it yet), so I can only find an alternative method. Here are two methods
:

      // 方法一
      // canvas绘制point,和热力图重合,并添加监听事件,也可以绘制mark,不过它们之间的性能需要分析下
      // 这个方法可以单独对点做弹窗
      // 对应的point文档:https://maptalks.org/maptalks.js/api/1.x/MultiPoint.html#setSymbol
      const h1 = []
      for (let i = 0; i < simingAreaData.simingAreaData.length; i++) {
    
    
        simingAreaData.simingAreaData[i].pointList[0].forEach(d => {
    
    
          h1.push(d)
        })
      }
      var mPoint = new maptalks.MultiPoint(h1, {
    
    
        visible: true,
        // 可交互
        interactive: true,
        cursor: 'pointer',
        // 可拖拽
        draggable: false,
        zIndex: 10,
        symbol: {
    
    
          // 不透明度,这里不设置0是因为,值为0交互会失效
          markerOpacity: 0.1,
          markerFillOpacity: 0.1,
          markerLineOpacity: 0,
          // 填充色,使用热力图相近的颜色
          markerFill: 'red',
          markerType: 'ellipse',
          // 这里保持和热力图(对应heatMap的radiu,而且要大一点,这里我是比热力图的大10)
          markerWidth: 35,
          markerHeight: 35
        }
      })
      mPoint.on('mousemove', function (e) {
    
    
        e.target.openInfoWindow(e.coordinate)
      }).on('mouseout', function (e) {
    
    
        e.target.closeInfoWindow()
      }).setInfoWindow({
    
    
        content: 'hello world',
        title: 'message',
        animationDuration: 0,
        autoOpenOn: false
      })
      new maptalks.VectorLayer('pointLayer', mPoint).addTo(this.mapEngine)


// 方法二
      // 用其他可交互图形替代,使用几何图形,这个要求是对真个热力图都是一样的弹窗,将整个热力图作为一个整体
      // 这里我用的几何图形,有太多限制,不太符合要求,但可以作为一个思路参考,
      const geometry = maptalks.GeoJSON.toGeometry(simingAreaData.geoData)
      if (geometry) {
    
    
        geometry.forEach(g => {
    
    
          g.setSymbol({
    
    
            // 线色
            lineColor: '#f298bc',
            // 线宽
            // 这里保持和热力图(对应heatMap的radiu,而且要大一点)
            lineWidth: 30,
            // 不透明度
            polygonOpacity: 0,
            // 圆角设置
            lineJoin: 'round'
          })
          g.on('mousemove', function (e) {
    
    
            e.target.openInfoWindow(e.coordinate)
          }).on('mouseout', function (e) {
    
    
            e.target.closeInfoWindow()
          }).setInfoWindow({
    
    
            content: 'hello world',
            title: 'message',
            animationDuration: 0,
            autoOpenOn: false
          })
        })
      }
      const options = {
    
    
        enableAltitude: true,
        zIndex: 10
      }
      new maptalks.VectorLayer('lineLayer', options).addGeometry(geometry).addTo(this.mapEngine)

The effect of the first method:
insert image description here

Demonstration of the effect of the second method:
insert image description here

3D - three.js

In addition to using the above echarts, there is also a three-dimensional front-end development framework three.js. It is synchronized with the operation of the map, and it feels much simpler;

Documentation after maptalks integrates three: maptalks.three/API.ZH-CN.md at master maptalks/maptalks.three GitHub

npm install maptalks.three

// Just install the 128 version here, other versions will report an error

npm install [email protected]

create board

maptalks integrates three.js, and some of them are customized for maptalks, but they are basically the same as three. Just looking at the documentation of maptalks.three will not make any progress. If you need to create a particularly powerful effect, you should read it The official documentation of three.

 drawArea() {
    
    
     const _t = this
	 const layer = new ThreeLayer('three')
     // 图层三维设置
     layer.setOptions({
    
    
         // 地图移动、缩放、旋转时强制渲染
         forceRenderOnMoving: true,
         forceRenderOnZooming: true,
         forceRenderOnRotating: true,
         zIndex: 10
     })
	
     layer.prepareToDraw = function(gl, scene) {
    
    
         _t.create3DPlan2(scene, layer)
     }

     _t.mapEngine.addLayer(layer)

 },
     
  /**
             * 创建3D板块
             * @param scene
             * @param layer
             * @returns {ExtrudePolygons}
             */
     create3DPlan(scene, layer) {
    
    
         // 创建直线光
         const light =  new THREE.DirectionalLight(0xffffff)
         light.position.set(0, -10, 10).normalize()
         scene.add(light)
         // 创建环境光
         scene.add(new THREE.AmbientLight(0xffffff, 0.2))

         // 创建材质
         var material = new THREE.MeshLambertMaterial({
    
     color: cc.defaultPolygonFillColor, opacity: 0.7 })

         const list = []
         const polygons = maptalks.GeoJSON.toGeometry(geoJson)
         polygons.forEach(p => {
    
    
             // 该方法是旧版本方法,官方推荐使用toExtrudePolygon
             var mesh = layer.toExtrudeMesh(p, 400, material)

             if (Array.isArray(mesh)) {
    
    
                 scene.add.apply(scene, mesh)
             } else {
    
    
                 scene.add(mesh.object3d)
             }
             // infowindow test
             mesh.setInfoWindow({
    
    
                 content: '<br style="color:#f00">中心点:' + p.properties.center + ' </br>行政区划:' + p.properties.adcode + ' </br>父级行政区划:' + p.properties.parent.adcode + '</div>',
                 title: 'message',
                 animationDuration: 0,
                 autoOpenOn: false
             })
             list.push(mesh)
         })
         // 批量添加到图层
         layer.addMesh(list)
     },
         
    create3DPlan2(scene, layer) {
    
    
        // 创建直线光
        const light =  new THREE.DirectionalLight(0xffffff)
        light.position.set(0, -10, 10).normalize()
        scene.add(light)
        // 创建环境光
        scene.add(new THREE.AmbientLight(0xffffff, 0.2))

        // 创建材质
        var material = new THREE.MeshLambertMaterial({
    
     color: cc.defaultPolygonFillColor, opacity: 0.7 })

        const options = {
    
    
            altitude: 1,
            height: 400,
            bottomHeight: 0,
            topColor: null,
            bottomColor: cc.defaultPolygonFillColor,
            interactive: true
        }

        const list = []
        const polygons = maptalks.GeoJSON.toGeometry(geoJson)
        polygons.forEach(p => {
    
    
            // 这里源码中,可以找到toExtrudePolygons,但是创建的得到的MultiExtrudePolygons无法正常添加到scence中,所以这里还是用它推荐的方法
            var mesh = layer.toExtrudePolygon(p, options, material)
            if (Array.isArray(mesh)) {
    
    
                scene.add.apply(scene, mesh)
            } else {
    
    
                scene.add(mesh)
            }

            mesh.setInfoWindow({
    
    
                title: p.properties.name,
                content: '<br style="color:#f00">中心点:' + p.properties.center + ' </br>行政区划:' + p.properties.adcode + ' </br>父级行政区划:' + p.properties.parent.adcode + '</div>',
                animationDuration: 0,
                autoOpenOn: false
            })
            list.push(mesh)
        })

        // 批量添加到图层
        layer.addMesh(list)
    }

There are two ways to extrude objects written above: toExtrudeMesh, toExtrudePolygonIn fact, there is another way. In the source code, toExtrudePolygonsthis can pass in an array, but in actual use, it gets an object, and the execution scene.add(mesh)will report an error, resulting in The result is a surface, not a 3D object.

The effect is as follows:

image-20211023225451890

Now the map block has been generated, and it is still a bit ugly, just like drawing the area, divide it into blocks, and then add mouse events.

Block

This is to add a dividing line to the generated object. You can use the previous method to add another layer to generate a surface. The transparency of the surface is set to 0, the wireframe, the width 1, and a macho fan, OK

 drawLine() {
    
    
     const geometry = maptalks.GeoJSON.toGeometry(geoJson)
     if (geometry) {
    
    
         geometry.forEach(g => {
    
    
             g.setSymbol({
    
    
                 // 线色
                 lineColor: '#f298bc',
                 // 线宽
                 lineWidth: 1,
                 // 不透明度
                 polygonOpacity: 0
             })
         })
     }
     new maptalks.VectorLayer("line").addGeometry(geometry).addTo(this.mapEngine)
 }

Let's see the effect

image-20211024134514871

This is pasted on the map, it's a bit ugly, let's set another elevation

drawLine() {
    
    
    const geometry = maptalks.GeoJSON.toGeometry(geoJson)
    if (geometry) {
    
    
        geometry.forEach(g => {
    
    
            g.setSymbol({
    
    
                // 线色
                lineColor: '#f298bc',
                // 线宽
                lineWidth: 1,
                // 不透明度
                polygonOpacity: 0
            })
            // 添加高程
            g.setProperties({
    
    
                // 高度设置(对应挤压的高度height)
                altitude: 400
            })
        })
    }
    const options = {
    
    
        enableAltitude: true,
        zIndex: 9
    }
    new maptalks.VectorLayer("line", options).addGeometry(geometry).addTo(this.mapEngine)
}

Effect:

image-20211024135517544

event

Object document created by three: maptalks.three/API.ZH-CN.md at master maptalks/maptalks.three GitHub

It can be seen that the adaptation is quite good.

onThe setting method is the same as that of the area plane, but there is a problem. Let’s look at the code first, and the monitoring is also added in the previous method.

create3DPlan2(scene, layer) {
    
    
                // 创建直线光
                const light =  new THREE.DirectionalLight(0xffffff)
                light.position.set(0, -10, 10).normalize()
                scene.add(light)
                // 创建环境光
                scene.add(new THREE.AmbientLight(0xffffff, 0.2))

                // 创建材质
                var material = new THREE.MeshLambertMaterial({
    
     color: cc.defaultPolygonFillColor, opacity: 0.7 })

                const options = {
    
    
                    altitude: 1,
                    height: 400,
                    bottomHeight: 0,
                    topColor: null,
                    bottomColor: cc.defaultPolygonFillColor,
                    interactive: true
                }

// 定义高亮材质
                const highlightmaterial = new THREE.MeshBasicMaterial({
    
     color: '#ff87a4', transparent: true, opacity: 0.8 })

                const list = []
                const polygons = maptalks.GeoJSON.toGeometry(geoJson)
                polygons.forEach(p => {
    
    
                    // 这里源码中,可以找到toExtrudePolygons,但是创建的得到的MultiExtrudePolygons无法正常添加到scence中,所以这里还是用它推荐的方法
                    var mesh = layer.toExtrudePolygon(p, options, material)
                    if (Array.isArray(mesh)) {
    
    
                        scene.add.apply(scene, mesh)
                    } else {
    
    
                        scene.add(mesh)
                    }

                    mesh.setInfoWindow({
    
    
                        title: p.properties.name,
                        content: '<br style="color:#f00">中心点:' + p.properties.center + ' </br>行政区划:' + p.properties.adcode + ' </br>父级行政区划:' + p.properties.parent.adcode + '</div>',
                        animationDuration: 0,
                        autoOpenOn: false
                    })
                    
                    // 添加鼠标事件
                    mesh.on('mouseover', function(e) {
    
    
                        e.target.setSymbol(highlightmaterial)
                        layer.redraw()
                      }).on('mouseout', function(e) {
    
    
                        e.target.setSymbol(material)
                        layer.redraw()
                    })
                    list.push(mesh)
                })
                // 批量添加到图层
                layer.addMesh(list)
            },

As you can see, I have added them to the event layer.redraw(), why?

There is no mention of this in the official documents and examples. I always thought that it would be rendered automatically like vector graphics, but it didn’t. Moreover, it was always rendered inexplicably. Later, I found out that whenever I move the map, or Its graphics will only change when it is rotated, which reminds me that the vector layer has a setting to force rendering when moving and rotating, which is very similar to the current situation, and then look at its documentation again, and there is no mention of rendering method, but what makes people happy is that the three layer is inherited from CanvasLayerand has all its methods. At this point, the problem is solved, just call to redraw()redraw the three layer.

One difference is that in maptalks.three, setSymbolthe parameter is the passed material object

Look at the renderings:

image-20211024141815638

Draw 3D without the help of other plugins

Without using three.js and echarts, you can also build a 3D structure through its own surface. It may be clearer if you have used a 3D model. In 3D software, the surface can be extruded, so that you can To get a polyhedron, the three here is the reason. The most traditional way is to combine faces in three-dimensional space, which can also be realized in maptalks.

Modify the previous code slightly:

   /**
             * 再图层上画面
             */
            drawArea() {
    
    
                const options = this.getOptions()

                const lowSymbol = {
    
    }
                const topSymbol = {
    
    }
                Object.assign(lowSymbol, options.symbol)
                Object.assign(topSymbol, options.symbol)

                const lowOptions = {
    
    
                    zIndex: 9,
                    symbol: lowSymbol,
                    properties: {
    
    
                        altitude: 0
                    }
                }

                const topOptions = {
    
    
                    zIndex: 11,
                    symbol: topSymbol,
                    properties: {
    
    
                        altitude: 400
                    }
                }

                const _t = this

                const layer = new maptalks.VectorLayer('vector')
                // 创建底面
                _t.drawAreaPlan(_t.geoJson, layer, lowOptions)
                // 高面
                const centerCoordinate = _t.drawAreaPlan(_t.geoJson, layer, topOptions, true)

                const lineOptions = {
    
    
                    zIndex: 10,
                    symbol: options.symbol,
                    properties: {
    
    
                        altitude: 400
                    }
                }
                const lineCoordinate = []
                if (geoJson.type === 'FeatureCollection') {
    
    
                    geoJson.features.forEach(c => {
    
    
                        lineCoordinate.push(c.geometry.coordinates[0])
                    })
                }
                lineCoordinate.forEach(l => {
    
    
                  const line = new maptalks.LineString(l[0], lineOptions).addTo(layer)
                })

                _t.drawMark(centerCoordinate, layer)
                layer.setOptions({
    
    
                    // 启用高度绘制
                    enableAltitude: true,
                    altitudeProperty: 'altitude',
                    // 绘制高度线
                    drawAltitude: {
    
    
                        // lineWidth: 1,
                        // lineColor: '#000',
                        // 高度面的填充色
                        polygonFill: options.symbol.polygonFill,
                        // 高度面的透明度
                        polygonOpacity: options.symbol.polygonOpacity
                    }
                })
                layer.addTo(_t.mapEngine)
            },

            /**
             * 根据geojson画区域面
             * @param geoJson geoJson数据
             * @param layer 需要话的图层
             */
            drawAreaPlan(geoJson, layer, options, isActions) {
    
    
                const geometry = maptalks.GeoJSON.toGeometry(geoJson)

                const centerCoordinates = []

                if (geometry) {
    
    
                    geometry.forEach(g => {
    
    
                        g.setSymbol(options.symbol)
                        const gProperties = g.properties
                        // 避免信息覆盖
                        gProperties.altitude = options.properties.altitude
                        g.setProperties(gProperties)
                        // 设置图形层级
                        g.setZIndex(options.zIndex)
                        // 设置信息框
                        g.setInfoWindow({
    
    
                            title: g.properties.name,
                            content: '<br style="color:#f00">中心点:' + g.properties.center + ' </br>行政区划:' + g.properties.adcode + ' </br>父级行政区划:' + g.properties.parent.adcode + '</div>'
                        })
                        g.setMenu({
    
    
                            width: 160,
                            custom: false,
                            items: [
                                {
    
     item: '菜单一', click: function() {
    
     alert('Query Clicked!'); return false } },
                                '-',
                                {
    
     item: '菜单二', click: function() {
    
     alert('Edit Clicked!') } },
                                {
    
     item: '菜单三', click: function() {
    
     alert('About Clicked!') } }
                            ]
                        })
                        // 鼠标交互事件监听
                        g.on('mouseenter', function(e) {
    
    
                            e.target.updateSymbol({
    
    
                                polygonFill: '#ffaeb0'
                            })
                        }).on('mouseout', function(e) {
    
    
                            e.target.updateSymbol({
    
    
                                polygonFill: 'rgb(135,196,240)'
                            })
                        })

                        if (isActions) {
    
    
                            g.on('click', function(e) {
    
    
                                e.target.openInfoWindow(e.coordinate)
                            });
                        }

                        // 获取中心坐标
                        centerCoordinates.push(g.properties)
                    })
                }
                layer.addGeometry(geometry)

               return centerCoordinates
            },
// mark
            drawMark(centerPointList, layer) {
    
    
                if (!centerPointList) {
    
    
                    console.log('无区域中心点数据')
                    return
                }
                const info = {
    
     content: '', width: 150, minHeight: 100 }
                const result = []
                // 这里 d 的数据格式是数组,如:[-0.113049, 51.498568]
                centerPointList.forEach(d => {
    
    
                    if (!d.info) {
    
    
                        d.info = info
                    }
                    // 设有高度、高亮的mark
                    const mark = new maptalks.Marker(d.center, {
    
    
                        // 设置了这个属性,会替换默认的图标
                        // symbol: {
    
    
                        // markerFile: 'foo.png',
                        // textName: d.name
                        // },
                        properties: {
    
    
                            // 高度设置
                            altitude: 400
                        }
                    }).addTo(layer)
                    mark.setInfoWindow({
    
    
                        title: d.name,
                        content: '<div>' + d.adcode + '</div>',
                        // autoPan: true,
                        width: d.info.width,
                        minHeight: d.info.minHeight,
                        // 'custom': false,
                        // 点击打开和关闭
                        // autoOpenOn: 'click',
                        // autoCloseOn: 'click'
                    })

                    // 没有高度的mark
                    // new maptalks.Marker(d).updateSymbol({
    
    
                    //   markerOpacity: 0.5,
                    //   markerFill: '#bbb'
                    // }).addTo(layer)

                    mark.setZIndex(1000)
                    result.push(mark)
                })
                return result
            },

            getOptions() {
    
    
                return {
    
    
                    zIndex: 10,
                    symbol: {
    
    
                        // 线色
                        lineColor: '#ff87a4',
                        // 线宽
                        lineWidth: 1,
                        // 填充色
                        polygonFill: 'rgb(135,196,240)',
                        // 不透明度
                        polygonOpacity: 0.9,
                    },
                    properties: {
    
    
                        // 高度设置(不生效,需要设置图层的高度polygonOpacity属性)
                        altitude: 400
                    }
                }
            }

The effect is as follows:

Here I am just providing ideas. To put it bluntly, the implementation effect of this method is not as good as that of three.

image-20211030152616191.png

Guess you like

Origin blog.csdn.net/qq_28911061/article/details/122515860
Recommended