Статьи

Использование MongoDB Geospatial с данными Spring и базовым пользовательским интерфейсом JQuery Mobile

 

дорожки-андроид-трек-деталь

Использование MongoDB Geospatial с Spring Data и базового пользовательского интерфейса JQuery Mobile

В этой статье показано, как использовать пространственную функцию MongoDB в сочетании с Spring и сервисом Web REST.

Сначала давайте представим цель примера веб-приложения. Наше приложение хранит информацию о треках, которые пользователь записал с помощью устройства слежения GPS. Для упрощения мы предполагаем, что данные отслеживания существуют в формате JSON. Этот документ хранится в MongoDB с дополнительной информацией о пользователе, который загрузил трек, и дополнительным атрибутом о позиции, с которой начинается трек.

С этой информацией о дорожках, хранящейся в MongoDB, мы начинаем поиск, который включает в себя все дорожки со стартовыми позициями в пределах определенного радиуса. Эти треки показаны на карте.

MongoDB часть

Если вы еще не установили MongoDB, загрузите последнюю версию, установите и запустите ее. Теперь мы должны запустить интерактивную оболочку монго . В консоли вы вводите следующие команды, показанные в листинге 1, чтобы использовать коллекцию треков и создать пространственный индекс Монго.

use tracks;
db.track.ensureIndex( { start : "2d" } );

Листинг 1: Команда для создания пространственного индекса MongoDB.

Теперь вы можете использовать особенность MongoDB. Например, вы можете выполнить команду в листинге 2, чтобы найти треки с ближайшим поиском в данной точке.

db.track.find( { start : { $near : [11.531439781188965,48.156700134277344] } } ).limit(2)

Листинг 2: Найти два ближайших пути к заданной точке с широтой и долготой.

Или вы можете выполнить команду из листинга 3, чтобы найти треки с ближайшим поиском в заданной точке с максимальным расстоянием. В примере мы ищем на расстоянии 1 километра. Поэтому мы должны разделить значение 1 на 111, чтобы получить максимальное расстояние в 1 километр. Для 1 мили мы должны разделить значение на 69.

db.track.find( { start : { $near : [11.531439781188965,48.156700134277344] , $maxDistance : 0.009009009009009 } } ).limit(2);

Листинг 3: Найти два ближайших пути к заданной точке с максимальным расстоянием 1 километр.

Документ JSON и соответствующие типы Java

Предположим, что информация о дорожке является документом JSON и имеет структуру, показанную в листинге 4.

{
    "name": "Nymphenburger Schloss",
    "start": { "lon":11.53144, "lat":48.1567, "ele":520 },
    "user": "[email protected]",
    "data":
    [
       {"lon":11.53144, "lat":48.1567, "ele":520},
       {"lon":11.53125, "lat":48.15672, "ele":520},
   ... // additional track information
    ]
}

Листинг 4: Структура документа JSON

Соответствующий Java-объект показан в листинге 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. ...

}

Листинг 5: Показать документ JSON, соответствующий Java-объекту

Сериализуемый интерфейс не обязательно требуется в этом примере, но, возможно, вы захотите использовать другую технологию внешнего интерфейса, такую ​​как wicket. Тогда, по крайней мере, вы должны добавить интерфейс к вашему Java-объекту.

Следующий листинг 6 показывает положение объекта Java, которое используется в дорожке объекта Java.

public class Position implements Serializable {

private Double lon;
private Double lat;
private Double ele;
// ... getter, setter, etc. ...
}

Листинг 6: Позиция, которая используется в треке.

В этом коротком примере пользователь не является объектом Java, это всего лишь атрибут в дорожке, имеющий уникальный идентификатор.

Теперь мы готовы настроить конфигурацию Spring для использования MongoDB.

Веб-сервис Spring REST

В первой части мы представили объекты, которые мы хотим сохранить в MongoDB. Теперь нам нужен сервис, чтобы сделать это для нас. Давайте рассмотрим конфигурацию и реализацию веб-службы REST для этого примера.

В листинге 7 показана конфигурация, необходимая в файле web.xml для запуска Spring и нашей службы отдыха.

<?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>

Листинг 7: Показывает конфигурацию Spring и веб-службы REST в web.xml

С такой конфигурацией каждый запрос, который вызывает URL-адрес по пути / services delegetas, будет прыгать. Для отображения сервлета с сервисами имен должен быть создан соответствующий файл с именем services-servlet.xml . В этом файле ничего не нужно настраивать. Интересной частью является файл applicationContext.xml, который находится в папке src / main / resources / com / comsysto / labs / mobile / track /.

В листинге 8 показана конфигурация applicationContext.xml . Это базовый файл конфигурации Spring в этом примере.

<?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>

Листинг 8: Показывает конфигурацию applicationContext.xml

