winprop二次开发

前言

工作需求,对该软件进行简单地二次开发,都是一些挺简单的代码,单纯是为了上传之后将其从本地删除

工具1——整合多个天线结果

用途

winprop最终的计算结果(必选项:接受功率,除此之外可选场强等)是以天线为单位给出热力图,如下图所示:
在这里插入图片描述

某些情况下,这种可视化的方式并不能满足我们的使用需求,比如:在一个实际项目中,我想知道我安装的两台路由器是否能够覆盖整个场景(使每个测试点位接受到的信号强度大于一定阈值),那么彼此独立的热力图并不能简洁地为你提供答案,这就是工具1的用途。
工具1中有一个关键的参数:rule,函数类型:输入是各天线在该测试点位的信号强度。在之前的场景中,我们希望的就是该点收到的信号强度中最强的那个大于阈值,所以我们的规则就是所有信号强度中取最大值。
在这里插入图片描述

代码实现

功能实现:
函数主体: plot_multi_heap_map
参数1:存放run结果的文件路径
参数2:规则(此处规则为平均值)

# ********************************************************************************************
#                                  Author: weixinzhuyi                                       #
#                                  Update: 2024-1-16                                         #
**********************************************************************************************
# a simple tool, but temporarily make up for defects that Winprop can't show us a synthetic image
# if you a better way to overcome the defect, please abandon this script immediately
import re
import os
import matplotlib.pyplot as plt
import numpy as np

base_path = 'D:/Desktop/黑龙江3D/PropName/'


# structure full path
def file_name(_site_name, _antenna_name):
    return base_path + "Site  " + str(_site_name) + " Antenna " + str(_antenna_name) + " Power.txt"


# extract information from a file with postfix "Power.txt"
def extract_pro(_file_path):
    test_point_pattern = re.compile(r'(\d+\.\d+)\s(\d+\.\d+)\s(-\d+\.\d+)')
    site_location_pattern = re.compile(r'LOCATION\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)')
    with open(_file_path, "r") as f:
        content = f.read()
    _test_point, test_point = re.findall(test_point_pattern, content), list()
    _site_location, site_location = re.findall(site_location_pattern, content), list()
    if _test_point:
        for _ in _test_point:
            test_point.append(list(map(float, list(_))))
        site_location = list(map(float, list(_site_location[0])))
    return site_location, test_point


# a interface to plot heatmap of a certain antenna of a certain site(ap)
def plot_single_heap_map(_site_location, _test_point):
    x, y, power = list(zip(*_test_point))
    x = set(x)
    y = set(y)
    plt.imshow(np.reshape(np.array(power), (len(x), len(y))), cmap=plt.get_cmap('rainbow'))
    plt.tight_layout()
    plt.colorbar()
    plt.show()


# get all file names which match name regex
def get_file_path(_path):
    # if you have a other name system, please change name regex here
    pattern = re.compile(r'Site\s+\d+ Antenna \d+ Power.txt')
    ret = list()
    for _ in os.listdir(_path):
        if re.findall(pattern, _):
            ret.append(_path + _)
    return ret


# plot a new heatmap according a certain rule that you offer
# we suppose that a rule you offer a lambda function or a bulit-in function, such as max, min
def plot_multi_heap_map(_path, rule=max):
    # Calculation
    file_dir = get_file_path(_path)
    site_locations, powers = list(), list()
    site_location, test_point = extract_pro(file_dir[0])
    _x, _y, power = list(zip(*test_point))
    site_locations.append(site_location)
    x = set(_x)
    y = set(_y)
    for file_path in file_dir[1:]:
        site_location, test_point = extract_pro(file_path)
        site_locations.append(site_location)
        _, __, power = list(zip(*test_point))
        powers.append(power)

    # Visualization
    final = list(map(rule, list(zip(*powers))))
    plt.imshow(np.reshape(np.array(final), (len(x), len(y))), cmap=plt.get_cmap('rainbow'))
    plt.tight_layout()
    plt.colorbar()
    _site_x, _site_y, _ = list(zip(*site_locations))
    site_x = (np.array(_site_x) - min(x)) * len(x) / (max(x) - min(x))
    site_y = (np.array(_site_y) - min(y)) * len(y) / (max(y) - min(y))
    plt.scatter(site_y, site_x)
    plt.show()


