GeocellManager.java :  » UnTagged » smartshop-project » com » beoui » geocell » Android Open Source

Android Open Source » UnTagged » smartshop project 
smartshop project » com » beoui » geocell » GeocellManager.java
package com.beoui.geocell;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;

import vnfoss2010.smartshop.serverside.Global;
import vnfoss2010.smartshop.serverside.utils.SearchJanitorUtils;

import com.beoui.geocell.comparator.LocationComparableTuple;
import com.beoui.geocell.model.BoundingBox;
import com.beoui.geocell.model.CostFunction;
import com.beoui.geocell.model.DefaultCostFunction;
import com.beoui.geocell.model.GeocellQuery;
import com.beoui.geocell.model.LocationCapable;
import com.beoui.geocell.model.Point;
import com.beoui.geocell.model.Tuple;

/**
 #
 # Copyright 2010 Alexandre Gellibert
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 #
 #     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
 */

/**
 * Ported java version of python geocell:
 * http://code.google.com/p/geomodel/source/browse/trunk/geo/geocell.py
 * 
 * Defines the notion of 'geocells' and exposes methods to operate on them.
 * 
 * A geocell is a hexadecimal string that defines a two dimensional rectangular
 * region inside the [-90,90] x [-180,180] latitude/longitude space. A geocell's
 * 'resolution' is its length. For most practical purposes, at high resolutions,
 * geocells can be treated as single points.
 * 
 * Much like geohashes (see http://en.wikipedia.org/wiki/Geohash), geocells are
 * hierarchical, in that any prefix of a geocell is considered its ancestor,
 * with geocell[:-1] being geocell's immediate parent cell.
 * 
 * To calculate the rectangle of a given geocell string, first divide the
 * [-90,90] x [-180,180] latitude/longitude space evenly into a 4x4 grid like
 * so:
 * 
 * +---+---+---+---+ (90, 180) | a | b | e | f | +---+---+---+---+ | 8 | 9 | c |
 * d | +---+---+---+---+ | 2 | 3 | 6 | 7 | +---+---+---+---+ | 0 | 1 | 4 | 5 |
 * (-90,-180) +---+---+---+---+
 * 
 * NOTE: The point (0, 0) is at the intersection of grid cells 3, 6, 9 and c.
 * And, for example, cell 7 should be the sub-rectangle from (-45, 90) to (0,
 * 180).
 * 
 * Calculate the sub-rectangle for the first character of the geocell string and
 * re-divide this sub-rectangle into another 4x4 grid. For example, if the
 * geocell string is '78a', we will re-divide the sub-rectangle like so:
 * 
 * . . . . . . +----+----+----+----+ (0, 180) | 7a | 7b | 7e | 7f |
 * +----+----+----+----+ | 78 | 79 | 7c | 7d | +----+----+----+----+ | 72 | 73 |
 * 76 | 77 | +----+----+----+----+ | 70 | 71 | 74 | 75 | . . (-45,90)
 * +----+----+----+----+ . . . .
 * 
 * Continue to re-divide into sub-rectangles and 4x4 grids until the entire
 * geocell string has been exhausted. The final sub-rectangle is the rectangular
 * region for the geocell.
 * 
 * @author api.roman.public@gmail.com (Roman Nurik)
 * @author (java portage) Alexandre Gellibert
 * 
 * 
 */

public class GeocellManager {

  static Logger log = Logger.getLogger(GeocellManager.class.getName());
  // The maximum *practical* geocell resolution.
  public static final int MAX_GEOCELL_RESOLUTION = 13;

  // The maximum number of geocells to consider for a bounding box search.
  private static final int MAX_FEASIBLE_BBOX_SEARCH_CELLS = 300;

  // Function used if no custom function is used in bestBboxSearchCells method
  private static final CostFunction DEFAULT_COST_FUNCTION = new DefaultCostFunction();

  private static final Logger logger = GeocellLogger.get();

  /**
   * Returns the list of geocells (all resolutions) that are containing the
   * point
   * 
   * @param point
   * @return Returns the list of geocells (all resolutions) that are
   *         containing the point
   */
  public static List<String> generateGeoCell(Point point) {
    List<String> geocells = new ArrayList<String>();
    String geocellMax = GeocellUtils.compute(point,
        GeocellManager.MAX_GEOCELL_RESOLUTION);
    for (int i = 1; i < GeocellManager.MAX_GEOCELL_RESOLUTION; i++) {
      geocells.add(GeocellUtils.compute(point, i));
    }
    geocells.add(geocellMax);
    return geocells;
  }

