//***************************************************************************** // This file is part of CheckIn4Me. Copyright � 2010 David Ivins // // CheckIn4Me is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // CheckIn4Me is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with CheckIn4Me. If not, see <http://www.gnu.org/licenses/>. //***************************************************************************** package com.davidivins.checkin4me.core; import com.davidivins.checkin4me.comparators.LocaleNameComparator; import com.davidivins.checkin4me.comparators.LocaleServicesTotalComparator; import java.util.*; public class Algorithms { private static final double EARTH_MEAN_RADIUS = 6371.0; // mean radius of earth in km private Algorithms() {} /** * mergeLocations * * merges locations from service api provided location lists into one list * * @param location_lists * @return List<Locale> */ synchronized static public List<Locale> mergeLocations(List<List<Locale>> location_lists) { int current_index = 0; List<Locale> locations = new ArrayList<Locale>(); Map<String, List<Integer>> name_indexes = new HashMap<String, List<Integer>>(); // if we don't have an empty list of location lists if (!location_lists.isEmpty()) { for (List<Locale> location_list : location_lists) { for (Locale location : location_list) { // if name already exists, add new map_id / location_id xref to existing location if (name_indexes.containsKey(location.getName().toLowerCase())) { boolean mapped = false; for (int index : name_indexes.get(location.getName().toLowerCase())) { double distance = Math.abs(getDistance(Double.valueOf(location.getLongitude()), Double.valueOf(location.getLatitude()), Double.valueOf(locations.get(index).getLongitude()), Double.valueOf(locations.get(index).getLatitude()))); // if the two locations are further than 1 km from each other, treat as different places if (distance <= 1.0) { Map<Integer,String> map_id_location_id_xref = location.getServiceIdToLocationIdMap(); Set<Integer> keys = map_id_location_id_xref.keySet(); for (int key : keys) // will only be one item for locations coming directly from api calls { if (!locations.get(index).getServiceIdToLocationIdMap().containsKey(key)) { locations.get(index).mapServiceIdToLocationId(key, map_id_location_id_xref.get(key)); mapped = true; break; } } } if (mapped) break; } // if we failed to map the location to any existing locations, even if the name existed // (ie: it wasn't close enough to any of the existing similarly named locations, add it // as a new location if (!mapped) { // add location to list locations.add(current_index, location); // get all possible xref names List<String> name_variations = getNameVariations(location.getName().toLowerCase()); // create arrays if necessary for (String name_variation : name_variations) { // create array of xrefs if array doesn't exist yet if (null == name_indexes.get(name_variation)) name_indexes.put(name_variation, new ArrayList<Integer>()); // add xref name_indexes.get(name_variation).add(current_index); } // increment location counter current_index++; } } else // add location as a new location if it doesn't exist yet { // add location to list locations.add(current_index, location); // get all possible xref names List<String> name_variations = getNameVariations(location.getName().toLowerCase()); // create arrays if necessary for (String name_variation : name_variations) { // create array of xrefs if array doesn't exist yet if (null == name_indexes.get(name_variation)) name_indexes.put(name_variation, new ArrayList<Integer>()); // add xref name_indexes.get(name_variation).add(current_index); } // increment location counter current_index++; } } } } // sort by locations with most services, secondary sort alphabetically Collections.sort(locations, new LocaleNameComparator()); Collections.sort(locations, new LocaleServicesTotalComparator()); return locations; } /** * getDistance * * gets the distance between two places in km given the places' longitudes and latitudes. * * @param longitude_1 * @param latitude_1 * @param longitude_2 * @param latitude_2 * @return double distance */ public static double getDistance(double longitude_1, double latitude_1, double longitude_2, double latitude_2) { double longitude_difference = Math.toRadians(longitude_2 - longitude_1); double latitude_difference = Math.toRadians(latitude_2 - latitude_1); double a = (Math.sin(latitude_difference / 2) * Math.sin(latitude_difference / 2)) + Math.cos(Math.toRadians(latitude_1)) * Math.cos(Math.toRadians(latitude_2)) * (Math.sin(longitude_difference / 2) * Math.sin(longitude_difference / 2)); double angle = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); // distance in km return angle * EARTH_MEAN_RADIUS; } /** * getNameVariations * * @param name * @return List<String> */ private static List<String> getNameVariations(String name) { List<String> name_variations = new ArrayList<String>(); name_variations.add(name); name_variations.add(name.replace("'", "")); name_variations.add(name.replace("-", " ")); name_variations.add(name.replace("-", "")); name_variations.add(name.replace(".", "")); name_variations.add(name.replace("'", "")); name_variations.add(name.replaceAll("[^A-Za-z0-9]", "")); return name_variations; } }