Could this be a bug in OreCZML's line of the satellite-to-ground visiblity

Hi Zudo:

While trying to run the SatTrackingExample, I wondered whether the satellite-to-ground link might have the same kind of issue that had appeared earlier with the inter-satellite link.
So I moved the example’s Las-Vegas station to a location near the equator [–80°, 0°] (roughly HaMa) and left every other parameter unchanged—my only goal was to guarantee that, at the start of the scenario, at least one station would have the satellite in view.
After running the case I noticed something contradictory:
The Output CZML segment:

"name": "Line between HaMa for Test and Spacecraft",
"availability": [
  "0001-01-01T00:00:00Z/2024-03-15T00:00:00Z",
  "2024-03-15T00:00:00Z/2024-03-15T00:08:01Z",
  "2024-03-15T00:08:01Z/2024-03-15T02:00:12Z",
  "2024-03-15T02:00:12Z/2024-03-15T02:12:15Z",
  ...
]

claims the line should exist from the very first instant, but the corresponding Boolean array

"show": [
  {
    "interval": "0001-01-01T00:00:00Z/2024-03-15T00:00:00Z",
    "boolean": true
  },
  {
    "interval": "2024-03-15T00:00:00Z/2024-03-15T00:08:01Z",
    "boolean": false
  },
  {
    "interval": "2024-03-15T00:08:01Z/2024-03-15T02:00:12Z",
    "boolean": false
  },
  {
    "interval": "2024-03-15T02:00:12Z/2024-03-15T02:12:15Z",
    "boolean": true
  },
  ...
]

sets the line to not be shown during those same initially-available intervals.
That looks inconsistent—possibly a bug in LineOfVisibilityBuilder or something.

Sorry I haven’t dived into the source to pinpoint the exact problem; after the changes that were made for the last “inter-satellite-link” issue, the code looks pretty different and I’m still getting my bearings.
At first I hesitated to open yet topic—I felt like the guy who borrows a friend’s bike and only complains about the scratches without ever saying thanks. But if pointing out the wobble keeps the next rider from getting hurt, it’s worth looking like a nag.

Thanks for all the hard work, and hope all friends ride safe!

Below is my test code. Basically, I changed nothing except moving the Las-Vegas station to HaMa (–80°, 0°).

/* Copyright 2002-2024 CS GROUP
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.orekit.czml.trackingvisu;

import org.hipparchus.ode.nonstiff.AdaptiveStepsizeIntegrator;
import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
import org.hipparchus.util.FastMath;
import org.orekit.bodies.GeodeticPoint;
import org.orekit.czml.TutorialUtils;
import org.orekit.czml.file.CzmlFile;
import org.orekit.czml.object.primary.Header;
import org.orekit.czml.object.primary.entities.CzmlGroundStation;
import org.orekit.czml.object.primary.entities.Spacecraft;
import org.orekit.czml.object.primary.visu.LineOfVisibility;
import org.orekit.czml.object.secondary.Clock;
import org.orekit.forces.ForceModel;
import org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel;
import org.orekit.forces.gravity.potential.GravityFieldFactory;
import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
import org.orekit.frames.FramesFactory;
import org.orekit.frames.TopocentricFrame;
import org.orekit.orbits.KeplerianOrbit;
import org.orekit.orbits.OrbitType;
import org.orekit.orbits.PositionAngleType;
import org.orekit.propagation.BoundedPropagator;
import org.orekit.propagation.EphemerisGenerator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.numerical.NumericalPropagator;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScalesFactory;
import org.orekit.utils.Constants;
import org.orekit.utils.IERSConventions;

import java.util.ArrayList;
import java.util.List;

/**
 * This tutorial shows an example of mission where a satellite is tracked by
 * several stations.
 */
public class SatTrackingExampleFailTest {

    private SatTrackingExampleFailTest() {
        // empty
    }