if __name__ == '__main__':
    plot_multi_heap_map(base_path, rule=lambda i: sum(i) / len(i))

工具2——wallman辅助工具

需求

众所周知,使用wallman工具建立三维仿真模型会得到一个**.idb的文件,这个文件可以用feko软件直接打开
在这里插入图片描述
除了保存为idb文件还可以保存为
.ida**二进制文本文件,我们对wallman的开发正是基于这种保存形式,
.ida文件的结构如下:(作者的注释用%%括起来,内容省略使用……)

* Indoor Database *     %数据库类型%
* Last changed on:  2. 1.2024 14: 8:50 *     %最后一次修改时间%

BEGIN_MATERIAL     %开始描述3D模型的材料,开头先介绍各种参数(电磁参数、厚度等)对应的位置%
* [MATERIAL]  [ID]  [GENERAL]  ["Name of Material"]  [Thickness (in cm)]   [Filled in Display]   [Color: Red]  [Color: Green]  [Color: Blue]
* [MATERIAL]  [ID]  [FREQUENCY]  [Frequency (in MHz)]  [Dielectrictity (relative)]   [Permeability (relative)]   [Conductivity (in S/m)]  [Transmission Loss Vertical (in dB)]  [Transmission Loss Horizontal (in dB)]  [Reflection Loss (in dB)]  [Diffraction Loss incident min (in dB)]   [Diffraction Loss incident max (in dB)]  [Diffraction Loss diffracted (in dB)]

MATERIAL 0 GENERAL "Default Material" 10.00000 1 150 150 150    %该材料只绑定了一个频段2000MHz%
MATERIAL 0 FREQUENCY 2000.000 4.000 1.000 0.010000 10.000 10.000 9.000 8.000 15.000 5.000 20.000 0.100 0.010 0.010 0.100

……
END_MATERIAL    %结束对材料的描述%

BEGIN_SHAPE    %对形状进行描述, 在保存时选择了compute the shape%
1
1 40 22.050100,89.050100, 5.000000 21.949900,89.050100, 5.000000 21.949900,56.050100, 5.000000 22.050100,89.050100, 5.000000
END_SHAPE    %结束对形状的描述%

BEGIN_WALLS   %开始对各类墙体的描述,一个条目代表一面墙,一个正方体对应6个条目%
63     %墙的总数%
%墙的编号    4    x1, y1, z1  x2, y2, z2  x3, y3, z3  x4, y4, z4  材料编号   类型(正常的墙还是有内嵌还是有洞)%
64 4 26.000000,17.000000, 5.000000 26.000000, 8.000000, 5.000000 26.000000, 8.000000, 0.000000 26.000000,17.000000, 0.000000  0 0 
63 4 27.000000,25.000000, 5.000000 27.000000,18.000000, 5.000000 27.000000,18.000000, 0.000000 27.000000,25.000000, 0.000000  0 0 
62 4 27.000000,34.000000, 5.000000 27.000000,28.000000, 5.000000 27.000000,28.000000, 0.000000 27.000000,34.000000, 0.000000  0 0 
……
1 4  5.000000, 0.000000, 0.000000  5.000000, 5.000000, 0.000000  0.000000, 5.000000, 0.000000  0.000000, 0.000000, 0.000000  0 0 
END_WALLS

在了解了ida文件格式后,我们可以通过构造ida文件解决许多繁琐的问题,比如:大量的复杂复制活动
这是一个立方体:
在这里插入图片描述

