Статьи

Бег по графику с использованием Neo4J Spatial и Gephi

Когда я начал работать несколько лет назад, я купил Garmin Forerunner 405 . Это изящное маленькое устройство, которое отслеживает координаты GPS во время работы. После запуска устройство можно синхронизировать, загрузив данные на веб-сайт Garmin Connect . На основе отслеженного времени и GPS-координат веб-сайт Garmin Connect предоставляет вам подробный обзор вашего пробега, включая расстояние , средний темп , потерю высоты / усиление и разбивку кругов . Он также визуализирует ваш бег, накладывая отслеживаемый курс на карты Bing и / или Google. Довольно круто! Один из моих последних пробежек можно найти здесь .

Помимо простых агрегаций, таких как общее расстояние и средняя скорость, веб-сайт Garmin Connect предоставляет небольшую поддержку или вообще не помогает получить более глубокое понимание всех моих пробежек. Поскольку я часто провожу один и тот же курс, было бы интересно рассчитать мой средний темп в определенных местах . Объединяя данные всех моих курсов, я мог вычесть часто встречающиеся местоположения . Наконец, может ли быть корреляция между моим средним темпом и расстоянием от дома? Чтобы получить ответы на эти вопросы, я импортирую свои текущие данные в хранилище данных Neo4J Spatial . Neo4J Spatial расширяет базу данных Neo4J Graphс необходимыми инструментами и утилитами для хранения и запроса пространственных данных в ваших моделях графа. Для визуализации моих текущих данных я буду использовать Gephi , инструмент для визуализации и манипуляции с открытым исходным кодом, который позволяет пользователям интерактивно просматривать и исследовать графики.

 

1. Извлечение данных GPX

Веб-сайт Garmin Connect позволяет загружать текущие данные в различных форматах, включая KML , TCX и GPX . GPX (формат обмена GPS) — это легкий формат данных XML, который используется для обмена данными GPS (путевые точки, маршруты и треки) между приложениями и веб-службами. Ниже вы можете найти экстракт GPX, перечисляющий несколько отслеживаемых точек. Каждая из этих точек содержит местоположение GPS , высоту и соответствующую временную метку .

<trkpt lon="4.723870977759361" lat="51.075748661533">
    <ele>29.799999237060547</ele>
    <time>2011-11-08T19:18:39.000Z</time>
</trkpt>
<trkpt lon="4.724105251953006" lat="51.075623352080584">
    <ele>29.799999237060547</ele>
    <time>2011-11-08T19:18:45.000Z</time>
</trkpt>
<trkpt lon="4.724143054336309" lat="51.07560558244586">
    <ele>29.799999237060547</ele>
    <time>2011-11-08T19:18:46.000Z</time>
</trkpt>

На основании этих данных можно рассчитать различные показатели, в том числе темп . Для этого мы будем использовать GPSdings , библиотеку Java, которая обеспечивает необходимую функциональность для извлечения и анализа данных GPX. Начнем с чтения в файле GPX. После этого мы анализируем контент с помощью GPSdings TrackAnalyzer, который, помимо других метрик, вычисляет темп для каждой точки, которая была отслежена во время пробега. Необходимая информация хранится в первом сегменте первого трека.

// Start by reading the file and analyzing it contents
Gpx gpx = GPSDings.readGPX(new FileInputStream(file));
TrackAnalyzer analyzer = new TrackAnalyzer();
analyzer.addAllTracks(gpx);
// The garmin GPX running data contains only one track containing one segment
Trkseg track = gpx.getTrk(0).getTrkseg(0);

2. Импорт данных GPS в Neo4J Spatial

Neo4J Spatial построен поверх Neo4J и обеспечивает поддержку пространственных данных . Как только ваши данные сохранены,могут быть выполнены пространственные операции , которые, например, позволяют искать данные в указанных регионах или на определенном расстоянии от конкретной точки интереса. Мы начнем с настройки Neo4J EmbeddedGraphDatabase . Затем мы оборачиваем его как SpatialDatabaseService , который позволяет нам создать EditableLayer . EditableLayer — это основная абстракция Neo4J, которая используется для определения коллекции геометрий . Каждый слой должен быть инициализирован определенным GeometryEncoder, который действует как адаптер для отображения графика и геометрии и наоборот. В нашем случае мы будем использовать SimplePointEncoder .

