3D Wind Farm Visualization System Based on HTML5+WebGL

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 in building a wind farm.In recent years, offshore and onshore wind power has developed rapidly in China.Seawater and land provide a good guarantee for our wind power generation.It is these sites that provide endless energy for our wind.Now we are trying to explore these areas.

This paper realizes the overall process of wind farm.You can see a complete wind preview system.

It is important to note that this project was built using Hightopo's HT for Web product.

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

Rough process

Here is a flowchart of the entire project.From the first page, we can enter the site distribution page and centralized control page.

The field area distribution page also includes two different 3D scenes, terrestrial and offshore.Clicking on two 3D fan farms will eventually enter the 3D fan scene.

Preview Effect

Home page:

1. World Map Effects

2. Map effects in China

2. Urban Map Effect

Centralized page (no animation effect):

Field Distribution Page (no animation effect):

Land fan farms:

Offshore fan farm:

code implementation

We can see that the first page of the globe has three perspectives, world map, China map, city map.Clicking on each status camera will go to the corresponding location.Before that, we'll save the center and eye.

We'd better create a new data.js file specifically for providing data.

The relevant pseudocode is as follows:

// Record Location
var cameraLocations = {
    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]
    }
}

Okay, with the data.It's time we listened to the incident next.We can either click on the button or click on the highlighted area (the world map only has buttons to click) to enter the perspective of the Chinese map.

This allows us to get the two nodes and then process their click events the same way.However, I think this way of thinking can be optimized and changed.

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

Below, we can see that if the current node does not have event privileges or if the current event itself does not have privileges, it will be filtered out.If all can be returned correctly, the corresponding event is executed.

The relevant pseudocode is as follows:

// Permission Events
this.eventMap = {
    clickData: true,
    onEnter: true,
    onLeave: true
}

// Permission Node
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.kind
        var tag = e.data && e.data.getTag()

         // Check whether the current event or node can be executed
         if (!self.eventMap[event] && !self.nodeMap[tag]) return false

         self.nodeEvent(event, tag)
    }

    gv.mi(eventFlow)
}    

As long as the node we are currently executing meets the requirements, we will pass events (currently executing events) and tag s (node labels) to the execution function nodeEvent to execute**.** This saves you from wasting resources dealing with invalid events or nodes.

Let's see what nodeEvent does next.

The relevant pseudocode is as follows:

/**
 * Bubble Events
 * @param { string } event Current Event
 * @param { string } propertyName Current Node Label
 */
bubblesEvent(event, propertyName) {
    var dm = this.dm
    var account = dm.getDataByTag('account')
    var currentNode = dm.getDataByTag(propertyName)
    var self = this

    var clickData = function() {
        // Perform cleanup action
        self.clearAction()
    }

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

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

    var allEvent = { clickData, onEnter, onLeave }

    allEvent[event] && allEvent[event]()
}

As you can see, we can stitch together a method name using the propertyName (node tag) string.For example, the node label you are currently getting is bubbles, and after this [`${properName}Event`], you get this['bubblesEvent'] method.Of course, this method was defined beforehand.

In the specific node method, we create the corresponding event function.Determine if you have a corresponding method based on the event passed in.Execute if there is one, otherwise return false.The benefits of this are decoupling, simple structure, and the ability to locate problems quickly.

But if we think about it carefully, when we click on the World Map and the China Map, the function is almost the same!It would be much easier if we could merge them!!Let's transform the code.

The relevant pseudocode is as follows:

/**
  * Execute Node Event
  */
nodeEvent(event, propertyName) {
    // Filter for events that can be merged
    var filterEvents = function(propertyName) {
        var isCombine = false     
        var self = this
        if (['earth', 'china'].includes(propertyName)) {
            self.changeCameraLocaltions(event, propertyName)
            isCombine = true
        }

        return !isCombine
    }
   var eventFun = this[`${propertyName}Event`]
   // Execute the corresponding node event
   filterEvents(propertyName)
   &&
   eventFun  &&
   eventFun(event, propertyName)
}

We determine in advance whether the current events can be merged, and if we can, return **false, ** no longer execute the following code, and then execute our own function.

