series;
    series.append(new QLineSeries());
    QLineSeries *s = series.first();
    QPen pen(getSeriesColor(0), 2, Qt::SolidLine);
    s->setPen(pen);
    qreal prevAz = polarSeries->at(0).x();
    qreal prevEl = polarSeries->at(0).y();
    for (int i = 1; i < polarSeries->count(); i++)
    {
        qreal az = polarSeries->at(i).x();
        qreal el = polarSeries->at(i).y();
        if ((prevAz > 270.0) && (az <= 90.0))
        {
            double elMid = Interpolation::interpolate(prevAz, prevEl, az+360.0, el, 360.0);
            s->append(360.0, elMid);
            series.append(new QLineSeries());
            s = series.last();
            s->setPen(pen);
            s->append(0.0, elMid);
            s->append(az, el);
        }
        else if ((prevAz <= 90.0) && (az > 270.0))
        {
            double elMid = Interpolation::interpolate(prevAz, prevEl, az-360.0, el, 0.0);
            s->append(0.0, elMid);
            series.append(new QLineSeries());
            s = series.last();
            s->setPen(pen);
            s->append(360.0, elMid);
            s->append(az, el);
        }
        else
            s->append(polarSeries->at(i));
        prevAz = az;
        prevEl = el;
    }
    for (int i = 0; i < series.length(); i++)
    {
        m_azElPolarChart->addSeries(series[i]);
        series[i]->attachAxis(angularAxis);
        series[i]->attachAxis(radialAxis);
    }
    // Create series with single point, so we can plot time of rising
    if (riseTime.isValid())
    {
        QLineSeries *riseSeries = new QLineSeries();
        riseSeries->append(polarSeries->at(riseIdx));
        riseSeries->setPointLabelsFormat(QString("Rise %1").arg(riseTime.time().toString("hh:mm")));
        riseSeries->setPointLabelsVisible(true);
        riseSeries->setPointLabelsClipping(false);
        m_azElPolarChart->addSeries(riseSeries);
        riseSeries->attachAxis(angularAxis);
        riseSeries->attachAxis(radialAxis);
    }
    // Create series with single point, so we can plot time of setting
    if (setTime.isValid())
    {
        QLineSeries *setSeries = new QLineSeries();
        setSeries->append(polarSeries->at(setIdx));
        setSeries->setPointLabelsFormat(QString("Set %1").arg(setTime.time().toString("hh:mm")));
        setSeries->setPointLabelsVisible(true);
        setSeries->setPointLabelsClipping(false);
        m_azElPolarChart->addSeries(setSeries);
        setSeries->attachAxis(angularAxis);
        setSeries->attachAxis(radialAxis);
    }
    if (maxElevation < 0)
        m_azElPolarChart->setTitle("Not visible from this latitude");
    else
        m_azElPolarChart->setTitle("");
    ui->chart->setChart(m_azElPolarChart);
    delete polarSeries;
    delete oldChart;
}
// Find target on the Map
void StarTrackerGUI::on_viewOnMap_clicked()
{
    QString target = m_settings.m_target == "Sun" || m_settings.m_target == "Moon" ? m_settings.m_target : "Star";
    FeatureWebAPIUtils::mapFind(target);
}
void StarTrackerGUI::updateChartSubSelect()
{
    if (ui->chartSelect->currentIndex() == 2)
    {
        ui->chartSubSelect->setItemText(6, QString("%1 MHz %2%3 Equatorial")
                                        .arg((int)std::round(m_settings.m_frequency/1e6))
                                        .arg((int)std::round(m_settings.m_beamwidth))
                                        .arg(QChar(0xb0)));
        ui->chartSubSelect->setItemText(7, QString("%1 MHz %2%3 Galactic")
                                        .arg((int)std::round(m_settings.m_frequency/1e6))
                                        .arg((int)std::round(m_settings.m_beamwidth))
                                        .arg(QChar(0xb0)));
    }
}
void StarTrackerGUI::on_chartSelect_currentIndexChanged(int index)
{
    bool oldState = ui->chartSubSelect->blockSignals(true);
    ui->chartSubSelect->clear();
    if (index == 0)
    {
        ui->chartSubSelect->addItem("Az/El vs time");
        ui->chartSubSelect->addItem("Polar");
    }
    else if (index == 2)
    {
        ui->chartSubSelect->addItem(QString("150 MHz 5%1 Equatorial").arg(QChar(0xb0)));
        ui->chartSubSelect->addItem(QString("150 MHz 5%1 Galactic").arg(QChar(0xb0)));
        ui->chartSubSelect->addItem("408 MHz 51' Equatorial");
        ui->chartSubSelect->addItem("408 MHz 51' Galactic");
        ui->chartSubSelect->addItem("1420 MHz 35' Equatorial");
        ui->chartSubSelect->addItem("1420 MHz 35' Galactic");
        ui->chartSubSelect->addItem("Custom Equatorial");
        ui->chartSubSelect->addItem("Custom Galactic");
        ui->chartSubSelect->setCurrentIndex(2);
        updateChartSubSelect();
    }
    else if (index == 3)
    {
        ui->chartSubSelect->addItem("Milky Way");
        ui->chartSubSelect->addItem("Milky Way annotated");
    }
    ui->chartSubSelect->blockSignals(oldState);
    plotChart();
}
void StarTrackerGUI::on_chartSubSelect_currentIndexChanged(int index)
{
    (void) index;
    plotChart();
}
double StarTrackerGUI::convertSolarFluxUnits(double sfu)
{
    switch (m_settings.m_solarFluxUnits)
    {
    case StarTrackerSettings::SFU:
        return sfu;
    case StarTrackerSettings::JANSKY:
        return Units::solarFluxUnitsToJansky(sfu);
    case StarTrackerSettings::WATTS_M_HZ:
        return Units::solarFluxUnitsToWattsPerMetrePerHertz(sfu);
    }
    return 0.0;
}
QString StarTrackerGUI::solarFluxUnit()
{
    switch (m_settings.m_solarFluxUnits)
    {
    case StarTrackerSettings::SFU:
        return "sfu";
    case StarTrackerSettings::JANSKY:
        return "Jy";
    case StarTrackerSettings::WATTS_M_HZ:
        return "Wm^-2Hz^-1";
    }
    return "";
}
// Calculate solar flux at given frequency in SFU
double StarTrackerGUI::calcSolarFlux(double freqMhz)
{
    if (m_solarFluxesValid)
    {
        double solarFlux;
        const int fluxes = sizeof(m_solarFluxFrequencies)/sizeof(*m_solarFluxFrequencies);
        int i;
        for (i = 0; i < fluxes; i++)
        {
            if (freqMhz < m_solarFluxFrequencies[i])
                break;
        }
        if (i == 0)
        {
            solarFlux = Interpolation::extrapolate((double)m_solarFluxFrequencies[0], (double)m_solarFluxes[0],
                                                    (double)m_solarFluxFrequencies[1], (double)m_solarFluxes[1],
                                                    freqMhz
                                                    );
        }
        else if (i == fluxes)
        {
            solarFlux = Interpolation::extrapolate((double)m_solarFluxFrequencies[fluxes-2], (double)m_solarFluxes[fluxes-2],
                                                    (double)m_solarFluxFrequencies[fluxes-1], (double)m_solarFluxes[fluxes-1],
                                                    freqMhz
                                                    );
        }
        else
        {
            solarFlux = Interpolation::interpolate((double)m_solarFluxFrequencies[i-1], (double)m_solarFluxes[i-1],
                                                    (double)m_solarFluxFrequencies[i], (double)m_solarFluxes[i],
                                                    freqMhz
                                                    );
        }
        return solarFlux;
    }
    else
    {
        return 0.0;
    }
}
void StarTrackerGUI::displaySolarFlux()
{
    if (((m_settings.m_solarFluxData == StarTrackerSettings::DRAO_2800) && (m_solarFlux == 0.0))
        || ((m_settings.m_solarFluxData != StarTrackerSettings::DRAO_2800) && !m_solarFluxesValid))
    {
        ui->solarFlux->setText("");
    }
    else
    {
        double solarFlux;
        double freqMhz = m_settings.m_frequency/1000000.0;
        if (m_settings.m_solarFluxData == StarTrackerSettings::DRAO_2800)
        {
            solarFlux = m_solarFlux;
            ui->solarFlux->setToolTip(QString("Solar flux density at 2800 MHz"));
        }
        else if (m_settings.m_solarFluxData == StarTrackerSettings::TARGET_FREQ)
        {
            solarFlux = calcSolarFlux(freqMhz);
            ui->solarFlux->setToolTip(QString("Solar flux density interpolated to %1 MHz").arg(freqMhz));
        }
        else
        {
            int idx = m_settings.m_solarFluxData-StarTrackerSettings::L_245;
            solarFlux = m_solarFluxes[idx];
            ui->solarFlux->setToolTip(QString("Solar flux density at %1 MHz").arg(m_solarFluxFrequencies[idx]));
        }
        ui->solarFlux->setText(QString("%1 %2").arg(convertSolarFluxUnits(solarFlux)).arg(solarFluxUnit()));
        ui->solarFlux->setCursorPosition(0);
        // Calculate value at target frequency in Jy for Radio Astronomy plugin
        m_starTracker->getInputMessageQueue()->push(StarTracker::MsgSetSolarFlux::create(Units::solarFluxUnitsToJansky(calcSolarFlux(freqMhz))));
    }
}
bool StarTrackerGUI::readSolarFlux()
{
    QFile file(getSolarFluxFilename());
    QDateTime lastModified = file.fileTime(QFileDevice::FileModificationTime);
    if (QDateTime::currentDateTime().secsTo(lastModified) >= -(60*60*24))
    {
        if (file.open(QIODevice::ReadOnly | QIODevice::Text))
        {
            QByteArray bytes = file.readLine();
            QString string(bytes);
            // HHMMSS 245    410     610    1415   2695   4995   8800  15400   Mhz
            // 000000 000019 000027 000037 000056 000073 000116 000202 000514  sfu
            // Occasionally, file will contain ////// in a column, presumably to indicate no data
            QRegExp re("([0-9]{2})([0-9]{2})([0-9]{2}) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+)");
            if (re.indexIn(string) != -1)
            {
                for (int i = 0; i < 8; i++)
                    m_solarFluxes[i] = re.capturedTexts()[i+4].toInt();
                m_solarFluxesValid = true;
                displaySolarFlux();
                plotChart();
                return true;
            }
            else
                qDebug() << "StarTrackerGUI::readSolarFlux: No match for " << string;
        }
    }
    else
        qDebug() << "StarTrackerGUI::readSolarFlux: Solar flux data is more than 1 day old";
    return false;
}
void StarTrackerGUI::networkManagerFinished(QNetworkReply *reply)
{
    ui->solarFlux->setText(""); // Don't show obsolete data
    QNetworkReply::NetworkError replyError = reply->error();
    if (replyError)
    {
        qWarning() << "StarTrackerGUI::networkManagerFinished:"
                << " error(" << (int) replyError
                << "): " << replyError
                << ": " << reply->errorString();
    }
    else
    {
        QString answer = reply->readAll();
        QRegExp re("\\| Observed Flux Density\\<\\/th\\>\\ | ([0-9]+(\\.[0-9]+)?)\\<\\/td\\>");
        if (re.indexIn(answer) != -1)
        {
            m_solarFlux = re.capturedTexts()[1].toDouble();
            displaySolarFlux();
        }
        else
            qDebug() << "StarTrackerGUI::networkManagerFinished - No Solar flux found: " << answer;
    }
    reply->deleteLater();
}
QString StarTrackerGUI::getSolarFluxFilename()
{
    return HttpDownloadManager::downloadDir() + "/solar_flux.srd";
}
void StarTrackerGUI::updateSolarFlux(bool all)
{
    qDebug() << "StarTrackerGUI: Updating Solar flux data";
    if ((m_settings.m_solarFluxData != StarTrackerSettings::DRAO_2800) || all)
    {
        QDate today = QDateTime::currentDateTimeUtc().date();
        QString solarFluxFile = getSolarFluxFilename();
        if (m_dlm.confirmDownload(solarFluxFile, nullptr, 0))
        {
            QString urlString = QString("https://www.sws.bom.gov.au/Category/World Data Centre/Data Display and Download/Solar Radio/station/learmonth/SRD/%1/L%2.SRD")
                                    .arg(today.year()).arg(today.toString("yyMMdd"));
            m_dlm.download(QUrl(urlString), solarFluxFile, this);
        }
    }
    if ((m_settings.m_solarFluxData == StarTrackerSettings::DRAO_2800) || all)
    {
        m_networkRequest.setUrl(QUrl("https://www.spaceweather.gc.ca/forecast-prevision/solar-solaire/solarflux/sx-4-en.php"));
        m_networkManager->get(m_networkRequest);
    }
}
void StarTrackerGUI::autoUpdateSolarFlux()
{
    updateSolarFlux(false);
}
void StarTrackerGUI::on_downloadSolarFlux_clicked()
{
    updateSolarFlux(true);
}
void StarTrackerGUI::on_darkTheme_clicked(bool checked)
{
    m_settings.m_chartsDarkTheme = checked;
    if (m_solarFluxChart) {
        m_solarFluxChart->setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
    }
    m_chart.setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
    plotChart();
    applySettings();
}
void StarTrackerGUI::on_drawSun_clicked(bool checked)
{
    m_settings.m_drawSunOnSkyTempChart = checked;
    plotChart();
    applySettings();
}
void StarTrackerGUI::on_drawMoon_clicked(bool checked)
{
    m_settings.m_drawMoonOnSkyTempChart = checked;
    plotChart();
    applySettings();
}
void StarTrackerGUI::downloadFinished(const QString& filename, bool success)
{
    (void) filename;
    if (success)
        readSolarFlux();
} |