Теперь давайте перейдем к той части, где все склеивается. Поэтому мы посмотрим на наш класс TrackService, показанный в листинге 9, в пакете org.comsysto.labs.web, где размещаются сервисы, которые автоматически определяются при сканировании компонента.

@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);
    }
}

Листинг 9: Реализация TrackService.

Сканирование компонента контекста сканирует все объекты с аннотацией @Controller, которая является целью нашего DispatcherServlet, отображенного в файле web.xml в пути / services . @RequestMapping ( «/ дорожка») отображает всю службу по дополнительным субтрассе / дорожке . Аннотация в методе getAll @RequestMapping (value = «/ get») снова отображает метод. Это означает, что для вызова метода getAll мы должны использовать путь / services / track / get / .

С помощью метода attriubte = RequestMethod.GET мы определяем метод, который выполняется только через запрос get. Атрибут yield = «application / json» определяет, что результатом метода является JSON. Результат автоматически преобразуется из объекта Java в JSON с помощью Spring. Поэтому, показанный в листинге 10, файл Jar Джексона должен присутствовать на пути к классам.

<dependency>
     <groupId>org.codehaus.jackson</groupId>
     <artifactId>jackson-jaxrs</artifactId>
     <version>1.9.5</version>
</dependency>

Листинг 10: Показывает зависимость Maven для Джексона

Другие методы действительно просты и, надеюсь, не требуют дополнительной документации.

Пользовательский интерфейс

Теперь, когда у нас есть хороший REST-сервис, нам нужен какой-то интерфейс. Поскольку мы хотели попробовать некоторые инструменты и платформы, которые мы еще не использовали, мы решили создать браузерное приложение для мобильных устройств. Так что для этого прототипа мы использовали:

Возможно, вы уже догадались, что приложение, которое мы создаем, по сути является клоном одного из множества сайтов, таких как GPSies или bikemap.net, где вы можете поделиться своими треками GPS.

Поскольку наше время было ограничено, мы начали с четырех основных функций:

  1. Загрузить треки
  2. Просмотр треков рядом с вашим текущим местоположением на карте
  3. Посмотреть детали трека
  4. Просмотр списка треков, которые вы загрузили

Каждая из функций (+ одно главное меню) требует отдельной страницы в приложении, поэтому мы начнем с многостраничного JQuery Mobile :

  <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>

Базовая мобильная страница JQuery для приложения ( источник )

В частичном исходном коде index.html вы можете легко увидеть, что у нас есть две страницы, идентифицируемые атрибутом data-role = "page" и каждая из которых содержит заголовок ( data-role = "header" ) и div для содержание ( data-role = "content" ). На нашей главной странице с id = "home" у нас есть просто список ссылок на другие страницы. Для ссылки на страницу вам просто нужна базовая ссылка, где href — это #, за которым следует идентификатор страницы (см. Строки 7 и 12). Если вы хотите иметь специальную анимацию для перехода, вы можете просто добавить data-transition -Attribute.

Итак, теперь у нас есть базовый html с некоторыми ссылками и страницами, но теперь нам нужно вызвать службу отдыха и получить материал, который мы хотим отобразить на страницах.
Для подготовки содержимого страницы JQuery Mobile предлагает несколько событий, которые вы можете прослушать. Для нашего случая использования наиболее важны события перехода страницы (pagebeforeshow, pageshow, pagehide) и события создания страницы (pagecreate и pageinit). Вы должны тщательно продумать, что вы делаете с каждым из событий (мы не сделали этого тщательно, поэтому наш код может быть не идеальным :-)).

Для нашей страницы, где мы отображаем треки рядом с вами на карте, это выглядит в основном так:

$("#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);
    }
});

Если страница сначала инициализируется, событие pageinit запускается JQueryMobile, если это происходит, мы подготавливаем нашу карту и проверяем, что она имеет правильный размер для устройства. Если страница показывается пользователю (Event: pageshow ), мы, наконец, проверяем текущее местоположение пользователя (если браузер поддерживает это) и загружаем треки для отображения на карте. Для вызова нашей службы отдыха мы просто используем хорошо известную функцию $ .getJSON () :

var path = '../services/track/get/' + lon + '/' + lat + '/' + maxdistance + '/';
$.getJSON(path, function(data) {
    $.each(data, function(idx, track) {
        addMarker(track);
    });
});

Другие части приложения построены таким же образом, поэтому просто получите код из GitHub и осмотрите его.

JQuery Mobile — это базовая и простая в использовании платформа для создания веб-приложения, ориентированного на мобильные устройства. Другие платформы, такие как Sencha Touch, могут предложить больше
функциональности. Но я думаю, что в следующий раз мы должны просто использовать адаптивный подход к дизайну .