Using MongoDB Geospatial with Spring Data and a Basic JQuery Mobile UI
Join the DZone community and get the full member experience.
Join For Free

using mongodb geospatial with spring data and a basic jquery mobile ui
this article shows how to use the mongodb spatial feature in combination with spring and a web rest service.
at first let us introduce the purpose of the example web application. our application stores track informations about tracks an user has recorded with a gps tracking device. for simplification we assume that the tracking data exists in a json format. this document is stored in the mongodb with the additional information about the user who uploaded the track and an additional attribute about the position where the track starts.
with that track information stored in the mongodb we start a search, that includes all tracks with startpositions within a certain radius. these tracks are shown on a map.
the mongodb part
if you have not installed mongodb yet, download the latest version and install and start it. now we have to start the interactive mongo shell . in the console you type following commands shown in listing 1 to use the track collection and create a mongo spatial index.
use tracks; db.track.ensureindex( { start : "2d" } );
listing 1: the command to creaet a mongodb spatial index.
now you can use the mongodb spaitial feature. for example you can execute the command in listing 2 to find the tracks with a nearby search to a given point.
db.track.find( { start : { $near : [11.531439781188965,48.156700134277344] } } ).limit(2)
listing 2: find the two nearst tracks to the given point with the latitude and longitude.
or you can execute the command in listing 3 to find the tracks with a nearby search to a given point with a maximum distance. in the example we search in a distance of 1 kilometer. therefore we have to divide the value 1 by 111 to get a maximum distance of 1 kilometer. for 1 mile we have to divide the value by 69.
db.track.find( { start : { $near : [11.531439781188965,48.156700134277344] , $maxdistance : 0.009009009009009 } } ).limit(2);
listing 3: find the two nearest tracks to a given point with a maximum distance of 1 kilometer.
json document and matching java types
lets assume the track information is a json document and has the structure shown in listing 4.
{ "name": "nymphenburger schloss", "start": { "lon":11.53144, "lat":48.1567, "ele":520 }, "user": "joe@joe.com", "data": [ {"lon":11.53144, "lat":48.1567, "ele":520}, {"lon":11.53125, "lat":48.15672, "ele":520}, ... // additional track information ] }
listing 4: json document structure
the corresponding java object is shown in listing 5.
public class track implements serializable{ private string id; private string name; private position start; private string user; private list<position> data; // ... getter, setter, etc. ... }
listing 5: show the json document corresponding java object
the serializable interface is not necessarily required in this example but maybe you want to use another frontend technology like wicket. then at least you have to add the interface to your java object.
the next listing 6 shows the java object position that is used in the java object track.
public class position implements serializable { private double lon; private double lat; private double ele; // ... getter, setter, etc. ... }
listing 6: the position that is used in the track.
in this short example the user is not a java object it’s only a attribute in the track that has a unique identifier.
now we are ready to set up a spring configuration to use mongodb.
the spring rest web service
in the first part we introduced the objects we want to store in the mongodb. now we need a service to do that for us. lets have a look at the configuration and implementation of a rest web service for this example.
the listing 7 shows the configuration that is necessary in the web.xml to get spring and and our rest service going.
<?xml version="1.0" encoding="utf-8"?> <web-app ...> <display-name>mobile mongo application</display-name> <context-param> <param-name>contextconfiglocation</param-name> <param-value>classpath:com/comsysto/labs/mobile/tracks/applicationcontext-security.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.contextloaderlistener </listener-class> </listener> <servlet> <servlet-name>service</servlet-name> <servlet-class> org.springframework.web.servlet.dispatcherservlet </servlet-class> <init-param> <param-name>contextconfiglocation</param-name> <param-value>classpath:com/comsysto/labs/mobile/tracks/applicationcontext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>service</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> </web-app>
listing 7: shows the configuration of spring and a rest web service in the web.xml
with that configuration every request that calls an url under the path of /services delegetas to spring. for the servlet mapping with the name services a corresponding file with the name services-servlet.xml must be created. in that file nothing has to be configured. the interesting part is the applicationcontext.xml file that is placed in the the folder src/main/resources/com/comsysto/labs/mobile/tracks/.
listing 8 show the confiuguration of the applicationcontext.xml . this is the base spring configuration file in this example.
<?xml version="1.0" encoding="utf-8"?> <beans ...> <!-- to enable spring mvc --> <mvc:annotation-driven/> <!-- scan all the classes in this package for autodetection. here are our <!-- service classes with the annotation @controller are placed. --> <context:component-scan base-package="com.comsysto.labs.mobile.tracks.rest"/> <!-- the mongodb database connection --> <mongo:mongo id="mongo"/> <!-- defaults: host="127.0.0.1" port="27017"--> <!-- the primary implementation of the interface mongooperations that --> <!-- specifies a basic set of mongodb operations. here configured to use --> <!-- the collection tracks. --> <bean id="tracksmongooperations" class="org.springframework.data.mongodb.core.mongotemplate"> <constructor-arg> <ref bean="mongo"/> </constructor-arg> <constructor-arg value="tracks"/> </bean> </beans>
listing 8: shows the configuration of the applicationcontext.xml
now lets come to the part where everything glues together. therefore we have a look in our trackservice class shown in listing 9 in the package org.comsysto.labs.web, where the services are placed which are autodetected by the component scan.
@controller @requestmapping("/track") public class trackservice { @autowired @qualifier("tracksmongooperations") public mongooperations mongooperations; public static final double kilometer = 111.0d; /** * the attribute that is used for the search for the start position */ public static final string start = "start"; /** * the attribute that is used for the search for the user */ private static final string user = "user"; @requestmapping(value = "/get", method = requestmethod.get, produces = "application/json") public @responsebody list<track> getall() throws exception { return mongooperations.findall(track.class); } @requestmapping(value = "/get/{lon1}/{lat1}/{lon2}/{lat2}/", method = requestmethod.get, produces = "application/json") public @responsebody list<track> getbybounds(@pathvariable("lon1") double lon1, @pathvariable("lat1") double lat1, @pathvariable("lon2") double lon2, @pathvariable("lat2") double lat2) throws exception { /** > box = [[40.73083, -73.99756], [40.741404, -73.988135]] > db.places.find({"loc" : {"$within" : {"$box" : box}}}) **/ criteria criteria = new criteria(start).within(new box(new point(lon1, lat1), new point(lon2, lat2))); list<track> tracks = mongooperations.find(new query(criteria), track.class); return tracks; } @requestmapping(value = "/get/{lon}/{lat}/{maxdistance}", method = requestmethod.get, produces = "application/json") public @responsebody list<track> getbylocation(@pathvariable("lon") double lon, @pathvariable("lat") double lat, @pathvariable("maxdistance") double maxdistance) throws exception { criteria criteria = new criteria(start).near(new point(lon, lat)). maxdistance(getinkilometer(maxdistance)); list<track> tracks = mongooperations.find(new query(criteria), track.class); return tracks; } /** * the current implementation of near assumes an idealized model of a flat * earth, meaning that an arcdegree * of latitude (y) and longitude (x) represent the same distance everywhere. * this is only true at the equator where they are both about equal to 69 miles * or 111km. therefore you must divide the * distance you want by 111 for kilometer and 69 for miles. * * @param maxdistance the distance around a point. * @return the calcuated distance in kilometer. */ private double getinkilometer(double maxdistance) { return maxdistance / kilometer; } @requestmapping(value = "/add", method = requestmethod.post, consumes = "application/json") @responsestatus(httpstatus.ok) public void add(@requestbody track track) throws exception { mongooperations.insert(track); } @requestmapping(value = "/foruser", method = requestmethod.get) @responsestatus(httpstatus.ok) public @responsebody list<track> tracksforuser(@requestparam("user") string user) throws exception { criteria criteria = criteria.where(user).is(user); list<track> tracks = mongooperations.find(new query(criteria), track.class); return tracks; } @requestmapping(value = "/upload", method = requestmethod.post) @responsestatus(httpstatus.ok) public void upload(@requestparam("file") multipartfile multipartfile) throws exception { objectmapper mapper = new objectmapper(); track track = mapper.readvalue(multipartfile.getbytes(), track.class); mongooperations.insert(track); } }
listing 9: the implementation of the trackservice.
the context component scan scans for all objects with the @controller annotation and that is the target of our dispatcherservlet mapped in the web.xml under the path /services . the @requestmapping(“/track”) maps the whole service under an additional sub-path /track . the annotation at the method getall @requestmapping(value = “/get”) maps the method again. this means to call the getall method we have to use the path /services/track/get/ .
with the attriubte method = requestmethod.get we define a method that is only executed via a get request. the attribute produces = “application/json” defines that the result of the method is json. the result is automatically converted from a java object to json by spring. therefore shown in listing 10 the jar file jackson must be present on the classpath.
<dependency> <groupid>org.codehaus.jackson</groupid> <artifactid>jackson-jaxrs</artifactid> <version>1.9.5</version> </dependency>
listing 10: shows the maven dependency for jackson
the other methods are really straight forward and hopefully need not furhter documentation.
the ui
so now that we have a nice rest-service we need some kind of ui. as we wanted to try some tools and frameworks we haven’t used yet, we decided to build a browser based app for mobile devices. so for this prototype we used:
- jquery mobile
- jquery
- google maps api
- flot (to draw some charts)
- tempo (for some templating)
you may have guessed the app we are building is basically a clone of one of the many sites like gpsies or bikemap.net where you can share your gps tracks.
as our time was limited we started with four basic features:
- upload tracks
- view tracks near your current location on a map
- view details of a track
- view the list of tracks you have uploaded
each of the features (+ one main menu) requires an own page in the app, so we start with a jquery mobile multipage :
<body> <div data-role="page" id="home" style="width:100%; height:100%;"> <div data-role="header" data-position="fixed"><h2>cs mobile tracks</h2></div> <div data-role="content" style="padding:0;"> <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="a"> <li data-role="list-divider">options</li> <li><a href="#view_map" data-transition="pop">view tracks near you</a></li> <!-- ... more links etc. ... --> </ul> </div> </div> <div data-role="page" id="view_map" style="width:100%; height:100%;"> <div data-role="header" data-position="fixed" id="view_map_header"> <a data-rel="back">back</a> <h2>tracks near you</h2> </div> <div data-role="content" style="width:100%; height:100%; padding:0;"> <div id="map_tracks"></div> </div> </div> <!-- ... more pages ... --> </body>
basic jquery mobile mulitpage for the app ( source )
in the partial source code of the index.html you can easily see, that we have two pages, identified by data-role="page" -attribute and each contains a header ( data-role="header" ) and a div for the content ( data-role="content" ). on our main page with the id="home" we have just a list of links to the other pages. to link to a page, you just need a basic link where the href is a # followed by the id of the page (see lines 7 and 12). if you want to have a special animation for the transition you can simply add a data-transition -attribute.
so now we have the basic html with some links and pages, but now we
have to call the rest-service an get the stuff we want to display on the
pages.
to prepare the content of a page jquery mobile offers some
events
you can listen to. for our use case the page transition events
(pagebeforeshow, pageshow, pagehide) and the page creation events
(pagecreate and pageinit) are the most important. you should consider
carefully what you do on each of the events (we have not done this
carefully, so our code might not be perfect
).
for our page where we display the tracks near you on a map this looks basically like this:
$("#view_map").live("pageinit", function() { initmapsize("#map_tracks"); }); $("#view_map").live("pageshow", function() { mapmarkers = []; var lat = 48.128569; // default location: cs hq var lon = 11.557289; if(navigator.geolocation) { navigator.geolocation.getcurrentposition(function(position){ lat = position.coords.latitude; lon = position.coords.longitude; inittrackspage(lat, lon); }); } else { inittrackspage(lat, lon); } });
if the page is first initialized the pageinit event ist triggered by jquerymobile, if this happens we prepare our map and make sure it has the right size for the device. if the page is shown to the user (event: pageshow ) we finally make sure we get the current location of the user (if the browser supports this) and load the tracks to show on the map. to call our rest-service we simply use the well known $.getjson() :
var path = '../services/track/get/' + lon + '/' + lat + '/' + maxdistance + '/'; $.getjson(path, function(data) { $.each(data, function(idx, track) { addmarker(track); }); });
the other parts of the app are build the same way, so simply get the code from github and have a look around.
jquery mobile is a basic and easy to use framework to create a webapp focused on mobile devices. other frameworks like
sencha touch
may offer some more
functionality. but i guess next time we should simply use a
responsive design
approach.
Published at DZone with permission of Comsysto Gmbh, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments