LB Wind Profile - Suggestions

1. Is it possible to customize the dir_count for the Wind Profile so that the prevailing direction can match the Wind Rose?

In the image shown below, I have set my Wind Rose dir_count to 16, and the prevailing result points to “WNW”. However, in the Wind Profile, it becomes “W” due to the preset having only 8 directions, I suppose.

2. Could there be an option for the Wind Profile to lay flat (2D) instead of standing vertically, even when the met_wind_dir is input (for report plotting purposes)?

At this point, I collect all the output geometries and manually rotate them and redo the title text in GH.

It would be appreciated if the suggestions above could be taken into account.

WR+WP.gh (35.3 KB)

Yes, you just have to change this line of code inside the LB Wind Profile component:

Just change the 8 to whatever the dir_count is on your wind rose.

Yea, just add this line of code:

met_wd = None

… inside the component somewhere around here:

That should get the the wind profile to to lay flat while still putting the text of the prevailing direction in the header.

Generally speaking, just play around the component source code if you want to customize highly specific things like this.

Hi @chris

Thanks for the help.

For Q1, I’ve changed 8 to 16:

prev_dir = WindRose.prevailing_direction_from_data(met_wind_dir_, 16)[0]

After that the dir_label seem not follow correctly,

so I edited:

# dictionary to map integers to cardinal directions
DIR_TEXT = {
    '0': 'N', '1': 'NNE', '2': 'NE', '3': 'ENE', '4': 'E', '5': 'ESE', '6': 'SE', '7': 'SSE',
    '8': 'S', '9': 'SSW', '10': 'SW', '11': 'WSW', '12': 'W', '13': 'WNW', '14': 'NW', '15': 'NNW',
    'N': 'N', 'NNE': 'NNE', 'NE': 'NE', 'ENE': 'ENE', 'E': 'E', 'ESE': 'ESE', 'SE': 'SE', 'SSE': 'SSE',
    'S': 'S', 'SSW': 'SSW', 'SW': 'SW', 'WSW': 'WSW', 'W': 'W', 'WNW': 'WNW', 'NW': 'NW', 'NNW': 'NNW'
}

DIR_RANGE = {
    'N': (348.75, 11.25), 'NNE': (11.25, 33.75), 'NE': (33.75, 56.25), 'ENE': (56.25, 78.75),
    'E': (78.75, 101.25), 'ESE': (101.25, 123.75), 'SE': (123.75, 146.25), 'SSE': (146.25, 168.75),
    'S': (168.75, 191.25), 'SSW': (191.25, 213.75), 'SW': (213.75, 236.25), 'WSW': (236.25, 258.75),
    'W': (258.75, 281.25), 'WNW': (281.25, 303.75), 'NW': (303.75, 326.25), 'NNW': (326.25, 348.75)
}

And it return the error message as fllow:
Runtime error (AssertionException): Data Collection must include at least one value

Traceback:
  line 1064, in _check_values, "C:\Program Files\ladybug_tools\python\Lib\site-packages\ladybug\_datacollectionbase.py"
  line 96, in values, "C:\Program Files\ladybug_tools\python\Lib\site-packages\ladybug\_datacollectionbase.py"
  line 93, in __init__, "C:\Program Files\ladybug_tools\python\Lib\site-packages\ladybug\datacollection.py"
  line 756, in filter_by_pattern, "C:\Program Files\ladybug_tools\python\Lib\site-packages\ladybug\datacollection.py"
  line 231, in script

Ok, I finally got around to editing this. Below is the result, with my custom changes marked.

import math

try:
    from ladybug_geometry.geometry2d import Vector2D
    from ladybug_geometry.geometry3d import Point3D
except ImportError as e:
    raise ImportError('\nFailed to import ladybug_geometry:\n\t{}'.format(e))

try:
    from ladybug.datatype.speed import WindSpeed
    from ladybug.datacollection import BaseCollection
    from ladybug.graphic import GraphicContainer
    from ladybug.windprofile import WindProfile
    from ladybug.windrose import WindRose
except ImportError as e:
    raise ImportError('\nFailed to import ladybug:\n\t{}'.format(e))

try:
    from ladybug_rhino.togeometry import to_point3d, to_vector2d
    from ladybug_rhino.fromgeometry import from_point3d, from_vector3d, \
        from_mesh3d, from_linesegment3d, from_polyline3d
    from ladybug_rhino.text import text_objects
    from ladybug_rhino.fromobjects import legend_objects
    from ladybug_rhino.grasshopper import all_required_inputs, objectify_output
    from ladybug_rhino.config import conversion_to_meters, units_system
except ImportError as e:
    raise ImportError('\nFailed to import ladybug_rhino:\n\t{}'.format(e))

