Commit 8a4f2259 authored by 张启明's avatar 张启明

test: 添加模型编辑、拾取类,完善粒子特效、图层管理、模型剖切

parent 0bfc17a3
#container {
position: relative;
height: 100vh;
width: 100vw;
}
.widgets {
......
......@@ -8,7 +8,11 @@
<app-underground></app-underground>
<app-soil-excavation></app-soil-excavation>
<app-particle-effect></app-particle-effect>
<app-tileset-clipping></app-tileset-clipping>
<app-layer-manager></app-layer-manager>
<div style="border: 1px solid white; padding: 5px; margin: 5px;">
<div><app-layer-manager></app-layer-manager></div>
<div>3D tiles 剖切:<app-tileset-clipping></app-tileset-clipping></div>
<div>3D tiles 位置编辑:<app-tileset-editor></app-tileset-editor></div>
<app-pick></app-pick>
</div>
</div>
</div>
......@@ -18,7 +18,8 @@ export class AppComponent implements OnInit {
this.viewer = this.viewerService.initViewer('container', {
sceneMode: smart3d.SceneMode.SCENE3D,
scene3DOnly: true,
terrainProvider: smart3d.TerrainManager.createWorldTerrain(),
// terrainProvider: smart3d.TerrainManager.createWorldTerrain(),
baseMapMode: smart3d.BaseMapMode.ESRI,
helper: false,
});
}
......
......@@ -14,6 +14,8 @@ import {
SoilExcavationComponent,
ParticleEffectComponent,
TilesetClippingComponent,
TilesetEditorComponent,
PickComponent,
} from './components';
@NgModule({
......@@ -29,6 +31,8 @@ import {
SoilExcavationComponent,
ParticleEffectComponent,
TilesetClippingComponent,
TilesetEditorComponent,
PickComponent,
],
imports: [
BrowserModule,
......
......@@ -8,3 +8,5 @@ export * from './underground';
export * from './soil-excavation';
export * from './particle-effect';
export * from './tileset-clipping';
export * from './tileset-editor';
export * from './pick';
#modal {
max-width: 80vw;
max-height: 80vh;
border: 1px solid #333;
border-radius: .3em;
box-shadow: 0 0 0 71vmax rgba(0, 0, 0, .8);
}
\ No newline at end of file
<p>layer-manager works!</p>
<select [(ngModel)]="currentLayerName" (ngModelChange)="handleCurrentLayerNameChange()">
<option *ngFor="let layerStore of layersStore" [value]="layerStore.name">
{{ layerStore.name }} —— {{ layerStore.type }}
</option>
</select>
<button (click)="locatingLayer()">定位到该图层</button>
<button (click)="removeLayer()">删除该图层</button>
<button (click)="toggleLayerShow()">显隐该图层</button>
<button (click)="addLayer()">添加图层</button>
<dialog id="modal" #dialog>
<form method="dialog">
<p>
<label for="layer-name">图层名:</label><input id="layer-name" type="text" [(ngModel)]="addLayerStore.name" name="name" />
</p>
<p>
<label for="layer-url">图层 URL:</label><input id="layer-url" type="url" [(ngModel)]="addLayerStore.url" name="url" />
</p>
<p>
<label for="layer-type">图层类型:</label>
<select id="layer-type" [(ngModel)]="addLayerStore.type" name="type">
<option [value]="LayerTypeMode.MODEL">模型</option>
<option [value]="LayerTypeMode.IMAGERY">倾斜摄影</option>
<option [value]="LayerTypeMode.POINTCLOUD">点云</option>
<option [value]="LayerTypeMode.TERRAIN">地形</option>
<option [value]="LayerTypeMode.IMAGEDATA">地图影像</option>
<option [value]="LayerTypeMode.PANORAMICIMAGE">全景影像</option>
<option [value]="LayerTypeMode.VECTOR">矢量</option>
<option [value]="LayerTypeMode.WMS">WMS服务</option>
<option [value]="LayerTypeMode.WMTS">WMTS服务</option>
</select>
</p>
<menu>
<button (click)="confirmAddLayer('canceled')">取消</button>
<button (click)="confirmAddLayer('confirmed')">确定</button>
</menu>
</form>
</dialog>
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
import { ViewerService } from 'src/app/services';
@Component({
selector: 'app-layer-manager',
......@@ -7,9 +8,118 @@ import { Component, OnInit } from '@angular/core';
})
export class LayerManagerComponent implements OnInit {
constructor() { }
public layersStore = [
{
name: '黄石海事倾斜',
url: 'http://172.16.126.125:8090/qlhtest/huangshihaishi/010202ban_qlh/tileset.json',
type: smart3d.LayerTypeMode.IMAGERY
},
{
name: '测绘大楼分层模型',
url: 'http://www.south-smart.com/w3d-data/modeldata/nanfangcehuiBIM/NFCH0911/CHDLFC/tileset.json',
type: smart3d.LayerTypeMode.MODEL
},
{
name: '南昌林业点云服务',
url: 'http://www.south-smart.com/w3d-data/modeldata/nanchanglinye/tileset.json',
type: smart3d.LayerTypeMode.POINTCLOUD
},
{
name: 'UTM51DOM服务(WMTS)',
url: 'http://172.16.10.221:8080/geoserver/gwc/service/wmts?layer=gwth:UTM51DOM',
type: smart3d.LayerTypeMode.WMTS
},
{
name: '甘肃联通植被控制点服务',
url: 'http://www.south-smart.com/w3d-data/modeldata/gansuliantong/vector/zbpoint/zbpoint.json',
type: smart3d.LayerTypeMode.VECTOR,
vectorStyle: new smart3d.VectorStyle({
fillColor: '#FF0000'
})
},
{
name: '甘肃联通其它道路服务',
url: 'http://www.south-smart.com/w3d-data/modeldata/gansuliantong/vector/road_QT/road_QT.json',
type: smart3d.LayerTypeMode.VECTOR,
vectorStyle: new smart3d.VectorStyle({
lineColor: '#FF0000',
lineWidth: 5
})
}
];
public currentLayerName = this.layersStore.length > 0 ? this.layersStore[0].name : '';
@ViewChild('dialog', { static: false }) dialog: ElementRef;
public addLayerStore = {
url: '',
name: '',
type: '',
};
public LayerTypeMode = smart3d.LayerTypeMode;
constructor(
private viewerService: ViewerService,
) { }
ngOnInit() {
for (const layerStore of this.layersStore) {
const layer = new smart3d.Layer({
...layerStore,
});
this.viewerService.addLayer(layer);
}
this.viewerService.currentLayerNameChange.emit(this.currentLayerName);
}
/**
* 定位到当前图层,依赖于 this.currentLayerName
*/
locatingLayer(): void {
this.viewerService.flyToLayerByName(this.currentLayerName);
}
addLayer() {
if (typeof this.dialog.nativeElement.showModal === 'function') {
this.dialog.nativeElement.showModal();
} else {
alert('你的浏览器不支持 dialog API。');
}
}
confirmAddLayer(type: 'canceled' | 'confirmed') {
if (type === 'confirmed') {
if (Object.values(this.addLayerStore).some(v => v === '')) {
alert('图层名、图层 URL 或图层类型不能为空');
return;
}
this.dialog.nativeElement.close();
try {
this.viewerService.addLayer({ ...this.addLayerStore });
this.layersStore.push({ ...this.addLayerStore });
} catch (e) {
alert(e.message);
}
} else if (type === 'canceled') {
this.dialog.nativeElement.close();
}
}
removeLayer() {
this.viewerService.removeLayerByName(this.currentLayerName);
const layerIndex = this.layersStore.findIndex(layer => layer.name === this.currentLayerName);
this.layersStore.splice(layerIndex, 1);
}
toggleLayerShow() {
this.viewerService.toggleLayerShow(this.currentLayerName);
}
handleCurrentLayerNameChange(): void {
this.viewerService.currentLayerNameChange.emit(this.currentLayerName);
}
}
<div>
<button (click)="enableParticleEffect('rain')">开启雨</button>
<button (click)="rainEffect ? disableEffect('rain') : enableParticleEffect('rain')">
{{ rainEffect ? '取消' : '开启' }}雨
</button>
<input type="range" (change)="handleChangeRange('rain', $event)" max="1" min="0" step="0.1" [disabled]="rainEffect === undefined">
<button (click)="enableParticleEffect('snow')">开启雪</button>
<button (click)="snowEffect ? disableEffect('snow') : enableParticleEffect('snow')">
{{ snowEffect ? '取消' : '开启' }}雪
</button>
<input type="range" (change)="handleChangeRange('snow', $event)" max="1" min="0" step="0.1" [disabled]="snowEffect === undefined">
<button (click)="enableParticleEffect('fog')">开启雾</button>
<button (click)="fogEffect ? disableEffect('fog') : enableParticleEffect('fog')">
{{ fogEffect ? '取消' : '开启' }}雾
</button>
<input type="range" (change)="handleChangeRange('fog', $event)" max="1" min="0" step="0.1" [disabled]="fogEffect === undefined">
<button (click)="enableParticleEffect('smoke')">添加烟</button>
<button (click)="enableParticleEffect('fire')">添加火</button>
......
......@@ -22,6 +22,29 @@ export class ParticleEffectComponent implements OnInit {
this.particleEffect = new smart3d.ParticleEffect(viewer);
}
disableEffect(type: string) {
switch (type) {
case 'rain':
if (this.rainEffect) {
this.rainEffect.clear();
this.rainEffect = undefined;
}
break;
case 'snow':
if (this.snowEffect) {
this.snowEffect.clear();
this.snowEffect = undefined;
}
break;
case 'fog':
if (this.fogEffect) {
this.fogEffect.clear();
this.fogEffect = undefined;
}
break;
}
}
enableParticleEffect(type: string) {
const viewer = this.viewerService.viewer;
const cartesian = Cesium.Cartesian3.fromDegrees(115, 24, 250);
......
export * from './pick.component';
<button (click)="handlePick('highlight')">高亮拾取</button>
<button (click)="handlePick('offset')">偏移拾取</button>
<button (click)="handlePick('transparent')">透明拾取</button>
<button (click)="clearPick()">清除拾取效果</button>
<button (click)="destroyPick()">取消拾取</button>
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PickComponent } from './pick.component';
describe('PickComponent', () => {
let component: PickComponent;
let fixture: ComponentFixture<PickComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PickComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PickComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { ViewerService } from 'src/app/services';
@Component({
selector: 'app-pick',
templateUrl: './pick.component.html',
styleUrls: ['./pick.component.css']
})
export class PickComponent implements OnInit {
private pick;
constructor(
private viewerService: ViewerService,
) { }
ngOnInit() {
}
handlePick(type: 'highlight' | 'offset' | 'transparent') {
const viewer = this.viewerService.viewer;
if (undefined !== this.pick) {
this.pick.destroy();
}
this.pick = new smart3d.Pick(viewer, smart3d.PickMode[type.toUpperCase()]);
this.pick.start();
this.pick.clickEvent.addEventListener((pickedObject, cartesian) => {
const degreesCartographic = this.viewerService.cartesianToDegreesCartographic(cartesian);
alert('拾取到的坐标:(' + degreesCartographic.longitude + ', ' + degreesCartographic.latitude + ', ' + degreesCartographic.height + ')');
});
}
clearPick() {
if (undefined !== this.pick) {
this.pick.clear();
}
}
destroyPick() {
if (undefined !== this.pick) {
this.pick.destroy();
}
}
}
import { Component, OnInit } from '@angular/core';
import { ViewerService } from 'src/app/services';
const tilesetUrl = 'https://www.south-smart.com/cesium/modeldata/nanfangcehuiBIM/NFCH0911/CHDLFC/tileset.json';
@Component({
selector: 'app-tileset-clipping',
templateUrl: './tileset-clipping.component.html',
......@@ -11,7 +9,6 @@ const tilesetUrl = 'https://www.south-smart.com/cesium/modeldata/nanfangcehuiBIM
export class TilesetClippingComponent implements OnInit {
public clippingPlane: 'X' | 'Y' | 'Z' = 'Z';
private tileset;
private clipping;
constructor(
......@@ -22,9 +19,15 @@ export class TilesetClippingComponent implements OnInit {
}
enableClipping(enable?: boolean) {
const currentLayerPrimitive = this.viewerService.getPrimitiveByLayerName(this.viewerService.currentLayerName);
if (!(currentLayerPrimitive instanceof Cesium.Cesium3DTileset)) {
alert('当前图层不是模型、倾斜或点云,不能编辑。');
return;
}
const viewer = this.viewerService.viewer;
if (this.clipping) {
if (undefined !== this.clipping) {
this.clipping.destroy();
}
......@@ -38,26 +41,12 @@ export class TilesetClippingComponent implements OnInit {
outline: true,
outlineColor: Cesium.Color.RED,
edgeStylingEnabled: true,
debugBoundingVolumesEnabled: true,
// debugBoundingVolumesEnabled: true,
};
const that = this;
function createAndStartClipping() {
that.clipping = new smart3d.Clipping(viewer, clippingOptions);
that.clipping.start(that.tileset, smart3d.ClippingMode[that.clippingPlane]);
}
if (undefined === this.tileset) {
this.tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
url: tilesetUrl,
}));
this.tileset.readyPromise.then(tileset => {
createAndStartClipping();
viewer.flyTo(tileset);
});
} else {
createAndStartClipping();
viewer.flyTo(this.tileset);
}
this.clipping = new smart3d.Clipping(viewer, clippingOptions);
this.clipping.start(currentLayerPrimitive, smart3d.ClippingMode[this.clippingPlane]);
viewer.flyTo(currentLayerPrimitive);
}
}
export * from './tileset-editor.component';
<button (click)="editTileset()">开始编辑</button>
<button (click)="saveEdit()">保存</button>
<button (click)="resetEdit()">重置</button>
<button (click)="cancelEdit()">取消编辑</button>
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TilesetEditorComponent } from './tileset-editor.component';
describe('TilesetEditorComponent', () => {
let component: TilesetEditorComponent;
let fixture: ComponentFixture<TilesetEditorComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TilesetEditorComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TilesetEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { ViewerService } from 'src/app/services';
@Component({
selector: 'app-tileset-editor',
templateUrl: './tileset-editor.component.html',
styleUrls: ['./tileset-editor.component.css'],
})
export class TilesetEditorComponent implements OnInit {
private tilesetEdit;
constructor(
private viewerService: ViewerService,
) { }
ngOnInit() {
}
editTileset() {
const currentLayerPrimitive = this.viewerService.getPrimitiveByLayerName(this.viewerService.currentLayerName);
if (!(currentLayerPrimitive instanceof Cesium.Cesium3DTileset)) {
alert('当前图层不是模型、倾斜或点云,不能编辑。');
return;
}
const viewer = this.viewerService.viewer;
if (undefined !== this.tilesetEdit) {
this.tilesetEdit.destroy();
}
this.tilesetEdit = new smart3d.TilesetEdit({
viewer,
model: currentLayerPrimitive,
});
this.tilesetEdit.start();
viewer.flyTo(currentLayerPrimitive);
}
saveEdit() {
if (undefined !== this.tilesetEdit) {
this.tilesetEdit.save();
// 测试缩放、旋转、平移
// this.tilesetEdit.scale(2);
// this.tilesetEdit.rotation(smart3d.EditAxisMode.Z, 45);
// this.tilesetEdit.translation(smart3d.EditAxisMode.Y, 30);
// 测试设置 3D tiles 的 modelMatrix
// this.tilesetEdit.pasteMatrix(Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(0, 0, 100)));
}
}
resetEdit() {
if (undefined !== this.tilesetEdit) {
this.tilesetEdit.reset();
}
}
cancelEdit() {
if (undefined !== this.tilesetEdit) {
this.tilesetEdit = this.tilesetEdit.destroy();
}
}
isUrl(value: string): boolean {
// const reg = /^https?:\/\/(.)*(:)?\/?/;
const strRegex = '^((https|http|ftp|rtsp|mms)?://)'
+ '?(([0-9a-z_!~*\'().&=+$%-]+: )?[0-9a-z_!~*\'().&=+$%-]+@)?' // ftp 的 user@
+ '(([0-9]{1,3}.){3}[0-9]{1,3}' // IP形式的URL:199.194.52.184
+ '|' // 允许 IP 和域名
+ '([0-9a-z_!~*\'()-]+.)*' // 域名的 www.
+ '([0-9a-z][0-9a-z-]{0,61})?[0-9a-z].' // 二级域名
+ '[a-z]{2,6})' // com
+ '(:[0-9]{1,4})?' // 端口
+ '((/?)|' // 如果没有文件名则不需要反斜杠
+ '(/[0-9a-z_!~*\'().;?:@&=+$,%#-]+)+/?)$'; // 文件路径
const re = new RegExp(strRegex);
return re.test(value);
}
}
export * from './viewer';
export * from './layer-manager';
export * from './layer-manager.service';
import { TestBed } from '@angular/core/testing';
import { LayerManagerService } from './layer-manager.service';
describe('LayerManagerService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: LayerManagerService = TestBed.get(LayerManagerService);
expect(service).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
import { ViewerService } from '../viewer';
@Injectable({
providedIn: 'root'
})
export class LayerManagerService {
private layerManager;
constructor(
private viewerService: ViewerService
) {
console.log(this.viewerService.viewer);
}
}
import { Injectable } from '@angular/core';
import { Injectable, EventEmitter } from '@angular/core';
@Injectable({
providedIn: 'root'
......@@ -8,37 +8,116 @@ export class ViewerService {
// tslint:disable-next-line: variable-name
private _viewer;
private layerManager;
private layers = [];
private measureHandler;
public currentLayerNameChange: EventEmitter<string> = new EventEmitter();
public currentLayerName: string; // 当前的图层名
constructor() {
console.log('%c viewer service constructed! ', 'background-color: red; color: white; padding: 5px');
this.handleMeasure = this.handleMeasure.bind(this);
this.currentLayerNameChange.subscribe(name => this.currentLayerName = name);
}
get viewer() {
return this._viewer;
}
/**
* 初始化 Viewer 和图层管理类
* @param container HTML 标签或标签 ID
* @param options 见 smart3d.Viewer
*/
initViewer(container: string | Element, options?: object): object {
if (!this._viewer) {
this._viewer = new smart3d.Viewer(container, options || {});
this.layerManager = new smart3d.LayerManager(this._viewer);
// tslint:disable-next-line: no-string-literal
window['viewer'] = this._viewer;
// tslint:disable-next-line
window['viewer'] = this._viewer; window['layerManager'] = this.layerManager; window['primitives'] = this.viewer.scene.primitives._primitives; window['layers'] = this.layers;
}
return this._viewer;
}
/**
* 添加图层
* @param layer smart3d.Layer
*/
addLayer(layer) {
this.layerManager.addLayer(layer);
if (!(layer instanceof smart3d.Layer)) {
layer = new smart3d.Layer(layer);
}
this.layers.push(layer);
this.layerManager.add(layer);
}
removeLayerByName(name: string): void {
const layer = this.getLayerByName(name);
const layerIndex = this.getLayerIndexByName(name);
if (undefined !== layer) {
this.layerManager.remove(layer);
this.layers.splice(layerIndex, 1);
}
}
toggleLayerShow(name: string) {
const layer = this.getLayerByName(name);
if (undefined !== layer) {
layer.show = !layer.show;
this.layerManager.setVisible(layer);
}
}
/**
* 根据图层名获取图层
* @param name 图层名
* @returns smart3d.Layer | undefined
*/
private getLayerByName(name: string) {
return this.layers.find(layer => layer.name === name);
}
flyToLayer(layer) {
private getLayerIndexByName(name: string): number {
return this.layers.findIndex(layer => layer.name === name);
}
/**
* 获取图层底下的原始数据
* @param name 图层名
* @returns Cesium3DTileset | DataSource | ImageryLayer | undefined
*/
getPrimitiveByLayerName(name: string) {
const layer = this.getLayerByName(name);
return layer ? this.layerManager.getLayer(layer) : undefined;
}
/**
* 将相机视角设为该图层
* @param layer smart3d.Layer
*/
flyToLayer(layer): void {
this.layerManager.flyTo(layer);
}
handleMeasure(measureMode: number, isGround?: boolean, showLabel?: boolean) {
/**
* 将相机视角设为图层名对应的图层
* @param name 图层名
*/
flyToLayerByName(name: string): void {
const currentLayer = this.getLayerByName(name);
if (undefined !== currentLayer) {
this.flyToLayer(currentLayer);
}
}
/**
* 开启测量
* @param measureMode 测量模型,例如距离测量、面积测量
* @param isGround 是否是贴地测量
* @param showLabel 是否显示测量结果
*/
handleMeasure(measureMode: number, isGround?: boolean, showLabel?: boolean): void {
if (
measureMode !== smart3d.MeasureMode.Distance &&
measureMode !== smart3d.MeasureMode.Angle &&
......@@ -56,4 +135,24 @@ export class ViewerService {
this.measureHandler.activate();
}
}
/**
* 笛卡尔坐标转角度制的经纬度高度
* @param cartesian 笛卡尔坐标
*/
cartesianToDegreesCartographic(cartesian: {
x: number;
y: number;
z: number
}): { longitude: number, latitude: number; height: number } {
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
const height = Cesium.Math.toDegrees(cartographic.height);
return {
longitude,
latitude,
height
};
}
}
/* You can add global styles to this file, and also import other style files */
body {
margin: 0;
padding: 0;
}
.text-white {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment