From 368372123607c7e1d470de03994923ced3752767 Mon Sep 17 00:00:00 2001 From: Mathieu Darse Date: Thu, 12 Nov 2015 18:29:19 +0100 Subject: [PATCH] Index GPS positions with care of cardinal points --- .../Indexer/Record/Hydrator/GpsPosition.php | 99 ++++++++++++++++ .../Record/Hydrator/MetadataHydrator.php | 35 +++++- .../SearchEngine/Indexer/GpsPositionTest.php | 112 ++++++++++++++++++ 3 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/GpsPosition.php create mode 100644 tests/Alchemy/Tests/Phrasea/SearchEngine/Indexer/GpsPositionTest.php diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/GpsPosition.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/GpsPosition.php new file mode 100644 index 0000000000..97efeea8cb --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/GpsPosition.php @@ -0,0 +1,99 @@ +longitude = (float) $value; + break; + + case self::LATITUDE_TAG_NAME: + Assertion::numeric($value); + $this->latitude = (float) $value; + break; + + case self::LONGITUDE_REF_TAG_NAME: + $normalized = strtoupper($value); + if ($normalized !== self::LONGITUDE_REF_EAST && $normalized !== self::LONGITUDE_REF_WEST) { + throw new \InvalidArgumentException(sprintf('Invalid longitude reference "%s" (expecting "%s" or "%s").', $value, self::LONGITUDE_REF_EAST, self::LONGITUDE_REF_WEST)); + } + $this->longitude_ref = $value; + break; + + case self::LATITUDE_REF_TAG_NAME: + $normalized = strtoupper($value); + if ($normalized !== self::LATITUDE_REF_NORTH && $normalized !== self::LATITUDE_REF_SOUTH) { + throw new \InvalidArgumentException(sprintf('Invalid latitude reference "%s" (expecting "%s" or "%s").', $value, self::LATITUDE_REF_NORTH, self::LATITUDE_REF_SOUTH)); + } + $this->latitude_ref = $normalized; + break; + + default: + throw new \InvalidArgumentException(sprintf('Unsupported tag name "%s".', $tag_name)); + } + } + + public static function isSupportedTagName($tag_name) + { + return in_array($tag_name, [ + self::LONGITUDE_TAG_NAME, + self::LONGITUDE_REF_TAG_NAME, + self::LATITUDE_TAG_NAME, + self::LATITUDE_REF_TAG_NAME + ]); + } + + public function isComplete() + { + return $this->longitude !== null + && $this->longitude_ref !== null + && $this->latitude !== null + && $this->latitude_ref !== null; + } + + public function getSignedLongitude() + { + if ($this->longitude === null) { + return null; + } + return $this->longitude * ($this->longitude_ref === self::LONGITUDE_REF_WEST ? -1 : 1); + } + + public function getSignedLatitude() + { + if ($this->latitude === null) { + return null; + } + return $this->latitude * ($this->latitude_ref === self::LATITUDE_REF_SOUTH ? -1 : 1); + } +} diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/MetadataHydrator.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/MetadataHydrator.php index 34fe519718..5785ecd2e1 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/MetadataHydrator.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/MetadataHydrator.php @@ -19,6 +19,7 @@ use Alchemy\Phrasea\Utilities\StringHelper; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\Connection as DriverConnection; use DomainException; +use InvalidArgumentException; class MetadataHydrator implements HydratorInterface { @@ -26,6 +27,8 @@ class MetadataHydrator implements HydratorInterface private $structure; private $helper; + private $gps_position_buffer = []; + public function __construct(DriverConnection $connection, Structure $structure, RecordHelper $helper) { $this->connection = $connection; @@ -93,12 +96,16 @@ SQL; break; case 'exif': - // EXIF data is single-valued + if (GpsPosition::isSupportedTagName($key)) { + $this->handleGpsPosition($records, $id, $key, $value); + break; + } $tag = $this->structure->getMetadataTagByName($key); if ($tag) { $value = $this->sanitizeValue($value, $tag->getType()); } - $record['exif'][$key] = $value; + // EXIF data is single-valued + $record['metadata_tags'][$key] = $value; break; default: @@ -106,6 +113,8 @@ SQL; break; } } + + $this->clearGpsPositionBuffer(); } private function sanitizeValue($value, $type) @@ -131,4 +140,26 @@ SQL; return $value; } } + + private function handleGpsPosition(&$records, $id, $tag_name, $value) + { + // Get position object + if (!isset($this->gps_position_buffer[$id])) { + $this->gps_position_buffer[$id] = new GpsPosition(); + } + $position = $this->gps_position_buffer[$id]; + // Push this tag into object + $position->set($tag_name, $value); + // Try to output complete position + if ($position->isComplete()) { + $records[$id]['metadata_tags']['Longitude'] = $position->getSignedLongitude(); + $records[$id]['metadata_tags']['Latitude'] = $position->getSignedLatitude(); + unset($this->gps_position_buffer[$id]); + } + } + + private function clearGpsPositionBuffer() + { + $this->gps_position_buffer = []; + } } diff --git a/tests/Alchemy/Tests/Phrasea/SearchEngine/Indexer/GpsPositionTest.php b/tests/Alchemy/Tests/Phrasea/SearchEngine/Indexer/GpsPositionTest.php new file mode 100644 index 0000000000..54b37c37b4 --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/SearchEngine/Indexer/GpsPositionTest.php @@ -0,0 +1,112 @@ +set('Latitude', 48.856578); + $this->assertFalse($position->isComplete()); + } + + public function testIsCompleteWithAllSet() + { + $position = new GpsPosition(); + $position->set('Latitude', 48.856578); + $position->set('LatitudeRef', 'N'); + $position->set('Longitude', 2.351828); + $position->set('LongitudeRef', 'E'); + $this->assertTrue($position->isComplete()); + } + + /** + * @dataProvider getSupportedTagNames + */ + public function testSupportedTagNames($tag_name) + { + $this->assertTrue(GpsPosition::isSupportedTagName($tag_name)); + } + + public function getSupportedTagNames() + { + return [ + ['Longitude'], + ['LongitudeRef'], + ['Latitude'], + ['LatitudeRef'], + [GpsPosition::LONGITUDE_TAG_NAME], + [GpsPosition::LONGITUDE_REF_TAG_NAME], + [GpsPosition::LATITUDE_TAG_NAME], + [GpsPosition::LATITUDE_REF_TAG_NAME] + ]; + } + + /** + * @dataProvider getUnsupportedTagNames + */ + public function testUnsupportedTagNames($tag_name) + { + $this->assertFalse(GpsPosition::isSupportedTagName($tag_name)); + } + + public function getUnsupportedTagNames() + { + return [ + ['foo'], + ['lOnGiTuDe'] + ]; + } + + public function testGetSignedLongitude() + { + $position = new GpsPosition(); + $position->set('Longitude', 2.351828); + $this->assertEquals(2.351828, $position->getSignedLongitude()); + + $position = new GpsPosition(); + $position->set('LongitudeRef', 'E'); + $this->assertNull($position->getSignedLongitude()); + + $position = new GpsPosition(); + $position->set('Longitude', 2.351828); + $position->set('LongitudeRef', 'E'); + $this->assertEquals(2.351828, $position->getSignedLongitude()); + + $position = new GpsPosition(); + $position->set('Longitude', 2.351828); + $position->set('LongitudeRef', 'W'); + $this->assertEquals(-2.351828, $position->getSignedLongitude()); + } + + public function testGetSignedLatitude() + { + $position = new GpsPosition(); + $position->set('Latitude', 48.856578); + $this->assertEquals(48.856578, $position->getSignedLatitude()); + + $position = new GpsPosition(); + $position->set('LatitudeRef', 'N'); + $this->assertNull($position->getSignedLatitude()); + + $position = new GpsPosition(); + $position->set('Latitude', 48.856578); + $position->set('LatitudeRef', 'N'); + $this->assertEquals(48.856578, $position->getSignedLatitude()); + + $position = new GpsPosition(); + $position->set('Latitude', 48.856578); + $position->set('LatitudeRef', 'S'); + $this->assertEquals(-48.856578, $position->getSignedLatitude()); + } +}