• Welcome to Journal web site.

我是 PHP 程序员

- 开发无止境 -

Next
Prev

微信小程序手绘地图实现之《Canvas》 - 码农教程

Data: 2015-01-02 07:14:18Form: JournalClick: 0

微信小程序手绘地图实现之《Canvas》
微信小程序手绘地图实现之《Canvas》 - 码农教程

本文章向大家介绍微信小程序手绘地图实现之《Canvas》,主要包括微信小程序手绘地图实现之《Canvas》使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

 

环境:微信SDK2.9+

正题:

先创建一个地图组件

  1 <template>  2   <view class="customCanvasComponent">  3     <!-- 建立画布坐标系 -->  4     <canvas  5       :style="{  6         width: `${options.style.width}rpx`,  7         height: `${options.style.height}rpx`,  8         border: options.style.border,  9         background: options.style.background 10       }" 11       type="2d" 12       :id="customMapId" 13       :canvas-id="customMapId" 14       @click="clickToCanvas" 15       @touchstart="touchStartToCanvas" 16       @touchmove="touchMoveToCanvas" 17       @touchend="touchEndToCanvas"> 18       <!-- 由于微信限制 暂时只支持这种写法 请不要秀其他方式 否则凉凉 --> 19       <!-- Marker点集合 --> 20       <!-- <blank v-for="poi in handlerMarkerList" :key="poi.id"> 21         <cover-view 22           class="point" 23           @click="pointChange(poi)" 24           :style="{ 25             position: 'absolute', 26             display: 'flex', 27             flexDirection: 'column', 28             alignItems: 'center', 29             left: poi.x + 'px', 30             top: poi.y + 'px', 31             transform: `translate(-50%, -100%)` 32           }"> 33           <cover-image :style="poi.stringStyle" :src="poi.icon"></cover-image> 34           <cover-view class="labelView" :style="poi.stringLabelStyle"> 35             <cover-view class="labelTitle">{{poi.label}}</cover-view> 36           </cover-view> 37         </cover-view> 38       </blank> --> 39       <!-- WindowInfo窗体设置 --> 40       <blank v-if="checkPointMarker"> 41         <cover-view class="windowInfoGroupBox" :style="{ 42           position: 'absolute', 43           left: checkPointMarker.x + 'px', 44           top: checkPointMarker.y + 'px', 45           transform: `translate(-50%, calc(-100% - 90rpx))` 46         }"> 47           <cover-view class="infoTitle"> 48             <cover-view class="infoVoiceBtn"> 49               <cover-image class="infoImage" :src="checkPointMarker.image"></cover-image> 50               <cover-image class="playControl" src="https://weixin.xmzt.cn/static/scenic/tour_play@2x.png"></cover-image> 51               <cover-image class="playControl" src="https://weixin.xmzt.cn/static/scenic/tour_pause@2x.png"></cover-image> 52             </cover-view> 53             <cover-view class="infoContent"> 54               <cover-view class="title otext2"></cover-view> 55               <cover-view class="distance"></cover-view> 56             </cover-view> 57           </cover-view> 58           <cover-view class="btnTools"> 59             <cover-view class="btn"> 60               <cover-image src="https://weixin.xmzt.cn/static/scenic/tour_poi_voice@2x.png"></cover-image> 61               <cover-view class="btnText">解说</cover-view> 62             </cover-view> 63             <cover-view class="btn"> 64               <cover-image src="https://weixin.xmzt.cn/static/scenic/tour_poi_info@2x.png"></cover-image> 65               <cover-view class="btnText">详情</cover-view> 66             </cover-view> 67           </cover-view> 68         </cover-view> 69       </blank> 70       <!-- 预留控件 由于小程序限制机制 请使用时仅可使用顶级标签<cover-view><cover-image> --> 71       <!-- 默认返回处理后的Marker点集合 --> 72       <!-- ControlFirmware Left --> 73       <slot name="control-l"/> 74       <!-- ControlFirmware Right --> 75       <slot name="control-r"/> 76       <!-- ControlFirmware Top --> 77       <slot name="control-t"/> 78       <!-- ControlFirmware Bottom --> 79       <slot name="control-b"/> 80       <!-- 其他控件预留 --> 81       <slot name="other"/> 82       <!-- <cover-view class="toolsBox"> 83         <cover-view class="pointGroupBox"> 84           <blank> 85             <cover-view v-for="poi in handlerMarkerList" :key="poi.id" class="point" :style="{position: 'absolute', left: poi.x + 'px', top: poi.y + 'px'}"> 86               <cover-image :style="{...poi.style}" :src="poi.icon"></cover-image> 87               <cover-view class="labelView" :style="{...poi.labelStyle}"> 88                 <cover-view class="labelTitle">{{poi.label}}</cover-view> 89               </cover-view> 90             </cover-view> 91           </blank> 92         </cover-view> 93         <cover-view class="windowInfoGroupBox"> 94           测试 95           <cover-image style="" src="/static/images/scenic/tour_voice_poi_01@2x.png"></cover-image> 96         </cover-view> 97       </cover-view> --> 98     </canvas> 99     <!-- 建立与画布对应的平面坐标系 -->100   </view>101 </template>102 103 <script>104 import CustomCavnasMap from './map'105 let CustomMapInital = null106 export default {107   // 组件配置说明 必须基于某个地图提供商进行的适配  高德  百度  腾讯  谷歌108   // 这里使用高德109   props: {110     // 部分配置参数111     options: {112       type: Object,113       default: () => {114         return {115           // 样式层116           style: {117             // 宽高单位均为rpx118             width: 750,119             height: 1334,120             // 背景支持色值或者网络图片背景图121             background: 'pink',122             border: 'none'123           },124           // 坐标中心点 LngLat对象125           center: [113.9120864868165, 22.545537650869],126           // 地图范围 [LngLat, LngLat] 取点应为对角两个坐标 !!!注意坐标点位置 [右上<RT>, 左下<LB>]127           limitBounds: [[113.914489746094, 22.54744015693], [113.909683227539, 22.543635144808]],128           // 初始化地图层级129           initalZoom: 16,130           // 地图层级范围131           zooms: [16, 18],132           // 图层133           layers: [134             {135               // 图片覆盖物 坐标范围  !!!注意坐标点位置 [右上<RT>, 左下<LB>]136               limitBounds: [[113.914489746094, 22.54744015693], [113.909683227539, 22.543635144808]],137               // 覆盖物地址138               image: 'https://xxx/static/map-bg.jpeg',139               // 透明度140               opacity: 1,141               // 缩放范围142               zooms: [16, 19]143             }144           ],145           // 路线146           lineStyle: {147             lineWidth: 5,148             lineColor: 'red',149             lineArray: []150           },151           // 自定义Marker152           markers: [153             {154               icon: '/static/images/scenic/tour_voice_poi_01@2x.png',155               position: [113.9128,22.544674],156               style: {157                 width: '93rpx',158                 height: '105rpx',159                 position: 'relative',160                 top: '60rpx'161               },162               label: '(内测)城管大楼',163               labelStyle: {164                 position: 'relative',165                 top: '-90rpx',166                 left: '50%',167                 transform: 'translateX(-50%)',168                 background: '#FFF',169                 padding: '5rpx 10rpx',170                 fontSize: '28rpx'171               }172             },173             {174               icon: '/static/images/scenic/tour_voice_poi_01@2x.png',175               position: [113.911765,22.545397],176               style: {177                 width: '93rpx',178                 height: '105rpx',179                 position: 'relative',180                 top: '60rpx'181               },182               label: '(内测)凉亭',183               labelStyle: {184                 position: 'relative',185                 top: '-90rpx',186                 left: '50%',187                 transform: 'translateX(-50%)',188                 background: '#FFF',189                 padding: '5rpx 10rpx',190                 fontSize: '28rpx'191               }192             }193           ]194         }195       }196     },197     // canvasId198     customMapId: {199       type: String,200       default: 'customMap'201     }202   },203   data () {204     return {205       // initalZoom: null,206       // CustomMapInital: null, // 不要定义到data中 容易引发内存互换207       handlerMarkerList: [],208       checkPointMarker: null209     }210   },211   watch: {212     'options.lineStyle.lineArray': {213       handler (_new, _old) {214         if (_new !== _old) {215           this.drawLine(_new)216         }217       },218       deep: true219     }220   },221   methods: {222     initalCanvasMap () {223       // console224       CustomMapInital = new CustomCavnasMap({225         customMapId: this.customMapId,226         _component: this227       }, Object.assign({}, this.options, {228         markerCallBack: (list) => {229           console.log(list)230           this.handlerMarkerList = list231         },232         cilckPointChange: (info) => {233           if (info) {234             console.log(info)235             console.log('得到点击成功后的触发')236             this.pointChange(info)237           } else {238             console.log('得到点击空白的回调')239           }240         }241       }))256     },257     fetchCustomBoxSize () {258       nui.getImageInfo({259         src: '',260         success: (rect) => {261           console.log(rect.fillPath[0])262         }263       })264     },265     /**266      * @Function267      * @public 公共类方法268      * @return Object269      */270     // 设置缩放比例271     setZoom (zoom, callback) {272       // 最低限制为初始化的缩放比例273       if (zoom > this.options.initalZoom) {274         // 逻辑处理275         CustomMapInital.setZoom(this.initalZoom, callback)276       } else {277         CustomMapInital.setZoom(zoom, callback)278       }279     },280     // 获取缩放比例281     getZoom (callback) {282       if (callback) {283         callback && callback(CustomMapInital.getZoom())284       } else {285         return CustomMapInital.getZoom()286       }287     },288     /**289      * 
290      * @touch 事件向this.CustomMapInital触发291      */292     touchStartToCanvas (e) {293       CustomMapInital.touchStartToCanvas(e)294     },295     touchMoveToCanvas (e) {296       CustomMapInital.touchMoveToCanvas(e)297     },298     touchEndToCanvas (e) {299       CustomMapInital.touchEndToCanvas(e)300     },301     /**302      * @click 事件向下触发303      */304     clickToCanvas (e) {305       CustomMapInital.clickToCanvas(e)306       // 点击其他地方进行清空WindowInfo窗体307       this.checkPointMarker = null308     },309     /**310      * @param {info<Object>} 类型为Marker数据对象311      */312     pointChange (info) {313       this.checkPointMarker = info314     },315     /**316      * @param {lineArray<Array|Object>} 传入的线路数据317      * @param {Object} {longitude, latitude} 必须318      */319     drawLine (lineArray) {320       CustomMapInital.drawLine(CustomMapInital.LngLatConversionToPixel(lineArray))321     }322   },323   onReady () {324     this.initalCanvasMap()325   },326   onUnload () {327     CustomMapInital = null328   }329 }330 </script>331 332 <style lang="sass" scoped>333   $defaultBg: #FFF334   $bgF4: #F4F4F4335   $color3: #333336   $color6: #666337   $color9: #999338   // $defaultBg: pink339   // 取消默认样式340   cover-view341     overflow: initial !important342   .customCanvasComponent343     // .toolsBox344     //   position: absolute345     .point346       position: absolute347       z-index: -1348       display: flex349       flex-direction: column350       align-items: center351       .labelView352         border-radius: 10rpx353         background-color: $defaultBg354         .labelTitle355           font-size: 28rpx356     .windowInfoGroupBox357       background-color: $defaultBg358       border-radius: 10rpx359       width: 320rpx360       height: 228rpx361       box-shadow: 10rpx 10rpx 20rpx -10rpx $color6362       display: flex363       flex-direction: column364       z-index: 99365       .infoTitle366         display: flex367         align-items: center368         padding: 20rpx369         .infoVoiceBtn370           width: 120rpx371           height: 120rpx372           flex: 0 0 120rpx373           border: 1px solid $bgF4374           border-radius: 50%375           overflow: hidden376           position: relative377           cover-image378             width: 100%379             height: 100%380             object-fit: contain381           .playControl382             position: absolute383             width: 68rpx384             height: 68rpx385             top: 50%386             left: 50%387             transform: translate(-50%, -50%)388         .infoContent389           flex: 1390           margin-left: 20rpx391           .title392             font-size: 28rpx393             line-height: 28rpx394             min-height: 56rpx395             color: $color3396             font-weight: bold397             // margin-right: 58rpx398             overflow: inherit399           .distance400             font-size: 22rpx401             color: $color9402             // margin-right: 0.58rem403             margin-top: 10rpx404       .btnTools405         display: flex406         flex: 1407         .btn408           flex: 0 0 calc(50% - 40rpx)409           display: flex410           margin: 0 20rpx 15rpx 20rpx411           align-items: center412           justify-content: center413           border-radius: 30rpx414           cover-image415             width: 30rpx416             height: 30rpx417           .btnText418             color: $defaultBg419             font-size: 28rpx420         .btn:nth-child(1)421           background: #80D2FC422           background: linear-gradient(#80D2FC, #188EE9)423           background: linear-gradient(to right, #80D2FC, #188EE9)424         .btn:nth-child(2)425           background: #FBA326426           background: linear-gradient(#FBA326, #FBA326)427           background: linear-gradient(to right, #FBA326, #FBA326)428 </style>

.map.js

  1 module.exports = class CustomCavnasMap {  2   canvasContext = null  3   // 定义背景装载图  4   layersImages = []  5   // 初始化Lock锁超出最大值停止初始化  6   initLock = 0  7   maxLockValue = 1000  8   // 记录手指按下时的坐标 以及位置  9   startingCoordinate = null 10   // 旋转时中心点或者缩放时中心点 默认为画布起点 11   rotateCenter = { 12     x: 0, 13     y: 0 14   } 15   // 背景图的偏移量 16   offsetConfig = { 17     mapX: 0, 18     mapY: 0 19   } 20   // 捏合缩放倍数或者滚轮缩放倍数 21   mapScale = 1 22   // 捏合缩放状态 23   mapZoom = false 24   // 双指旋转角度地图旋转角度 25   mapRotate = 0 26   // 两指距离 27   mapDistance = 0 28   // 地图层级限制 最大值 默认两倍 29   mapMaxZoom = 2 30   // 地图层级限制 最小值 默认一倍 31   mapMinZoom = 1 32   // 惯性的运动距离 带方向的距离单位 33   inertialMotion = { 34     x: 0, 35     y: 0 36   } 37   // 新增拖拽惯性支持 摩擦系数&mu;s 范围应该在0-1之间 38   us = 0.9 39   // 惯性定时器 40   inertialMotionTimer = null 41   COMPUT_TIME = null 42   // 图片预加载对象 43   pictureExtractionObject = {} 44   // 点击Canvas后的点位 45   clickPoint = { 46     x: 0, 47     y: 0 48   } 49   // 点击触发后的状态 0未点击 1点击了 2点击了但是点击错了 50   clickStatus = 0 51   /** 52    * @methods 53    * @param {Object<customMapId,_component>} canvasOtions 画布对象 54    * @param {Object<style,center,limitBounds,initalZoom,layers>} options 地图参数管控 55    */ 56   constructor(canvasOtions, options) { 57     // super(this) 58     console.log('进入构造函数-->') 59     // Object.keys(options) 60     // 获取设备属性 61     this.asyncFetchSystemInfo() 62     // this.systemInfo = wx.getSystemInfoSync() 63     // 属性继承 64     Object.assign(this, canvasOtions, options) 65     // 手动处理范围值 66     this.zooms && (this.mapMaxZoom = this.zooms[1] - (this.initalZoom || this.zooms[0])) && (this.mapMinZoom = (this.zooms[0] - this.initalZoom) || 1) 67     console.log('当前限制范围为:' + this.mapMinZoom + '-' + this.mapMaxZoom) 68     // if (canvasOtions instanceof Object) { 69     //   this.canvasContext = wx.createCanvasContext(canvasOtions.customMapId, canvasOtions._component) 70     // } else { 71     //   this.canvasContext = wx.createCanvasContext(canvasOtions.customMapId) 72     // } 73     // 设置分辨率 74     // this.dpr = 1 75     // 设置画布实际大小 76     // this.canvasOptions = { 77     //   width: parseInt(this.rpxToPx(options.style.width) * this.dpr), 78     //   height: parseInt(this.rpxToPx(options.style.height) * this.dpr) 79     // } 80     // 获取Canvas节点元素 81     this.wxCreateSelectorQuery().select(`#${canvasOtions.customMapId}`).fields({ 82       node: true, 83       rect: true 84     }, res => { 85       // console.log(res) 86       this.customCanvas = res.node 87       // this.computedConversionData() 88       // this.createMapBGImage(rect.node) 89       this.dpr = this.systemInfo.pixelRatio 90  91       // this.dpr = 1 92       // 设置大小 93       this.customCanvas.width = parseInt(this.rpxToPx(options.style.width) * this.dpr) 94       this.customCanvas.height = parseInt(this.rpxToPx(options.style.height) * this.dpr) 95       // 获取画布context上下文 2d 96       this.ctxCanvas = this.customCanvas.getContext('2d') 97       // 获取画布context上下文 webgl 98       // this.glCanvas = this.customCanvas.getContext('webgl') 99       // console.log(this.customCanvas)100     }).exec()101     // 开始初始化自定义地图102     this.initalCanvasChange()103   }104   // 初始化Canvas画布对象105   initalCanvasChange() {106     if (this.customCanvas) {107       this.computedConversionData()108     } else {109       setTimeout(() => {110         console.log('设置延迟100ms进行渲染Canvas画布')111         this.initLock++112         this.initLock < this.maxLockValue && this.initalCanvasChange()113       }, 100)114     }115   }116   // 提供选择节点的公共方法117   wxCreateSelectorQuery() {118     if (this._component) {119       return wx.createSelectorQuery().in(this._component)120     } else {121       return wx.createSelectorQuery()122     }123   }124   // 计算两点坐标实际距离公式125   GetDistance(LngLat1, LngLat2) {126     var radLat1 = LngLat1[1] * Math.PI / 180.0127     var radLat2 = LngLat2[1] * Math.PI / 180.0128     var a = radLat1 - radLat2129     var b = LngLat1[0] * Math.PI / 180.0 - LngLat2[0] * Math.PI / 180.0130     var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)))131     s = s * 6378.137 // EARTH_RADIUS132     s = Math.round(s * 10000) / 10000133     return s134   }135   // 顺序构建map图库136   createMapBGImage() {144     // 清空页面绘制 2d145     this.ctxCanvas.clearRect(0, 0, this.customCanvas.width, this.customCanvas.height)146 147     // 绘制canvas背景颜色148     // this.ctxCanvas.fillStyle = this.style.background149     // this.ctxCanvas.fillRect(0, 0, this.customCanvas.width, this.customCanvas.height)150     // this.canvasContext.clearRect(0, 0, this.canvasOptions.width, this.canvasOptions.height)151     // this.glCanvas.clear(this.glCanvas.COLOR_BUFFER_BIT)152     // console.log(this.rotateCenter)153     // 设置旋转中心点154     this.ctxCanvas.translate(this.rotateCenter.x, this.rotateCenter.y)155     // 对画布进行旋转 暂时关闭旋转156     // this.ctxCanvas.rotate(this.mapRotate * Math.PI / 180)157     // 当绘制结束后 还原旋转中心点158     this.ctxCanvas.translate(-this.rotateCenter.x, -this.rotateCenter.y)159     this.ctxCanvas.save()160     // 循环进行处理图片 缩放 平移控制161     this.layersImages.map(img => {162       // console.log(img)163       // 设置图片透明度164       this.ctxCanvas.globalAlpha = img.opacity169       this.ctxCanvas.drawImage(img, 0, 0, img.width, img.height, this.canvasLimitConfig.offsetLeft + this.offsetConfig.mapX * this.dpr, this.canvasLimitConfig.offsetTop + this.offsetConfig.mapY * this.dpr, this.canvasLimitConfig.viewWidth * this.mapScale * this.dpr, this.canvasLimitConfig.viewHeight * this.mapScale * this.dpr)174       this.ctxCanvas.restore()175     })176     // 清除旋转角度177     // this.ctxCanvas.rotate(this.mapRotate)178     this.mapRotate = 0179     // console.log('绘画完成')180     // this.ctxCanvas.restore()181     this.ctxCanvas.save()182     this.COMPUT_TIME = new Date().getTime()183     console.log('开始计算坐标点:' + this.COMPUT_TIME)184     // 计算点185     this.drawMarker(this.markers)186   }187   // 绘制Marker景点 传入参数MarkerList对象188   drawMarker(infoList = []) {189     // console.log(infoList)190     if (infoList instanceof Array && infoList.length > 0) {191       // 计算之前 先得到图标192       if (Object.keys(this.pictureExtractionObject).length > 0) {193         // 开始绘制194         // 使用定位解决方案 避免canvas数据量过大造成卡顿 [定位方案更卡。。。]195         // this.LngLatToPixel()196         this.handlerMarkerList = infoList.map((item, index) => {197           item.stringStyle = ''198           Object.keys(item.style).map(key => {199             item.stringStyle += `${key}: ${item.style[key]};`200           })201           item.stringLabelStyle = ''202           Object.keys(item.labelStyle).map(key => {203             item.stringLabelStyle += `${key}: ${item.labelStyle[key]};`204           })207           return Object.assign(item, this.LngLatToPixel(item.position), {id: index})208         })209         // 创建ICON图标211         this.handlerMarkerList.map(item => {212           this.ctxCanvas.beginPath()213           this.ctxCanvas.arc(item.canvasX, item.canvasY, 5, 0, 2 * Math.PI)214           this.ctxCanvas.strokeStyle = 'red'215           this.ctxCanvas.fillStyle = 'pink'216           this.ctxCanvas.fill()217           this.ctxCanvas.stroke()218           this.ctxCanvas.restore()220           const w = this.rpxToPx(parseInt(item.style.width)) * this.dpr221           const h = this.rpxToPx(parseInt(item.style.height)) * this.dpr222           this.ctxCanvas.drawImage(this.pictureExtractionObject[item.icon], item.canvasX - w / 2, item.canvasY - h / 3 * 2, w, h)223           this.ctxCanvas.restore()224           this.ctxCanvas.rect(item.canvasX - w / 2, item.canvasY - h / 3 * 2, w, h)225           const clickPointX = this.clickPoint.x * this.mapScale * this.dpr + this.offsetConfig.mapX * this.dpr + this.canvasLimitConfig.offsetLeft226           const clickPointY = this.clickPoint.y * this.mapScale * this.dpr + this.offsetConfig.mapY * this.dpr + this.canvasLimitConfig.offsetTop229           if (this.clickStatus !== 0) {230             if (this.ctxCanvas.isPointInPath(clickPointX, clickPointY)) {231               this.cilckPointChange(item)232               this.clickStatus = 1233               console.log('成功触发画布点击回调')234             } else {235               console.log('点位错误')236             }237           }238         })239         if (this.clickStatus === 2) {240           // 触发未点中的回调241           this.cilckPointChange()242         }243         // console.log(this.handlerMarkerList)244         const END_TIME = new Date().getTime()245         246         console.log('计算结束:' + (END_TIME - this.COMPUT_TIME))247         this.markerCallBack(this.handlerMarkerList)248       } else {249         setTimeout(() => {250           this.drawMarker(infoList)251         }, 100)252       }253     }254   }255   LngLatConversionToPixel (LngLatArray = []) {256     if (LngLatArray instanceof Array && LngLatArray.length > 0) {257       return LngLatArray.map((item, index) => {258         return Object.assign(item, this.LngLatToPixel([item.longitude, item.latitude]), {id: index})259       })260     }261   }262   // 绘制线路263   drawLine(LinePathArray = []) {264     if (LinePathArray instanceof Array && LinePathArray.length > 0) {265       // 设置绘制样式266       this.ctxCanvas.strokeStyle = this.lineStyle.lineColor || '#000000'267       this.ctxCanvas.lineWidth = this.lineStyle.lineWidth || 5268       // 开始绘制269       LinePathArray.map((line, index) => {270         if (index === 1) {271     &              
Name:
<提交>