0

I've got this class:

[UsedImplicitly]
public class ClassicalKeplerian
{
    public ClassicalKeplerian(Angle argumentOfPeriapsis, Angle inclination, UnitFraction eccentricity, Angle rightAscensionAscendingNode, Length semiMajorAxis, ReferenceFrame referenceFrame, Angle? trueAnomaly = null, DateTime? epoch = null)
    {
        ArgumentOfPeriapsis = argumentOfPeriapsis;
        Inclination = inclination;
        Eccentricity = eccentricity;
        RightAscensionAscendingNode = rightAscensionAscendingNode;
        SemiMajorAxis = semiMajorAxis;
        ReferenceFrame = referenceFrame;
        TrueAnomaly = trueAnomaly;
        Epoch = epoch;
    }
    

    [CommonSymbol("ω")]
    public Angle ArgumentOfPeriapsis { get; }
    
    [CommonSymbol("𝑖")]
    public Angle Inclination { get; }
    
    [CommonSymbol("𝑒")]
    public UnitFraction Eccentricity { get; }
    
    [CommonSymbol("Ω")]
    public Angle RightAscensionAscendingNode { get; }
    
    [CommonSymbol("𝑎")]
    public Length SemiMajorAxis { get; }
    
    public ReferenceFrame ReferenceFrame { get; }
    
    [CommonSymbol("𝓥")]
    public Angle? TrueAnomaly { get; }
    
    [CommonSymbol("t₀")]
    public DateTime? Epoch { get; }
    
}

The ReferenceFrame is an enum that can be one of J2000, TEME, or ICFR (for now, this could grow over time).

I need to be able to convert all of the values on this object between any permutation of these three reference frames. Now, I could just add ToJ2000, ToTeme and ToIcfr methods but each of those would then need a switch case for which frame it's coming from and it will quickly devolve into an absolute mess.

In terms of the process of doing conversions, I'm thinking I can implement from every reference frame to ICFR and then from ICFR to everything else. That at least makes the amount of code needed for each new reference frame linear, rather than exponential.

What's a good way to approach this?

6
  • 3
    What do you mean by "best"?
    – svidgen
    Jan 8, 2023 at 0:47
  • I just mean a good way of approaching it, one that keeps the code clean, easy to understand, and extensible. Jan 8, 2023 at 1:22
  • What you described sounds like a sane, scalable, and even familiar pattern to me. Have you encountered or thought of any pitfalls?
    – svidgen
    Jan 8, 2023 at 2:05
  • That's reassuring to hear... I was somewhat unsure of how to structure it though... Throwing it all into one converter class feels like it would be doing too many things Jan 8, 2023 at 2:43
  • 1
    Re throwing it all into a converter class, indeed that sounds bad, why not make a ClassicalKeplerianConversionsToICFR interface, and then have one implementation per reference frame. And then your switch statement just returns the appropriate ClassicalKeplerianConversionsToICFR. Ideally ClassicalKeplerianConversionsToICFR would be generic in the specific value of enum you are using, but I don't know how feasible that is in c#.
    – Jovash
    Jan 8, 2023 at 17:01

1 Answer 1

2

I'm not particularly familiar with the specific context here (of celestial mechanics), but wherever there are multiple mutually-convertible representations of the same information, then (in line with your own thinking) it is normal to designate one representation as the common or master representation, and then implement all conversions to secondary representations via this master representation.

In this approach, there needs to be a set of two specific conversions implemented for each secondary representation - one for conversion from the master to the secondary, another for conversion from the secondary to the master.

Also, when the desired overall conversion is from secondary to secondary, this will be a two-step process involving two specific conversions, rather than a one-step process when the initial or final representation is the master.

A crucial question for the implementation is whether you want this overall conversion to be something handled automatically within the code, or whether you will just program the specific conversions by hand.

In other words, whether you will be able to code ck.ConvertTo(J2000), and have the code automatically work out what specific conversion steps are required to get from the existing representation (stored in ck) to the J2000 representation, or whether you will have to code the specific steps at the site of each conversion CkConverter.FromICFRToJ2000(CkConverter.FromTEMEToICFR(ck)) (example assuming the existing representation in ck is TEME).

In the automatic approach, you will have to program a lookup table (or some other way of expressing a linkage), with one entry for each possible combination of initial and final representation, and with each entry expressing the specific conversion(s) (one or two) necessary to apply to achieve the overall conversion.

The specific conversion code will not grow exponentially with each additional representation - so if each conversion is mathematically complicated, you have saved coding effort on that front - but this lookup table (containing the simple and essential data about the conversion paths between each representation) will still grow exponentially.

7
  • Ideally, I want this two-step to be somewhat automatic. I'm wondering if I could generate this map somewhat automatically using reflection and perhaps some IToJ2000Converter<T> and IFromJ2000Converter<T> to inject everything into the converter class Jan 8, 2023 at 17:12
  • @ScottishTapWater, it depends what you mean by "generate automatically". Obviously there is an irreducible minimum of conceptual content that must be actually expressed in the code. With 3 representations - one primary and two secondary - there are only 4 specific conversion methods, and 6 overall conversion routes. With 4 representations, there would be 6 specific conversion methods and 12 routes. With 5 representations, it's 8 and 20. That seems quite manageable to code directly.
    – Steve
    Jan 8, 2023 at 18:17
  • What I'm thinking is for any given conversion you could lookup the implementation of IToJ2000Converter<T> where T is the type you're coming from, and the implementation of IFromJ2000Converter<T> where T is the type you're going to, and then call Convert on both. Then you don't need to hardcode any of these routes (except a short-circuit for if you're just converting to J2000 Jan 8, 2023 at 18:22
  • @ScottishTapWater, to my eye the use of OO and generics looks heavyweight for this. For an IToJ2000Converter<T>, the only valid type of T would be ICFR - because that is the primary representation to or from which all conversions occur. Also, it is not strictly necessary to hardcode all routes, but it's almost certainly the clearest representation for a modest number of available routes. You'd only want more machinery to calculate routes, if they weren't so easily hardcoded.
    – Steve
    Jan 8, 2023 at 18:37
  • 1
    @ScottishTapWater, well you've obviously changed your mind since you wrote your question, on the point about which frame is to be master. I also don't see the need for interfaces and generics - if they are necessary, it must be as part of some larger solution which we haven't been shown. I would expect conversions to require the definition of just a static method each, and a static class/module to hold these methods, and no more complexity than that. And if the routes are hardcoded, probably one more method to cope with all overall conversion (probably handled using a switch statement).
    – Steve
    Jan 8, 2023 at 19:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.