Skip to content

Commit

Permalink
First working version (#633).
Browse files Browse the repository at this point in the history
New API for performing a proximity search.

Geospatial API:
{{{
import de.deepamehta.plugins.geomaps.model.GeoCoordinate;

List<Topic> getTopicsWithinDistance(GeoCoordinate geoCoord, double maxDistanceInKm)
}}}

REST API:
{{{
GET /geospatial/<lon>,<lat>/distance/<km>
}}}

Returned are Geo Coordinate topics (as defined in the Geomaps module).

Each time a Geo Coordinate topic is created or updated its values are automatically indexed by means of Neo4j Spatial (http://www.neo4j.org/develop/spatial). That is typically each time an Address topic is created/updated, e.g. when editing a Person or an Institution.

Existing Geo Coordinate topics are not yet indexed (this remains to be implemented). Only create and update operations are respected at the moment.

There is a bug in Neo4j Spatial: when the above API is used while the geospatial index has no entries yet, a NullPointerException is thrown.

Hint: with a Geo Coordinate topic at hand you can retrieve the corresponding domain topic (e.g. a Person or Institution) by calling the Geomaps service's
    `Topic getDomainTopic(long geoCoordId)`

See #633.
  • Loading branch information
jri committed Aug 7, 2014
1 parent 15040fb commit 52baac3
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 13 deletions.
54 changes: 46 additions & 8 deletions pom.xml
Expand Up @@ -21,48 +21,84 @@
<groupId>de.deepamehta</groupId>
<artifactId>deepamehta-geomaps</artifactId>
<version>4.4-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- Neo4j -->
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-kernel</artifactId>
<version>1.8.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-graph-collections</artifactId>
<version>0.4-neo4j-1.8.2</version>
<scope>provided</scope>
</dependency>
<!-- Neo4j Spatial -->
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-spatial</artifactId>
<version>0.9.1-neo4j-1.8.2</version>
<scope>provided</scope>
</dependency>
<!-- GeoTools -->
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-graph-collections</artifactId>
<version>0.4-neo4j-1.8.2</version>
<groupId>org.geotools</groupId>
<artifactId>gt-api</artifactId>
<version>8.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-metadata</artifactId>
<artifactId>gt-opengis</artifactId>
<version>8.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-opengis</artifactId>
<artifactId>gt-metadata</artifactId>
<version>8.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-referencing</artifactId>
<version>8.0</version>
<scope>runtime</scope>
</dependency>
<!-- -->
<dependency>
<groupId>com.vividsolutions</groupId>
<artifactId>jts</artifactId>
<version>1.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jscience</groupId>
<artifactId>jscience</artifactId>
<version>4.3.1</version>
<scope>runtime</scope>
</dependency>
<!-- Tinkerpop -->
<dependency>
<groupId>com.tinkerpop.gremlin</groupId>
<artifactId>gremlin-java</artifactId>
<version>1.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.tinkerpop.gremlin</groupId>
<artifactId>gremlin-groovy</artifactId>
<version>1.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.tinkerpop</groupId>
<artifactId>pipes</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

Expand Down Expand Up @@ -93,18 +129,20 @@
</Bundle-Activator>
<Import-Package>
org.neo4j.graphdb, org.neo4j.graphdb.traversal, org.neo4j.kernel, !org.neo4j.*,
!com.tinkerpop.*,
!com.tinkerpop.blueprints.*,
!org.geotools.*, !org.json.simple.*,
!au.com.objectix.jgridshift,
!groovy.lang, !org.codehaus.groovy.*, !jline,
!org.apache.commons.collections, !org.apache.commons.pool.*, !org.apache.commons.logging.*,
!org.apache.commons.lang,
!org.apache.log4j,
!javax.vecmath, !javax.media.jai.*, !javax.transaction.*, *
</Import-Package>
<Embed-Dependency>
neo4j-spatial, neo4j-graph-collections,
gt-metadata, gt-opengis, gt-referencing,
jts, jscience
gt-api, gt-opengis, gt-metadata, gt-referencing,
jts, jscience,
gremlin-java, gremlin-groovy, pipes
</Embed-Dependency>
</instructions>
</configuration>
Expand Down
@@ -1,5 +1,6 @@
package de.deepamehta.plugins.geospatial;

import de.deepamehta.plugins.geospatial.service.GeospatialService;
import de.deepamehta.plugins.geomaps.model.GeoCoordinate;

import de.deepamehta.core.CompositeValue;
Expand All @@ -11,26 +12,48 @@
import de.deepamehta.core.service.event.PostCreateTopicListener;
import de.deepamehta.core.service.event.PostUpdateTopicListener;

import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.collections.rtree.NullListener;

import org.neo4j.gis.spatial.SimplePointLayer;
import org.neo4j.gis.spatial.SpatialDatabaseRecord;
import org.neo4j.gis.spatial.SpatialDatabaseService;
import org.neo4j.gis.spatial.pipes.GeoPipeFlow;
import org.neo4j.gis.spatial.pipes.GeoPipeline;

// ### import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Point;
// ### import com.vividsolutions.jts.geom.Geometry;
// ### import com.vividsolutions.jts.geom.GeometryFactory;
// ### import com.vividsolutions.jts.geom.Point;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.Produces;
import javax.ws.rs.Consumes;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;



public class GeospatialPlugin extends PluginActivator implements PostCreateTopicListener, PostUpdateTopicListener {
@Path("/geospatial")
@Consumes("application/json")
@Produces("application/json")
public class GeospatialPlugin extends PluginActivator implements GeospatialService, PostCreateTopicListener,
PostUpdateTopicListener {

// ------------------------------------------------------------------------------------------------------- Constants

private static final String DEFAULT_LAYER_NAME = "dm4.geospatial.layer";

private static final String PROP_GEO_COORD_ID = "geo_coord_id";

// ---------------------------------------------------------------------------------------------- Instance Variables

SimplePointLayer layer;
Expand All @@ -41,6 +64,43 @@ public class GeospatialPlugin extends PluginActivator implements PostCreateTopic



// ****************************************
// *** GeospatialService Implementation ***
// ****************************************



@GET
@Path("/{geo_coord}/distance/{distance}")
@Override
public List<Topic> getTopicsWithinDistance(@PathParam("geo_coord") GeoCoordinate geoCoord,
@PathParam("distance") double maxDistanceInKm) {
try {
// query geospatial index
Coordinate point = new Coordinate(geoCoord.lon, geoCoord.lat);
GeoPipeline spatialRecords = GeoPipeline.startNearestNeighborLatLonSearch(layer, point, maxDistanceInKm);
/* ### .sort("OrthodromicDistance") */
//
// build result
List<Topic> geoCoords = new ArrayList();
for (GeoPipeFlow spatialRecord : spatialRecords) {
// Note: long distance = spatialRecord.getProperty("OrthodromicDistance")
long geoCoordId = (Long) spatialRecord.getRecord().getGeomNode().getProperty(PROP_GEO_COORD_ID, -1L);
if (geoCoordId == -1) {
Point p = (Point) spatialRecord.getGeometry();
throw new RuntimeException("A spatial database record misses the \"" + PROP_GEO_COORD_ID +
"\" property (lon=" + p.getX() + ", lat=" + p.getY() + ")");
}
geoCoords.add(dms.getTopic(geoCoordId, true)); // fetchComposite=true
}
return geoCoords;
} catch (Exception e) {
throw new RuntimeException("Quering the geospatial index failed", e);
}
}



// ****************************
// *** Hook Implementations ***
// ****************************
Expand All @@ -52,7 +112,7 @@ public void init() {
GraphDatabaseService neo4j = (GraphDatabaseService) dms.getDatabaseVendorObject();
SpatialDatabaseService spatialDB = new SpatialDatabaseService(neo4j);
//
//spatialDB.deleteLayer(DEFAULT_LAYER_NAME, null);
// spatialDB.deleteLayer(DEFAULT_LAYER_NAME, new NullListener());
//
if (spatialDB.containsLayer(DEFAULT_LAYER_NAME)) {
logger.info("########## Default layer already exists (\"" + DEFAULT_LAYER_NAME + "\")");
Expand Down Expand Up @@ -91,7 +151,8 @@ private void indexIfGeoCoordinate(Topic topic) {
GeoCoordinate geoCoord = geoCoordinate(topic);
logger.info("########## Indexing Geo Coordinate " + topic.getId() + " (long=" + geoCoord.lon +
", lat=" + geoCoord.lat + ")");
layer.add(geoCoord.lon, geoCoord.lat);
SpatialDatabaseRecord record = layer.add(geoCoord.lon, geoCoord.lat);
record.setProperty(PROP_GEO_COORD_ID, topic.getId());
}
}

Expand All @@ -103,4 +164,13 @@ private GeoCoordinate geoCoordinate(Topic geoCoordTopic) {
comp.getDouble("dm4.geomaps.latitude")
);
}

// ### not used
private Map nodeProperties(Node node) {
Map props = new HashMap();
for (String key : node.getPropertyKeys()) {
props.put(key, node.getProperty(key));
}
return props;
}
}
@@ -0,0 +1,15 @@
package de.deepamehta.plugins.geospatial.service;

import de.deepamehta.plugins.geomaps.model.GeoCoordinate;

import de.deepamehta.core.Topic;
import de.deepamehta.core.service.PluginService;

import java.util.List;



public interface GeospatialService extends PluginService {

List<Topic> getTopicsWithinDistance(GeoCoordinate geoCoord, double maxDistanceInKm);
}

0 comments on commit 52baac3

Please sign in to comment.