这是一群立方体:
在这里插入图片描述
或者规模更大
在这里插入图片描述
看起来,我们可以在操作界面通过简单的复制黏贴获得这样的图形,没必要通过构造ida文件的方式。
确实是,对于简单图形来讲,直接复制粘贴更简单,但是一旦遇到了含有嵌入关系或者是由几个基础高度不同的图形拼接起来的整体,往往无法直接复制粘贴。

代码实现

功能实现

可以自由选取墙体批量进行任意位移(增益:gain),比如在上面的例子中,选取的墙体为构成单个正方体的六面墙,位移是 ( x , y , 0 ) , x , y ∈ [ 2 , 4 , 6 , 8 , ⋯   ] (x , y, 0), x,y\in[2, 4, 6, 8,\cdots] (x,y,0),x,y[2,4,6,8,](没有高度的变化,所以第三个分量置为0)

参数输入

  • 入口函数:construct_new_ida_file
  • 输入参数1:位移(增益)(要求为可迭代对象
  • 输入参数2:位移对象(在原来ida文件中该墙体对应的编号)(要求为可迭代对象
  • 输入参数3:原来ida文件的路径
  • 输入参数4:新的ida文件的路径,缺省时默认为原有ida文件路径,进行覆盖
from collections import Iterable
from math import log10, ceil
import re

# 读取初始ida文件
def read_origin_ida_file(_path: str):
    with open(_path, 'r') as f:
        raw = f.read()
    wall_index = raw.find('BEGIN_WALLS')
    tmp = raw[wall_index + 12:-11].split('\n')
    amount = int(tmp[0])
    return amount, tmp[1:]

# 找到目标语句
def find_target_sentences(_index: Iterable, _path: str):
    amount, sentences = read_origin_ida_file(_path)
    found = 0
    target_sentences = list()
    for _ in sentences:
        if int(re.split(r'\s+', _)[0]) in _index:
            found += 1
            target_sentences.append(_)

    if len(target_sentences) != len(_index):
        print("Invalid index exists")
        return None
    return target_sentences


def construct_new_ida_file(_gain: Iterable, _index: Iterable, _input_path, _output_path=None):
    if not _output_path:
        _output_path = _input_path

    target_sentences = find_target_sentences(_index, _input_path)
    if not target_sentences:
        print("Error!")
        return
    new_sentences = list()
    for _ in _gain:
        for __ in target_sentences:
            tmp = list(filter(lambda i: i, re.split(r'\s+|,', __)))
            for ___ in range(12):
                tmp[___ + 2] = '%.6f' % (float(tmp[___ + 2]) + _[___ % 3])
            new_sentences.append(' '.join(tmp))
    print(new_sentences)
    with open(_input_path, 'r') as f:
        raw = f.read()
    wall_index = raw.find('BEGIN_WALLS')
    new_content = raw[:wall_index + 12]
    tmp = re.split('\n', raw[wall_index + 12:-11])
    pre_len = int(tmp[0])
    new_content += str(pre_len + len(new_sentences))
    new_content += raw[wall_index + 12 + ceil(log10(int(tmp[0]))):-12] + ' \n'
    for index, sentence in enumerate(new_sentences):
        _ = re.split(r'\s+', sentence)
        new_content += str(index + 1 + pre_len) + ' ' + _[1] + '  ' + _[2] + ', ' + _[3] + ',' + _[4] + '  ' + _[
            5] + ', ' + _[6] + ',' + _[7] + '  ' + _[8] + ', ' + _[9] + ',' + _[10] + '  ' + _[11] + ', ' + _[
                           12] + ',' + _[13] + '  ' + _[14] + ' ' + _[15] + ' \n'
    new_content += r'END_WALLS\n'
    print(new_content)
    with open(_output_path, 'w') as f:
        f.write(new_content)


if __name__ == '__main__':
    construct_new_ida_file(([12, 12, 12], [10, 10, 10]), list(range(1, 13)),
                           'D:/Desktop/等会就删.ida', 'D:/Desktop/hahahai.ida')

实验

不考虑实用性,假如这样一个场景:小正方体沿着圆柱盘旋上升,该怎么画?
选取位移对象是简单的,可以随便构造一个 1 × 1 × 1的立方体即可。关键在于对于位移的构造。
尝试使用极坐标的思想,让小正方体每次位移2,位移角度每次增加30°,同时高度每次增加0.2,
构造40个试试:

from math import cos, sin, pi
result = list()
result.append([2, 0, 0])
angle = 0
theta_z = 0.2
for _ in range(39):
    angle += pi / 6
    base = result[-1]
    theta_x = 2 * cos(angle)
    theta_y = 2 * sin(angle)
    result.append([base[0] + theta_x, base[1] + theta_y, base[2] + theta_z])

print(result)

效果如下:
在这里插入图片描述

改进

以上用于构造ida文件的construct_new_ida_file函数在构造大规模场景时会很慢,原因在于读写文件的方法存在问题:一次性将新的文本内容写入文件是缓慢的,合理的做法是:先以’w’模式创建新的文件,然后每次将一定规模的文本(如1000行)以’a’模式添加进文件。确实,这个过程中会多次打开和关闭文件,但是整体效果是远远优于一次性写入。当然,这个规模取多少,需要自己去尝试,不同电脑的最优规模大概率不等同。
函数改进如下:

def construct_new_ida_file_accelerate(_gain: Iterable | Sized, _index: Iterable | Sized, _input_path, _output_path=None):
    if not _output_path:
        _output_path = _input_path

    target_sentences = find_target_sentences(_index, _input_path)
    if not target_sentences:
        print("Error!")
        return

    new_len = len(_gain) * len(_index)
    with open(_input_path, 'r') as f:
        raw = f.read()
    wall_index = raw.find('BEGIN_WALLS')
    new_content = raw[:wall_index + 12]
    tmp = re.split('\n', raw[wall_index + 12:-11])
    pre_len = int(tmp[0])
    new_content += str(pre_len + new_len)
    new_content += raw[wall_index + 12 + ceil(log10(int(tmp[0]))):-12] + ' \n'

    with open(_output_path, 'w') as f:
        f.write(new_content)

    counter, cache = 0, str()
    for gain_unit in _gain:
        for sentence in target_sentences:
            counter += 1
            tmp = list(filter(lambda i: i, re.split(r'\s+|,', sentence)))
           cache += str(counter + pre_len) + ' ' + tmp[1] + '  ' + '%.6f' % (
                        float(tmp[2]) + gain_unit[0]) + ', ' + '%.6f' % (
                                 float(tmp[3]) + gain_unit[1]) + ',' + '%.6f' % (
                                 float(tmp[4]) + gain_unit[2]) + '  ' + '%.6f' % (
                                 float(tmp[5]) + gain_unit[0]) + ', ' + '%.6f' % (
                                 float(tmp[6]) + gain_unit[1]) + ',' + '%.6f' % (
                                 float(tmp[7]) + gain_unit[2]) + '  ' + '%.6f' % (
                                 float(tmp[8]) + gain_unit[0]) + ', ' + '%.6f' % (
                                 float(tmp[9]) + gain_unit[1]) + ',' + '%.6f' % (
                                 float(tmp[10]) + gain_unit[2]) + '  ' + '%.6f' % (
                                 float(tmp[11]) + gain_unit[0]) + ', ' + '%.6f' % (
                                 float(tmp[12]) + gain_unit[1]) + ',' + '%.6f' % (
                                 float(tmp[13]) + gain_unit[2]) + '  ' + tmp[14] + ' ' + tmp[15] + ' \n'

            if counter % 1000 == 0:
                with open(_output_path, 'a') as f:
                    f.write(cache)
                cache = str()

    cache += r'END_WALLS\n'
    with open(_output_path, 'a') as f:
        f.write(cache)
    return

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值