# dictionary to map integers to terrain types
TERRAIN_TYPES = {
    '0': 'city',
    '1': 'suburban',
    '2': 'country',
    '3': 'water',
    'city': 'city',
    'suburban': 'suburban',
    'country': 'country',
    'water': 'water'
}

# dictionary to map integers to cardinal directions
### ------ Custom modification (Start) ------ ###
DIR_TEXT = {
    '0': 'N',   '1': 'NNE', '2': 'NE',  '3': 'ENE',
    '4': 'E',   '5': 'ESE', '6': 'SE',  '7': 'SSE',
    '8': 'S',   '9': 'SSW', '10': 'SW', '11': 'WSW',
    '12': 'W',  '13': 'WNW','14': 'NW', '15': 'NNW',

    'N': 'N', 'NNE': 'NNE', 'NE': 'NE', 'ENE': 'ENE',
    'E': 'E', 'ESE': 'ESE', 'SE': 'SE', 'SSE': 'SSE',
    'S': 'S', 'SSW': 'SSW', 'SW': 'SW', 'WSW': 'WSW',
    'W': 'W', 'WNW': 'WNW', 'NW': 'NW', 'NNW': 'NNW'
}
DIR_RANGE = {
    'N': (348.75, 11.25), 'NNE': (11.25, 33.75), 'NE': (33.75, 56.25), 'ENE': (56.25, 78.75),
    'E': (78.75, 101.25), 'ESE': (101.25, 123.75), 'SE': (123.75, 146.25), 'SSE': (146.25, 168.75),
    'S': (168.75, 191.25), 'SSW': (191.25, 213.75), 'SW': (213.75, 236.25), 'WSW': (236.25, 258.75),
    'W': (258.75, 281.25), 'WNW': (281.25, 303.75), 'NW': (303.75, 326.25), 'NNW': (326.25, 348.75)
}
### ------ Custom modification (End)  ------ ###

