* @throws ClavinException if an error occurs while resolving locations
*/
public ResolvedMultipartLocation resolveMultipartLocation(MultipartLocationName location, boolean fuzzy)
throws ClavinException {
// find all component locations in the gazetteer
QueryBuilder queryBuilder = new QueryBuilder()
// translate CLAVIN 1.x 'fuzzy' parameter into NO_EXACT or OFF; it isn't
// necessary, or desirable to support FILL for the multi-part resolution algorithm
.fuzzyMode(fuzzy ? FuzzyMode.NO_EXACT : FuzzyMode.OFF)
.includeHistorical(true)
.maxResults(MAX_RESULTS);
// country query should only include country-like feature codes
queryBuilder.location(location.getCountry()).addCountryCodes();
List<ResolvedLocation> countries = gazetteer.getClosestLocations(queryBuilder.build());
// remove all "countries" that are not considered top-level administrative divisions; this
// filters out territories that do not contain descendant GeoNames
Iterator<ResolvedLocation> iter = countries.iterator();
while (iter.hasNext()) {
if (!iter.next().getGeoname().isTopLevelAdminDivision()) {
iter.remove();
}
}
Set<CountryCode> foundCountries = EnumSet.noneOf(CountryCode.class);
// state query should only include admin-level feature codes with ancestors
// in the list of located countries
queryBuilder.location(location.getState()).clearFeatureCodes().addAdminCodes();
for (ResolvedLocation country : countries) {
queryBuilder.addParentIds(country.getGeoname().getGeonameID());
foundCountries.add(country.getGeoname().getPrimaryCountryCode());
}
List<ResolvedLocation> states = gazetteer.getClosestLocations(queryBuilder.build());
// city query should only include city-level feature codes; ancestry is restricted
// to the discovered states or, if no states were found, the discovered countries or,
// if neither states nor countries were found, no ancestry restrictions are added and
// the most populated city will be selected
queryBuilder.location(location.getCity()).clearFeatureCodes().addCityCodes();
if (!states.isEmpty()) {
Set<CountryCode> stateCodes = EnumSet.noneOf(CountryCode.class);
// only clear the parent ID restrictions if states were found; otherwise
// we will continue our search based on the existing country restrictions, if any
queryBuilder.clearParentIds();
for (ResolvedLocation state : states) {
// only include the first administrative division found for each target
// country
if (!stateCodes.contains(state.getGeoname().getPrimaryCountryCode())) {
queryBuilder.addParentIds(state.getGeoname().getGeonameID());
stateCodes.add(state.getGeoname().getPrimaryCountryCode());
}
// since we are only including one "state" per country, short-circuit
// the loop if we have added one for each unique country code returned
// by the countries search
if (!foundCountries.isEmpty() && foundCountries.equals(stateCodes)) {
break;
}
}
}
List<ResolvedLocation> cities = gazetteer.getClosestLocations(queryBuilder.build());
// initialize return objects components
ResolvedLocation finalCity = null;
ResolvedLocation finalState = null;
ResolvedLocation finalCountry = null;