Skip to content

Commit aad960b

Browse files
committed
Merge pull request #225 from GoogleCloudPlatform/ds-geo
Add AE Datastore geo query sample.
2 parents 87ee84d + 2d79581 commit aad960b

File tree

10 files changed

+650
-0
lines changed

10 files changed

+650
-0
lines changed

appengine/datastore/geo/pom.xml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<!--
2+
Copyright 2016 Google Inc. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
<project>
17+
<modelVersion>4.0.0</modelVersion>
18+
<packaging>war</packaging>
19+
<version>1.0-SNAPSHOT</version>
20+
<groupId>com.example.appengine</groupId>
21+
<artifactId>appengine-datastore-geo</artifactId>
22+
23+
<parent>
24+
<groupId>com.google.cloud</groupId>
25+
<artifactId>doc-samples</artifactId>
26+
<version>1.0.0</version>
27+
<relativePath>../../..</relativePath>
28+
</parent>
29+
30+
<dependencies>
31+
<dependency>
32+
<groupId>com.google.appengine</groupId>
33+
<artifactId>appengine-api-1.0-sdk</artifactId>
34+
<version>${appengine.sdk.version}</version>
35+
</dependency>
36+
<dependency>
37+
<groupId>javax.servlet</groupId>
38+
<artifactId>servlet-api</artifactId>
39+
<type>jar</type>
40+
<scope>provided</scope>
41+
</dependency>
42+
43+
<!-- Test Dependencies -->
44+
<dependency>
45+
<groupId>junit</groupId>
46+
<artifactId>junit</artifactId>
47+
<version>4.10</version>
48+
<scope>test</scope>
49+
</dependency>
50+
<dependency>
51+
<groupId>org.mockito</groupId>
52+
<artifactId>mockito-all</artifactId>
53+
<version>1.10.19</version>
54+
<scope>test</scope>
55+
</dependency>
56+
<dependency>
57+
<groupId>com.google.appengine</groupId>
58+
<artifactId>appengine-testing</artifactId>
59+
<version>${appengine.sdk.version}</version>
60+
<scope>test</scope>
61+
</dependency>
62+
<dependency>
63+
<groupId>com.google.appengine</groupId>
64+
<artifactId>appengine-api-stubs</artifactId>
65+
<version>${appengine.sdk.version}</version>
66+
<scope>test</scope>
67+
</dependency>
68+
<dependency>
69+
<groupId>com.google.appengine</groupId>
70+
<artifactId>appengine-tools-sdk</artifactId>
71+
<version>${appengine.sdk.version}</version>
72+
<scope>test</scope>
73+
</dependency>
74+
<dependency>
75+
<groupId>com.google.truth</groupId>
76+
<artifactId>truth</artifactId>
77+
<version>0.28</version>
78+
<scope>test</scope>
79+
</dependency>
80+
</dependencies>
81+
82+
<build>
83+
<!-- for hot reload of the web application -->
84+
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
85+
<plugins>
86+
<!-- Parent POM defines ${appengine.sdk.version} (updates frequently). -->
87+
<plugin>
88+
<groupId>com.google.appengine</groupId>
89+
<artifactId>appengine-maven-plugin</artifactId>
90+
<version>${appengine.sdk.version}</version>
91+
</plugin>
92+
</plugins>
93+
</build>
94+
</project>
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.appengine;
18+
19+
import com.google.appengine.api.datastore.DatastoreService;
20+
import com.google.appengine.api.datastore.DatastoreServiceFactory;
21+
import com.google.appengine.api.datastore.Entity;
22+
import com.google.appengine.api.datastore.FetchOptions;
23+
import com.google.appengine.api.datastore.GeoPt;
24+
import com.google.appengine.api.datastore.Query;
25+
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
26+
import com.google.appengine.api.datastore.Query.Filter;
27+
import com.google.appengine.api.datastore.Query.FilterOperator;
28+
import com.google.appengine.api.datastore.Query.FilterPredicate;
29+
import com.google.appengine.api.datastore.Query.GeoRegion.Circle;
30+
import com.google.appengine.api.datastore.Query.GeoRegion.Rectangle;
31+
import com.google.appengine.api.datastore.Query.StContainsFilter;
32+
33+
import java.io.IOException;
34+
import java.io.PrintWriter;
35+
import java.util.List;
36+
37+
import javax.servlet.ServletException;
38+
import javax.servlet.http.HttpServlet;
39+
import javax.servlet.http.HttpServletRequest;
40+
import javax.servlet.http.HttpServletResponse;
41+
42+
/**
43+
* A servlet to demonstrate the use of Cloud Datastore geospatial queries.
44+
*/
45+
public class GeoServlet extends HttpServlet {
46+
static final String BRAND_PROPERTY = "brand";
47+
static final String LOCATION_PROPERTY = "location";
48+
49+
static final String BRAND_PARAMETER = "brand";
50+
static final String LATITUDE_PARAMETER = "lat";
51+
static final String LONGITUDE_PARAMETER = "lon";
52+
static final String RADIUS_PARAMETER = "r";
53+
54+
static final String BRAND_DEFAULT = "Ocean Ave Shell";
55+
static final String LATITUDE_DEFAULT = "37.7895873";
56+
static final String LONGITUDE_DEFAULT = "-122.3917317";
57+
static final String RADIUS_DEFAULT = "1000.0";
58+
59+
// Number of meters (approximately) in 1 degree of latitude.
60+
// http://gis.stackexchange.com/a/2964
61+
private static final double DEGREE_METERS = 111111.0;
62+
63+
private final DatastoreService datastore;
64+
65+
public GeoServlet() {
66+
datastore = DatastoreServiceFactory.getDatastoreService();
67+
}
68+
69+
private static String getParameterOrDefault(
70+
HttpServletRequest req, String parameter, String defaultValue) {
71+
String value = req.getParameter(parameter);
72+
if (value == null || value.isEmpty()) {
73+
value = defaultValue;
74+
}
75+
return value;
76+
}
77+
78+
private static GeoPt getOffsetPoint(
79+
GeoPt original, double latOffsetMeters, double lonOffsetMeters) {
80+
// Approximate the number of degrees to offset by meters.
81+
// http://gis.stackexchange.com/a/2964
82+
// How long (approximately) is one degree of longitude?
83+
double lonDegreeMeters = DEGREE_METERS * Math.cos(original.getLatitude());
84+
return new GeoPt(
85+
(float) (original.getLatitude() + latOffsetMeters / DEGREE_METERS),
86+
// This may cause errors if given points near the north or south pole.
87+
(float) (original.getLongitude() + lonOffsetMeters / lonDegreeMeters));
88+
}
89+
90+
@Override
91+
public void doGet(HttpServletRequest req, HttpServletResponse resp)
92+
throws IOException, ServletException {
93+
resp.setContentType("text/plain");
94+
resp.setCharacterEncoding("UTF-8");
95+
PrintWriter out = resp.getWriter();
96+
97+
String brand = getParameterOrDefault(req, BRAND_PARAMETER, BRAND_DEFAULT);
98+
String latStr = getParameterOrDefault(req, LATITUDE_PARAMETER, LATITUDE_DEFAULT);
99+
String lonStr = getParameterOrDefault(req, LONGITUDE_PARAMETER, LONGITUDE_DEFAULT);
100+
String radiusStr = getParameterOrDefault(req, RADIUS_PARAMETER, RADIUS_DEFAULT);
101+
102+
float latitude;
103+
float longitude;
104+
double r;
105+
try {
106+
latitude = Float.parseFloat(latStr);
107+
longitude = Float.parseFloat(lonStr);
108+
r = Double.parseDouble(radiusStr);
109+
} catch (IllegalArgumentException e) {
110+
resp.sendError(
111+
HttpServletResponse.SC_BAD_REQUEST, String.format("Got bad value: %s", e.getMessage()));
112+
return;
113+
}
114+
115+
// Get lat/lon for rectangle.
116+
GeoPt c = new GeoPt(latitude, longitude);
117+
GeoPt ne = getOffsetPoint(c, r, r);
118+
float neLat = ne.getLatitude();
119+
float neLon = ne.getLongitude();
120+
GeoPt sw = getOffsetPoint(c, -1 * r, -1 * r);
121+
float swLat = sw.getLatitude();
122+
float swLon = sw.getLongitude();
123+
124+
// [START geospatial_stcontainsfilter_examples]
125+
// Testing for containment within a circle
126+
GeoPt center = new GeoPt(latitude, longitude);
127+
double radius = r; // Value is in meters.
128+
Filter f1 = new StContainsFilter("location", new Circle(center, radius));
129+
Query q1 = new Query("GasStation").setFilter(f1);
130+
131+
// Testing for containment within a rectangle
132+
GeoPt southwest = new GeoPt(swLat, swLon);
133+
GeoPt northeast = new GeoPt(neLat, neLon);
134+
Filter f2 = new StContainsFilter("location", new Rectangle(southwest, northeast));
135+
Query q2 = new Query("GasStation").setFilter(f2);
136+
// [END geospatial_stcontainsfilter_examples]
137+
138+
List<Entity> circleResults = datastore.prepare(q1).asList(FetchOptions.Builder.withDefaults());
139+
out.printf("Got %d stations in %f meter radius circle.\n", circleResults.size(), radius);
140+
printStations(out, circleResults);
141+
out.println();
142+
143+
List<Entity> rectResults = datastore.prepare(q2).asList(FetchOptions.Builder.withDefaults());
144+
out.printf("Got %d stations in rectangle.\n", rectResults.size());
145+
printStations(out, rectResults);
146+
out.println();
147+
148+
List<Entity> brandResults = getStationsWithBrand(center, radius, brand);
149+
out.printf("Got %d stations in circle with brand %s.\n", brandResults.size(), brand);
150+
printStations(out, brandResults);
151+
out.println();
152+
}
153+
154+
private void printStations(PrintWriter out, List<Entity> stations) {
155+
for (Entity station : stations) {
156+
GeoPt location = (GeoPt) station.getProperty(LOCATION_PROPERTY);
157+
out.printf(
158+
"%s: @%f, %f\n",
159+
(String) station.getProperty(BRAND_PROPERTY),
160+
location.getLatitude(),
161+
location.getLongitude());
162+
}
163+
}
164+
165+
private List<Entity> getStationsWithBrand(GeoPt center, double radius, String value) {
166+
// [START geospatial_containment_and_equality_combination]
167+
Filter f =
168+
CompositeFilterOperator.and(
169+
new StContainsFilter("location", new Circle(center, radius)),
170+
new FilterPredicate("brand", FilterOperator.EQUAL, value));
171+
// [END geospatial_containment_and_equality_combination]
172+
Query q = new Query("GasStation").setFilter(f);
173+
return datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());
174+
}
175+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.appengine;
18+
19+
import com.google.appengine.api.datastore.DatastoreService;
20+
import com.google.appengine.api.datastore.DatastoreServiceFactory;
21+
import com.google.appengine.api.datastore.Entity;
22+
import com.google.appengine.api.datastore.EntityNotFoundException;
23+
import com.google.appengine.api.datastore.GeoPt;
24+
import com.google.appengine.api.datastore.Key;
25+
import com.google.appengine.api.datastore.KeyFactory;
26+
27+
import java.io.IOException;
28+
29+
import javax.servlet.ServletException;
30+
import javax.servlet.http.HttpServlet;
31+
import javax.servlet.http.HttpServletRequest;
32+
import javax.servlet.http.HttpServletResponse;
33+
34+
/**
35+
* A startup handler to populate the datastore with example entities.
36+
*/
37+
public class StartupServlet extends HttpServlet {
38+
static final String IS_POPULATED_ENTITY = "IsPopulated";
39+
static final String IS_POPULATED_KEY_NAME = "is-populated";
40+
41+
@Override
42+
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
43+
throws ServletException, IOException {
44+
resp.setContentType("text/plain");
45+
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
46+
47+
Key isPopulatedKey = KeyFactory.createKey(IS_POPULATED_ENTITY, IS_POPULATED_KEY_NAME);
48+
boolean isAlreadyPopulated;
49+
try {
50+
datastore.get(isPopulatedKey);
51+
isAlreadyPopulated = true;
52+
} catch (EntityNotFoundException expected) {
53+
isAlreadyPopulated = false;
54+
}
55+
if (isAlreadyPopulated) {
56+
resp.getWriter().println("ok");
57+
return;
58+
}
59+
60+
// [START create_entity_with_geopt_property]
61+
Entity station = new Entity("GasStation");
62+
station.setProperty("brand", "Ocean Ave Shell");
63+
station.setProperty("location", new GeoPt(37.7913156f, -122.3926051f));
64+
datastore.put(station);
65+
// [END create_entity_with_geopt_property]
66+
67+
station = new Entity("GasStation");
68+
station.setProperty("brand", "Charge Point Charging Station");
69+
station.setProperty("location", new GeoPt(37.7909778f, -122.3929963f));
70+
datastore.put(station);
71+
72+
station = new Entity("GasStation");
73+
station.setProperty("brand", "76");
74+
station.setProperty("location", new GeoPt(37.7860533f, -122.3940325f));
75+
datastore.put(station);
76+
77+
datastore.put(new Entity(isPopulatedKey));
78+
resp.getWriter().println("ok");
79+
}
80+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright 2016 Google Inc. All Rights Reserved.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
18+
<application>YOUR-PROJECT-ID</application>
19+
<version>YOUR-VERSION-ID</version>
20+
<threadsafe>true</threadsafe>
21+
</appengine-web-app>

0 commit comments

Comments
 (0)