if all_required_inputs(ghenv.Component):
    # interpret the model units
    scale_fac = 1 / conversion_to_meters()
    unit_sys = units_system()

    # set default values
    if north_ is not None:  # process the north_
        try:
            north_ = math.degrees(
                to_vector2d(north_).angle_clockwise(Vector2D(0, 1)))
        except AttributeError:  # north angle instead of vector
            north_ = float(north_)
    else:
        north_ = 0
    _terrain_ = 'city' if _terrain_ is None else TERRAIN_TYPES[_terrain_.lower()]
    _met_height_ = 10 if _met_height_ is None else _met_height_
    _met_terrain_ = 'country' if _met_terrain_ is None \
        else TERRAIN_TYPES[_met_terrain_.lower()]
    log_law_ = False if log_law_ is None else log_law_
    bp = Point3D(0, 0, 0) if _base_pt_ is None else to_point3d(_base_pt_)
    if unit_sys in ('Feet', 'Inches'):
        _profile_height_ = 30.48 if _profile_height_ is None else _profile_height_
        _vec_spacing_ = 3.048 if _vec_spacing_ is None else _vec_spacing_
        feet_labels = True
    else:
        _profile_height_ = 30 if _profile_height_ is None else _profile_height_
        _vec_spacing_ = 2 if _vec_spacing_ is None else _vec_spacing_
        feet_labels = False
    _vec_scale_ = 5 if _vec_scale_ is None else _vec_scale_
    len_d, height_d = _vec_scale_, _vec_scale_ / 5

    # process the data collections and wind direction if reuqested
    if isinstance(met_wind_dir_, BaseCollection):
        if profile_dir_ is not None:
            dir_label = DIR_TEXT[profile_dir_]
            dir_txt = '\nWind Direction = {}'.format(dir_label)
        else:  # get the prevailing wind direction
            ### ------ Custom modification (Start) ------ ###
            prev_dir = WindRose.prevailing_direction_from_data(met_wind_dir_, 16)[0]
            dir_label = DIR_TEXT[str(int(prev_dir / 22.5))]
            dir_txt = '\nPrevailing Wind Direction = {}'.format(dir_label)
        dir_range = DIR_RANGE[dir_label]
        met_wd = sum(dir_range) / 2 if dir_range != (348.75, 11.25) else 0
        if isinstance(_met_wind_vel, BaseCollection):
            lw, hg = dir_range
            if dir_range == (348.75, 11.25):
                pattern = [lw < v or v < hg for v in met_wind_dir_]
            else:
                pattern = [lw < v < hg for v in met_wind_dir_]
            ### ------ Custom modification (End)  ------ ###
            _met_wind_vel = _met_wind_vel.filter_by_pattern(pattern)
    else:
        met_wd = float(met_wind_dir_) if met_wind_dir_ is not None else None
        dir_txt = '\nWind Direction = {} degrees'.format(int(met_wd)) \
            if met_wind_dir_ is not None else ''
    if isinstance(_met_wind_vel, BaseCollection):
        met_ws = _met_wind_vel.average
        head = _met_wind_vel.header
        loc_txt = '{} Terrain'.format(_terrain_.title()) if 'city' not in head.metadata \
            else '{} - {} Terrain'.format(head.metadata['city'], _terrain_.title())
        title_txt = '{}{}\nAverage Met Wind Speed = {} m/s'.format(
            loc_txt, dir_txt, round(met_ws, 2))
    else:
        met_ws = float(_met_wind_vel)
        title_txt = '{} Terrain{}\nMeteorological Speed = {} m/s'.format(
            _terrain_.title(), dir_txt, round(met_ws, 2))
    if met_wd is not None and north_ != 0:
        met_wd = met_wd - north_

    ### ------ Custom modification (Start) ------ ###
    # Force flat 2D mode (ignore wind direction)
    if _flat_2d_:
        met_wd = None
    ### ------ Custom modification (End)  ------ ###

    # create the wind profile and the graphic container
    profile = WindProfile(_terrain_, _met_terrain_, _met_height_, log_law_)
    _, mesh_ars, wind_speeds, wind_vectors, anchor_pts = \
        profile.mesh_arrow_profile(
            met_ws, _profile_height_, _vec_spacing_, met_wd, bp,
            len_d, height_d, scale_fac)
    profile_polyline, _, _ = profile.profile_polyline3d(
            met_ws, _profile_height_, 0.1,
            met_wd, bp, len_d, scale_fac)
    max_speed = round(wind_speeds[-1]) if _max_speed_ is None else _max_speed_
    max_pt = Point3D(bp.x + ((max_speed + 2) * len_d * scale_fac),
                     bp.y + (30 * scale_fac), bp.z)
    graphic = GraphicContainer(
        wind_speeds, bp, max_pt, legend_par_, WindSpeed(), 'm/s')

    # draw profile geometry and mesh arrows in the scene
    mesh_arrows = []
    for mesh, col in zip(mesh_ars, graphic.value_colors):
        mesh.colors = [col] * len(mesh)
        mesh_arrows.append(from_mesh3d(mesh))
    profile_curve = from_polyline3d(profile_polyline)

    # draw axes and legend in the scene
    txt_h = graphic.legend_parameters.text_height
    axis_line, axis_arrow, axis_ticks, text_planes, text = \
        profile.speed_axis(max_speed, met_wd, bp, len_d, scale_fac, txt_h)
    speed_axis = [from_linesegment3d(axis_line), from_mesh3d(axis_arrow)]
    for tic in axis_ticks:
        speed_axis.append(from_linesegment3d(tic))
    for i, (pl, txt) in enumerate(zip(text_planes, text)):
        txt_i_h = txt_h if i != len(text) - 1 else txt_h * 1.25
        txt_obj = text_objects(txt, pl, txt_i_h, graphic.legend_parameters.font, 1, 0)
        speed_axis.append(txt_obj)
    axis_line, axis_arrow, axis_ticks, text_planes, text = \
        profile.height_axis(_profile_height_, _vec_spacing_ * 2, met_wd, bp,
                            scale_fac, txt_h, feet_labels)
    height_axis = [from_linesegment3d(axis_line), from_mesh3d(axis_arrow)]
    for tic in axis_ticks:
        height_axis.append(from_linesegment3d(tic))
    for i, (pl, txt) in enumerate(zip(text_planes, text)):
        if i != len(text) - 1:
            txt_i_h, ha, va = txt_h, 2, 3
        else:
            txt_i_h, ha, va = txt_h * 1.25, 1, 5
        txt_obj = text_objects(txt, pl, txt_i_h, graphic.legend_parameters.font, ha, va)
        height_axis.append(txt_obj)
    
    # draw the legend and the title
    if graphic.legend_parameters.is_base_plane_default:
        graphic.legend_parameters.base_plane = \
            profile.legend_plane(max_speed, met_wd, bp, len_d, scale_fac)
    legend = legend_objects(graphic.legend)
    title_pl = profile.title_plane(met_wd, bp, len_d, scale_fac, txt_h)
    title = text_objects(title_txt, title_pl, txt_h, graphic.legend_parameters.font, 0, 0)

    # process the output lists of data
    anchor_pts = [from_point3d(pt) for pt in anchor_pts]
    wind_vectors = [from_vector3d(vec) for vec in wind_vectors]
    wind_speeds.insert(0, 0)  # insert 0 wind speed for bottom of curve

    # create the output VisualizationSet arguments
    vis_set = [profile, met_ws, met_wd, legend_par_, bp, _profile_height_,
               _vec_spacing_, len_d, height_d, max_speed, scale_fac, feet_labels]
    vis_set = objectify_output('VisualizationSet Aruments [WindProfile]', vis_set)