  /**
   * Returns an efficient set of geocells to search in a bounding box query.
   * 
   * This method is guaranteed to return a set of geocells having the same
   * resolution.
   * 
   * @param bbox
   *            : A geotypes.Box indicating the bounding box being searched.
   * @param costFunction
   *            : A function that accepts two arguments: numCells: the number
   *            of cells to search resolution: the resolution of each cell to
   *            search and returns the 'cost' of querying against this number
   *            of cells at the given resolution.)
   * @return A list of geocell strings that contain the given box.
   */
  public static List<String> bestBboxSearchCells(BoundingBox bbox,
      CostFunction costFunction) {

    String cellNE = GeocellUtils.compute(bbox.getNorthEast(),
        GeocellManager.MAX_GEOCELL_RESOLUTION);
    String cellSW = GeocellUtils.compute(bbox.getSouthWest(),
        GeocellManager.MAX_GEOCELL_RESOLUTION);

    // The current lowest BBOX-search cost found; start with practical
    // infinity.
    double minCost = Double.MAX_VALUE;

    // The set of cells having the lowest calculated BBOX-search cost.
    List<String> minCostCellSet = new ArrayList<String>();

    // First find the common prefix, if there is one.. this will be the base
    // resolution.. i.e. we don't have to look at any higher resolution
    // cells.
    int minResolution = 0;
    int maxResoltuion = Math.min(cellNE.length(), cellSW.length());
    while (minResolution < maxResoltuion
        && cellNE.substring(0, minResolution + 1).startsWith(
            cellSW.substring(0, minResolution + 1))) {
      minResolution++;
    }

    // Iteravely calculate all possible sets of cells that wholely contain
    // the requested bounding box.
    for (int curResolution = minResolution; curResolution < GeocellManager.MAX_GEOCELL_RESOLUTION + 1; curResolution++) {
      String curNE = cellNE.substring(0, curResolution);
      String curSW = cellSW.substring(0, curResolution);

      int numCells = GeocellUtils.interpolationCount(curNE, curSW);
      if (numCells > MAX_FEASIBLE_BBOX_SEARCH_CELLS) {
        continue;
      }

      List<String> cellSet = GeocellUtils.interpolate(curNE, curSW);
      Collections.sort(cellSet);

      double cost;
      if (costFunction == null) {
        cost = DEFAULT_COST_FUNCTION.defaultCostFunction(
            cellSet.size(), curResolution);
      } else {
        cost = costFunction.defaultCostFunction(cellSet.size(),
            curResolution);
      }

      if (cost <= minCost) {
        minCost = cost;
        minCostCellSet = cellSet;
      } else {
        if (minCostCellSet.size() == 0) {
          minCostCellSet = cellSet;
        }
        // Once the cost starts rising, we won't be able to do better,
        // so abort.
        break;
      }
    }
    logger.log(Level.INFO,
        "Calculate cells " + StringUtils.join(minCostCellSet, ", ")
            + " in box (" + bbox.getSouth() + "," + bbox.getWest()
            + ") (" + bbox.getNorth() + "," + bbox.getEast() + ")");
    return minCostCellSet;
  }

