preface

Wind energy is a kind of clean energy in development, which is inexhaustible and inexhaustible. Of course, meteorological conditions and social and natural conditions should be considered first when building wind farms. In recent years, China’s offshore and onshore wind power has developed rapidly. Sea water and land provide a good geological guarantee for our wind power generation. It is these fields that provide our wind with an inexhaustible source of energy. Now we’re trying to explore those areas.

This article realizes the whole process of wind farm. We can see a complete wind power preview system.

It should be noted that this project was built using Hightopo’s HT for Web product.

Preview address: hightopo.com/demo/wind-p…

A flowchart

Below is a flow chart of the entire project. We can enter the field distribution page and centralized control page from the home page.

The site distribution page also includes two different 3D scenes, namely land wind airport and sea wind airport. Clicking on both 3D airfields will bring you to the 3D fan scene.

preview

Home page:

1. World map effect

2. China map effect

2. City map effect

Centralized control center page (no animation effect) :

Site distribution page(No animation):

Land Wind Airport:

Sea Wind Airport:

Code implementation

We can see that the earth on the home page has three view states: world map, China map and city map. Clicking on each state will move the camera to the corresponding position. Before that, we need to store the corresponding center and eye in advance.

It would be better to create a new data.js file just to provide the data.

Relevant pseudocodes are as follows:

Var cameraLocations = {earth: {eye: [-73, 448, 2225], center: [0, 0, 0]}, China: {eye: [-73, 448, 2225] [-91, 476, 916], center: [0, 0, 0] }, tsankiang: { eye: [35, 241, 593], center: [0, 0, 0] } }Copy the code

Okay, so once we have the data. We should listen for events next. We can click the button, or we can click on the highlighted area (world map only button can be clicked) to enter the view of China map.

We can get these two nodes and then do the same for their click events. However, I think this approach can be optimized to change the way of thinking.

We can filter events first by creating two arrays, one that holds events like Click and onEnter that can be executed, and one that holds all nodes that can trigger events. This will help us maintain and make the structure clearer.

In the figure below, we can see that if the current node does not have event permissions or the current event itself does not have permissions, it will be filtered out. If both returns correctly, the corresponding event is executed.

Relevant pseudocodes are as follows:

// Permission event this.eventMap = {clickData:true,
    onEnter: true,
    onLeave: true} // This. NodeMap = {outline:true,
    outline2: true,
    earth: true,
    bubbles: true,
    circle: true} /** * listen for events */initMonitor() {var gv = this.gv var self = this var evntFlow =function(e) {var event = e.type var tag = e.ata &&e.data.getTag () // Checks whether the current event or node can be executedif(! self.eventMap[event] && ! self.nodeMap[tag])return false

         self.nodeEvent(event, tag)
    }

    gv.mi(eventFlow)
}    Copy the code

As long as the node we are currently executing meets the requirements, we pass the event (currently executing event) and tag (node tag) to the executing function nodeEvent. This way, you don’t waste resources dealing with invalid events or nodes.

Let’s take a look at nodeEvent.

Relevant pseudocodes are as follows:

/** * bubble event * @param {string} event Current event * @param {string} propertyName Current node tag */ bubblesEvent(event, event) propertyName) { var dm = this.dm var account = dm.getDataByTag('account')
    var currentNode = dm.getDataByTag(propertyName)
    var self = this

    var clickData = function() {// perform the clearAction self.clearaction ()} var onEnter =function() {
       // do something
    }

    var onLeave = function() {/ /do something
    }

    var allEvent = { clickData, onEnter, onLeave }

    allEvent[event] && allEvent[event]()
}Copy the code

As you can see, we can concatenate the propertyName string to form a method name. For example, the current node tag is bubbles, and this[‘ ${properName}Event ‘] is the method this[‘bubblesEvent’]. Of course, this method is defined in advance.

Within the specific node method, we create the corresponding event function. According to the passed event to determine whether there is a corresponding method. Execute if there is, otherwise return false. The advantages of this approach are: decoupling, simple structure, and quick location of problems.

However, if we think about it, when we click on the world map and China map, the function is similar! It would be much easier if we could merge them!! Let’s change the code.

Relevant pseudocodes are as follows:

