18 votos

Obtención de los datos del sitio web al hacer clic en QGIS

Me gustaría obtener los datos al hacer clic en QGIS y mostrarlos como marcas de posición.

Básicamente, el problema viene de este sitio web:

https://eclipse.gsfc.nasa.gov/SEgoogle/SEgoogle2001/SE2024Apr08Tgoogle.html

después de hacer clic se obtienen las circunstancias locales del fenómeno.

enter image description here

Si, por ejemplo, tengo en cuenta, cuando el evento de inicio (destacó en el rojo), debo buscar exactamente el mismo tiempo alrededor de la forma (que es la umbra en este caso), como por abajo:

enter image description here

El asunto lleva tiempo, ya que el sitio web no ofrece la opción de descarga para estas circunstancias. Además, no existe ninguna opción para descargar estos puntos en formato a.geojson, por ejemplo.

Me puso en una situación difícil y tediosa, en la que tengo que dibujar literalmente toda la forma manualmente, como se indica a continuación:

enter image description here

pero esto no es el final, ya que no puedo descargar los puntos on-click, como he mencionado anteriormente. Ahora si quiero obtener esta forma externamente, tengo que redibujarla en un lugar diferente. Yo solía hacerlo en el ScribbleMaps, y guardarlo como un archivo .kml después.

enter image description here enter image description here

A continuación, podría visualizarlo, por ejemplo, en Google Earth.

enter image description here

Lo mismo puedo hacer con el QGIS si selecciono las imágenes de Google Satellite del QuickMapServices y creo la capa shapefile, donde empezaré a dibujar la forma.

enter image description here

¿Cómo introduzco estas circunstancias en QGIS en forma de capa?

Más detalles de mi problema aquí: http://www.mkrgeo-blog.com/total-solar-eclipse-vizualisation-on-google-earth-part-1/

ACTUALIZACIÓN:

Sólo lo he hecho un poco más rápido copiando las coordenadas de la ventana emergente. Por desgracia, el proceso sigue siendo demasiado largo, ya que necesito unos 180 puntos individuales para sumar en mi capa:

enter image description here enter image description here enter image description here enter image description here

ACTUALIZACIÓN:

La misma situación se aplica a los puntos con la misma magnitud, que finalmente crean la línea de larga distancia, como en la imagen de abajo:

enter image description here

ACTUALIZACIÓN II:

La respuesta que figura a continuación va sin duda por buen camino. Sólo pregunto - ¿es posible rascar el contenido emergente? ¿Y hacer que se rellene en la tabla de atributos de datos? ¿Cómo puedo eliminar al instante los puntos, que recogí mal?

enter image description here

ACTUALIZACIÓN III

He intentado utilizar las siguientes respuestas a este sitio:

http://www.eclipsewise.com/solar/SEgmapx/2001-2100/SE2023Oct14Agmapx.html#map

pero no funcionaron. Supongo, el código Python está bien, pero tengo que cambiar el fragmento de JavaScript.

Lo mismo aquí:

http://xjubier.free.fr/en/site_pages/solar_eclipses/ASE_2023_GoogleMapFull.html?Lat=43.90829&Lng=-121.54164&Zoom=8&LC=1

el script no funciona.

¿Hay algo que pueda cambiar?

18voto

nitinsavant Puntos 6

Demo:

enter image description here

SOLUCIÓN : Se trata de una solución en dos pasos,

Paso 1: Navegador (JavaScript). Copia el texto emergente en el portapapeles.
Paso 2: QGIS (Python). Obtiene el texto del portapapeles, lo analiza para obtener la latitud y la longitud y, a continuación, añade el punto a la capa activa.

En el navegador:

  • Abre el sitio web.

  • Abrir DevTools Ctrl+Shift+C .

  • Fuente > Fragmentos > Nuevo fragmento > Copiar/pegar el siguiente script:

    function copyToClipboard(text) {
         var dummy = document.createElement("textarea");
         document.body.appendChild(dummy);
         dummy.value = text;
         dummy.select();
         document.execCommand("copy");
         document.body.removeChild(dummy);
    }
    
    var text_prev = null;
    a = setInterval(function () {
         var text = document.getElementById('mapmarker');
         if (text && text !== text_prev) {
             copyToClipboard(text.innerText);
             text_prev = text;
         }
    }, 500); // 500 milisecond, so don't be fast.
  • Ejecute el script ( Ctrl+Enter ).

enter image description here

