Potree 003 基于Potree Desktop创建自定义工程
第三方库js库选择dojo,其官网地址为https://dojotoolkit.org/,git地址为https://github.com/dojo/dojo,demo地址为https://demos.dojotoolkit.org/demos/,如果打不开,可以多刷新几次。
因为使用ArcGIS API for js开发,接触到了dojo,dojo是一个非常优秀的js框架库,包含的内容非常全,做单页面Web应用程序是一个非常不错的选择。
Main.js文件主要还是设置electron的一些参数,代码如下。
var electron = require("electron"); var app = electron.app; var BrowserWindow = electron.BrowserWindow; var path = require("path"); var Menu = electron.Menu; var myMainWindow; function CreateWindow() { // 新建主窗体,设置图标、加载主页面 myMainWindow = new BrowserWindow({ webPreferences: { nodeIntegration: true, backgroundThrottling: false }, show: false }); myMainWindow.maximize(); myMainWindow.setIcon(path.join(__dirname, "Res/Images/Ico64.png")); myMainWindow.loadFile(path.join(__dirname, "Index.html")); myMainWindow.show(); //把electron带的菜单隐藏 Menu.setApplicationMenu(null); //屏蔽警告 process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true' myMainWindow.on("closed", function () { myMainWindow = null; }); } app.on("ready", CreateWindow); // Quit when all windows are closed. app.on("window-all-closed", function () { if (process.platform !== "darwin") { app.quit() } }); app.on("activate", function () { if (myMainWindow === null) { CreateWindow() } });
我们在Potree Desktop代码中的Main.js文件的基础上上做了修改。通过引用electron,得到electron、app、BrowserWindow、path、Menu等引用,并定义了CreateWindow函数。
该函数是Main.js最主要的函数,在代码中,我们创建了一个BrowserWindow窗体,并最大化该窗体。设置了窗体图标、加载的页面等。最后调用窗体的Show函数,打开窗体。BrowserWindow窗体自带了一个菜单,通过Menu.setApplicationMenu(null);隐藏electron自带的菜单。当窗体关闭的时候,设置myMainWindow变量的值为null。
后面的代码主要针对全局App,当系统环境准备好的时候,调用CreateWindow函数。当所有的窗体都关闭后,退出App。当App被激活的时候,如果myMainWindow变量为null,则调用CreateWindow函数。这些逻辑基本上都是从Potree Desktop中拷贝过来的,修改了下变量名称,逻辑未变。
代码运行Mian.js,实例化BrowserWindow窗体,加载Index.html,此时系统就进入了Index.html页面。
我们的目的是做一个Web单页面应用程序,样式以及操作方式要尽量能贴近普通的桌面应用程序。整体布局包括菜单栏、工具栏、左侧工程树以及中间的主显示区。
首先引用外部的js和css文件,包括dojo、Potree定义的文件以及我们系统中自己定义的一些文件,代码如下。
<link rel="stylesheet" type="text/css" href="./libs/potree/potree.css"> <link rel="stylesheet" type="text/css" href="./libs/jquery-ui/jquery-ui.min.css"> <link rel="stylesheet" type="text/css" href="./libs/openlayers3/ol.css"> <link rel="stylesheet" type="text/css" href="./libs/spectrum/spectrum.css"> <link rel="stylesheet" type="text/css" href="./libs/jstree/themes/mixed/style.css"> <link rel="stylesheet" href="Res/dojo/dijit/themes/claro/claro.css" /> <link rel="stylesheet" href="Res/dojo/dojox/grid/resources/claroGrid.css" /> <link rel="stylesheet" href="Res/dojo/dojox/form/resources/CheckedMultiSelect.css" /> <link rel="stylesheet" href="Res/dojo/dojox/form/resources/RangeSlider.css" /> <link rel="stylesheet" href="Res/dojo/dojox/widget/ColorPicker/ColorPicker.css" /> <link rel="stylesheet" href="Index.css" /> <link rel="stylesheet" href="AppUI/AppMenuUI.css" /> <link rel="stylesheet" href="AppUI/AppToolBarUI.css" /> <script> if (typeof module === 'object') { window.module = module; module = undefined; } </script> <script src="./libs/jquery/jquery-3.1.1.min.js"></script> <script src="./libs/spectrum/spectrum.js"></script> <script src="./libs/jquery-ui/jquery-ui.min.js"></script> <script src="./libs/other/BinaryHeap.js"></script> <script src="./libs/tween/tween.min.js"></script> <script src="./libs/d3/d3.js"></script> <script src="./libs/proj4/proj4.js"></script> <script src="./libs/openlayers3/ol.js"></script> <script src="./libs/i18next/i18next.js"></script> <script src="./libs/jstree/jstree.js"></script> <script src="./libs/potree/potree.js"></script> <script src="./libs/plasio/js/laslaz.js"></script> <script src="./libs/three.js/build/three.js"></script> <script src="./libs/three.js/build/three.js"></script>
接下来定义对electron和Nodejs中一些功能的引用。
<script> var Electron = require('electron'); var ElectronDialog = require('electron').remote.dialog; var NodeFS = require('fs'); var NodePath = require('path'); var NodeChildProcess = require('child_process'); </script>
ElectronDialog可弹出本地对话框,包括消息对话框,是否对话框等,类似于.Net中的MessageBox。NodeFS模块可对文件进行操作,类似于.Net中的File类。NodePath模块可文件路径进行操作,类似于.Net中的Path类。NodeChildProcess模块可调用本地的exe文件,并可以传入参数,捕捉输出信息等。
接下来是使用dojo库的一些配置以及Index.html页面直接引用的一些js文件。
<script> var dojoConfig = { async: true, parseOnLoad: false, //用于页面加载时立即加载的JS依赖 deps: ["dojo/parser"], callback: function (parser) { }, //加载一个模块的请求超时时间,如果超时说明加载模块失败 waitSeconds: 10, //如果为true可以避免模块缓存(原理就是在请求模块的URL加上当前时间戳) //cacheBust: true, } </script> <script src="Res/dojo/dojo/dojo.js"></script> <script src="Framework/URLHelper.js"></script> <script src="Framework/DateFormat.js"></script> <script src="Index.js"></script>
Index.html文件最后,是该页面的布局代码,如下所示。
<body class="claro"> <table id="UI_Main_Table"> <tr style="height:30px"> <td padding:0"> <div id="UI_AppMenuUI_Div"></div> </td> </tr> <tr style="height:30px"> <td padding:0"> <div id="UI_AppToolBarUI_Div"></div> </td> </tr> <tr> <td style="padding:0;"> <div data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="gutters:true, liveSplitters:false" style="width: 100%; height: 100%; margin: 0; padding: 0; "> <div id="UI_AppTreeUI_Div" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="minSize:20, region:'leading', splitter:true" style="width: 260px;"></div> <div id="UI_Center_BorderContainer" data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="region:'center'" style="width: 100%; height: 100%; margin: 0; padding: 0; "> <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'center',splitter:true"> <div class="potree_container" style="height:100%"> <div id="potree_render_area"></div> </div> </div> </div> </div> </td> </tr> </table> </body>
我们在body中直接定义了一个Table作为根元素,该表格分三行,第一行菜单栏,第二行工具条,第三行就是主显示区。如果想在底部增加状态栏,可再增加一行。
主显示区使用了dojo中定义的dijit/layout/BorderContainer,在BorderContainer中添加了两个dijit/layout/ContentPane,其中一个ContentPane的region属性设置为leading,另外一个ContentPane的region属性设置为center,并都设置splitter=true。这样主显示区域左侧会添加一个区域,宽度为260px,剩下的为中间区域,并且两区域可左右调整大小。
菜单我们使用dojo中定义的dijit/MenuBar,如果我们添加文件子菜单,html代码如下。
<div style="font-size:14px"> <div data-dojo-type="dijit/MenuBar"> <div data-dojo-type="dijit/PopupMenuBarItem"> <span>文件</span> <div data-dojo-type="dijit/DropDownMenu" data-dojo-attach-point="UI_File_DropDownMenu"></div> </div> </div> </div>
在js文件中,我们可以实例化一个dijit/MenuItem,添加到UI_File_DropDownMenu中。
CreateMenuItem: function () { var myMenuItem = new MenuItem({ label: "打开文件", iconClass: "AppMenuUIMenuItemIcon FileOpenIcon" }); var myThis = this; var myEventHander = on(myMenuItem, "click", function () { myThis._OnClick(); }); this._EventHanderArray.push(myEventHander); return myMenuItem; }, //点击命令按钮执行的函数 _OnClick: function () { var myFilePathArray = ElectronDialog.showOpenDialogSync(null, { title: "选择工程文件", properties: ["openFile"], filters: [ { name: '工程文件', extensions: ['project'] } ] }); if (myFilePathArray == null) { return; } var myProject = new Project(); myProject.Open(myFilePathArray[0]); this._Application.SetProject(myProject); },
this.UI_File_DropDownMenu.addChild(new FileNewCommand({}, this._Application).CreateMenuItem());
工具条使用dojo中定义的dijit/Toolbar,html代码定义如下。
<div> <div data-dojo-type="dijit/Toolbar" data-dojo-attach-point="UI_Toolbar"> <button data-dojo-type="dijit/form/Button" data-dojo-attach-point="UI_TopView_Button" data-dojo-props="iconClass:'AppToolBarUIButtonIcon TopViewIcon',showLabel:false"> 上视图 </button> <button data-dojo-type="dijit/form/Button" data-dojo-attach-point="UI_BottomView_Button" data-dojo-props="iconClass:'AppToolBarUIButtonIcon BottomViewIcon',showLabel:false"> 下视图 </button> <span data-dojo-type="dijit/ToolbarSeparator"></span> <button data-dojo-type="dijit/form/Button" data-dojo-attach-point="UI_FullExtent_Button" data-dojo-props="iconClass:'AppToolBarUIButtonIcon FullExtentIcon',showLabel:false"> 全图 </button> </div> </div>
在js文件中,可以具体定义Button按钮点击后执行的函数,代码如下。
//点击全图按钮执行的函数 on(this.UI_FullExtent_Button, "click", function () { myThis._Application.Viewer.fitToScreen(); });
一些逻辑较为复杂的工具,我们就要单独定义了,类似于定义菜单,我们会定义一个工具按钮。我们以新建工程为例,js代码如下。
define([ "dojo/_base/declare", "dojo/on", "dijit/MenuItem", "dijit/form/Button" ], function ( declare, on, MenuItem, Button ) { return declare("ProjectNewCommand", null, { _Application: null, _EventHanderArray: null, //构造函数 constructor: function (args, pApplication) { declare.safeMixin(this, args); this._Application = pApplication; this._EventHanderArray = []; }, //创建MenuItem CreateMenuItem: function () { var myMenuItem = new MenuItem({ label: "新建工程", iconClass: "AppMenuUIMenuItemIcon ProjectNewIcon" }); var myThis = this; var myEventHander = on(myMenuItem, "click", function () { myThis._OnClick(); }); this._EventHanderArray.push(myEventHander); return myMenuItem; }, //创建Button CreateButton: function () { var myButton = new Button({ label: "新建工程", iconClass: "AppToolBarUIButtonIcon ProjectNewIcon", showLabel: false, }); var myThis = this; var myEventHander = on(myButton, "click", function () { myThis._OnClick(); }); this._EventHanderArray.push(myEventHander); return myButton; }, //点击命令按钮执行的函数 _OnClick: function () { var myThis = this; require(["WebRoot/CoreUI/Projects/ProjectNewDialog"], function (Dialog) { var myDialog = new Dialog({}); myDialog.ShowDialog(); myDialog.on("ProjectCreated", function (e) { myThis._Application.SetProject(e.Project); }); }); }, //默认销毁函数 destroy: function () { for (var i = 0; i < this._EventHanderArray.length; i++) { this._EventHanderArray[i].remove(); } this._EventHanderArray = []; }, }); });
代码中包含了CreateMenuItem和CreateButton函数,分别返回MenuItem和Button,点击这两个UI都是执行_OnClick函数,也就是说,我们当前定义的ProjectNewCommand.js,实例化得到ProjectNewCommand对象后,调用CreateMenuItem函数获取的按钮可以添加到菜单上,调用CreateButton函数获取的按钮可以添加到工具条上,且点击后,两个按钮的行为一致。
var myProjectNewCommand = new ProjectNewCommand({}, myApplication); myAppToolBarUI.UI_Toolbar.addChild(myProjectNewCommand.CreateButton(), 0);
系统整体布局、菜单栏,工具条、左侧工程区域以及中间显示区域都定义好了之后,我们就要定义Viewer了。定义的时候,我们依然要参考Potree Desktop中实例化Viewer的代码,并根据实际需求修改。
var myPotreeRenderArea = document.getElementById("potree_render_area"); var myViewerArgs = { noDragAndDrop: true, }; var myViewer = new Potree.Viewer(myPotreeRenderArea, myViewerArgs); myViewer.setEDLEnabled(true); myViewer.setFOV(60); myViewer.setPointBudget(3 * 1000 * 1000); myViewer.setMinNodeSize(0); myViewer.loadSettingsFromURL(); myViewer.setDescription(""); myViewer.setControls(myViewer.earthControls);