# 核心对象

以下我们将对vis3d.js中包含的各个类的使用中的关键点进行说明,同时也会阐释各个类之间的关系,各个类的具体属性及方法请查阅:api开发文档 (opens new window)

注:vis3d.js中的对象都需要通过 vis3d. 开头来进行调用,如调用量算对象:vis3d.measure,调用标绘对象vis3d.plost。

# 构建地图viewer(MapViewer (opens new window)

在创建地图对象的过程中,我们常用以下方式构建地图对象:

let mapViewer = (window.mapViewer = new vis3d.MapViewer(
    "mapContainer",
    mapConfig
));
let viewer = mapViewer._viewer;

其中 mapViewer._viewer即为原生Cesium.Viewerd对象。

mapConfig属性说明:详细说明

# 公共方法(util (opens new window)

在开发过程中,我们经常要重复使用到各种方法,此对象内部封装了多种常用方法。 如:cartesianToLnglat(世界坐标转经纬度)、computeArea(计算面积)、getCameraView(获取相机姿态)......

# 小工具

框架内置了许多小工具,方便大家在开发使用。

# 指北针及比例尺

可自定义指北针位置及初始化视角等属性。

let compassTool = new Navigation(viewer, {
    enableCompass: true, // 罗盘
    /* compass: { // 罗盘位置设置
        style: {
            top: "120px",
            left: "120px"
        }
    }, */
    enableZoomControls: true, // 缩放控制器
    enableDistanceLegend: true, // 比例尺
    /*  distanceLegend: { // 比例尺位置
            style: {
                top: "120px",
                left: "120px"
            }
        }, */
    enableCompassOuterRing: true, // 罗盘外环
    view: {
        "x": 117.16651563768625,
        "y": 31.826203338964064,
        "z": 1652.63605234587,
        "heading": 88.01757556163417,
        "pitch": -32.44506291616423,
        "roll": 0.0015492995898727518,
        "duration": 0
    } // 初始化视角
});

# 信息提示框

展示鼠标当前位置以及相机当前姿态。

let lnglatNavigation = new LatlngNavigation(viewer);

# 右键菜单

点击鼠标右键时,弹出操作菜单。

let rightTool = new RightTool(this.viewer, {});

# 创建图层(layer)

我们在框架内封装了多个服务加载类,只需要进行简单的配置,即可加载对应的地图服务。

# 普通加载

普通加载有以下两种方式:

  1. (推荐)使用new layer.Tool(),通过此对象add方法,传入不同type类型,进行不同图层的创建。
let layerTool = new vis3d.layer.Tool(viewer);
layerTool.add({
    type: "mapserver",
    iconImg: "./vis3d/images/baseMap/geoq.png",
    url: "http://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetPurplishBlue/MapServer"
})

type类型有:xyz、wfs、geojson、mapserver、arcgiscache、tdt、singleImage、tms、3dtiles、wms、wmts、grid、tencent、baidu、osm、urltemplate。

说明:layerTool.add()方法实际上与第2种方式原理是一致的。

  1. 通过vis3d.layer下的各个图层类来创建图层对象。 以下为layer所包含的图层类型:
图层服务类型 说明
TilesetLayer 3dtiles模型
MapserverLayer arcmap处理的规范切片
ArcgiscacheLayer arcgis标准mapserver服务
GridLayer 网格类型
GeojsonLayer geojson类型数据
TDTLayer 天地图
SingleImageLayer 单张图片
TMSLayer tms类型服务
XYZLayer xyz格式切片
WMSLayer ogc wms服务
WMTSLayer ogc wmts服务
TencentLayer 腾讯地图
BaiduLayer 百度地图
osmLayer osm服务
UrltemplateLayer cesium urltemplate服务
let layerObj = new vis3d.layer.ArcgiscacheLayer(viewer, opt);
layerObj.load();

说明:第1种和第2种方式相比,可创建一个layer.Tool对象后,通过该对象的load()方法重复多次加载不同类型的服务,并且用该对象对这些服务进行管理,所以推荐第1种。

# 快速加载

大多时候,我们不希望写繁琐的代码加载单个服务,此时就可以通过vis3d.layerload.add()方法来创建图层对象、加载地图服务。

vis3d.layerload.add({
    url: "http://mapgl.com/data/model/qx-simiao/tileset.json",
    maximumScreenSpaceError: 1,
    type: "3dtiles",
    center: {
        z: 120
    },
    flyTo: false
}, viewer);

此方法一般用于快速加载某类型数据。

# 标绘相关(plot)

vis3d中支持多种类型的标绘,并且针对标绘的各个阶段(如:开始标绘、开始编辑等)都加上了监听。仅需要如下创建对象即可:

let plotDrawTool = new vis3d.plot.Tool(window.viewer, {
    canEdit: true,
});
plotDrawTool.on("endCreate", function (entObj, ent) {
    // 创建完成后 
});
plotDrawTool.on("startEdit", function (entObj, ent) {
    // 开始编辑
});
plotDrawTool.on("endEdit", function (entObj, ent) {
    // 编辑完成后
});

下面我们就可以通过plotDrawTool来进行不同类型的标绘了。

# 普通标绘

vis3d中,我们可以快速进行点、线、面、模型以及其他多种类型对象的绘制。 目前支持的通用类型有:

标绘类型 说明
point
polyline 线
polygon
billboard 图标
circle
rectangle 矩形
gltfModel gltf/glb小模型

plotDrawTool.start({
    "type": "polyline", // 线标绘
    "style": { // 样式
        "clampToGround": true,
        "color": "#ffff00"
    }
});

# 军事标绘

并且在标绘中,也支持常见的军事类型标绘。

包含:1-攻击箭头、2-攻击箭头(平尾)、3-攻击箭头(燕尾)、4-闭合曲面、5-钳击箭头、6-单尖直箭头、7-粗单尖直箭头(带燕尾)、8-集结地、9-弓形面、10-直箭头、11-矩形旗、12-扇形、13-三角旗、14-矩形波浪旗、17-多边形、18-圆形等。

plotDrawTool.start({
    "type": "arrow", // 军事标绘
    "arrowType" : 1, // 箭头类型
    "style": { // 样式
        "color": "#ddff00",
        "heightReference": 1
    }
});

# 快速标绘

通常情况下,我们需要快速进行标绘,此时可使用vis3d.draw

vis3d.draw.start({
    type: "circle",
    style : {
        color : "#ff0000",
        colorAlpha:.1,
    },
    success(entObj: any) {
        
    }
}, viewer);

# 图上量算(measure)

vis3d中,支持以下类型的测量:

1-空间距离测量、2-贴地距离测量、3-空间面积测量、4-高度测量、5-三角测量、6-坐标量算、7-方位角测量、8-剖面测量、9-单点坡度。

与标绘类似,也可以给绘制对象绑定不同的监听事件(startEdit 开始编辑时 / endEdit 编辑结束时 / endCreate 创建完成后)。

// 创建对象
let measureTool = new vis3d.measure.Tool(viewer);
measureTool.on("(startEdit", function (ms) {
    // 开始编辑时
});
measureTool.on("endEdit", function (ms) { 
    // 编辑结束时
});
measureTool.on("endCreate", function (ms) { 
    // 创建完成后
});

// 开始量算
measureTool.start({
    type: 1
});

# 模型相关(tileset)

在Cesium中,osgb、bim以及3dmax等模型通常会转为3dtiles格式,针对3dtiles模型我们可以做许多特效。以下针对模型的特效均基于Cesium1.103之后开发。

# 模型剖切

模型剖切的原理是使用了Cesium.ClippingPlane。目前支持按方向剖切以及按角度剖切。

  1. 按方向剖切
let modelClip = new vis3d.tileset.Clip(viewer,{
    tileset : tileset, // 模型对象
    dir : new Cesium.Cartesian3(0.0, 1.0, 0.0) , // 剖切面法向量方向
    distance : 10 // 剖切距离
});
modelClip.start();
  1. 按角度剖切
let modelClip = new vis3d.tileset.Clip(viewer,{
    tileset : tileset, // 模型对象
    angle : 60 , // 剖切角度
    distance : 10 // 剖切距离
});
modelClip.start();

# 模型编辑

针对模型的位置、大小、姿态进行编辑。

# 模型压平

# 模型剖切

# 空间分析

# 可视域分析

展示3dtiles模型上的可见区与不可见区域。

let visualFieldTool = new window.vis3d.analysis.VisualFieldTool(
    viewer
);
visualFieldTool.startDraw({
      horizontalFov: 120, // 水平张角
      verticalFov: 60, // 垂直张角
      distance: 0, // 距离
      heading: 0, // 偏转角
      pitch: 0, // 仰俯角
      visibleAreaColor: "#00FF00", // 可视区域颜色
      visibleAreaColorAlpha: 0.5, // 可视区域颜色透明度
      hiddenAreaColor: "#FF0000", // 不可视区域颜色
      hiddenAreaColorAlpha: 0.5 // 不可视区域颜色透明度
});

# 缓冲区分析

一般来说,缓冲区是和业务进行结合的,此处仅供参考。核心是利用利用了plot以及turf.js。

