File indexing completed on 2024-04-28 03:50:24

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2011 Guillaume Martres <smarter@ubuntu.com>
0004 //
0005 
0006 #include "SatellitesTLEItem.h"
0007 
0008 #include "MarbleClock.h"
0009 #include "MarbleDebug.h"
0010 #include "MarbleGlobal.h"
0011 #include "GeoPainter.h"
0012 #include "GeoDataCoordinates.h"
0013 #include "GeoDataPlacemark.h"
0014 #include "GeoDataStyle.h"
0015 #include "GeoDataTrack.h"
0016 
0017 #include <sgp4ext.h>
0018 
0019 #include <QFile>
0020 #include <QDateTime>
0021 #include <QAction>
0022 #include <QColor>
0023 
0024 #include <cmath>
0025 
0026 namespace Marble {
0027 
0028 SatellitesTLEItem::SatellitesTLEItem( const QString &name,
0029                                       elsetrec satrec,
0030                                       const MarbleClock *clock )
0031     : TrackerPluginItem( name ),
0032       m_satrec( satrec ),
0033       m_track( new GeoDataTrack() ),
0034       m_clock( clock )
0035 {
0036     double tumin, mu, xke, j2, j3, j4, j3oj2;
0037     double radiusearthkm;
0038     getgravconst( wgs84, tumin, mu, radiusearthkm, xke, j2, j3, j4, j3oj2 );
0039     m_earthSemiMajorAxis = radiusearthkm;
0040 
0041     setDescription();
0042 
0043     placemark()->setVisualCategory(GeoDataPlacemark::Satellite);
0044     placemark()->setZoomLevel( 0 );
0045     placemark()->setGeometry( m_track );
0046 
0047     update();
0048 }
0049 
0050 void SatellitesTLEItem::setDescription()
0051 {
0052     QFile templateFile(QStringLiteral(":/marble/satellites/satellite.html"));
0053     if (!templateFile.open(QIODevice::ReadOnly)) {
0054         placemark()->setDescription(QObject::tr("No info available."));
0055         return;
0056     }
0057     QString html = templateFile.readAll();
0058 
0059     html.replace("%name%", name());
0060     html.replace("%noradId%", QString::number(m_satrec.satnum));
0061     html.replace("%perigee%", QString::number(perigee(), 'f', 2));
0062     html.replace("%apogee%", QString::number(apogee(), 'f', 2));
0063     html.replace("%inclination%", QString::number(inclination(), 'f', 2));
0064     html.replace("%period%", QString::number(period(), 'f', 2));
0065     html.replace("%semiMajorAxis%", QString::number(semiMajorAxis(), 'f', 2));
0066 
0067     placemark()->setDescription( html );
0068 }
0069 
0070 void SatellitesTLEItem::update()
0071 {
0072     if( !isEnabled() ) {
0073         return;
0074     }
0075 
0076     QDateTime startTime = m_clock->dateTime();
0077     QDateTime endTime = startTime;
0078     if( isTrackVisible() ) {
0079         startTime = startTime.addSecs( -2 * 60 );
0080         endTime = startTime.addSecs( period() );
0081     }
0082 
0083     m_track->removeBefore( startTime );
0084     m_track->removeAfter( endTime );
0085 
0086     addPointAt( m_clock->dateTime() );
0087 
0088     // time interval between each point in the track, in seconds
0089     double step = period() / 100.0;
0090 
0091     for ( double i = startTime.toTime_t(); i < endTime.toTime_t(); i += step ) {
0092         // No need to add points in this interval
0093         if ( i >= m_track->firstWhen().toTime_t() ) {
0094             i = m_track->lastWhen().toTime_t() + step;
0095         }
0096 
0097         addPointAt( QDateTime::fromTime_t( i ) );
0098     }
0099 }
0100 
0101 void SatellitesTLEItem::addPointAt( const QDateTime &dateTime )
0102 {
0103     // in minutes
0104     double timeSinceEpoch = (double)( dateTime.toTime_t() -
0105         timeAtEpoch().toTime_t() ) / 60.0;
0106 
0107     double r[3], v[3];
0108     sgp4( wgs84, m_satrec, timeSinceEpoch, r, v );
0109 
0110     GeoDataCoordinates coordinates = fromTEME(
0111         r[0], r[1], r[2], gmst( timeSinceEpoch ) );
0112     if ( m_satrec.error != 0 ) {
0113         return;
0114     }
0115 
0116     m_track->addPoint( dateTime, coordinates);
0117 }
0118 
0119 QDateTime SatellitesTLEItem::timeAtEpoch() const
0120 {
0121     int year = m_satrec.epochyr + ( m_satrec.epochyr < 57 ? 2000 : 1900 );
0122 
0123     int month, day, hours, minutes;
0124     double seconds;
0125     days2mdhms( year, m_satrec.epochdays, month, day, hours , minutes, seconds );
0126 
0127     int ms = fmod(seconds * 1000.0, 1000.0);
0128 
0129     return QDateTime( QDate( year, month, day ),
0130                       QTime( hours, minutes, (int)seconds, ms ),
0131                       Qt::UTC );
0132 }
0133 
0134 double SatellitesTLEItem::period() const
0135 {
0136     // no := mean motion (rad / min)
0137     return 60 * (2 * M_PI / m_satrec.no);
0138 }
0139 
0140 double SatellitesTLEItem::apogee() const
0141 {
0142     return m_satrec.alta * m_earthSemiMajorAxis;
0143 }
0144 
0145 double SatellitesTLEItem::perigee() const
0146 {
0147     return m_satrec.altp * m_earthSemiMajorAxis;
0148 }
0149 
0150 double SatellitesTLEItem::semiMajorAxis() const
0151 {
0152 
0153     return m_satrec.a * m_earthSemiMajorAxis;
0154 }
0155 
0156 double SatellitesTLEItem::inclination() const
0157 {
0158     return m_satrec.inclo / M_PI * 180;
0159 }
0160 
0161 GeoDataCoordinates SatellitesTLEItem::fromTEME( double x,
0162                                                 double y,
0163                                                 double z,
0164                                                 double gmst ) const
0165 {
0166     double lon = atan2( y, x );
0167     // Rotate the angle by gmst (the origin goes from the vernal equinox
0168     // point to the Greenwich Meridian)
0169     lon = GeoDataCoordinates::normalizeLon( fmod(lon - gmst, 2 * M_PI) );
0170 
0171     double lat = atan2( z, sqrt( x*x + y*y ) );
0172 
0173     //TODO: determine if this is worth the extra precision
0174     // Algorithm from https://celestrak.com/columns/v02n03/
0175     //TODO: demonstrate it.
0176     double a = m_earthSemiMajorAxis;
0177     double planetRadius = sqrt( x*x + y*y );
0178     double latp = lat;
0179     double C;
0180     for ( int i = 0; i < 3; i++ ) {
0181         C = 1 / sqrt( 1 - square( m_satrec.ecco * sin( latp ) ) );
0182         lat = atan2( z + a * C * square( m_satrec.ecco ) * sin( latp ), planetRadius );
0183     }
0184 
0185     double alt = planetRadius / cos( lat ) - a * C;
0186 
0187     lat = GeoDataCoordinates::normalizeLat( lat );
0188 
0189     return GeoDataCoordinates( lon, lat, alt * 1000 );
0190 }
0191 
0192 double SatellitesTLEItem::gmst( double minutesP ) const
0193 {
0194     // Earth rotation rate in rad/min, from sgp4io.cpp
0195     double rptim = 4.37526908801129966e-3;
0196     return fmod( m_satrec.gsto + rptim * minutesP, 2 * M_PI );
0197 }
0198 
0199 double SatellitesTLEItem::square( double x )
0200 {
0201     return x * x;
0202 }
0203 
0204 } // namespace Marble