1   /* Copyright 2002-2024 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.forces.radiation;
18  
19  import java.util.List;
20  
21  import org.hipparchus.CalculusFieldElement;
22  import org.hipparchus.analysis.polynomials.PolynomialFunction;
23  import org.hipparchus.analysis.polynomials.PolynomialsUtils;
24  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
25  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26  import org.hipparchus.geometry.euclidean.threed.Rotation;
27  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.hipparchus.util.FastMath;
30  import org.hipparchus.util.FieldSinCos;
31  import org.hipparchus.util.MathUtils;
32  import org.hipparchus.util.SinCos;
33  import org.orekit.annotation.DefaultDataContext;
34  import org.orekit.bodies.OneAxisEllipsoid;
35  import org.orekit.data.DataContext;
36  import org.orekit.forces.ForceModel;
37  import org.orekit.frames.FieldStaticTransform;
38  import org.orekit.frames.Frame;
39  import org.orekit.frames.StaticTransform;
40  import org.orekit.propagation.FieldSpacecraftState;
41  import org.orekit.propagation.SpacecraftState;
42  import org.orekit.time.AbsoluteDate;
43  import org.orekit.time.FieldAbsoluteDate;
44  import org.orekit.time.TimeScale;
45  import org.orekit.utils.Constants;
46  import org.orekit.utils.ExtendedPVCoordinatesProvider;
47  import org.orekit.utils.ParameterDriver;
48  
49  /** The Knocke Earth Albedo and IR emission force model.
50   * <p>
51   * This model is based on "EARTH RADIATION PRESSURE EFFECTS ON SATELLITES", 1988, by P. C. Knocke, J. C. Ries, and B. D. Tapley.
52   * </p> <p>
53   * This model represents the effects of radiation pressure coming from the Earth.
54   * It considers Solar radiation which has been reflected by Earth (albedo) and Earth infrared emissions.
55   * The planet is considered as a sphere and is divided into elementary areas.
56   * Each elementary area is considered as a plane and emits radiation according to Lambert's law.
57   * The flux the satellite receives is then equal to the sum of the elementary fluxes coming from Earth.
58   * </p> <p>
59   * The radiative model of the satellite, and its ability to diffuse, reflect  or absorb radiation is handled
60   * by a {@link RadiationSensitive radiation sensitive model}.
61   * </p> <p>
62   * <b>Caution:</b> This model is only suitable for Earth. Using it with another central body is prone to error..
63   * </p>
64   *
65   * @author Thomas Paulet
66   * @since 10.3
67   */
68  public class KnockeRediffusedForceModel implements ForceModel {
69  
70      /** 7Earth rotation around Sun pulsation in rad/sec. */
71      private static final double EARTH_AROUND_SUN_PULSATION = MathUtils.TWO_PI / Constants.JULIAN_YEAR;
72  
73      /** Coefficient for solar irradiance computation. */
74      private static final double ES_COEFF = 4.5606E-6;
75  
76      /** First coefficient for albedo computation. */
77      private static final double A0 = 0.34;
78  
79      /** Second coefficient for albedo computation. */
80      private static final double C0 = 0.;
81  
82      /** Third coefficient for albedo computation. */
83      private static final double C1 = 0.10;
84  
85      /** Fourth coefficient for albedo computation. */
86      private static final double C2 = 0.;
87  
88      /** Fifth coefficient for albedo computation. */
89      private static final double A2 = 0.29;
90  
91      /** First coefficient for Earth emissivity computation. */
92      private static final double E0 = 0.68;
93  
94      /** Second coefficient for Earth emissivity computation. */
95      private static final double K0 = 0.;
96  
97      /** Third coefficient for Earth emissivity computation. */
98      private static final double K1 = -0.07;
99  
100     /** Fourth coefficient for Earth emissivity computation. */
101     private static final double K2 = 0.;
102 
103     /** Fifth coefficient for Earth emissivity computation. */
104     private static final double E2 = -0.18;
105 
106     /** Sun model. */
107     private final ExtendedPVCoordinatesProvider sun;
108 
109     /** Spacecraft. */
110     private final RadiationSensitive spacecraft;
111 
112     /** Angular resolution for emissivity and albedo computation in rad. */
113     private final double angularResolution;
114 
115     /** Earth equatorial radius in m. */
116     private final double equatorialRadius;
117 
118     /** Reference date for periodic terms: December 22nd 1981.
119      * Without more precision, the choice is to set it at midnight, UTC. */
120     private final AbsoluteDate referenceEpoch;
121 
122     /** Default Constructor.
123      * <p>This constructor uses the {@link DataContext#getDefault() default data context}</p>.
124      * @param sun Sun model
125      * @param spacecraft the object physical and geometrical information
126      * @param equatorialRadius the Earth equatorial radius in m
127      * @param angularResolution angular resolution in rad
128      */
129     @DefaultDataContext
130     public KnockeRediffusedForceModel (final ExtendedPVCoordinatesProvider sun,
131                                        final RadiationSensitive spacecraft,
132                                        final double equatorialRadius,
133                                        final double angularResolution) {
134 
135         this(sun, spacecraft, equatorialRadius, angularResolution, DataContext.getDefault().getTimeScales().getUTC());
136     }
137 
138     /** General constructor.
139      * @param sun Sun model
140      * @param spacecraft the object physical and geometrical information
141      * @param equatorialRadius the Earth equatorial radius in m
142      * @param angularResolution angular resolution in rad
143      * @param utc the UTC time scale to define reference epoch
144      */
145     public KnockeRediffusedForceModel (final ExtendedPVCoordinatesProvider sun,
146                                        final RadiationSensitive spacecraft,
147                                        final double equatorialRadius,
148                                        final double angularResolution,
149                                        final TimeScale utc) {
150         this.sun               = sun;
151         this.spacecraft        = spacecraft;
152         this.equatorialRadius  = equatorialRadius;
153         this.angularResolution = angularResolution;
154         this.referenceEpoch    = new AbsoluteDate(1981, 12, 22, 0, 0, 0.0, utc);
155     }
156 
157 
158     /** {@inheritDoc} */
159     @Override
160     public boolean dependsOnPositionOnly() {
161         return false;
162     }
163 
164     /** {@inheritDoc} */
165     @Override
166     public Vector3D acceleration(final SpacecraftState s,
167                                  final double[] parameters) {
168 
169         // Get date
170         final AbsoluteDate date = s.getDate();
171 
172         // Get frame
173         final Frame frame = s.getFrame();
174 
175         // Get satellite position
176         final Vector3D satellitePosition = s.getPosition();
177 
178         // Get Sun position
179         final Vector3D sunPosition = sun.getPosition(date, frame);
180 
181         // Get spherical Earth model
182         final OneAxisEllipsoid earth = new OneAxisEllipsoid(equatorialRadius, 0.0, frame);
183 
184         // Project satellite on Earth as vector
185         final Vector3D projectedToGround = satellitePosition.normalize().scalarMultiply(equatorialRadius);
186 
187         // Get elementary vector east for Earth browsing using rotations
188         final Vector3D east = earth.transform(satellitePosition, frame, date).getEast();
189 
190         // Initialize rediffused flux with elementary flux coming from the circular area around the projected satellite
191         final double centerArea = MathUtils.TWO_PI * equatorialRadius * equatorialRadius *
192                                  (1.0 - FastMath.cos(angularResolution));
193         Vector3D rediffusedFlux = computeElementaryFlux(s, projectedToGround, sunPosition, earth, centerArea);
194 
195         // Sectorize the part of Earth which is seen by the satellite into crown sectors with constant angular resolution
196         for (double eastAxisOffset = 1.5 * angularResolution;
197              eastAxisOffset < FastMath.asin(equatorialRadius / satellitePosition.getNorm());
198              eastAxisOffset = eastAxisOffset + angularResolution) {
199 
200             // Build rotation transformations to get first crown elementary sector center
201             final StaticTransform eastRotation = StaticTransform.of(date,
202                                                           new Rotation(east, eastAxisOffset, RotationConvention.VECTOR_OPERATOR));
203 
204             // Get first elementary crown sector center
205             final Vector3D firstCrownSectorCenter = eastRotation.transformPosition(projectedToGround);
206 
207             // Browse the entire crown
208             for (double radialAxisOffset = 0.5 * angularResolution;
209                  radialAxisOffset < MathUtils.TWO_PI;
210                  radialAxisOffset = radialAxisOffset + angularResolution) {
211 
212                 // Build rotation transformations to get elementary area center
213                 final StaticTransform radialRotation  = StaticTransform.of(date,
214                                                                 new Rotation(projectedToGround, radialAxisOffset, RotationConvention.VECTOR_OPERATOR));
215 
216                 // Get current elementary crown sector center
217                 final Vector3D currentCenter = radialRotation.transformPosition(firstCrownSectorCenter);
218 
219                 // Compute current elementary crown sector area, it results of the integration of an elementary crown sector
220                 // over the angular resolution
221                 final double sectorArea = equatorialRadius * equatorialRadius *
222                                           2.0 * angularResolution * FastMath.sin(0.5 * angularResolution) * FastMath.sin(eastAxisOffset);
223 
224                 // Add current sector contribution to total rediffused flux
225                 rediffusedFlux = rediffusedFlux.add(computeElementaryFlux(s, currentCenter, sunPosition, earth, sectorArea));
226             }
227         }
228 
229         return spacecraft.radiationPressureAcceleration(s, rediffusedFlux, parameters);
230     }
231 
232 
233     /** {@inheritDoc} */
234     @Override
235     public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(final FieldSpacecraftState<T> s,
236                                                                          final T[] parameters) {
237         // Get date
238         final FieldAbsoluteDate<T> date = s.getDate();
239 
240         // Get frame
241         final Frame frame = s.getFrame();
242 
243         // Get zero
244         final T zero = date.getField().getZero();
245 
246         // Get satellite position
247         final FieldVector3D<T> satellitePosition = s.getPosition();
248 
249         // Get Sun position
250         final FieldVector3D<T> sunPosition = sun.getPosition(date, frame);
251 
252         // Get spherical Earth model
253         final OneAxisEllipsoid earth = new OneAxisEllipsoid(equatorialRadius, 0.0, frame);
254 
255         // Project satellite on Earth as vector
256         final FieldVector3D<T> projectedToGround = satellitePosition.normalize().scalarMultiply(equatorialRadius);
257 
258         // Get elementary vector east for Earth browsing using rotations
259         final FieldVector3D<T> east = earth.transform(satellitePosition, frame, date).getEast();
260 
261         // Initialize rediffused flux with elementary flux coming from the circular area around the projected satellite
262         final T centerArea = zero.getPi().multiply(2.0).multiply(equatorialRadius).multiply(equatorialRadius).
263                         multiply(1.0 - FastMath.cos(angularResolution));
264         FieldVector3D<T> rediffusedFlux = computeElementaryFlux(s, projectedToGround, sunPosition, earth, centerArea);
265 
266         // Sectorize the part of Earth which is seen by the satellite into crown sectors with constant angular resolution
267         for (double eastAxisOffset = 1.5 * angularResolution;
268              eastAxisOffset < FastMath.asin(equatorialRadius / satellitePosition.getNorm().getReal());
269              eastAxisOffset = eastAxisOffset + angularResolution) {
270 
271             // Build rotation transformations to get first crown elementary sector center
272             final FieldStaticTransform<T> eastRotation = FieldStaticTransform.of(date,
273                                                                         new FieldRotation<>(east,
274                                                                                             zero.add(eastAxisOffset),
275                                                                                             RotationConvention.VECTOR_OPERATOR));
276 
277             // Get first elementary crown sector center
278             final FieldVector3D<T> firstCrownSectorCenter = eastRotation.transformPosition(projectedToGround);
279 
280             // Browse the entire crown
281             for (double radialAxisOffset = 0.5 * angularResolution;
282                  radialAxisOffset < MathUtils.TWO_PI;
283                  radialAxisOffset = radialAxisOffset + angularResolution) {
284 
285                 // Build rotation transformations to get elementary area center
286                 final FieldStaticTransform<T> radialRotation  = FieldStaticTransform.of(date,
287                                                                                new FieldRotation<>(projectedToGround,
288                                                                                                    zero.add(radialAxisOffset),
289                                                                                                    RotationConvention.VECTOR_OPERATOR));
290 
291                 // Get current elementary crown sector center
292                 final FieldVector3D<T> currentCenter = radialRotation.transformPosition(firstCrownSectorCenter);
293 
294                 // Compute current elementary crown sector area, it results of the integration of an elementary crown sector
295                 // over the angular resolution
296                 final T sectorArea = zero.newInstance(equatorialRadius * equatorialRadius *
297                         2.0 * angularResolution * FastMath.sin(0.5 * angularResolution) * FastMath.sin(eastAxisOffset));
298 
299                 // Add current sector contribution to total rediffused flux
300                 rediffusedFlux = rediffusedFlux.add(computeElementaryFlux(s, currentCenter, sunPosition, earth, sectorArea));
301             }
302         }
303 
304         return spacecraft.radiationPressureAcceleration(s, rediffusedFlux, parameters);
305     }
306 
307 
308     /** {@inheritDoc} */
309     @Override
310     public List<ParameterDriver> getParametersDrivers() {
311         return spacecraft.getRadiationParametersDrivers();
312     }
313 
314     /** Compute Earth albedo.
315      * Albedo value represents the fraction of solar radiative flux that is reflected by Earth.
316      * Its value is in [0;1].
317      * @param date the date
318      * @param phi the equatorial latitude in rad
319      * @return the albedo in [0;1]
320      */
321     public double computeAlbedo(final AbsoluteDate date, final double phi) {
322 
323         // Get duration since coefficient reference epoch
324         final double deltaT = date.durationFrom(referenceEpoch);
325 
326         // Compute 1rst Legendre polynomial coeficient
327         final SinCos sc = FastMath.sinCos(EARTH_AROUND_SUN_PULSATION * deltaT);
328         final double A1 = C0 +
329                           C1 * sc.cos() +
330                           C2 * sc.sin();
331 
332         // Get 1rst and 2nd order Legendre polynomials
333         final PolynomialFunction firstLegendrePolynomial  = PolynomialsUtils.createLegendrePolynomial(1);
334         final PolynomialFunction secondLegendrePolynomial = PolynomialsUtils.createLegendrePolynomial(2);
335 
336         // Get latitude sinus
337         final double sinPhi = FastMath.sin(phi);
338 
339         // Compute albedo
340         return A0 +
341                A1 * firstLegendrePolynomial.value(sinPhi) +
342                A2 * secondLegendrePolynomial.value(sinPhi);
343 
344     }
345 
346 
347     /** Compute Earth albedo.
348      * Albedo value represents the fraction of solar radiative flux that is reflected by Earth.
349      * Its value is in [0;1].
350      * @param date the date
351      * @param phi the equatorial latitude in rad
352      * @param <T> extends CalculusFieldElement
353      * @return the albedo in [0;1]
354      */
355     public <T extends CalculusFieldElement<T>> T computeAlbedo(final FieldAbsoluteDate<T> date, final T phi) {
356 
357         // Get duration since coefficient reference epoch
358         final T deltaT = date.durationFrom(referenceEpoch);
359 
360         // Compute 1rst Legendre polynomial coeficient
361         final FieldSinCos<T> sc = FastMath.sinCos(deltaT.multiply(EARTH_AROUND_SUN_PULSATION));
362         final T A1 = sc.cos().multiply(C1).add(
363                      sc.sin().multiply(C2)).add(C0);
364 
365         // Get 1rst and 2nd order Legendre polynomials
366         final PolynomialFunction firstLegendrePolynomial  = PolynomialsUtils.createLegendrePolynomial(1);
367         final PolynomialFunction secondLegendrePolynomial = PolynomialsUtils.createLegendrePolynomial(2);
368 
369         // Get latitude sinus
370         final T sinPhi = FastMath.sin(phi);
371 
372         // Compute albedo
373         return firstLegendrePolynomial.value(sinPhi).multiply(A1).add(
374                secondLegendrePolynomial.value(sinPhi).multiply(A2)).add(A0);
375 
376     }
377 
378     /** Compute Earth emisivity.
379      * Emissivity is used to compute the infrared flux that is emitted by Earth.
380      * Its value is in [0;1].
381      * @param date the date
382      * @param phi the equatorial latitude in rad
383      * @return the emissivity in [0;1]
384      */
385     public double computeEmissivity(final AbsoluteDate date, final double phi) {
386 
387         // Get duration since coefficient reference epoch
388         final double deltaT = date.durationFrom(referenceEpoch);
389 
390         // Compute 1rst Legendre polynomial coefficient
391         final SinCos sc = FastMath.sinCos(EARTH_AROUND_SUN_PULSATION * deltaT);
392         final double E1 = K0 +
393                           K1 * sc.cos() +
394                           K2 * sc.sin();
395 
396         // Get 1rst and 2nd order Legendre polynomials
397         final PolynomialFunction firstLegendrePolynomial  = PolynomialsUtils.createLegendrePolynomial(1);
398         final PolynomialFunction secondLegendrePolynomial = PolynomialsUtils.createLegendrePolynomial(2);
399 
400         // Get latitude sinus
401         final double sinPhi = FastMath.sin(phi);
402 
403         // Compute albedo
404         return E0 +
405                E1 * firstLegendrePolynomial.value(sinPhi) +
406                E2 * secondLegendrePolynomial.value(sinPhi);
407 
408     }
409 
410 
411     /** Compute Earth emisivity.
412      * Emissivity is used to compute the infrared flux that is emitted by Earth.
413      * Its value is in [0;1].
414      * @param date the date
415      * @param phi the equatorial latitude in rad
416      * @param <T> extends CalculusFieldElement
417      * @return the emissivity in [0;1]
418      */
419     public <T extends CalculusFieldElement<T>> T computeEmissivity(final FieldAbsoluteDate<T> date, final T phi) {
420 
421         // Get duration since coefficient reference epoch
422         final T deltaT = date.durationFrom(referenceEpoch);
423 
424         // Compute 1rst Legendre polynomial coeficient
425         final FieldSinCos<T> sc = FastMath.sinCos(deltaT.multiply(EARTH_AROUND_SUN_PULSATION));
426         final T E1 = sc.cos().multiply(K1).add(
427                      sc.sin().multiply(K2)).add(K0);
428 
429         // Get 1rst and 2nd order Legendre polynomials
430         final PolynomialFunction firstLegendrePolynomial  = PolynomialsUtils.createLegendrePolynomial(1);
431         final PolynomialFunction secondLegendrePolynomial = PolynomialsUtils.createLegendrePolynomial(2);
432 
433         // Get latitude sinus
434         final T sinPhi = FastMath.sin(phi);
435 
436         // Compute albedo
437         return firstLegendrePolynomial.value(sinPhi).multiply(E1).add(
438                secondLegendrePolynomial.value(sinPhi).multiply(E2)).add(E0);
439 
440     }
441 
442     /** Compute total solar flux impacting Earth.
443      * @param sunPosition the Sun position in an Earth centered frame
444      * @return the total solar flux impacting Earth in J/m^3
445      */
446     public double computeSolarFlux(final Vector3D sunPosition) {
447 
448         // Compute Earth - Sun distance in UA
449         final double earthSunDistance = sunPosition.getNorm() / Constants.JPL_SSD_ASTRONOMICAL_UNIT;
450 
451         // Compute Solar flux
452         return ES_COEFF * Constants.SPEED_OF_LIGHT / (earthSunDistance * earthSunDistance);
453     }
454 
455 
456     /** Compute total solar flux impacting Earth.
457      * @param sunPosition the Sun position in an Earth centered frame
458      * @param <T> extends CalculusFieldElement
459      * @return the total solar flux impacting Earth in J/m^3
460      */
461     public <T extends CalculusFieldElement<T>> T computeSolarFlux(final FieldVector3D<T> sunPosition) {
462 
463         // Compute Earth - Sun distance in UA
464         final T earthSunDistance = sunPosition.getNorm().divide(Constants.JPL_SSD_ASTRONOMICAL_UNIT);
465 
466         // Compute Solar flux
467         return earthSunDistance.multiply(earthSunDistance).reciprocal().multiply(ES_COEFF * Constants.SPEED_OF_LIGHT);
468     }
469 
470 
471     /** Compute elementary rediffused flux on satellite.
472      * @param state the current spacecraft state
473      * @param elementCenter the position of the considered area center
474      * @param sunPosition the position of the Sun in the spacecraft frame
475      * @param earth the Earth model
476      * @param elementArea the area of the current element
477      * @return the rediffused flux from considered element on the spacecraft
478      */
479     public Vector3D computeElementaryFlux(final SpacecraftState state,
480                                           final Vector3D elementCenter,
481                                           final Vector3D sunPosition,
482                                           final OneAxisEllipsoid earth,
483                                           final double elementArea) {
484 
485         // Get satellite position
486         final Vector3D satellitePosition = state.getPosition();
487 
488         // Get current date
489         final AbsoluteDate date = state.getDate();
490 
491         // Get frame
492         final Frame frame = state.getFrame();
493 
494         // Get solar flux impacting Earth
495         final double solarFlux = computeSolarFlux(sunPosition);
496 
497         // Get satellite viewing angle as seen from current elementary area
498         final double alpha = Vector3D.angle(elementCenter, satellitePosition);
499 
500         // Check that satellite sees the current area
501         if (FastMath.abs(alpha) < MathUtils.SEMI_PI) {
502 
503             // Get current elementary area center latitude
504             final double currentLatitude = earth.transform(elementCenter, frame, date).getLatitude();
505 
506             // Compute Earth emissivity value
507             final double e = computeEmissivity(date, currentLatitude);
508 
509             // Initialize albedo
510             double a = 0.0;
511 
512             // Check if elementary area is in day light
513             final double sunAngle = Vector3D.angle(elementCenter, sunPosition);
514 
515             if (FastMath.abs(sunAngle) < MathUtils.SEMI_PI) {
516                 // Elementary area is in day light, compute albedo value
517                 a = computeAlbedo(date, currentLatitude);
518             }
519 
520             // Compute elementary area contribution to rediffused flux
521             final double albedoAndIR = a * solarFlux * FastMath.cos(sunAngle) +
522                                        e * solarFlux * 0.25;
523 
524             // Compute elementary area - satellite vector and distance
525             final Vector3D r = satellitePosition.subtract(elementCenter);
526             final double rNorm = r.getNorm();
527 
528             // Compute attenuated projected elemetary area vector
529             final Vector3D projectedAreaVector = r.scalarMultiply(elementArea * FastMath.cos(alpha) /
530                                                                  (FastMath.PI * rNorm * rNorm * rNorm));
531 
532             // Compute elementary radiation flux from current elementary area
533             return projectedAreaVector.scalarMultiply(albedoAndIR / Constants.SPEED_OF_LIGHT);
534 
535         } else {
536 
537             // Spacecraft does not see the elementary area
538             return new Vector3D(0.0, 0.0, 0.0);
539         }
540 
541     }
542 
543 
544     /** Compute elementary rediffused flux on satellite.
545      * @param state the current spacecraft state
546      * @param elementCenter the position of the considered area center
547      * @param sunPosition the position of the Sun in the spacecraft frame
548      * @param earth the Earth model
549      * @param elementArea the area of the current element
550      * @param <T> extends CalculusFieldElement
551      * @return the rediffused flux from considered element on the spacecraft
552      */
553     public <T extends CalculusFieldElement<T>> FieldVector3D<T> computeElementaryFlux(final FieldSpacecraftState<T> state,
554                                                                                       final FieldVector3D<T> elementCenter,
555                                                                                       final FieldVector3D<T> sunPosition,
556                                                                                       final OneAxisEllipsoid earth,
557                                                                                       final T elementArea) {
558 
559         // Get satellite position
560         final FieldVector3D<T> satellitePosition = state.getPosition();
561 
562         // Get current date
563         final FieldAbsoluteDate<T> date = state.getDate();
564 
565         // Get frame
566         final Frame frame = state.getFrame();
567 
568         // Get zero
569         final T zero = date.getField().getZero();
570 
571         // Get solar flux impacting Earth
572         final T solarFlux = computeSolarFlux(sunPosition);
573 
574         // Get satellite viewing angle as seen from current elementary area
575         final T alpha = FieldVector3D.angle(elementCenter, satellitePosition);
576 
577         // Check that satellite sees the current area
578         if (FastMath.abs(alpha).getReal() < MathUtils.SEMI_PI) {
579 
580             // Get current elementary area center latitude
581             final T currentLatitude = earth.transform(elementCenter, frame, date).getLatitude();
582 
583             // Compute Earth emissivity value
584             final T e = computeEmissivity(date, currentLatitude);
585 
586             // Initialize albedo
587             T a = zero;
588 
589             // Check if elementary area is in day light
590             final T sunAngle = FieldVector3D.angle(elementCenter, sunPosition);
591 
592             if (FastMath.abs(sunAngle).getReal() < MathUtils.SEMI_PI) {
593                 // Elementary area is in day light, compute albedo value
594                 a = computeAlbedo(date, currentLatitude);
595             }
596 
597             // Compute elementary area contribution to rediffused flux
598             final T albedoAndIR = a.multiply(solarFlux).multiply(FastMath.cos(sunAngle)).add(
599                                   e.multiply(solarFlux).multiply(0.25));
600 
601             // Compute elementary area - satellite vector and distance
602             final FieldVector3D<T> r = satellitePosition.subtract(elementCenter);
603             final T rNorm = r.getNorm();
604 
605             // Compute attenuated projected elemetary area vector
606             final FieldVector3D<T> projectedAreaVector = r.scalarMultiply(elementArea.multiply(FastMath.cos(alpha)).divide(
607                                                                           rNorm.square().multiply(rNorm).multiply(zero.getPi())));
608 
609             // Compute elementary radiation flux from current elementary area
610             return projectedAreaVector.scalarMultiply(albedoAndIR.divide(Constants.SPEED_OF_LIGHT));
611 
612         } else {
613 
614             // Spacecraft does not see the elementary area
615             return new FieldVector3D<>(zero, zero, zero);
616         }
617 
618     }
619 
620 }