/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package org.apache.usergrid.persistence; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.usergrid.AbstractCoreIT; import org.apache.usergrid.utils.MapUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class GeoIT extends AbstractCoreIT { private static final Logger logger = LoggerFactory.getLogger(GeoIT.class); int NEARBY_RADIUS = 10000; int CIRCUMFERENCE_OF_THE_EARTH = 40000000; /* A list of concrete entities with locations to be used for geoQuery tests NOTE: Adding or removing items from this list could affect test outcome!!! */ private static List<Map<String, Object>> LOCATION_PROPERTIES = new ArrayList<Map<String, Object>>(); static { LOCATION_PROPERTIES.add(new LinkedHashMap<String, Object>() {{ put("name", "norwest"); put("location", new LinkedHashMap<String, Object>() {{ put("latitude", -33.746369); put("longitude", 150.952183); }}); }}); LOCATION_PROPERTIES.add(new LinkedHashMap<String, Object>() {{ put("type", "store"); put("name", "ashfield"); put("location", new LinkedHashMap<String, Object>() {{ put("latitude", -33.889058); put("longitude", 151.124024); }}); }}); } public GeoIT() throws Exception { super(); } @Test public void testRoundingGeolocationIssue() throws Exception { EntityManager em = app.getEntityManager(); assertNotNull(em); //1. Create an entity with location Entity office = createEntitywithLocation( em,"office",37.334115,-121.894340); assertNotNull(office); Entity pizza = createEntitywithLocation( em,"pizza",37.335616,-121.894168); assertNotNull(pizza); Entity market = createEntitywithLocation( em,"market",37.336499,-121.894356); assertNotNull(market); Entity park = createEntitywithLocation( em,"park",37.339079,-121.891422); assertNotNull(park); Entity news = createEntitywithLocation( em,"news",37.337812,-121.890784); assertNotNull(news); Entity hotel = createEntitywithLocation( em,"hotel",37.334370,-121.895081); assertNotNull(hotel); app.waitForQueueDrainAndRefreshIndex(); //2. Query with a globally large distance to verify location Query query = Query.fromQL("select * where location within 609.7 of 37.334110260009766, -121.89434051513672"); Results listResults = em.searchCollection(em.getApplicationRef(), "collars", query); assertEquals( 5, listResults.size()); query = Query.fromQL("select * where location within 609.8 of 37.334110260009766, -121.89434051513672"); listResults = em.searchCollection(em.getApplicationRef(), "collars", query); assertEquals( 6, listResults.size()); } private Entity createEntitywithLocation( final EntityManager em,String name ,Double lat,Double lon) throws Exception { Map<String, Object> properties = new LinkedHashMap<String, Object>() {{ put("name", name); put("location", new LinkedHashMap<String, Object>() {{ put("latitude", lat); put("longitude", lon); }}); }}; return em.create("collars", properties); } @Test public void testRemovedLocationQuery() throws Exception { //Get the EntityManager instance EntityManager em = app.getEntityManager(); assertNotNull(em); //1. Create an entity with location Map<String, Object> properties = new LinkedHashMap<String, Object>() {{ put("username", "edanuff"); put("email", "ed@anuff.com"); put("location", new LinkedHashMap<String, Object>() {{ put("latitude", 37.776753); put("longitude", -122.407846); }}); }}; Entity user = em.create("user", properties); assertNotNull(user); app.waitForQueueDrainAndRefreshIndex(); //2. Query with a globally large distance to verify location Query query = Query.fromQL("select * where location within " + Integer.MAX_VALUE + " of 0, 0"); Results listResults = em.searchCollection(em.getApplicationRef(), "users", query); assertEquals("1 result returned", 1, listResults.size()); //3. Remove the entity's location properties.remove("location"); user.getDynamicProperties().remove("location"); em.updateProperties(user, properties); em.update(user); app.waitForQueueDrainAndRefreshIndex(); //4. Repeat the query, expecting no results listResults = em.searchCollection(em.getApplicationRef(), "users", query); assertEquals(0, listResults.size()); em.delete(user); } /** * Validate the ability to query a moving entity * 1. Create an entity with location * 2. Query from a point near the entity's location * 3. Move the entity farther away from the center point * 4. Run the same query again to verify the entity is no longer in the area */ @Test public void testMovingTarget() throws Exception { logger.info("GeoIT.testMovingTarget"); //Get the EntityManager instance EntityManager em = app.getEntityManager(); assertNotNull(em); //1. Create an entity with location Map<String, Object> properties = new LinkedHashMap<String, Object>() {{ put("username", "edanuff"); put("email", "ed@anuff.com"); put("location", new LinkedHashMap<String, Object>() {{ put("latitude", 37.776753); put("longitude", -122.407846); }}); }}; Entity user = em.create("user", properties); assertNotNull(user); app.waitForQueueDrainAndRefreshIndex(); final double lat = 37.776753; final double lon = -122.407846; //2. Query from a point near the entity's location Query query = Query.fromQL("select * where location within 100 of " + lat + "," + lon); Results listResults = em.searchCollection(em.getApplicationRef(), "users", query); assertEquals(1, listResults.size()); //3. Move the entity farther away from the center point updatePos(em, user, 37.428526, -122.140916); //4. Run the same query again to verify the entity is no longer in the area listResults = em.searchCollection(em.getApplicationRef(), "users", query); assertEquals(0, listResults.size()); em.delete(user); } /** * Validate the ability to query a moving entity * 1. Create an entity with location * 2. Query from a point near the entity's location * 3. Move the entity farther away from the center point * 4. Run the same query again to verify the entity is no longer in the area */ @Test public void validateDistanceQueryExists() throws Exception { logger.info("GeoIT.validateDistanceQueryExists"); //Get the EntityManager instance EntityManager em = app.getEntityManager(); assertNotNull(em); //1. Create an entity with location Map<String, Object> properties = new LinkedHashMap<String, Object>() {{ put("username", "edanuff"); put("email", "ed@anuff.com"); put("location", new LinkedHashMap<String, Object>() {{ put("latitude", 37.776753); put("longitude", -122.407846); }}); }}; Entity user = em.create("user", properties); assertNotNull(user); app.waitForQueueDrainAndRefreshIndex(); final double lat = 37.776753; final double lon = -122.407846; //2. Query from a point near the entity's location Query query = Query.fromQL("select * where location within 100 of " + lat + "," + lon); Results listResults = em.searchCollection(em.getApplicationRef(), "users", query); assertEquals(1, listResults.size()); Entity entity = listResults.getEntity(); assertTrue(entity.getMetadata("distance")!=null); assertTrue(Double.parseDouble( entity.getMetadata("distance").toString())>0); em.delete(user); } /** * Validate the ability to query connections within proximity of the users * 1. Create an entity with location * 2. Create a user with location * 3. Create a connection between the user and the entity * 4. Test that the user is within 2000m of the entity * 5. Test that the user is NOT within 1000m of the entity */ @Test public void testGeoDistanceOfConnection() throws Exception { //Get the EntityManager instance EntityManager em = app.getEntityManager(); assertNotNull(em); //1. Create an entity with location Map<String, Object> restaurantProps = new LinkedHashMap<String, Object>(); restaurantProps.put("name", "Brickhouse"); restaurantProps.put("address", "426 Brannan Street"); restaurantProps.put("location", getLocation(37.779632, -122.395131)); Entity restaurant = em.create("restaurant", restaurantProps); assertNotNull(restaurant); //2. Create a user with location Map<String, Object> userProperties = new LinkedHashMap<String, Object>() {{ put("username", "edanuff"); put("email", "ed@anuff.com"); put("location", new LinkedHashMap<String, Object>() {{ put("latitude", 37.776753); put("longitude", -122.407846); }}); }}; Entity user = em.create("user", userProperties); assertNotNull(user); app.waitForQueueDrainAndRefreshIndex(); //3. Create a connection between the user and the entity em.createConnection(user, "likes", restaurant); app.waitForQueueDrainAndRefreshIndex(); //4. Test that the user is within 2000m of the entity Results emSearchResults = em.searchTargetEntities(user, Query.fromQL("location within 5000 of " + ((LinkedHashMap<String, Object>) userProperties.get("location")).get("latitude") + ", " + ((LinkedHashMap<String, Object>) userProperties.get("location")).get("longitude")).setConnectionType("likes")); assertEquals(1, emSearchResults.size()); //5. Test that the user is NOT within 1000m of the entity emSearchResults = em.searchTargetEntities(user, Query.fromQL("location within 1000 of " + ((LinkedHashMap<String, Object>) userProperties.get("location")).get("latitude") + ", " + ((LinkedHashMap<String, Object>) userProperties.get("location")).get("longitude")).setConnectionType("likes")); assertEquals(0, emSearchResults.size()); //cleanup em.delete(user); em.delete(restaurant); } /** * Validate loaded entities for geo queries * 1. load test entities * 2. validate the size of the result * 3. verify each entity has geo data */ @Test public void testGeolocationEntities() throws Exception { //1. load test entities EntityManager em = app.getEntityManager(); assertNotNull(em); //2. load test entities for (Map<String, Object> location : LOCATION_PROPERTIES) { Entity entity = em.create("store", location); assertNotNull(entity); logger.debug("Entity {} created", entity.getProperty("name")); } app.waitForQueueDrainAndRefreshIndex(); //2. validate the size of the result Query query = new Query(); Results listResults = em.searchCollection(em.getApplicationRef(), "stores", query); assertEquals("total number of 'stores'", LOCATION_PROPERTIES.size(), listResults.size()); //3. verify each entity has geo data for (Entity entity : listResults.entities) { Map location = (Map)entity.getProperty("location"); assertNotNull(location); assertNotNull(location.get("longitude")); assertNotNull(location.get("latitude")); } } @Test public void testNonGeolocationEntities() throws Exception { //1. load test entities EntityManager em = app.getEntityManager(); assertNotNull(em); List<Map<String, Object>> locations = new ArrayList<Map<String, Object>>(); locations.add(new LinkedHashMap<String, Object>() {{ put("name", "norwest"); put("location", "texas"); }}); locations.add(new LinkedHashMap<String, Object>() {{ put("type", "store"); put("name", "ashfield"); put("location", "new jersey"); }}); //2. load test entities for (Map<String, Object> location : locations) { Entity entity = em.create("store", location); assertNotNull(entity); logger.debug("Entity {} created", entity.getProperty("name")); } app.waitForQueueDrainAndRefreshIndex(); //2. validate the size of the result Query query = new Query(); Results listResults = em.searchCollection(em.getApplicationRef(), "stores", query); assertEquals("total number of 'stores'", locations.size(), listResults.size()); //3. verify each entity has geo data for (Entity entity : listResults.entities) { Object location = entity.getProperty("location"); assertNotNull(location); assertTrue(location instanceof String); } query = Query.fromQL("select * where location='texas'"); listResults = em.searchCollection(em.getApplicationRef(), "stores", query); assertEquals("total number of 'stores'", 1, listResults.size()); //3. verify each entity has geo data for (Entity entity : listResults.entities) { Object location = entity.getProperty("location"); assertNotNull(location); assertTrue(location instanceof String); } } @Test /** * Load entities with location data and query them from a far away location * 1. create entities with geo * 2. Query the collection from a point more than 10000m from the locations * and ensure that no entities are returned when restricted to a 10000m radius * 3. Query the collection from a point more than 10000m from the locations * and ensure that all entities are returned when the distance is set to the * circumference of the earth */ public void testGeoFromFarAwayLocation() throws Exception { //1. create entities with geo EntityManager em = loadGeolocationTestEntities(); //2. Query the collection from a point more than 10000m from the locations // and ensure that no entities are returned when restricted to a 10000m radius final double lat = 37.776753; final double lon = -122.407846; Query query = Query.fromQL("select * where location within " + NEARBY_RADIUS + " of " + lat + "," + lon); Results listResults = em.searchCollection(em.getApplicationRef(), "stores", query); assertEquals("Results within " + NEARBY_RADIUS + "m from center", 0, listResults.size()); //3. Query the collection from a point more than 10000m from the locations // and ensure that all entities are returned when the distance is set to the // circumference of the earth Query query2 = Query.fromQL("select * where location within " + CIRCUMFERENCE_OF_THE_EARTH + " of " + lat + "," + lon); listResults = em.searchCollection(em.getApplicationRef(), "stores", query2); assertEquals("Results within " + CIRCUMFERENCE_OF_THE_EARTH + "m from center", LOCATION_PROPERTIES.size(), listResults.size()); } @Test /** * Load entities with location data and query them from a nearby location * 1. create entities with geo * 2. Query the collection from a point less than 10000m from the locations * and ensure that one entity is returned when restricted to a 10000m radius * 3. Query the collection from a point less than 10000m from the locations * and ensure that all entities are returned when the distance is set to the * circumference of the earth */ public void testGeoFromNearbyLocation() throws Exception { logger.info( "GeoIT.testGeoFromNearbyLocation" ); //1. create entities with geo EntityManager em = loadGeolocationTestEntities(); final double lat = -33.746369; final double lon = 150.952185; //2. Query the collection from a point less than 10000m from the locations // and ensure that one entity is returned when restricted to a 10000m radius Query query = Query.fromQL("select * where location within " + NEARBY_RADIUS + " of " + lat + "," + lon); Results listResults = em.searchCollection(em.getApplicationRef(), "stores", query); assertEquals( "Results within " + NEARBY_RADIUS + "m from center", 1, listResults.size() ); //3. Query the collection from a point less than 10000m from the locations // and ensure that all entities are returned when the distance is set to the // circumference of the earth query = Query.fromQL( "select * where location within " + CIRCUMFERENCE_OF_THE_EARTH + " of " + lat + "," + lon ); listResults = em.searchCollection(em.getApplicationRef(), "stores", query); assertEquals( "Results within " + CIRCUMFERENCE_OF_THE_EARTH + "m from center", LOCATION_PROPERTIES.size(), listResults.size() ); } /** * Load entities with location data and query them from multiple locations * to ensure proper bounds * 1. Create entities with geo * 2. Create a list of points from different geographic areas * 3. Query the collection from each point * and ensure that no entities are returned when restricted to a 10000m radius * 4. Query the collection from each point * and ensure that all entities are returned when the distance is set to the * circumference of the earth */ @Test public void testGeoFromMultipleLocations() throws Exception { logger.info("GeoIT.testGeoFromMultipleLocations"); //1 Create entities with geo EntityManager em = loadGeolocationTestEntities(); //2 Create a list of points from different geographic areas List<double[]> points = new ArrayList<>(); points.add(new double[]{-90.000000, 90.000000});//Antarctica points.add(new double[]{90, 90});;//Santa's house points.add( new double[]{ 33.746369, -89});;//Woodland, MS points.add( new double[] { 34.35, 58.22 } );; //Buenos Aires points.add( new double[] { 39.55, 116.25 } );;//Beijing, China points.add( new double[]{ 44.52, 20.32});; //Belgrade, Serbia points.add( new double[] { -1.000000, 102.000000 } );;//Somewhere in Indonesia for (double[] center : points) { //3 Query the collection from each point // and ensure that no entities are returned when restricted to a 10000m radius final double lat = center[0]; final double lon = center[1]; Query query = Query.fromQL("select * where location within 10000 of " + lat + "," + lon); Results listResults = em.searchCollection(em.getApplicationRef(), "stores", query); assertEquals("Results less than 10000m away from center", 0, listResults.size()); //4 Query the collection from each point // and ensure that all entities are returned when the distance is set to the // circumference of the earth Query query2 = Query.fromQL("select * where location within 40000000 of " + lat + "," + lon); listResults = em.searchCollection(em.getApplicationRef(), "stores", query2); assertEquals("Results from center point to ridiculously far", LOCATION_PROPERTIES.size(), listResults.size()); } } @Test public void testPointPaging() throws Exception { EntityManager em = app.getEntityManager(); assertNotNull( em ); // save objects in a diagonal line from -90 -180 to 90 180 int numEntities = 50; float minLattitude = -90; float maxLattitude = 90; float minLongitude = -180; float maxLongitude = 180; float lattitudeDelta = (maxLattitude - minLattitude) / numEntities; float longitudeDelta = (maxLongitude - minLongitude) / numEntities; for (int i = 0; i < numEntities; i++) { float lattitude = minLattitude + lattitudeDelta * i; float longitude = minLongitude + longitudeDelta * i; Map<String, Float> location = MapUtils.hashMap("latitude", lattitude).map("longitude", longitude); Map<String, Object> data = new HashMap<String, Object>(2); data.put("name", String.valueOf(i)); data.put("location", location); em.create("store", data); } app.waitForQueueDrainAndRefreshIndex(); // earth's circumference is 40,075 kilometers. Up it to 50,000kilometers // just to be save Query query = Query.fromQL("location within 50000000 of -90, -180" ); query.setLimit( 10 ); int count = 0; Results results; double previousDistance = 0d; do { results = em.searchCollection(em.getApplicationRef(), "stores", query); for (Entity entity : results.getEntities()) { assertEquals(String.valueOf(count), entity.getName()); Object distanceObject = entity.getMetadata("distance"); assertNotNull( distanceObject ); assertTrue( distanceObject instanceof Double ); double distance = (Double)distanceObject; assertTrue( distance >= previousDistance ); previousDistance = distance; count++; } // set for the next "page" query.setCursor(results.getCursor()); } while (results.getCursor() != null); // check we got back all 500 entities assertEquals( numEntities, count ); } @Test public void testSamePointPaging() throws Exception { EntityManager em = app.getEntityManager(); assertNotNull(em); // save objects in a diagonal line from -90 -180 to 90 180 int numEntities = 10; for (int i = 0; i < numEntities; i++) { Map<String, Object> data = new HashMap<String, Object>(2); data.put("name", String.valueOf(i)); setPos(data, 0, 0); em.create("store", data); } app.waitForQueueDrainAndRefreshIndex(); Thread.sleep(2000); // earth's circumference is 40,075 kilometers. Up it to 50,000kilometers // just to be save Query query = Query.fromQL("location within 50000000 of 0, 0" ); query.setLimit( 5 ); int count = 0; Results results; double previousDistance = 0d; do { results = em.searchCollection(em.getApplicationRef(), "stores", query); for (Entity entity : results.getEntities()) { //TODO:can we assert order final int expected = numEntities - count - 1; Object distanceObject = entity.getMetadata("distance"); assertNotNull( distanceObject ); assertTrue( distanceObject instanceof Double ); double distance = (Double)distanceObject; assertTrue( distance >= previousDistance ); previousDistance = distance; assertEquals(String.valueOf(expected), entity.getName()); count++; } // set for the next "page" query.setCursor(results.getCursor()); } while (results.getCursor() != null); // check we got back all 500 entities assertEquals(numEntities, count); } @Test public void testDistanceByLimit() throws Exception { EntityManager em = app.getEntityManager(); assertNotNull(em); // save objects in a diagonal line from -90 -180 to 90 180 int numEntities = 10; float minLattitude = -90; float maxLattitude = 90; float minLongitude = -180; float maxLongitude = 180; float lattitudeDelta = (maxLattitude - minLattitude) / numEntities; float longitudeDelta = (maxLongitude - minLongitude) / numEntities; for (int i = 0; i < numEntities; i++) { float lattitude = minLattitude + lattitudeDelta * i; float longitude = minLongitude + longitudeDelta * i; Map<String, Float> location = MapUtils.hashMap("latitude", lattitude).map("longitude", longitude); Map<String, Object> data = new HashMap<String, Object>(2); data.put("name", String.valueOf(i)); data.put("location", location); em.create("store", data); } app.waitForQueueDrainAndRefreshIndex(); // earth's circumference is 40,075 kilometers. Up it to 50,000kilometers // just to be save Query query = Query.fromQL( "location within 50000000 of -90, -180" ); query.setLimit(10); int count = 0; do { Results results = em.searchCollection(em.getApplicationRef(), "stores", query); double previousDistance = 0d; for (Entity entity : results.getEntities()) { assertEquals(String.valueOf(count), entity.getName()); Object distanceObject = entity.getMetadata("distance"); assertNotNull( distanceObject ); assertTrue( distanceObject instanceof Double ); double distance = (Double)distanceObject; assertTrue( distance >= previousDistance ); previousDistance = distance; count++; } } while (query.getCursor().isPresent()); // check we got back all 500 entities assertEquals(numEntities, count); } @Test public void testGeoWithIntersection() throws Exception { EntityManager em = app.getEntityManager(); assertNotNull( em ); int size = 10; int min = 5; int max = 9; List<Entity> created = new ArrayList<Entity>(size); for (int i = 0; i < size; i++) { // save all entities in the same location Map<String, Object> data = new HashMap<String, Object>(2); data.put("name", String.valueOf(i)); data.put("index", i); setPos(data, 0, 0); Entity e = em.create("store", data); created.add(e); } app.waitForQueueDrainAndRefreshIndex(); int startDelta = size - min; // String queryString = String.format("select * where location within 100 of 0, // 0 and index >= %d and index < %d order by index",min, max); String queryString = String.format( "select * where index >= %d and index < %d order by index", min, max); Query query = Query.fromQL(queryString); Results r; int count = 0; do { r = em.searchCollection(em.getApplicationRef(), "stores", query); for (Entity e : r.getEntities()) { assertEquals(created.get(startDelta + count), e); count++; } query.setCursor(r.getCursor()); } while (r.hasCursor()); assertEquals(startDelta - (size - max), count); } @Test public void testDenseSearch() throws Exception { EntityManager em = app.getEntityManager(); assertNotNull(em); // save objects in a diagonal line from -90 -180 to 90 180 int numEntities = 25; float minLatitude = 48.32455f; float maxLatitude = 48.46481f; float minLongitude = 9.89561f; float maxLongitude = 10.0471f; float latitudeDelta = (maxLatitude - minLatitude) / numEntities; float longitudeDelta = (maxLongitude - minLongitude) / numEntities; for (int i = 0; i < numEntities; i++) { float latitude = minLatitude + latitudeDelta * i; float longitude = minLongitude + longitudeDelta * i; Map<String, Float> location = MapUtils.hashMap("latitude", latitude).map("longitude", longitude); Map<String, Object> data = new HashMap<String, Object>(2); data.put("name", String.valueOf(i)); data.put("location", location); em.create("store", data); } app.waitForQueueDrainAndRefreshIndex(); //do a direct geo iterator test. We need to make sure that we short circuit on the correct tile. int limit = 8; long startTime = System.currentTimeMillis(); //now test at the EM level, there should be 0 results. Query query = Query.fromQL("location within 1000 of 48.38626, 9.94175"); query.setLimit(limit); Results results = em.searchCollection(em.getApplicationRef(), "stores", query); assertEquals(0, results.size()); long endTime = System.currentTimeMillis(); logger.info("Runtime took {} milliseconds to search", endTime - startTime); } /** * Load entities for geo queries * 1. Get an instance of the entity manager * 2. load test entities * 3. refresh the index * 4. return the entity manager */ private EntityManager loadGeolocationTestEntities() throws Exception { logger.info("GeoIT.loadGeolocationTestEntities"); //1. Get an instance of the entity manager EntityManager em = app.getEntityManager(); assertNotNull(em); //2. load test entities for (Map<String, Object> location : LOCATION_PROPERTIES) { logger.info("Create entity with location '{}'", location.get("name")); Entity entity = em.create("store", location); assertNotNull(entity); } //3. refresh the index app.waitForQueueDrainAndRefreshIndex(); //4. return the entity manager return em; } public Map<String, Object> getLocation(double latitude, double longitude) throws Exception { Map<String, Object> latlong = new LinkedHashMap<String, Object>(); latlong.put("latitude", latitude); latlong.put("longitude", longitude); return latlong; } public void updatePos(EntityManager em, EntityRef entity, double latitude, double longitude) throws Exception { Map<String, Object> latlong = new LinkedHashMap<String, Object>(); latlong.put("latitude", latitude); latlong.put("longitude", longitude); em.setProperty(entity, "location", latlong); app.waitForQueueDrainAndRefreshIndex(); } public void setPos(Map<String, Object> data, double latitude, double longitude) { Map<String, Object> latlong = new LinkedHashMap<String, Object>(); latlong.put("latitude", latitude); latlong.put("longitude", longitude); data.put("location", latlong); } }