Geospatial Queries with Google Cloud Datastore (Java)
NOTICE
This article describes geospatial queries for Google Cloud Datastore + Java.
Please keep in mind that using Google Cloud Platform you can also use Cloud SQL (compatible with MySQL), which allows to use much more solutions / libraries for geospatial queries. |
Geospatial queries are integral part of modern applications which allow to serve geo-contextual data - ex. shops in the nearest surroundings.
PROBLEM: Currently Google Cloud Datastore doesn't provide built-in API to execute geospatial queries
This functionality is under development (alpha version, invitation only): https://cloud.google.com./appengine/docs/java/datastore/geosearchDatastore Geosearch API is available on local environment (DEVSERVER), but if you don't have invitation to use alpha version, you cannot deploy your solution to production:
Creating an index failed for entity_type: "Location"ancestor: falseProperty { name: "geoPt" mode: 3}: Permission denied for creating a search index
|
CURRENT SOLUTION: JavaGeoModel
For the time Datastore doesn't support native geospatial queries, you can use JavaGeoModel library ( https://github.com/apigee/javageomodel) in combination with JPA (or with any other API after implementing QueryEngine interface)Step 1: Annotate your domain class
Annotate your domain class to distinguish fields which store latitude and longitude and special field to keep information about geocells.package co.quickmenu; import com.beoui.geocell.annotations.Geocells; import com.beoui.geocell.annotations.Latitude; import com.beoui.geocell.annotations.Longitude; public class Location { private String name; @Latitude private double latitude; @Longitude private double longitude; @Geocells private List<String> geoCells; //not listing getters/setters to keep this example short } |
Step 2: Store Latitude / Longitude and Geocells data for every object
Ensure that every object in your DB has information about latitude, longitude and geoCells.GeoCells can be generated using GeocellManager:
import com.beoui.geocell.GeocellManager;
import com.beoui.geocell.model.
//create object and setup geo location
Location location = new Location(lat, lng);
List<String> geoCells =
GeocellManager.generateGeoCell(new Point(lat, lng));
location.setGeoCells(geoCells);
//store object using your persistence API
...
|
Step 3: Implement method to search objects within given circle (point + radius)
JPA API queries
If you are using JPA, just pass EntityManager to GeocellManager together with center point and radius. If you're using non-JPA method, please navigate to "Non-JPA queries" section.private List<Location> findLocations(Double lat, Double lng,
Double maxDistanceInMeters) {
Point center = new Point(lat, lng);
EntityManager em =
EntityManagerFactoryCache.getInstance().createEntityManager();
//Additional conditions can be applied using base query
GeocellQuery activeLocQuery =
new GeocellQuery("select location from Location location " +
"where location.active=true");
List<Location> result = GeocellManager.proximitySearch
(center, MAX_RESULTS, maxDistanceInMeters,
Location.class, baseQuery, em);
em.close();
return result;
}
|
NOTICE: JPA implementation of GeocellQueryEngine (com.beoui.geocell.JPAGeocellQueryEngine) doesn't support result limit (parameter passed to GeoCellManager.proximitySearch won't be applied). It's design issue and may lead to memory management issues.
Please keep in mind that JPA is not recommended for use with Datastore (https://cloud.google.com/appengine/docs/java/datastore/jpa/overview)
Non-JPA API queries
If you don't use JPA, you should implement class extending GeocellQueryEngine interface (specific for your persistence API), and pass instance of this class implementation to GeoCellManager.proximitySearch:package com.beoui.geocell;
import java.util.List;
import com.beoui.geocell.model.GeocellQuery;
public interface GeocellQueryEngine {
public <T> List<T> query(GeocellQuery baseQuery,
List<String> curGeocellsUnique, Class<T> entityClass);
}
|
//instantiate your implementation of query engine GeocellQueryEngine queryEngine = new DatastoreQueryEngine(); //pass it as an argument to GeocellManager#proximitySearch List<Location> locationsList = GeocellManager.proximitySearch(center, MAX_RESULTS, 0, maxDistanceInMeters, Location.class, baseQuery, queryEngine, GeocellManager.MAX_GEOCELL_RESOLUTION) .getResults(); |
EXAMPLE: Simple implementation for Datastore API (notice: it doesn't support base query).
package co.quickmenu;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import com.beoui.geocell.GeocellQueryEngine;
import com.beoui.geocell.GeocellUtils;
import com.beoui.geocell.model.GeocellQuery;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Query;
/**
* Simple implementation for Datastore (simple == ignores baseQuery)
*
*/
public class DatastoreQueryEngine implements GeocellQueryEngine {
private static Logger logger =
Logger.getLogger(DatastoreQueryEngine.class.getName());
private static int LIMIT = 200;
/* (non-Javadoc)
* @see com.beoui.geocell.GeocellQueryEngine#
* query(com.beoui.geocell.model.GeocellQuery,
* java.util.List, java.lang.Class)
*/
@Override
public <T> List<T> query(GeocellQuery baseQuery,
List<String> curGeocellsUnique, Class<T> entityClass) {
Object geocellsFieldName =
GeocellUtils.getGeocellsFieldName(entityClass);
logger.info("Executing query: " +
entityClass.getSimpleName() + "." + geocellsFieldName +
" in " + curGeocellsUnique);
DatastoreService datastore =
DatastoreServiceFactory.getDatastoreService();
Query query = new Query(entityClass.getSimpleName());
Query.FilterPredicate geoCellsFilter = new Query.FilterPredicate
("geoCells", FilterOperator.IN, curGeocellsUnique);
query.setFilter(geoCellsFilter);
List<Entity> entities = datastore.prepare(query)
.asList(FetchOptions.Builder.withLimit(LIMIT));
logger.info("Got " + entities.size() + " " +
entityClass.getSimpleName() + " matching criteria.");
return (List<T>)entities;
}
}
|
SUMMARY
Cons and pros of JavaGeoModel library:PROS | CONS |
---|---|
|
|
Other solutions
Google Search API:http://ralphbarbagallo.com/2013/05/14/app-engine-geospatial-datastore-search-a-new-way/
Komentarze
Prześlij komentarz