Simulación en Python para la determinación de la intensidad de campo recibida por onda de superficie para frecuencias entre 10 kHz y 30 MHz según la Rec. UIT-R P.368
Abstract
En este trabajo se ha desarrollado un simulador con interfaz gráfica en Python bajo el nombre GroundWave Simulator, basado en el programa de computador GRWAVE que utiliza la Rec. ITU-R P.368 [1]. Sobre escenarios de transmisión de ondas superficiales, se estudia la intensidad de campo eléctrico que se recibe a cierta distancia, en función de las características del terreno, para frecuencias entre 10 kHz y 30 MHz en una suposición de Tierra esférica lisa
Full text
Proyecto Fin de Carrera Ingeniería de Telecomunicación Formato de Publicación de la Escuela Técnica Superior de Ingeniería Autor: F. Javier Payán Somet Tutor: Juan José Murillo Fuentes Dep. Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2013 Trabajo Fin de Grado Grado en Ingeniería de las Tecnologías de Telecomunicación Simulación en Python para la determinación de la intensidad de campo recibida por onda de superficie para frecuencias entre 10 kHz y 30 MHz según la Rec. UIT-R P.368 Autor: Javier Ojeda Prieto Tutor: Susana Hornillo Mellado Dpto. Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2020
Trabajo Fin de Grado Grado en Ingeniería de las Tecnologías de Telecomunicación Simulación en Python para la determinación de la intensidad de campo recibida por onda de superficie para frecuencias entre 10 kHz y 30 MHz según la Rec. UIT-R P.368 Autor: Javier Ojeda Prieto Tutor: Susana Hornillo Mellado Profesora Contratada Doctora Dpto. Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2020
Trabajo Fin de Grado: Simulación en Python para la determinación de la intensidad de campo recibida por onda de superficie para frecuencias entre 10 kHz y 30 MHz según la Rec. UIT-R P.368 Autor: Javier Ojeda Prieto Tutor: Susana Hornillo Mellado El tribunal nombrado para juzgar el trabajo arriba indicado, compuesto por los siguientes profesores: Presidente: Vocal/es: Secretario: acuerdan otorgarle la calificación de: El Secretario del Tribunal Fecha:
Agradecimientos Este proyecto no ha sido desarrollado en solitario, sino que ha necesitado colaboración en varios campos que han agilizado y mejorardo el contenido. La correcta orientación y motivación para completar este trabajo corresponde a Susana Hornillo, aludiendo especialmente al conocimiento aportado sobre teoría de ondas superficiales. A Fran agradezco su ayuda para el análisis realizado del código fuente mediante SonarQube, y asistencia en lenguaje Python. El diseño del documento en código L A T EXse ha apoyado en la asistencia de Alex. No obstante, bastante contenido de este trabajo perdería calidad de no haber contado con la aportación de mi compañero Andrés. Javier Ojeda Prieto Sevilla, 2020 I
Resumen En este trabajo se ha desarrollado un simulador con interfaz gráfica en Python bajo el nombre GroundWave Simulator, basado en el programa de computador GRWAVE que utiliza la Rec. ITU-R P.368 [1]. Sobre escenarios de transmisión de ondas superficiales, se estudia la intensidad de campo eléctrico que se recibe a cierta distancia, en función de las características del terreno, para frecuencias entre 10 kHz y 30 MHz en una suposición de Tierra esférica lisa. III
1 Introducción La necesidad de modernizar la ejecución del programa GRWAVE, creado en 1985, hace que surja este simulador en Python, que ofrecerá la posibilidad de conectarse con otros módulos creados en este lenguaje para facilitar estudios sobre la propagación de ondas. GRWAVE fue desarrollado para el cálculo de intensidad de campo eléctrico y sus pérdidas en función de valores de distancia, utilizando ondas de superficie. Está escrito en código Fortran y funciona únicamente en entornos MS-DOS, que con el tiempo ha supuesto un obstáculo por desuso. Como primer objetivo, se persigue extraer el máximo rendimiento de las capacidades de GRWAVE, haciendo un estudio sobre teoría de ondas de superficie. Siguiendo la Rec. ITU-R P.368 [ 1 ], se logra sacar partido a la herramienta, permitiendo obtener resultados tanto para escenarios con un terreno homogéneo como para terrenos mixtos mediante el Método de Millington. Se proporciona además una interfaz gráfica al usuario simple y eficaz, desarrollada con la librería PyQt de Python, concretamente la versión PyQt5, compaginado con el entorno de desarrollo Qt Designer para el diseño, que permite al usuario desconocer detalles sobre la obtención de datos de GRWAVE en MS-DOS. Finalmente, a raíz de la realización de pruebas mediante el simulador, se formulan conclusiones relacionadas con los parámetros de entrada. En primer lugar, sobre la selección de alturas de antena se concluye la poca influencia de éstas, salvo para casos de frecuencias cercanas a 30 MHz. Seguidamente se determina la importancia de utilizar polarización vertical frente a la desventaja que supondría una polarización horizontal, que atenúa rápidamente los valores de intensidad. Por último, se intenta comprobar los efectos de alterar el orden de los terrenos en ejecuciones para terreno mixto, reflejando cambios notables para más de dos superficies con características eléctricas distintas. 1
2 Teoría de ondas de superficie Las bases para asentar la teoría de ondas de superficie fueron introducidas por Sommerfeld [ 2 ] en 1909 a través de un dipolo eléctrico vertical situado en la interfaz entre un plano aislante y otro conductor, donde se trabajaba con un modelo de Tierra plana con una conductividad y permitividad finita. Este modelo estudiaba la atenuación considerando pérdidas. Norton [ 3 ] amplió este desarrollo años después corrigiendo algunos errores prácticos de Sommerfeld, también para Tierra plana [ 4 ]. En 1939, Van der Pol y Bremmer [ 5 ], con el uso de fórmulas de serie de residuos, avanzaron a la consideración de Tierra esférica, con el resultado final de un valor de intensidad de campo eléctrico a largas distancias. Dos años después, Norton [ 6 ] añadió algunos matices a este último desarrollo haciéndolo más práctico, y del que saldría la terminación “Norton Surface wave” usada para denotar ondas superficiales con antenas adyacentes a la superficie de la Tierra. Sin embargo, todos estos trabajos no servían si tratabas de variar la caracterización del terreno a lo largo del cálculo, principalmente la conductividad de éste y la permitividad relativa. En pos de solventar esta incapacidad, Millington [ 7 ] en 1949 desarrolló un método aplicable a más de un terreno, aproximando los efectos topográficos usando una conductividad equivalente. Este método generaría errores de cálculos considerables para terrenos montañosos. 2.1 Onda de superficie En la propagación electromagnética de onda media, cuando el objetivo es la comunicación a largas distancias, la onda de suelo u onda de superficie juega un papel fundamental. Hablamos de la banda de 300 kHz a 3 MHz (MF). La onda de superficie se propaga cerca de la capa de la superficie terrestre como se muestra en la figura 2.1. Debido a la radiación en linea recta de las ondas electromagnéticas, la curvatura de la Tierra limitará la distancia de la línea de visión. En cambio, dependiendo de la dirección, algunas ondas son curvadas o reflejadas de vuelta a la superficie por la ionosfera, que facilitará la transmisión por radio sobre la Tierra haciendo llegar la onda más allá de la línea de visión. Fue bastante común la onda superficial en sistemas de radionavegación marítima y aérea, o localización por radio, hasta ser eclipsado por la comunicación por satélite. Sin embargo, a bajas frecuencias (LF) sigue siendo una herramienta común. Para conseguir una máxima precisión en el cálculo, es muy necesario conocer las variables que influyen, como la refracción de la atmósfera, los distintos tipos de terreno que conducen a valores propios de conductividad y permitividad relativa, topografía, etc. Hϕ=−E Z0 (2.1) La onda de superficie está compuesta por onda espacial y onda de suelo. Cuando las antenas transmisoras y receptoras se encuentran cerca de la superficie terrestre, la onda de suelo es el principal modo de propagación. Se debe a que las ondas directa y reflejada, que conforman la onda espacial, se contraponen en fase y cancelan entre sí. No obstante, cuando hablamos de frecuencias altas y antenas con elevación, el propio suelo producirá reflexiones o difracciones que harán que no convenga utilizar la onda de superficie como modo de propagación. 3
4Capítulo 2. Teoría de ondas de superficie Figura 2.1 Transmisión de onda superficial y onda de cielo [8]. 2.1.1 Polarización Las ondas de tierra, que se propagan por la superficie terrestre, deberán estar polarizadas verticalmente. Solo de esta manera no existirá el componente tangencial del campo eléctrico y por tanto, podrá propagarse a lo largo de la superficie de la Tierra. Si se utilizara polarización horizontal, en una suposición de Tierra como conductor perfecto, se haría imposible la propagación de cualquier componente horizontal, que, en contacto con la Tierra, sería cortocirtuitado. Sabiendo que la Tierra no es conductor perfecto y su conductividad depende de valores finitos ( σ , εr ), sí podrá albergar un campo eléctrico en su interior, es decir, una parte de esa componente horizontal podrá penetrar en la superficie terrestre. A pesar de esto, se disipará rápidamente en forma de calor y la onda se atenuará, alcanzando distancias muy cortas (algo que en las comunicaciones no es lo deseable). Habitualmente se utilizan antenas formadas por dipolos eléctricos verticales, que radian con polarización vertical, sobre tierra y con una altura desde 50 a 200 metros aproximadamente. El alcance que conseguirán dependerá de la potencia con la que se radie y la frecuencia. 2.1.2 Atenuación y pérdidas Como la onda propagada contiene un componente de campo magnético horizontal que afectará al campo eléctrico según la fórmula 2.2, conforme la distancia va aumentando, la onda tiende a inclinarse a la horizontal, y esa inclinación derivará pérdidas y atenuación. Es decir, el vector de onda en las cercanías de la superficie terrestre es curvado ligeramente en el sentido de la propagación. Dependerá de las características de conductividad del terreno y la frecuencia que se usa en la transmisión. Hϕ=−E Z0 (2.2) siendo Z0 la impedancia intrínseca de valor 120 πΩ , E el módulo de intensidad de campo eléctrico recibido yHϕla componente azimutal de campo magnético (trabajando en coordenadas cilíndricas).
2.1 Onda de superficie 5 Figura 2.2 Vector del frente de ondas en transmisión de onda de superficie. Esa inclinación se puede medir mediante la permitividad dieléctrica compleja que actúa relacionada con las componentes del campo eléctrico: Eρ Ez≈u=1 √Kr (2.3) El procedimiento en detalle para este cálculo se puede consultar en el manual sobre propagación de ondas superficiales de la ITU [9]. Además, la conductividad del terreno atenúa la intensidad del campo eléctrico cuando penetra en la superficie terrestre de forma exponencial: Et(x) = E0e−x δ(2.4) Donde Et(x) es la intensidad de campo eléctrico que ha penetrado en la superficie terrestre en función de la distancia x , E0 se toma como la intensidad de campo eléctrico en la frontera entre el aire y la superficie terrestre, y δes la profundidad de penetración expresada en metros: δ=s1 πfµσ (2.5) Aparece un nuevo parámetro µ, que se define como la permeabilidad del medio. 2.1.3 Simplificación de Tierra plana En un escenario con distancias relativamente cortas entre transmisor y receptor que harían que se desprecie la curvatura terrestre, con un terreno homogéneo, se puede usar un método de obtención de la intensidad de campo eléctrico de Tierra plana, también llamado método de ecuación integral [10]. En el siguiente caso de propagación, se observa que el rayo directo es más corto que el rayo de la onda reflejada, es decir, existirá un desfase entre las dos ondas que permitirá, como ya se ha comentado, que el principal modo de propagación sea la onda de suelo. Figura 2.3 Propagación con reflexión sobre tierra plana.
6Capítulo 2. Teoría de ondas de superficie Con la suposición de Tierra como conductor imperfecto, Richards [ 11 ] demostró el calculo de la intensidad de campo incluyendo procedimientos que tenían en cuenta la atenuación por conductividad finita, a falta de la ya mencionada atenuación por curvatura terrestre. 2.1.4 Tierra esférica lisa Dejando a un lado el modelo de Sommerfeld para distancias cortas, se puede seguir considerando Tierra plana para una distancia en kilómetros marcada por la expresión: d=80 3 pf(MHz)(2.6) Para distancias mayores, si se quiere obtener con mayor precisión la intensidad de campo, es necesario considerar los efectos producidos por la difracción por curvatura terrestre, descritos por Bremmer [12]. 2.2 Recomendación ITU-R P.368-9 El Sector de Radiocomunicaciones de la Unión Internacional de Telecomunicaciones, ITU-R, consideró útil reunir distintas ejecuciones de curvas de propagación por onda de superficie, utilizando distintos terrenos y frecuencias. Para las frecuencias, conociendo que las bandas de LF y MF son provechosas y efectivas, marcan un rango que va desde los 10 kHz hasta los 30 MHz, y el documento aporta multitud de casos en los que recomienda basarse para la determinación de la intensidad de campo eléctrico recibida. Sin embargo, hay que asegurarse de que los efectos por reflexiones ionosféricas sean despreciables para el caso que se esté analizando, así como la altura de la antena receptora no puede colocarse a una altura superior a: h=1,2σ1 2λ3 2(2.7) donde σ es la conductividad y λ la longitud de onda, en los casos en los que la permitividad relativa cumpla: εr60πλ (2.8) La recomendación se basa en la hipótesis de Tierra esférica homogénea lisa. Realmente la superficie terrestre no suele ser homogénea, sino que se compone por varias capas, cada una con distinta característica eléctrica y de ahí nace el concepto parámetro efectivo que representa al suelo homogéneo en una zona. Los cálculos de dichos parámetros efectivos vienen descritos en la Recomendación P. 527 de la ITU-R [ 13 ]. Esta hipótesis se basa en un modelo aproximado que pierde exactitud en cuanto el terreno se vuelve montañoso o se manifiestan edificios considerables en zonas urbanas, es decir, aparece el fenómeno fading o desvanecimiento [14]. Para la estimación del índice de refracción radioeléctrica se ha seguido la Recomendación P. 453 de la ITU-R [15], donde se describe que en la troposféra disminuye de forma exponencial con dependencia de la altura. Tanto antenas transmisora como receptora se encuentran instaladas en tierra, con un monopolo vertical corto (comparado con λ /4) como elemento radiante, PRA de 1 kW y fuerza ciclomotriz 300 V. Y los resultados se han calculado para región de campo lejano de la antena. Las curvas del Anexo 1 de ese documento se han generado con el programa GRWAVE, que determina el valor de campo E (dB(µV/m)), y del que se pueden derivar las pérdidas básicas de propagación: Lb[dB] = 142,0+20log fMHz −E(2.9) Las curvas representadas en función de los valores de GRWAVE, y cuya semejanza se tiene que imitar en el simulador propuesto para el proyecto, serían del estilo a la figura 2.4. Se han incluido curvas para el rango entre 10 kHz hasta 30 MHz, expresando en la leyenda también la longitud de onda correspondiente.
2.2 Recomendación ITU-R P.368-9 7 Figura 2.4 Ejemplo de curva de la Rec. P.368-9 para terreno homogéneo agua de mar (salinidad baja). Sin embargo, para obtener el resultado final teniendo en cuenta los factores de la antena emisora, es necesario utilizar la directividad y potencia radiada de la antena. E=EcurvarPRA ·D 3(2.10) Donde D es la directividad y PRA la potencia radiada por la antena. Como hemos comentado, la PRA se corresponde con 1 kW. Respecto a la directividad, en una antena vertical corta es de 1,5, sin embargo, este valor se duplica debido a la consideración de la Tierra como conductor (teoría de imágenes aplicadas a antenas [16]). Sustituyendo estos valores, Ecurva se puede escribir como el resultado final. 2.2.1 Profundidad de penetración Los parámetros que caracterizan el terreno influyen en la transmisión de las ondas superficiales a través de la profundidad de penetración δ en el suelo. La determinación de la profundidad de penetración en un material se utiliza la siguiente fórmula: δ= √2 ω√µrµ0εrε0! s1+σ ωεrε02 −1 −1 2 (2.11) donde ω =2 π f, µr la permeabilidad relativa, µ0 la permeabilidad en espacio libre y ε0 la permitividad en espacio libre, además de las ya mencionadas δ,σyεr. A la pregunta de qué parámetro influye más en la propagación, DeMinco respondió con un estudio realizado con el Modelo de Campo sin Perturbaciones [ 17 ]. Con alturas de antenas fijadas a 0 metros se realizaban mediciones para determinar, según la frecuencia, qué distancias era capaz de cubrir la emisión. En frecuencias desde 30 a 915 MHz todavía se identificaba la onda de suelo hasta unos 250 metros y las pérdidas se producían en mayor medida por el efecto de la permitividad relativa y la conductividad era casi despreciable. Sin embargo, para rangos de frecuencias menores a 150 MHz, la conclusión se invertía, siendo la conductividad la característica predominante.
8Capítulo 2. Teoría de ondas de superficie Figura 2.5 Pérdidas de propagación en función de epsilon, con σ=0.001 y distancia=10 m [17]. Figura 2.6 Pérdidas de propagación en función de sigma, con εr=4.0 y distancia=10 m [17]. En la figura 2.5 extraída del estudio se comprueba como para valores por encima de 30 MHz de frecuencia, εr aumenta su efecto sobre las pérdidas conforme la frecuencia crece. En la figura 2.6 la dependencia de σ es prácticamente despreciable, y solo se muestra una pequeña influencia en valores muy cercanos a 30 MHz. En cuanto a la atenuación que sufre la onda, Sommerfeld [ 2 ] y Norton [ 3 ] [ 4 ] obtuvieron un valor de factor de atenuación F para Tierra plana de conductividad finita que variaba en función de las características del
2.2 Recomendación ITU-R P.368-9 9 terreno y distancia: F=1−jq(πw)exp(−w)er f c(j√w)(2.12) donde w=−j2kr2u2(1−u2cos2ψ2) (1−Rv)(2.13) u2=2 (εr−jx)(2.14) x=σ (wε0)=1,8·104σ fMHz (2.15) yer f c la función error complementaria. Este factor estará presente en las expresiones de intensidad de campo de la onda de superficie, cuyas componentes en coordenadas cilíndricas son: Ez=−j30kIdl cos2ψ1 exp(−jkr1) r1 +cos2ψ2 exp(−jkr2) r2+ (1−Rv)(1−u2+u4cos2ψ2)Fexp(−jkr2) r2(2.16) Eρ=−j30kIdl sinψ1cosψ1 exp(−jkr1) r1 +sinψ2cosψ2 exp(−jkr2) r2 −cosψ2(1−Rv)uq(1−u2cos2ψ2)1−u2 2(1−u2cos2ψ2)+ sin2ψ2 2Fexp(−jkr2) r2 (2.17) donde ψ1 y ψ2 se detallan en la figura 2.3, Idl es el producto de la corriente de la fuente y la longitud, Rv el coeficiente de reflexión de Fresnel de onda plana para la polarización vertical, 2.2.2 Método de Millington Para el caso de terrenos mixtos, se detalla el procedimiento de obtención de la intensidad de campo eléctrico con rigurosidad, utilizando el Método semi-empírico de Millington, que supone tierra esférica. Sigue el principio de reciprocidad, el campo total recibido será una media aritmética de ER y ET , que se corresponden con el módulo de intensidad de campo recibido cuando R es el receptor y cuando T es el receptor respectivamente, obtenidos tras una serie de cálculos invirtiendo los emplazamientos. Existirá mayor precisión mientras más parecidos sean los valores ERyET. En términos generales, las fórmulas a utilizar serían: ER= N ∑ k=1 Ek(Sk)− N ∑ k=2 Ek(Sk−1)(2.18) Sk= k ∑ n=1 dn(2.19) ET= N ∑ k=1 EN−k+1(rk)− N ∑ k=2 EN−k+1(rk−1)(2.20)
16 Capítulo 3. Herramientas de desarrollo •HELP. Lista los comandos básicos con una breve descripción •EXIT. Cierra el simulador. Un ejemplo de los pasos para hacer funcionar GRWAVE en DOSBox se muestra en la siguiente figura: Figura 3.4 Ejecución de GRWAVE en DOSBox. El emulador ha ejecutado correctamente grwave.exe, ubicado en la carpeta en la que se ha montado la unidad (C: \ GWSim), se ha cambiado a dicha unidad C:, y los datos de salida se ven reflejados en el fichero de salida grwave.out. En caso de ejecutar grwave.exe sin especificar ficheros de entrada ni salida, los parámetros se irían introduciendo en tiempo de ejecución, y la salida se volcaría en la interfaz de DOSBox. Además, es posible modificar el fichero de configuración para ejecutar comandos al iniciar el emulador automáticamente. Como se explica en el manual de DOSBox, en su primera ejecución creará, si no existe, un fichero .conf en la ruta C: \%USERPROFILE%\AppData\Local\DOSBox , con el nombre “DOSBox 0.74-3 Manual.conf”. Al archivo de configuración original se le ha añadido en la parte de autoejecución el siguiente código: Código 3.3 Código para llamada automática de GRWAVE en DOSBox. mount c C:\GWSim c: grwave <entrada.dat >grwave.out exit 3.3 Anaconda Anaconda es una distribución de software libre para Python que funciona como un gestor de entorno. Desde un principio se utilizó por su comodidad de instalación de librerías y paquetes, e incorpora una interfaz Navigator para escoger sus herramientas, entre las que se encuentra el IDE Spyder que se ha usado para escribir todo el código del simulador.
3.4 Python 3.6.5 17 3.4 Python 3.6.5 Todas las lineas de código se escribieron en Spyder, pero se necesitó un intérprete de código Python para que, ejecutando los archivos .py en el terminal de Windows, se pudiera entender todo el código aportado y generar una respuesta. La versión usada ha sido la 3.6.5. Se han añadido paquetes para el desarrollo de nuestro objetivo. En primer lugar, el instalador de paquetes pip permitía añadir nuevas librerías sin la necesidad de descargarlas navegando en internet: Código 3.4 Ejemplo de uso del instalador de paquetes pip. pip install [package] 3.4.1 Librerías añadidas Es muy habitual en la programación tener que incluir nuevas librerías que no aparecen entre las predeterminadas, para permitir el uso de nuevas funciones. Para este proyecto se han agregado las siguientes: •Numpy, para soporte sobre arrays y funciones matemáticas. •Math, para otras funciones matemáticas que no incorpora Python. •Pandas, para estructuras de datos. •Matplotlib, para la generación de gráficas. •Pyinstaller, para crear un ejecutable a partir de un archivo Python .py. Uno de los objetivos más importantes de este proyecto buscaba presentar una interfaz gráfica al Usuario con aspecto moderno y llamativo, para poder recoger los datos de entrada y presentar los de salida de la manera más clara y eficaz posible. Python integra la librería Tkinter para generar una GUI, pero por su poco potencial se creyó conveniente usar PyQt5 (previa instalación a través de pip). Realmente es una biblioteca que permite usar la GUI de Qt (escrita en C++), y para agilizar el proceso se decidió utilizar la herramienta Qt Designer. PyQt5 utiliza fundamentalmente una jerarquía de herencia múltiple, que se detallará en capitulos posteriores. 3.4.2 Producción del ejecutable con Pyinstaller La función de esta librería, Pyinstaller, es agrupar todos los archivos de código fuente, librerías utilizadas, y demás dependencias, en un solo paquete. Se genera un ejecutable que funciona como aplicación empaquetada sin la necesidad de tener Python y sus módulos instalados. Por defecto, el ejecutable abriría simultáneamente el terminal de Windows. Nuestro proyecto evitará esto, además de incluir una opción que añade icono de aplicación. También sería posible añadir –onefile para que todo quede comprimido en un solo fichero ejecutable, pero sería más pesado y aumentaría el tiempo de apertura de la aplicación. Al no ser necesario, se ignora esa posibilidad. Código 3.5 Comando para generar ejecutable del Simulador. pyinstaller.exe --windowed --icon=iconn.ico grmain.py 3.5 Qt Designer Qt Designer es una herramienta que facilita la construcción de ventanas con widgets de Qt. Se puede ver en tiempo real la posición y estilo de cada elemento que conforma base de la ventana que se está creando, además de permitir dar forma al estilo con lenguaje CSS. Las ventanas se pueden configurar inicialmente de dos tipos: • QMainWindow, como ventana principal de la aplicación. Clase para construir la interfaz principal.
18 Capítulo 3. Herramientas de desarrollo • QDialog, ventana secundaria preparada para presentar al usuario ciertas opciones o notificaciones. Los elementos más usados han sido: •QLabel, para etiquetas de texto. •QPushButton, para botones a los que aplicar eventos. •QLineEdit, para casillas de texto. •QRadioButton, boton circular con opción para marcar. •QTabWidget, para incluir varias pestañas dentro de la misma ventana. • QFrame, que aportará un cuadro vacío que otro software aprovechará (en el caso del proyecto, se ha utilizado para la implementación de gráficas). •QComboBox, para desplegables. Figura 3.5 Interfaz de Qt Designer para la construcción de ventanas de la GUI. Qt Designer genera un archivo .ui que se convertirá en .py con código Python utilizando pyuic5 en el terminal de Windows. Código 3.6 Ejemplo traducción de archivo de Qt Designer para Python. pyuic5 -x [archivo.ui] -o [archivo.py] La modalidad –x genera código extra para testear las clases, y se corresponden con los __ init __ que aparecen al final de los ficheros convertidos con esta opción. La opción –o vuelca la salida en un fichero en concreto y no en la salida estándar.
4 Funcionalidad de GroundWave Simulator El simulador se basa en una interfaz gráfica presentada al usuario para interactuar con él, que se inicia mediante el ejecutable “GroundWave Simulator.exe”. Está disponible en https://github.com/Ssuki27/GroundWave- Simulator y su código fuente en https://github.com/Ssuki27/GWSim-sourceCode. Para su correcta instalación, se debe descomprimir el archivo y reubicar la carpeta GWSim en el disco duro principal de la computadora, Disco Local (C:). Como ya ha sido comentado, incluye todas las librerías necesarias para su funcionamiento, intérprete Python, emulador DOSBox y GRWAVE. 4.1 Interfaz de bienvenida La ventana principal, considerada de bienvenida, de la que heredará la ventana encargada de los cálculos del programa, es de tamaño fijo 867x604 px. Por consiguiente, se cancela el botón de maximizar, para evitar posible desestructuración de los elementos que conforman la ventana y mantener la apariencia deseada. Figura 4.1 Ventana de Bienvenida de GroundWave Simulator. 19
20 Capítulo 4. Funcionalidad de GroundWave Simulator Se ha diseñado con Adobe Photoshop un logo central en específico para el simulador, por mera cuestión visual, que integra una figura de la Tierra simulando un efecto de transmisión de ondas, en referencia a la superficie terrestre como conductor para propagar ondas de superficie. Esta figura se ha utilizado también como icono de ventana y de aplicación en la barra de tareas. En la esquina inferior izquierda aparece el correo universitario del autor, para posibles problemas o dudas, y en la inferior derecha un botón que abrirá el archivo Guia.txt donde se aporta información básica para comenzar. Figura 4.2 Guía sobre uso e instalación del simulador. Lo más peculiar de esta ventana es la inclusión de un botón deslizante para elegir el entorno de ejecución, entre el terminal de Windows o MS-DOS mediante DOSBox. Figura 4.3 Botón deslizante de la interfaz principal. Cuenta con las siguientes funciones: •Se mueve sobre el ancho de la casilla de fondo. • Modifica su hoja de estilos cuando el ratón pasa por encima (al igual que el resto de los QPushButton del simulador). •Posee dos estados. Desde el centro del ancho hasta cada uno de los extremos: Windows y MSDOS. • Al traspasar el centro del ancho total hacia el lado contrario, actualiza en tiempo real el texto de la etiqueta principal, y el color de fondo (actualizarEstado(self)). • No es necesario arrastrar hasta el extremo de la casilla. Al soltar el botón, se desliza automáticamente hacia el extremo más cercano (mousePressEvent(self,event)). • También es posible cambiar el estado sin deslizar, pinchando directamente en el ancho contrario reservado para el otro estado. Además, haciendo click en el mismo botón, se deslizara automáticamente hacia el otro estado (mousePressEvent(self,event)). • Una vez modificado el valor del botón, se actualizará la etiqueta inferior indicando en que entorno se ejecutará el simulador (estadoInterruptor(self,texto)). Así, el simulador escogerá un camino u otro a la
4.2 Ventana de cómputos 21 hora de llamar a GRWAVE y realizar los cálculos. Por último, el QPushButton verde con etiqueta Empezar, avanzaría hacia la segunda ventana del programa para configurar los cálculos. 4.2 Ventana de cómputos Construida a partir de la clase interruptorPalanca de tipo QDialog, correspondiente a la ventana de bienvenida. Python no tiene la potencia para generar aplicaciones como podría tener un lenguaje como Java, que permitiría fácilmente avanzar de una ventana a otra, actualizando el aspecto de la interfaz. En este simulador, la técnica que se ha utilizado ha sido ocultar (hide) la ventana de bienvenida y seguidamente mostrar (show) la ventana de cálculos, y a simple vista el efecto se corresponde con el deseado. Destaca la presencia de dos QTabWidget (pestañas). Cada una es un cajón con bloques totalmente distintos a los de su homóloga, aunque el aspecto pueda parecer muy similar. Figura 4.4 QTabWidget Terreno mixto seleccionado. El QTabWidget seleccionado, cambiará su hoja de estilos para presentar su fondo negro y letra blanca, además de incrementar su tamaño ligeramente para facilitar la intuición visual del resalte que está seleccionado. El objetivo en el interior de estas solapas es introducir los parámetros de entrada que serán enviados a GRWAVE, mostrar gráficamente los resultados obtenidos en curvas de propagación, y obtener un valor para un punto (distancia) en específico de éstas. 4.2.1 Pestaña Terreno homogéneo Figura 4.5 Ventana para los cálculos de un Terreno homogéneo.
22 Capítulo 4. Funcionalidad de GroundWave Simulator Para la altura de las antenas, características del terreno y frecuencias, se utilizan elementos QLineEdit. Casillas que guardan el texto introducido en variables de tipo String. Como lo introducido debe ser un número, cuando sea necesario operar con ellos se transformará a tipo Float. La polarización consta de dos QRadioButton con etiquetas Vertical (marcada por defecto) y Horizontal. Al marcar una, se deselecciona la otra, siendo imposible señalar ambas o ninguna de forma sincrónica. Para la selección rápida de características del terreno se ha utilizado un QComboBox. Un desplegable con distintos ítems ya configurados según valores de la curva de la Figura 17 de la ITU-R Rec. P.527-5 [13]. Figura 4.6 Desplegable ComboBox para la selección de terreno. Al seleccionar una opción, se introducirá en las casillas de texto su valor de conductividad y constante dieléctrica. Además, se cambiarán al modo solo lectura (setReadOnly), para evitar confusiones sobre las características de un terreno. Solo estará disponible en la opción Personalizado, modificar manualmente las magnitudes de σyεr. El QPushButton con etiqueta Ayuda, abre una nueva ventana mostrando una tabla de valores predeterminados para cada medio. Figura 4.7 Ventana de ayuda para selección de terreno. En esta pestaña esta permitido el uso de varias frecuencias. Esto no significa que los cálculos se hagan teniendo en cuenta todas en su conjunto, sino que por separado se muestran tantas curvas en la gráfica como frecuencias se hayan escogido. Otro QComboBox facilitará esta utilidad, nuevamente con otro botón de Ayuda para disipar posibles dudas sobre la ejecución con varios niveles de frecuencia. El número máximo de frecuencias simultáneas es de ocho. Se ha considerado que una cantidad mayor dificulta la lectura y análisis de la gráfica, además de complicar el aspecto del simulador.
4.2 Ventana de cómputos 23 Figura 4.8 Desplegable QComboBox para la selección de número de frecuencias. Conociendo todos estos parámetros a rellenar, es posible generar una primera gráfica de prueba, mediante el botón verde Ejecutar, visible en la Figura 4.9. El simulador recogerá los campos completados y rellenará el fichero de entrada para GRWAVE de extensión .dat. Se comprobará cuál es el número de frecuencias escogido, y llamará a GRWAVE esa cantidad de veces. Figura 4.9 Ejemplo de ejecución de gráfica para terreno homogéneo. Para una representación gráfica como la de la Figura ?? se ha creado la clase MplWidget de tipo QWidget. Se ha instanciado un objeto de dicha clase, incrustado dentro del Frame que se colocó mediante Qt Designer, y ejecutado usando las funciones de la librería matplotlib. Código 4.1 Fragmentos de código en referencia a la creación y rellenado de gráfica. No todas las líneas pertenecen al mismo fichero ni son las únicas para ésto.. class MplWidget(QWidget):
24 Capítulo 4. Funcionalidad de GroundWave Simulator def __init__(self, parent = None): QWidget.__init__(self, parent) self.canvas = FigureCanvas(Figure()) vertical_layout = QVBoxLayout() vertical_layout.addWidget(self.canvas) self.canvas.axes = self.canvas.figure.add_subplot(111,position=[0.15, 0.17, 0.75, 0.75]) self.setLayout(vertical_layout) self.tab = QtWidgets.QWidget() self.MplWidget = MplWidget(self.tab) self.MplWidget.canvas.axes.plot(distancia, intensidad,label=etiqueta,color= colorGrafica) self.MplWidget.canvas.draw() El eje de abcisas está expresado en escala logarítmica. Para tener un equilibrio de puntos en cada división de potencia de 10, se optó por llamar a GRWAVE en cuatro ocasiones (de 0 a 10, de 10 a 100, de 100 a 1000 y de 1000 a 10000) por cada frecuencia. Esto consumiría más recursos de la cuenta y alargaría el tiempo de ejecución (siendo notable cuando ejecutas ocho frecuencias simultáneamente), pero se conseguiría tener el mismo número de puntos por división: 100. Por cada una de esas cuatro ejecuciones, los resultados de salida se van escribiendo en otro fichero distinto al que hemos llamado “grwave2.out”, únicamente con las tres columnas de resultados que GRWAVE ofrece. Cada curva tendrá un color asociado con el que será dibujada en la gráfica, y será etiquetada en la leyenda de la figura junto a la frecuencia que la distingue. Se ha evitado la generación aleatoria de tonos para eludir posibles parecidos, siendo escogidos suaves y distantes. Figura 4.10 Gama de colores para la generación de curvas expresados en hexadecimal. El QPushButton con etiqueta Limpiar, borraría todos los elementos añadidos sobre la figura. Existe la posibilidad de exportar la gráfica con el botón específico para ello, abriendo un selector de carpetas donde guardar el archivo. Las opciones de extensión que ofrece matplotlib para exportar gráficas aparecen en la Figura 4.11 El último módulo implementado en esta pestaña, el QGroupBox para calcular la intensidad de campo eléctrico recibido, solo está disponible una vez generada una gráfica, para una única frecuencia. En los instantes en que la gráfica se encuentra vacía o completada con más de una frecuencia, no tiene sentido realizar este cálculo en función de una distancia introducida.
4.2 Ventana de cómputos 25 Figura 4.11 Ventana selector de carpetas para exportar gráfica. Figura 4.12 Ejemplo de cálculo de intensidad de campo eléctrico para terreno homogéneo.
32 Capítulo 5. Deducciones y validez del simulador Una vez finalizado el desarrollo de la aplicación, ha sido analizada completamente con el fin de obtener los resultados de calidad estática que posee. De esta manera se puede garantizar que esta producida de la manera más eficiente y garantizando la seguridad y robustez del código. Como se muestra en la figura 5.1, el código desarrollado posee nivel A (el de mejor calidad) en los umbrales de fiabilidad, seguridad, mantenibilidad y revisión de seguridad. No posee ningún error de software, vulnerabilidades ni hotspots de seguridad, con lo que podemos garantizar que el desarrollo de la aplicación es lo más eficiente, robusto y seguro posible. Presenta 158 code smells, los cuales afectan a la mantenibilidad del programa. Siguiendo indicaciones de nomenclatura, los code smells aconsejan renombrar algunos nombres de variables y funciones, pero no atañe negativamente en el rendimiento, eficiencia, robustez y seguridad del software. En la figura 5.2 se detalla la información dividida por ficheros. Figura 5.2 Informe de análisis desglosado de SonarQube. 5.2 Discusiones utilizando el simulador El programa, que basa sus resultados en los cálculos de GRWAVE y las fórmulas del método de Millington, realiza hipótesis derivadas de dichos criterios para simplificar los procedimientos, intentando alejarse lo mínimo posible de la realidad. En esta sección se tratará de demostrar alguna de esas aproximaciones, además de las consecuencias de estas y de la teoría de ondas de superficie. Además, la recomendación de la ITU-R P.368 que sigue este proyecto proporciona una amplia variedad de figuras a las que recurrir en caso de necesidad. Sin embargo, este simulador proporciona esas mismas gráficas sin el inconveniente que suponen los valores discretos de frecuencia o características del terreno fijados en ellas. Es decir, se proporciona independencia y libertad de análisis. Figura 5.3 Curva de propagación por GroundWave Simulator en tierra húmeda (σ=0.01, εr=30).
5.2 Discusiones utilizando el simulador 33 La figura 5.3, extraída del simulador, presenta hasta ocho frecuencias para una ejecución de terreno homogéneo con tierra húmeda, haciendo apreciable la similitud con la figura 5.4 perteneciente al documento de la ITU-R P.368 Figura 5.4 Curva de propagación ITU-R de onda de superficie en tierra húmeda (σ=0.01, εr=30). Uno de los primeros parámetros a configurar en la aplicación es la altura de antena, tanto para transmisor como para receptor. En la recomendación de referencia se alude a una condición de altura límite para la receptora, sin embargo, es inevitable pensar de qué manera puede afectar la altura de antenas en la transmisión. Se han analizado los datos obtenidos para campo eléctrico de la figura 5.5, para los cuales se ha usado una polarización vertical con un terreno de agua dulce, haciendo variar simultáneamente la frecuencia y las alturas de antenas HTT y HRR (altura antena transmisora y receptora respectivamente). 5.5 perteneciente al documento de la ITU-R P.368 Figura 5.5 Comparación de intensidad de campo eléctrico según la altura de antenas para agua dulce y distancia de 50 km. Se observa que para las frecuencias más altas dentro del rango permitido, se produce una fluctuación de la
34 Capítulo 5. Deducciones y validez del simulador intensidad del campo en función de la altura de las antenas. Mientras más alta, más notable es este efecto. Por el contrario, para frecuencias más bajas, esa alteración es casi despreciable, además de conseguir una mejora en la intensidad de campo recibida para estos niveles (debido a longitudes de onda más largas). Otra condición a la que se hace referencia en varias ocasiones es la polarización. En las subsecciones 2.1.1 y 2.1.2 se habla la importancia que tiene realizar la transmisión con polarización vertical, y de las consecuencias de pérdidas y atenuación que tiene la polarización horizontal. Se han realizado ejecuciones en el simulador para comparar los resultados con distintas polarizaciones, reflejado en la tabla 5.1, donde el terreno es homogéneo con Tierra húmeda durante todo el trayecto y alturas de antenas a 5 m cada una. Se han realizado tres simulaciones con distinta frecuencia, y en ellas se ha ido aumentando la distancia a la que se calcula la intensidad del campo eléctrico recibido. Se aprecia rápidamente la rápida atenuación del caso con polarización horizontal respecto a la vertical. Además, conforme disminuye la frecuencia, la polarización vertical mejora la intensidad y la horizontal la empeora. Tabla 5.1 Comparación de intensidad según la polarización para Tierra húmeda (σ=0.01 S/m, εr=30). Frecuencia Distancia Polarización vertical Polarización horizontal 10 MHz 10 km 51.98 dBu 22.93 dBu 10 MHz 40 km 28.26 dBu -0.81 dBu 10 MHz 70 km 16.53 dBu -12.68 dBu 10 MHz 100 km 7.81 dBu -21.56 dBu 1 MHz 10 km 85.30 dBu 9.80 dBu 1 MHz 40 km 67.43 dBu -13.52 dBu 1 MHz 70 km 57.80 dBu -23.42 dBu 1 MHz 100 km 50.41 dBu -30.51 dBu 0.1 MHz 10 km 88.60 dBu 1.46 dBu 0.1 MHz 40 km 77.03 dBu -21.60 dBu 0.1 MHz 70 km 72.07 dBu -31.43 dBu 0.1 MHz 100 km 68.95 dBu -37.91 dBu Visualmente se aprecia en la figura 5.6. Las curvas en color azul y verde corresponden a la misma frecuencia (10 MHz) pero distinta polarización. La horizontal claramente tiene niveles menores de intensidad. Al disminuir la frecuencia hasta 1 MHz, la línea verde de polarización vertical evoluciona a la de color morado, mejorando los resultados. La azul, de polarización horizontal, se transformaría en la amarilla, con menor intensidad de campo eléctrico. Figura 5.6 Curva de propagación en tierra húmeda (σ=0.01, εr=30) variando polarización y frecuencia. Para escenarios con terreno mixto, surgía la duda de hasta qué punto el orden de los terrenos podía alterar los resultados. Para ello, se realizaron mediciones de intensidad de campo eléctrico con antenas situadas a 0
5.2 Discusiones utilizando el simulador 35 metros de altura. En primer lugar, se tomaba un escenario ficticio como el de la figura 5.7, con un primer terreno de Tierra de 200 km de longitud y otro de agua del mar con salinidad baja de 100 km de longitud. Aunque se representa con relieve, los cálculos se eximen de tener en cuenta dichos efectos. Figura 5.7 Escenario de transmisión de dos terrenos. En la primera ejecución de la tabla 5.2 se toma la Antena 1 como transmisora, y Antena 2 como receptora. Para la segunda medición se invierten los roles de transmisor y receptor, obteniendo la misma intensidad de campo eléctrico, 48.86 dBu. Se concluye así que para dos terrenos no importa el orden en una transmisión, incluso cuando las distancias no son idénticas. Tabla 5.2 Comparación de intensidad recibida en Método Millington según el orden de dos terrenos con distinta longitud y f=1 MHz. Terreno 1 Terreno 2 Campo eléctrico Agua del mar, sal. baja Tierra 48.86 dBu (σ=1, εr=80) (σ=0.03, εr=40) Longitud=200 km Longitud = 100 km Tierra Agua del mar, sal. baja 48.86 dBu (σ=0.03, εr=40) (σ=1, εr=80) Longitud=100 km Longitud = 200 km A continuación se intenta comprobar los mismos efectos para la combinación de tres terrenos con 100 kilómetros de longitud cada uno. Figura 5.8 Escenarios de transmisión de tres terrenos.
36 Capítulo 5. Deducciones y validez del simulador Se realizan ejecuciones ordenando los terrenos de tres maneras diferentes. En la tabla 5.3 se comprueba como los valores recibidos de campo eléctrico varían. De la primera a la segunda medida existe una pequeña variación, pero sorprende bastante más el tercer dato que se espacia del resto en más de 20 dBu. La deducción que se obtiene es que en la fase final de la transmisión, que es donde más pérdidas se generan, que haya un tipo de terreno con buena conductividad hace que la intensidad se mantenga a buen nivel durante mas distancia, tarda mas en atenuarse. En los primeros terrenos, que la conductividad sea mas alta o menos alta no influye tanto. Por eso, la tercera ejecución presenta como tercer trayecto agua de mar con salinidad baja, con una conductividad mucho mayor que el resto de terrenos. En definitiva, el orden a partir de tres terrenos importa, incluso cuando las longitudes sean idénticas. Tabla 5.3 Comparación de intensidad recibida en Método Millington según el orden de tres terrenos con 100km de longitud cada uno y f=1 MHz. Terreno 1 Terreno 2 Terreno 3 Campo eléctrico Tierra muy seca Agua del mar, sal. baja Tierra 18.62 dBu (σ=0.0001, εr=3) (σ=1, εr=80) (σ=0.03, εr=40) Agua del mar, sal. baja Tierra Tierra muy seca 19.01 dBu (σ=1, εr=80) (σ=0.03, εr=40) (σ=0.0001, εr=3) Tierra Tierra muy seca Agua del mar, sal. baja 40.86 dBu (σ=0.03, εr=40) (σ=0.0001, εr=3) (σ=1, εr=80)
6 Conclusiones y líneas futuras El desarrollo del proyecto implicó inicialmente el aprendizaje del lenguaje Python unido a la dificultad de entender su librería PyQt5 para interfaces gráficas. Multitud de módulos han sido sustituidos en pleno proceso por otros con mayores garantías, como los reemplazos de Tkinter por PyQt5 para la interfaz gráfica, que supondría un salto de calidad en ésta, o la librería pqytgraph por matplotlib para la generación de gráficas entre los más destacados. Sin embargo, el aprendizaje en pleno camino ha penalizado la posibilidad en ocasiones de remodelar la estructura básica del programa, y por ello, es necesario listar modificaciones factibles a realizar en una continuación de este trabajo. En primer lugar, se ha comentado la forzosa opción escogida para representar los datos en la gráfica en escala logarítmica, que pasaba por realizar cuatro llamadas a GRWAVE por cada curva. Cuando el entorno utilizado es el terminal de Windows, el retardo no llega a ser molesto, incluso para la ejecución de ocho mediciones distintas. No obstante, la obligatoriedad de utilizar MS-DOS hace que por cada llamada se inicie en cuatro ocasiones el emulador DOSBox, proceso que sí ocupa bastante tiempo de ejecución, más cuando varias curvas son generadas. Sería conveniente buscar un método más sencillo para representar la curva deseada con una única llamada a GRWAVE, sin la necesidad de dividir las secciones de la escala del eje de abcisas. En acorde con esto, el programa realiza llamadas a GRWAVE de una manera u otra en función de qué entorno se escoja para ello: terminal de Windows o emulador DOSBox para MS-DOS. No se ha logrado reconocer cual es el factor que determina por qué GRWAVE puede ser ejecutado en algunos sistemas operativos Windows mediante su propio terminal, y en otros ordenadores con dicho sistema operativo fuera necesario un emulador para recrear MS-DOS puro. GroundWave Simulator será más productivo cuando se ejecute en el terminal de Windows por la razón ya comentada de prescindir de la apertura de un emulador, con lo cual, descubrir esa causa puede resultar útil para simplificar el uso del simulador. Otro punto pendiente del programa atañe al trazado de gráficas para el método Millington en terrenos mixtos. Resultaría cómodo poder expresar el resultado final en una curva combinación de todos los terrenos teniendo en cuenta las fórmulas del método que se sigue, como sucede en la figura 2.8. Sin embargo, la obtención de datos se hace de tal manera que se machacan estos valores con el paso de una curva a otra, con lo cual, con la actual estructura, no se ha encontrado forma de realizar la unión de las curvas que componen el terreno mixto en una exclusiva. Por otro lado, varias ideas quedaron en el tintero por cuestiones de tiempo. Amoldar el simulador a mediciones reales hubiera sido un gran paso. El plan para esto pasaba por acoplar una opción de selección de terrenos usando la Recomendación P.832 [ 19 ] de la ITU-R que proporciona un Atlas Mundial, con mapas de conductividad del terreno en todo el planeta. Un paso todavía más profesional sería incluir una selección de distancias atractiva y real, mediante una aplicación externa como Google Earth u otra similar que pueda unirse a una base de datos con características eléctricas del terreno en función de las coordenadas geográficas escogidas (que conllevaría la necesidad de calcular longitudes mediante dos pares de coordenadas). Es decir, el usuario seleccionaría ubicaciones reales para las antenas mediante una aplicación de cartografía global. Todo esto dejaría a un lado la elección manual de terreno homogéneo o mixto, pues el propio simulador debería ser capaz de reconocer cuantos terrenos atraviesa la transmisión escogida mediante puntos de latitud y longitud. 37
38 Capítulo 6. Conclusiones y líneas futuras Respecto a la validez de los cálculos, los modelos utilizados por GRWAVE ignoran grandes efectos de relieve montañoso o edificios como obstáculos. Una línea de estudio podría avanzar en este sentido, haciendo los resultados obtenidos mucho más cercanos a la realidad, cuando se consiga completar un método de predicción en zonas urbanas con altos edificios y relieve intensificado. El último análisis corresponde al objetivo marcado para el proyecto. A la necesidad de adaptar el uso del arcaico programa GRWAVE a los ordenadores actuales, se ha respondido con una interfaz moderna y sencilla que facilita tanto la introducción de variables como la representación y extracción de datos y figuras, incluyendo resultados para el método Millington. Se concluye, por tanto, que el principal propósito del trabajo se ha cumplido colmadamente.
Apéndice A Código fuente GRMain.py 1from PyQt5.QtGui import QIcon, QFont, QPalette, QColor, QPixmap 2from PyQt5.QtCore import Qt, QPoint, pyqtSignal, QTimer, QPropertyAnimation, QAbstractAnimation 3from PyQt5.QtWidgets import QApplication, QPushButton, QDialog, QFrame, QLabel 4from PyQt5 import QtCore, QtGui, QtWidgets 5from ventanaCalculos import ∗ 6import os 7 8# ========================= CLASE Frame =========================== 9 10 class Frame(QFrame): 11 clicked = pyqtSignal(str ) 12 MIN_VALOR = 1 13 MAX_VALOR = 121 14 VALOR = MAX_VALOR + MIN_VALOR 15 16 def __init__( self , parent=None): 17 super(Frame, self) . __init__ (parent) 18 self .parent = parent 19 self .mover = QPoint() 20 self . habilitado = False 21 22 def actualizarEstado( self ) : 23 # Si la posicion x del boton mas la mitad de su ancho 24 # es menor que la mitad del ancho del widget padre, 25 # entonces esta apagado (NO) 26 if ( self .parent.button.x() + ( self .parent.button.width() / 2)) < Frame.VALOR / 2: 27 self . habilitado = False 28 # Si la posicion x del boton mas la mitad de su ancho 29 # es mayor que la mitad del ancho del widget padre, 30 # entonces esta encendido (SI) 31 if ( self .parent.button.x() + ( self .parent.button.width() / 2)) > Frame.VALOR / 2: 32 self . habilitado = True 33 34 if self . habilitado: 35 self .parent.button.setText("MSDOS") 36 color = QColor(250, 185, 78) 37 elif not self . habilitado : 38 self .parent.button.setText("Windows") 39 color = QColor(78, 172, 250) 40 41 colorFrame = self . palette () 42 colorFrame.setColor(QPalette.Background, color) 43 self . setPalette (colorFrame) 44 45 def mousePressEvent(self, event) : 46 if event .button() == Qt.LeftButton: 47 self .mover.setY(1) 48 if event .pos() .x() < Frame.VALOR / 2: 49 self .mover.setX(Frame.MIN_VALOR) 50 elif event .pos() .x() > Frame.VALOR / 2: 39
40 Appendix A. Código fuente 51 self .mover.setX(Frame.MAX_VALOR −self.parent.button.width()) 52 53 self .animacion = QPropertyAnimation(self.parent.button, b"pos") 54 self .animacion.setDuration(150) 55 self .animacion.setEndValue(self.mover) 56 self .animacion.valueChanged.connect(self.actualizarEstado) 57 self .animacion.finished .connect( self .emitirEstado) 58 self .animacion.start(QAbstractAnimation.DeleteWhenStopped) 59 60 def emitirEstado( self ): 61 self . clicked .emit( self .parent.button. text () ) 62 63 # ====================== CLASE PushButton ========================= 64 65 class PushButton(QPushButton): 66 clicked = pyqtSignal(str ) 67 68 MIN_VALOR = 1 69 MAX_VALOR = 121 70 VALOR = MAX_VALOR + MIN_VALOR 71 72 def __init__( self , parent=None): 73 super(PushButton, self ) . __init__ (parent) 74 self .parent = parent 75 self .pressing = False 76 self . inicio = QPoint() 77 self .mover = QPoint() 78 self . habilitado = False 79 self .arrastrado = False 80 81 self .actualizarEstado() 82 self . actualizarPosicion () 83 84 def mousePressEvent(self, event) : 85 if event .button() == Qt.LeftButton: 86 self .ultimo = True 87 self . inicio = event .pos() 88 self .pressing = True 89 self .actualizarEstado() 90 91 def actualizarEstado( self ) : 92 # Si la posicion x del boton mas la mitad de su ancho 93 # es menor que la mitad del ancho del widget padre, 94 # entonces esta apagado (NO) 95 if ( self .x() + ( self .width() / 2)) < PushButton.VALOR / 2: 96 self . habilitado = False 97 98 # Si la posicion x del boton mas la mitad de su ancho 99 # es mayor que la mitad del ancho del widget padre, 100 # entonces esta encendido (SI) 101 if ( self .x() + ( self .width() / 2)) > PushButton.VALOR / 2: 102 self . habilitado = True 103 104 if self . habilitado: 105 self . setText("MSDOS") 106 color = QColor(250, 185, 78) 107 elif not self . habilitado : 108 self . setText("Windows") 109 color = QColor(78, 172, 250) 110 111 colorFrame = self . palette () 112 colorFrame.setColor(QPalette.Background, color) 113 self .parent. setPalette (colorFrame) 114 115 def mouseMoveEvent(self, event): 116 if self .pressing: 117 self .mover = self .mapToParent(event.pos() −self. inicio ) 118 self .mover.setY(1) 119 self .move(self .mover) 120 self .arrastrado = True 121 self .restringirMovimiento()
41 122 self .actualizarEstado() 123 124 def actualizarPosicion ( self ): 125 self .mover.setY(1) 126 if self . habilitado: 127 self .mover.setX(PushButton.MAX_VALOR −self.width()) 128 else : 129 self .mover.setX(PushButton.MIN_VALOR) 130 131 self .animacion = QPropertyAnimation(self, b"pos") 132 self .animacion.setDuration(150) 133 self .animacion.setEndValue(self.mover) 134 self .animacion.finished .connect( self .emitirEstado) 135 self .animacion.start(QAbstractAnimation.DeleteWhenStopped) 136 137 def restringirMovimiento( self ): 138 self .mover.setY(1) 139 140 # Restringir lado izquierdo 141 if self .x() < PushButton.MIN_VALOR: 142 self .mover.setX(PushButton.MIN_VALOR) 143 self .move(self .mover) 144 return 145 146 # Restringir lado derecho 147 if ( self .x() + self .width()) > PushButton.MAX_VALOR: 148 self .mover.setX(PushButton.MAX_VALOR −self.width()) 149 self .move(self .mover) 150 return 151 152 def mouseReleaseEvent(self, event): 153 if self .pressing: 154 self .pressing = False 155 self .actualizarEstado() 156 self . actualizarPosicion () 157 158 if not self .arrastrado and self .ultimo: 159 # QApplication.instance() .doubleClickInterval() 160 QTimer.singleShot(100, self .performSingleClickAction) 161 else : 162 self .arrastrado = False 163 164 def mouseDoubleClickEvent(self, event): 165 if event .button() == Qt.LeftButton: 166 self .ultimo = False 167 168 def performSingleClickAction(self): 169 if self .ultimo: 170 self .mover.setY(1) 171 if self . habilitado: 172 self .mover.setX(PushButton.MIN_VALOR) 173 else : 174 self .mover.setX(PushButton.MAX_VALOR −self.width()) 175 176 self .animacion = QPropertyAnimation(self, b"pos") 177 self .animacion.setDuration(150) 178 self .animacion.setEndValue(self.mover) 179 self .animacion.valueChanged.connect(self.actualizarEstado) 180 self .animacion.finished .connect( self .emitirEstado) 181 self .animacion.start(QAbstractAnimation.DeleteWhenStopped) 182 183 def emitirEstado( self ): 184 self . clicked .emit( self . text () ) 185 186 # =================== CLASE interruptorPalanca ==================== 187 188 class interruptorPalanca(QDialog): 189 def __init__( self , parent=None): 190 super(interruptorPalanca, self ) . __init__ (parent) 191 self .setWindowIcon(QIcon("iconn.png")) 192 self .setWindowTitle("GroundWave Simulator")
48 Appendix A. Código fuente 291 self .label_10 .setAlignment(QtCore.Qt.AlignCenter) 292 self .label_10 .setObjectName("label_10") 293 self . button_iniciar = QtWidgets.QPushButton(self.tab) 294 self . button_iniciar .setGeometry(QtCore.QRect(420, 320, 101, 31)) 295 self . button_iniciar .setObjectName("button_iniciar") 296 self . label = QtWidgets.QLabel(self.tab) 297 self . label .setGeometry(QtCore.QRect(250, 20, 41, 21)) 298 self . label .setAlignment(QtCore.Qt.AlignCenter) 299 self . label .setObjectName("label") 300 self . label_8 = QtWidgets.QLabel(self.tab) 301 self . label_8.setGeometry(QtCore.QRect(250, 50, 41, 21)) 302 self . label_8 .setAlignment(QtCore.Qt.AlignCenter) 303 self . label_8 .setObjectName("label_8") 304 self . label_6 = QtWidgets.QLabel(self.tab) 305 self . label_6.setGeometry(QtCore.QRect(20, 170, 151, 16)) 306 self . label_6 .setObjectName("label_6") 307 self . label_5 = QtWidgets.QLabel(self.tab) 308 self . label_5.setGeometry(QtCore.QRect(20, 80, 81, 16)) 309 self . label_5 .setObjectName("label_5") 310 self .Horizontal = QtWidgets.QRadioButton(self.tab) 311 self .Horizontal.setGeometry(QtCore.QRect(210, 80, 82, 17)) 312 self .Horizontal.setObjectName("Horizontal") 313 self . label_2 = QtWidgets.QLabel(self.tab) 314 self . label_2.setGeometry(QtCore.QRect(20, 20, 171, 16)) 315 self . label_2 .setObjectName("label_2") 316 self .groupBox_3 = QtWidgets.QGroupBox(self.tab) 317 self .groupBox_3.setGeometry(QtCore.QRect(10, 230, 281, 291)) 318 self .groupBox_3.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) 319 self .groupBox_3.setFlat(False) 320 self .groupBox_3.setCheckable(False) 321 self .groupBox_3.setObjectName("groupBox_3") 322 self . label_9 = QtWidgets.QLabel(self.groupBox_3) 323 self . label_9.setGeometry(QtCore.QRect(240, 50, 41, 21)) 324 self . label_9 .setAlignment(QtCore.Qt.AlignCenter) 325 self . label_9 .setObjectName("label_9") 326 self . label_4 = QtWidgets.QLabel(self.groupBox_3) 327 self . label_4.setGeometry(QtCore.QRect(10, 50, 121, 16)) 328 self . label_4 .setObjectName("label_4") 329 self .FREQ = QtWidgets.QLineEdit(self.groupBox_3) 330 self .FREQ.setGeometry(QtCore.QRect(170, 50, 71, 20)) 331 self .FREQ.setStyleSheet("") 332 self .FREQ.setObjectName("FREQ") 333 self .comboBox_2 = QtWidgets.QComboBox(self.groupBox_3) 334 self .comboBox_2.setGeometry(QtCore.QRect(10, 20, 61, 22)) 335 self .comboBox_2.setObjectName("comboBox_2") 336 self .comboBox_2.addItem("") 337 self .comboBox_2.addItem("") 338 self .comboBox_2.addItem("") 339 self .comboBox_2.addItem("") 340 self .comboBox_2.addItem("") 341 self .comboBox_2.addItem("") 342 self .comboBox_2.addItem("") 343 self .comboBox_2.addItem("") 344 self .label_11 = QtWidgets.QLabel(self.groupBox_3) 345 self .label_11.setGeometry(QtCore.QRect(10, 80, 121, 16)) 346 self .label_11 .setObjectName("label_11") 347 self .label_12 = QtWidgets.QLabel(self.groupBox_3) 348 self .label_12.setGeometry(QtCore.QRect(240, 80, 41, 21)) 349 self .label_12 .setAlignment(QtCore.Qt.AlignCenter) 350 self .label_12 .setObjectName("label_12") 351 self .label_15 = QtWidgets.QLabel(self.groupBox_3) 352 self .label_15.setGeometry(QtCore.QRect(10, 110, 121, 16)) 353 self .label_15 .setObjectName("label_15") 354 self .label_16 = QtWidgets.QLabel(self.groupBox_3) 355 self .label_16.setGeometry(QtCore.QRect(240, 140, 41, 21)) 356 self .label_16 .setAlignment(QtCore.Qt.AlignCenter) 357 self .label_16 .setObjectName("label_16") 358 self .label_17 = QtWidgets.QLabel(self.groupBox_3) 359 self .label_17.setGeometry(QtCore.QRect(240, 110, 41, 21)) 360 self .label_17 .setAlignment(QtCore.Qt.AlignCenter) 361 self .label_17 .setObjectName("label_17")
49 362 self .label_18 = QtWidgets.QLabel(self.groupBox_3) 363 self .label_18.setGeometry(QtCore.QRect(10, 140, 121, 16)) 364 self .label_18 .setObjectName("label_18") 365 self .label_19 = QtWidgets.QLabel(self.groupBox_3) 366 self .label_19.setGeometry(QtCore.QRect(10, 170, 121, 16)) 367 self .label_19 .setObjectName("label_19") 368 self .label_20 = QtWidgets.QLabel(self.groupBox_3) 369 self .label_20.setGeometry(QtCore.QRect(240, 200, 41, 21)) 370 self .label_20 .setAlignment(QtCore.Qt.AlignCenter) 371 self .label_20 .setObjectName("label_20") 372 self .label_21 = QtWidgets.QLabel(self.groupBox_3) 373 self .label_21.setGeometry(QtCore.QRect(240, 170, 41, 21)) 374 self .label_21 .setAlignment(QtCore.Qt.AlignCenter) 375 self .label_21 .setObjectName("label_21") 376 self .label_22 = QtWidgets.QLabel(self.groupBox_3) 377 self .label_22.setGeometry(QtCore.QRect(240, 260, 41, 21)) 378 self .label_22 .setAlignment(QtCore.Qt.AlignCenter) 379 self .label_22 .setObjectName("label_22") 380 self .label_23 = QtWidgets.QLabel(self.groupBox_3) 381 self .label_23.setGeometry(QtCore.QRect(10, 200, 121, 16)) 382 self .label_23 .setObjectName("label_23") 383 self .label_24 = QtWidgets.QLabel(self.groupBox_3) 384 self .label_24.setGeometry(QtCore.QRect(10, 260, 121, 16)) 385 self .label_24 .setObjectName("label_24") 386 self .label_25 = QtWidgets.QLabel(self.groupBox_3) 387 self .label_25.setGeometry(QtCore.QRect(240, 230, 41, 21)) 388 self .label_25 .setAlignment(QtCore.Qt.AlignCenter) 389 self .label_25 .setObjectName("label_25") 390 self .label_26 = QtWidgets.QLabel(self.groupBox_3) 391 self .label_26.setGeometry(QtCore.QRect(10, 230, 121, 16)) 392 self .label_26 .setObjectName("label_26") 393 self .pushButton_3 = QtWidgets.QPushButton(self.groupBox_3) 394 self .pushButton_3.setGeometry(QtCore.QRect(80, 20, 61, 23)) 395 self .pushButton_3.setObjectName("pushButton_3") 396 self .FREQ_2 = QtWidgets.QLineEdit(self.groupBox_3) 397 self .FREQ_2.setGeometry(QtCore.QRect(170, 80, 71, 20)) 398 self .FREQ_2.setStyleSheet("") 399 self .FREQ_2.setObjectName("FREQ_2") 400 self .FREQ_3 = QtWidgets.QLineEdit(self.groupBox_3) 401 self .FREQ_3.setGeometry(QtCore.QRect(170, 110, 71, 20)) 402 self .FREQ_3.setStyleSheet("") 403 self .FREQ_3.setObjectName("FREQ_3") 404 self .FREQ_4 = QtWidgets.QLineEdit(self.groupBox_3) 405 self .FREQ_4.setGeometry(QtCore.QRect(170, 140, 71, 20)) 406 self .FREQ_4.setStyleSheet("") 407 self .FREQ_4.setObjectName("FREQ_4") 408 self .FREQ_5 = QtWidgets.QLineEdit(self.groupBox_3) 409 self .FREQ_5.setGeometry(QtCore.QRect(170, 170, 71, 20)) 410 self .FREQ_5.setStyleSheet("") 411 self .FREQ_5.setObjectName("FREQ_5") 412 self .FREQ_6 = QtWidgets.QLineEdit(self.groupBox_3) 413 self .FREQ_6.setGeometry(QtCore.QRect(170, 200, 71, 20)) 414 self .FREQ_6.setStyleSheet("") 415 self .FREQ_6.setObjectName("FREQ_6") 416 self .FREQ_7 = QtWidgets.QLineEdit(self.groupBox_3) 417 self .FREQ_7.setGeometry(QtCore.QRect(170, 230, 71, 20)) 418 self .FREQ_7.setStyleSheet("") 419 self .FREQ_7.setObjectName("FREQ_7") 420 self .FREQ_8 = QtWidgets.QLineEdit(self.groupBox_3) 421 self .FREQ_8.setGeometry(QtCore.QRect(170, 260, 71, 20)) 422 self .FREQ_8.setStyleSheet("") 423 self .FREQ_8.setObjectName("FREQ_8") 424 self .button_exportar = QtWidgets.QPushButton(self.tab) 425 self .button_exportar.setGeometry(QtCore.QRect(420, 420, 101, 31)) 426 self .button_exportar.setObjectName("button_exportar") 427 self .HTT = QtWidgets.QLineEdit(self.tab) 428 self .HTT.setGeometry(QtCore.QRect(180, 20, 71, 20)) 429 self .HTT.setObjectName("HTT") 430 self .HRR = QtWidgets.QLineEdit(self.tab) 431 self .HRR.setGeometry(QtCore.QRect(180, 50, 71, 20)) 432 self .HRR.setObjectName("HRR")
50 Appendix A. Código fuente 433 self .EPSLON = QtWidgets.QLineEdit(self.tab) 434 self .EPSLON.setGeometry(QtCore.QRect(180, 170, 71, 20)) 435 self .EPSLON.setObjectName("EPSLON") 436 self .SIGMA = QtWidgets.QLineEdit(self.tab) 437 self .SIGMA.setGeometry(QtCore.QRect(180, 200, 71, 20)) 438 self .SIGMA.setObjectName("SIGMA") 439 self .MplWidget = MplWidget(self.tab) 440 self .MplWidget.setGeometry(QtCore.QRect(320, 0, 541, 281)) 441 self .MplWidget.setStyleSheet("background−color:white;") 442 self .MplWidget.setObjectName("MplWidget") 443 self .button_cerrar = QtWidgets.QPushButton(self.tab) 444 self .button_cerrar.setGeometry(QtCore.QRect(700, 490, 101, 31)) 445 self .button_cerrar.setObjectName("button_cerrar") 446 self .tabWidget.addTab(self.tab, "") 447 self .tab_2 = QtWidgets.QWidget() 448 self .tab_2.setObjectName("tab_2") 449 self .label_31 = QtWidgets.QLabel(self.tab_2) 450 self .label_31.setGeometry(QtCore.QRect(250, 20, 41, 21)) 451 self .label_31 .setAlignment(QtCore.Qt.AlignCenter) 452 self .label_31 .setObjectName("label_31") 453 self . Vertical_2 = QtWidgets.QRadioButton(self.tab_2) 454 self . Vertical_2 .setGeometry(QtCore.QRect(131, 80, 91, 16)) 455 self . Vertical_2 .setChecked(True) 456 self . Vertical_2 .setObjectName("Vertical_2") 457 self .label_32 = QtWidgets.QLabel(self.tab_2) 458 self .label_32.setGeometry(QtCore.QRect(20, 80, 81, 16)) 459 self .label_32 .setObjectName("label_32") 460 self .label_33 = QtWidgets.QLabel(self.tab_2) 461 self .label_33.setGeometry(QtCore.QRect(250, 50, 41, 21)) 462 self .label_33 .setAlignment(QtCore.Qt.AlignCenter) 463 self .label_33 .setObjectName("label_33") 464 self .HTT_2 = QtWidgets.QLineEdit(self.tab_2) 465 self .HTT_2.setGeometry(QtCore.QRect(180, 20, 71, 20)) 466 self .HTT_2.setObjectName("HTT_2") 467 self .label_34 = QtWidgets.QLabel(self.tab_2) 468 self .label_34.setGeometry(QtCore.QRect(20, 50, 121, 16)) 469 self .label_34 .setObjectName("label_34") 470 self .Horizontal_2 = QtWidgets.QRadioButton(self.tab_2) 471 self .Horizontal_2.setGeometry(QtCore.QRect(210, 80, 82, 17)) 472 self .Horizontal_2.setObjectName("Horizontal_2") 473 self .label_35 = QtWidgets.QLabel(self.tab_2) 474 self .label_35.setGeometry(QtCore.QRect(20, 20, 131, 16)) 475 self .label_35 .setObjectName("label_35") 476 self .label_36 = QtWidgets.QLabel(self.tab_2) 477 self .label_36.setGeometry(QtCore.QRect(20, 110, 121, 16)) 478 self .label_36 .setObjectName("label_36") 479 self .label_37 = QtWidgets.QLabel(self.tab_2) 480 self .label_37.setGeometry(QtCore.QRect(250, 110, 41, 21)) 481 self .label_37 .setAlignment(QtCore.Qt.AlignCenter) 482 self .label_37 .setObjectName("label_37") 483 self .MplWidget_2 = MplWidget(self.tab_2) 484 self .MplWidget_2.setGeometry(QtCore.QRect(320, 0, 541, 281)) 485 self .MplWidget_2.setStyleSheet("background−color:white;") 486 self .MplWidget_2.setObjectName("MplWidget_2") 487 self .button_cerrar_2 = QtWidgets.QPushButton(self.tab_2) 488 self .button_cerrar_2.setGeometry(QtCore.QRect(700, 490, 101, 31)) 489 self .button_cerrar_2.setObjectName("button_cerrar_2") 490 self .groupBox_4 = QtWidgets.QGroupBox(self.tab_2) 491 self .groupBox_4.setGeometry(QtCore.QRect(560, 310, 241, 61)) 492 self .groupBox_4.setFlat(False) 493 self .groupBox_4.setCheckable(False) 494 self .groupBox_4.setObjectName("groupBox_4") 495 self .label_38 = QtWidgets.QLabel(self.groupBox_4) 496 self .label_38.setGeometry(QtCore.QRect(80, 30, 81, 20)) 497 self .label_38 .setObjectName("label_38") 498 self . lineEdit_4 = QtWidgets.QLineEdit(self.groupBox_4) 499 self . lineEdit_4 .setEnabled(True) 500 self . lineEdit_4 .setGeometry(QtCore.QRect(170, 30, 61, 20)) 501 self . lineEdit_4 . setStyleSheet ("") 502 self . lineEdit_4 .setReadOnly(True) 503 self . lineEdit_4 .setObjectName("lineEdit_4")
51 504 self .button_exportar_2 = QtWidgets.QPushButton(self.tab_2) 505 self .button_exportar_2.setGeometry(QtCore.QRect(420, 420, 101, 31)) 506 self .button_exportar_2.setObjectName("button_exportar_2") 507 self .button_limpiar_2 = QtWidgets.QPushButton(self.tab_2) 508 self .button_limpiar_2.setGeometry(QtCore.QRect(420, 370, 101, 31)) 509 self .button_limpiar_2.setObjectName("button_limpiar_2") 510 self . button_iniciar_2 = QtWidgets.QPushButton(self.tab_2) 511 self . button_iniciar_2.setGeometry(QtCore.QRect(420, 320, 101, 31)) 512 self . button_iniciar_2 .setObjectName("button_iniciar_2") 513 self .groupBox_5 = QtWidgets.QGroupBox(self.tab_2) 514 self .groupBox_5.setGeometry(QtCore.QRect(10, 140, 281, 51)) 515 self .groupBox_5.setObjectName("groupBox_5") 516 self .comboBox_3 = QtWidgets.QComboBox(self.groupBox_5) 517 self .comboBox_3.setGeometry(QtCore.QRect(10, 20, 61, 22)) 518 self .comboBox_3.setStyleSheet("backgroundr: white;") 519 self .comboBox_3.setDuplicatesEnabled(False) 520 self .comboBox_3.setFrame(True) 521 self .comboBox_3.setObjectName("comboBox_3") 522 self .comboBox_3.addItem("") 523 self .comboBox_3.addItem("") 524 self .comboBox_3.addItem("") 525 self .pushButton_7 = QtWidgets.QPushButton(self.groupBox_5) 526 self .pushButton_7.setGeometry(QtCore.QRect(160, 20, 111, 21)) 527 self .pushButton_7.setObjectName("pushButton_7") 528 self .groupBox_6 = QtWidgets.QGroupBox(self.tab_2) 529 self .groupBox_6.setGeometry(QtCore.QRect(10, 200, 281, 331)) 530 self .groupBox_6.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) 531 self .groupBox_6.setFlat(False) 532 self .groupBox_6.setCheckable(False) 533 self .groupBox_6.setObjectName("groupBox_6") 534 self .label_44 = QtWidgets.QLabel(self.groupBox_6) 535 self .label_44.setGeometry(QtCore.QRect(210, 10, 61, 20)) 536 self .label_44 .setAlignment(QtCore.Qt.AlignCenter) 537 self .label_44 .setObjectName("label_44") 538 self .label_45 = QtWidgets.QLabel(self.groupBox_6) 539 self .label_45.setGeometry(QtCore.QRect(160, 10, 41, 20)) 540 self .label_45 .setAlignment(QtCore.Qt.AlignCenter) 541 self .label_45 .setObjectName("label_45") 542 self .label_46 = QtWidgets.QLabel(self.groupBox_6) 543 self .label_46.setGeometry(QtCore.QRect(120, 60, 81, 21)) 544 self .label_46 .setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) 545 self .label_46 .setObjectName("label_46") 546 self .comboBox_4 = QtWidgets.QComboBox(self.groupBox_6) 547 self .comboBox_4.setGeometry(QtCore.QRect(10, 30, 141, 22)) 548 self .comboBox_4.setStyleSheet("backgroundr: white;") 549 self .comboBox_4.setDuplicatesEnabled(False) 550 self .comboBox_4.setFrame(True) 551 self .comboBox_4.setObjectName("comboBox_4") 552 self .comboBox_4.addItem("") 553 self .comboBox_4.addItem("") 554 self .comboBox_4.addItem("") 555 self .comboBox_4.addItem("") 556 self .comboBox_4.addItem("") 557 self .comboBox_4.addItem("") 558 self .comboBox_4.addItem("") 559 self .comboBox_4.addItem("") 560 self .comboBox_4.addItem("") 561 self .comboBox_4.addItem("") 562 self .comboBox_4.addItem("") 563 self .label_47 = QtWidgets.QLabel(self.groupBox_6) 564 self .label_47.setGeometry(QtCore.QRect(160, 90, 41, 20)) 565 self .label_47 .setAlignment(QtCore.Qt.AlignCenter) 566 self .label_47 .setObjectName("label_47") 567 self .label_48 = QtWidgets.QLabel(self.groupBox_6) 568 self .label_48.setGeometry(QtCore.QRect(210, 90, 61, 20)) 569 self .label_48 .setAlignment(QtCore.Qt.AlignCenter) 570 self .label_48 .setObjectName("label_48") 571 self .comboBox_5 = QtWidgets.QComboBox(self.groupBox_6) 572 self .comboBox_5.setGeometry(QtCore.QRect(10, 110, 141, 22)) 573 self .comboBox_5.setStyleSheet("backgroundr: white;") 574 self .comboBox_5.setDuplicatesEnabled(False)
52 Appendix A. Código fuente 575 self .comboBox_5.setFrame(True) 576 self .comboBox_5.setObjectName("comboBox_5") 577 self .comboBox_5.addItem("") 578 self .comboBox_5.addItem("") 579 self .comboBox_5.addItem("") 580 self .comboBox_5.addItem("") 581 self .comboBox_5.addItem("") 582 self .comboBox_5.addItem("") 583 self .comboBox_5.addItem("") 584 self .comboBox_5.addItem("") 585 self .comboBox_5.addItem("") 586 self .comboBox_5.addItem("") 587 self .comboBox_5.addItem("") 588 self .label_49 = QtWidgets.QLabel(self.groupBox_6) 589 self .label_49.setGeometry(QtCore.QRect(120, 140, 81, 21)) 590 self .label_49 .setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) 591 self .label_49 .setObjectName("label_49") 592 self .label_50 = QtWidgets.QLabel(self.groupBox_6) 593 self .label_50.setGeometry(QtCore.QRect(160, 170, 41, 20)) 594 self .label_50 .setAlignment(QtCore.Qt.AlignCenter) 595 self .label_50 .setObjectName("label_50") 596 self .label_51 = QtWidgets.QLabel(self.groupBox_6) 597 self .label_51.setGeometry(QtCore.QRect(210, 170, 61, 20)) 598 self .label_51 .setAlignment(QtCore.Qt.AlignCenter) 599 self .label_51 .setObjectName("label_51") 600 self .comboBox_6 = QtWidgets.QComboBox(self.groupBox_6) 601 self .comboBox_6.setGeometry(QtCore.QRect(10, 190, 141, 22)) 602 self .comboBox_6.setStyleSheet("backgroundr: white;") 603 self .comboBox_6.setDuplicatesEnabled(False) 604 self .comboBox_6.setFrame(True) 605 self .comboBox_6.setObjectName("comboBox_6") 606 self .comboBox_6.addItem("") 607 self .comboBox_6.addItem("") 608 self .comboBox_6.addItem("") 609 self .comboBox_6.addItem("") 610 self .comboBox_6.addItem("") 611 self .comboBox_6.addItem("") 612 self .comboBox_6.addItem("") 613 self .comboBox_6.addItem("") 614 self .comboBox_6.addItem("") 615 self .comboBox_6.addItem("") 616 self .comboBox_6.addItem("") 617 self .label_52 = QtWidgets.QLabel(self.groupBox_6) 618 self .label_52.setGeometry(QtCore.QRect(120, 220, 81, 21)) 619 self .label_52 .setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) 620 self .label_52 .setObjectName("label_52") 621 self .label_53 = QtWidgets.QLabel(self.groupBox_6) 622 self .label_53.setGeometry(QtCore.QRect(120, 300, 81, 21)) 623 self .label_53 .setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) 624 self .label_53 .setObjectName("label_53") 625 self .label_54 = QtWidgets.QLabel(self.groupBox_6) 626 self .label_54.setGeometry(QtCore.QRect(210, 250, 61, 20)) 627 self .label_54 .setAlignment(QtCore.Qt.AlignCenter) 628 self .label_54 .setObjectName("label_54") 629 self .comboBox_7 = QtWidgets.QComboBox(self.groupBox_6) 630 self .comboBox_7.setGeometry(QtCore.QRect(10, 270, 141, 22)) 631 self .comboBox_7.setStyleSheet("backgroundr: white;") 632 self .comboBox_7.setDuplicatesEnabled(False) 633 self .comboBox_7.setFrame(True) 634 self .comboBox_7.setObjectName("comboBox_7") 635 self .comboBox_7.addItem("") 636 self .comboBox_7.addItem("") 637 self .comboBox_7.addItem("") 638 self .comboBox_7.addItem("") 639 self .comboBox_7.addItem("") 640 self .comboBox_7.addItem("") 641 self .comboBox_7.addItem("") 642 self .comboBox_7.addItem("") 643 self .comboBox_7.addItem("") 644 self .comboBox_7.addItem("") 645 self .comboBox_7.addItem("")
53 646 self .label_55 = QtWidgets.QLabel(self.groupBox_6) 647 self .label_55.setGeometry(QtCore.QRect(160, 250, 41, 20)) 648 self .label_55 .setAlignment(QtCore.Qt.AlignCenter) 649 self .label_55 .setObjectName("label_55") 650 self . line = QtWidgets.QFrame(self.groupBox_6) 651 self . line .setGeometry(QtCore.QRect(0, 240, 281, 16)) 652 self . line .setFrameShape(QtWidgets.QFrame.HLine) 653 self . line .setFrameShadow(QtWidgets.QFrame.Sunken) 654 self . line .setObjectName("line") 655 self . line_2 = QtWidgets.QFrame(self.groupBox_6) 656 self . line_2 .setGeometry(QtCore.QRect(0, 160, 281, 16)) 657 self . line_2 .setFrameShape(QtWidgets.QFrame.HLine) 658 self . line_2 .setFrameShadow(QtWidgets.QFrame.Sunken) 659 self . line_2 .setObjectName("line_2") 660 self . line_3 = QtWidgets.QFrame(self.groupBox_6) 661 self . line_3 .setGeometry(QtCore.QRect(0, 80, 281, 16)) 662 self . line_3 .setFrameShape(QtWidgets.QFrame.HLine) 663 self . line_3 .setFrameShadow(QtWidgets.QFrame.Sunken) 664 self . line_3 .setObjectName("line_3") 665 self .Epsilon1 = QtWidgets.QLineEdit(self.groupBox_6) 666 self .Epsilon1.setGeometry(QtCore.QRect(160, 30, 41, 20)) 667 self .Epsilon1.setObjectName("Epsilon1") 668 self .Sigma1 = QtWidgets.QLineEdit(self.groupBox_6) 669 self .Sigma1.setGeometry(QtCore.QRect(210, 30, 61, 20)) 670 self .Sigma1.setObjectName("Sigma1") 671 self .Long1 = QtWidgets.QLineEdit(self.groupBox_6) 672 self .Long1.setGeometry(QtCore.QRect(210, 60, 61, 20)) 673 self .Long1.setObjectName("Long1") 674 self .Epsilon2 = QtWidgets.QLineEdit(self.groupBox_6) 675 self .Epsilon2.setGeometry(QtCore.QRect(160, 110, 41, 20)) 676 self .Epsilon2.setObjectName("Epsilon2") 677 self .Sigma2 = QtWidgets.QLineEdit(self.groupBox_6) 678 self .Sigma2.setGeometry(QtCore.QRect(210, 110, 61, 20)) 679 self .Sigma2.setObjectName("Sigma2") 680 self .Long2 = QtWidgets.QLineEdit(self.groupBox_6) 681 self .Long2.setGeometry(QtCore.QRect(210, 140, 61, 20)) 682 self .Long2.setObjectName("Long2") 683 self .Epsilon3 = QtWidgets.QLineEdit(self.groupBox_6) 684 self .Epsilon3.setGeometry(QtCore.QRect(160, 190, 41, 20)) 685 self .Epsilon3.setObjectName("Epsilon3") 686 self .Sigma3 = QtWidgets.QLineEdit(self.groupBox_6) 687 self .Sigma3.setGeometry(QtCore.QRect(210, 190, 61, 20)) 688 self .Sigma3.setObjectName("Sigma3") 689 self .Long3 = QtWidgets.QLineEdit(self.groupBox_6) 690 self .Long3.setGeometry(QtCore.QRect(210, 220, 61, 20)) 691 self .Long3.setObjectName("Long3") 692 self .Epsilon4 = QtWidgets.QLineEdit(self.groupBox_6) 693 self .Epsilon4.setGeometry(QtCore.QRect(160, 270, 41, 20)) 694 self .Epsilon4.setObjectName("Epsilon4") 695 self .Sigma4 = QtWidgets.QLineEdit(self.groupBox_6) 696 self .Sigma4.setGeometry(QtCore.QRect(210, 270, 61, 20)) 697 self .Sigma4.setObjectName("Sigma4") 698 self .Long4 = QtWidgets.QLineEdit(self.groupBox_6) 699 self .Long4.setGeometry(QtCore.QRect(210, 300, 61, 20)) 700 self .Long4.setObjectName("Long4") 701 self .HRR_2 = QtWidgets.QLineEdit(self.tab_2) 702 self .HRR_2.setGeometry(QtCore.QRect(180, 50, 71, 20)) 703 self .HRR_2.setObjectName("HRR_2") 704 self .FREQmixto = QtWidgets.QLineEdit(self.tab_2) 705 self .FREQmixto.setGeometry(QtCore.QRect(180, 110, 71, 20)) 706 self .FREQmixto.setObjectName("FREQmixto") 707 self .tabWidget.addTab(self.tab_2, "") 708 MainWindow.setCentralWidget(self.centralwidget) 709 self .menubar = QtWidgets.QMenuBar(MainWindow) 710 self .menubar.setGeometry(QtCore.QRect(0, 0, 867, 21)) 711 self .menubar.setObjectName("menubar") 712 MainWindow.setMenuBar(self.menubar) 713 self .statusbar = QtWidgets.QStatusBar(MainWindow) 714 self .statusbar.setObjectName("statusbar") 715 MainWindow.setStatusBar(self.statusbar) 716
54 Appendix A. Código fuente 717 self .MplWidget.setGeometry(QtCore.QRect(320, 0, 541, 309)) #SUSTITUCION 718 self .MplWidget_2.setGeometry(QtCore.QRect(320, 0, 541, 309)) #SUSTITUCION 719 self . button_iniciar . setIcon(QtGui.QIcon("media2.png")) #INCRUSTADO 720 self . button_iniciar . setIconSize (QtCore.QSize(20,20)) #INCRUSTADO 721 self .button_limpiar.setIcon(QtGui.QIcon("goma.png")) #INCRUSTADO 722 self .button_limpiar.setIconSize (QtCore.QSize(20,20)) #INCRUSTADO 723 self .button_exportar.setIcon(QtGui.QIcon("export.png")) #INCRUSTADO 724 self .button_exportar.setIconSize(QtCore.QSize(15,15)) #INCRUSTADO 725 self .button_cerrar.setIcon(QtGui.QIcon("back.png")) #INCRUSTADO 726 self .button_cerrar.setIconSize (QtCore.QSize(20,20)) #INCRUSTADO 727 728 self . button_iniciar_2 . setIcon(QtGui.QIcon("media2.png")) #INCRUSTADO 729 self . button_iniciar_2 . setIconSize (QtCore.QSize(20,20)) #INCRUSTADO 730 self .button_limpiar_2.setIcon(QtGui.QIcon("goma.png")) #INCRUSTADO 731 self .button_limpiar_2.setIconSize(QtCore.QSize(20,20)) #INCRUSTADO 732 self .button_exportar_2.setIcon(QtGui.QIcon("export.png")) #INCRUSTADO 733 self .button_exportar_2.setIconSize(QtCore.QSize(15,15)) #INCRUSTADO 734 self .button_cerrar_2.setIcon(QtGui.QIcon("back.png")) #INCRUSTADO 735 self .button_cerrar_2.setIconSize(QtCore.QSize(20,20)) #INCRUSTADO 736 737 738 self .MplWidget.canvas.axes. set_title (’Curva de propagación’,fontsize=10)#INCRUSTADO 739 self .MplWidget.canvas.axes.set_xlabel(’Distancia [km]’, fontsize =8)#INCRUSTADO 740 self .MplWidget.canvas.axes.set_ylabel(’Intensidad de campo [dBu]’,fontsize=8)#INCRUSTADO 741 self .MplWidget.canvas.axes.set_ylim(−20,120) #INCRUSTADO 742 self .MplWidget.canvas.axes.semilogx() #INCRUSTADO 743 self .MplWidget.canvas.axes.set_xlim(1,10000) #INCRUSTADO 744 self .MplWidget.canvas.axes.grid(True,which="both",linewidth=0.5) #INCRUSTADO 745 self .MplWidget_2.canvas.axes. set_title ( ’Curva de propagación’,fontsize=10)#INCRUSTADO 746 self .MplWidget_2.canvas.axes.set_xlabel(’Distancia [km]’, fontsize =8)#INCRUSTADO 747 self .MplWidget_2.canvas.axes.set_ylabel(’Intensidad de campo [dBu]’,fontsize=8)#INCRUSTADO 748 self .MplWidget_2.canvas.axes.set_ylim(−20,120) #INCRUSTADO 749 self .MplWidget_2.canvas.axes.semilogx() #INCRUSTADO 750 self .MplWidget_2.canvas.axes.set_xlim(1,10000) #INCRUSTADO 751 self .MplWidget_2.canvas.axes.grid(True,which="both",linewidth=0.5) #INCRUSTADO 752 self .FREQ_2.setVisible(False) #INCRUSTADO 753 self .FREQ_3.setVisible(False) #INCRUSTADO 754 self .FREQ_4.setVisible(False) #INCRUSTADO 755 self .FREQ_5.setVisible(False) #INCRUSTADO 756 self .FREQ_6.setVisible(False) #INCRUSTADO 757 self .FREQ_7.setVisible(False) #INCRUSTADO 758 self .FREQ_8.setVisible(False) #INCRUSTADO 759 self . label_11. setVisible (False) #INCRUSTADO 760 self . label_12. setVisible (False) #INCRUSTADO 761 self . label_15. setVisible (False) #INCRUSTADO 762 self . label_16. setVisible (False) #INCRUSTADO 763 self . label_17. setVisible (False) #INCRUSTADO 764 self . label_18. setVisible (False) #INCRUSTADO 765 self . label_19. setVisible (False) #INCRUSTADO 766 self . label_20. setVisible (False) #INCRUSTADO 767 self . label_21. setVisible (False) #INCRUSTADO 768 self . label_22. setVisible (False) #INCRUSTADO 769 self . label_23. setVisible (False) #INCRUSTADO 770 self . label_24. setVisible (False) #INCRUSTADO 771 self . label_25. setVisible (False) #INCRUSTADO 772 self . label_26. setVisible (False) #INCRUSTADO 773 self . lineEdit .setReadOnly(True) #INCRUSTADO 774 self . lineEdit_4 .setReadOnly(True) #INCRUSTADO 775 self . lineEdit . setStyleSheet ("background−color: rgb(235, 235, 235);")#INCRUSTADO 776 self . lineEdit_2 . setStyleSheet ("background−color: rgb(235, 235, 235);")#INCRUSTADO 777 self . lineEdit_3 . setStyleSheet ("background−color: rgb(235, 235, 235);")#INCRUSTADO 778 779 self . label_50. setVisible (False) #INCRUSTADO 780 self . label_51. setVisible (False) #INCRUSTADO 781 self . label_52. setVisible (False) #INCRUSTADO 782 self .comboBox_6.setVisible(False) #INCRUSTADO 783 self .Epsilon3. setVisible (False) #INCRUSTADO 784 self .Sigma3.setVisible(False) #INCRUSTADO 785 self .Long3.setVisible(False) #INCRUSTADO 786 787 self . label_53. setVisible (False) #INCRUSTADO
55 788 self . label_54. setVisible (False) #INCRUSTADO 789 self . label_55. setVisible (False) #INCRUSTADO 790 self .comboBox_7.setVisible(False) #INCRUSTADO 791 self .Epsilon4. setVisible (False) #INCRUSTADO 792 self .Sigma4.setVisible(False) #INCRUSTADO 793 self .Long4.setVisible(False) #INCRUSTADO 794 self . line . setVisible (False) #INCRUSTADO 795 796 self . retranslateUi (MainWindow) 797 self .tabWidget.setCurrentIndex(0) 798 QtCore.QMetaObject.connectSlotsByName(MainWindow) 799 800 def retranslateUi ( self , MainWindow): 801 _translate = QtCore.QCoreApplication.translate 802 MainWindow.setWindowTitle(_translate("MainWindow","GroundWave Simulator")) 803 self .button_limpiar.setText( _translate ("MainWindow","Limpiar")) 804 self .groupBox.setTitle( _translate ("MainWindow","Elegir tipo de terreno")) 805 self .pushButton.setText(_translate ("MainWindow","Ayuda")) 806 self .comboBox.setItemText(0, _translate("MainWindow","Personalizado")) 807 self .comboBox.setItemText(1, _translate("MainWindow","Agua del mar, salinidad baja")) 808 self .comboBox.setItemText(2, _translate("MainWindow","Agua del mar, salinidad media")) 809 self .comboBox.setItemText(3, _translate("MainWindow","Agua dulce")) 810 self .comboBox.setItemText(4, _translate("MainWindow","Tierra")) 811 self .comboBox.setItemText(5, _translate("MainWindow","Tierra húmeda")) 812 self .comboBox.setItemText(6, _translate("MainWindow","Tierra moderadamente seca")) 813 self .comboBox.setItemText(7, _translate("MainWindow","Tierra seca")) 814 self .comboBox.setItemText(8, _translate("MainWindow","Tierra muy seca")) 815 self .comboBox.setItemText(9, _translate("MainWindow","Hielo de agua dulce, −1C")) 816 self .comboBox.setItemText(10, _translate("MainWindow","Hielo de agua dulce, −10C")) 817 self . label_7 . setText ( _translate ("MainWindow","(Conductividad)")) 818 self . Vertical . setText( _translate ("MainWindow","Vertical")) 819 self .groupBox_2.setTitle( _translate ("MainWindow","Calcular intensidad de campo eléctrico")) 820 self .label_14. setText( _translate ("MainWindow","E")) 821 self .pushButton_2.setText(_translate("MainWindow","Calcular")) 822 self .label_13. setText( _translate ("MainWindow","Distancia")) 823 self .label_27. setText( _translate ("MainWindow","Lb")) 824 self .label_28. setText( _translate ("MainWindow","km")) 825 self .label_29. setText( _translate ("MainWindow","dBu")) 826 self .label_30. setText( _translate ("MainWindow","dB")) 827 self . label_3 . setText ( _translate ("MainWindow","Altura antena receptora")) 828 self .label_10. setText( _translate ("MainWindow","S/m")) 829 self . button_iniciar . setText( _translate ("MainWindow","Ejecutar")) 830 self . label . setText( _translate ("MainWindow","m")) 831 self . label_8 . setText ( _translate ("MainWindow","m")) 832 self . label_6 . setText ( _translate ("MainWindow","(Constante dieléctrica)")) 833 self . label_5 . setText ( _translate ("MainWindow","Polarización")) 834 self .Horizontal.setText( _translate ("MainWindow","Horizontal")) 835 self . label_2 . setText ( _translate ("MainWindow","Altura antena transmisora")) 836 self .groupBox_3.setTitle( _translate ("MainWindow","Número de frecuencias")) 837 self . label_9 . setText ( _translate ("MainWindow","MHz")) 838 self . label_4 . setText ( _translate ("MainWindow","Frecuencia 1")) 839 self .comboBox_2.setItemText(0, _translate("MainWindow","1")) 840 self .comboBox_2.setItemText(1, _translate("MainWindow","2")) 841 self .comboBox_2.setItemText(2, _translate("MainWindow","3")) 842 self .comboBox_2.setItemText(3, _translate("MainWindow","4")) 843 self .comboBox_2.setItemText(4, _translate("MainWindow","5")) 844 self .comboBox_2.setItemText(5, _translate("MainWindow","6")) 845 self .comboBox_2.setItemText(6, _translate("MainWindow","7")) 846 self .comboBox_2.setItemText(7, _translate("MainWindow","8")) 847 self .label_11. setText( _translate ("MainWindow","Frecuencia 2")) 848 self .label_12. setText( _translate ("MainWindow","MHz")) 849 self .label_15. setText( _translate ("MainWindow","Frecuencia 3")) 850 self .label_16. setText( _translate ("MainWindow","MHz")) 851 self .label_17. setText( _translate ("MainWindow","MHz")) 852 self .label_18. setText( _translate ("MainWindow","Frecuencia 4")) 853 self .label_19. setText( _translate ("MainWindow","Frecuencia 5")) 854 self .label_20. setText( _translate ("MainWindow","MHz")) 855 self .label_21. setText( _translate ("MainWindow","MHz")) 856 self .label_22. setText( _translate ("MainWindow","MHz")) 857 self .label_23. setText( _translate ("MainWindow","Frecuencia 6")) 858 self .label_24. setText( _translate ("MainWindow","Frecuencia 8"))
56 Appendix A. Código fuente 859 self .label_25. setText( _translate ("MainWindow","MHz")) 860 self .label_26. setText( _translate ("MainWindow","Frecuencia 7")) 861 self .pushButton_3.setText(_translate("MainWindow","Ayuda")) 862 self .button_exportar.setText( _translate ("MainWindow","Exportar")) 863 self .button_cerrar.setText( _translate ("MainWindow","Regresar")) 864 self .tabWidget.setTabText(self .tabWidget.indexOf(self.tab) , _translate ("MainWindow","Terreno homogé neo")) 865 self .label_31. setText( _translate ("MainWindow","m")) 866 self . Vertical_2 . setText ( _translate ("MainWindow","Vertical")) 867 self .label_32. setText( _translate ("MainWindow","Polarización")) 868 self .label_33. setText( _translate ("MainWindow","m")) 869 self .label_34. setText( _translate ("MainWindow","Altura antena receptora")) 870 self .Horizontal_2.setText( _translate ("MainWindow","Horizontal")) 871 self .label_35. setText( _translate ("MainWindow","Altura antena transmisora")) 872 self .label_36. setText( _translate ("MainWindow","Frecuencia")) 873 self .label_37. setText( _translate ("MainWindow","MHz")) 874 self .button_cerrar_2.setText( _translate ("MainWindow","Regresar")) 875 self .groupBox_4.setTitle( _translate ("MainWindow","Intensidad de campo eléctrico recibido")) 876 self .label_38. setText( _translate ("MainWindow","E [dBu")) 877 self .button_exportar_2.setText( _translate ("MainWindow","Exportar")) 878 self .button_limpiar_2.setText( _translate ("MainWindow","Limpiar")) 879 self . button_iniciar_2 . setText ( _translate ("MainWindow","Ejecutar")) 880 self .groupBox_5.setTitle( _translate ("MainWindow","Elegir número de terrenos")) 881 self .comboBox_3.setItemText(0, _translate("MainWindow","2")) 882 self .comboBox_3.setItemText(1, _translate("MainWindow","3")) 883 self .comboBox_3.setItemText(2, _translate("MainWindow","4")) 884 self .pushButton_7.setText(_translate("MainWindow","Información")) 885 self .groupBox_6.setTitle( _translate ("MainWindow","Parámetros de cada terreno")) 886 self .label_44. setText( _translate ("MainWindow","Cond. [S/m]")) 887 self .label_45. setText( _translate ("MainWindow","Epsilon_r")) 888 self .label_46. setText( _translate ("MainWindow","Longitud [km]")) 889 self .comboBox_4.setItemText(0, _translate("MainWindow","Personalizado")) 890 self .comboBox_4.setItemText(1, _translate("MainWindow","Agua del mar, salinidad baja")) 891 self .comboBox_4.setItemText(2, _translate("MainWindow","Agua del mar, salinidad media")) 892 self .comboBox_4.setItemText(3, _translate("MainWindow","Agua dulce")) 893 self .comboBox_4.setItemText(4, _translate("MainWindow","Tierra")) 894 self .comboBox_4.setItemText(5, _translate("MainWindow","Tierra húmeda")) 895 self .comboBox_4.setItemText(6, _translate("MainWindow","Tierra moderadamente seca")) 896 self .comboBox_4.setItemText(7, _translate("MainWindow","Tierra seca")) 897 self .comboBox_4.setItemText(8, _translate("MainWindow","Tierra muy seca")) 898 self .comboBox_4.setItemText(9, _translate("MainWindow","Hielo de agua dulce, −1C")) 899 self .comboBox_4.setItemText(10, _translate("MainWindow","Hielo de agua dulce, −10C")) 900 self .label_47. setText( _translate ("MainWindow","Epsilon_r")) 901 self .label_48. setText( _translate ("MainWindow","Cond. [S/m]")) 902 self .comboBox_5.setItemText(0, _translate("MainWindow","Personalizado")) 903 self .comboBox_5.setItemText(1, _translate("MainWindow","Agua del mar, salinidad baja")) 904 self .comboBox_5.setItemText(2, _translate("MainWindow","Agua del mar, salinidad media")) 905 self .comboBox_5.setItemText(3, _translate("MainWindow","Agua dulce")) 906 self .comboBox_5.setItemText(4, _translate("MainWindow","Tierra")) 907 self .comboBox_5.setItemText(5, _translate("MainWindow","Tierra húmeda")) 908 self .comboBox_5.setItemText(6, _translate("MainWindow","Tierra moderadamente seca")) 909 self .comboBox_5.setItemText(7, _translate("MainWindow","Tierra seca")) 910 self .comboBox_5.setItemText(8, _translate("MainWindow","Tierra muy seca")) 911 self .comboBox_5.setItemText(9, _translate("MainWindow","Hielo de agua dulce, −1C")) 912 self .comboBox_5.setItemText(10, _translate("MainWindow","Hielo de agua dulce, −10C")) 913 self .label_49. setText( _translate ("MainWindow","Longitud [km]")) 914 self .label_50. setText( _translate ("MainWindow","Epsilon_r")) 915 self .label_51. setText( _translate ("MainWindow","Cond. [S/m]")) 916 self .comboBox_6.setItemText(0, _translate("MainWindow","Personalizado")) 917 self .comboBox_6.setItemText(1, _translate("MainWindow","Agua del mar, salinidad baja")) 918 self .comboBox_6.setItemText(2, _translate("MainWindow","Agua del mar, salinidad media")) 919 self .comboBox_6.setItemText(3, _translate("MainWindow","Agua dulce")) 920 self .comboBox_6.setItemText(4, _translate("MainWindow","Tierra")) 921 self .comboBox_6.setItemText(5, _translate("MainWindow","Tierra húmeda")) 922 self .comboBox_6.setItemText(6, _translate("MainWindow","Tierra moderadamente seca")) 923 self .comboBox_6.setItemText(7, _translate("MainWindow","Tierra seca")) 924 self .comboBox_6.setItemText(8, _translate("MainWindow","Tierra muy seca")) 925 self .comboBox_6.setItemText(9, _translate("MainWindow","Hielo de agua dulce, −1C")) 926 self .comboBox_6.setItemText(10, _translate("MainWindow","Hielo de agua dulce, −10C")) 927 self .label_52. setText( _translate ("MainWindow","Longitud [km]")) 928 self .label_53. setText( _translate ("MainWindow","Longitud [km]"))
57 929 self .label_54. setText( _translate ("MainWindow","Cond. [S/m]")) 930 self .comboBox_7.setItemText(0, _translate("MainWindow","Personalizado")) 931 self .comboBox_7.setItemText(1, _translate("MainWindow","Agua del mar, salinidad baja")) 932 self .comboBox_7.setItemText(2, _translate("MainWindow","Agua del mar, salinidad media")) 933 self .comboBox_7.setItemText(3, _translate("MainWindow","Agua dulce")) 934 self .comboBox_7.setItemText(4, _translate("MainWindow","Tierra")) 935 self .comboBox_7.setItemText(5, _translate("MainWindow","Tierra húmeda")) 936 self .comboBox_7.setItemText(6, _translate("MainWindow","Tierra moderadamente seca")) 937 self .comboBox_7.setItemText(7, _translate("MainWindow","Tierra seca")) 938 self .comboBox_7.setItemText(8, _translate("MainWindow","Tierra muy seca")) 939 self .comboBox_7.setItemText(9, _translate("MainWindow","Hielo de agua dulce, −1C")) 940 self .comboBox_7.setItemText(10, _translate("MainWindow","Hielo de agua dulce, −10C")) 941 self .label_55. setText( _translate ("MainWindow","Epsilon_r")) 942 self .tabWidget.setTabText(self .tabWidget.indexOf(self.tab_2), _translate ("MainWindow","Terreno mixto" )) 943 from mplwidget import MplWidget 944 945 946 if __name__ == "__main__": 947 import sys 948 app = QtWidgets.QApplication(sys.argv) 949 MainWindow = QtWidgets.QMainWindow() 950 ui = Ui_MainWindow() 951 ui.setupUi(MainWindow) 952 MainWindow.show() 953 sys . exit (app.exec_() ) . lectura2.py 1#−− coding: utf−8−− 2""" 3Created on Mon Mar 30 17:28:31 2020 4 5author: Javier Ojeda Prieto 6""" 7from matplotlib.pyplot import figure , show 8import pandas as pd 9import subprocess 10 import os.path 11 import os 12 import shutil 13 14 def ejecucion( self ): 15 ENTORNO=self.parent().labelEstado.text() #Obtenemos de la interfaz de bienvenida el estado del interruptor 16 if ’Windows’ in ENTORNO: 17 subprocess. call (’grwave <entrada.dat >grwave.out’, shell =True) 18 return True 19 elif ’MSDOS’ in ENTORNO: 20 homedir = os.path.expanduser("~")#Obtenemos nombre de carpeta de usuario extendida 21 direccion=homedir+"\AppData\Local\DOSBox" #le aadimos el resto de direccion donde necesitamos 22 #que exista un fichero de configuracion de DOSBox 23 os.makedirs(direccion, exist_ok=True) #Creamos la carpeta, pero que no de fallo si ya existe 24 shutil .copy("dosbox−0.74−3.conf", direccion) #copiamos el archivo a la direccion 25 subprocess. call (’DOSBox−0.74−3\DOSBox’, shell=True) #llamamos al programa DOSBox 26 fnew=open("grwave.out","r") 27 contenedor=fnew.read() 28 if ("mode" in contenedor): 29 self .lanzarWarning("No disponible en MS−DOS. \n Elija terminal de Windows.") 30 return False 31 else : 32 return True 33 fnew.close () 34 35 def lecturaSalida ( self , inicial ): 36 #lee el archivo, salta primeras 31 filas 37 fo = open("grwave.out","r") 38 if ( inicial ==0): #se llama a GRWAVE 4 veces por cada grafica (por temas de escala logaritmica)
64 Appendix A. Código fuente 165 self . label_16. setVisible (False) 166 self . label_18. setVisible (False) 167 self .FREQ_5.setVisible(False) 168 self . label_19. setVisible (False) 169 self . label_21. setVisible (False) 170 self .FREQ_6.setVisible(False) 171 self . label_20. setVisible (False) 172 self . label_23. setVisible (False) 173 self .FREQ_7.setVisible(False) 174 self . label_25. setVisible (False) 175 self . label_26. setVisible (False) 176 self .FREQ_8.setVisible(False) 177 self . label_24. setVisible (False) 178 self . label_22. setVisible (False) 179 self . ocultaDistancia () #oculta el calculo de distancia porque a mas de una frecuencia no esta disponible 180 elif (comboText=="3"): 181 self .FREQ_2.setVisible(True) 182 self . label_11. setVisible (True) 183 self . label_12. setVisible (True) 184 self .FREQ_3.setVisible(True) 185 self . label_15. setVisible (True) 186 self . label_17. setVisible (True) 187 self .FREQ_4.setVisible(False) 188 self . label_16. setVisible (False) 189 self . label_18. setVisible (False) 190 self .FREQ_5.setVisible(False) 191 self . label_19. setVisible (False) 192 self . label_21. setVisible (False) 193 self .FREQ_6.setVisible(False) 194 self . label_20. setVisible (False) 195 self . label_23. setVisible (False) 196 self .FREQ_7.setVisible(False) 197 self . label_25. setVisible (False) 198 self . label_26. setVisible (False) 199 self .FREQ_8.setVisible(False) 200 self . label_24. setVisible (False) 201 self . label_22. setVisible (False) 202 self . ocultaDistancia () 203 elif (comboText=="4"): 204 self .FREQ_2.setVisible(True) 205 self . label_11. setVisible (True) 206 self . label_12. setVisible (True) 207 self .FREQ_3.setVisible(True) 208 self . label_15. setVisible (True) 209 self . label_17. setVisible (True) 210 self .FREQ_4.setVisible(True) 211 self . label_16. setVisible (True) 212 self . label_18. setVisible (True) 213 self .FREQ_5.setVisible(False) 214 self . label_19. setVisible (False) 215 self . label_21. setVisible (False) 216 self .FREQ_6.setVisible(False) 217 self . label_20. setVisible (False) 218 self . label_23. setVisible (False) 219 self .FREQ_7.setVisible(False) 220 self . label_25. setVisible (False) 221 self . label_26. setVisible (False) 222 self .FREQ_8.setVisible(False) 223 self . label_24. setVisible (False) 224 self . label_22. setVisible (False) 225 self . ocultaDistancia () 226 elif (comboText=="5"): 227 self .FREQ_2.setVisible(True) 228 self . label_11. setVisible (True) 229 self . label_12. setVisible (True) 230 self .FREQ_3.setVisible(True) 231 self . label_15. setVisible (True) 232 self . label_17. setVisible (True) 233 self .FREQ_4.setVisible(True) 234 self . label_16. setVisible (True)
65 235 self . label_18. setVisible (True) 236 self .FREQ_5.setVisible(True) 237 self . label_19. setVisible (True) 238 self . label_21. setVisible (True) 239 self .FREQ_6.setVisible(False) 240 self . label_20. setVisible (False) 241 self . label_23. setVisible (False) 242 self .FREQ_7.setVisible(False) 243 self . label_25. setVisible (False) 244 self . label_26. setVisible (False) 245 self .FREQ_8.setVisible(False) 246 self . label_24. setVisible (False) 247 self . label_22. setVisible (False) 248 self . ocultaDistancia () 249 elif (comboText=="6"): 250 self .FREQ_2.setVisible(True) 251 self . label_11. setVisible (True) 252 self . label_12. setVisible (True) 253 self .FREQ_3.setVisible(True) 254 self . label_15. setVisible (True) 255 self . label_17. setVisible (True) 256 self .FREQ_4.setVisible(True) 257 self . label_16. setVisible (True) 258 self . label_18. setVisible (True) 259 self .FREQ_5.setVisible(True) 260 self . label_19. setVisible (True) 261 self . label_21. setVisible (True) 262 self .FREQ_6.setVisible(True) 263 self . label_20. setVisible (True) 264 self . label_23. setVisible (True) 265 self .FREQ_7.setVisible(False) 266 self . label_25. setVisible (False) 267 self . label_26. setVisible (False) 268 self .FREQ_8.setVisible(False) 269 self . label_24. setVisible (False) 270 self . label_22. setVisible (False) 271 self . ocultaDistancia () 272 elif (comboText=="7"): 273 self .FREQ_2.setVisible(True) 274 self . label_11. setVisible (True) 275 self . label_12. setVisible (True) 276 self .FREQ_3.setVisible(True) 277 self . label_15. setVisible (True) 278 self . label_17. setVisible (True) 279 self .FREQ_4.setVisible(True) 280 self . label_16. setVisible (True) 281 self . label_18. setVisible (True) 282 self .FREQ_5.setVisible(True) 283 self . label_19. setVisible (True) 284 self . label_21. setVisible (True) 285 self .FREQ_6.setVisible(True) 286 self . label_20. setVisible (True) 287 self . label_23. setVisible (True) 288 self .FREQ_7.setVisible(True) 289 self . label_25. setVisible (True) 290 self . label_26. setVisible (True) 291 self .FREQ_8.setVisible(False) 292 self . label_24. setVisible (False) 293 self . label_22. setVisible (False) 294 self . ocultaDistancia () 295 elif (comboText=="8"): 296 self .FREQ_2.setVisible(True) 297 self . label_11. setVisible (True) 298 self . label_12. setVisible (True) 299 self .FREQ_3.setVisible(True) 300 self . label_15. setVisible (True) 301 self . label_17. setVisible (True) 302 self .FREQ_4.setVisible(True) 303 self . label_16. setVisible (True) 304 self . label_18. setVisible (True) 305 self .FREQ_5.setVisible(True)
66 Appendix A. Código fuente 306 self . label_19. setVisible (True) 307 self . label_21. setVisible (True) 308 self .FREQ_6.setVisible(True) 309 self . label_20. setVisible (True) 310 self . label_23. setVisible (True) 311 self .FREQ_7.setVisible(True) 312 self . label_25. setVisible (True) 313 self . label_26. setVisible (True) 314 self .FREQ_8.setVisible(True) 315 self . label_24. setVisible (True) 316 self . label_22. setVisible (True) 317 self . ocultaDistancia () 318 319 def comprobarWarning(self): #se comprueba que ningun parametro introducido esta vacio, a excepcion de las frecuencias 2−3−4−5−6−7−8 320 if ( self .HTT.text()=="" or self .HRR.text()=="" or self.SIGMA.text()=="" 321 or self .EPSLON.text()=="" or self.FREQ.text()==""): 322 parar=True #si hay alguno vacio, se debe parar la ejecucion 323 self .lanzarWarning("Es necesario rellenar todos los campos")#se lanza ventana emergente con error especificado 324 #Se comprueba que todos los valores introducidos estan en formato float , a excepcion de frecuencias 2−3−4−5−6−7−8 325 elif ( self . is_float ( self .HTT.text())==False or self . is_float ( self .HRR.text())==False or 326 self . is_float ( self .SIGMA.text())==False or self . is_float ( self .EPSLON.text())==False or 327 self . is_float ( self .FREQ.text())==False): 328 parar=True 329 self .lanzarWarning("Los parámetros deben ser formato float") 330 331 elif (float ( self .FREQ.text())>30 or float ( self .FREQ.text())<0.01): #se comprueba que la frecuencia 1 esta dentro del rango (10 kHz−30 MHz) 332 self .lanzarWarning("La frecuencia debe estar entre \n 10 KHz y 30 MHz") 333 parar=True 334 else :#si no hay errores, no se para la ejecucion 335 parar=False 336 return parar 337 338 def lanzarWarning(self,texto ): #Usada en ambas pestaas . Muestra ventanas emergentes para notificar errores . 339 self .ventana=QtWidgets.QMainWindow() #crea un QMainWindow 340 self . ui=Ui_popUp() #crea un objeto con el constructor de Ui_popUp 341 self .ui.setupUi( self .ventana) #a ese objeto le aplica la configuracion setupUi, pasandole la ventana a la que se lo aplicará 342 self . ui .labelError. setText ( texto ) #a la etiqueta de error le introduce el texto que se quiere mostrar como error 343 self .ventana.setWindowFlags(QtCore.Qt.WindowCloseButtonHint); #solo el boton de cerrar esta disponible, ni minimizar ni maximizar 344 self .ventana.setWindowIcon(QIcon("warning.png")) #icono de ventana, el de la aplicacion 345 self .ventana.show() #muestra la ventana 346 347 def calculaRango(self , frecuencia) : #Usada en ambas pestaas . Comprueba que el rango de frecuencia contiene a la frecuencia introducida 348 if (float (frecuencia)>30 or float (frecuencia)<0.01): 349 return 1 350 else : 351 return 0 352 353 def compruebaAltura(self,HRR,SIGMA,EPSLON,FREQ): #Usada en ambas pestaas 354 #Comprueba que la altura de antena receptora cumpla dos condiciones. Vease pagina 3 de Rec. P.368−9 de la ITU−R 355 c=300 356 landa=c/FREQ 357 cond1=60∗landa∗SIGMA 358 cond2=1.2∗math.sqrt(SIGMA)∗math.sqrt(landa∗landa∗landa) 359 if (100∗EPSLON<cond1 and HRR>=cond2): 360 return True 361 else : 362 return False 363 364 def is_float ( self ,cadena): #Usada en ambas pestaas 365 try: 366 float (cadena) 367 return True #si ha ido bien la conversion, es float y se devuelve True
67 368 except ValueError: 369 return False #si ha ido mal la conversion, no es float y se devuelve False 370 371 def secuencia( self ): #en funcion de cuantas frecuencias se hayan seleccionado, se obtendran los valores de esas frecuencias y se llamara a Generar datos para grafica 372 #tantas veces como numero de frecuencias se hayan seleccionado. Para que cada curva sea independiente 373 self . clearr () #se limpia grafica anterior , para evitar mezclar resultados y que el usuario no tenga que limpiarla manualmente 374 comboText = self .comboBox_2.currentText() 375 parar=self.comprobarWarning() #comprueba que no existen errores principales de rango de frecuencia, parametros incompletos... 376 if (parar==False): #si todo ha ido bien en la comprobacion anterior 377 if (comboText=="1"): #si el numero de frecuencias es 1 378 self . lineEdit .setReadOnly(False) #activa el calculo de distancia 379 self . lineEdit . setStyleSheet ("background−color: rgb(255, 255, 255);") 380 self . lineEdit_2 . setStyleSheet ("background−color: rgb(255, 255, 255);") 381 self . lineEdit_3 . setStyleSheet ("background−color: rgb(255, 255, 255);") 382 FREQ=self.FREQ.text() #selecciona valor de frecuencia 383 Label1=’f=’+FREQ+’ MHz’ #prepara etiqueta para leyenda de grafica 384 self .generar(FREQ,’#0093FF’,Label1) #se llama a funcion que genera datos para grafica y su representacion, con etiqueta y un color para esta frecuencia 385 elif (comboText=="2"): #si numero de frecuencias es 2 386 if ( self .FREQ_2.text()==""): #comprueba que la freq2 no esta vacia 387 self .lanzarWarning("Es necesario rellenar todos los campos") 388 elif ( self . is_float ( self .FREQ_2.text())==False): #comprueba que es un valor entero o decimal 389 self .lanzarWarning("Los parámetros deben ser formato float") 390 else : 391 if ( self .calculaRango(self .FREQ_2.text())==1): #comprueba su rango 392 self .lanzarWarning("Las frecuencias deben estar entre \n 10 KHz y 30 MHz") 393 else : 394 FREQ=self.FREQ.text() #obtiene valores de frecuencias 1 y 2, con sus etiquetas y colores , y se generan sus curvas en la grafica 395 Label1=’f=’+FREQ+’ MHz’ 396 self .generar(FREQ,’#0093FF’,Label1) 397 FREQ=self.FREQ_2.text() 398 Label1=’f=’+FREQ+’ MHz’ 399 self .generar(FREQ, ’#FFB726’,Label1) 400 elif (comboText=="3"): #se repite todo el proceso recogiendo y comprobando los datos de tres frecuencias 401 if ( self .FREQ_2.text()=="" or self.FREQ_3.text()==""): 402 self .lanzarWarning("Es necesario rellenar todos los campos") 403 elif ( self . is_float ( self .FREQ_2.text())==False or self . is_float ( self .FREQ_3.text())==False): 404 self .lanzarWarning("Los parámetros deben ser formato float") 405 else : 406 if ( self .calculaRango(self .FREQ_2.text())==1 or self .calculaRango(self .FREQ_3.text())==1): 407 self .lanzarWarning("Las frecuencias deben estar entre \n 10 KHz y 30 MHz") 408 else : 409 FREQ=self.FREQ.text() 410 Label1=’f=’+FREQ+’ MHz’ 411 self .generar(FREQ,’#0093FF’,Label1) 412 FREQ=self.FREQ_2.text() 413 Label1=’f=’+FREQ+’ MHz’ 414 self .generar(FREQ, ’#FFB726’,Label1) 415 FREQ=self.FREQ_3.text() 416 Label1=’f=’+FREQ+’ MHz’ 417 self .generar(FREQ,’#60C146’,Label1) 418 elif (comboText=="4"): 419 if ( self .FREQ_2.text()=="" or self.FREQ_3.text()=="" or self.FREQ_4.text()==""): 420 self .lanzarWarning("Es necesario rellenar todos los campos") 421 elif ( self . is_float ( self .FREQ_2.text())==False or self . is_float ( self .FREQ_3.text())==False 422 or self . is_float ( self .FREQ_4.text())==False): 423 self .lanzarWarning("Los parámetros deben ser formato float") 424 else : 425 if ( self .calculaRango(self .FREQ_2.text())==1 or self .calculaRango(self .FREQ_3.text())==1 426 or self .calculaRango(self .FREQ_4.text())==1): 427 self .lanzarWarning("Las frecuencias deben estar entre \n 10 KHz y 30 MHz") 428 else : 429 FREQ=self.FREQ.text() 430 Label1=’f=’+FREQ+’ MHz’ 431 self .generar(FREQ,’#0093FF’,Label1) 432 FREQ=self.FREQ_2.text()
68 Appendix A. Código fuente 433 Label1=’f=’+FREQ+’ MHz’ 434 self .generar(FREQ, ’#FFB726’,Label1) 435 FREQ=self.FREQ_3.text() 436 Label1=’f=’+FREQ+’ MHz’ 437 self .generar(FREQ,’#60C146’,Label1) 438 FREQ=self.FREQ_4.text() 439 Label1=’f=’+FREQ+’ MHz’ 440 self .generar(FREQ,’#BE54C7’,Label1) 441 elif (comboText=="5"): 442 if ( self .FREQ_2.text()=="" or self.FREQ_3.text()=="" or self.FREQ_4.text()=="" 443 or self .FREQ_5.text()==""): 444 self .lanzarWarning("Es necesario rellenar todos los campos") 445 elif ( self . is_float ( self .FREQ_2.text())==False or self . is_float ( self .FREQ_3.text())==False 446 or self . is_float ( self .FREQ_4.text())==False or self . is_float ( self .FREQ_5.text())==False): 447 self .lanzarWarning("Los parámetros deben ser formato float") 448 else : 449 if ( self .calculaRango(self .FREQ_2.text())==1 or self .calculaRango(self .FREQ_3.text())==1 450 or self .calculaRango(self .FREQ_4.text())==1 or self .calculaRango(self .FREQ_5.text())==1): 451 self .lanzarWarning("Las frecuencias deben estar entre \n 10 KHz y 30 MHz") 452 else : 453 FREQ=self.FREQ.text() 454 Label1=’f=’+FREQ+’ MHz’ 455 self .generar(FREQ,’#0093FF’,Label1) 456 FREQ=self.FREQ_2.text() 457 Label1=’f=’+FREQ+’ MHz’ 458 self .generar(FREQ, ’#FFB726’,Label1) 459 FREQ=self.FREQ_3.text() 460 Label1=’f=’+FREQ+’ MHz’ 461 self .generar(FREQ,’#60C146’,Label1) 462 FREQ=self.FREQ_4.text() 463 Label1=’f=’+FREQ+’ MHz’ 464 self .generar(FREQ,’#BE54C7’,Label1) 465 FREQ=self.FREQ_5.text() 466 Label1=’f=’+FREQ+’ MHz’ 467 self .generar(FREQ,’#50DCA5’,Label1) 468 elif (comboText=="6"): 469 if ( self .FREQ_2.text()=="" or self.FREQ_3.text()=="" or self.FREQ_4.text()=="" 470 or self .FREQ_5.text()=="" or self.FREQ_6.text()==""): 471 self .lanzarWarning("Es necesario rellenar todos los campos") 472 elif ( self . is_float ( self .FREQ_2.text())==False or self . is_float ( self .FREQ_3.text())==False 473 or self . is_float ( self .FREQ_4.text())==False or self . is_float ( self .FREQ_5.text())==False 474 or self . is_float ( self .FREQ_6.text())==False): 475 self .lanzarWarning("Los parámetros deben ser formato float") 476 else : 477 if ( self .calculaRango(self .FREQ_2.text())==1 or self .calculaRango(self .FREQ_3.text())==1 478 or self .calculaRango(self .FREQ_4.text())==1 or self .calculaRango(self .FREQ_5.text())==1 479 or self .calculaRango(self .FREQ_6.text())==1): 480 self .lanzarWarning("Las frecuencias deben estar entre \n 10 KHz y 30 MHz") 481 else : 482 FREQ=self.FREQ.text() 483 Label1=’f=’+FREQ+’ MHz’ 484 self .generar(FREQ,’#0093FF’,Label1) 485 FREQ=self.FREQ_2.text() 486 Label1=’f=’+FREQ+’ MHz’ 487 self .generar(FREQ, ’#FFB726’,Label1) 488 FREQ=self.FREQ_3.text() 489 Label1=’f=’+FREQ+’ MHz’ 490 self .generar(FREQ,’#60C146’,Label1) 491 FREQ=self.FREQ_4.text() 492 Label1=’f=’+FREQ+’ MHz’ 493 self .generar(FREQ,’#BE54C7’,Label1) 494 FREQ=self.FREQ_5.text() 495 Label1=’f=’+FREQ+’ MHz’ 496 self .generar(FREQ,’#50DCA5’,Label1) 497 FREQ=self.FREQ_6.text() 498 Label1=’f=’+FREQ+’ MHz’ 499 self .generar(FREQ,’#808080’,Label1) 500 elif (comboText=="7"): 501 if ( self .FREQ_2.text()=="" or self.FREQ_3.text()=="" or self.FREQ_4.text()=="" 502 or self .FREQ_5.text()=="" or self.FREQ_6.text()=="" or self.FREQ_7.text()==""): 503 self .lanzarWarning("Es necesario rellenar todos los campos")
69 504 else : 505 if ( self .calculaRango(self .FREQ_2.text())==1 or self .calculaRango(self .FREQ_3.text())==1 506 or self .calculaRango(self .FREQ_4.text())==1 or self .calculaRango(self .FREQ_5.text())==1 507 or self .calculaRango(self .FREQ_6.text())==1 or self .calculaRango(self .FREQ_7.text())==1): 508 self .lanzarWarning("Las frecuencias deben estar entre \n 10 KHz y 30 MHz") 509 elif ( self . is_float ( self .FREQ_2.text())==False or self . is_float ( self .FREQ_3.text())==False 510 or self . is_float ( self .FREQ_4.text())==False or self . is_float ( self .FREQ_5.text())==False 511 or self . is_float ( self .FREQ_6.text())==False or self . is_float ( self .FREQ_7.text())==False) : 512 self .lanzarWarning("Los parámetros deben ser formato float") 513 else : 514 FREQ=self.FREQ.text() 515 Label1=’f=’+FREQ+’ MHz’ 516 self .generar(FREQ,’#0093FF’,Label1) 517 FREQ=self.FREQ_2.text() 518 Label1=’f=’+FREQ+’ MHz’ 519 self .generar(FREQ, ’#FFB726’,Label1) 520 FREQ=self.FREQ_3.text() 521 Label1=’f=’+FREQ+’ MHz’ 522 self .generar(FREQ,’#60C146’,Label1) 523 FREQ=self.FREQ_4.text() 524 Label1=’f=’+FREQ+’ MHz’ 525 self .generar(FREQ,’#BE54C7’,Label1) 526 FREQ=self.FREQ_5.text() 527 Label1=’f=’+FREQ+’ MHz’ 528 self .generar(FREQ,’#50DCA5’,Label1) 529 FREQ=self.FREQ_6.text() 530 Label1=’f=’+FREQ+’ MHz’ 531 self .generar(FREQ,’#808080’,Label1) 532 FREQ=self.FREQ_7.text() 533 Label1=’f=’+FREQ+’ MHz’ 534 self .generar(FREQ,’#7C1A2F’,Label1) 535 elif (comboText=="8"): 536 if ( self .FREQ_2.text()=="" or self.FREQ_3.text()=="" or self.FREQ_4.text()=="" 537 or self .FREQ_5.text()=="" or self.FREQ_6.text()=="" 538 or self .FREQ_7.text()=="" or self.FREQ_8.text()==""): 539 self .lanzarWarning("Es necesario rellenar todos los campos") 540 elif ( self . is_float ( self .FREQ_2.text())==False or self . is_float ( self .FREQ_3.text())==False 541 or self . is_float ( self .FREQ_4.text())==False or self . is_float ( self .FREQ_5.text())==False 542 or self . is_float ( self .FREQ_6.text())==False or self . is_float ( self .FREQ_7.text())==False 543 or self . is_float ( self .FREQ_8.text())==False): 544 self .lanzarWarning("Los parámetros deben ser formato float") 545 else : 546 if ( self .calculaRango(self .FREQ_2.text())==1 or self .calculaRango(self .FREQ_3.text())==1 547 or self .calculaRango(self .FREQ_4.text())==1 or self .calculaRango(self .FREQ_5.text())==1 548 or self .calculaRango(self .FREQ_6.text())==1 or self .calculaRango(self .FREQ_7.text())==1 549 or self .calculaRango(self .FREQ_8.text())==1): 550 self .lanzarWarning("Las frecuencias deben estar entre \n 10 KHz y 30 MHz") 551 else : 552 FREQ=self.FREQ.text() 553 Label1=’f=’+FREQ+’ MHz’ 554 self .generar(FREQ,’#0093FF’,Label1) 555 FREQ=self.FREQ_2.text() 556 Label1=’f=’+FREQ+’ MHz’ 557 self .generar(FREQ, ’#FFB726’,Label1) 558 FREQ=self.FREQ_3.text() 559 Label1=’f=’+FREQ+’ MHz’ 560 self .generar(FREQ,’#60C146’,Label1) 561 FREQ=self.FREQ_4.text() 562 Label1=’f=’+FREQ+’ MHz’ 563 self .generar(FREQ,’#BE54C7’,Label1) 564 FREQ=self.FREQ_5.text() 565 Label1=’f=’+FREQ+’ MHz’ 566 self .generar(FREQ,’#50DCA5’,Label1) 567 FREQ=self.FREQ_6.text() 568 Label1=’f=’+FREQ+’ MHz’ 569 self .generar(FREQ,’#808080’,Label1) 570 FREQ=self.FREQ_7.text() 571 Label1=’f=’+FREQ+’ MHz’ 572 self .generar(FREQ,’#7C1A2F’,Label1) 573 FREQ=self.FREQ_8.text()
70 Appendix A. Código fuente 574 Label1=’f=’+FREQ+’ MHz’ 575 self .generar(FREQ,’#FF4711’,Label1) 576 577 def generar( self ,FREQ,color,label): 578 global ultimo 579 HTT=self.HTT.text() #recoge textos de los parametros introducidos 580 HRR=self.HRR.text() 581 SIGMA=self.SIGMA.text() 582 EPSLON=self.EPSLON.text() 583 if ( self .compruebaAltura(float(HRR), float(SIGMA),float(EPSLON), float(FREQ)) == True): #comprueba que la altura receptora cumple una restriccion de la ITU−R P.368 584 self .lanzarWarning("Cuando 60 , debe elegir altura \n antena receptora tal que h< \n Fallo para frecuencia:"+FREQ+" MHz") 585 else : 586 if self .Horizontal.isChecked(): 587 IPOLRN=’2’ #Para GRWAVE, 2 es horizontal y 1 es vertical en el parametro IPOLRN 588 elif self . Vertical .isChecked(): 589 IPOLRN=’1’ 590 #Se itera durante 4 veces. Cada curva será consecuencia de 4 llamadas a GRWAVE. Para que la escala logaritmica del eje X, segmentada en 4 divisiones , tenga en cada 591 #una de ellas los mismos puntos (100), se ha hecho eso. Cada uno tiene un dstep distinto para asegurar que hay 100 puntos en cada division . 592 for iin [0, 1, 2, 3]: 593 if i==0: 594 dmin=’1’ 595 dmax=’10’ 596 dstep=’0.1’ 597 inicial =1; #marca si en el fichero de salida se debe empezar a escribir desde cero (por ser la primera vez), o continuando al final del fichero sin borrar lo ya escrito 598 elif i==1: 599 dmin=’10’ 600 dmax=’100’ 601 dstep=’1’ 602 inicial =0; 603 elif i==2: 604 dmin=’100’ 605 dmax=’1000’ 606 dstep=’10’ 607 inicial =0; 608 elif i==3: 609 dmin=’1000’ 610 dmax=’10000’ 611 dstep=’100’ 612 inicial =0; 613 self .rellenarEntrada(HTT,HRR,FREQ,SIGMA,EPSLON,IPOLRN,dmin,dmax,dstep) #se rellena fichero de entrada para GRWAVE, entrada.dat 614 seguir=self .empezar(inicial ) #realiza llamada a grwave y recoge datos de salida . Variable SEGUIR indica si ha habido fallo en la seleccion del entorno Windows o MSDOS. 615 if (seguir==True): #no ha habido fallo ejecutando GRWAVE 616 d_km, intensidad,perdidas=leerGRW2("grwave2.out",2) #el numero 2 viene explicado en dicha funcion, solo elimina un fallo que se contemplaba para esta funcion 617 ultimo=d_km[−1] #coge el ultimo valor que podrá usarse para calcular la distancia , metiendolo en una variable global 618 self .update_graph(d_km,intensidad,color,label) #se llama a la funcion que genera la grafica 619 620 def rellenarEntrada( self ,HTT,HRR,FREQ,SIGMA,EPSLON,IPOLRN,dmin,dmax,dstep): #Usada en ambas pestaas. Rellena fichero de entrada.dat para GRWAVE 621 fentrada = open(’entrada.dat’ ,’w’) 622 fentrada.write(’HTT ’+HTT+’\n’+’HRR ’+HRR+’\n’+’FREQ ’+FREQ+’\n’+’SIGMA ’ 623 +SIGMA+’\n’+’EPSLON ’+EPSLON+’\n’+’IPOLRN ’+IPOLRN+’\n’+’dmin ’+dmin+’\n’+ 624 ’dmax ’+dmax+’\n’+’dstep ’+dstep+’\n’+’GO’+’\n’+’STOP’+’\n’) 625 fentrada. close () 626 627 def empezar(self, inicial ) : #Usada en ambas pestaas 628 seguir=ejecucion( self ) #hace llamada a GRWAVE 629 lecturaSalida ( self , inicial ) #procesa los datos de salida 630 return seguir # notifica si ha habido fallo en llamada a GRWAVE por seleccion de entorno equivocado 631 632 def update_graph(self, distancia , intensidad, colorGrafica, etiqueta ) : #Dibuja gráfica terreno homogéneo 633 self .MplWidget.canvas.axes. set_title (’Curva de propagación’,fontsize=10) #Titulo grafica 634 self .MplWidget.canvas.axes.set_xlabel(’Distancia [km]’, fontsize =8) #Etiqueta eje X
71 635 self .MplWidget.canvas.axes.set_ylabel(’Intensidad de campo [dB(V /m)]’, fontsize =8) #Etiqueta eje Y 636 self .MplWidget.canvas.axes.set_ylim(−20,120) #Limite superior e inferior de eje Y 637 self .MplWidget.canvas.axes.set_xlim(1,10000) #Limite superior e inferior de eje X 638 self .MplWidget.canvas.axes.grid(True,which="both",linewidth=0.5) #Activar cuadrícula en ambos ejes con anchura 0.5 639 #se introduce en la grafica la curva con valores de distancia e intensidad para cada eje . Las etiquetas y color 640 #se pasan por parámetros para diferenciar una curva de otra en la misma grafica 641 self .MplWidget.canvas.axes.plot(distancia , intensidad , label=etiqueta , color=colorGrafica,linewidth=1) 642 self .MplWidget.canvas.axes.legend(loc=’upper right’,prop={’size’: 7}) #leyenda situada arriba a la derecha, con tamao de fuente 7. 643 self .MplWidget.canvas.axes.semilogx(distancia,intensidad , color=colorGrafica) #aplica escala logaritmica a eje X. Se vuelve a pasar datos de ejes X e Y, y el color . 644 self .MplWidget.canvas.draw() #Se dibuja en la gráfica , es decir, se aplican y muestran los cambios. 645 646 def saveFig1( self ): #Abre QDialog para guardar archivo seleccionando carpeta, en las extensiones especificadas . 647 fileName = QFileDialog.getSaveFileName(self, 648 self . tr("Exportar gráfica"), 649 "", self . tr ("PNG (∗.png);;PDF (∗.pdf);;EPS (∗.eps);;RAW (∗.raw);;PGF (∗.pgf);; PS (∗.ps);;RAW (∗.raw );;" 650 "RGBA (∗.rgba);;SVG (∗.svg);;SVGZ (∗.svgz)"))[0] 651 if fileName: #si se ha seleccionado el boton Guardar, fileName estara relleno y contiene el nombre del archivo con la extension 652 self .MplWidget.canvas.figure.savefig (fileName, dpi=300, quality=80, optimize=True, progressive=True) 653 654 def calculaDistancia ( self ) : #calculara el valor de intensidad para distancia introducida una vez generada grafica 655 global ultimo #se coge la variable ultimo haciendola global , para usarla en otra funcion 656 distancia=self . lineEdit . text () #se toma el texto de la distancia 657 if ( distancia==""): # se comprueba que no se ha dejado vacio una vez pulsado Calcular, sino, error 658 self .lanzarWarning("Es necesario especificar una distancia") 659 elif (float ( distancia )>ultimo): #se comprueba que la distancia no supera el rango maximo de distancia que se puede obtener 660 self .lanzarWarning("Distancia fuera de rango. \n Valor máximo: "+str(ultimo)) 661 else : 662 dmin=distancia #pone el valor que queremos calcular como dmin, que se metera en entrada.dat 663 distancia2=float ( distancia ) #lo convierte a float para realizarle una suma 664 dmax=distancia2+1 665 dmax2=str(dmax) #distancia+1 se convierte a cadena para introducirse en entrada.dat, ya que en ficheros solo se pueden escribir strings . 666 dstep="1" #el salto será solo de 1, es decir, de dmin a dmax solo habra 1 paso, solo 2 valores 667 HTT=self.HTT.text() 668 HRR=self.HRR.text() 669 FREQ=self.FREQ.text() 670 SIGMA=self.SIGMA.text() 671 EPSLON=self.EPSLON.text() 672 if self .Horizontal.isChecked(): #comprueba si esta marcada la polarizacion horizontal 673 IPOLRN=’2’ #el valor 2 es porque GRWAVE entiende 2 como horizontal y 1 como vertical 674 elif self . Vertical .isChecked(): #comprueba si es polarizacion vertical 675 IPOLRN=’1’ 676 self .rellenarEntrada(HTT,HRR,FREQ,SIGMA,EPSLON,IPOLRN,dmin,dmax2,dstep) #rellena fichero entrada.dat 677 seguir=self .empezar(1) #llama a funcion empezar, que ejecutará llamada a GRWAVE y recogerá datos de salida 678 if (seguir==True): #no ha habido fallo ejecutando GRWAVE 679 d_km, intensidad,perdidas=leerGRW2("grwave2.out",1) #se recogen los valores de grwave2.out (solo habra 2 pares) 680 if (d_km.size==0): #comprobar que no esta vacio, es decir , que no se ha metido un valor demasiado alto o nulo 681 self .lanzarWarning("Distancia fuera de rango") 682 self . lineEdit_2 . setText("")#se ponen los campos de E y Lb vacios, para evitar confusiones. 683 self . lineEdit_3 . setText("") 684 685 else : 686 self . lineEdit_2 . setText( str (intensidad.item(0)) ) #si todo esta bien, se mete en E y Lb el primer par de valores correspondientes a dmin, que es el valor que queremos 687 self . lineEdit_3 . setText( str (perdidas.item(0))) 688 689 def clearr( self ): #limpia la gráfica homogenea, pero vuelve a asignar valores de configuración de gráfica vací a
72 Appendix A. Código fuente 690 self .MplWidget.canvas.axes.clear() 691 self .MplWidget.canvas.axes. set_title (’Curva de propagación’,fontsize=10) 692 self .MplWidget.canvas.axes.set_xlabel(’Distancia [km]’, fontsize =8) 693 self .MplWidget.canvas.axes.set_ylabel(’Intensidad de campo [dB(V /m)]’, fontsize =8) 694 self .MplWidget.canvas.axes.set_ylim(−20,120) 695 self .MplWidget.canvas.axes.set_xlim(1,10000) 696 self .MplWidget.canvas.axes.grid(True,which="both",linewidth=0.5) 697 self .MplWidget.canvas.axes.semilogx() 698 self .MplWidget.canvas.draw() 699 self . ocultaDistancia () 700 701 def ocultaDistancia ( self ): #cuando se limpia una grafica , o se selecciona mas de una frecuencia, se desactivan las casillas para calcular la distancia 702 self . lineEdit .setReadOnly(True) #Distancia en modo solo lectura , para no admitir que se pueda escribir . 703 self . lineEdit . setStyleSheet ("background−color: rgb(235, 235, 235);") 704 self . lineEdit_2 . setStyleSheet ("background−color: rgb(235, 235, 235);") 705 self . lineEdit_3 . setStyleSheet ("background−color: rgb(235, 235, 235);") 706 self . lineEdit . setText ("") 707 self . lineEdit_2 . setText("") 708 self . lineEdit_3 . setText("") 709 710 ################################################################################## 711 ################################################################################## 712 ################################################################################## 713 ################################################################################## 714 # FUNCIONES TERRENO MIXTO 715 716 def informacion(self ): #Abre informacion con la tabla de caracteristicas de terrenos 717 self .ventanaInfo=QtWidgets.QMainWindow() 718 self .ui=Ui_Info() 719 self .ui.setupUi( self .ventanaInfo) 720 self .ventanaInfo.setWindowFlags(QtCore.Qt.WindowCloseButtonHint); 721 self .ventanaInfo.setWindowIcon(QIcon("iconn.png")) 722 self .ventanaInfo.show() 723 724 def comprobarWarning_2(self): 725 #Se comprueba que ningun parametro introducido esta vacio, a excepcion de los terrenos 3 y 4 que se hara 726 #mas adelante 727 if ( self .HTT_2.text()=="" or self.HRR_2.text()=="" or self.Sigma1.text()=="" 728 or self .Epsilon1. text () =="" or self .Sigma2.text()=="" 729 or self .Epsilon2. text () =="" or self .Long1.text()=="" 730 or self .Long2.text()=="" or self .FREQmixto.text()==""): 731 parar=True #si hay alguno vacio, se debe parar la ejecucion 732 self .lanzarWarning("Es necesario rellenar todos los campos")#se lanza ventana emergente con error especificado 733 734 #Se comprueba que todos los valores introducidos estan en formato float , a excepcion de terrenos 3 y 4 735 elif ( self . is_float ( self .HTT_2.text())==False or self . is_float ( self .HRR_2.text())==False or 736 self . is_float ( self .Sigma2.text() )==False or self . is_float ( self .Epsilon1. text () )==False or 737 self . is_float ( self .Sigma1.text() )==False or self . is_float ( self .Long1.text() )==False or 738 self . is_float ( self .Epsilon2. text () )==False or self . is_float ( self .Long2.text() )==False or 739 self . is_float ( self .FREQmixto.text())==False): 740 parar=True 741 self .lanzarWarning("Los parámetros deben ser formato float") 742 743 #Se comprueba que la frecuencia esta entre 10 kHz y 30 MHz 744 elif (float ( self .FREQmixto.text())>30 or float( self .FREQmixto.text())<0.01): #se comprueba que la frecuencia esta dentro del rango (10 kHz−30 MHz) 745 self .lanzarWarning("La frecuencia debe estar entre \n 10 KHz y 30 MHz") 746 parar=True 747 else :#si no hay errores, no se para la ejecucion 748 parar=False 749 return parar 750 751 def inhabilitaSigEps1( self ): #cuando se ha seleccionado un terreno predeterminado, las casillas de sigma y epsilon se configuran para solo lectura 752 self .Sigma1.setReadOnly(True) 753 self .Epsilon1.setReadOnly(True) 754 self .Sigma1.setStyleSheet("background−color: rgb(235, 235, 235);") 755 self .Epsilon1. setStyleSheet ("background−color: rgb(235, 235, 235);") 756
73 757 def inhabilitaSigEps2( self ): #igual , pero para el terreno 2 758 self .Sigma2.setReadOnly(True) 759 self .Epsilon2.setReadOnly(True) 760 self .Sigma2.setStyleSheet("background−color: rgb(235, 235, 235);") 761 self .Epsilon2. setStyleSheet ("background−color: rgb(235, 235, 235);") 762 763 def inhabilitaSigEps3( self ): #igual para terreno 3 764 self .Sigma3.setReadOnly(True) 765 self .Epsilon3.setReadOnly(True) 766 self .Sigma3.setStyleSheet("background−color: rgb(235, 235, 235);") 767 self .Epsilon3. setStyleSheet ("background−color: rgb(235, 235, 235);") 768 769 def inhabilitaSigEps4( self ): #igual para terreno 4 770 self .Sigma4.setReadOnly(True) 771 self .Epsilon4.setReadOnly(True) 772 self .Sigma4.setStyleSheet("background−color: rgb(235, 235, 235);") 773 self .Epsilon4. setStyleSheet ("background−color: rgb(235, 235, 235);") 774 775 def comprobarTerrenos(self): #numero de terrenos, desplegable 1 2 3 4 776 comboText = self .comboBox_3.currentText() 777 if (comboText=="2"): #si el numero de terrenos seleccionados es 2, oculta los parametros y etiquetas de los bloques de terrenos 3 y 4 778 self . label_50. setVisible (False) #Correspondiente al bloque 3 779 self . label_51. setVisible (False) 780 self . label_52. setVisible (False) 781 self .comboBox_6.setVisible(False) 782 self .Epsilon3. setVisible (False) 783 self .Sigma3.setVisible(False) 784 self .Long3.setVisible(False) 785 self . label_53. setVisible (False) #Correspondiente al bloque 4 786 self . label_54. setVisible (False) 787 self . label_55. setVisible (False) 788 self .comboBox_7.setVisible(False) 789 self .Epsilon4. setVisible (False) 790 self .Sigma4.setVisible(False) 791 self .Long4.setVisible(False) 792 self . line . setVisible (False) 793 elif (comboText=="3"): 794 self . label_50. setVisible (True) #Correspondiente al bloque 3 795 self . label_51. setVisible (True) 796 self . label_52. setVisible (True) 797 self .comboBox_6.setVisible(True) 798 self .Epsilon3. setVisible (True) 799 self .Sigma3.setVisible(True) 800 self .Long3.setVisible(True) 801 self . label_53. setVisible (False) #Correspondiente al bloque 4 802 self . label_54. setVisible (False) 803 self . label_55. setVisible (False) 804 self .comboBox_7.setVisible(False) 805 self .Epsilon4. setVisible (False) 806 self .Sigma4.setVisible(False) 807 self .Long4.setVisible(False) 808 self . line . setVisible (True) 809 elif (comboText=="4"): 810 self . label_50. setVisible (True) #Correspondiente al bloque 3 811 self . label_51. setVisible (True) 812 self . label_52. setVisible (True) 813 self .comboBox_6.setVisible(True) 814 self .Epsilon3. setVisible (True) 815 self .Sigma3.setVisible(True) 816 self .Long3.setVisible(True) 817 self . label_53. setVisible (True) #Correspondiente al bloque 4 818 self . label_54. setVisible (True) 819 self . label_55. setVisible (True) 820 self .comboBox_7.setVisible(True) 821 self .Epsilon4. setVisible (True) 822 self .Sigma4.setVisible(True) 823 self .Long4.setVisible(True) 824 self . line . setVisible (True) 825
80 Appendix A. Código fuente 1235 self .MplWidget_2.canvas.axes.plot(distancia, intensidad , label=etiqueta , color=colorGrafica) 1236 self .MplWidget_2.canvas.axes.legend(loc=’upper right’,prop={’size’: 8})#leyenda situada arriba a la derecha, con tamao de fuente 7. 1237 self .MplWidget_2.canvas.axes.semilogx(distancia,intensidad, color=colorGrafica)#aplica escala logaritmica a eje X. Se vuelve a pasar datos de ejes X e Y, y el color . 1238 self .MplWidget_2.canvas.draw() #Se dibuja en la gráfica , es decir, se aplican y muestran los cambios. 1239 1240 def clearr_2( self ): #limpia la gráfica mixta, pero vuelve a asignar valores de configuración de gráfica vacía 1241 self .MplWidget_2.canvas.axes.clear() 1242 self .MplWidget_2.canvas.axes. set_title ( ’Curva de propagación’,fontsize=10) 1243 self .MplWidget_2.canvas.axes.set_xlabel(’Distancia [km]’, fontsize =8) 1244 self .MplWidget_2.canvas.axes.set_ylabel(’Intensidad de campo [dB(V /m)]’, fontsize =8) 1245 self .MplWidget_2.canvas.axes.set_ylim(−20,120) 1246 self .MplWidget_2.canvas.axes.set_xlim(1,10000) 1247 self .MplWidget_2.canvas.axes.grid(True,which="both",linewidth=0.5) 1248 self .MplWidget_2.canvas.axes.semilogx() 1249 self .MplWidget_2.canvas.draw() 1250 self . lineEdit_4 . setText("") 1251 1252 def saveFig2( self ): #Abre QDialog para guardar archivo seleccionando carpeta, en las extensiones especificadas . 1253 fileName = QFileDialog.getSaveFileName(self, 1254 self . tr("Exportar gráfica"), 1255 "", self . tr ("PNG (∗.png);;PDF (∗.pdf);;EPS (∗.eps);;RAW (∗.raw);;PGF (∗.pgf);; PS (∗.ps);;RAW (∗.raw );;" 1256 "RGBA (∗.rgba);;SVG (∗.svg);;SVGZ (∗.svgz)"))[0] 1257 if fileName: #si se ha seleccionado el boton Guardar, fileName estara relleno y contiene el nombre del archivo con la extension 1258 self .MplWidget_2.canvas.figure.savefig(fileName, dpi=300, quality=80, optimize=True, progressive=True ) 1259 1260 def calculaDistancia11( self , distancia ): #calcula intensidad de campo electrico segun metodo millington con datos del terreno 1, a la distancia que se le marque por parametro 1261 if (float ( distancia )>10000): #La grafica y GRWAVE solo llega a 10000, si es mayor se descarta 1262 self .lanzarWarning(’Suma de distancias fuera de rango’+’\n’+’Imposible calcular E’) 1263 self . lineEdit_4 . setText("")#Se rellena vacio el resultado del campo, para borrar el resultado anterior 1264 else :#Se llama a GRWAVE pero solo usando el terreno 1 1265 dmin=distancia 1266 distancia2=float ( distancia ) 1267 dmax=distancia2+1 1268 dmax2=str(dmax) 1269 dstep="1" 1270 HTT=self.HTT_2.text() 1271 HRR=self.HRR_2.text() 1272 FREQ=self.FREQmixto.text() 1273 SIGMA=self.Sigma1.text() 1274 EPSLON=self.Epsilon1.text() 1275 if self .Horizontal_2.isChecked(): 1276 IPOLRN=’2’ 1277 elif self . Vertical_2 .isChecked(): 1278 IPOLRN=’1’ 1279 self .rellenarEntrada(HTT,HRR,FREQ,SIGMA,EPSLON,IPOLRN,dmin,dmax2,dstep) 1280 seguir=self .empezar(1) 1281 if (seguir==True): #no ha habido fallo ejecutando GRWAVE 1282 d_km, intensidad,perdidas=leerGRW2("grwave2.out",1) 1283 if (d_km.size==0): #Solo debe devolver un valor el del campo para esa distancia. Si su tamao es 0, error 1284 self .lanzarWarning(’Suma de distancias fuera de rango’+’\n’+’Imposible calcular E’) 1285 self . lineEdit_4 . setText("") 1286 else : 1287 return intensidad.item(0) 1288 1289 def calculaDistancia22( self , distancia ): #calcula intensidad de campo electrico segun metodo millington con datos del terreno 2, a la distancia que se le marque por parametro 1290 if (float ( distancia )>10000): 1291 self .lanzarWarning(’Suma de distancias fuera de rango’+’\n’+’Imposible calcular E’) 1292 self . lineEdit_4 . setText("") 1293 else :#Se llama a GRWAVE pero solo usando el terreno 2 1294 dmin=distancia 1295 distancia2=float ( distancia )
81 1296 dmax=distancia2+1 1297 dmax2=str(dmax) 1298 dstep="1" 1299 HTT=self.HTT_2.text() 1300 HRR=self.HRR_2.text() 1301 FREQ=self.FREQmixto.text() 1302 SIGMA=self.Sigma2.text() 1303 EPSLON=self.Epsilon2.text() 1304 if self .Horizontal_2.isChecked(): 1305 IPOLRN=’2’ 1306 elif self . Vertical_2 .isChecked(): 1307 IPOLRN=’1’ 1308 self .rellenarEntrada(HTT,HRR,FREQ,SIGMA,EPSLON,IPOLRN,dmin,dmax2,dstep) 1309 seguir=self .empezar(1) 1310 if (seguir==True): #no ha habido fallo ejecutando GRWAVE 1311 d_km, intensidad,perdidas=leerGRW2("grwave2.out",1) 1312 if (d_km.size==0): #Solo debe devolver un valor el del campo para esa distancia. Si su tamao es 0, error 1313 self .lanzarWarning(’Suma de distancias fuera de rango’+’\n’+’Imposible calcular E’) 1314 self . lineEdit_4 . setText("") 1315 else : 1316 return intensidad.item(0) 1317 1318 def calculaDistancia33( self , distancia ): #para datos del terreno 3 1319 if (float ( distancia )>10000): 1320 self .lanzarWarning(’Suma de distancias fuera de rango’+’\n’+’Imposible calcular E’) 1321 self . lineEdit_4 . setText("") 1322 else :#Se llama a GRWAVE pero solo usando el terreno 3 1323 dmin=distancia 1324 distancia2=float ( distancia ) 1325 dmax=distancia2+1 1326 dmax2=str(dmax) 1327 dstep="1" 1328 HTT=self.HTT_2.text() 1329 HRR=self.HRR_2.text() 1330 FREQ=self.FREQmixto.text() 1331 SIGMA=self.Sigma3.text() 1332 EPSLON=self.Epsilon3.text() 1333 if self .Horizontal_2.isChecked(): 1334 IPOLRN=’2’ 1335 elif self . Vertical_2 .isChecked(): 1336 IPOLRN=’1’ 1337 self .rellenarEntrada(HTT,HRR,FREQ,SIGMA,EPSLON,IPOLRN,dmin,dmax2,dstep) 1338 seguir=self .empezar(1) 1339 if (seguir==True): #no ha habido fallo ejecutando GRWAVE 1340 d_km, intensidad,perdidas=leerGRW2("grwave2.out",1) 1341 if (d_km.size==0): #Solo debe devolver un valor el del campo para esa distancia. Si su tamao es 0, error 1342 self .lanzarWarning(’Suma de distancias fuera de rango’+’\n’+’Imposible calcular E’) 1343 self . lineEdit_4 . setText("") 1344 else : 1345 return intensidad.item(0) 1346 1347 def calculaDistancia44( self , distancia ): #para datos del terreno 4 1348 if (float ( distancia )>10000): 1349 self .lanzarWarning(’Suma de distancias fuera de rango’+’\n’+’Imposible calcular E’) 1350 self . lineEdit_4 . setText("") 1351 else :#Se llama a GRWAVE pero solo usando el terreno 4 1352 dmin=distancia 1353 distancia2=float ( distancia ) 1354 dmax=distancia2+1 1355 dmax2=str(dmax) 1356 dstep="1" 1357 HTT=self.HTT_2.text() 1358 HRR=self.HRR_2.text() 1359 FREQ=self.FREQmixto.text() 1360 SIGMA=self.Sigma4.text() 1361 EPSLON=self.Epsilon4.text() 1362 if self .Horizontal_2.isChecked(): 1363 IPOLRN=’2’ 1364 elif self . Vertical_2 .isChecked():
82 Apéndice A. Código fuente 1365 IPOLRN=’1’ 1366 self .rellenarEntrada(HTT,HRR,FREQ,SIGMA,EPSLON,IPOLRN,dmin,dmax2,dstep) 1367 seguir=self .empezar(1) 1368 if (seguir==True): #no ha habido fallo ejecutando GRWAVE 1369 d_km, intensidad,perdidas=leerGRW2("grwave2.out",1) 1370 if (d_km.size==0): #Solo debe devolver un valor el del campo para esa distancia. Si su tamao es 0, error 1371 self .lanzarWarning(’Suma de distancias fuera de rango’+’\n’+’Imposible calcular E’) 1372 self . lineEdit_4 . setText("") 1373 else : 1374 return intensidad.item(0) 1375 1376 if __name__ == "__main__": 1377 app = QtWidgets.QApplication([]) 1378 window = MainWindow() 1379 window.show() 1380 app.setWindowIcon(QtGui.QIcon(’iconn.ico’)) 1381 window.setWindowIcon(QtGui.QIcon(’iconn.ico’)) 1382 app.exec_() .
Índice de Figuras 2.1 Transmisión de onda superficial y onda de cielo [8] 4 2.2 Vector del frente de ondas en transmisión de onda de superficie 5 2.3 Propagación con reflexión sobre tierra plana 5 2.4 Ejemplo de curva de la Rec. P.368-9 para terreno homogéneo agua de mar (salinidad baja) 7 2.5 Pérdidas de propagación en función de epsilon, con σ=0.001 y distancia=10 m [17] 8 2.6 Pérdidas de propagación en función de sigma, con εr=4.0 y distancia=10 m [17] 8 2.7 Ejemplo de curva de la Rec. P.368-9 para terreno mixto con distintos valores de σyεr10 2.8 Curva resultante tras aplicar Método Millington para dos terrenos 11 3.1 Ejemplo de fichero de entrada para GRWAVE 14 3.2 Ejemplo de fichero de salida para GRWAVE 14 3.3 Interfaz inicial DOSBox 15 3.4 Ejecución de GRWAVE en DOSBox 16 3.5 Interfaz de Qt Designer para la construcción de ventanas de la GUI 18 4.1 Ventana de Bienvenida de GroundWave Simulator 19 4.2 Guía sobre uso e instalación del simulador 20 4.3 Botón deslizante de la interfaz principal 20 4.4 QTabWidget Terreno mixto seleccionado 21 4.5 Ventana para los cálculos de un Terreno homogéneo 21 4.6 Desplegable ComboBox para la selección de terreno 22 4.7 Ventana de ayuda para selección de terreno 22 4.8 Desplegable QComboBox para la selección de número de frecuencias 23 4.9 Ejemplo de ejecución de gráfica para terreno homogéneo 23 4.10 Gama de colores para la generación de curvas expresados en hexadecimal 24 4.11 Ventana selector de carpetas para exportar gráfica 25 4.12 Ejemplo de cálculo de intensidad de campo eléctrico para terreno homogéneo 25 4.13 Ventana para los cálculos de un Terreno mixto. 26 4.14 Desplegable ComboBox para la selección del número de terrenos 26 4.15 Ejemplo de producción de gráfica para terreno mixto. 27 4.16 Ventana de error generado por falta de parámetros 28 4.17 Ventana de error generado por frecuencia fuera de rango 28 4.18 Ventana de error producido por incumplir restricciones de altura de antena receptora 28 4.19 Ventana de error producido por distancia fuera de rango 29 4.20 Ventana de error producido por suma de longitudes fuera de rango 30 5.1 Análisis de calidad del código del simulador con SonarQube 31 5.2 Informe de análisis desglosado de SonarQube 32 5.3 Curva de propagación por GroundWave Simulator en tierra húmeda (σ=0.01, εr=30) 32 5.4 Curva de propagación ITU-R de onda de superficie en tierra húmeda (σ=0.01, εr=30) 33 83
84 Índice de Figuras 5.5 Comparación de intensidad de campo eléctrico según la altura de antenas para agua dulce y distancia de 50 km 33 5.6 Curva de propagación en tierra húmeda (σ=0.01, εr=30) variando polarización y frecuencia 34 5.7 Escenario de transmisión de dos terrenos 35 5.8 Escenarios de transmisión de tres terrenos 35
Índice de Tablas 5.1 Comparación de intensidad según la polarización para Tierra húmeda (σ=0.01 S/m, εr=30) 34 5.2 Comparación de intensidad recibida en Método Millington según el orden de dos terrenos con distinta longitud y f=1 MHz 35 5.3 Comparación de intensidad recibida en Método Millington según el orden de tres terrenos con 100km de longitud cada uno y f=1 MHz 36 85
Índice de Códigos 3.1 Ejecución de GRWAVE con ficheros de entrada y salida 13 3.2 Ejemplo para montar directorio en DOSBox 15 3.3 Código para llamada automática de GRWAVE en DOSBox 16 3.4 Ejemplo de uso del instalador de paquetes pip 17 3.5 Comando para generar ejecutable del Simulador 17 3.6 Ejemplo traducción de archivo de Qt Designer para Python 18 4.1 Fragmentos de código en referencia a la creación y rellenado de gráfica. No todas las líneas pertenecen al mismo fichero ni son las únicas para ésto. 23 87
Bibliografía [1] Curvas de propagación por onda de superficie para frecuencias comprendidas entre 10 kHz y 30 MHz. Rec ITU-R P.368-9, 02/2007. [2] A. Sommerfeld, The propagation of waves in wireless telegraphy, 1909, vol. 28. [3] K. Norton, The propagation of radio waves over the surface of the Earth and in the upper atmosphere. Part 1, 1936, vol. 24. [4] K. A. Norton, The propagation of radio waves over the surface of the Earth and in the upper atmosphere. Part 2, 1937, vol. 25. [5] B. V. der Pol y H. Bremmer, The diffraction of electromagnetic waves from an electrical point source round a finitely conducting sphere, 1937, vol. 25. [6] K. Norton, The calculation of ground-wave field intensity over a finitely conducting spherical Earth, 1941. [7] G. Millington, Ground-wave propagation over an inhomogeneous smooth earth, 1941, vol. 96, no. 56. [8] Ground wave physics. Encyclopædia Britannica, Inc. [Online]. Available: https://www.britannica.com/ science/ground-wave [9] ITU-R, Manual sobre propagación por onda de superficie, 2014. [10] L. Zhou, LF Ground-Wave Propagation Over Irregular Terrain, 2011, no. 59. [11] J. A. Richards, An introduction for the non specialist. Springer, 2008. [12] H. Bremmer, Terrestrial Radio Waves. Elvesier, 1949. [13] Electrical characteristics of the surface of the Earth, Radiowave propagation. Rec ITU-R P.527, 08/2009. [14] F. Dagefu and K. Sarabandi, Analysis and modeling of near-ground wave propagation in the presence of building walls, 2011, vol. 50. [15] Índice de refracción radioeléctrica: su fórmula y datos sobre la refractividad. Rec ITU-R P.453, 08/2019. [16] Teoría de imágenes, aplicada a los radiadores electromagnéticos: monopolos y monopolos de λ /4. Antenaruval. [Online]. Available: https://cutt.ly/Gydysry#eqlatex [17] N. DeMinco, Free-Field Measurements of the Electrical Properties of Soil Using the Surface Wave Propagation Between Two Monopole Antennas. NTIA-Report TR-12-484, 2012. [18] R. Gill, Ground-wave propagation program GRWAVE. Cambridge Press, 2010. [19] World atlas of ground conductivities. Rec ITU-R P.832, 07/2015. 89