/** * Executes node events */ nodeEvent(event, propertyName) {// Filters whether there are events that can be merged var filterEvents =function(propertyName) {
        var isCombine = falsevar self = thisCopy the code
        if (['earth'.'china'].includes(propertyName)) {
            self.changeCameraLocaltions(event, propertyName)
            isCombine = true
        }

        return! isCombine } var eventFun = this[`${propertyName}FilterEvents (propertyName) && eventFun&& eventFun(Event, propertyName)}Copy the code

We determine whether the current event can be merged, return false if so, do not execute the following code, and then execute our own function.

At this point, we can obtain the corresponding center and Eye from the data.js cameraLocations variable through the corresponding node label.

Relevant pseudocodes are as follows:

/** * @param {object} config Coordinate object */ moveCameraAnim(gv, Config) {var eye = config.eyevar center = config.center// If the animation already exists, clear itif(globalAnim moveCameraAnim) {globalAnim. MoveCameraAnim. Stop () globalAnim. MoveCameraAnim = null} var animConfig = { Duration: E3} 2 globalAnim. MoveCameraAnim = gv. MoveCamera (eye, center, animConfig)} / / need to change the camera position changeCameraLocaltions (event, ProperName) {var config = cameraLocations[properName] // moveCameraAnim(this.gv, config)}Copy the code

Moving camera animation uses GV’s moveCamera method, which takes three parameters, Eye (camera), Center (target), and animConfig (animation configuration). We then return the current animation to globalAnim’s moveCameraAnim property for us to clean up.

Next, it’s time to switch pages, which needs to be done very carefully. If a property is not cleared, it can cause problems such as memory leaks and slow performance. This will cause the page to freeze!

So we need a function, clearAction, dedicated to cleaning up the data model. We should put all the animated objects into one object or array. This makes it easy to clean up when switching pages.

Relevant pseudocodes are as follows:

/** * clearAction(index) {var {dm, gv} = this var {g3d, d3d } = window allListener.mi3d && g3d.umi(allListener.mi3d) allListener.mi2d && gv.umi(allListener.mi2d) dm.removeScheduleTask(this.schedule) dm && dm.clear() d3d && d3d.clear() window.d3d = null window.dm = nullfor (var i inGlobalAnim) {globalAnim[I] && globalAnim[I].pause() globalAnim[I] = null} // Remove the corresponding 3D drawing Ht.default.removehtml (g3D) Gv.addtodom () ht.default. xhrLoad(' displays/HT-project_2019/ wV /${index}.json`, function (text) {
        let json = ht.Default.parse(text)
        gv.deserialize(json, function(json, dm2, gv2, datas) {
            if (json.title) document.title = json.title

            if (json.a['json.background']) {
                let bgJSON = json.a['json.background']
                if (bgJSON.indexOf('scenes') === 0) {
                    var bgG3d

                    if (g3d) {
                        bgG3d = g3d
                    } else {
                        bgG3d = new ht.graph3d.Graph3dView()
                    }

                    var bgG3dStyle = bgG3d.getView()
                    bgG3dStyle.className = index === 1 ? ' ' : index === 3 ? 'land' : 'offshore'

                    bgG3d.deserialize(bgJSON, function(json, dm3, gv3, datas) {
                        init3d(dm3, gv3)
                    })

                    bgG3d.addToDOM()
                    gv.addToDOM(bgG3dStyle)
                }
                gv.handleScroll = function () {}
            }

            init2d(dm2, gv2)
        })
    })
}Copy the code

First we need to clear out the DM (data model) and GV (drawing). Also note that mi(listener function) and schedule(task) should be removed before dm.clear(). Stop () is applied to all animations and its value is set to NULL. Note here that the finishFunc callback is called once after stop is executed.

When our 2D drawing contains a 3D background, we need to determine whether there is already a 3D instance. If there is, we do not need to create it again. If you are interested, take a look at webGL’s application memory leaks.

When entering the two 3D scenes, we need an opening animation, like the opening effect GIF. Therefore, we need to save the center and eye of the two opening animations into the cameraLocations that we have defined.

Var cameraLocations = {earth: {eye: [-73, 448, 2225], center: [0, 0, 0]}, China: {eye: [-73, 448, 2225] [-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: { eye: [61, 27169, 55], center: [0, 0, 0] }, offshoreEnd: { eye: [-3912, 241, 834], center: [0, 0, 0] }, landEnd: { eye: [4096, 4122, -5798], center: [1261, 2680, -2181] } }Copy the code

OffshoreStart, offshoreEnd, landStart, and landEnd indicate the start and end positions of the offshore and onshore power plants.

We need to determine whether the current load is offshore or onshore. We can add the className when loading the corresponding drawing.

We’ve already defined index in the clearAction function, which will send the number 3 if you click on a land field, or 4 if you click on an offshore field.

G3d. className = index == 3? ‘land’ : ‘offshore’ to add className.

And then we do the initialization judgment in init.

Relevant pseudocodes are as follows:

init() {var className = g3d.getView().className // Executes a separate event this.selfAnimStart(className) this.initData() // listens for events this.monitor() }Copy the code

We take the corresponding className, pass in the corresponding type and execute the corresponding initialization event, and animate the camera with the moveCameraAnim function we have defined.

Relevant pseudocodes are as follows:

/** * selfAnimStart(selfAnimStart)type) {
    var gv = this.gv
    var { eye, center } = cameraLocations[`${type}End`]
    var config = {
        duration: 3000,
        eye,
        center,
     }

     this.moveCameraAnim(gv, config)
}Copy the code

conclusion

This project has taught us a lot about wind power. Whether it is the regional advantages of wind farms, or the structure and operation principle of wind turbines.

After finishing this project, I got a lot of growth and comprehension. A good way for technology to grow quickly is to be constantly mincing details. A project is a work of art that needs to be polished until you are satisfied. Each subtle point affects subsequent performance. Therefore, we should do everything in the spirit of the craftsman.

Of course, I also hope that some partners will have the courage to explore the industrial Internet. We can achieve much more than that. This requires us to use our imagination and add more fun and useful demos to the field. And you can learn a lot about the industry.