  /**
   * 
   * Performs a proximity/radius fetch on the given query.
   * 
   * Fetches at most <max_results> entities matching the given query, ordered
   * by ascending distance from the given center point, and optionally limited
   * by the given maximum distance.
   * 
   * This method uses a greedy algorithm that starts by searching
   * high-resolution geocells near the center point and gradually looking in
   * lower and lower resolution cells until max_results entities have been
   * found matching the given query and no closer possible entities can be
   * found.
   * 
   * @param center
   *            A Point indicating the center point around which to search for
   *            matching entities.
   * @param maxResults
   *            (required) must be > 0. The larger this number, the longer the
   *            fetch will take.
   * @param maxDistance
   *            (optional) A number indicating the maximum distance to search,
   *            in meters. Set to 0 if no max distance is expected
   * @param entityClass
   *            class of the entity to search. MUST implement LocationCapable
   *            class because we use entity location and key, and also
   *            "GEOCELLS" columnn in query.
   * @param baseQuery
   *            query that will be enhanced by algorithm. see GeocellQuery
   *            class for more information.
   * @param pm
   *            PersistentManager to be used to create new queries
   * @param maxGeocellResolution
   *            the resolution (size of cell) when we start the algorithm. If
   *            you expect your search to run until big boxes (not many
   *            entities near the center), think about using a lower
   *            resolution for better performance. If you don't want to
   *            bother, use other method below without this parameter.
   * @return the list of entities found near the center and ordered by
   *         distance.
   * 
   * @throws all
   *             exceptions that can be thrown when running queries.
   */
  @SuppressWarnings("unchecked")
  public static final <T extends LocationCapable> List<T> proximityFetch(
      Point center, int maxResults, double maxDistance,
      Class<T> entityClass, GeocellQuery baseQuery,
      PersistenceManager pm, int maxGeocellResolution) {
    if (maxResults == 0)
      maxResults = Integer.MAX_VALUE;
    List<LocationComparableTuple<T>> results = new ArrayList<LocationComparableTuple<T>>();

    Validate.isTrue(maxGeocellResolution <= MAX_GEOCELL_RESOLUTION,
        "Invalid max resolution parameter. Must be inferior to ",
        MAX_GEOCELL_RESOLUTION);

    // The current search geocell containing the lat,lon.
    String curContainingGeocell = GeocellUtils.compute(center,
        maxGeocellResolution);

    // Set of already searched cells
    Set<String> searchedCells = new HashSet<String>();

    /*
     * The currently-being-searched geocells. NOTES: Start with max
     * possible. Must always be of the same resolution. Must always form a
     * rectangular region. One of these must be equal to the
     * cur_containing_geocell.
     */
    List<String> curGeocells = new ArrayList<String>();
    curGeocells.add(curContainingGeocell);
    double closestPossibleNextResultDist = 0;

    /*
     * Assumes both a and b are lists of (entity, dist) tuples, *sorted by
     * dist*. NOTE: This is an in-place merge, and there are guaranteed no
     * duplicates in the resulting list.
     */

    int noDirection[] = { 0, 0 };
    List<Tuple<int[], Double>> sortedEdgesDistances = Arrays
        .asList(new Tuple<int[], Double>(noDirection, 0d));

    while (!curGeocells.isEmpty()) {
      closestPossibleNextResultDist = sortedEdgesDistances.get(0)
          .getSecond();
      if (maxDistance > 0 && closestPossibleNextResultDist > maxDistance) {
        break;
      }

      Set<String> curTempUnique = new HashSet<String>(curGeocells);
      curTempUnique.removeAll(searchedCells);
      List<String> curGeocellsUnique = new ArrayList<String>(
          curTempUnique);

      // Run query on the next set of geocells.
      // String and =
      // baseQuery.getBaseQuery().toUpperCase().contains("WHERE") ? " && "
      // : " ";
      String and = baseQuery.getBaseQuery() != null
          && baseQuery.getBaseQuery().trim().length() != 0 ? " && "
          : " ";
      Query query = pm.newQuery(entityClass, baseQuery.getBaseQuery()
          + and + "geocellsP.contains(geocells)");

      if (baseQuery.getDeclaredParameters() == null
          || baseQuery.getDeclaredParameters().trim().length() == 0) {
        query.declareParameters("String geocellsP");
      } else {
        query.declareParameters(baseQuery.getDeclaredParameters()
            + ", String geocellsP");
      }

      List<T> newResultEntities;
      if (baseQuery.getParameters() == null
          || baseQuery.getParameters().isEmpty()) {
        newResultEntities = (List<T>) query.execute(curGeocellsUnique);
      } else {
        List<Object> parameters = new ArrayList<Object>(
            baseQuery.getParameters());

        parameters.add(curGeocellsUnique);
        newResultEntities = (List<T>) query.executeWithArray(parameters
            .toArray());
      }

      logger.log(
          Level.FINE,
          "fetch complete for: "
              + StringUtils.join(curGeocellsUnique, ", "));

      searchedCells.addAll(curGeocells);

      // Begin storing distance from the search result entity to the
      // search center along with the search result itself, in a tuple.
      List<LocationComparableTuple<T>> newResults = new ArrayList<LocationComparableTuple<T>>();
      for (T entity : newResultEntities) {
        newResults.add(new LocationComparableTuple<T>(entity,
            GeocellUtils.distance(center, entity.getLocation())));
      }
      // TODO (Alex) we can optimize here. Sort is needed only if
      // new_results.size() > max_results.
      Collections.sort(newResults);
      newResults = newResults.subList(0,
          Math.min(maxResults, newResults.size()));

      // Merge new_results into results
      for (LocationComparableTuple<T> tuple : newResults) {
        // contains method will check if entity in tuple have same key
        if (!results.contains(tuple)) {
          results.add(tuple);
        }
      }

      Collections.sort(results);
      results = results.subList(0, Math.min(maxResults, results.size()));

      sortedEdgesDistances = GeocellUtils.distanceSortedEdges(
          curGeocells, center);

      if (results.size() == 0 || curGeocells.size() == 4) {
        /*
         * Either no results (in which case we optimize by not looking
         * at adjacents, go straight to the parent) or we've searched 4
         * adjacent geocells, in which case we should now search the
         * parents of those geocells.
         */
        curContainingGeocell = curContainingGeocell.substring(0,
            Math.max(curContainingGeocell.length() - 1, 0));
        if (curContainingGeocell.length() == 0) {
          break; // Done with search, we've searched everywhere.
        }
        List<String> oldCurGeocells = new ArrayList<String>(curGeocells);
        curGeocells.clear();
        for (String cell : oldCurGeocells) {
          if (cell.length() > 0) {
            String newCell = cell.substring(0, cell.length() - 1);
            if (!curGeocells.contains(newCell)) {
              curGeocells.add(newCell);
            }
          }
        }
        if (curGeocells.size() == 0) {
          break; // Done with search, we've searched everywhere.
        }
      } else if (curGeocells.size() == 1) {
        // Get adjacent in one direction.
        // TODO(romannurik): Watch for +/- 90 degree latitude edge case
        // geocells.
        int nearestEdge[] = sortedEdgesDistances.get(0).getFirst();
        curGeocells.add(GeocellUtils.adjacent(curGeocells.get(0),
            nearestEdge));
      } else if (curGeocells.size() == 2) {
        // Get adjacents in perpendicular direction.
        int nearestEdge[] = GeocellUtils
            .distanceSortedEdges(
                Arrays.asList(curContainingGeocell), center)
            .get(0).getFirst();
        int[] perpendicularNearestEdge = { 0, 0 };
        if (nearestEdge[0] == 0) {
          // Was vertical, perpendicular is horizontal.
          for (Tuple<int[], Double> edgeDistance : sortedEdgesDistances) {
            if (edgeDistance.getFirst()[0] != 0) {
              perpendicularNearestEdge = edgeDistance.getFirst();
              break;
            }
          }
        } else {
          // Was horizontal, perpendicular is vertical.
          for (Tuple<int[], Double> edgeDistance : sortedEdgesDistances) {
            if (edgeDistance.getFirst()[0] == 0) {
              perpendicularNearestEdge = edgeDistance.getFirst();
              break;
            }
          }
        }
        List<String> tempCells = new ArrayList<String>();
        for (String cell : curGeocells) {
          tempCells.add(GeocellUtils.adjacent(cell,
              perpendicularNearestEdge));
        }
        curGeocells.addAll(tempCells);
      }

      // We don't have enough items yet, keep searching.
      if (results.size() < maxResults) {
        logger.log(Level.FINE, results.size()
            + " results found but want " + maxResults
            + " results, continuing search.");
        continue;
      }

      logger.log(Level.FINE, results.size() + " results found.");

      // If the currently max_results'th closest item is closer than any
      // of the next test geocells, we're done searching.
      double currentFarthestReturnableResultDist = GeocellUtils.distance(
          center, results.get(maxResults - 1).getFirst()
              .getLocation());
      if (closestPossibleNextResultDist >= currentFarthestReturnableResultDist) {
        logger.log(Level.FINE, "DONE next result at least "
            + closestPossibleNextResultDist
            + " away, current farthest is "
            + currentFarthestReturnableResultDist + " dist");
        break;
      }
      logger.log(Level.FINE, "next result at least "
          + closestPossibleNextResultDist
          + " away, current farthest is "
          + currentFarthestReturnableResultDist + " dist");
    }// end while

    List<T> result = new ArrayList<T>();
    for (Tuple<T, Double> entry : results.subList(0,
        Math.min(maxResults, results.size()))) {
      if (maxDistance == 0 || entry.getSecond() < maxDistance) {
        result.add(entry.getFirst());
      }
    }
    logger.log(Level.INFO,
        "Proximity query looked in " + searchedCells.size()
            + " geocells and found " + result.size() + " results.");
    return result;
  }