// Create the graph db
graphDb = new EmbeddedGraphDatabase("var/geo");
// Wrap it as a spatial db service
spatialDb = new SpatialDatabaseService(graphDb);
// Create the layer to store our spatial data
runningLayer = (EditableLayer) spatialDb.getOrCreateLayer("running", SimplePointEncoder.class, EditableLayerImpl.class, "lon:lat");

Добавить пространственные данные в рабочий слой очень просто. Мы начинаем с создания Координаты для каждой точки, которая анализируется GPSdings. Затем мы добавляем эту новую координату в бегущий слой. Эта операция возвращает SpatialDatabaseRecord, который под капотом является обычным узлом Neo4J . Следовательно, мы можем добавить любое свойство, которое мы хотим, к этому узлу. В нашем случае мы добавим два свойства. Одно свойство с именем speed , указывающее (средний) темп. Одно свойство, названное вхождением , указывает, сколько раз эта конкретная координата встречалась в общем наборе данных. Как только новая координата создана, мы соединяем предыдущий узел с вновь созданным узлом через NEXTтип отношений. Следовательно, наш граф является перечислением встреченных координат, связанных через СЛЕДУЮЩИЕ ребра.

// Create a new coordinate for this point
Coordinate to = new Coordinate(track.getTrkpt(i).getLon().doubleValue(),track.getTrkpt(i).getLat().doubleValue());

// Add the new coordinate
torecord = runningLayer.add(runningLayer.getGeometryFactory().createPoint(to));
// Set the data accordingly
torecord.setProperty("speed", analyzer.getHorizontalSpeed(track.getTrkpt(i).getTime()));
torecord.setProperty("occurences", 1);

// Add relationship
Relationship next = fromrecord.getGeomNode().createRelationshipTo(torecord.getGeomNode(), RelTypes.NEXT);

Если координата встречается несколько раз, мы пересчитываем среднюю скорость и увеличиваем количество встреч .

// Recalculate average speed
double previousspeed  =  (Double)torecord.getProperty("speed");
int previousoccurences =  (Integer)torecord.getProperty("occurences");
double currentspeed = analyzer.getHorizontalSpeed(track.getTrkpt(i).getTime());
double denormalizespeed = previousspeed * previousoccurences;
double newspeed = ((denormalizespeed + currentspeed) / (previousoccurences + 1));
// Update the data accordingly
torecord.setProperty("speed",newspeed);
torecord.setProperty("occurences",previousoccurences+1);

К сожалению, шансы встретить уже существующую координату малы, так как координаты в файле GPX имеют 15-значную точность справа от десятичной точки. Вместо того, чтобы пытаться округлить эти координаты самостоятельно, мы будем использовать Neo4J Spatial Querying API . Простой ближайший сосед — поиск, ограниченный 20 метрами, позволяет нам находить совпадающие координаты. (Я выбираю 20 метров, так как 20 немного выше среднего расстояния между двумя координатами). Если мы найдем координату в пределах этого 20-метрового диапазона, мы будем использовать ее повторно . В противном случае мы просто создаем новую координату . Полный алгоритм импорта нескольких наборов данных GPX можно найти ниже.

