diff --git a/composer.json b/composer.json index 86c8320..ef5cd37 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,13 @@ { - "name": "everyman/neo4jphp", + "name": "tomcorbett/neo4jphp", "type": "library", "description": "Wrapper for the Neo4j graph database REST interface", "keywords": ["neo4j","graph","database"], "homepage": "http://github.com/jadell/neo4jphp/wiki", "license": "MIT", "authors": [ - {"name": "Josh Adell", "email": "josh.adell@gmail.com", "homepage": "http://joshadell.com"} + {"name": "Josh Adell", "email": "josh.adell@gmail.com", "homepage": "http://joshadell.com"}, + {"name": "Thomas Corbett", "email": "thomas.michael.corbett@gmail.com"} ], "require": { "php": ">=5.3.0", @@ -19,5 +20,6 @@ }, "autoload": { "psr-0": {"Everyman\\Neo4j":"lib/"} - } + }, + "version": "1.0.1-beta" } diff --git a/lib/Everyman/Neo4j/Client.php b/lib/Everyman/Neo4j/Client.php index a919d5e..021e5ae 100644 --- a/lib/Everyman/Neo4j/Client.php +++ b/lib/Everyman/Neo4j/Client.php @@ -17,6 +17,7 @@ class Client const CapabilityGremlin = 'gremlin'; const CapabilityLabel = 'label'; const CapabilityTransactions = 'transactions'; + const CapabilitySpatialPlugin = 'spatial'; protected $transport = null; protected $entityMapper = null; @@ -504,6 +505,12 @@ public function hasCapability($capability) return $info['extensions']['GremlinPlugin']['execute_script']; } return false; + + case self::CapabilitySpatialPlugin: + if (isset($info['extensions']['SpatialPlugin'])) { + return true; + } + return false; default: return false; @@ -726,6 +733,80 @@ public function searchIndex(Index $index, $key, $value) return $this->runCommand($command); } + /** + * Create a new simple point layer + * + * @param string $layer + * @param string $lon + * @param string $lat + * @return \Everyman\Neo4j\SpatialLayer\SimplePointLayer + */ + public function makeSimplePointLayer($layer, $lon = null, $lat = null) + { + $simplePointLayer = new SpatialLayer\SimplePointLayer($this, $layer, $lat, $lon); + return $simplePointLayer; + } + + /** + * Save the given layer + * + * @param SpatialLayer $layer + * @return boolean + */ + public function saveLayer(SpatialLayer $layer) + { + return $this->runCommand(new Command\CreateSimplePointLayer($this, $layer)); + } + + /** + * Add an entity to a Layer + * + * @param SpatialLayer $layer + * @param PropertyContainer $entity + * @return boolean + */ + public function addToLayer(SpatialLayer $layer, PropertyContainer $entity) + { + // @todo implement batching support + return $this->runCommand(new Command\AddToLayer($this, $layer, $entity)); + } + + /** + * Search for nodes within a specified distance of X and Y + * + * @param \Everyman\Neo4j\SpatialLayer $layer + * @param float $pointX + * @param float $pointY + * @param float $distance + * @return array + */ + public function findNodesWithinDistance(SpatialLayer $layer, $pointX, $pointY, $distance) + { + return $this->runCommand(new Command\FindNodesWithinDistance($this, $layer, $pointX, $pointY, $distance)); + } + + /** + * Search for nodes within a specified bounding box area + * + * @param \Everyman\Neo4j\SpatialLayer $layer + * @param type $pointX + * @param type $pointY + * @param type $distance + * @return type + */ + public function findNodesInBBox(SpatialLayer $layer, $minX, $maxX, $minY, $maxY) + { + return $this->runCommand(new Command\FindNodesInBBox($this, $layer, $minX, $maxX, $minY, $maxY)); + } + + + + + + + + + /** * Set the cache to use * diff --git a/lib/Everyman/Neo4j/Command/AddToLayer.php b/lib/Everyman/Neo4j/Command/AddToLayer.php new file mode 100644 index 0000000..7b0e930 --- /dev/null +++ b/lib/Everyman/Neo4j/Command/AddToLayer.php @@ -0,0 +1,88 @@ +layer = $layer; + $this->entity = $entity; + } + + /** + * Return the data to pass + * + * @return mixed + */ + protected function getData() + { + if (!$this->entity || !$this->entity->hasId()) { + throw new Exception('No entity to add to layer specified'); + } + + return array( + 'node' => $this->getTransport()->getEndpoint().'/'. 'node' .'/'.$this->entity->getId(), + 'layer' => $this->layer->getName() + ); + } + + /** + * Return the transport method to call + * + * @return string + */ + protected function getMethod() + { + return 'post'; + } + + /** + * Return the path to use + * + * @return string + */ + protected function getPath() + { + return '/ext/SpatialPlugin/graphdb/addNodeToLayer'; + } + + /** + * Use the results + * + * @param integer $code + * @param array $headers + * @param array $data + * @return integer on failure + */ + protected function handleResult($code, $headers, $data) + { + if ((int)($code / 100) != 2) { + $this->throwException('Unable to add entity to spatial layer', $code, $headers, $data); + } + return true; + } +} diff --git a/lib/Everyman/Neo4j/Command/CreateSimplePointLayer.php b/lib/Everyman/Neo4j/Command/CreateSimplePointLayer.php new file mode 100644 index 0000000..6f42f41 --- /dev/null +++ b/lib/Everyman/Neo4j/Command/CreateSimplePointLayer.php @@ -0,0 +1,104 @@ +simplePointLayer = $simplePointLayer; + } + + protected function getData() + { + return array( + 'layer' => $this->getName(), + 'lat' => $this->getLat(), + 'lon' => $this->getLon(), + ); + } + + /** + * Return the name of the layer + * + * @return string + */ + protected function getName() + { + return $this->simplePointLayer->getName() ?: null; + } + + /** + * + * @return string + */ + protected function getLat() + { + return $this->simplePointLayer->getLat() ?: null; + } + + /** + * + * @return string + */ + protected function getLon() + { + return $this->simplePointLayer->getLon() ?: null; + } + + /** + * Return the transport method to call + * + * @return string + */ + protected function getMethod() + { + return 'post'; + } + + /** + * Return the path to use + * + * @return string + */ + protected function getPath() + { + return '/ext/SpatialPlugin/graphdb/addSimplePointLayer'; + } + + /** + * Use the results + * + * @param integer $code + * @param array $headers + * @param array $data + * @return boolean true on success + * @throws Exception on failure + */ + protected function handleResult($code, $headers, $data) + { + if ((int)($code / 100) != 2) { + $this->throwException('Unable to create SimplePointLayer', $code, $headers, $data); + } + + //$nodeId = $this->getEntityMapper()->getIdFromUri($headers['Location']); + //$this->node->setId($nodeId); + //$this->getEntityCache()->setCachedEntity($this->node); + return true; + } +} diff --git a/lib/Everyman/Neo4j/Command/FindNodesInBBox.php b/lib/Everyman/Neo4j/Command/FindNodesInBBox.php new file mode 100644 index 0000000..38607d4 --- /dev/null +++ b/lib/Everyman/Neo4j/Command/FindNodesInBBox.php @@ -0,0 +1,94 @@ +layer = $layer; + $this->minX = $minX; + $this->maxX = $maxX; + $this->minY = $minY; + $this->maxY = $maxY; + } + + /** + * Return the data to pass + * + * @return mixed + */ + protected function getData() + { + return array( + 'layer' => $this->layer->getName(), + 'minx' => $this->minX, + 'maxx' => $this->maxX, + 'miny' => $this->minY, + 'maxy' => $this->maxY + ); + } + + /** + * Return the transport method to call + * + * @return string + */ + protected function getMethod() + { + return 'post'; + } + + /** + * Return the path to use + * + * @return string + */ + protected function getPath() + { + return '/ext/SpatialPlugin/graphdb/findGeometriesInBBox'; + } + + /** + * Use the results + * + * @param integer $code + * @param array $headers + * @param array $data + * @return Everyman\Neo4j\Query\Row + * @throws Exception on failure + */ + protected function handleResult($code, $headers, $data) + { + if ((int)($code / 100) != 2) { + $this->throwException('Unable to retrieve nodes in layer within BBox', $code, $headers, $data); + } + + return new Row($this->client, array_keys($data), $data); + } +} diff --git a/lib/Everyman/Neo4j/Command/FindNodesWithinDistance.php b/lib/Everyman/Neo4j/Command/FindNodesWithinDistance.php new file mode 100644 index 0000000..31a5007 --- /dev/null +++ b/lib/Everyman/Neo4j/Command/FindNodesWithinDistance.php @@ -0,0 +1,90 @@ +layer = $layer; + $this->pointX = $pointX; + $this->pointY = $pointY; + $this->distance = $distance; + } + + /** + * Return the data to pass + * + * @return mixed + */ + protected function getData() + { + return array( + 'layer' => $this->layer->getName(), + 'pointX' => $this->pointX, + 'pointY' => $this->pointY, + 'distanceInKm' => $this->distance + ); + } + + /** + * Return the transport method to call + * + * @return string + */ + protected function getMethod() + { + return 'post'; + } + + /** + * Return the path to use + * + * @return string + */ + protected function getPath() + { + return '/ext/SpatialPlugin/graphdb/findGeometriesWithinDistance'; + } + + /** + * Use the results + * + * @param integer $code + * @param array $headers + * @param array $data + * @return Everyman\Neo4j\Query\Row + * @throws Exception on failure + */ + protected function handleResult($code, $headers, $data) + { + if ((int)($code / 100) != 2) { + $this->throwException('Unable to retrieve nodes in layer within distance', $code, $headers, $data); + } + + return new Row($this->client, array_keys($data), $data); + } +} diff --git a/lib/Everyman/Neo4j/EntityMapper.php b/lib/Everyman/Neo4j/EntityMapper.php index 13d1705..8608b4a 100644 --- a/lib/Everyman/Neo4j/EntityMapper.php +++ b/lib/Everyman/Neo4j/EntityMapper.php @@ -34,7 +34,9 @@ public function getEntityFor($value) if (array_key_exists('self', $value)) { if (array_key_exists('type', $value)) { $value = $this->makeRelationship($value); - } else { + } else if(array_key_exists('data', $value) && is_array($value['data']) && array_key_exists('geomencoder', $value['data'])) { + $value = $this->makeSpatialLayer($value); + } else { $value = $this->makeNode($value); } } else if (array_key_exists('nodes', $value) && array_key_exists('relationships', $value)) { @@ -79,6 +81,31 @@ public function makeRelationship($data) $rel = $this->getRelationshipFromUri($data['self']); return $this->populateRelationship($rel, $data); } + + /** + * Generate and populate a spatial layer from the given data + * + * @param array $data + * @return SpatialLayer + */ + public function makeSpatialLayer($data) + { + $rel = $this->getSpatialLayerFromUri($data['self']); + return $this->populateSpatialLayer($rel, $data); + } + + /** + * Fill a spatial layer with data + * + * @param SpatialLayer $spatialLayer + * @param array $data + * @return SpatialLayer + */ + public function populateSpatialLayer(SpatialLayer $spatialLayer, $data) + { + + return $node; + } /** * Fill a node with data @@ -144,6 +171,18 @@ public function populateRelationship(Relationship $rel, $data) return $rel; } + /** + * Retrieve a spaltial layer by it's 'self' uri + * + * @param string $uri + * @return SpatialLayer + */ + protected function getSpatialLayerFromUri($uri) + { + $relId = $this->getIdFromUri($uri); + return $this->client->getSpatialLayer($relId, true); + } + /** * Retrieve a node by it's 'self' uri * diff --git a/lib/Everyman/Neo4j/Node.php b/lib/Everyman/Neo4j/Node.php index 4a4b449..5daf997 100644 --- a/lib/Everyman/Neo4j/Node.php +++ b/lib/Everyman/Neo4j/Node.php @@ -42,6 +42,17 @@ public function addLabels($labels) $this->labels = $this->client->addLabels($this, $labels); return $this->labels; } + + /** + * Add node to SimplePointLayer + * + * @param SimplePointLayer $layer + * @return bool + */ + public function addToSimplePointLayer($layer) + { + return $this->client->addToLayer($layer, $this); + } /** * Delete this node diff --git a/lib/Everyman/Neo4j/SpatialLayer.php b/lib/Everyman/Neo4j/SpatialLayer.php new file mode 100644 index 0000000..1904313 --- /dev/null +++ b/lib/Everyman/Neo4j/SpatialLayer.php @@ -0,0 +1,256 @@ +setClient($client); + $this->setName((string)$name); + $this->setLat((string)$lat); + $this->setLon((string)$lon); + $this->setType((string)$type); + } + + public function __sleep() + { + return array('name', 'layerClass', 'goemEncoder', 'goemEncoderConfig', 'createdTime'); + } + + /** + * Save this entity + * + * @return SpatialLayer + * @throws Exception on failure + */ + public function save() + { + return $this->client->saveLayer($this); + } + + /* + * Add an entity to the layer + * + * @param PropertyContainer $entity + * @return boolean + */ + public function add($entity) + { + return $this->client->addToLayer($this, $entity); + } + + /* + * Find nodes with the given layer within the specified distance and lat/long + * + * @param float $pointX + * @param float $pointY + * @param float $distance + */ + public function findNodesWithinDistance($pointX, $pointY, $distance) + { + return $this->client->findNodesWithinDistance($this, $pointX, $pointY, $distance); + } + + /** + * Search for nodes within a specified bounding box area + * + * @param float $minX + * @param float $maxX + * @param float $minY + * @param float $maxY + * @return array + */ + public function findNodesWithinBBox($minX, $maxX, $minY, $maxY) + { + return $this->client->findNodesInBBox($this, $minX, $maxX, $minY, $maxY); + } + + /** + * Get the entity's client + * + * @return Client + */ + public function getClient() + { + return $this->client; + } + + /** + * Set the entity's client + * + * @param Client $client + * @return SpatialLayer + */ + public function setClient(Client $client) + { + $this->client = $client; + + return $this; + } + + /** + * Get the layer name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get the timestamp that this layer was created + * + * @return type + */ + public function getCreatedTime() + { + return $this->createdTime; + } + + /** + * Set the layer name + * + * @param string $name + * @return \Everyman\Neo4j\SpatialLayer + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Set the geom encoder + * + * @param string $geomEncoder + * @return \Everyman\Neo4j\SpatialLayer + */ + public function setGeomEncoder($geomEncoder) + { + $this->geomEncoder = $geomEncoder; + + return $this; + } + + /** + * Get the layer type + * + * @return type + */ + public function getType() + { + return $this->type; + } + + /** + * Set the type of spatial layer + * + * @param string $type + * @return \Everyman\Neo4j\SpatialLayer + */ + public function setType($type) + { + $this->type = $type; + + return $this; + } + + /** + * @return string + */ + public function getLat() + { + return $this->lat; + } + + /** + * @return string + */ + public function getLon() + { + return $this->lon; + } + + /** + * Set the property name to use for latitude + * + * @param string $lon + * @return \Everyman\Neo4j\SpatialLayer + */ + public function setLat($lat) + { + $this->lat = $lat; + + return $this; + } + + /** + * Set the property name to use for longitude + * + * @param string $lon + * @return \Everyman\Neo4j\SpatialLayer + */ + public function setLon($lon) + { + $this->lon = $lon; + + return $this; + } + + +} diff --git a/lib/Everyman/Neo4j/SpatialLayer/SimplePointLayer.php b/lib/Everyman/Neo4j/SpatialLayer/SimplePointLayer.php new file mode 100644 index 0000000..17bbb04 --- /dev/null +++ b/lib/Everyman/Neo4j/SpatialLayer/SimplePointLayer.php @@ -0,0 +1,28 @@ +client = $this->getMock('Everyman\Neo4j\Client', array(), array(), '', false); + $this->layer = new SimplePointLayer($this->client, SpatialLayer::TypeSimplePoint, 'layername'); + } + + public function testSave_SavesSelfUsingClient() + { + $this->client->expects($this->once()) + ->method('saveLayer') + ->with($this->layer) + ->will($this->returnValue(true)); + + $this->assertTrue($this->layer->save()); + } + + public function testAdd_AddsEntityUsingClient() + { + $node = new Node($this->client); + + $this->client->expects($this->once()) + ->method('addToLayer') + ->with($this->layer, $node) + ->will($this->returnValue(true)); + + $this->assertTrue($this->layer->add($node)); + } + + public function testFind_FindsNodesWithinDistanceUsingClient() + { + $node = new Node($this->client); + + $this->client->expects($this->once()) + ->method('findNodesWithinDistance') + ->with($this->layer, -25, 50, 100) + ->will($this->returnValue(array($node))); + + $result = $this->layer->findNodesWithinDistance(-25, 50, 100); + $this->assertEquals(1, count($result)); + $this->assertSame($node, $result[0]); + } + + public function testFind_FindsNodesInBBoxUsingClient() + { + $node = new Node($this->client); + + $this->client->expects($this->once()) + ->method('findNodesWithinBBox') + ->with($this->layer, -25, 25, -50, 40) + ->will($this->returnValue(array($node))); + + $result = $this->layer->findNodesWithinBBox(-25, 25, -50, 40); + $this->assertEquals(1, count($result)); + $this->assertSame($node, $result[0]); + } +}