let bufferDrawTool = new window.vis3d.plot.Tool(viewer, {
    canEdit: false,
});
// 1、点状缓冲区
const drawPoint = () => {
  bufferDrawTool.start({
    name: "图标",
    type: "billboard",
    style: {
      image: markerimg,
    },
    styleType: "billboard",
    success: function (entObj, ent) {
      let lnglats = entObj.getPositions(true);
      let turfObj = turf.point(lnglats);
      let buffered = turf.buffer(turfObj, Number(radius.value) / 1000, {
        units: "miles",
      });
      createPolygon(buffered);
    },
  });
}

// 2、线状缓冲区
const drawLine = () => {
  bufferDrawTool.start({
    type: "polyline",
    styleType: "polyline",
    style: {
      clampToGround: true,
      color: "#ffff00",
    },
    success: function (entObj, ent) {
      let lnglats = entObj.getPositions(true);
      let turfObj = turf.lineString(lnglats);
      let buffered = turf.buffer(turfObj, Number(radius.value) / 1000, {
        units: "miles",
      });
      createPolygon(buffered);
    },
  });
}

// 3、面状缓冲区
const darwArea = () => {
  bufferDrawTool.start({
    type: "polygon",
    styleType: "polygon",
    style: {
      color: "#00FFFF",
      colorAlpha: 0.5,
      fill: false,
      outline: true,
      outlineColor: "#ff0000",
      heightReference: 1,
    },
    success: function (entObj, ent) {
      let lnglats = entObj.getPositions(true);
      lnglats = lnglats.concat([lnglats[0]]);
      let turfObj = turf.polygon([lnglats]);
      let buffered = turf.buffer(turfObj, Number(radius.value) / 1000, {
        units: "miles",
      });
      createPolygon(buffered);
    },
  });
}

// 构建缓冲区范围
const createPolygon = (positions){}

# 限高分析

let limitHeightDrawTool = new window.vis3d.plot.Tool(viewer, {
      canEdit: false,
    });

limitHeightDrawTool.on("endCreate", function (entObj: any, ent: any) {
    const positions = entObj.getPositions();
    limitHeightDrawTool.removeAll();
    limitHeight = new window.vis3d.analysis.LimitHeight(viewer, {
        positions: positions,
        bottomHeight: Number(bottomHeight.value), // 底部高度
        topHeight: Number(topHeight.value) // 顶部高度
    });
});

# 方量分析

方量分析,原理是对面进行插值求和。纯前端中,求地形高度时,不通地图层级下的同一个点高度不同,所以此处会有较大的误差,若想精确求方量,建议结合原始tiff文件进行计算。

let drawTool = new vis3d.plot.Tool(viewer, {
    canEdit: false,
});
drawTool.on("endCreate", function (entObj, ent) {
    const positions = entObj.getPositions();
    let uniformData = window.vis3d.util.computeUniforms(
        positions,
        false,
        viewer
    );
    const minh = uniformData.minHeight; // 当前范围内的最低点高度
    const maxh = uniformData.maxHeight; // 当前范围内的最高点高度
    const digTotalV = digV(uniformData, minh); // 挖方量
    const fillTotalV = fillV(uniformData, maxh); // 填方量
});
// 挖方体积
const digV = (data, minh) => {
  if (!data) return;
  let uniforms = data.uniformArr;
  if (!uniforms || uniforms.length == 0 || !minh) return;
  let totalV = 0;
  for (let i = 0; i < uniforms.length; i++) {
    let item = uniforms[i];
    if (item.height > minh) {
      let v = item.area * (item.height - minh);
      totalV += v;
    }
  }
  totalV = Number(totalV).toFixed(2);
  return totalV;
}
// 填方体积
const fillV = (data, maxh) => {
  if (!data) return;
  let uniforms = data.uniformArr;
  if (!uniforms || uniforms.length == 0 || !maxh) return;
  let totalV = 0;
  for (let i = 0; i < uniforms.length; i++) {
    let item = uniforms[i];
    if (maxh > item.height) {
      let v = item.area * (maxh - item.height);
      totalV += v;
    }
  }
  totalV = Number(totalV).toFixed(2);
  return totalV;
}
 

# 通视分析

通视分析分两种:1、基于地形的通视分析 2、基于3dtiles的通视分析

// 1、地形通视分析
let ctgc_center = Cesium.Cartographic.fromCartesian(centerPosition);
ctgc_center.height = ctgc_center.height + 2;
let center = Cesium.Cartographic.toCartesian(ctgc_center);
let ctgc_aim = Cesium.Cartographic.fromCartesian(aimPosition);
ctgc_aim.height = ctgc_aim.height + 1;
let aim = Cesium.Cartographic.toCartesian(ctgc_aim);
// 计算障碍点坐标
let point = window.vis3d.util.getIntersectPosition({
    startPoint: center,
    endPoint: aim,
},viewer);

