winprop二次开发
- 前言
- 工具1——整合多个天线结果
- 用途
- 代码实现
- 工具2——wallman辅助工具
- 需求
- 代码实现
- 功能实现
- 参数输入
- 实验
前言
工作需求,对该软件进行简单地二次开发,都是一些挺简单的代码,单纯是为了上传之后将其从本地删除
工具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 npbase_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 herepattern = 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):# Calculationfile_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)# Visualizationfinal = 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 = 0target_sentences = list()for _ in sentences:if int(re.split(r'\s+', _)[0]) in _index:found += 1target_sentences.append(_)if len(target_sentences) != len(_index):print("Invalid index exists")return Nonereturn target_sentencesdef construct_new_ida_file(_gain: Iterable, _index: Iterable, _input_path, _output_path=None):if not _output_path:_output_path = _input_pathtarget_sentences = find_target_sentences(_index, _input_path)if not target_sentences:print("Error!")returnnew_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 / 6base = 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)
效果如下: