• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.telephony.tools.sats2;
18 
19 import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
20 import com.android.telephony.sats2range.read.SatS2RangeFileReader;
21 import com.android.telephony.sats2range.read.SuffixTableRange;
22 import com.android.telephony.sats2range.write.SatS2RangeFileWriter;
23 
24 import com.google.common.base.Stopwatch;
25 import com.google.common.geometry.S2CellId;
26 
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.InputStream;
30 import java.nio.charset.StandardCharsets;
31 import java.util.ArrayList;
32 import java.util.Comparator;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Objects;
38 import java.util.Scanner;
39 import java.util.concurrent.TimeUnit;
40 
41 /** A util class for creating a satellite S2 file from the list of S2 cells. */
42 public final class SatS2FileCreator {
43     /**
44      * @param inputFile The input text file containing the list of S2 Cell IDs. Each line in the
45      *                  file contains two numbers separated by a comma. The first number is in the
46      *                  range of a 64bit number which represents the ID of a S2 cell. The second
47      *                  number is satellite access config ID for the S2 cell.
48      * @param s2Level The S2 level of all S2 cells in the input file.
49      * @param isAllowedList {@code true} means the input file contains an allowed list of S2 cells.
50      *                      {@code false} means the input file contains a disallowed list of S2
51      *                      cells.
52      * @param outputFile The output file to which the satellite S2 data in block format will be
53      *                   written.
54      */
create(String inputFile, int s2Level, boolean isAllowedList, int entryValueSizeInBytes, int versionNumber, String outputFile)55     public static void create(String inputFile, int s2Level, boolean isAllowedList,
56             int entryValueSizeInBytes, int versionNumber, String outputFile) throws Exception {
57         // Read a list of S2 cells from input file
58         List<Pair<Long, Integer>> s2Cells = readS2CellsFromFile(inputFile);
59         System.out.println("Number of S2 cells read from file:" + s2Cells.size());
60 
61         // Convert the input list of S2 Cells into the list of sorted S2CellId
62         System.out.println("Denormalizing S2 Cell IDs to the expected s2 level=" + s2Level);
63         List<Pair<S2CellId, Integer>> sortedS2CellIds = normalize(s2Cells, s2Level);
64 
65         // IDs of S2CellId are converted to unsigned long numbers, which will be then used to
66         // compare S2CellId.
67         sortedS2CellIds.sort(Comparator.comparing(o -> o.first));
68         System.out.println("Number of S2 cell IDs:" + sortedS2CellIds.size());
69 
70         // Compress the list of S2CellId into S2 ranges
71         List<SatS2Range> satS2Ranges = createSatS2Ranges(sortedS2CellIds, s2Level);
72 
73         // Write the S2 ranges into a block file
74         SatS2RangeFileFormat fileFormat = FileFormats.getFileFormatForLevel(s2Level, isAllowedList,
75                 entryValueSizeInBytes, versionNumber);
76         try (SatS2RangeFileWriter satS2RangeFileWriter =
77                      SatS2RangeFileWriter.open(new File(outputFile), fileFormat)) {
78             Iterator<SuffixTableRange> suffixTableRangeIterator = satS2Ranges
79                     .stream()
80                     .map(x -> new SuffixTableRange(x.rangeStart.id(), x.rangeEnd.id(),
81                             x.entryValue))
82                     .iterator();
83             /*
84              * Group the sorted ranges into contiguous suffix blocks. Big ranges might get split as
85              * needed to fit them into suffix blocks.
86              */
87             satS2RangeFileWriter.createSortedSuffixBlocks(suffixTableRangeIterator);
88         }
89 
90         // Validate the output block file
91         System.out.println("Validating the output block file...");
92         try (SatS2RangeFileReader satS2RangeFileReader =
93                      SatS2RangeFileReader.open(new File(outputFile))) {
94             if (isAllowedList != satS2RangeFileReader.isAllowedList()) {
95                 throw new IllegalStateException("isAllowedList="
96                         + satS2RangeFileReader.isAllowedList() + " does not match the input "
97                         + "argument=" + isAllowedList);
98             }
99 
100             // Verify that all input S2 cells are present in the output block file and the their
101             // entry value matches the provided entry value
102             for (Pair<S2CellId, Integer> s2CellInfo : sortedS2CellIds) {
103                 SuffixTableRange entry =
104                         satS2RangeFileReader.findEntryByCellId(s2CellInfo.first.id());
105                 if (entry == null) {
106                     throw new IllegalStateException("s2CellInfo=" + s2CellInfo
107                             + " is not present in the output sat s2 file");
108                 } else if (entry.getEntryValue() != s2CellInfo.second) {
109                     throw new IllegalStateException("entry.getEntryValue=" + entry.getEntryValue()
110                             + " does not match the provided entry value=" + s2CellInfo.second);
111                 }
112             }
113 
114             // Verify the cell right before the first cell in the sortedS2CellIds is not present in
115             // the output block file
116             S2CellId prevCell = sortedS2CellIds.getFirst().first.prev();
117             boolean containsPrevCell = sortedS2CellIds.stream()
118                     .anyMatch(pair -> pair.first.equals(prevCell));
119             if (!containsPrevCell
120                     && satS2RangeFileReader.findEntryByCellId(prevCell.id()) != null) {
121                 throw new IllegalStateException("The cell " + prevCell + ", which is right "
122                         + "before the first cell is unexpectedly present in the output sat s2"
123                         + " file");
124             } else {
125                 System.out.println("prevCell=" + prevCell + " is in the sortedS2CellIds");
126             }
127 
128             // Verify the cell right after the last cell in the sortedS2CellIds is not present in
129             // the output block file
130             S2CellId nextCell = sortedS2CellIds.getLast().first.next();
131             boolean containsNextCell = sortedS2CellIds.stream()
132                     .anyMatch(pair -> pair.first.equals(nextCell));
133             if (!containsNextCell
134                     && satS2RangeFileReader.findEntryByCellId(nextCell.id()) != null) {
135                 throw new IllegalStateException("The cell " + nextCell + ", which is right "
136                         + "after the last cell is unexpectedly present in the output sat s2"
137                         + " file");
138             } else {
139                 System.out.println("nextCell=" + nextCell + " is in the sortedS2CellIds");
140             }
141         }
142         System.out.println("Successfully validated the output block file");
143     }
144 
145     /**
146      * Read a list of S2 cells from the inputFile.
147      *
148      * @param inputFile A file containing the list of S2 cells. Each line in the inputFile contains
149      *                  a 64-bit number - the ID of a S2 cell.
150      * @return A list of S2 cells.
151      */
readS2CellsFromFile(String inputFile)152     private static List<Pair<Long, Integer>> readS2CellsFromFile(String inputFile)
153             throws Exception {
154         List<Pair<Long, Integer>> s2Cells = new ArrayList<>();
155         InputStream inputStream = new FileInputStream(inputFile);
156         try (Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8)) {
157             while (scanner.hasNextLine()) {
158                 String line = scanner.nextLine();
159                 String[] s2CellInfoStrs = line.split(",");
160                 if (s2CellInfoStrs == null || s2CellInfoStrs.length != 2) {
161                     throw new IllegalStateException("The Input s2 cell file has invalid format, "
162                             + "current line=" + line);
163                 }
164                 try {
165                     s2Cells.add(new Pair<>(Long.parseLong(s2CellInfoStrs[0]),
166                             Integer.parseUnsignedInt(s2CellInfoStrs[1])));
167                 } catch (Exception ex) {
168                     throw new IllegalStateException("Input s2 cell file has invalid format, "
169                             + "current line=" + line + ", ex=" + ex);
170                 }
171             }
172         }
173         return s2Cells;
174     }
175 
176     /**
177      * Convert the list of S2 Cell numbers into the list of S2 Cell IDs at the expected level.
178      */
normalize( List<Pair<Long, Integer>> s2CellNumbers, int s2Level)179     private static List<Pair<S2CellId, Integer>> normalize(
180             List<Pair<Long, Integer>> s2CellNumbers, int s2Level) {
181         Map<S2CellId, Integer> s2CellIdMap = new HashMap<>();
182         for (Pair<Long, Integer> s2CellInfo : s2CellNumbers) {
183             S2CellId s2CellId = new S2CellId(s2CellInfo.first);
184             if (s2CellId.level() == s2Level) {
185                 if (!s2CellIdMap.containsKey(s2CellId)) {
186                     s2CellIdMap.put(s2CellId, s2CellInfo.second);
187                 }
188             } else if (s2CellId.level() < s2Level) {
189                 S2CellId childEnd = s2CellId.childEnd(s2Level);
190                 for (s2CellId = s2CellId.childBegin(s2Level); !s2CellId.equals(childEnd);
191                         s2CellId = s2CellId.next()) {
192                     if (!s2CellIdMap.containsKey(s2CellId)) {
193                         s2CellIdMap.put(s2CellId, s2CellInfo.second);
194                     }
195                 }
196             } else {
197                 S2CellId parent = s2CellId.parent(s2Level);
198                 if (!s2CellIdMap.containsKey(parent)) {
199                     s2CellIdMap.put(parent, s2CellInfo.second);
200                 }
201             }
202         }
203 
204         List<Pair<S2CellId, Integer>> result = new ArrayList();
205         for (Map.Entry<S2CellId, Integer> entry : s2CellIdMap.entrySet()) {
206             result.add(new Pair<>(entry.getKey(), entry.getValue()));
207         }
208         return result;
209     }
210 
211     /**
212      * Compress the list of sorted S2CellId into S2 ranges.
213      *
214      * @param sortedS2CellIds List of S2CellId sorted in ascending order.
215      * @param s2Level The level of all S2CellId.
216      * @return List of S2 ranges.
217      */
createSatS2Ranges(List<Pair<S2CellId, Integer>> sortedS2CellIds, int s2Level)218     private static List<SatS2Range> createSatS2Ranges(List<Pair<S2CellId, Integer>> sortedS2CellIds,
219             int s2Level) {
220         Stopwatch stopwatch = Stopwatch.createStarted();
221         List<SatS2Range> ranges = new ArrayList<>();
222         if (sortedS2CellIds != null && !sortedS2CellIds.isEmpty()) {
223             S2CellId rangeStart = null;
224             S2CellId rangeEnd = null;
225             int rangeEntryValue = 0;
226             for (int i = 0; i < sortedS2CellIds.size(); i++) {
227                 S2CellId currentS2CellId = sortedS2CellIds.get(i).first;
228                 checkCellIdIsAtLevel(currentS2CellId, s2Level);
229 
230                 SatS2Range currentRange = createS2Range(currentS2CellId, s2Level,
231                         sortedS2CellIds.get(i).second);
232                 S2CellId currentS2CellRangeStart = currentRange.rangeStart;
233                 S2CellId currentS2CellRangeEnd = currentRange.rangeEnd;
234 
235                 if (rangeStart == null) {
236                     // First time round the loop initialize rangeStart / rangeEnd only.
237                     rangeStart = currentS2CellRangeStart;
238                     rangeEntryValue = currentRange.entryValue;
239                 } else if (rangeEnd.id() != currentS2CellRangeStart.id()
240                         || currentRange.entryValue != rangeEntryValue) {
241                     // If there's a gap between cellIds or entry values are different, store the
242                     // range we have so far and start a new range.
243                     ranges.add(new SatS2Range(rangeStart, rangeEnd, rangeEntryValue));
244                     rangeStart = currentS2CellRangeStart;
245                     rangeEntryValue = currentRange.entryValue;
246                 }
247                 rangeEnd = currentS2CellRangeEnd;
248             }
249             ranges.add(new SatS2Range(rangeStart, rangeEnd, rangeEntryValue));
250         }
251 
252         // Sorting the ranges is not necessary. As the input is sorted , it will already be sorted.
253         System.out.printf("Created %s SatS2Ranges in %s milliseconds\n",
254                 ranges.size(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
255         return ranges;
256     }
257 
258     /**
259      * @return A pair of S2CellId for the range [s2CellId, s2CellId's next sibling)
260      */
createS2Range(S2CellId s2CellId, int s2Level, int entryValue)261     private static SatS2Range createS2Range(S2CellId s2CellId, int s2Level, int entryValue) {
262         // Since s2CellId is at s2Level, s2CellId.childBegin(s2Level) returns itself.
263         S2CellId firstS2CellRangeStart = s2CellId.childBegin(s2Level);
264         // Get the immediate next sibling of s2CellId
265         S2CellId firstS2CellRangeEnd = s2CellId.childEnd(s2Level);
266 
267         if (firstS2CellRangeEnd.face() < firstS2CellRangeStart.face()
268                 || !firstS2CellRangeEnd.isValid()) {
269             // Fix this if it becomes an issue.
270             throw new IllegalStateException("firstS2CellId=" + s2CellId
271                     + ", childEnd(" + s2Level + ") produced an unsupported"
272                     + " value=" + firstS2CellRangeEnd);
273         }
274         return new SatS2Range(firstS2CellRangeStart, firstS2CellRangeEnd, entryValue);
275     }
276 
checkCellIdIsAtLevel(S2CellId cellId, int s2Level)277     private static void checkCellIdIsAtLevel(S2CellId cellId, int s2Level) {
278         if (cellId.level() != s2Level) {
279             throw new IllegalStateException("Bad level for cellId=" + cellId
280                     + ". Must be s2Level=" + s2Level);
281         }
282     }
283 
284     /**
285      * A range of S2 cell IDs at a fixed S2 level. The range is expressed as a start cell ID
286      * (inclusive) and an end cell ID (exclusive).
287      */
288     private static class SatS2Range {
289         public final int entryValue;
290         public final S2CellId rangeStart;
291         public final S2CellId rangeEnd;
292 
293         /**
294          * Creates an instance. If the range is invalid or the cell IDs are from different levels
295          * this method throws an {@link IllegalArgumentException}.
296          */
SatS2Range(S2CellId rangeStart, S2CellId rangeEnd, int entryValue)297         SatS2Range(S2CellId rangeStart, S2CellId rangeEnd, int entryValue) {
298             this.entryValue = entryValue;
299             this.rangeStart = Objects.requireNonNull(rangeStart);
300             this.rangeEnd = Objects.requireNonNull(rangeEnd);
301             if (rangeStart.level() != rangeEnd.level()) {
302                 throw new IllegalArgumentException(
303                         "Levels differ: rangeStart=" + rangeStart + ", rangeEnd=" + rangeEnd);
304             }
305             if (rangeStart.greaterOrEquals(rangeEnd)) {
306                 throw new IllegalArgumentException(
307                         "Range start (" + rangeStart + " >= range end (" + rangeEnd + ")");
308             }
309         }
310     }
311 
312     /** A basic pair class. */
313     static class Pair<A, B> {
314 
315         public final A first;
316 
317         public final B second;
318 
Pair(A first, B second)319         Pair(A first, B second) {
320             this.first = first;
321             this.second = second;
322         }
323     }
324 }
325