// 2、模型通视分析 (模型通视误差较大)
......

# 坡度分析

......

# 日照分析

let sunshine = new window.vis3d.analysis.Sunshine(viewer, {
    startTime: startDate,
    endTime: endDate,
    multiplier: 2400 // 播放速度
});
sunshine.start();

# 动态材质

此处动态材质分两种,一种是graphic的材质,一种是primitive的材质。

# 动态圆

let redEllipse = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(117.50, 38.0),
ellipse: {
    semiMinorAxis: 1500.0,
    semiMajorAxis: 1500.0,
    heightReference: 1,
    material: new window.vis3d.material.EllipseWave({
    duration: 2000,
    color: Cesium.Color.RED,
    }),
}
})

# 椎体扫描

let cylinder = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(117, 32, 150000),
    cylinder: {
        length: 299800,
        topRadius: 0.0,
        bottomRadius: 30000,
        material: new window.vis3d.material.CylinderScan({ //CircleFadeMaterial CircleFadeMaterial
        duration: 2000,
        color: Cesium.Color.RED,
        }),
    }
});

# 雷达椎体

此为primitive。

let length = 10000; // 椎体长度
// 地面位置(垂直地面)
let positionOnEllipsoid = Cesium.Cartesian3.fromDegrees(opt.x, opt.y, opt.z);
// 矩阵计算
let modelMatrix = Cesium.Matrix4.multiplyByTranslation(
Cesium.Transforms.eastNorthUpToFixedFrame(positionOnEllipsoid),
new Cesium.Cartesian3(0.0, 0.0, length * 0.5), new Cesium.Matrix4()
);
// 创建雷达放射波
let cylinderGeometry = new Cesium.CylinderGeometry({
length: length,
topRadius: 0.0,
bottomRadius: opt.bottomRadius || length * 0.5,
vertexFormat: Cesium.MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat
});
//  创建GeometryInstance
let redCone = new Cesium.GeometryInstance({
geometry: cylinderGeometry,
modelMatrix: modelMatrix,
});
// 创建Primitive
let radar = viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: [redCone],
appearance: new Cesium.MaterialAppearance({
    // 自定义纹理
    material: new Cesium.Material({
    fabric: {
        type: 'radarS',
        uniforms: {
        color: opt.color || new Cesium.Color(2, 1, 0.0, 0.8),
        repeat: 30.0,
        offset: 0.0,
        thickness: 0.3,
        },
        source: `
                    uniform vec4 color;
                    uniform float repeat;
                    uniform float offset;
                    uniform float thickness;
                    czm_material czm_getMaterial(czm_materialInput materialInput)
                    {
                        czm_material material = czm_getDefaultMaterial(materialInput);
                        float sp = 1.0/repeat;
                        vec2 st = materialInput.st;
                        float dis = distance(st, vec2(0.5));
                        float m = mod(dis + offset, sp);
                        float a = step(sp*(1.0-thickness), m);
                        material.diffuse = color.rgb;
                        material.alpha = a * color.a;
                        return material;
                    }
                        `
    },
    translucent: false
    }),
    faceForward: false, // 当绘制的三角面片法向不能朝向视点时,自动翻转法向,从而避免法向计算后发黑等问题
    closed: true // 是否为封闭体,实际上执行的是 是否进行背面裁剪
}),
}));

// 动态修改雷达材质中的offset变量,从而实现动态效果。
viewer.scene.preUpdate.addEventListener(function () {
let offset = radar.appearance.material.uniforms.offset;
offset -= 0.001;
if (offset > 1.0) offset = 0.0;
radar.appearance.material.uniforms.offset = offset;
});

# 动态球体

let ent = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(117.297110, 31.814635, 29.86),
    ellipsoid: {
      radii: new Cesium.Cartesian3(1000.0, 1000.0, 1000.0),
      material: new vis3d.material.Elipsoid({
        color: new Cesium.Color(1.0, 1.0, 0.0, 1.0),
        speed: 3
      })
    }
  })

# 飞线、流动线

 viewer.entities.add({
    polyline: {
        positions: positions,
        width: 1,
        material: new window.vis3d.material.LineFlow({ 
            color: Cesium.Color.fromRandom(),
            duration: 3000,
            image: texture,
            repeat: new Cesium.Cartesian2(1, 1) //平铺
        })
    }
  });

# 光罩

