前言
在上一篇中,对maptalks的基础功能,及地图如何绘制已经了解,对于有探索能力 的小伙伴可能已经完成了更加高级的功能,但在这里,作为手册性质还是会慢慢记录下开发中的细节。
客户需要的效果千姿百态,但也不可逃离的是功能性的变化。
下面的例子基于上一遍的例子进行
mark
实际应用中的创建与消除
在实际应用中,mark标记,是随着用户选择的类型进行显示,那么这涉及到了mark的消除与创建。
- 我们先定义一个页面,如下,在地图上方留一个工具栏
<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>
效果不知道怎么传视频。代码已竟可能全了,没有全贴是不想占太多空间;
这里说一下,它的一些方法
- layer.clear() ,他会清除图层上的所以东西;
- mark.remove() , 移除mark可以用这个,不过这个需要mark对象调用,如果前端要实现上述功能,那么就要保存mark列表,这个很不明智,遇到撒点多的时候,这个前端可能承受不了;所以这里缓存了每个类型的图层,使用图层进行操作;
自定义图标
这里就以vue的logo作为替换图标进行示例
代码语言:javascript复制顶部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)
这里markerFile才是替换图标的,markerType,marker类型,它表示的是什么样的图标,当你有markerFile属性时,它是覆盖不了的,没有markerFile时,可以以markerType进行显示,它有几种类型:ellipse cross x diamond bar square triangle pin pie
,效果可以自己设置一下看看。
效果如下:
增加动画效果
增加了自己的mark后,可能会要求视觉上的一些效果,mark也提供了animate
的方法设置自己的动画,那么我们就设置一个mark出现时的动画,vue logo的横向展开,
在上面代码的基础上增加下面代码,然后将new maptalks.Marker
里默认设置的symbol.markerHeight
设置为0, symbol.markerWidth
设置为10,表示初始高度0, 初始宽度10,,变换到高度40, 宽度40。
mark.animate({
symbol: {
markerWidth: 40,
markerHeight: 40
},
properties: {
altitude: 800
}
}, {
duration: 150,
}, function (frame) {
if (frame.state.playState === 'finished') {
console.log('animation finished');
}
});
当我们点击学校或医院选项时,这些撒点就自动展开,也可以增加监听,在每个动画阶段都可以做一些处理。
完整的代码:
代码语言:javascript复制 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
},
可以看到,在一开始创建mark对象时,设置的宽度width
只有10,这就是动画的开始宽度,之后在animate方法里设置的属性就是需要变化的属性,也是最终的属性,通过duration
控制动画的执行的时间.
工具
这里基本都是symbol,那么这里还是要再提一次,就是symbol
,涉及这个属性的,我们都可以去查它的文档,因为它是一个系统,都是统一的完整的;
下面的相关属性注释第一个比较全,后面的差不多,所以不会都标注
测距工具
API: Class: DistanceTool (maptalks.org)
距离测算,复制官方案例,进行稍微修改,增加结束事件,
代码语言:javascript复制 /**
* 测距工具
* @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() '米')
})
},
增加工具按钮:
这里增加了【测距】按钮,在点击时激活测绘过年过节,再次点击时,关闭测绘
代码语言:javascript复制 /**
* 创建工具栏
*/
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)
},
效果:
上面这个功能是,在点击【测距】后开启测距模式,但也有的情况下是,点击一次按钮,测一次,这个在这里也是可以实现的,maptalks提供了这样的api,只需要在options里增加“onece”属性,并为true就行了。
代码语言:javascript复制 /**
* 测距工具
* @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() '米')
})
}
如上配置的话,在addToolbar
这个方法里,我们也不需要修改,因为,这个onece的属性,在一次测绘后,也是禁用的,所以,我们addToolbar
里的方法还是可以延用。
绘制工具
同样,可以直接把官方demo拿过来改改。
代码语言:javascript复制/**
* 绘制工具
*/
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)
},
效果:
测面工具
另一个工具也是一个比较有面子的工具:
代码语言:javascript复制 /**
* 测面工具
*/
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
}
绘制工具栏里,添加这个菜单;
代码语言:javascript复制 {
item: '测面工具',
click: function () {
if (_t.areaTool.isEnabled()) {
_t.areaTool.disable()
} else {
_t.areaTool.enable()
}
}
},
在绘制工具栏里也添加一个菜单,然后就是下面的效果:
地图动画
如果,你的页面一打开,镜头由上到下,慢慢的,360度旋转后定位到指定为,然后图标跳出来,这样的一个效果,一定是能够俘获大部分的心的。
在maptalks里也提供了api,我们也只是调用一下:
代码语言:javascript复制 /**
* 地图动画
*/
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)
}
这个我们软件,截不了 动图,这个可也代码跑一下就行;
而一般会带上marker,marker也是动画的,两个合起来一看就是比较完美的。
所以,我这里把上面做的学校和医院的功能,拿下来,组合一下:
代码语言:javascript复制 /**
* 地图动画
*/
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)
}
我再增加了一个播放按钮,可以重复播放,恩。没有软件,没有gif.
聚合
聚合这个功能也不得不说,所有的地图设计到缩放,都会有这个功能。
这个单靠maptalks是有点难的,但是用maptalks的好处就是,它有很多插件,所有这个聚合功能我们也用插件来实现:
文档: GitHub - maptalks/maptalks.markercluster: A layer of maptalks to cluster markers.
引入插件: npm install maptalks.markercluster
使用如下;
代码语言:javascript复制import * as maptalks from 'maptalks'
import {ClusterLayer} from 'maptalks.markercluster'
我们将之前写的marker页面修改下:
这次我们创建的图层是markercluster,会有些不一样,我们只将图层创建方式变更一下,相应注释我也添加了,再看代码会比较清晰些。
这里的symbol,我没有写全,一是直接复制官网的,二是懒了,它和其他的symbol是一样的,都可以复制的。
代码语言:javascript复制// 先创建图层
// 创建学校和医院的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)
addMarker
方法修改:
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
},
效果如下:
底图风格
常规的地图设计不好,会使看的人很疲惫,所以可以换一个风格。
这个比较简单,就是一个属性: 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%)'
})
})
},
它提供了两种:
效果:
热力图
热力图,它是集成了heatmap.js
增加热力图层插件 npm install maptalks.heatmap
添加方式也是它的一个方式,它的数据是一个坐标数组,自己变量添加就行,这里就把做
代码语言:javascript复制// 获取热力点
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)
首先要了解一下热力的呈现方式
- 以点为中心向外渐变;
- 多个点可以叠加,或多个点在聚集在一起,呈现出面;
- 每个点的显示是一样的,都有一个热力梯度,就是从外到内的一个颜色变化(从浅到什);
所以它和上面的几何不一样,几何需要3个点以上,而热力图最少只需要一个:
上面代码也对热力属性进行了标注:
- heatValueScale: 这个值的作用不是很理解,
- forceRenderOnRotating:旋转时强制刷新
- forceRenderOnMoving:移动时强制刷新
- blur:模糊因子, 值越大,越平滑,默认值是15
- radius:每个数据点的半径(默认值25,也建议25,我建议不能小于20)
- minOpacity:最小不透明读,越小越透明
- gradient:热力梯度,是热力点外围的颜色值,从外围到里,值是递增的,最大值就是中心的位置
上述值都有默认配置,可以直接使用,也可以自定义;
3D - three.js
除了使用上面的echarts,还有专门做三维的前端开发框架three.js,这个还是比较出门的,这个就比echarts好了很多,而且它集成到地图后,有做绑定,所以,基本上是和地图的操作同步,而且感觉简单许多;
maptalks集成three后 的文档:maptalks.three/API.ZH-CN.md at master · maptalks/maptalks.three · GitHub
npm install maptalks.three // 这里安装128的版本就行,其他版本会出现报错 npm install three@0.128.0
创建板块
maptalks集成了three.js,有些转为maptalks定制的用法,但基本上还是和three的一样,只是看maptalks.three 的文档,是不会有什么进步的,有需要制作特别厉害的效果,还是看three的官方文档。
代码语言:javascript复制 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)
}
上面写了两种挤压出物的方式:toExtrudeMesh
,toExtrudePolygon
,其实还有一种,在源码里toExtrudePolygons
这个是可以传入一个数组,但是实际使用时,它得到的是一个对象,并且,执行scene.add(mesh)
还会报错,导致得到的结果是一个面,而不是一个三维物体。
效果如下:
现在已经生成了地图块,而且还有点难看,和画区域面一样,给他分块,然后加入鼠标事件。
分块
这个就是在生成的物体上增加分割线,可以使用之前的方法,再增加一个图层,生成一个面,面的透明度设为0,线框,宽度1 ,再加个猛男粉,OK
代码语言:javascript复制 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)
}
来看看效果
这是贴在地图上的,有点不好看,我们再设个高程
代码语言:javascript复制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)
}
效果:
事件
three创建的对象文档:maptalks.three/API.ZH-CN.md at master · maptalks/maptalks.three · GitHub
可以看出适配还是挺不错的。
设置方式和区域面的一样,不过存在一个问题,先看代码,也是在之前的方法中新增了on
监听
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)
},
可以看到,我在事件中都加了layer.redraw()
,为什么呢?
官方文档及示例中并没有提及要这么做,我一直以为它和矢量图形一样,会自动渲染,但并没有,而且,总是莫名其妙的就渲染了,后来发现,每当我移动地图、或旋转时,它的图形才会变化,这让我想起了,矢量图层有一个设置在移动、旋转时强制渲染,和现在的情况非常相似,然后再次看它的文档,并没有提及渲染的方法,但让人高兴的是,three图层是继承于CanvasLayer
,拥有它的所有方法,到这,问题就解决了,只要调用redraw()
重新绘制three图层就可以了。
有一点不同的是在maptalks.three里,
setSymbol
参数是传的材质对象
来看效果图:
不借助其他插件绘制3D
在不使用three.js 和 echarts的情况下,也可以通过它自身的面去搭建一个3D结构,用过3维模型的可能比较清晰一点,在三维软件中,面可以被挤压,这样就可以得到一个多面体,这里的three就是这个道理,最传统的就是在三维空间里,将面组合,在maptalks里也可以实现。
将之前的代码稍微修改下:
代码语言:javascript复制 /**
* 再图层上画面
*/
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
}
}
}
效果如下:
这里我只是提供思路,直白的说这种方法的实现效果没有three的好