  @SuppressWarnings("unchecked")
  public static final <T extends LocationCapable> List<T> proximityFetchNew(
      Point center, int maxResults, double maxDistance,
      Class<T> entityClass, GeocellQuery baseQuery,
      PersistenceManager pm, int maxGeocellResolution) {
    List<LocationComparableTuple<T>> results = new ArrayList<LocationComparableTuple<T>>();

    Validate.isTrue(maxGeocellResolution <= MAX_GEOCELL_RESOLUTION,
        "Invalid max resolution parameter. Must be inferior to ",
        MAX_GEOCELL_RESOLUTION);

    // The current search geocell containing the lat,lon.
    String curContainingGeocell = GeocellUtils.compute(center,
        maxGeocellResolution);

    // Set of already searched cells
    Set<String> searchedCells = new HashSet<String>();

    /*
     * The currently-being-searched geocells. NOTES: Start with max
     * possible. Must always be of the same resolution. Must always form a
     * rectangular region. One of these must be equal to the
     * cur_containing_geocell.
     */
    List<String> curGeocells = new ArrayList<String>();
    curGeocells.add(curContainingGeocell);
    double closestPossibleNextResultDist = 0;

    /*
     * Assumes both a and b are lists of (entity, dist) tuples, *sorted by
     * dist*. NOTE: This is an in-place merge, and there are guaranteed no
     * duplicates in the resulting list.
     */

    int noDirection[] = { 0, 0 };
    List<Tuple<int[], Double>> sortedEdgesDistances = Arrays
        .asList(new Tuple<int[], Double>(noDirection, 0d));

    while (!curGeocells.isEmpty()) {
      closestPossibleNextResultDist = sortedEdgesDistances.get(0)
          .getSecond();
      if (maxDistance > 0 && closestPossibleNextResultDist > maxDistance) {
        break;
      }

      Set<String> curTempUnique = new HashSet<String>(curGeocells);
      curTempUnique.removeAll(searchedCells);
      List<String> curGeocellsUnique = new ArrayList<String>(
          curTempUnique);

      // Run query on the next set of geocells.
      // String and =
      // baseQuery.getBaseQuery().toUpperCase().contains("WHERE") ? " && "
      // : " ";
      String and = baseQuery.getBaseQuery() != null
          && baseQuery.getBaseQuery().trim().length() != 0 ? " && "
          : " ";
      Query query = pm.newQuery(entityClass, baseQuery.getBaseQuery()
          + and + "geocellsP.contains(geocells)");

      if (baseQuery.getDeclaredParameters() == null
          || baseQuery.getDeclaredParameters().trim().length() == 0) {
        query.declareParameters("String geocellsP");
      } else {
        query.declareParameters(baseQuery.getDeclaredParameters()
            + ", String geocellsP");
      }

      if (baseQuery.getDecleredImports() != null
          && baseQuery.getDecleredImports().trim().length() > 0) {
        query.declareImports(baseQuery.getDecleredImports());
      }

      if (baseQuery.getToRecord() > 0
          && baseQuery.getToRecord() > baseQuery.getFromRecord()) {
        query.setRange(baseQuery.getFromRecord(),
            baseQuery.getToRecord());
      }

      List<T> newResultEntities;
      if (baseQuery.getParameters() == null
          || baseQuery.getParameters().isEmpty()) {
        newResultEntities = (List<T>) query.execute(curGeocellsUnique);
      } else {
        // List<Object> parameters = new ArrayList<Object>(baseQuery
        // .getParameters());
        // newResultEntities = (List<T>)
        // query.executeWithArray(parameters
        // .toArray());
        newResultEntities = (List<T>) query.execute(
            baseQuery.getParameters(), curGeocells);
      }

      logger.log(
          Level.FINE,
          "fetch complete for: "
              + StringUtils.join(curGeocellsUnique, ", "));

      searchedCells.addAll(curGeocells);

      // Begin storing distance from the search result entity to the
      // search center along with the search result itself, in a tuple.
      List<LocationComparableTuple<T>> newResults = new ArrayList<LocationComparableTuple<T>>();
      for (T entity : newResultEntities) {
        newResults.add(new LocationComparableTuple<T>(entity,
            GeocellUtils.distance(center, entity.getLocation())));
      }
      // TODO (Alex) we can optimize here. Sort is needed only if
      // new_results.size() > max_results.
      Collections.sort(newResults);
      newResults = newResults.subList(0,
          Math.min(maxResults, newResults.size()));

      // Merge new_results into results
      for (LocationComparableTuple<T> tuple : newResults) {
        // contains method will check if entity in tuple have same key
        if (!results.contains(tuple)) {
          results.add(tuple);
        }
      }

      Collections.sort(results);
      results = results.subList(0, Math.min(maxResults, results.size()));

      sortedEdgesDistances = GeocellUtils.distanceSortedEdges(
          curGeocells, center);

      if (results.size() == 0 || curGeocells.size() == 4) {
        /*
         * Either no results (in which case we optimize by not looking
         * at adjacents, go straight to the parent) or we've searched 4
         * adjacent geocells, in which case we should now search the
         * parents of those geocells.
         */
        curContainingGeocell = curContainingGeocell.substring(0,
            Math.max(curContainingGeocell.length() - 1, 0));
        if (curContainingGeocell.length() == 0) {
          break; // Done with search, we've searched everywhere.
        }
        List<String> oldCurGeocells = new ArrayList<String>(curGeocells);
        curGeocells.clear();
        for (String cell : oldCurGeocells) {
          if (cell.length() > 0) {
            String newCell = cell.substring(0, cell.length() - 1);
            if (!curGeocells.contains(newCell)) {
              curGeocells.add(newCell);
            }
          }
        }
        if (curGeocells.size() == 0) {
          break; // Done with search, we've searched everywhere.
        }
      } else if (curGeocells.size() == 1) {
        // Get adjacent in one direction.
        // TODO(romannurik): Watch for +/- 90 degree latitude edge case
        // geocells.
        int nearestEdge[] = sortedEdgesDistances.get(0).getFirst();
        curGeocells.add(GeocellUtils.adjacent(curGeocells.get(0),
            nearestEdge));
      } else if (curGeocells.size() == 2) {
        // Get adjacents in perpendicular direction.
        int nearestEdge[] = GeocellUtils
            .distanceSortedEdges(
                Arrays.asList(curContainingGeocell), center)
            .get(0).getFirst();
        int[] perpendicularNearestEdge = { 0, 0 };
        if (nearestEdge[0] == 0) {
          // Was vertical, perpendicular is horizontal.
          for (Tuple<int[], Double> edgeDistance : sortedEdgesDistances) {
            if (edgeDistance.getFirst()[0] != 0) {
              perpendicularNearestEdge = edgeDistance.getFirst();
              break;
            }
          }
        } else {
          // Was horizontal, perpendicular is vertical.
          for (Tuple<int[], Double> edgeDistance : sortedEdgesDistances) {
            if (edgeDistance.getFirst()[0] == 0) {
              perpendicularNearestEdge = edgeDistance.getFirst();
              break;
            }
          }
        }
        List<String> tempCells = new ArrayList<String>();
        for (String cell : curGeocells) {
          tempCells.add(GeocellUtils.adjacent(cell,
              perpendicularNearestEdge));
        }
        curGeocells.addAll(tempCells);
      }

      // We don't have enough items yet, keep searching.
      if (results.size() < maxResults) {
        logger.log(Level.FINE, results.size()
            + " results found but want " + maxResults
            + " results, continuing search.");
        continue;
      }

      logger.log(Level.FINE, results.size() + " results found.");

      // If the currently max_results'th closest item is closer than any
      // of the next test geocells, we're done searching.
      if (maxResults > 0) {
        double currentFarthestReturnableResultDist = GeocellUtils
            .distance(center, results.get(maxResults - 1)
                .getFirst().getLocation());
        if (closestPossibleNextResultDist >= currentFarthestReturnableResultDist) {
          logger.log(Level.FINE, "DONE next result at least "
              + closestPossibleNextResultDist
              + " away, current farthest is "
              + currentFarthestReturnableResultDist + " dist");
          break;
        }
        logger.log(Level.FINE, "next result at least "
            + closestPossibleNextResultDist
            + " away, current farthest is "
            + currentFarthestReturnableResultDist + " dist");
      }
    }
    List<T> result = new ArrayList<T>();
    for (Tuple<T, Double> entry : results.subList(0,
        Math.min(maxResults, results.size()))) {
      if (maxDistance == 0 || entry.getSecond() < maxDistance) {
        result.add(entry.getFirst());
      }
    }
    logger.log(Level.INFO,
        "Proximity query looked in " + searchedCells.size()
            + " geocells and found " + result.size() + " results.");
    return result;
  }

