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;
70 static ClosestCityMapper getInstance() throws IOException {
71 if (instance == null) {
72 instance =
new ClosestCityMapper();
79 private LatLngMap<CityRecord> latLngMap = null;
82 private final java.util.logging.Logger logger;
89 private ClosestCityMapper() throws IOException {
91 GeolocationSummary.class.getResourceAsStream(CITIES_CSV_FILENAME),
92 Logger.getLogger(ClosestCityMapper.class.getName()));
104 private ClosestCityMapper(InputStream citiesInputStream, java.util.logging.Logger logger) throws IOException {
105 this.logger = logger;
106 latLngMap =
new LatLngMap<CityRecord>(parseCsvLines(citiesInputStream,
true));
117 CityRecord findClosest(CityRecord point) {
118 return latLngMap.findClosest(point);
129 private Double tryParse(String s) {
135 return Double.parseDouble(s);
136 }
catch (NumberFormatException ex) {
150 private String parseCountryName(String orig,
int lineNum) {
151 if (StringUtils.isBlank(orig)) {
152 logger.log(Level.WARNING, String.format(
"No country name determined for line %d.", lineNum));
156 Matcher m = COUNTRY_WITH_COMMA.matcher(orig);
158 return String.format(
"%s %s", m.group(1), m.group(2));
173 private CityRecord getCsvCityRecord(List<String> csvRow,
int lineNum) {
174 if (csvRow == null || csvRow.size() <= MAX_IDX) {
175 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)));
180 String cityName = csvRow.get(CITY_NAME_IDX);
181 if (StringUtils.isBlank(cityName)) {
182 logger.log(Level.WARNING, String.format(
"No city name determined for line %d.", lineNum));
187 String stateName = csvRow.get(STATE_NAME_IDX);
188 String countryName = parseCountryName(csvRow.get(COUNTRY_NAME_IDX), lineNum);
190 Double lattitude = tryParse(csvRow.get(LAT_IDX));
191 if (lattitude == null) {
192 logger.log(Level.WARNING, String.format(
"No lattitude determined for line %d.", lineNum));
196 Double longitude = tryParse(csvRow.get(LONG_IDX));
197 if (longitude == null) {
198 logger.log(Level.WARNING, String.format(
"No longitude determined for line %d.", lineNum));
202 return new CityRecord(cityName, stateName, countryName, lattitude, longitude);
213 private List<String> parseCsvLine(String line,
int lineNum) {
214 if (line == null || line.length() <= 0) {
215 logger.log(Level.INFO, String.format(
"Line at %d had no content", lineNum));
219 List<String> allMatches =
new ArrayList<String>();
220 Matcher m = CSV_NAIVE_REGEX.matcher(line);
222 allMatches.add(m.group(1));
240 private List<CityRecord> parseCsvLines(InputStream csvInputStream,
boolean ignoreHeaderRow)
throws IOException {
241 List<CityRecord> cityRecords =
new ArrayList<>();
242 try (BufferedReader reader =
new BufferedReader(
new InputStreamReader(csvInputStream,
"UTF-8"))) {
244 String line = reader.readLine();
246 if (line != null && ignoreHeaderRow) {
247 line = reader.readLine();
251 while (line != null) {
253 List<String> rowElements = parseCsvLine(line, lineNum);
255 if (rowElements != null) {
256 cityRecords.add(getCsvCityRecord(rowElements, lineNum));
259 line = reader.readLine();