1   /* Copyright 2002-2025 Thales Alenia Space
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.files.sinex;
18  
19  import java.util.Arrays;
20  import java.util.List;
21  
22  /** Key for antenna.
23   * @author Luc Maisonobe
24   * @since 13.0
25   */
26  public class AntennaKey {
27  
28      /** Constant matching other radome codes. */
29      public static final String OTHER_RADOME_CODE = "NONE";
30  
31      /** Constant matching any serial numbers. */
32      public static final String ANY_SERIAL_NUMBER = "-----";
33  
34      /** Antenna name. */
35      private final String name;
36  
37      /** Radome code. */
38      private final String radomeCode;
39  
40      /** Serial number. */
41      private final String serialNumber;
42  
43      /** Simple constructor.
44       * <p>
45       * The Sinex file specification uses a single 20 characters field named "Antenna type"
46       * and described as "Antenna name and model" (Antex specification is similar). In
47       * practice this field contains a variable length name and the last four characters are
48       * a radome code, which may be set to {@link #OTHER_RADOME_CODE "NONE"} for a catch-all
49       * entry. Here, we separate this field into its two components, so we can provide
50       * {@link #matchingCandidates() fuzzy matching} by tweaking the radome code if needed.
51       * </p>
52       * @param name antenna name
53       * @param radomeCode radome code
54       * @param serialNumber serial number
55       */
56      public AntennaKey(final String name, final String radomeCode, final String serialNumber) {
57          this.name         = name;
58          this.radomeCode   = radomeCode;
59          this.serialNumber = serialNumber;
60      }
61  
62      /** Get candidates for fuzzy matching of this antenna key.
63       * <p>
64       * Some Sinex files use specific keys in the SITE/ANTENNA block and catch-all
65       * keys in the SITE/GPS_PHASE_CENTER, SITE/GAL_PHASE_CENTER blocks. As
66       * an example, file JAX0MGXFIN_20202440000_01D_000_SOL.SNX contains the
67       * following entries related to antenna type ASH700936D_M:
68       * </p>
69       * <pre>
70       * SITE/ANTENNA
71       * AMU2  A ---- P 00:000:00000 00:000:00000 ASH700936D_M    SCIS 13569
72       * ARTU  A ---- P 00:000:00000 00:000:00000 ASH700936D_M    DOME CR130
73       * DRAG  A ---- P 00:000:00000 00:000:00000 ASH700936D_M    SNOW CR143
74       * PALM  A ---- P 00:000:00000 00:000:00000 ASH700936D_M    SCIS CR141
75       * SITE/GPS_PHASE_CENTER
76       * ASH700936D_M    NONE -----  .0910  .0004 -.0003  .1204 -.0001 -.0001 igs14_%Y%m
77       * ASH700936D_M    SCIS -----  .0879  .0005 -.0001  .1192  .0001 -.0001 igs14_%Y%m
78       * ASH700936D_M    SNOW -----  .0909  .0003 -.0002  .1192  .0001  .0001 igs14_%Y%m
79       * </pre>
80       * <p>
81       * Apart from the obvious formatting error of the last field in SITE/GPS_PHASE_CENTER,
82       * it appears there are no phase center data for the antenna used at ARTU site, because
83       * no radome code match "DOME". We consider here that a "close enough" entry would be
84       * to use {@link #OTHER_RADOME_CODE "NONE"} as the radome code, and {@link #ANY_SERIAL_NUMBER "-----"}
85       * as the serial number.
86       * </p>
87       * <p>
88       * Another example is file ESA0OPSFIN_20241850000_01D_01D_SOL.SNX which contains the
89       * following entries related to antenna type ASH701945G_M:
90       * </p>
91       * <pre>
92       * SITE/ANTENNA
93       * FAIR  A    1 P 24:184:86382 24:185:86382 ASH701945G_M    JPLA CR520    0
94       * KOKB  A    1 P 24:184:86382 24:185:86382 ASH701945G_M    NONE CR620    0
95       * SUTH  A    1 P 24:184:86382 24:185:86382 ASH701945G_M    NONE CR620    0
96       * SITE/GPS_PHASE_CENTER
97       * ASH701945G_M    NONE CR520 0.0895 0.0001 -.0001 0.1162 -.0007 -.0001 IGS20_2317
98       * ASH701945G_M    NONE CR620 0.0895 0.0001 -.0001 0.1162 -.0007 -.0001 IGS20_2317
99       * ASH701945G_M    NONE CR620 0.0895 0.0001 -.0001 0.1162 -.0007 -.0001 IGS20_2317
100      * SITE/GAL_PHASE_CENTER
101      * ASH701945G_M    NONE CR520 0.0895 0.0001 -.0001 0.1162 -.0007 -.0001 IGS20_2317
102      * ASH701945G_M    NONE CR520 0.1162 -.0007 -.0001 0.1162 -.0007 -.0001 IGS20_2317
103      * ASH701945G_M    NONE CR520 0.1162 -.0007 -.0001                      IGS20_2317
104      * ASH701945G_M    NONE CR620 0.0895 0.0001 -.0001 0.1162 -.0007 -.0001 IGS20_2317
105      * ASH701945G_M    NONE CR620 0.1162 -.0007 -.0001 0.1162 -.0007 -.0001 IGS20_2317
106      * ASH701945G_M    NONE CR620 0.1162 -.0007 -.0001                      IGS20_2317
107      * ASH701945G_M    NONE CR620 0.0895 0.0001 -.0001 0.1162 -.0007 -.0001 IGS20_2317
108      * ASH701945G_M    NONE CR620 0.1162 -.0007 -.0001 0.1162 -.0007 -.0001 IGS20_2317
109      * ASH701945G_M    NONE CR620 0.1162 -.0007 -.0001                      IGS20_2317
110      * </pre>
111      * <p>
112      * Here, the phase centers for serial number CR620 appear twice (fortunately with
113      * the same values). There are no phase center data for the antenna used at FAIR site,
114      * because no radome code match "JPLA". We consider here that a "close enough" entry
115      * would be to use {@link #OTHER_RADOME_CODE "NONE"}, and keep the provided serial number.
116      * </p>
117      * <p>
118      * The logic we adopted is to use the following candidates:
119      * </p>
120      * <table border="1" style="background-color:#f5f5dc;">
121      * <caption>Antenna key matching candidates</caption>
122      * <tr style="background-color:#c9d5c9;"><th>order</th><th>name</th><th>radome code</th><th>serial number</th></tr>
123      * <tr><td style="background-color:#c9d5c9; padding:5px">first candidate</td>
124      *     <td>{@link #getName()}</td><td>{@link #getRadomeCode()} </td><td>{@link #getSerialNumber()}</td></tr>
125      * <tr><td style="background-color:#c9d5c9; padding:5px">second candidate</td>
126      *     <td>{@link #getName()}</td><td>{@link #getRadomeCode()} </td><td>{@link #ANY_SERIAL_NUMBER "-----"}</td></tr>
127      * <tr><td style="background-color:#c9d5c9; padding:5px">third candidate</td>
128      *     <td>{@link #getName()}</td><td>{@link #OTHER_RADOME_CODE "NONE"} </td><td>{@link #getSerialNumber()}</td></tr>
129      * <tr><td style="background-color:#c9d5c9; padding:5px">fourth candidate</td>
130      *     <td>{@link #getName()}</td><td>{@link #OTHER_RADOME_CODE "NONE"} </td><td>{@link #ANY_SERIAL_NUMBER "-----"}</td></tr>
131      * </table>
132      * @return candidates for matching instance key, sorted from stricter to looser match
133      */
134     public List<AntennaKey> matchingCandidates() {
135         return Arrays.asList(this,
136                              new AntennaKey(getName(), radomeCode,        ANY_SERIAL_NUMBER),
137                              new AntennaKey(getName(), OTHER_RADOME_CODE, serialNumber),
138                              new AntennaKey(getName(), OTHER_RADOME_CODE, ANY_SERIAL_NUMBER));
139     }
140 
141     /** Get the antenna name.
142      * @return antenna name
143      */
144     public String getName() {
145         return name;
146     }
147 
148     /** Get the radome code.
149      * @return radome code
150      */
151     public String getRadomeCode() {
152         return radomeCode;
153     }
154 
155     /** Get the serial number.
156      * @return serial number
157      */
158     public String getSerialNumber() {
159         return serialNumber;
160     }
161 
162     @Override
163     public boolean equals(final Object object) {
164         if (object instanceof AntennaKey) {
165             final AntennaKey other = (AntennaKey) object;
166             return this.getName().equals(other.getName()) &&
167                    this.getRadomeCode().equals(other.getRadomeCode()) &&
168                    this.getSerialNumber().equals(other.getSerialNumber());
169         }
170         return false;
171     }
172 
173     @Override
174     public int hashCode() {
175         return getName().hashCode() ^ getRadomeCode().hashCode() ^ getSerialNumber().hashCode();
176     }
177 
178 }