let shader = `
    uniform vec4 color;
    uniform float speed;
    #define pi 3.1415926535
    #define PI2RAD 0.01745329252
    #define TWO_PI (2. * PI)

    float rands(float p){
        return fract(sin(p) * 10000.0);
    }

    float noise(vec2 p){
        float time = fract( czm_frameNumber * speed / 1000.0);
        float t = time / 20000.0;
        if(t > 1.0) t -= floor(t);
        return rands(p.x * 14. + p.y * sin(t) * 0.5);
    }

    vec2 sw(vec2 p){
        return vec2(floor(p.x), floor(p.y));
    }

    vec2 se(vec2 p){
        return vec2(ceil(p.x), floor(p.y));
    }

    vec2 nw(vec2 p){
        return vec2(floor(p.x), ceil(p.y));
    }

    vec2 ne(vec2 p){
        return vec2(ceil(p.x), ceil(p.y));
    }

    float smoothNoise(vec2 p){
        vec2 inter = smoothstep(0.0, 1.0, fract(p));
        float s = mix(noise(sw(p)), noise(se(p)), inter.x);
        float n = mix(noise(nw(p)), noise(ne(p)), inter.x);
        return mix(s, n, inter.y);
    }

    float fbm(vec2 p){
        float z = 2.0;
        float rz = 0.0;
        vec2 bp = p;
        for(float i = 1.0; i < 6.0; i++){
            rz += abs((smoothNoise(p) - 0.5)* 2.0) / z;
            z *= 2.0;
            p *= 2.0;
        }
        return rz;
    }

    czm_material czm_getMaterial(czm_materialInput materialInput)
    {
        czm_material material = czm_getDefaultMaterial(materialInput);
        vec2 st = materialInput.st;
        vec2 st2 = materialInput.st;
        float time = fract( czm_frameNumber * speed / 1000.0);
        if (st.t < 0.5) {
            discard;
        }
        st *= 4.;
        float rz = fbm(st);
        st /= exp(mod( time * 2.0, pi));
        rz *= pow(15., 0.9);
        vec4 temp = vec4(0);
        temp = mix( color / rz, vec4(color.rgb, 0.1), 0.2);
        if (st2.s < 0.05) {
            temp = mix(vec4(color.rgb, 0.1), temp, st2.s / 0.05);
        }
        if (st2.s > 0.95){
            temp = mix(temp, vec4(color.rgb, 0.1), (st2.s - 0.95) / 0.05);
        }
        material.diffuse = temp.rgb;
        material.alpha = temp.a * 2.0;
        return material;
    }
`;
  Cesium.Material.EllipsoidElectricType = 'EllipsoidElectric';
  Cesium.Material._materialCache.addMaterial(
    Cesium.Material.EllipsoidElectricType,
    {
      fabric: {
        type: Cesium.Material.EllipsoidElectricType,
        uniforms: {
          color: new Cesium.Color(1.0, 0.0, 0.0, 0.7),
          speed: 1,
        },
        source: shader,
      },
      translucent: function (material) {
        return true;
      },
    }
  );
  let op = viewer.scene.primitives.add(
    new Cesium.Primitive({
      geometryInstances: new Cesium.GeometryInstance({
        geometry: new Cesium.EllipsoidGeometry({
          radii: { x: 1000, y: 1000, z: 1000 },
          maximumCone: Cesium.Math.PI_OVER_TWO,
        }),
        modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(
          Cesium.Cartesian3.fromDegrees(110.95115601835178, 34.93149218673128,100)
        ),
      }),
      appearance: new Cesium.MaterialAppearance({
        material: Cesium.Material.fromType('EllipsoidElectric', {
          color: Cesium.Color.GREEN,
          speed: 5,
        }),
      }),
    })
  );

# 扫描圆

let position = Cesium.Cartesian3.fromDegrees(117.28251052606026, 31.72);
let entity = viewer.entities.add({
    position: position,
    ellipse: {
        semiMinorAxis: 600.0,
        semiMajorAxis: 600.0,
        heightReference: 1,
        material: new window.vis3d.material.EllipseScan({//多个圆圈
        duration: 2000,//动画时长,单位:毫秒
        color: Cesium.Color.YELLOW
        }),
    }
});

# 动态墙

viewer.entities.add({
wall: {
    positions: positions1,
    width: 5,
    maximumHeights: maximumHeights1,
    minimumHeights: minimumHeights1,
    material: new window.vis3d.material.Wall({ //动画线材质
        color: Cesium.Color.RED,
        duration: 3000,
        image: texture,
        axisY: false,
        repeat: new Cesium.Cartesian2(1, 1) //平铺
    }),
}
});