    /**
     * Main of sat tracking tutorial.
     *
     * @param args the args
     * @throws Exception the exception
     */
    public static void main(final String[] args)
        throws Exception {
        // Load orekit data
        TutorialUtils.loadOrekitData();

        // Paths
        final String output = TutorialUtils.generateOutput();
        // !!! Here you need to change the path inside 'generateJsPath' to the
        // path you are using for images or Model.
        // This folder can also be the public folder of your cesium javascript
        // interface.
        final String pathToJSFolder =
            TutorialUtils.generateJSPath(System.getProperty("user.dir") +
                                         "/Javascript/public");

        // Creation of the clock.

        final AbsoluteDate startDate =
            new AbsoluteDate(2024, 3, 15, 0, 0, 0.0,
                             TimeScalesFactory.getUTC());
        final AbsoluteDate finalDate =
            startDate.shiftedBy(TutorialUtils.CLASSIC_DURATION_OF_SIMULATION);
        final Clock clock =
            new Clock(startDate, finalDate,
                      TutorialUtils.STEP_BETWEEN_EACH_INSTANT);

        // Creation of the header
        final Header header =
            new Header("Tracking of a satellite by several stations", clock,
                       pathToJSFolder);

        //// Creation of the satellite
        // Creation of the orbit

        final KeplerianOrbit initialOrbit =
            new KeplerianOrbit(7878000, 0, FastMath.toRadians(20), 0,
                               FastMath.toRadians(90), FastMath.toRadians(0),
                               PositionAngleType.MEAN,
                               FramesFactory.getEME2000(), startDate,
                               Constants.WGS84_EARTH_MU);

        final SpacecraftState initialState = new SpacecraftState(initialOrbit);

        // Build of the propagator

        final double[][] tolerances =
            NumericalPropagator.tolerances(TutorialUtils.POSITION_TOLERANCE,
                                           initialOrbit, OrbitType.CARTESIAN);
        final AdaptiveStepsizeIntegrator integrator =
            new DormandPrince853Integrator(TutorialUtils.MIN_STEP,
                                           TutorialUtils.MAX_STEP,
                                           tolerances[0], tolerances[1]);

        final NumericalPropagator propagator =
            new NumericalPropagator(integrator);

        final NormalizedSphericalHarmonicsProvider provider =
            GravityFieldFactory.getNormalizedProvider(10, 10);
        final ForceModel holmesFeatherstone =
            new HolmesFeatherstoneAttractionModel(FramesFactory
                .getITRF(IERSConventions.IERS_2010, true), provider);

        propagator.setOrbitType(OrbitType.CARTESIAN);
        propagator.addForceModel(holmesFeatherstone);
        propagator.setInitialState(initialState);

        final EphemerisGenerator generator = propagator.getEphemerisGenerator();

        propagator.propagate(startDate, finalDate);
        final BoundedPropagator boundedPropagator =
            generator.getGeneratedEphemeris();

        // Build of the satellite
        final Spacecraft satellite =
            Spacecraft.builder(boundedPropagator, header).withOnlyOnePeriod()
                .build();

        //// Creation of several ground stations

        // Creation of a topocentric frame around Toulouse.
        final GeodeticPoint toulouseFrame =
            new GeodeticPoint(FastMath.toRadians(43.6047),
                              FastMath.toRadians(1.4442), 10);
        final TopocentricFrame topocentricToulouse =
            new TopocentricFrame(TutorialUtils.getEarth(), toulouseFrame,
                                 "Toulouse");
        // Creation of a topocentric frame around Quito
        final GeodeticPoint quitoFrame =
            new GeodeticPoint(FastMath.toRadians(0.1807),
                              FastMath.toRadians(11.5382), 2850);
        final TopocentricFrame topocentricQuito =
            new TopocentricFrame(TutorialUtils.getEarth(), quitoFrame, "Quito");
        // Creation of a topocentric frame around Sydney
        final GeodeticPoint sydneyFrame =
            new GeodeticPoint(FastMath.toRadians(-33.8688),
                              FastMath.toRadians(-241.2093), 100);
        final TopocentricFrame topocentricSydney =
            new TopocentricFrame(TutorialUtils.getEarth(), sydneyFrame,
                                 "Sydney");
        // Creation of a topocentric frame around gibraltar
        final GeodeticPoint gibraltarFrame =
            new GeodeticPoint(FastMath.toRadians(36.1408),
                              FastMath.toRadians(5.3536), 400);
        final TopocentricFrame topocentricGibraltar =
            new TopocentricFrame(TutorialUtils.getEarth(), gibraltarFrame,
                                 "Gibraltar");
        // Creation of another topocentric frame around Las Vegas.
        // ***Change to Hama for test***
        final GeodeticPoint lasVegasFrame =
            new GeodeticPoint(FastMath.toRadians(0),
                              FastMath.toRadians(-80), 10);
        final TopocentricFrame topocentricLasVegas =
            new TopocentricFrame(TutorialUtils.getEarth(), lasVegasFrame,
                                 "HaMa for Test");

        // Creation of a list of topocentric frame containing both frames.
        final List<TopocentricFrame> stations = new ArrayList<>();
        stations.add(topocentricToulouse);
        stations.add(topocentricSydney);
        stations.add(topocentricQuito);
        stations.add(topocentricGibraltar);
        stations.add(topocentricLasVegas);

        final List<CzmlGroundStation> groundStations = new ArrayList<>();
        for (TopocentricFrame station : stations) {
            final CzmlGroundStation currentCzmlStation =
                CzmlGroundStation.builder(station, header)
                    .displayCircle(satellite, 70.0).build();
            groundStations.add(currentCzmlStation);
        }

        final LineOfVisibility lineOfVisibilityToulouse =
            LineOfVisibility.builder(topocentricToulouse, satellite, header)
                .withAngleOfAperture(70.0).build();
        final LineOfVisibility lineOfVisibilityGibraltar =
            LineOfVisibility.builder(topocentricGibraltar, satellite, header)
                .withAngleOfAperture(70.0).build();
        final LineOfVisibility lineOfVisibilityQuito =
            LineOfVisibility.builder(topocentricQuito, satellite, header)
                .withAngleOfAperture(70.0).build();
        final LineOfVisibility lineOfVisibilityLasVegas =
            LineOfVisibility.builder(topocentricLasVegas, satellite, header)
                .withAngleOfAperture(70.0).build();
        final LineOfVisibility lineOfVisibilitySydney =
            LineOfVisibility.builder(topocentricSydney, satellite, header)
                .withAngleOfAperture(70.0).build();

        //// Creation of a line of visu between the satellite and all the ground
        //// stations
        final CzmlFile file =
            CzmlFile.builder().withHeader(header).withSpacecraft(satellite)
                .withCzmlGroundStation(groundStations)
                .withLineOfVisibility(lineOfVisibilityToulouse)
                .withLineOfVisibility(lineOfVisibilitySydney)
                .withLineOfVisibility(lineOfVisibilityLasVegas)
                .withLineOfVisibility(lineOfVisibilityGibraltar)
                .withLineOfVisibility(lineOfVisibilityQuito).build();

        // Write inside the CzmlFile the objects
        file.write(output);
    }
}

Hello @houmingyang ,

So basically, it seems like it is a boundary conditions situation, where the line of visibility is showing the line when it should not and not when it should right ?

You can open a ticket about this one, and link this topic with the issue, so that the code will be used and tested to see what is happening.

Thanks again for this, it makes OreCzml better each day !

@houmingyang If you’d like to write the fix for this one I’d be happy to work with you to get it into the repo.

Yes, thank you for your reply. It seems that every program tends to encounter issues at the boundaries.

1 Like

Thank you for your trust. I will open an issue on Git. However, when I attempted to modify the InterSatVisu problem last time, I felt that my understanding of the program was still insufficient. I will try to continue learning and analyze this issue further. At the same time, I appreciate your efforts and particularly excellent modifications in resolving the InterSatVisu problem.I am confident that you will find a solution to this ground-satellite link display boundary issue much more quickly than I could, given its similarity to the previous problem.I look forward to seeing your solution.

1 Like