// Import the data from a GPX file. Boolean indicates whether data has been imported before
public void addData(File file, boolean firsttime) throws IOException, FunctionEvaluationException {

    // Start by reading the file and analyzing it contents
    Gpx gpx = GPSDings.readGPX(new FileInputStream(file));
    TrackAnalyzer analyzer = new TrackAnalyzer();
    analyzer.addAllTracks(gpx);
    // The garmin GPX running data contains only one track containing one segment
    Trkseg track = gpx.getTrk(0).getTrkseg(0);

    // Start a new transaction
    Transaction tx = graphDb.beginTx();
    // Contains the record that was added previously (in order to create a relation between the new and the previous node)
    SpatialDatabaseRecord fromrecord = null;

    // Iterate all points
    for (int i = 0; i < track.getTrkptCount(); i++) {

        // Create a new coordinate for this point
        Coordinate to = new Coordinate(track.getTrkpt(i).getLon().doubleValue(),track.getTrkpt(i).getLat().doubleValue());

        // Check whether we can find a node from which is located within a distance of 20 meters
        List<GeoPipeFlow> closests = 
            GeoPipeline.startNearestNeighborLatLonSearch(runningLayer, to, 0.02).sort("OrthodromicDistance").getMin("OrthodromicDistance").toList();
        SpatialDatabaseRecord torecord = null;

        // If first time, we add all nodes. Otherwise, we check whether we find a node that is close enough to the current location
        if (!firsttime && (closests.size() == 1)) {
            // Retrieve the node
            System.out.println("Using existing: " + closests.get(0).getProperty("OrthodromicDistance"));
            torecord = closests.get(0).getRecord();
            // Recalculate average speed
            double previousspeed  =  (Double)torecord.getProperty("speed");
            int previousoccurences =  (Integer)torecord.getProperty("occurences");
            double currentspeed = analyzer.getHorizontalSpeed(track.getTrkpt(i).getTime());
            double denormalizespeed = previousspeed * previousoccurences;
            double newspeed = ((denormalizespeed + currentspeed) / (previousoccurences + 1));
            // Update the data accordingly
            torecord.setProperty("speed",newspeed);
            torecord.setProperty("occurences",previousoccurences+1);
        }
        else {
            // New node, add it
            torecord = runningLayer.add(runningLayer.getGeometryFactory().createPoint(to));
            // Set the data accordingly
            torecord.setProperty("speed", analyzer.getHorizontalSpeed(track.getTrkpt(i).getTime()));
            torecord.setProperty("occurences", 1);
        }

        // If a previous node is available (and they are not identical), add a directed relationship between both
        if (fromrecord != null && (!fromrecord.equals(torecord)))  {
            Relationship next = fromrecord.getGeomNode().createRelationshipTo(torecord.getGeomNode(), RelTypes.NEXT);
        }
        // Previous record is put on new record
        fromrecord = torecord;
    }

    // Commit transaction
    tx.success();
    tx.finish();

}

3. Визуализация текущих данных

Используя API пространственных запросов Neo4J , мы можем получить набор координат, которые удовлетворяют определенному условию. Однако координаты несколько абстрактны для интерпретации. Вместо этого мы будем использовать отличный инструмент визуализации и исследования Gephi Graph. Установив плагин Gephi Neo4j , мы можем загрузить и изучить графики, которые хранятся в (Spatial) датасторе Neo4j. Давайте начнем с импорта нашего набора данных в Gephi.

gephi

Отображаемый график содержит другие типы узлов и ребер (например, информацию об уровне слоя и RTree ), в дополнение к координатам и СЛЕДУЮЩИМ ребрам, которые мы добавили сами. Давайте избавимся от них, отфильтровав наш граф по следующему типу отношений.

gephi

 

Остается только половина краев … Однако мы все равно не получим нового понимания этого беспорядка. Давайте разместим наш график с помощью плагина Gephi GeoLayout . Этот Layouter принимает геокодированные графики в качестве входных данных и строит графики в соответствии с геокодированными атрибутами. Обязательно увеличьте масштабирование, так как наши координаты расположены близко друг к другу. Здорово! Эта точка зрения четко описывает курсы, которые я провожу.

gephi

Давайте визуализируем координаты, которые часто встречались во время 4 прогонов, которые импортируются в хранилище данных Neo4J Spatial. Для этого мы будем использовать свойство узла InDegree , которое указывает количество входящих ребер для каждой координаты. Мы оцениваем вес узла (т.е. размер узла) через это свойство. Следовательно, часто встречающиеся узлы будут отображаться больше. В моем случае часто встречающиеся координаты находятся вокруг того места, где я живу (и, следовательно, начинаю бегать) и на перекрестках улиц.

gephi

Давайте сделаем один заключительный анализ, а именно визуализацию, которая иллюстрирует средний темп на протяжении всех запусков . Для этого, мы занимаем как вес узла и цвет узла через скорость свойства. Следовательно, координаты с высоким средним темпом окрашены в зеленый цвет и отображаются больше. Координаты с низким средним темпом окрашены в красный цвет и отображаются меньше. В мгновение ока я теперь могу интерпретировать свой средний темп, учитывая мой общий набор бегущих данных!

gephi

 

4. Вывод

В этой статье описывается использование хранилища данных Neo4J Spatial и Gephi для анализа текущих данных Garmin. Как всегда, полный исходный код можно найти в общедоступном GitHub-хранилище Datablend . Любые идеи для других типов анализа, которые могут быть выполнены на наборе данных?

Источник: 
http://datablend.be/?p=1255