前言风能是一种正在开发中的清洁能源,取之不尽,用之不竭。当然,建设风电场首先要考虑气象条件和社会自然条件。近年来,我国海上和陆上风电发展迅速。海水和陆地为我国风力发电提供了良好的地质保障。正是这些站点为我们的风力发电提供了取之不尽、用之不竭的能源。现在我们正在尝试探索这些领域。本文实现了风电场的整体流程。让大家看到一套完整的风电预报系统。需要注意的是,这个项目是使用Hightopo的HTforWeb产品构建的。预览地址:hightopo.com/demo/wind-p...下面的大致流程是整个项目的流程图。从首页可以进入现场分布页面和集中控制页面。站点分布页面包括两个不同的3D场景,分别是陆地风力机场和海上风力机场。单击两个3D风电场最终将进入3D风力涡轮机场景。预览效果首页:1.世界地图效果2.中国地图效果2.城市地图效果集中控制中心页面(无动画效果):站点分布页面(无动画效果):陆风机场:海上风机场:代码实现我们可以看到首页的地球有世界地图、中国地图、城市地图三种透视状态。点击每个状态的相机将转到相应的位置。在此之前,我们需要预先存储对应的center和eye。我们最好新建一个data.js文件专门提供数据。相关伪代码如下://记录位置varcameraLocations={earth:{eye:[-73,448,2225],center:[0,0,0]},china:{eye:[-91,476,916],center:[0,0,0]},tsankiang:{eye:[35,241,593],center:[0,0,0]}}好了,现在我们有了数据。接下来我们应该监听事件。我们可以点击按钮,或者点击高亮区域(世界地图只能点击按钮)进入中国地图视角。我们可以通过这种方式先获取到这两个节点,然后对它们的点击事件进行同样的处理。不过,我觉得这个方法可以优化,换成一种思路。我们可以先过滤事件。我们创建两个数组,一个保存可执行事件,如click和onEnter,另一个保存所有可以触发事件的节点。这可以帮助我们维护并使结构更清晰。在下图中我们可以看到,如果当前节点没有事件权限或者当前事件本身没有权限,就会被过滤掉。如果都能正确返回,则执行相应的事件。相关伪代码如下://权限事件this.eventMap={clickData:true,onEnter:true,onLeave:true}//权限节点this.nodeMap={outline:true,outline2:true,earth:true,bubbles:true,circle:true}/***监控事件*/initMonitor(){vargv=this.gv varself=thisvarevntFlow=function(e){varevent=e.kindvartag=e.data&&e.data.getTag()//检查当前事件或节点是否可以执行if(!self.eventMap[event]&&!self.nodeMap[tag])returnfalseself.nodeEvent(event,tag)}总值。mi(eventFlow)}只要我们当前执行的节点满足要求,我们就会将事件(当前正在执行的事件)和标签(nodetag)传递给执行函数nodeEvent执行。这样就不会浪费资源去处理无效的事件或节点。来看看nodeEvent是怎么处理的吧!相关伪代码如下:/***bubbleevent*@param{string}event当前事件*@param{string}propertyName当前节点标签*/bubblesEvent(event,propertyName){vardm=this.dmvaraccount=分米。getDataByTag('account')varcurrentNode=dm.getDataByTag(propertyName)varself=thisvarclickData=function(){//执行清除操作self.clearAction()}varonEnter=function(){//做某事}varonLeave=function(){ //dosomething}varallEvent={clickData,onEnter,onLeave}allEvent[event]&&allEvent[event]()}如你所见,我们可以使用propertyName(节点label)string连接起来形成一个方法名。比如当前获取到的节点标签是bubbles。在this[`${properName}Event`]之后,得到的方法就是this['bubblesEvent']。当然,这个方法是事先定义好的。在具体的节点方法中,我们创建了相应的事件函数。根据传入的事件判断是否有对应的方法。如果有则执行,否则返回false。这样做的好处是:解耦,结构简单,问题定位快。不过大家仔细想想,我们点击世界地图和中国地图的时候,功能是差不多的!如果能把它们结合起来就方便多了!!让我们稍微修改一下代码。相关伪代码如下:/***执行节点事件*/nodeEvent(event,propertyName){//过滤是否有可以组合的事件varfilterEvents=function(propertyName){varisCombine=false varself=thisif(['earth','china'].includes(propertyName)){self.changeCameraLocaltions(event,propertyName)isCombine=true}return!isCombine}vareventFun=this[`${propertyName}Event`]复制代码//执行对应的节点事件filterEvents(propertyName)&&eventFun &&eventFun(event,propertyName)}我们提前判断当前事件是否可以合并,如果可以,则返回false,不执行下面的代码,并且然后执行我们自己的功能。这时我们可以通过相应的节点标签从data.js的cameraLocations变量中获取到相应的center和eye。相关伪代码如下:/***移动相机动画*@param{object}config坐标对象*/moveCameraAnim(gv,config){ vareye=config.eye varcenter=config..moveCameraAnim=gv。moveCamera(eye,center,animConfig)}//需要改变相机位置changeCameraLocaltions(event,properName){varconfig=cameraLocations[properName]//移动相机this.moveCameraAnim(this.gv,config)}移动相机动画使用gvmoveCamera方法,接受3个参数,eye(相机),center(目标),animConfig(动画配置)。然后我们将当前动画返回给globalAnim的moveCameraAnim属性,让我们清理。接下来就是切换页面了,需要非常小心。因为一旦某个属性没有被清除,就会造成内存泄漏等问题,性能会越来越慢。会导致页面卡死!所以我们需要一个专门用来清除数据模型的函数clearAction。我们应该把所有的动画对象放到一个对象或者数组中。这使得切换页面时很容易清理。默认值:/***ClearAction(index){var{dm,gv}=thisvar{g3d,d3d}=windowallListener.mi3d&&g3d.umi(allListener.mi3d)allListener.mi2d&&;gv.umi(allListeners.mi2d)dm.removeScheduleTask(this.schedule)dm&&dm.clear()d3d&&d3d.clear()window.d3d=nullwindow.dm=nullfor(variinglobalAnim){globalAnim[i]&&globalAnim[i].pause()globalAnim[i]=null}//打开免费的3D界面ht.Default.removeHTML(g3d)gv.addToDOM()ht.Default.xhrLoad(`displays/HT-project_2019/风电/${index}.json`,function(text){letjson=ht.Default.parse(text)gv.deserialize(json,function(json,dm2,gv2,datas){if(json.title);document.title=json.titleif(json.a['json.background']){让bgJSON=json.a['json.background']if(bgJSON.indexOf('scenes')===0){;是bgG3d如果(g3d){bgG3d=g3d}else{bgG3d=newht.graph3d.Graph3dView()}varbgG3dStyle=bgG3d.getView()bgG3dStyle.className=index===1?'':索引===3?'土地':'离岸'bgG3d.deserialize(bgJSON,function(json,dm3,gv3,datas){init3d(dm3,gv3)})bgG3d.addToDOM()gv.addToDOM(bgG3dStyle)}gv.handleScroll=function(){}}init2d(dm2,gv2)})})}首先要明确dm(数据模型)和gv(绘图)注意:mi(监控函数),schedule(调度任务)应该在dm中。删除前清除()。所有动画都停止(),然后设置为null。这里需要注意的是,执行stop后,会调用一次finishFunc回调函数。当我们的2D绘图包含3D背景时,我们需要判断3D实例是否已经存在,如果存在,则不需要重新创建。有兴趣的可以了解一下webGL的应用内存泄漏问题。当进入两个3D场景时,我们需要一个开场动画,就像开场效果gif一样。所以我们需要将两个开场动画的center和eye存放在我们定义的cameraLocations中。//记录位置varcameraLocations={earth:{eye:[-73,448,2225],center:[0,0,0]},china:{eye:[-91,476,916],center:[0,0,0]},tsankiang:{eye:[35,241,593],center:[0,0,0]},offshoreStart:{eye:[-849,15390,-482],center:[0,0,0]},landStart:{眼睛:[61,27169,55],中心:[0,0,0]},离岸结束:{眼睛:[-3912,241,834],中心:[0,0,0]},landEnd:{eye:[4096,4122,-5798],center:[1261,2680,-2181]}}offshoreStart,offshoreEnd,landStart,landEnd表示海上和陆上电厂的起始位置和结束位置。我们需要判断当前负载是海上电站还是陆上电站。我们可以在加载相应的图纸时加上cla??ssName。我们在clearAction函数中定义了index参数。如果点击陆上电厂,则传3号,如果是海上电厂,则传4号。比如我需要加载陆上电厂,那么我可以通过判断添加classNameg3d.className=index===3?'陆地':'离岸'。然后在init中进行初始化判断。相关伪代码如下:init(){varclassName=g3d.getView().className//执行一个单独的事件this.selfAnimStart(className)this.initData()//监听事件this.monitor()}我们获取对应的className,传入对应的type并执行对应的初始化事件,通过我们定义的moveCameraAnim函数进行相机动画。相关伪代码如下:/***不同风电场的开启动画*/selfAnimStart(type){vargv=this.gvvar{eye,center}=cameraLocations[`${type}End`]varconfig={duration:3000,eye,center,}this.moveCameraAnim(gv,config)}总结这个项目让我们对风力发电有了更深入的了解。无论是风电场的区域优势,还是风电机组的结构和运行原理。完成这个项目后,我收获了很多成长和感悟。技术快速增长的一个好方法是不断挖掘细节。一个项目就是一件艺术品,需要不断打磨,直到令人满意为止。每一个微小的点都会影响后者的表现。因此,我们要以工匠精神做每一件事。当然,也希望有小伙伴勇于探索工业互联网领域。我们可以实现的远不止于此。这就需要我们发挥想象力,为这个领域添加更多好玩实用的demo。并且可以学到很多工业领域的知识。