  /**
   * Enhance proximityFetchNew with query strring, allow to search in fts
   * column
   * 
   * @author VoMinhTam
   */
  public static final <T extends LocationCapable, SearchCapable> List<T> proximityFetchWithQuery(
      Point center, int maxResults, double maxDistance,
      String queryString, Class<T> entityClass, PersistenceManager pm,
      int maxGeocellResolution) {
    StringBuffer queryBuffer = new StringBuffer();
    StringBuffer declareParametersBuffer = new StringBuffer();
    List<Object> parametersForSearch = new ArrayList<Object>();
    if (queryString != null) {
      // Prepare to search
      Set<String> queryTokens = SearchJanitorUtils
          .getTokensForIndexingOrQuery(queryString,
              Global.MAXIMUM_NUMBER_OF_WORDS_TO_SEARCH);
      int parameterCounter = 0;
      parametersForSearch.addAll(queryTokens);
      while (parameterCounter < queryTokens.size()) {

        queryBuffer.append("fts == param" + parameterCounter);
        declareParametersBuffer.append("String param"
            + parameterCounter);

        if (parameterCounter + 1 < queryTokens.size()) {
          queryBuffer.append(" && ");
          declareParametersBuffer.append(", ");
        }
        parameterCounter++;
      }
      // ///////
    }

    GeocellQuery baseQuery = new GeocellQuery(queryBuffer.toString(),
        declareParametersBuffer.toString(), parametersForSearch);
    return proximityFetch(center, maxResults, maxDistance, entityClass,
        baseQuery, pm, maxGeocellResolution);
  }

