1 /*
2  * Copyright (C) 2020 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 package com.android.timezone.location.validation;
17 
18 import com.google.common.geometry.S2LatLng;
19 
20 import java.io.BufferedReader;
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.IOException;
24 import java.io.InputStreamReader;
25 import java.nio.charset.StandardCharsets;
26 import java.util.Objects;
27 import java.util.stream.Stream;
28 
29 /** A support class for accessing a geonames "cities" file. */
30 class CitiesFile {
31 
32     /** The index of the value containing the city name. */
33     private static final int NAME_INDEX = 1;
34     /** The index of the value containing the city latitude. */
35     private static final int LATITUDE_INDEX = 4;
36     /** The index of the value containing the city longitude. */
37     private static final int LONGITUDE_INDEX = 5;
38     /** The index of the value containing the city ISO 3166 alpha-2 country code. */
39     private static final int ISO_COUNTRY_CODE_INDEX = 8;
40     /** The index of the value containing the time zone Olson ID. */
41     private static final int POPULATION_INDEX = 14;
42     /** The index of the value containing the time zone Olson ID. */
43     private static final int TIME_ZONE_ID_INDEX = 17;
44 
45     /** An individual geonames city entry. */
46     static class City {
47 
48         private final String mName;
49         private final S2LatLng mLatLng;
50         private final String mIsoCountryCode;
51         private final int mPopulation;
52         private final String mTimeZoneId;
53 
City(String name, S2LatLng latLng, String isoCountryCode, int population, String timeZoneId)54         City(String name, S2LatLng latLng, String isoCountryCode, int population,
55                 String timeZoneId) {
56             this.mName = Objects.requireNonNull(name);
57             this.mLatLng = Objects.requireNonNull(latLng);
58             this.mIsoCountryCode = isoCountryCode;
59             this.mPopulation = population;
60             this.mTimeZoneId = timeZoneId;
61         }
62 
getName()63         String getName() {
64             return mName;
65         }
66 
getLatLng()67         S2LatLng getLatLng() {
68             return mLatLng;
69         }
70 
getIsoCountryCode()71         String getIsoCountryCode() {
72             return mIsoCountryCode;
73         }
74 
getPopulation()75         int getPopulation() {
76             return mPopulation;
77         }
78 
getTimeZoneId()79         String getTimeZoneId() {
80             return mTimeZoneId;
81         }
82     }
83 
CitiesFile()84     private CitiesFile() {
85     }
86 
87     /** Creates a {@link Stream} of {@link City} objects from the specified file. */
read(File file)88     static Stream<City> read(File file) throws IOException {
89         BufferedReader reader = new BufferedReader(
90                 new InputStreamReader(
91                         new FileInputStream(file), StandardCharsets.UTF_8));
92         return reader.lines().map(CitiesFile::createCity);
93     }
94 
createCity(String s)95     private static City createCity(String s) {
96         String[] fields = s.split("\\t");
97         String name = fields[NAME_INDEX];
98         String lat = fields[LATITUDE_INDEX];
99         String lng = fields[LONGITUDE_INDEX];
100         S2LatLng latLng = S2LatLng.fromDegrees(Double.parseDouble(lat), Double.parseDouble(lng));
101 
102         String isoCountryCode = fields[ISO_COUNTRY_CODE_INDEX];
103         if (isoCountryCode.isEmpty()) {
104             isoCountryCode = null;
105         } else {
106             isoCountryCode = isoCountryCode.toLowerCase();
107         }
108 
109         String populationString = fields[POPULATION_INDEX];
110         int population = 0;
111         if (populationString.isEmpty()) {
112             System.out.println("No population found for " + name);
113         } else {
114             population = Integer.parseInt(populationString);
115         }
116 
117         String timeZoneId = fields[TIME_ZONE_ID_INDEX];
118         if (timeZoneId.isEmpty()) {
119             timeZoneId = null;
120         }
121 
122         return new City(name, latLng, isoCountryCode, population, timeZoneId);
123     }
124 }
125