""" Модуль для расчета параметров РЛС по модели бистатической РЛС Автор кода: Владислав Калинников """ import math as mt from typing import List # скорость света в вакууме, м|с C = 299792458 # постоянная Больцмана, Дж/К BOLTZMANN = 1.38 * 10 ** (-23) # температура предатчика/приемника, К RADAR_TEMPERATURE = 290 # ширина антенны, м # ANTENNA_WIDTH = 0.08 # длина антенны, м # ANTENNA_LENGTH = 0.25 # коэффициент усиления антенны, дБ # ANTENNA_GAIN = 14 # длина волны, м # WAVELENGTH = 0.032 # пиковая мощность, ВТ # PEAK_POWER = 10 # полоса пропускания, МГц # BANDWIDTH = 1000 # шум-фактор, дБ # NOISE_FACTOR = 3 # коэффициент заполнения # q_fill = 0.08 # угол поляризации, град. 0 - горизонтальная поляризация, 90 - вертикальная поляризация # POLATIZATION_TILT_ANGLE = 0 # функция определяет координаты углов области пересечения проекций на земную поверхность главных лепестков # point_name - имя точки (A,B,C,D), по умолчанию равно A # h - высота полета в м, uav_interval - размер базы между РЛС, м # psi_t - угол курса оси главного лепестка ДНА на излучение (по часовой стрелке>0, против часовой <0), град # psi_r - угол курса оси главного лепестка ДНА на прием (по часовой стрелке>0, против часовой <0), град def footprint_corner_crd( point_name: str, h: float, uav_interval: float, psi_t: float, psi_r: float, wavelength: float, antenna_length: float, ): # угловой размер главного главного лепестка по азимуту theta = wavelength / antenna_length # угол курса главного лепестка ДНА на излучение в радианах psi_t = psi_t * mt.pi / 180 # угол курса главного лепестка ДНА на прием в радианах psi_r = psi_r * mt.pi / 180 if psi_r - psi_t > theta: # начальные значения поправок угла курса точки относительно РЛС1 и РЛС2 dpsi_1 = 0.1 dpsi_2 = 0.1 # начальные обновленные значения поправок угла курса точки относительно РЛС1 и РЛС2 dpsi_1upd = 0 dpsi_2upd = 0 # коэффициенты для расчета углов места и углов курса точки относительно РЛС1 и РЛС2 k1 = 1 k2 = 1 if point_name == "B": k1 = 1 k2 = -1 elif point_name == "C": k1 = -1 k2 = -1 elif point_name == "D": k1 = -1 k2 = 1 # определение углов места и углов курса точки относительно РЛС1 и РЛС2 методом последовательных приближений j = 0 while abs(dpsi_1upd - dpsi_1) > 0.01 or abs(dpsi_2upd - dpsi_2) > 0.01: dpsi_1 = dpsi_1upd dpsi_2 = dpsi_2upd psi_1 = psi_t + dpsi_1 psi_2 = psi_r + dpsi_2 phi_1 = mt.atan(uav_interval / h * mt.cos(psi_2) / mt.sin(psi_2 - psi_1)) phi_2 = mt.atan(uav_interval / h * mt.cos(psi_1) / mt.sin(psi_2 - psi_1)) if ( abs(mt.sin(theta / 2) / mt.sin(phi_1)) > 1 or abs(mt.sin(theta / 2) / mt.sin(phi_2)) > 1 or j > 1e3 ): return "none" j += 1 dpsi_1upd = k1 * mt.asin(mt.sin(theta / 2) / mt.sin(phi_1)) dpsi_2upd = k2 * mt.asin(mt.sin(theta / 2) / mt.sin(phi_2)) # итоговые значения углов места и углов курса точки относительно РЛС1 и РЛС2 dpsi_1 = dpsi_1upd dpsi_2 = dpsi_2upd psi_1 = psi_t + dpsi_1 psi_2 = psi_r + dpsi_2 phi_1 = mt.atan(uav_interval / h * mt.cos(psi_2) / mt.sin(psi_2 - psi_1)) phi_2 = mt.atan(uav_interval / h * mt.cos(psi_1) / mt.sin(psi_2 - psi_1)) # координаты точки x = h * mt.tan(phi_2) * mt.cos(psi_2) y = h * mt.tan(phi_2) * mt.sin(psi_2) return [x, y] else: return "none" # функция определяет водность облаков, г|м3 # cloud_thickness - толщина облаков def cloud_liquid_water_content(cloud_thickness: float) -> float: if cloud_thickness != 0: W = 0.132574 * (cloud_thickness / 1000) ** 2.30215 return W / cloud_thickness * 1000 else: return 0 # функция определяет ординтаты точек пересечения выпуклого многоугольника (polygon) с лучом, имеющем ординату x def polygon_cross_points(polygon: List[float], x: float) -> List[float]: y1 = "none" y2 = "none" for i in range(0, len(polygon[0])): if i < len(polygon[0]) - 1: k = i + 1 else: k = 0 if x >= min(polygon[0][i], polygon[0][k]) and x <= max( polygon[0][i], polygon[0][k] ): if polygon[0][i] != polygon[0][k]: if y1 == "none": y1 = (polygon[1][k] - polygon[1][i]) / ( polygon[0][k] - polygon[0][i] ) * (x - polygon[0][i]) + polygon[1][i] else: y2 = (polygon[1][k] - polygon[1][i]) / ( polygon[0][k] - polygon[0][i] ) * (x - polygon[0][i]) + polygon[1][i] else: y1 = polygon[1][i] y2 = polygon[1][k] return [min(y1, y2), max(y1, y2)] # функция определяет координаты углов РЛИ # h - высота полета в м, uav_interval - размер базы между РЛС, м # psi_t - угол курса оси главного лепестка ДНА на излучение (по часовой стрелке>0, против часовой <0), град # psi_r - угол курса оси главного лепестка ДНА на прием (по часовой стрелке>0, против часовой <0), град def frame_corner_crd( h: float, uav_interval: float, psi_t: float, psi_r: float, wavelength: float, antenna_length: float, ): # координаты области пересечения проекций на земную поверхность главных лепестков crd_A = footprint_corner_crd( point_name="A", h=h, uav_interval=uav_interval, psi_t=psi_t, psi_r=psi_r, wavelength=wavelength, antenna_length=antenna_length, ) crd_B = footprint_corner_crd( point_name="B", h=h, uav_interval=uav_interval, psi_t=psi_t, psi_r=psi_r, wavelength=wavelength, antenna_length=antenna_length, ) crd_C = footprint_corner_crd( point_name="C", h=h, uav_interval=uav_interval, psi_t=psi_t, psi_r=psi_r, wavelength=wavelength, antenna_length=antenna_length, ) crd_D = footprint_corner_crd( point_name="D", h=h, uav_interval=uav_interval, psi_t=psi_t, psi_r=psi_r, wavelength=wavelength, antenna_length=antenna_length, ) if crd_A != "none" and crd_B != "none" and crd_C != "none" and crd_D != "none": polygon = [ [crd_A[0], crd_B[0], crd_C[0], crd_D[0]], [crd_A[1], crd_B[1], crd_C[1], crd_D[1]], ] # максимальное и минимальное значение абсциисы области пересечения проекций на земную поверхность главных лепестков x_min = min(polygon[0]) x_max = max(polygon[0]) # точность оценики координат углов РЛИ accuracy = 20 # число итераций для поиска максимальной площади РЛИ N = 1 + int((x_max - x_min) / accuracy) # шаг по оси абсцисс при поиске максимальной плоащади РЛИ dx = (x_max - x_min) / N # начальные значения коориднат углов и площади РЛИ s_upd = 0 # dq_upd = 100 x1_upd = "none" x2_upd = "none" y1_upd = "none" y2_upd = "none" # поиск макисальной площади и углов РЛИ for i in range(0, N - 1): x1 = x_min + i * dx for j in range(i + 1, N): x2 = x_min + j * dx y11 = polygon_cross_points(polygon, x1)[0] y12 = polygon_cross_points(polygon, x1)[1] y21 = polygon_cross_points(polygon, x2)[0] y22 = polygon_cross_points(polygon, x2)[1] if y11 >= y21 and y12 <= y22: y1 = y11 y2 = y12 elif y11 <= y21 and y12 >= y21 and y12 <= y22: y1 = y21 y2 = y12 elif y11 <= y22 and y11 >= y21 and y12 >= y22: y1 = y11 y2 = y22 elif y11 <= y21 and y12 >= y22: y1 = y21 y2 = y22 else: y1 = "none" y2 = "none" if y1 != "none" and y2 != "none": s = (x2 - x1) * (y2 - y1) # dq = abs((y2-y1)/(x2-x1)-q) if s > s_upd: # dq < dq_upd: s_upd = s # dq_upd = dq x1_upd = x1 x2_upd = x2 y1_upd = y1 y2_upd = y2 # возврат массива координат углов РЛК (точек T1,T2,T3,T4) return [[x1_upd, x1_upd, x2_upd, x2_upd], [y1_upd, y2_upd, y2_upd, y1_upd]] else: return "none" # параметры a1, a2, a3 модели Кулемина для расчета УЭПР разных типов подстилающих поверхностей, дБ kulemin_parameters = { "лес летом": [-20, 10, 6], "лес зимой": [-40, 10, 6], "луг высокотравный": [-21, 10, 6], "луг низкотравный": [-28, 10, 6], "пашня": [-37, 18, 15], "снег": [-34, 25, 15], } # функция определеяет УЭПР поверхности в дБ согласно модели Кулемина # surface_type - тип подстилающей поверхности (пашня, снег и т.д.), phi - угол падения, град # если задан неизвестный тип подстилающей поверхности, функция вернет значение -20 дБ def kulemin_specific_rcs(surface_type, phi, wavelength): # частота зондирования в ГГц f = C / wavelength * 10 ** (-9) # угол скольжения в град slip = 90 - phi if kulemin_parameters.get(surface_type) != None: a1, a2, a3 = kulemin_parameters[surface_type] return a1 + a2 * mt.log(slip / 20, 10) + a3 * mt.log(f / 10, 10) else: return -20 # функция определяет коэффициент погонного ослабления в облаке, дБ/км/(г/м3) def itu_cloud_attenuation(wavelength): # частота зондирования в ГГц f = C / wavelength * 10 ** (-9) theta = 300 / 273.15 eps0 = 77.66 + 103.3 * (theta - 1) eps1 = 0.0671 * eps0 eps2 = 3.52 fp = 20.20 - 146 * (theta - 1) + 316 * (theta - 1) ** 2 fs = 39.8 * fp eps_prime = f * (eps0 - eps1) / fp / (1 + (f / fp) ** 2) + f * ( eps1 - eps2 ) / fs / (1 + (f / fs) ** 2) eps_double_prime = ( (eps0 - eps1) / (1 + (f / fp) ** 2) + (eps1 - eps2) / (1 + (f / fs) ** 2) + eps2 ) nabla = (2 + eps_prime) / eps_double_prime return 0.819 * f / eps_double_prime / (1 + nabla**2) # параметры kh, ah, kv, av модели погонного ослабления в дожде МСЭ-R P.838-3 для частот 7 - 12 ГГц itu_rain_parameters = { 7: [0.001915, 1.4810, 0.001425, 1.4745], 8: [0.004115, 1.3905, 0.003450, 1.3797], 9: [0.007535, 1.3155, 0.006691, 1.2895], 10: [0.012170, 1.2571, 0.011290, 1.2156], 11: [0.017720, 1.2140, 0.017310, 1.1617], 12: [0.023860, 1.1825, 0.024550, 1.1216], } # классификация погодных условий по интенсивности осадков, мм/ч rain_rate_classes = { "ясно": 0, "слабый дождь": 5, "умеренный дождь": 12, "сильный дождь": 30, "ливень": 40, } # функция определяет коэффициент погонного ослабления в дожде в дБ/км для диапазона X согласно модели МСЭ-R P.838-3 # rain_rate - интенсивность дождя в мм/ч, phi- угол падения, град # если длина волны зондирования лежит не в диапазоне X, функция вернет 0 def itu_rain_attenuation( rain_rate: float, phi: float, wavelength: float, polarization_tilt_angle: float ) -> float: # частота зондирования в ГГц f = C / wavelength * 10 ** (-9) # угол скольжения в радианах slip = (90 - phi) * mt.pi / 180 # угол поляризации в радианах tilt = polarization_tilt_angle * mt.pi / 180 # целочисленные значения частот вокруг частоты зондирования f1 = int(f) f2 = int(f) + 1 if (itu_rain_parameters.get(f1) != None) and (itu_rain_parameters.get(f2) != None): # параметры модели для частоты f1 kh1 = itu_rain_parameters[f1][0] ah1 = itu_rain_parameters[f1][1] kv1 = itu_rain_parameters[f1][2] av1 = itu_rain_parameters[f1][3] # параметры модели для частоты f2 kh2 = itu_rain_parameters[f2][0] ah2 = itu_rain_parameters[f2][1] kv2 = itu_rain_parameters[f2][2] av2 = itu_rain_parameters[f2][3] # интерполяция параметров модели на частоту зондирования f kh = kh1 + (kh2 - kh1) / (f2 - f1) * (f - f1) ah = ah1 + (ah2 - ah1) / (f2 - f1) * (f - f1) kv = kv1 + (kv2 - kv1) / (f2 - f1) * (f - f1) av = av1 + (av2 - av1) / (f2 - f1) * (f - f1) # параметры модели для заданной поляризации k = (kh + kv + (kh - kv) * (mt.cos(slip)) ** 2 * mt.cos(2 * tilt)) / 2 a = ( ( kh * ah + kv * av + (kh * ah - kv * av) * (mt.cos(slip)) ** 2 * mt.cos(2 * tilt) ) / 2 / k ) return k * (rain_rate) ** a else: return 0 # входные параметры модели: # v - скорость полета, м/с # h - высота полета, м # uav_interval - размер базы между РЛС, м # psi_t - угол курса оси главного лепестка ДНА на излучение (по часовой стрелке>0, против часовой <0), град # psi_r - угол курса оси главного лепестка ДНА на прием (по часовой стрелке>0, против часовой <0), град # srcs - УЭПР фона, дБ или тип поверхности по модели Кулемина # cloud_base - высота нижней границы облаков, м # cloud_thickness - толщина облаков, м # rain_rate - интенсивность дождя, мм/ч # выходные параметры модели: # dx - предельное разрешение по горизонтальной дальности в центре РЛК, м # dy - предельное разрешение по азимуту в центре РЛК, м # snr - отношение сигнал/шум фона в центре РЛК, дБ # tau - длина импульса, с # tau_echo - длина эхо-сигнала, с # t_repeat - период повторения импульсов, с # t_synthesis_max - максимальное время синтеза апертуры, с def bistatic_radar_model( v: float, h: float, uav_interval: float, psi_t: float, psi_r: float, srcs: str | float, cloud_base: float, cloud_thickness: float, rain_rate: float, q_fill: float, bandwidth: float, wavelength: float, antenna_gain: float, antenna_length: float, noise_factor: float, peak_power: float, polarization_tilt_angle: float, ): # координаты РЛС1 x1 = 0 y1 = uav_interval # координаты РЛС2 x2 = 0 y2 = 0 # коодинаты точки T1 frame_crd = frame_corner_crd( h=h, uav_interval=uav_interval, psi_t=psi_t, psi_r=psi_r, wavelength=wavelength, antenna_length=antenna_length, ) if frame_crd != "none": x_t1 = frame_crd[0][0] y_t1 = frame_crd[1][0] # коодинаты точки T2 x_t2 = frame_crd[0][1] y_t2 = frame_crd[1][1] # коодинаты точки T3 x_t3 = frame_crd[0][2] y_t3 = frame_crd[1][2] # коодинаты точки T4 x_t4 = frame_crd[0][3] y_t4 = frame_crd[1][3] # коодинаты центра РЛК xo = (x_t2 + x_t3) / 2 yo = (y_t1 + y_t2) / 2 # наклонные дальности от РЛС1 до углов и центра РЛК r1_t1 = mt.sqrt((x_t1 - x1) ** 2 + (y_t1 - y1) ** 2 + h**2) r1_t2 = mt.sqrt((x_t2 - x1) ** 2 + (y_t1 - y1) ** 2 + h**2) r1_t3 = mt.sqrt((x_t3 - x1) ** 2 + (y_t1 - y1) ** 2 + h**2) r1_t4 = mt.sqrt((x_t4 - x1) ** 2 + (y_t1 - y1) ** 2 + h**2) r1_o = mt.sqrt((xo - x1) ** 2 + (yo - y1) ** 2 + h**2) # наклонные дальности от РЛС2 до углов и центра РЛК r2_t1 = mt.sqrt((x_t1 - x2) ** 2 + (y_t1 - y2) ** 2 + h**2) r2_t2 = mt.sqrt((x_t2 - x2) ** 2 + (y_t1 - y2) ** 2 + h**2) r2_t3 = mt.sqrt((x_t3 - x2) ** 2 + (y_t1 - y2) ** 2 + h**2) r2_t4 = mt.sqrt((x_t4 - x2) ** 2 + (y_t1 - y2) ** 2 + h**2) r2_o = mt.sqrt((xo - x2) ** 2 + (yo - y2) ** 2 + h**2) # угол падения импульса от РЛС1 в центр РЛК phi1_o = mt.atan(h / r1_o) * 180 / mt.pi # угол отражения импульса от центра РЛК к РЛС2 phi2_o = mt.atan(h / r2_o) * 180 / mt.pi # модуль градиента от суммы наклонных дальностей от РЛС1 и РЛС2 до центра РЛК grad_r = mt.sqrt((xo / r1_o + xo / r2_o) ** 2 + (yo / r1_o + yo / r2_o) ** 2) # модуль градиента от суммы доплеровских частот на трассах от РЛС1 до центра РЛК и от центра РЛК до РЛС2 grad_f = ( v / wavelength * mt.sqrt( (1 / r1_o + 1 / r2_o - xo**2 / r1_o**3 - xo**2 / r2_o**3) ** 2 + (xo * yo / r1_o**3 + xo * yo / r2_o**3) ** 2 ) ) # длина импульса tau = min(r1_t1 + r2_t1, r1_t2 + r2_t2) / C # длина эхо-сигнала tau_echo = ( tau + max(r1_t3 + r2_t3, r1_t4 + r2_t4) / C - min(r1_t1 + r2_t1, r1_t2 + r2_t2) / C ) # период повторения импульсов t_repeat = tau / q_fill # максимальный коэффициент сжатия k_compression_max = bandwidth * 1000000 * tau # максимальное время синтеза апертуры t_synthesis_max = 2 * (x_t3 - x_t2) / v # максимальное число когерентных импульсов n_coh_max = 1 + int(t_synthesis_max / t_repeat) # разрешение по горизонтальной дальности dx = C * tau / k_compression_max / grad_r # разрешение по азимуту dy = 1 / t_synthesis_max / grad_f # УЭПР фона if type(srcs) == str: srcs = kulemin_specific_rcs(surface_type=srcs, phi=(phi1_o + phi2_o) / 2, wavelength=wavelength) # ЭПР точечного отражателя фона sigma = 10 ** (srcs / 10) * dx * dy # эффективная площадь антенны antenna_area = 10 ** (antenna_gain / 10) * wavelength**2 / 4 / mt.pi # мощность эхо-сигнала от центра РЛК p_echo = ( peak_power * 10 ** (antenna_gain / 10) * antenna_area / (4 * mt.pi * r1_o * r2_o) ** 2 * sigma * k_compression_max * n_coh_max ) # мощность шума p_noise = ( BOLTZMANN * 10 ** (noise_factor / 10) * RADAR_TEMPERATURE * bandwidth * 1000000 ) # отношение сигнал/шум без учета погоды snr = 10 * mt.log(p_echo / p_noise, 10) # протяженность наклонных дальностей в облаках и дожде, км if cloud_thickness != 0: cloud_path1 = ( r1_o / 1000 * (min(h, cloud_base + cloud_thickness) - min(h, cloud_base)) / h ) cloud_path2 = ( r2_o / 1000 * (min(h, cloud_base + cloud_thickness) - min(h, cloud_base)) / h ) rain_path1 = r1_o / 1000 * min(h, cloud_base) / h rain_path2 = r2_o / 1000 * min(h, cloud_base) / h else: cloud_path1 = 0 cloud_path2 = 0 rain_path1 = 0 rain_path2 = 0 # водность облаков w = cloud_liquid_water_content(cloud_thickness=cloud_thickness) # ослабление в облаках по модели МСЭ-R P.840-7 cloud_att1 = itu_cloud_attenuation(wavelength=wavelength) * w * cloud_path1 cloud_att2 = itu_cloud_attenuation(wavelength=wavelength) * w * cloud_path2 # ослабление в дожде по модели МСЭ-R P.838-3 rain_att1 = ( itu_rain_attenuation( rain_rate=rain_rate, phi=phi1_o, wavelength=wavelength, polarization_tilt_angle=polarization_tilt_angle, ) * rain_path1 ) rain_att2 = ( itu_rain_attenuation( rain_rate=rain_rate, phi=phi2_o, wavelength=wavelength, polarization_tilt_angle=polarization_tilt_angle, ) * rain_path2 ) # отношение сигнал/шум с учетом погодных условий snr = snr - cloud_att1 - cloud_att2 - rain_att1 - rain_att2 return { "dx": dx, "dy": dy, "snr": snr, "tau": tau, "tau_echo": tau_echo, "t_repeat": t_repeat, "t_synthesis_max": t_synthesis_max, } else: return "none"