  @SuppressWarnings("unchecked")
  public static final <T extends LocationCapable> List<T> proximityFetchNewN(
      Point center, int maxResults, double maxDistance,
      Class<T> entityClass, GeocellQuery baseQuery,
      PersistenceManager pm, int maxGeocellResolution) {
    List<LocationComparableTuple<T>> results = new ArrayList<LocationComparableTuple<T>>();

    ArrayList parameters = new ArrayList();

    Validate.isTrue(maxGeocellResolution <= MAX_GEOCELL_RESOLUTION,
        "Invalid max resolution parameter. Must be inferior to ",
        MAX_GEOCELL_RESOLUTION);

    // The current search geocell containing the lat,lon.
    String curContainingGeocell = GeocellUtils.compute(center,
        maxGeocellResolution);

    // Set of already searched cells
    Set<String> searchedCells = new HashSet<String>();

    /*
     * The currently-being-searched geocells. NOTES: Start with max
     * possible. Must always be of the same resolution. Must always form a
     * rectangular region. One of these must be equal to the
     * cur_containing_geocell.
     */
    List<String> curGeocells = new ArrayList<String>();
    curGeocells.add(curContainingGeocell);
    double closestPossibleNextResultDist = 0;

    /*
     * Assumes both a and b are lists of (entity, dist) tuples, *sorted by
     * dist*. NOTE: This is an in-place merge, and there are guaranteed no
     * duplicates in the resulting list.
     */

    int noDirection[] = { 0, 0 };
    List<Tuple<int[], Double>> sortedEdgesDistances = Arrays
        .asList(new Tuple<int[], Double>(noDirection, 0d));

    while (!curGeocells.isEmpty()) {
      closestPossibleNextResultDist = sortedEdgesDistances.get(0)
          .getSecond();
      if (maxDistance > 0 && closestPossibleNextResultDist > maxDistance) {
        break;
      }

      Set<String> curTempUnique = new HashSet<String>(curGeocells);
      curTempUnique.removeAll(searchedCells);
      List<String> curGeocellsUnique = new ArrayList<String>(
          curTempUnique);

      // Run query on the next set of geocells.
      // String and =
      // baseQuery.getBaseQuery().toUpperCase().contains("WHERE") ? " && "
      // : " ";
      String and = baseQuery.getBaseQuery() != null
          && baseQuery.getBaseQuery().trim().length() != 0 ? " && "
          : " ";
      // Query query = pm.newQuery(entityClass, baseQuery.getBaseQuery()
      // + and + "geocellsP.contains(geocells)");
      Query query = null;
      StringBuffer declareParametersBuffer = new StringBuffer();
      StringBuffer queryBuffer = new StringBuffer();
      int curGCSize = curGeocells.size();
      for (int i = 0; i < curGCSize; i++) {
        queryBuffer.append("geocells.contains(grocellsP" + i + ")");
        declareParametersBuffer.append("String grocellsP" + i);
        if (i != curGCSize - 1) {
          queryBuffer.append(" &&");
          declareParametersBuffer.append(", ");
        }
      }
      query = pm.newQuery(entityClass, baseQuery.getBaseQuery() + and
          + queryBuffer.toString());

      if (baseQuery.getDeclaredParameters() == null
          || baseQuery.getDeclaredParameters().trim().length() == 0) {
        query.declareParameters(declareParametersBuffer.toString());
      } else {
        query.declareParameters(baseQuery.getDeclaredParameters()
            + ", " + declareParametersBuffer.toString());
      }

      if (baseQuery.getDecleredImports() != null
          && baseQuery.getDecleredImports().trim().length() > 0) {
        query.declareImports(baseQuery.getDecleredImports());
      }

      if (baseQuery.getToRecord() > 0
          && baseQuery.getToRecord() > baseQuery.getFromRecord()) {
        query.setRange(baseQuery.getFromRecord(),
            baseQuery.getToRecord());
      }

      List<T> newResultEntities;
      if (baseQuery.getParameters() == null
          || baseQuery.getParameters().isEmpty()) {
        newResultEntities = (List<T>) query
            .executeWithArray(curGeocellsUnique.toArray());
      } else {
        parameters.clear();
        parameters.addAll(baseQuery.getParameters());
        parameters.addAll(curGeocells);
        newResultEntities = (List<T>) query.executeWithArray(parameters
            .toArray());
      }

      logger.log(
          Level.FINE,
          "fetch complete for: "
              + StringUtils.join(curGeocellsUnique, ", "));

      searchedCells.addAll(curGeocells);

      // Begin storing distance from the search result entity to the
      // search center along with the search result itself, in a tuple.
      List<LocationComparableTuple<T>> newResults = new ArrayList<LocationComparableTuple<T>>();
      for (T entity : newResultEntities) {
        newResults.add(new LocationComparableTuple<T>(entity,
            GeocellUtils.distance(center, entity.getLocation())));
      }
      // TODO (Alex) we can optimize here. Sort is needed only if
      // new_results.size() > max_results.
      Collections.sort(newResults);
      newResults = newResults.subList(0,
          Math.min(maxResults, newResults.size()));

      // Merge new_results into results
      for (LocationComparableTuple<T> tuple : newResults) {
        // contains method will check if entity in tuple have same key
        if (!results.contains(tuple)) {
          results.add(tuple);
        }
      }

      Collections.sort(results);
      results = results.subList(0, Math.min(maxResults, results.size()));

      sortedEdgesDistances = GeocellUtils.distanceSortedEdges(
          curGeocells, center);

      if (results.size() == 0 || curGeocells.size() == 4) {
        /*
         * Either no results (in which case we optimize by not looking
         * at adjacents, go straight to the parent) or we've searched 4
         * adjacent geocells, in which case we should now search the
         * parents of those geocells.
         */
        curContainingGeocell = curContainingGeocell.substring(0,
            Math.max(curContainingGeocell.length() - 1, 0));
        if (curContainingGeocell.length() == 0) {
          break; // Done with search, we've searched everywhere.
        }
        List<String> oldCurGeocells = new ArrayList<String>(curGeocells);
        curGeocells.clear();
        for (String cell : oldCurGeocells) {
          if (cell.length() > 0) {
            String newCell = cell.substring(0, cell.length() - 1);
            if (!curGeocells.contains(newCell)) {
              curGeocells.add(newCell);
            }
          }
        }
        if (curGeocells.size() == 0) {
          break; // Done with search, we've searched everywhere.
        }
      } else if (curGeocells.size() == 1) {
        // Get adjacent in one direction.
        // TODO(romannurik): Watch for +/- 90 degree latitude edge case
        // geocells.
        int nearestEdge[] = sortedEdgesDistances.get(0).getFirst();
        curGeocells.add(GeocellUtils.adjacent(curGeocells.get(0),
            nearestEdge));
      } else if (curGeocells.size() == 2) {
        // Get adjacents in perpendicular direction.
        int nearestEdge[] = GeocellUtils
            .distanceSortedEdges(
                Arrays.asList(curContainingGeocell), center)
            .get(0).getFirst();
        int[] perpendicularNearestEdge = { 0, 0 };
        if (nearestEdge[0] == 0) {
          // Was vertical, perpendicular is horizontal.
          for (Tuple<int[], Double> edgeDistance : sortedEdgesDistances) {
            if (edgeDistance.getFirst()[0] != 0) {
              perpendicularNearestEdge = edgeDistance.getFirst();
              break;
            }
          }
        } else {
          // Was horizontal, perpendicular is vertical.
          for (Tuple<int[], Double> edgeDistance : sortedEdgesDistances) {
            if (edgeDistance.getFirst()[0] == 0) {
              perpendicularNearestEdge = edgeDistance.getFirst();
              break;
            }
          }
        }
        List<String> tempCells = new ArrayList<String>();
        for (String cell : curGeocells) {
          tempCells.add(GeocellUtils.adjacent(cell,
              perpendicularNearestEdge));
        }
        curGeocells.addAll(tempCells);
      }

      // We don't have enough items yet, keep searching.
      if (results.size() < maxResults) {
        logger.log(Level.FINE, results.size()
            + " results found but want " + maxResults
            + " results, continuing search.");
        continue;
      }

      logger.log(Level.FINE, results.size() + " results found.");

      // If the currently max_results'th closest item is closer than any
      // of the next test geocells, we're done searching.
      if (maxResults > 0) {
        double currentFarthestReturnableResultDist = GeocellUtils
            .distance(center, results.get(maxResults - 1)
                .getFirst().getLocation());
        if (closestPossibleNextResultDist >= currentFarthestReturnableResultDist) {
          logger.log(Level.FINE, "DONE next result at least "
              + closestPossibleNextResultDist
              + " away, current farthest is "
              + currentFarthestReturnableResultDist + " dist");
          break;
        }
        logger.log(Level.FINE, "next result at least "
            + closestPossibleNextResultDist
            + " away, current farthest is "
            + currentFarthestReturnableResultDist + " dist");
      }
    }
    List<T> result = new ArrayList<T>();
    for (Tuple<T, Double> entry : results.subList(0,
        Math.min(maxResults, results.size()))) {
      if (maxDistance == 0 || entry.getSecond() < maxDistance) {
        result.add(entry.getFirst());
      }
    }
    logger.log(Level.INFO,
        "Proximity query looked in " + searchedCells.size()
            + " geocells and found " + result.size() + " results.");
    return result;
  }

  /**
   * Enhance proximityFetchNew with query strring, allow to search in fts
   * column
   * 
   * @author VoMinhTam
   */
  // @SuppressWarnings("unchecked")
  // public static final <T extends LocationCapable, SearchCapable> List<T>
  // proximityFetchWithQuery(
  // Point center, int maxResults, double maxDistance,
  // String queryString, Class<T> entityClass, GeocellQuery baseQuery,
  // PersistenceManager pm, int maxGeocellResolution) {
  // List<LocationComparableTuple<T>> results = new
  // ArrayList<LocationComparableTuple<T>>();
  //
  // Validate.isTrue(maxGeocellResolution <= MAX_GEOCELL_RESOLUTION,
  // "Invalid max resolution parameter. Must be inferior to ",
  // MAX_GEOCELL_RESOLUTION);
  //
  // // The current search geocell containing the lat,lon.
  // String curContainingGeocell = GeocellUtils.compute(center,
  // maxGeocellResolution);
  //
  // // Set of already searched cells
  // Set<String> searchedCells = new HashSet<String>();
  //
  // /*
  // * The currently-being-searched geocells. NOTES: Start with max
  // * possible. Must always be of the same resolution. Must always form a
  // * rectangular region. One of these must be equal to the
  // * cur_containing_geocell.
  // */
  // List<String> curGeocells = new ArrayList<String>();
  // curGeocells.add(curContainingGeocell);
  // double closestPossibleNextResultDist = 0;
  //
  // /*
  // * Assumes both a and b are lists of (entity, dist) tuples, *sorted by
  // * dist*. NOTE: This is an in-place merge, and there are guaranteed no
  // * duplicates in the resulting list.
  // */
  //
  // int noDirection[] = { 0, 0 };
  // List<Tuple<int[], Double>> sortedEdgesDistances = Arrays
  // .asList(new Tuple<int[], Double>(noDirection, 0d));
  //
  // while (!curGeocells.isEmpty()) {
  // closestPossibleNextResultDist = sortedEdgesDistances.get(0)
  // .getSecond();
  // if (maxDistance > 0 && closestPossibleNextResultDist > maxDistance) {
  // break;
  // }
  //
  // Set<String> curTempUnique = new HashSet<String>(curGeocells);
  // curTempUnique.removeAll(searchedCells);
  // List<String> curGeocellsUnique = new ArrayList<String>(
  // curTempUnique);
  //
  // // Prepare to search
  // StringBuffer queryBuffer = new StringBuffer();
  // // queryBuffer
  // // .append("SELECT FROM " + Product.class.getName() + " WHERE ");
  // Set<String> queryTokens = SearchJanitorUtils
  // .getTokensForIndexingOrQuery(queryString,
  // Global.MAXIMUM_NUMBER_OF_WORDS_TO_SEARCH);
  // List<Object> parametersForSearch = new ArrayList<Object>(
  // queryTokens);
  // StringBuffer declareParametersBuffer = new StringBuffer();
  // int parameterCounter = 0;
  //
  // while (parameterCounter < queryTokens.size()) {
  //
  // queryBuffer.append("fts == param" + parameterCounter);
  // declareParametersBuffer.append("String param"
  // + parameterCounter);
  //
  // if (parameterCounter + 1 < queryTokens.size()) {
  // queryBuffer.append(" && ");
  // declareParametersBuffer.append(", ");
  // }
  // parameterCounter++;
  // }
  // // ///////
  //
  // // Run query on the next set of geocells.
  // // String and =
  // // baseQuery.getBaseQuery().toUpperCase().contains("WHERE") ? " && "
  // // : " ";
  // String and = baseQuery.getBaseQuery() != null
  // && baseQuery.getBaseQuery().trim().length() != 0 ? " && "
  // : " ";
  // Query query = pm.newQuery(entityClass, baseQuery.getBaseQuery()
  // + and + "geocellsP.contains(geocells) && " + queryBuffer);
  //
  // List<T> newResultEntities = new ArrayList<T>();
  // if (baseQuery.getDeclaredParameters() == null
  // || baseQuery.getDeclaredParameters().trim().length() == 0) {
  // query.declareParameters("String geocellsP , " +
  // declareParametersBuffer.toString());
  // log.log(Level.SEVERE, "Declare: "
  // + declareParametersBuffer.toString());
  // parametersForSearch.add(0, curGeocellsUnique);
  // newResultEntities = (List<T>) query
  // .executeWithArray(parametersForSearch.toArray());
  // // newResultEntities = (List<T>) query.execute(curGeocellsUnique);
  // }
  // // else {
  // // query.declareParameters(baseQuery.getDeclaredParameters()
  // // + ", String geocellsP , "
  // // + declareParametersBuffer.toString());
  // // newResultEntities = (List<T>) query.execute(baseQuery
  // // .getParameters(), curGeocells);
  // // }
  //
  // logger.log(Level.SEVERE, "fetch complete for: "
  // + StringUtils.join(curGeocellsUnique, ", "));
  //
  // searchedCells.addAll(curGeocells);
  //
  // // Begin storing distance from the search result entity to the
  // // search center along with the search result itself, in a tuple.
  // List<LocationComparableTuple<T>> newResults = new
  // ArrayList<LocationComparableTuple<T>>();
  // for (T entity : newResultEntities) {
  // newResults.add(new LocationComparableTuple<T>(entity,
  // GeocellUtils.distance(center, entity.getLocation())));
  // }
  // // TODO (Alex) we can optimize here. Sort is needed only if
  // // new_results.size() > max_results.
  // Collections.sort(newResults);
  // newResults = newResults.subList(0, Math.min(maxResults, newResults
  // .size()));
  //
  // // Merge new_results into results
  // for (LocationComparableTuple<T> tuple : newResults) {
  // // contains method will check if entity in tuple have same key
  // if (!results.contains(tuple)) {
  // results.add(tuple);
  // }
  // }
  //
  // Collections.sort(results);
  // results = results.subList(0, Math.min(maxResults, results.size()));
  //
  // sortedEdgesDistances = GeocellUtils.distanceSortedEdges(
  // curGeocells, center);
  //
  // if (results.size() == 0 || curGeocells.size() == 4) {
  // /*
  // * Either no results (in which case we optimize by not looking
  // * at adjacents, go straight to the parent) or we've searched 4
  // * adjacent geocells, in which case we should now search the
  // * parents of those geocells.
  // */
  // curContainingGeocell = curContainingGeocell.substring(0, Math
  // .max(curContainingGeocell.length() - 1, 0));
  // if (curContainingGeocell.length() == 0) {
  // break; // Done with search, we've searched everywhere.
  // }
  // List<String> oldCurGeocells = new ArrayList<String>(curGeocells);
  // curGeocells.clear();
  // for (String cell : oldCurGeocells) {
  // if (cell.length() > 0) {
  // String newCell = cell.substring(0, cell.length() - 1);
  // if (!curGeocells.contains(newCell)) {
  // curGeocells.add(newCell);
  // }
  // }
  // }
  // if (curGeocells.size() == 0) {
  // break; // Done with search, we've searched everywhere.
  // }
  // } else if (curGeocells.size() == 1) {
  // // Get adjacent in one direction.
  // // TODO(romannurik): Watch for +/- 90 degree latitude edge case
  // // geocells.
  // int nearestEdge[] = sortedEdgesDistances.get(0).getFirst();
  // curGeocells.add(GeocellUtils.adjacent(curGeocells.get(0),
  // nearestEdge));
  // } else if (curGeocells.size() == 2) {
  // // Get adjacents in perpendicular direction.
  // int nearestEdge[] = GeocellUtils.distanceSortedEdges(
  // Arrays.asList(curContainingGeocell), center).get(0)
  // .getFirst();
  // int[] perpendicularNearestEdge = { 0, 0 };
  // if (nearestEdge[0] == 0) {
  // // Was vertical, perpendicular is horizontal.
  // for (Tuple<int[], Double> edgeDistance : sortedEdgesDistances) {
  // if (edgeDistance.getFirst()[0] != 0) {
  // perpendicularNearestEdge = edgeDistance.getFirst();
  // break;
  // }
  // }
  // } else {
  // // Was horizontal, perpendicular is vertical.
  // for (Tuple<int[], Double> edgeDistance : sortedEdgesDistances) {
  // if (edgeDistance.getFirst()[0] == 0) {
  // perpendicularNearestEdge = edgeDistance.getFirst();
  // break;
  // }
  // }
  // }
  // List<String> tempCells = new ArrayList<String>();
  // for (String cell : curGeocells) {
  // tempCells.add(GeocellUtils.adjacent(cell,
  // perpendicularNearestEdge));
  // }
  // curGeocells.addAll(tempCells);
  // }
  //
  // // We don't have enough items yet, keep searching.
  // if (results.size() < maxResults) {
  // logger.log(Level.FINE, results.size()
  // + " results found but want " + maxResults
  // + " results, continuing search.");
  // continue;
  // }
  //
  // logger.log(Level.FINE, results.size() + " results found.");
  //
  // // If the currently max_results'th closest item is closer than any
  // // of the next test geocells, we're done searching.
  // if (maxResults > 0) {
  // double currentFarthestReturnableResultDist = GeocellUtils
  // .distance(center, results.get(maxResults - 1)
  // .getFirst().getLocation());
  // if (closestPossibleNextResultDist >= currentFarthestReturnableResultDist)
  // {
  // logger.log(Level.FINE, "DONE next result at least "
  // + closestPossibleNextResultDist
  // + " away, current farthest is "
  // + currentFarthestReturnableResultDist + " dist");
  // break;
  // }
  // logger.log(Level.FINE, "next result at least "
  // + closestPossibleNextResultDist
  // + " away, current farthest is "
  // + currentFarthestReturnableResultDist + " dist");
  // }
  // }
  // List<T> result = new ArrayList<T>();
  // for (Tuple<T, Double> entry : results.subList(0, Math.min(maxResults,
  // results.size()))) {
  // if (maxDistance == 0 || entry.getSecond() < maxDistance) {
  // result.add(entry.getFirst());
  // }
  // }
  // logger.log(Level.INFO, "Proximity query looked in "
  // + searchedCells.size() + " geocells and found " + result.size()
  // + " results.");
  // return result;
  // }

  /**
   * 
   * See javadoc of method with parameter maxResolution. Use
   * MAX_GEOCELL_RESOLUTION as a starting resolution.
   * 
   */
  public static final <T extends LocationCapable> List<T> proximityFetch(
      Point center, int maxResults, double maxDistance,
      Class<T> entityClass, GeocellQuery baseQuery, PersistenceManager pm) {
    return proximityFetchNew(center, maxResults, maxDistance, entityClass,
        baseQuery, pm, MAX_GEOCELL_RESOLUTION);
  }

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.