19 package org.sleuthkit.autopsy.datasourcesummary.datamodel;
21 import java.io.BufferedReader;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.logging.Level;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 import java.util.stream.Stream;
31 import org.apache.commons.lang3.StringUtils;
38 class ClosestCityMapper {
41 private static final String CITIES_CSV_FILENAME =
"worldcities.csv";
44 private static final int CITY_NAME_IDX = 0;
45 private static final int STATE_NAME_IDX = 7;
46 private static final int COUNTRY_NAME_IDX = 4;
47 private static final int LAT_IDX = 2;
48 private static final int LONG_IDX = 3;
51 private static final Pattern CSV_NAIVE_REGEX = Pattern.compile(
"\"\\s*(([^\"]+?)?)\\s*\"");
54 private static final Pattern COUNTRY_WITH_COMMA = Pattern.compile(
"^\\s*([^,]*)\\s*,\\s*([^,]*)\\s*$");
56 private static final int MAX_IDX = Stream.of(CITY_NAME_IDX, STATE_NAME_IDX, COUNTRY_NAME_IDX, LAT_IDX, LONG_IDX)
57 .max(Integer::compare)
61 private static ClosestCityMapper instance = null;
69 static ClosestCityMapper getInstance() throws IOException {
70 if (instance == null) {
71 instance =
new ClosestCityMapper();
78 private LatLngMap<CityRecord> latLngMap = null;
81 private final java.util.logging.Logger logger;
88 private ClosestCityMapper() throws IOException {
90 GeolocationSummary.class.getResourceAsStream(CITIES_CSV_FILENAME),
91 Logger.getLogger(ClosestCityMapper.class.getName()));
102 private ClosestCityMapper(InputStream citiesInputStream, java.util.logging.Logger logger) throws IOException {
103 this.logger = logger;
104 latLngMap =
new LatLngMap<CityRecord>(parseCsvLines(citiesInputStream,
true));
114 CityRecord findClosest(CityRecord point) {
115 return latLngMap.findClosest(point);
125 private Double tryParse(String s) {
131 return Double.parseDouble(s);
132 }
catch (NumberFormatException ex) {
145 private String parseCountryName(String orig,
int lineNum) {
146 if (StringUtils.isBlank(orig)) {
147 logger.log(Level.WARNING, String.format(
"No country name determined for line %d.", lineNum));
151 Matcher m = COUNTRY_WITH_COMMA.matcher(orig);
153 return String.format(
"%s %s", m.group(1), m.group(2));
167 private CityRecord getCsvCityRecord(List<String> csvRow,
int lineNum) {
168 if (csvRow == null || csvRow.size() <= MAX_IDX) {
169 logger.log(Level.WARNING, String.format(
"Row at line number %d is required to have at least %d elements and does not.", lineNum, (MAX_IDX + 1)));
174 String cityName = csvRow.get(CITY_NAME_IDX);
175 if (StringUtils.isBlank(cityName)) {
176 logger.log(Level.WARNING, String.format(
"No city name determined for line %d.", lineNum));
181 String stateName = csvRow.get(STATE_NAME_IDX);
182 String countryName = parseCountryName(csvRow.get(COUNTRY_NAME_IDX), lineNum);
184 Double lattitude = tryParse(csvRow.get(LAT_IDX));
185 if (lattitude == null) {
186 logger.log(Level.WARNING, String.format(
"No lattitude determined for line %d.", lineNum));
190 Double longitude = tryParse(csvRow.get(LONG_IDX));
191 if (longitude == null) {
192 logger.log(Level.WARNING, String.format(
"No longitude determined for line %d.", lineNum));
196 return new CityRecord(cityName, stateName, countryName, lattitude, longitude);
206 private List<String> parseCsvLine(String line,
int lineNum) {
207 if (line == null || line.length() <= 0) {
208 logger.log(Level.INFO, String.format(
"Line at %d had no content", lineNum));
212 List<String> allMatches =
new ArrayList<String>();
213 Matcher m = CSV_NAIVE_REGEX.matcher(line);
215 allMatches.add(m.group(1));
231 private List<CityRecord> parseCsvLines(InputStream csvInputStream,
boolean ignoreHeaderRow)
throws IOException {
232 List<CityRecord> cityRecords =
new ArrayList<>();
233 try (BufferedReader reader =
new BufferedReader(
new InputStreamReader(csvInputStream,
"UTF-8"))) {
235 String line = reader.readLine();
237 if (line != null && ignoreHeaderRow) {
238 line = reader.readLine();
242 while (line != null) {
244 List<String> rowElements = parseCsvLine(line, lineNum);
246 if (rowElements != null) {
247 cityRecords.add(getCsvCityRecord(rowElements, lineNum));
250 line = reader.readLine();