102 lines
3.1 KiB
TypeScript
102 lines
3.1 KiB
TypeScript
/**
|
|
* Mudança de rugosidade do terreno — NBR 6123:2023, sec. 5.5
|
|
*
|
|
* Implementa o perfil de velocidades (fatores S₂) na zona de transição
|
|
* entre duas categorias de rugosidade.
|
|
*/
|
|
|
|
import type { TerrainCategory } from './wind-kernel';
|
|
import { Z0_BY_CATEGORY, ZG_BY_CATEGORY } from './nbr-tables/table-5';
|
|
|
|
export interface RoughnessTransitionInput {
|
|
/** Categoria da edificação (terreno próximo) */
|
|
near: TerrainCategory;
|
|
/** Categoria do terreno a barlavento (mais afastado) */
|
|
far: TerrainCategory;
|
|
/** Distância horizontal x a partir da linha de mudança (m) */
|
|
distance: number;
|
|
/** Altura z acima do terreno (m) */
|
|
height: number;
|
|
}
|
|
|
|
export interface RoughnessTransitionResult {
|
|
z1: number;
|
|
z2: number;
|
|
/** S₂ equivalente na posição (x, z) */
|
|
s2: number;
|
|
}
|
|
|
|
/** Calcula A conforme sec. 5.5.1 ou 5.5.2 */
|
|
function coefficientA(z02: number, z01: number, case2to1: boolean): number {
|
|
if (case2to1) {
|
|
return 0.63 - 0.03 * Math.log(z02 / z01);
|
|
}
|
|
return 0.73 - 0.03 * Math.log(z02 / z01);
|
|
}
|
|
|
|
/**
|
|
* Cálculo da altura z₁ (topo da camada de transição — perfil da
|
|
* categoria mais próxima). Caso 5.5.1: z_{02} > z_{01}.
|
|
*/
|
|
function computeZ1(z02: number, z01: number, x: number, case2to1: boolean): number {
|
|
const A = coefficientA(z02, z01, case2to1);
|
|
return A * z02 * Math.pow(x / z02, 0.8);
|
|
}
|
|
|
|
/** Caso 5.5.2: z_{02} < z_{01} — calcula z₁ diferente */
|
|
function computeZ1Case5_5_2(z02: number, x: number): number {
|
|
const A = 0.73 - 0.03 * Math.log(z02 / 0.07);
|
|
return A * z02 * Math.pow(x / z02, 0.8);
|
|
}
|
|
|
|
/** S₂ da categoria para uma altura z (usa tabela de parâmetros) */
|
|
function s2FromCategory(cat: TerrainCategory, z: number, params: Map<TerrainCategory, { b: number; p: number; fr: number }>): number {
|
|
const p = params.get(cat);
|
|
if (!p) throw new Error(`Categoria inválida: ${cat}`);
|
|
const zEff = Math.max(5, Math.min(z, ZG_BY_CATEGORY[cat]));
|
|
return p.b * p.fr * Math.pow(zEff / 10, p.p);
|
|
}
|
|
|
|
/**
|
|
* Calcula S₂ efetivo considerando mudança de rugosidade.
|
|
* Requer tabela de parâmetros já construída pelo chamador (vide
|
|
* `getDefaultParams` em wind-kernel).
|
|
*/
|
|
export function applyRoughnessChange(
|
|
input: RoughnessTransitionInput,
|
|
params: Map<TerrainCategory, { b: number; p: number; fr: number }>,
|
|
): RoughnessTransitionResult {
|
|
const { near, far, distance, height } = input;
|
|
const z01 = Z0_BY_CATEGORY[near];
|
|
const z02 = Z0_BY_CATEGORY[far];
|
|
const x = Math.max(0, distance);
|
|
|
|
const case2to1 = z02 > z01;
|
|
|
|
let z1: number;
|
|
if (case2to1) {
|
|
z1 = computeZ1(z02, z01, x, true);
|
|
} else {
|
|
z1 = computeZ1Case5_5_2(z02, x);
|
|
}
|
|
|
|
const s2Far = s2FromCategory(far, height, params);
|
|
const s2Near = s2FromCategory(near, height, params);
|
|
|
|
let s2: number;
|
|
if (height >= z1) {
|
|
s2 = s2Far;
|
|
} else {
|
|
const z2 = case2to1
|
|
? 0.36 * z02 * Math.pow(x / z02, 0.8)
|
|
: 0.36 * z02 * Math.pow(x / z02, 0.8);
|
|
if (height <= z2) {
|
|
s2 = s2Near;
|
|
} else {
|
|
const t = (height - z2) / (z1 - z2);
|
|
s2 = s2Near + t * (s2Far - s2Near);
|
|
}
|
|
}
|
|
|
|
return { z1, z2: 0.36 * Z0_BY_CATEGORY[far] * Math.pow(x / Z0_BY_CATEGORY[far], 0.8), s2 };
|
|
} |