En QGIS:

  • Crear nueva capa de puntos (Capa > Crear capa) / temporal o shapefile / CRS: WGS84. Añadir dos campos, a saber MAGNITUDE y EC_TIME .

    enter image description here

  • Copie/pegue el siguiente script en el Editor Python de QGIS.

    import re
    
    try: QApplication.clipboard().dataChanged.disconnect()
    except: pass
    
    layer = iface.activeLayer()
    layer.startEditing()
    
    def clipboard_changed():
    
        text = QApplication.clipboard().text()
    
        try:
            t = re.split(r' |\xa0|\n|\t|eclipse', text)
            t = list(filter(('').__ne__, t))
            t = list(filter((':').__ne__, t))
    
            lat = float(t[1][:-1])
            lon = float(t[4][:-1])
    
            i = t.index('Magnitude:') + 1
            magnitude = float(t[i])
            i = t.index('Maximum') + 2
            ec_time = t[i]
    
            if t[2]=='S': lat = -lat
            if t[5]=='W': lon = -lon
    
            if t != text_prev:
                print(lat, lon, magnitude, ec_time) 
                f = QgsFeature(layer.fields())
                f["MAGNITUDE"] = magnitude
                f["EC_TIME"] = ec_time
                g = QgsGeometry.fromPointXY(QgsPointXY(lon, lat))
                f.setGeometry(g)
    
                layer.beginEditCommand("Remove the last added point")
                layer.addFeature(f)
                layer.endEditCommand()
                iface.mapCanvas().refresh()
    
        except ValueError: print("ValueError")
        except IndexError: print("IndexError")
    
    text_prev = None
    QApplication.clipboard().dataChanged.connect(clipboard_changed)
  • Asegúrese de que la capa está seleccionada en el panel "Capas" y, a continuación, ejecute el script

  • Vuelve al navegador y haz clic en el mapa.

NOTA 1 : Por supuesto, existen algunas limitaciones. La lógica subyacente es que cada vez que se abre una ventana emergente (haciendo clic o pasando el ratón) JavaScript copia el texto de la ventana emergente en el portapapeles, el script PyQGIS obtiene las coordenadas y añade un punto a la capa. Si se pasa el ratón sobre cualquier marcador existente (lo que significa que se abre una ventana emergente), el mismo punto se añade a la capa de nuevo (no he podido resolver este problema). Por lo tanto, ejecute el script Eliminar geometrías duplicadas cuando hayas terminado.

NOTA 2 : Si actualiza la página web, deberá ejecutar de nuevo el código JavaScript.

NOTA 3 : Supongo que, haciendo clic en todos los puntos en primer lugar, a continuación, añadirlos a QGIS de una sola vez no es posible ya que las ventanas emergentes se crean en la demanda (al hacer clic o se cierne).

16voto

J. Monticolo Puntos 46

EDITAR : ver https://gis.stackexchange.com/a/409403/93097 para obtener una respuesta actualizada con los datos de otro sitio web, tras el cierre del sitio web Eclipse de la NASA.


