Статьи

Clojure / Java Interop — импорт пространственных данных Neo4j

Около недели назад я написал пост, в котором описал, как я добавил футбольные стадионы в свой футбольный график, используя neo4j пространственный, и после того, как я это сделал, я хотел включить его в свой скрипт импорта вместе с остальными данными.

Я подумал, что leiningen , вероятно, вполне подойдет для этого, поскольку вы можете указать его на класс Java и выполнить его.

Для начала мне пришлось немного изменить код импорта, чтобы связать стадионы с командами, которые уже добавлены в график:

package main.java;
 
// imports excluded
 
public class StadiumsImport {
    public static void main(String[] args) throws IOException {
        List<String> lines = readFile("data/stadiums.csv");
 
        EmbeddedGraphDatabase db = new EmbeddedGraphDatabase("neo4j-community-1.9.M04/data/graph.db");
        Index<Node> stadiumsIndex = createSpatialIndex(db, "stadiumsLocation");
        Transaction tx = db.beginTx();
 
        for (String stadium : lines) {
            String[] columns = stadium.split(",");
            Index<Node> teamsIndex = db.index().forNodes("teams");
            String team = columns[1].replaceAll("\"","");
            Node teamNode = teamsIndex.get("name", team).getSingle();
 
            if(teamNode != null) {
                Node stadiumNode = db.createNode();
                stadiumNode.setProperty("wkt", String.format("POINT(%s %s)", columns[4], columns[3]));
                stadiumNode.setProperty("name", columns[0].replaceAll("\"",""));
                stadiumsIndex.add(stadiumNode, "dummy", "value");
                teamNode.createRelationshipTo(stadiumNode, DynamicRelationshipType.withName("play_at"));
            }
 
        }
 
        tx.success();
        tx.finish();
    }
 
    private static Index<Node> createSpatialIndex(EmbeddedGraphDatabase db,  String indexName) {
        return db.index().forNodes(indexName, SpatialIndexProvider.SIMPLE_WKT_CONFIG);
    }
 
    // readFile excluded
}

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

Единственное изменение по сравнению с версией прошлой недели — это то, что мы сейчас ищем команду, которой принадлежит стадион, и создаем отношения play_at от команды к стадиону.

Затем я смог выполнить этот код, вызвав ‘lein run’ на основе следующего файла project.clj:

(defproject neo4jfootball "1.0.0-SNAPSHOT"
  :description "neo4j football project"
  :main "main.java.StadiumsImport"
  :dependencies [[org.clojure/clojure "1.4.0"] 
                 [org.neo4j/neo4j-spatial "0.11-SNAPSHOT"]
                 [clojure-csv/clojure-csv "2.0.0-alpha1"]]
  :jvm-opts ["-Xmx2g"]
  :plugins [[lein-idea "1.0.1"]]
  :repositories {"local" ~(str (.toURI (java.io.File. "maven_repository")))}
  :java-source-paths ["src/main/java"] )

Я использую локальный репозиторий Maven для хранения JAR neo4j. Запись Maven была создана с помощью следующей команды, из которой я проверил пространственный проект neo4j :

 mvn install:install-file -Dfile=target/neo4j-spatial-0.11-SNAPSHOT.jar -DartifactId=neo4j-spatial -Dversion=0.11-SNAPSHOT -DgroupId=org.neo4j -Dpackaging=jar -DlocalRepositoryPath=/path/to/neo4j-football/maven_repository -DpomFile=pom.xml

Это сработало достаточно хорошо, но я подумал, что было бы интересно посмотреть, как будет выглядеть приведенный выше код, если он будет написан в clojure.

Вот чем я закончил:

(ns neo4jfootball.core
  (:require [clojure-csv.core :as csv])
  (:use clojure.java.io)
  (:import (org.neo4j.kernel EmbeddedGraphDatabase) (org.neo4j.gis.spatial.indexprovider SpatialIndexProvider) (org.neo4j.graphdb DynamicRelationshipType)))
 
(defn take-csv [fname]
  (with-open [file (reader fname)]
    (csv/parse-csv (slurp file))))
 
(defn transform [line] {:stadium (get line 0) :team (get line 1) :lat (get line 3) :long (get line 4)})
 
(def not-nil? (comp not nil?))
 
(defn create-stadium-node [db line]
  (let [stadium-node (.. db createNode)]
    (.. stadium-node (setProperty "wkt" (format "POINT(%s %s)" (:long line) (:lat line))))
    (.. stadium-node (setProperty "name" (:stadium line)))
  stadium-node))
 
(defn -main []
  (do
    (let [db (new EmbeddedGraphDatabase "neo4j-community-1.9.M04/data/graph.db")
          tx (.beginTx db)
          stadiums-index (.. db index (forNodes "stadiumsLocation" (SpatialIndexProvider/SIMPLE_WKT_CONFIG)))
          teams-index    (.. db index (forNodes "teams"))]
      (doseq [line (drop 1 (map transform (take-csv "data/stadiums.csv")))]
        (let [team-node (.. teams-index (get "name" (:team line)) getSingle)]
          (if (not-nil? team-node)
            (let [stadium-node (create-stadium-node db line)]
              (.. stadiums-index (add stadium-node "dummy" "value"))
              (.. team-node (createRelationshipTo stadium-node (DynamicRelationshipType/withName "play_at")))))))
      (.. tx success)
      (.. tx finish))))

Код значительно упрощается благодаря использованию библиотеки CSV clojure, так что я мог бы достичь аналогичного в версии Java, используя эквивалентную библиотеку.

Немного проще увидеть, какие свойства строки в CSV-файле используются, где в результате функции преобразования мы конвертируем массив в карту.

Для достижения аналогичной цели в Java потребовалось бы немного больше кода, поэтому я не стал беспокоиться.

Страница Java Interop на сайте clojure была весьма полезна для разработки способов вызова различных методов в Java API.

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

Затем мы можем назвать этот код из lein следующим образом:

lein run -m neo4jfootball.core