At this point, we can get the corresponding center, eye from the cameraLocations variable of data.js through the corresponding node label.

The relevant pseudocode is as follows:

/**
 * Moving Lens Animation
 * @param { object } config Coordinate object
 */
moveCameraAnim(gv, config) {
  var eye = config.eye  var center = config.center  // If the animation already exists, empty it
   if(globalAnim.moveCameraAnim) {
      globalAnim.moveCameraAnim.stop()    globalAnim.moveCameraAnim = null
   }

   var animConfig = {
     duration: 2e3
   }

   globalAnim.moveCameraAnim = gv.moveCamera(eye, center, animConfig)
}

// Need to change camera position
changeCameraLocaltions(event, properName) {
    var config = cameraLocations[properName]

    // Mobile Camera
    this.moveCameraAnim(this.gv, config)
}

Moving lens animation uses gv's moveCamera method, which accepts three parameters, eye (camera), **center (target), animConfig (animation configuration).** We then return the current animation to the moveCameraAnim property of globalAnim for our cleanup.

Next, you switch pages, which requires great care.Because once an attribute is not cleaned up, memory leaks and other problems will occur and performance will be slower and slower.Page jam will result!

So we need a function specifically designed to clean up the data model**clearAction.** We should put all the animated objects in one object or array.This makes it easy to clean up when switching pages.

The relevant pseudocode is as follows:

/**
 * Clear Action
 */
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 = null

    for (var i in globalAnim) {
        globalAnim[i] && globalAnim[i].pause()
        globalAnim[i] = null
    }

    // Clear corresponding 3D drawings
    ht.Default.removeHTML(g3d)

    gv.addToDOM()
    ht.Default.xhrLoad(`displays/HT-project_2019/Wind Power/${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)
        })
    })
}

First we need to clear the DM (data model) and GV (drawing).Also note that MI (listening function), schedule (scheduling task) should be remove d before dm.clear().All animations stop() and set their value to null.It is important to note that the finishFunc callback function is called once after stop is executed.

When our 2D drawings contain 3D backgrounds, it is important to determine if there are instances of 3D that do not need to be recreated.It is interesting to learn about the application memory leaks in webGL.

When we enter two 3D scene scenes, we need an opening animation, like a gif image of the opening effect.So we need to store both center and eye of the opening animation in the cameraLocations that we have defined.

// Record Location
var cameraLocations = {
    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: {
        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]
    }
}

OffhoreStart, offshoreEnd, landStart, landEnd represent the start and end locations of offshore and onshore power plants**.**

We need to determine whether we are currently loading offshore or onshore power plants.We can add className when loading the corresponding drawings.

We have defined index as a parameter in the clearAction function. If you click on a terrestrial power plant, you will get number 3, if you click on an offshore power plant, you will get number 4.

For example, if I need to load a terrestrial power plant, I can add a className by judging g3d.className = index == 3?'land':'offshore'.

Then make the initialization judgment in init.

The relevant pseudocode is as follows:

init() {
    var className = g3d.getView().className
    
    // Execute separate events
    this.selfAnimStart(className)
    this.initData()

    // Listen for events
    this.monitor()
}

We get the corresponding **className, ** pass in the corresponding type and execute the corresponding initialization event to animate the camera through the moveCameraAnim function that we have defined.

The relevant pseudocode is as follows:

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

     this.moveCameraAnim(gv, config)
}

summary

This project gives us a better understanding of wind power.Whether it is the regional advantages of wind farms, or the structure and operation principle of wind turbines.

After completing this project, I got a lot of growth and inspiration.A good way for technology to grow rapidly is to skim through the details.A project is a work of art that needs to be polished continuously until you are satisfied with it.Each tiny point can affect subsequent performance.So we should do everything in the spirit of craftsmen.

Of course, I also want some partners to be brave in exploring the industrial Internet.We can do much more than that.This requires our imagination to add more fun and useful demo s to the field.It also learns a lot about industry.

Tags: JSON Mobile Attribute

Posted on Tue, 11 Feb 2020 23:08:24 -0500 by Erestar