Aquí mi solución, enteramente en QGIS (versión >= 3.14 (para versiones >= 3.0 y < 3.14, tal vez sean necesarios algunos ajustes).

La solución es crear una rejilla de puntos en QGIS y recuperar los datos de las funciones de JavaScript y actualizar cada punto.

Los parámetros de entrada de la función son :

  • Interfaz QGIS iface

  • Los elementos del eclipse solar, los puedes encontrar en el código fuente de la página del eclipse solar de la NASA

    por ejemplo ( fuente : Eclipse total de Sol de 2024 Abr 08 ):

    /* Insert Eclipse Besselian Elements below */
    
    //
    // Eclipse Elements
    //
    // First line -
    //   (0) Julian date
    //   (1) t0
    //   (2) tmin
    //   (3) tmax
    //   (4) dT
    // Second line -
    //   (5) X0, X1, X2, X3 - X elements
    // Third line -
    //   (9) Y0, Y1, Y2, Y3 - Y elements
    // Fourth line -
    //   (13) D0, D1, D2 - D elements
    // Fifth line -
    //   (16) M0, M1, M2 - mu elements
    // Sixth line -
    //   (19) L10, L11, L12 - L1 elements
    // Seventh line -
    //   (22) L20, L21, L22 - L2 elements
    // Eighth line -
    //   (25) tan f1
    //   (26) tan f2
    //
    
    var elements = new Array(
    //*** #0U - Input Besselian Elements here
    2460409.262835,  18.0,  -4.0,   4.0,    70.6,
       -0.31815711,    0.51171052,    0.00003265,   -0.00000852,
        0.21974689,    0.27095860,   -0.00005943,   -0.00000467,
        7.58619928,    0.01484434,   -0.00000168,
       89.59121704,   15.00408363,   -0.00000130,
        0.53581262,    0.00006179,   -0.00001275,
       -0.01027351,    0.00006148,   -0.00001269,
        0.00466826,    0.00464501
    );
  • Opcional : la fecha y la hora de la umbra que desea crear

  • Opcional : el espaciado de los puntos de la cuadrícula en grados, por defecto = 0.1°

Sólo tienes que copiar el código de abajo en un nuevo editor de la consola Python de QGIS, modificar los parámetros (últimas líneas del código) y ejecutarlo.

Aparecerá un práctico cuadro de diálogo :

QGIS solution dialog

Defina, en WGS 84 (EPSG: 4326) la extensión de cuadrícula deseada, puede recuperarla de una capa de proyecto de QGIS, de la extensión actual del lienzo de QGIS o simplemente dibujarla usted mismo.

Por último, haga clic en el botón Create Eclipse Layer y espere a que finalice el proceso.

El código :

#!/usr/bin/env python3

from datetime import datetime
from html.parser import HTMLParser

import processing
from PyQt5.QtCore import (
    QCoreApplication,
    QObject,
    QRunnable,
    QThreadPool,
    QVariant,
    pyqtSignal,
)
from PyQt5.QtWebKitWidgets import QWebPage, QWebView
from PyQt5.QtWidgets import QProgressBar, QPushButton, QVBoxLayout, QWidget
from qgis.core import (
    QgsCoordinateReferenceSystem,
    QgsField,
    QgsProcessingFeatureSourceDefinition,
    QgsProject,
)
from qgis.gui import QgsExtentGroupBox

class MarkerHTMLParser(HTMLParser):
    """Parse map marker html"""

    def __init__(self):
        super(MarkerHTMLParser, self).__init__()
        self.loc_data = {}
        self.loc_data["event"] = {}
        self.data_name = ""
        self.in_table = False
        self.table_num = 0
        self.table_line = 0
        self.table_headers = []
        self.current_header = 0
        self.current_line = ""

    def handle_starttag(self, tag, attrs):
        if tag == "table":
            self.in_table = True
            self.table_num += 1
        elif tag == "tr":
            self.table_line += 1
            self.current_header = 0
            self.current_line = ""
        else:
            self.data_name = ""

    def handle_endtag(self, tag):
        if tag == "table":
            self.in_table = False
            self.table_line = 0
            self.table_headers = []
            self.current_header = 0
            self.current_line = ""

    def handle_data(self, data):
        if self.in_table and self.table_num == 2:
            data = data.replace("\xa0", " ")
            if self.table_line == 1:
                self.table_headers.append(data)
            else:
                if self.current_header == 0:
                    self.current_line = data.strip()
                    self.loc_data["event"][self.current_line] = {}
                else:
                    self.loc_data["event"][self.current_line][
                        self.table_headers[self.current_header]
                    ] = data

                self.current_header += 1
        else:
            # \xa0 = &nbsp;
            data_content = data.split("\xa0")
            if "Eclipse" in data_content:
                self.data_name = "type"
                self.loc_data[self.data_name] = data.replace("\xa0", " ")
            elif data_content[0] == "Duration":
                self.data_name = "duration_of_totality"
                self.loc_data[self.data_name] = data_content[3]
            elif data_content[0] == "Magnitude:":
                self.data_name = "magnitude"
                self.loc_data[self.data_name] = data_content[1]
            elif data_content[0] == "Obscuration:":
                self.data_name = "obscuration"
                self.loc_data[self.data_name] = data_content[1]
            elif data == "Lat.":
                self.data_name = "latitude"
            elif data == "Long.":
                self.data_name = "longitude"
            elif self.data_name in ["latitude", "longitude"]:
                self.loc_data[self.data_name] = data_content[1]

class CustomQWebPage(QWebPage):
    """Web page to send JS commands and get JS result"""

    result = pyqtSignal(dict)

    def __init__(self, parent=None):
        super(CustomQWebPage, self).__init__(parent)
        self.result_data = None
        self.result.connect(lambda d: setattr(self, "result_data", d))

    def javaScriptConsoleMessage(self, msg, line_number, source_id):
        parser = MarkerHTMLParser()
        parser.feed(msg)
        self.result.emit(parser.loc_data)

    def get_data(self, latitude: float, longitude: float):
        self.mainFrame().evaluateJavaScript(
            f"console.log(loc_circ({latitude}, {longitude}))"
        )
        while not self.result_data:
            QCoreApplication.processEvents()

        data = self.result_data
        self.result_data = None
        return data

class WorkerSignals(QObject):
    progress = pyqtSignal(int)
    result = pyqtSignal(object)

class Runnable(QRunnable):
    def __init__(self, webpage, vl_pts):
        super(Runnable, self).__init__()
        self.webpage = webpage
        self.vl_pts = vl_pts
        # signals
        self.signals = WorkerSignals()

    def run(self) -> None:
        total = self.vl_pts.featureCount()
        # edit mode
        self.vl_pts.startEditing()
        for i, feat in enumerate(self.vl_pts.getFeatures()):
            # send progress
            self.signals.progress.emit(int(i / total * 100) + 1)

            point = feat.geometry().asPoint()
            lon = point.x()
            lat = point.y()
            data = self.webpage.get_data(lat, lon)

            feat["type_eclip"] = data["type"]
            feat["lat"] = data["latitude"]
            feat["lon"] = data["longitude"]
            if "Start of partial eclipse (C1) :" in data["event"]:
                c_type = "Start of partial eclipse (C1) :"
                c_field = "c1"
                c_data = data["event"][c_type]
                dt = datetime.strptime(
                    f'{c_data["Date"]} {c_data["Time (UT)"]}', "%Y/%m/%d %H:%M:%S.%f"
                )
                feat[c_field] = dt.strftime("%Y-%m-%d %H:%M %S")

            if "Start of total eclipse (C2) :" in data["event"]:
                c_type = "Start of total eclipse (C2) :"
                c_field = "c2"
                c_data = data["event"][c_type]
                dt = datetime.strptime(
                    f'{c_data["Date"]} {c_data["Time (UT)"]}', "%Y/%m/%d %H:%M:%S.%f"
                )
                feat[c_field] = dt.strftime("%Y-%m-%d %H:%M %S")

            if "Maximum eclipse :" in data["event"]:
                c_type = "Maximum eclipse :"
                c_field = "max_eclip"
                c_data = data["event"][c_type]
                dt = datetime.strptime(
                    f'{c_data["Date"]} {c_data["Time (UT)"]}', "%Y/%m/%d %H:%M:%S.%f"
                )
                feat[c_field] = dt.strftime("%Y-%m-%d %H:%M %S")

            if "End of total eclipse (C3) :" in data["event"]:
                c_type = "End of total eclipse (C3) :"
                c_field = "c3"
                c_data = data["event"][c_type]
                dt = datetime.strptime(
                    f'{c_data["Date"]} {c_data["Time (UT)"]}', "%Y/%m/%d %H:%M:%S.%f"
                )
                feat[c_field] = dt.strftime("%Y-%m-%d %H:%M %S")

            if "End of partial eclipse (C4) :" in data["event"]:
                c_type = "End of partial eclipse (C4) :"
                c_field = "c4"
                c_data = data["event"][c_type]
                dt = datetime.strptime(
                    f'{c_data["Date"]} {c_data["Time (UT)"]}', "%Y/%m/%d %H:%M:%S.%f"
                )
                feat[c_field] = dt.strftime("%Y-%m-%d %H:%M %S")

            if "magnitude" in data:
                feat["magnitude"] = data["magnitude"]

            if "obscuration" in data:
                obscuration = float(data["obscuration"].replace("%", ""))
                feat["obscuration"] = obscuration

            self.vl_pts.updateFeature(feat)
        # end of edit mode
        self.vl_pts.commitChanges()
        # finish the process, emit a result
        self.signals.result.emit(True)

class Total_Eclipse(QWidget):
    def __init__(
        self,
        iface,
        elements: str,
        umbra_datetime: str = None,
        spacing: float = 0.1,
        parent=None,
    ):
        super(Total_Eclipse, self).__init__(parent)
        self.iface = iface
        self._umbra_dt = umbra_datetime
        self.spacing = spacing
        self.lyr_pts = None
        self.custom_page = CustomQWebPage(self)
        self.webview = QWebView(self)
        self.webview.setPage(self.custom_page)
        self.layout = QVBoxLayout(self)
        self.crs = QgsCoordinateReferenceSystem("EPSG:4326")
        self.extent_box = QgsExtentGroupBox()
        self.extent_box.setMapCanvas(self.iface.mapCanvas())
        self.extent_box.setOutputCrs(self.crs)
        self.progress_bar = QProgressBar(self)
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(100)
        self.launch_process = QPushButton("Create Eclipse Layer", self)
        self.launch_process.setEnabled(False)
        self.layout.addWidget(self.extent_box)
        self.layout.addWidget(self.progress_bar)
        self.layout.addWidget(self.launch_process)
        # multithreading
        self.pool = QThreadPool.globalInstance()
        self.runnable = None
        # signals
        self.webview.loadFinished.connect(lambda: self.launch_process.setEnabled(True))
        self.launch_process.clicked.connect(self.start_process)
        # load JS scripts in the webview
        html = (
            '<!DOCTYPE html><html><head><meta charset="UTF-8">'
            '<script type="text/javascript">'
            "var elements = new Array({0});"
            '</script><script type="text/javascript" '
            'src="https://eclipse.gsfc.nasa.gov/SEgoogle/SEcirc.js">'
            "</script></head></html>".format(elements.replace("\n", "").strip())
        )
        self.webview.setHtml(html)

    @property
    def umbra_datetime(self) -> str:
        return self._umbra_dt

    @umbra_datetime.setter
    def umbra_datetime(self, value):
        self._umbra_dt = value

    def get_data(self, latitude: float, longitude: float):
        return self.custom_page.get_data(latitude, longitude)

    def start_process(self):
        self.launch_process.setEnabled(False)
        extent = self.extent_box.outputExtent()
        extent_bounds = [
            str(extent.xMinimum()),
            str(extent.xMaximum()),
            str(extent.yMinimum()),
            str(extent.yMaximum()),
        ]
        regular_points = processing.run(
            "qgis:regularpoints",
            {
                "EXTENT": f'{",".join(extent_bounds)} [EPSG:4326]',
                "SPACING": self.spacing,
                "INSET": 0,
                "RANDOMIZE": False,
                "IS_SPACING": True,
                "CRS": self.crs,
                "OUTPUT": "TEMPORARY_OUTPUT",
            },
        )
        self.lyr_pts = regular_points["OUTPUT"]
        self.format_lyr_pts()
        # multithreading
        self.runnable = Runnable(self.custom_page, self.lyr_pts)
        self.runnable.signals.result.connect(self.thread_result)
        self.runnable.signals.progress.connect(self.update_progress)
        self.pool.start(self.runnable)

    def format_lyr_pts(self):
        fields = [
            ("type_eclip", QVariant.String),
            ("lat", QVariant.String),
            ("lon", QVariant.String),
            ("magnitude", QVariant.Double),
            ("obscuration", QVariant.Double),
            ("c1", QVariant.DateTime),
            ("c2", QVariant.DateTime),
            ("max_eclip", QVariant.DateTime),
            ("c3", QVariant.DateTime),
            ("c4", QVariant.DateTime),
        ]

        self.lyr_pts.startEditing()
        for fld_name, fld_type in fields:
            self.lyr_pts.addAttribute(QgsField(fld_name, fld_type))

        self.lyr_pts.updateFields()
        self.lyr_pts.commitChanges()

    def update_progress(self, value):
        self.progress_bar.setValue(value)

    def thread_result(self, result):
        if result:
            self.lyr_pts.setName("eclipse_points")
            QgsProject.instance().addMapLayer(self.lyr_pts)
            if self._umbra_dt:
                lyr_umbra = self.create_umbra(self._umbra_dt)
                QgsProject.instance().addMapLayer(lyr_umbra)

            self.progress_bar.setValue(0)

    def create_umbra(self, umbra_date_time: str):
        expression = (
            "\"c2\" <= to_datetime('{0}', 'yyyy-MM-dd HH:mm:ss')"
            " and \"c3\" >= to_datetime('{0}', 'yyyy-MM-dd HH:mm:ss')"
        )
        self.lyr_pts.selectByExpression(expression.format(umbra_date_time))
        min_bounding = processing.run(
            "qgis:minimumboundinggeometry",
            {
                "INPUT": QgsProcessingFeatureSourceDefinition(
                    self.lyr_pts.id(), selectedFeaturesOnly=True
                ),
                "FIELD": "",
                "TYPE": 3,  # convex
                "OUTPUT": "TEMPORARY_OUTPUT",
            },
        )
        umbra = min_bounding["OUTPUT"]
        umbra.setName("umbra")
        return umbra

# Eclipse elements
eclipse_elements = """
    2460409.262835,  18.0,  -4.0,   4.0,    70.6,
   -0.31815711,    0.51171052,    0.00003265,   -0.00000852,
    0.21974689,    0.27095860,   -0.00005943,   -0.00000467,
    7.58619928,    0.01484434,   -0.00000168,
   89.59121704,   15.00408363,   -0.00000130,
    0.53581262,    0.00006179,   -0.00001275,
   -0.01027351,    0.00006148,   -0.00001269,
    0.00466826,    0.00464501
    """
umbra_datetime = "2024-04-08 19:14:32"
solar_te = Total_Eclipse(iface, eclipse_elements, umbra_datetime, spacing=0.1)
solar_te.show()

El resultado son dos capas :

  • Una capa de puntos de la cuadrícula de la extensión del diálogo, con toda la información necesaria sobre el eclipse (busque en la tabla de atributos)
  • Un polígono de la umbra solicitada (si se facilita la fecha/hora)

QGIS result

7voto

nitinsavant Puntos 6

En eclipsewise.com, el id de la ventana emergente es mapmaker . Por lo tanto, puede utilizar el mismo código JavaScript de la misma manera. El script copia el contenido de la ventana emergente en el portapapeles.

function copyToClipboard(text) {
     var dummy = document.createElement("textarea");
     document.body.appendChild(dummy);
     dummy.value = text;
     dummy.select();
     document.execCommand("copy");
     document.body.removeChild(dummy);
}

var text_prev = null;
a = setInterval(function () {
     var text = document.getElementById('mapmarker');
     if (text && text !== text_prev) {
         copyToClipboard(text.innerText);
         text_prev = text;
     }
}, 500); // 500 millisecond, so don't be fast.

Y utilice el siguiente script en QGIS Python Editor, como se menciona en esta respuesta .

import re

try: QApplication.clipboard().dataChanged.disconnect()
except: pass

layer = iface.activeLayer()
layer.startEditing()

def clipboard_changed():
    text = QApplication.clipboard().text()

    t = re.split(r' |\xa0|\n|\t|eclipse', text)
    t = list(filter(('').__ne__, t))
    t = list(filter((':').__ne__, t))

    lat = float(t[1][:-1])
    lon = float(t[4][:-1])

    i = t.index('Magnitude:') + 1
    magnitude = float(t[i])

    ### LINES REMOVED ###
    # i = t.index('Maximum') + 2
    # ec_time = t[i]
    #####################

    ### LINES ADDED #####
    if 'C2' in t: 
        ec_time = t[45]
    else:
        ec_time = t[32]
    #####################

    if t[2]=='S': lat = -lat
    if t[5]=='W': lon = -lon

    if t != text_prev:
        print(lat, lon, magnitude, ec_time) 
        f = QgsFeature(layer.fields())
        f["MAGNITUDE"] = magnitude
        f["EC_TIME"] = ec_time
        g = QgsGeometry.fromPointXY(QgsPointXY(lon, lat))
        f.setGeometry(g)

        layer.beginEditCommand("Remove the last added point")
        layer.addFeature(f)
        layer.endEditCommand()
        iface.mapCanvas().refresh()

text_prev = None
QApplication.clipboard().dataChanged.connect(clipboard_changed)

enter image description here


Q : ¿Qué parte del script necesitas cambiar y cómo?
A : Bueno. Depende. Afortunadamente, la forma en que eclipsewise presenta los datos es casi la misma que la anterior. Así que no se necesitan muchos cambios. Toda la lógica de mi solución consiste en dividir el texto en partes (convirtiéndolo en una lista), eliminar los elementos innecesarios y seleccionar el elemento en un índice determinado. Si los valores de la ventana emergente tuvieran sus propios ID independientes definidos en HTML, podrían recuperarse muy fácilmente. Pero como no es el caso, es necesario realizar el análisis sintáctico del texto en esta solución.

5voto

J. Monticolo Puntos 46

Esta es una nueva respuesta con los nuevos sitios web.

Procedimiento :

  • Vaya al sitio del eclipse deseado en http://xjubier.free.fr , por ejemplo

  • Ver la fuente de la página web (haga clic con el botón derecho en el cuadro de datos del marcador eclipse)

  • En la fuente HTML de la página, busque el bloque (pocas líneas por debajo de la parte superior) :

    [...] <script type="text/javascript"> //<![CDATA[ var elements = new Array( 2460232.25047, 18.0, -3.0, 3.0, 71.1, 71.1, 0.16965801, 0.45855331, 0.00002780, -0.00000543, 0.33485901, -0.24136710, 0.00002400, 0.00000303, -8.24419022, -0.01488800, 0.00000200, 93.50173187, 15.00352955, 0.00000000, 0.56431103, -0.00008910, -0.00001030, 0.01808300, -0.00008860, -0.00001030, 0.00468820, 0.00466480 );

    var eclipseInfos = "ASE 2023 General Circumstances:</span>"; eclipseInfos += "<ul type=\"square\">"; eclipseInfos += "<li>Type: Annular</li>"; [...]

  • Extraer datos del elements matriz

  • Modificar el código Python con el HTML elements matriz, eclipse_elements Variable Python

  • Ajuste el umbra_datetime para el polígono de la umbra con puntos extraídos (si no desea un polígono de umbra, ajústelo a None )

  • Ajuste el spacing entre puntos de datos, aquí 0.1 significa 0,1 grado por lo que alrededor de 11 km. Le aconsejo que establezca un número grande como 1 para ver dónde están los datos del eclipse y después, establecer un espaciamiento más preciso y el zoom en el área de interés.

  • Copie el código Python modificado bajo QGIS, en un editor Python y ejecútelo.

  • En el cuadro de diálogo, seleccione la extensión deseada y haga clic en el botón Crear capa Eclipse botón (y espera, más puntos = más espera)

  • Reviso mi código original y elimino la mayor parte de la parte multihilo para que la ejecución de este código sea estable.

    !/usr/bin/env python3

    from datetime import datetime from html.parser import HTMLParser

    import processing from PyQt5.QtCore import ( QCoreApplication, QObject, QVariant, pyqtSignal, ) from PyQt5.QtWebKitWidgets import QWebPage, QWebView from PyQt5.QtWidgets import QProgressBar, QPushButton, QVBoxLayout, QWidget from qgis.core import ( QgsCoordinateReferenceSystem, QgsField, QgsProcessingFeatureSourceDefinition, QgsProject, ) from qgis.gui import QgsExtentGroupBox

    QGIS task code from

    https://docs.qgis.org/3.16/en/docs/pyqgis_developer_cookbook/tasks.html#task-from-function

    MESSAGE_CATEGORY = 'Eclipse Fetcher'

    Eclipse elements

    eclipse_elements = """ 2460232.25047, 18.0, -3.0, 3.0, 71.1, 71.1, 0.16965801, 0.45855331, 0.00002780, -0.00000543, 0.33485901, -0.24136710, 0.00002400, 0.00000303, -8.24419022, -0.01488800, 0.00000200, 93.50173187, 15.00352955, 0.00000000, 0.56431103, -0.00008910, -0.00001030, 0.01808300, -0.00008860, -0.00001030, 0.00468820, 0.00466480 """ umbra_datetime = "2023-10-14 16:18:00" spacing = 0.1

    class MarkerHTMLParser(HTMLParser): """Parse map marker html"""

    def __init__(self):
        super(MarkerHTMLParser, self).__init__()
        self.loc_data = {}
        self.loc_data["event"] = {"type": ""}
        self.data_name = ""
        self.in_table = False
        self.table_num = 0
        self.table_line = 0
        self.table_headers = []
        self.current_header = 0
        self.current_line = ""
    
    def handle_starttag(self, tag, attrs):
        if tag == "table":
            self.in_table = True
            self.table_num += 1
        elif tag == "tr":
            self.table_line += 1
            self.current_header = 0
            self.current_line = ""
        else:
            self.data_name = ""
    
    def handle_endtag(self, tag):
        if tag == "table":
            self.in_table = False
            self.table_line = 0
            self.table_headers = []
            self.current_header = 0
            self.current_line = ""
    
    def handle_data(self, data):
        if self.in_table and self.table_num == 2:
            data = data.replace("\xa0", " ")
            if self.table_line == 1:
                self.table_headers.append(data)
            else:
                if self.current_header == 0:
                    self.current_line = data.strip()
                    self.loc_data["event"][self.current_line] = {}
                else:
                    self.loc_data["event"][self.current_line][
                        self.table_headers[self.current_header]
                    ] = data
    
                self.current_header += 1
        else:
            # \xa0 = &nbsp;
            data_content = data.split("\xa0")
            if "Eclipse" in data_content:
                self.data_name = "type"
                self.loc_data[self.data_name] = data.replace("\xa0", " ")
            elif data_content[0] == "Duration":
                self.data_name = "duration_of_totality"
                self.loc_data[self.data_name] = data_content[3]
            elif data_content[0] == "Magnitude:":
                self.data_name = "magnitude"
                self.loc_data[self.data_name] = data_content[1]
            elif data_content[0] == "Obscuration:":
                self.data_name = "obscuration"
                self.loc_data[self.data_name] = data_content[1]
            elif data == "Lat.":
                self.data_name = "latitude"
            elif data == "Long.":
                self.data_name = "longitude"
            elif self.data_name in ["latitude", "longitude"]:
                self.loc_data[self.data_name] = data_content[1]

    class CustomQWebPage(QWebPage): """Web page to send JS commands and get JS result"""

    result = pyqtSignal(dict)
    
    def __init__(self, parent=None):
        super(CustomQWebPage, self).__init__(parent)
        self.result_data = None
        self.result.connect(lambda d: setattr(self, "result_data", d))
    
    def javaScriptConsoleMessage(self, msg, line_number, source_id):
        parser = MarkerHTMLParser()
        parser.feed(msg)
        self.result.emit(parser.loc_data)
    
    def get_data(self, latitude: float, longitude: float):
        self.mainFrame().evaluateJavaScript(
            f"console.log(loc_circ({latitude}, {longitude}))"
        )
        while not self.result_data:
            QCoreApplication.processEvents()
    
        data = self.result_data
        self.result_data = None
        return data

    class Total_Eclipse(QWidget): def init( self, iface, elements: str, umbra_datetime: str = None, spacing: float = 0.1, parent=None, ): super(Total_Eclipse, self).init(parent) self.iface = iface self._umbra_dt = umbra_datetime self.spacing = spacing self.lyr_pts = None self.lyr_umbra = None self.custom_page = CustomQWebPage(self) self.webview = QWebView(self) self.webview.setPage(self.custom_page) self.layout = QVBoxLayout(self) self.crs = QgsCoordinateReferenceSystem("EPSG:4326") self.extent_box = QgsExtentGroupBox() self.extent_box.setMapCanvas(self.iface.mapCanvas()) self.extent_box.setOutputCrs(self.crs) self.progress_bar = QProgressBar(self) self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(100) self.launch_process = QPushButton("Create Eclipse Layer", self) self.launch_process.setEnabled(False) self.layout.addWidget(self.extent_box) self.layout.addWidget(self.progress_bar) self.layout.addWidget(self.launch_process)

    signals

        self.webview.loadFinished.connect(lambda: self.launch_process.setEnabled(True))
        self.launch_process.clicked.connect(self.start_process)
        # format elements
        elements = elements.replace("\n", "").strip().split(",")
        elements.pop(5)
        elements = ",".join(elements)
        # load JS scripts in the webview
        html = (
            '<!DOCTYPE html><html><head><meta charset="UTF-8">'
            '<script type="text/javascript">'
            "var elements = new Array({0});"
            '</script><script type="text/javascript" '
            'src="https://eclipse.gsfc.nasa.gov/SEgoogle/SEcirc.js">'
            "</script></head></html>".format(elements.replace("\n", "").strip())
        )
        self.webview.setHtml(html)
    
    def compute_points_data(self) -> None:
        self.progress_bar.setValue(0)
        total = self.lyr_pts.featureCount()
        # edit mode
        self.lyr_pts.startEditing()
        for i, feat in enumerate(self.lyr_pts.getFeatures()):
            point = feat.geometry().asPoint()
            lon = point.x()
            lat = point.y()
            data = self.custom_page.get_data(lat, lon)
    
            feat["type_eclip"] = data["type"] if "type" in data else None
            feat["lat"] = data["latitude"]
            feat["lon"] = data["longitude"]
            if "Start of partial eclipse (C1) :" in data["event"]:
                c_type = "Start of partial eclipse (C1) :"
                c_field = "c1"
                c_data = data["event"][c_type]
                dt = datetime.strptime(
                    f'{c_data["Date"]} {c_data["Time (UT)"]}', "%Y/%m/%d %H:%M:%S.%f"
                )
                feat[c_field] = dt.strftime("%Y-%m-%d %H:%M:%S")
    
            if "Start of total eclipse (C2) :" in data["event"]:
                c_type = "Start of total eclipse (C2) :"
                c_field = "c2"
                c_data = data["event"][c_type]
                dt = datetime.strptime(
                    f'{c_data["Date"]} {c_data["Time (UT)"]}', "%Y/%m/%d %H:%M:%S.%f"
                )
                feat[c_field] = dt.strftime("%Y-%m-%d %H:%M:%S")
    
            if "Maximum eclipse :" in data["event"]:
                c_type = "Maximum eclipse :"
                c_field = "max_eclip"
                c_data = data["event"][c_type]
                dt = datetime.strptime(
                    f'{c_data["Date"]} {c_data["Time (UT)"]}', "%Y/%m/%d %H:%M:%S.%f"
                )
                feat[c_field] = dt.strftime("%Y-%m-%d %H:%M:%S")
    
            if "End of total eclipse (C3) :" in data["event"]:
                c_type = "End of total eclipse (C3) :"
                c_field = "c3"
                c_data = data["event"][c_type]
                dt = datetime.strptime(
                    f'{c_data["Date"]} {c_data["Time (UT)"]}', "%Y/%m/%d %H:%M:%S.%f"
                )
                feat[c_field] = dt.strftime("%Y-%m-%d %H:%M:%S")
    
            if "End of partial eclipse (C4) :" in data["event"]:
                c_type = "End of partial eclipse (C4) :"
                c_field = "c4"
                c_data = data["event"][c_type]
                dt = datetime.strptime(
                    f'{c_data["Date"]} {c_data["Time (UT)"]}', "%Y/%m/%d %H:%M:%S.%f"
                )
                feat[c_field] = dt.strftime("%Y-%m-%d %H:%M:%S")
    
            if "magnitude" in data:
                feat["magnitude"] = data["magnitude"]
    
            if "obscuration" in data:
                obscuration = float(data["obscuration"].replace("%", ""))
                feat["obscuration"] = obscuration
    
            self.lyr_pts.updateFeature(feat)
            # update progress bar
            self.progress_bar.setValue(int(i / total * 100) + 1)
        # end of edit mode
        self.lyr_pts.commitChanges()
    
    def start_process(self):
        self.alg = QgsApplication.processingRegistry().algorithmById("qgis:regularpoints")
        context = QgsProcessingContext()
        self.feedback = QgsProcessingFeedback()
        self.launch_process.setEnabled(False)
        extent = self.extent_box.outputExtent()
        extent_bounds = [
            str(extent.xMinimum()),
            str(extent.xMaximum()),
            str(extent.yMinimum()),
            str(extent.yMaximum()),
        ]
        params = {
            "EXTENT": f'{",".join(extent_bounds)} [EPSG:4326]',
            "SPACING": self.spacing,
            "INSET": 0,
            "RANDOMIZE": False,
            "IS_SPACING": True,
            "CRS": self.crs,
            "OUTPUT": "TEMPORARY_OUTPUT",
        }
        self.task = QgsProcessingAlgRunnerTask(self.alg, params, context, self.feedback)
        self.task.executed.connect(lambda successful, result: self.task_finished(context, successful, result))
        QgsApplication.taskManager().addTask(self.task)
    
    def task_finished(self, context, successful, results):
        if not successful:
            QgsMessageLog.logMessage(
                "Task finished unsucessfully",
                MESSAGE_CATEGORY,
                Qgis.Warning
        )
        output_layer = context.getMapLayer(results["OUTPUT"])
        # because getMapLayer doesn't transfer ownership, the layer will
        # be deleted when context goes out of scope and you'll get a
        # crash.
        # takeMapLayer transfers ownership so it's then safe to add it
        # to the project and give the project ownership.
        if output_layer and output_layer.isValid():
            self.lyr_pts = context.takeResultLayer(output_layer.id())
            self.lyr_pts.setName("Eclipse points")
            self.format_lyr_pts()
            QgsProject.instance().addMapLayer(self.lyr_pts)
            self.compute_points_data()
            self.lyr_umbra = self.create_umbra()
            if self.lyr_umbra and self.lyr_umbra.isValid():
                QgsProject.instance().addMapLayer(self.lyr_umbra)
    
    def format_lyr_pts(self):
        fields = [
            ("type_eclip", QVariant.String),
            ("lat", QVariant.String),
            ("lon", QVariant.String),
            ("magnitude", QVariant.Double),
            ("obscuration", QVariant.Double),
            ("c1", QVariant.DateTime),
            ("c2", QVariant.DateTime),
            ("max_eclip", QVariant.DateTime),
            ("c3", QVariant.DateTime),
            ("c4", QVariant.DateTime),
        ]
    
        self.lyr_pts.startEditing()
        for fld_name, fld_type in fields:
            self.lyr_pts.addAttribute(QgsField(fld_name, fld_type))
    
        self.lyr_pts.updateFields()
        self.lyr_pts.commitChanges()
    
    def create_umbra(self):
        if not self._umbra_dt:
            return
    
        expression = (
            "\"c2\" <= to_datetime('{0}', 'yyyy-MM-dd HH:mm:ss')"
            " and \"c3\" >= to_datetime('{0}', 'yyyy-MM-dd HH:mm:ss')"
        )
        self.lyr_pts.selectByExpression(expression.format(self._umbra_dt))
        min_bounding = processing.run(
            "qgis:minimumboundinggeometry",
            {
                "INPUT": QgsProcessingFeatureSourceDefinition(
                    self.lyr_pts.id(), selectedFeaturesOnly=True
                ),
                "FIELD": "",
                "TYPE": 3,  # convex
                "OUTPUT": "TEMPORARY_OUTPUT",
            },
        )
        umbra = min_bounding["OUTPUT"]
        umbra.setName("umbra")
        return umbra

    solar_te = Total_Eclipse(iface, eclipse_elements, umbra_datetime, spacing=spacing) solar_te.show()

2voto

Eli Bendersky Puntos 82298

Los cálculos mostrados en la ventana emergente se calculan sobre la marcha en función de la ubicación del marcador. Puede utilizar su implementación para obtener todo el texto mostrado para una ubicación en particular.

He creado una demo https://jsfiddle.net/310fm6se/1/ . puede introducir la latitud y la longitud para obtener las horas calculadas del eclipse.

Esto también se puede utilizar para automatizar y calcular los tiempos de eclipse para un grupo de coordenadas.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X