diff --git a/.gitignore b/.gitignore index 782217c..c51ddc6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ dist vendor .idea +tags +coverage-report-html +composer.lock diff --git a/Makefile b/Makefile index 1b70c2d..4f47776 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,8 @@ PREFIX = . DIST_DIR = ${PREFIX}/dist PLY = Polyline.php -GMPET = ${DIST_DIR}/${PLY} -SRC_GMPET = ${SRC_DIR}/${PLY} +GMPET = ${DIST_DIR}/${PLY} +SRC_GMPET = ${SRC_DIR}/${PLY} NAMESPACE_GMPET = ${DIST_DIR}/emcconville/${PLY} GMPET_VER = $(shell git log -1 --pretty=format:%h\ %p\ %t) @@ -27,24 +27,26 @@ goodbye: polyline: ${SRC_GMPET} | ${DIST_DIR} @@echo "Building Polyline" - + @@cat ${SRC_GMPET} | \ sed 's/@VERSION@/'"${GMPET_VER}"'/' | \ sed 's/@DATE@/'"${GMPET_DATE}"'/' > ${GMPET}; - + namespace: polyline @@echo "Patching namespace" @@mkdir -p ${DIST_DIR}/emcconville @@cat ${GMPET} | \ sed 's/^\/\/@NAMESPACE@\s*//g' > ${NAMESPACE_GMPET} -test: +test: @@echo "Testing Polyline" @@if test ! -z ${PHPUNIT}; then \ ${PHPUNIT} --testdox ; \ else \ echo "PHPUnit not installed. Skipping build test."; \ fi - + +lint: + vendor/bin/phpcs --standard=tests/phpcs-ruleset.xml src tests + .PHONY: all clean goodbye polyline namespace test - \ No newline at end of file diff --git a/composer.json b/composer.json index 5d28bb5..dd74e3f 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,9 @@ "require": { "php": ">=5.3" }, + "require-dev": { + "squizlabs/php_codesniffer": "2.0.0a2" + }, "autoload": { "files": ["src/Polyline.php"] } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a909508..e2ceb03 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,8 +8,8 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" bootstrap="tests/bootstrap.php" > + ./tests/ @@ -21,5 +21,9 @@ ./src/ - + + + + + diff --git a/src/Polyline.php b/src/Polyline.php index 349a357..d944011 100755 --- a/src/Polyline.php +++ b/src/Polyline.php @@ -3,18 +3,18 @@ /** * Polyline * - * A simple class to handle polyline-encoding for Google Maps + * A simple class to handle polyline-encoding for Google Maps * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * @@ -28,13 +28,13 @@ //@NAMESPACE@ namespace emcconville { -class Polyline { - - /** - * @var array $polylines - */ - private $polylines = array(); - +class Polyline +{ + /** + * @var array $polylines + */ + private $polylines = array(); + /** * Default precision level of 1e-5. * @@ -49,200 +49,279 @@ class Polyline { */ protected static $precision = 5; - /** - * @var Polyline $instance - */ - private static $instance; - - /** - * Private constructor. Initialize class through Polyline::Singleton - * - * @see Polyline::Singleton - */ - private function __construct() {} - - /** - * Static instance method - * - * @return Polyline - */ - public static function Singleton() { - return self::$instance instanceof self ? self::$instance : self::$instance = new self; - } - - - /** - * Magic method for supporting wildcard getters - * - * @let {Node} be the name of the polyline - * @method get{Node}Points( ) //=> array of points for polyline "Node" - * @method get{Node}Encoded( ) //=> encoded string for polyline "Node" - * @method getPoints( "{Node}") //=> array of points for polyline "Node" - * @method getEncoded("{Node}") //=> encoded string for polyline "Node" - */ - public function __call($method,$arguments) { - $return = null; - if (preg_match('/^get(.+?)(points|encoded)$/i',$method,$matches)) { - list($all,$node,$type) = $matches; - return $this->getPolyline(strtolower($node),strtolower($type)); - } elseif (preg_match('/^get(points|encoded)$/i',$method,$matches)) { - list($all,$type) = $matches; - $node = array_shift($arguments); - return $this->getPolyline(strtolower($node),strtolower($type)); - } else { - throw new BadMethodCallException(); - } - return $return; - } - - /** - * Polyline getter - * @param string $node - * @param string $type - * @return mixed - */ - public function getPolyline($node, $type) { - $node = strtolower($node); - $type = in_array($type,array('points','encoded')) ? $type : 'encoded'; - return isset($this->polylines[$node]) - ? $this->polylines[$node][$type] - : ($type =='points' ? array() : null); - } - - - /** - * General purpose data method - * - * @param string polyline name - * @param mixed [ string | array ] optional - * @return array - */ - public function polyline() { - $arguments = func_get_args(); - $return = null; - switch (count($arguments)) { - case 2 : - list($node,$value) = $arguments; - $isArray = is_array($value); - $return = $this->polylines[strtolower($node)] = array( - 'points' => $isArray ? self::Flatten($value) : self::Decode($value), - 'encoded' => $isArray ? self::Encode($value) : $value - ); - $return = $return[$isArray ? 'encoded' : 'points' ]; - break; - case 1 : - $node = strtolower((string)array_shift($arguments)); - $return = isset($this->polylines[$node]) - ? $this->polylines[$node] - : array( 'points' => null, 'encoded' => null ); - break; - } - return $return; - } - - /** - * Retrieve list of polyline within singleton - * - * @return array $polylines - */ - public function listPolylines() { - return $return = array_keys($this->polylines); - } - - /** - * Apply Google Polyline algorithm to list of points - * - * @param array $points - * @param integer $precision optional - * @return string $encoded_string - */ - final public static function Encode($points) { - $points = self::Flatten($points); - $encoded_string = ''; - $index = 0; - $previous = array(0,0); - foreach($points as $number) { - $number = (float)($number); - $number = floor($number * pow(10, static::$precision)); - $diff = $number - $previous[$index % 2]; - $previous[$index % 2] = $number; - $number = $diff; - $index++; - $number = ($number < 0) ? ~($number << 1) : ($number << 1); - $chunk = ''; - while($number >= 0x20) { - $chunk .= chr((0x20 | ($number & 0x1f)) + 63); - $number >>= 5; - } - $chunk .= chr($number + 63); - $encoded_string .= $chunk; - } - return $encoded_string; - } - - /** - * Reverse Google Polyline algorithm on encoded string - * - * @param string $string - * @param integer $precision optional - * @return array $points - */ - final public static function Decode($string) { - $points = array(); - $index = $i = 0; - $previous = array(0,0); - while( $i < strlen($string) ) { - $shift = $result = 0x00; - do { - $bit = ord(substr($string,$i++)) - 63; - $result |= ($bit & 0x1f) << $shift; - $shift += 5; - } while( $bit >= 0x20 ) ; - - $diff = ($result & 1) ? ~($result >> 1) : ($result >> 1); - $number = $previous[$index % 2] + $diff; - $previous[$index % 2] = $number; - $index++; - $points[] = $number * 1 / pow(10, static::$precision); - } - return $points; - } - - /** - * Reduce multi-dimensional to single list - * - * @param array $array - * @return array $flatten - */ - final public static function Flatten($array) { - $flatten = array(); - foreach(array_values($array) as $node) { - if (is_array($node)) { - $flatten = array_merge($flatten,self::Flatten($node)); - } else { - $flatten[] = $node; - } - } - return $flatten; - } - - /** - * Concat list into pairs of points - * - * @param array $list - * @return array $pairs - */ - final public static function Pair($list) { - $pairs = array(); - if(!is_array($list)) { return $pairs; } - do { - $pairs[] = array( - array_shift($list), - array_shift($list) - ); - } while (!empty($list)); - return $pairs; - } + /** + * @var Polyline $instance + */ + private static $instance; + + /** + * Private constructor. Initialize class through Polyline::Singleton + * + * @see Polyline::Singleton + */ + private function __construct() + { + } + + /** + * Static instance method + * + * @return Polyline + */ + public static function Singleton() + { + return self::$instance instanceof self ? self::$instance : self::$instance = new self; + } + + /** + * Magic method for supporting wildcard getters + * + * @let {Node} be the name of the polyline + * @method get{Node}Points( ) //=> array of points for polyline "Node" + * @method get{Node}Encoded( ) //=> encoded string for polyline "Node" + * @method getPoints( "{Node}") //=> array of points for polyline "Node" + * @method getEncoded("{Node}") //=> encoded string for polyline "Node" + */ + public function __call($method, $arguments) + { + $return = null; + if (preg_match('/^get(.+?)(points|encoded)$/i', $method, $matches)) { + list($all,$node,$type) = $matches; + return $this->getPolyline(strtolower($node), strtolower($type)); + } elseif (preg_match('/^get(points|encoded)$/i', $method, $matches)) { + list($all,$type) = $matches; + $node = array_shift($arguments); + return $this->getPolyline(strtolower($node), strtolower($type)); + } else { + throw new BadMethodCallException($method); + } + return $return; + } + + /** + * Polyline getter + * @param string $node + * @param string $type + * @return mixed + */ + public function getPolyline($node, $type) + { + $node = strtolower($node); + $type = in_array($type, array('points','encoded')) ? $type : 'encoded'; + return isset($this->polylines[$node]) + ? $this->polylines[$node][$type] + : ($type =='points' ? array() : null); + } + + /** + * General purpose data method + * + * @param string polyline name + * @param mixed [ string | array ] optional + * @return array + */ + public function polyline($node, $value = '') + { + $node = strtolower($node); + + if (is_array($value)) { + return $this->importPolyArray($node, $value); + } else if ($value != '') { + return $this->importPolyString($node, $value); + } + + return $this->getNode($node); + } + + /** + * Imports a polyline specifed in an array of arrays + * + * @param string $node polyline name + * @param array $value + * @return string encoded polyline + */ + public function importPolyArray($node, $value) + { + if (!is_array($value)) { + throw new InvalidArgumentException(); + } + + $return = $this->polylines[strtolower($node)] = array( + 'points' => self::Flatten($value), + 'encoded' => self::Encode($value) + ); + return $return['encoded']; + } + + /** + * Imports a encoded polyline to the internal buffer + * @param string $node polyline name + * @param string $value + * @return decoded polyline + */ + public function importPolyString($node, $value) + { + if (!is_string($value)) { + throw new InvalidArgumentException(); + } + + $node = strtolower($node); + + $return = $this->polylines[$node] = array( + 'points' => self::Decode($value), + 'encoded' => $value + ); + return $return['points']; + } + + /** + * Imports a polyline specifed in an array of objects, + * extracting coordinates from latitude & longitude parameters + * of the objects + * + * @param string $node polyline name + * @param array $objs + * @return string encoded polyline + */ + public function importPolyObjects($node, $objs) + { + if (!is_array($objs)) { + throw new InvalidArgumentException(); + } + + $arr = array(); + + foreach ($objs as $obj) { + + if (property_exists($obj, 'latitude') && property_exists($obj, 'longitude')) { + $arr[] = array($obj->latitude, $obj->longitude); + } + } + + return $this->importPolyArray($node, $arr); + } + + /** + * @return polyline at the specified $node + */ + public function getNode($node) + { + $node = strtolower($node); + return isset($this->polylines[$node]) + ? $this->polylines[$node] + : array('points' => null, 'encoded' => null); + } + + /** + * Retrieve list of polyline within singleton + * + * @return array polylines + */ + public function listPolylines() + { + return $return = array_keys($this->polylines); + } + + /** + * Apply Google Polyline algorithm to list of points + * + * @param array $points + * @param integer $precision optional + * @return string encoded string + */ + final public static function Encode($points) + { + $points = self::Flatten($points); + $encodedString = ''; + $index = 0; + $previous = array(0,0); + foreach ($points as $number) { + $number = (float)($number); + $number = floor($number * pow(10, static::$precision)); + $diff = $number - $previous[$index % 2]; + $previous[$index % 2] = $number; + $number = $diff; + $index++; + $number = ($number < 0) ? ~($number << 1) : ($number << 1); + $chunk = ''; + while ($number >= 0x20) { + $chunk .= chr((0x20 | ($number & 0x1f)) + 63); + $number >>= 5; + } + $chunk .= chr($number + 63); + $encodedString .= $chunk; + } + return $encodedString; + } + + /** + * Reverse Google Polyline algorithm on encoded string + * + * @param string $string + * @param integer $precision optional + * @return array points + */ + final public static function Decode($string) + { + $points = array(); + $index = $i = 0; + $previous = array(0,0); + while ($i < strlen($string)) { + $shift = $result = 0x00; + do { + $bit = ord(substr($string, $i++)) - 63; + $result |= ($bit & 0x1f) << $shift; + $shift += 5; + } while ($bit >= 0x20); + + $diff = ($result & 1) ? ~($result >> 1) : ($result >> 1); + $number = $previous[$index % 2] + $diff; + $previous[$index % 2] = $number; + $index++; + $points[] = $number * 1 / pow(10, static::$precision); + } + return $points; + } + + /** + * Reduce multi-dimensional to single list + * + * @param array $array + * @return array flattened + */ + final public static function Flatten($array) + { + $flatten = array(); + foreach (array_values($array) as $node) { + if (is_array($node)) { + $flatten = array_merge($flatten, self::Flatten($node)); + } else { + $flatten[] = $node; + } + } + return $flatten; + } + + /** + * Concat list into pairs of points + * + * @param array $list + * @return array pairs + */ + final public static function Pair($list) + { + $pairs = array(); + if (!is_array($list)) { + return $pairs; + } + do { + $pairs[] = array( + array_shift($list), + array_shift($list) + ); + } while (!empty($list)); + return $pairs; + } } //@NAMESPACE@ } diff --git a/tests/PolylineTest.php b/tests/PolylineTest.php index 8508da6..a1d3a5c 100644 --- a/tests/PolylineTest.php +++ b/tests/PolylineTest.php @@ -1,4 +1,17 @@ latitude = $lat; + $this->longitude = $long; + } +} + /** * Generated by PHPUnit_SkeletonGenerator on 2012-02-17 at 14:08:49. */ @@ -7,14 +20,14 @@ class PolylineTest extends PHPUnit_Framework_TestCase protected $polylineName = "HydeParkRecords"; protected $encoded = '}`c~FlyquOnAE?`B@|HBpGJ?@pI'; protected $points = array( - array(41.79999,-87.58695), - array(41.79959,-87.58692), - array(41.79959,-87.58741), - array(41.79958,-87.58900), - array(41.79956,-87.59037), - array(41.79950,-87.59037), - array(41.79949,-87.59206) - ); + array(41.79999,-87.58695), + array(41.79959,-87.58692), + array(41.79959,-87.58741), + array(41.79958,-87.58900), + array(41.79956,-87.59037), + array(41.79950,-87.59037), + array(41.79949,-87.59206) + ); /** * @covers Polyline::Singleton @@ -23,23 +36,105 @@ class PolylineTest extends PHPUnit_Framework_TestCase public function testSingleton() { $object = Polyline::Singleton(); - $this->assertInstanceOf('Polyline',$object); + $this->assertInstanceOf('Polyline', $object); + return $object; + } + + /** + * @depends testSingleton + */ + public function testGooglePolyline(Polyline $object) + { + // uses example from google maps api docs + // at https://developers.google.com/maps/documentation/utilities/polylinealgorithm + $points = array( + array(38.5, -120.2), + array(40.7, -120.95), + array(43.252, -126.453) + ); + + $encoded = $object->polyline($this->polylineName, $points); + $this->assertEquals('_p~iF~ps|U_ulLnnqC_mqNvxq`@', $encoded); + } + + /** + * @covers Polyline::polyline + * @covers Polyline::Encode + * @covers Polyline::Flatten + * @depends testSingleton + */ + public function testPolyline(Polyline $object) + { + $encoded = $object->polyline($this->polylineName, $this->points); + $this->assertEquals($encoded, $this->encoded); + $hash = $object->polyline($this->polylineName); + $this->assertEquals($encoded, $hash['encoded']); return $object; } - /** - * @covers Polyline::polyline - * @covers Polyline::Encode - * @covers Polyline::Flatten - * @depends testSingleton - */ - public function testPolyline(Polyline $object) { - $encoded = $object->polyline($this->polylineName,$this->points); - $this->assertEquals($encoded,$this->encoded); - $hash = $object->polyline($this->polylineName); - $this->assertEquals($encoded,$hash['encoded']); - return $object; - } + /** + * @depends testSingleton + */ + public function testGetNode(Polyline $object) + { + $encoded = $object->importPolyArray($this->polylineName, $this->points); + $this->assertEquals($encoded, $this->encoded); + $hash = $object->getNode($this->polylineName); + $this->assertEquals($encoded, $hash['encoded']); + return $object; + } + + /** + * @depends testSingleton + */ + public function testImportPolyString(Polyline $object) + { + $x = $object->importPolyString('nodeKey', $this->encoded); + $this->assertEquals(14, count($x)); + } + + /** + * @depends testSingleton + */ + public function testImportPolyObjects(Polyline $object) + { + $objs = array(); + foreach ($this->points as $point) { + $objs[] = new MyCoord($point[0], $point[1]); + } + + $this->assertEquals( + $this->encoded, + $object->importPolyObjects($this->polylineName, $objs) + ); + } + + /** + * @depends testSingleton + * @expectedException InvalidArgumentException + */ + public function testImportPolyObjectsBadInput(Polyline $object) + { + $encoded = $object->importPolyObjects($this->polylineName, null); + } + + /** + * @depends testSingleton + * @expectedException InvalidArgumentException + */ + public function testImportPolyArrayBadInput(Polyline $object) + { + $encoded = $object->importPolyArray($this->polylineName, null); + } + + /** + * @depends testSingleton + * @expectedException InvalidArgumentException + */ + public function testImportPolyStringBadInput(Polyline $object) + { + $encoded = $object->importPolyString($this->polylineName, null); + } /** * @covers Polyline::getPolyline @@ -47,31 +142,31 @@ public function testPolyline(Polyline $object) { */ public function testGetPolyline(Polyline $object) { - $this->assertEquals($this->encoded,$object->getPolyline($this->polylineName,'encoded')); - $this->assertNull($object->getPolyline('I_Dont_exsits','encoded')); - return $object; + $this->assertEquals($this->encoded, $object->getPolyline($this->polylineName, 'encoded')); + $this->assertNull($object->getPolyline('I_Dont_exsits', 'encoded')); + return $object; } /** * @covers Polyline::__call - * @depends testGetPolyline + * @depends testGetPolyline */ public function testGetters(Polyline $object) { - $this->assertEquals($this->encoded,$object->getEncoded($this->polylineName)); - $this->assertEquals($this->encoded,$object->getHydeParkRecordsEncoded()); - return $object; + $this->assertEquals($this->encoded, $object->getEncoded($this->polylineName)); + $this->assertEquals($this->encoded, $object->getHydeParkRecordsEncoded()); + return $object; } - /** + /** * @covers Polyline::__call * @expectedException BadMethodCallException - * @depends testPolyline + * @depends testPolyline */ public function testGettersException(Polyline $object) { - $object->thisMethodFails(); - return $object; + $object->thisMethodFails(); + return $object; } /** @@ -81,7 +176,7 @@ public function testGettersException(Polyline $object) public function testListPolylines(Polyline $object) { $list = $object->listPolylines(); - $this->assertCount(1,$list); + $this->assertCount(2, $list); } /** @@ -91,10 +186,9 @@ public function testListPolylines(Polyline $object) public function testEncode() { // Remove the following lines when you implement this test. - $this->assertEquals($this->encoded,Polyline::Encode($this->points)); + $this->assertEquals($this->encoded, Polyline::Encode($this->points)); } - /** * @covers Polyline::Decode */ @@ -103,18 +197,17 @@ public function testDecode() $this->assertCount(count($this->points) * 2, Polyline::Decode($this->encoded)); } - /** * @covers Polyline::Flatten */ public function testFlatten() { $paired = array( - array(1,2), - array(3,4), - array(5,6) + array(1,2), + array(3,4), + array(5,6) ); - $this->assertEquals(array(1,2,3,4,5,6),Polyline::Flatten($paired)); + $this->assertEquals(array(1,2,3,4,5,6), Polyline::Flatten($paired)); } /** @@ -123,10 +216,10 @@ public function testFlatten() public function testPair() { $paired = array( - array(1,2), - array(3,4), - array(5,6) + array(1,2), + array(3,4), + array(5,6) ); - $this->assertEquals($paired,Polyline::Pair(array(1,2,3,4,5,6))); + $this->assertEquals($paired, Polyline::Pair(array(1,2,3,4,5,6))); } } diff --git a/tests/PrecisionTest.php b/tests/PrecisionTest.php index f01398d..2030511 100644 --- a/tests/PrecisionTest.php +++ b/tests/PrecisionTest.php @@ -3,17 +3,18 @@ class PrecisionPolyline extends Polyline { protected static $precision = 6; } + class PrecisionTest extends PHPUnit_Framework_TestCase { protected $encoded = 'q}~~|AdshNkSsBid@eGqBlm@yKhj@bA?'; protected $points = array( - 49.283049, -0.250691, - 49.283375, -0.250633, - 49.283972, -0.250502, - 49.284029, -0.251245, - 49.284234, -0.251938, - 49.284200, -0.251938 - ); + 49.283049, -0.250691, + 49.283375, -0.250633, + 49.283972, -0.250502, + 49.284029, -0.251245, + 49.284234, -0.251938, + 49.284200, -0.251938 + ); /** * @covers Polyline::Encode @@ -21,10 +22,10 @@ class PrecisionTest extends PHPUnit_Framework_TestCase */ public function testEncodePrecision() { - $this->assertEquals( - $this->encoded, - PrecisionPolyline::Encode($this->points) - ); + $this->assertEquals( + $this->encoded, + PrecisionPolyline::Encode($this->points) + ); } /** @@ -32,9 +33,9 @@ public function testEncodePrecision() */ public function testDecodePrecision() { - $this->assertEquals( - $this->points, - PrecisionPolyline::Decode($this->encoded) - ); + $this->assertEquals( + $this->points, + PrecisionPolyline::Decode($this->encoded) + ); } -} \ No newline at end of file +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 517a80a..7d8dadc 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,12 +1,12 @@ + + custom code convention rules + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +