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 }