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