diff --git a/src/Map/CHANGELOG.md b/src/Map/CHANGELOG.md index dfb22e55ceb..c400f59c456 100644 --- a/src/Map/CHANGELOG.md +++ b/src/Map/CHANGELOG.md @@ -1,5 +1,29 @@ # CHANGELOG +## 2.26 + +- Add support for creating `Polygon` with holes, by passing an array of `array` as `points` parameter to the `Polygon` constructor, e.g.: +```php +// Draw a polygon with a hole in it, on the French map +$map->addPolygon(new Polygon(points: [ + // First path, the outer boundary of the polygon + [ + new Point(48.117266, -1.677792), // Rennes + new Point(50.629250, 3.057256), // Lille + new Point(48.573405, 7.752111), // Strasbourg + new Point(43.296482, 5.369780), // Marseille + new Point(44.837789, -0.579180), // Bordeaux + ], + // Second path, it will make a hole in the previous one + [ + new Point(45.833619, 1.261105), // Limoges + new Point(45.764043, 4.835659), // Lyon + new Point(49.258329, 4.031696), // Reims + new Point(48.856613, 2.352222), // Paris + ], +])); +``` + ## 2.25 - Downgrade PHP requirement from 8.3 to 8.1 diff --git a/src/Map/assets/dist/abstract_map_controller.d.ts b/src/Map/assets/dist/abstract_map_controller.d.ts index 9eb15e76cbd..a593f9ee8a7 100644 --- a/src/Map/assets/dist/abstract_map_controller.d.ts +++ b/src/Map/assets/dist/abstract_map_controller.d.ts @@ -36,7 +36,7 @@ export type MarkerDefinition = WithIdentifier< }>; export type PolygonDefinition = WithIdentifier<{ infoWindow?: InfoWindowWithoutPositionDefinition; - points: Array; + points: Array | Array>; title: string | null; rawOptions?: PolygonOptions; extra: Record; diff --git a/src/Map/assets/src/abstract_map_controller.ts b/src/Map/assets/src/abstract_map_controller.ts index b73f0bcc989..f41134fa873 100644 --- a/src/Map/assets/src/abstract_map_controller.ts +++ b/src/Map/assets/src/abstract_map_controller.ts @@ -48,7 +48,7 @@ export type MarkerDefinition = WithIdentifier< export type PolygonDefinition = WithIdentifier<{ infoWindow?: InfoWindowWithoutPositionDefinition; - points: Array; + points: Array | Array>; title: string | null; /** * Raw options passed to the marker constructor, specific to the map provider (e.g.: `L.marker()` for Leaflet). diff --git a/src/Map/doc/index.rst b/src/Map/doc/index.rst index b45ae091aad..2f3eebf969e 100644 --- a/src/Map/doc/index.rst +++ b/src/Map/doc/index.rst @@ -71,7 +71,7 @@ Start by creating a new map instance:: use Symfony\UX\Map\Map; // Create a new map instance - $myMap = (new Map()); + $map = new Map(); Center and zoom ~~~~~~~~~~~~~~~ @@ -81,7 +81,7 @@ You can set the center and zoom of the map using the ``center()`` and ``zoom()`` use Symfony\UX\Map\Map; use Symfony\UX\Map\Point; - $myMap + $map // Explicitly set the center and zoom ->center(new Point(46.903354, 1.888334)) ->zoom(6) @@ -95,7 +95,7 @@ Add markers You can add markers to a map using the ``addMarker()`` method:: - $myMap + $map ->addMarker(new Marker( position: new Point(48.8566, 2.3522), title: 'Paris' @@ -154,7 +154,7 @@ Add Polygons You can also add Polygons, which represents an area enclosed by a series of ``Point`` instances:: - $myMap->addPolygon(new Polygon( + $map->addPolygon(new Polygon( points: [ new Point(48.8566, 2.3522), new Point(45.7640, 4.8357), @@ -166,12 +166,37 @@ You can also add Polygons, which represents an area enclosed by a series of ``Po ), )); +.. versionadded:: 2.26 + + `Polygon` with holes is available since UX Map 2.26. + +Since UX Map 2.26, you can also create polygons with holes in them, by passing an array of `array` to `points` parameter:: + + // Draw a polygon with a hole in it, on the French map + $map->addPolygon(new Polygon(points: [ + // First path, the outer boundary of the polygon + [ + new Point(48.117266, -1.677792), // Rennes + new Point(50.629250, 3.057256), // Lille + new Point(48.573405, 7.752111), // Strasbourg + new Point(43.296482, 5.369780), // Marseille + new Point(44.837789, -0.579180), // Bordeaux + ], + // Second path, it will make a hole in the previous one + [ + new Point(45.833619, 1.261105), // Limoges + new Point(45.764043, 4.835659), // Lyon + new Point(49.258329, 4.031696), // Reims + new Point(48.856613, 2.352222), // Paris + ], + ])); + Add Polylines ~~~~~~~~~~~~~ You can add Polylines, which represents a path made by a series of ``Point`` instances:: - $myMap->addPolyline(new Polyline( + $map->addPolyline(new Polyline( points: [ new Point(48.8566, 2.3522), new Point(45.7640, 4.8357), diff --git a/src/Map/src/Polygon.php b/src/Map/src/Polygon.php index 1d526a1c285..4eb6747f171 100644 --- a/src/Map/src/Polygon.php +++ b/src/Map/src/Polygon.php @@ -21,7 +21,8 @@ final class Polygon implements Element { /** - * @param array $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side + * @param array|array> $points A list of point representing the polygon, or a list of paths (each path is an array of points) representing a polygon with holes. + * @param array $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side */ public function __construct( private readonly array $points, @@ -36,7 +37,7 @@ public function __construct( * Convert the polygon to an array representation. * * @return array{ - * points: array, + * points: array|array, * title: string|null, * infoWindow: array|null, * extra: array, @@ -46,7 +47,9 @@ public function __construct( public function toArray(): array { return [ - 'points' => array_map(fn (Point $point) => $point->toArray(), $this->points), + 'points' => current($this->points) instanceof Point + ? array_map(fn (Point $point) => $point->toArray(), $this->points) + : array_map(fn (array $path) => array_map(fn (Point $point) => $point->toArray(), $path), $this->points), 'title' => $this->title, 'infoWindow' => $this->infoWindow?->toArray(), 'extra' => $this->extra, @@ -56,7 +59,7 @@ public function toArray(): array /** * @param array{ - * points: array, + * points: array>, * title: string|null, * infoWindow: array|null, * extra: array, @@ -70,7 +73,10 @@ public static function fromArray(array $polygon): self if (!isset($polygon['points'])) { throw new InvalidArgumentException('The "points" parameter is required.'); } - $polygon['points'] = array_map(Point::fromArray(...), $polygon['points']); + + $polygon['points'] = isset($polygon['points'][0]['lat'], $polygon['points'][0]['lng']) + ? array_map(Point::fromArray(...), $polygon['points']) + : array_map(fn(array $points) => array_map(Point::fromArray(...), $points), $polygon['points']); if (isset($polygon['infoWindow'])) { $polygon['infoWindow'] = InfoWindow::fromArray($polygon['infoWindow']); diff --git a/src/Map/tests/PolygonTest.php b/src/Map/tests/PolygonTest.php new file mode 100644 index 00000000000..ccd9755ff13 --- /dev/null +++ b/src/Map/tests/PolygonTest.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Map\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\UX\Map\Exception\InvalidArgumentException; +use Symfony\UX\Map\InfoWindow; +use Symfony\UX\Map\Point; +use Symfony\UX\Map\Polygon; + +class PolygonTest extends TestCase +{ + public function testToArray() + { + $point1 = new Point(1.1, 2.2); + $point2 = new Point(3.3, 4.4); + + $infoWindow = new InfoWindow('info content'); + + $polygon = new Polygon( + points: [$point1, $point2], + title: 'Test Polygon', + infoWindow: $infoWindow, + extra: ['foo' => 'bar'], + id: 'poly1' + ); + + $array = $polygon->toArray(); + $this->assertSame([ + 'points' => [['lat' => 1.1, 'lng' => 2.2], ['lat' => 3.3, 'lng' => 4.4]], + 'title' => 'Test Polygon', + 'infoWindow' => [ + 'headerContent' => 'info content', + 'content' => null, + 'position' => null, + 'opened' => false, + 'autoClose' => true, + 'extra' => $array['infoWindow']['extra'], + ], + 'extra' => ['foo' => 'bar'], + 'id' => 'poly1', + ], $array); + } + + public function testToArrayMultidimensional() + { + $point1 = new Point(1.1, 2.2); + $point2 = new Point(3.3, 4.4); + $point3 = new Point(5.5, 6.6); + + $polygon = new Polygon( + points: [[$point1, $point2], [$point3]], + ); + + $array = $polygon->toArray(); + $this->assertSame([ + 'points' => [ + [['lat' => 1.1, 'lng' => 2.2], ['lat' => 3.3, 'lng' => 4.4]], + [['lat' => 5.5, 'lng' => 6.6]], + ], + 'title' => null, + 'infoWindow' => null, + 'extra' => $array['extra'], + 'id' => null, + ], $array); + } + + public function testFromArray() + { + $data = [ + 'points' => [ + ['lat' => 1.1, 'lng' => 2.2], ['lat' => 3.3, 'lng' => 4.4], + ], + 'title' => 'Test Polygon', + 'infoWindow' => ['content' => 'info content'], + 'extra' => ['foo' => 'bar'], + 'id' => 'poly1', + ]; + + $polygon = Polygon::fromArray($data); + + $this->assertInstanceOf(Polygon::class, $polygon); + + $array = $polygon->toArray(); + $this->assertSame([ + 'points' => [['lat' => 1.1, 'lng' => 2.2], ['lat' => 3.3, 'lng' => 4.4]], + 'title' => 'Test Polygon', + 'infoWindow' => [ + 'headerContent' => null, + 'content' => 'info content', + 'position' => null, + 'opened' => false, + 'autoClose' => true, + 'extra' => $array['infoWindow']['extra'], + ], + 'extra' => ['foo' => 'bar'], + 'id' => 'poly1', + ], $array); + } + + public function testFromArrayMultidimensional() + { + $data = [ + 'points' => [ + [['lat' => 1.1, 'lng' => 2.2], ['lat' => 3.3, 'lng' => 4.4]], + [['lat' => 5.5, 'lng' => 6.6]], + ], + 'title' => 'Test Polygon', + 'infoWindow' => ['content' => 'info content'], + 'extra' => ['foo' => 'bar'], + 'id' => 'poly1', + ]; + + $polygon = Polygon::fromArray($data); + + $this->assertInstanceOf(Polygon::class, $polygon); + + $array = $polygon->toArray(); + $this->assertSame([ + 'points' => [ + [['lat' => 1.1, 'lng' => 2.2], ['lat' => 3.3, 'lng' => 4.4]], + [['lat' => 5.5, 'lng' => 6.6]], + ], + 'title' => 'Test Polygon', + 'infoWindow' => [ + 'headerContent' => null, + 'content' => 'info content', + 'position' => null, + 'opened' => false, + 'autoClose' => true, + 'extra' => $array['infoWindow']['extra'], + ], + 'extra' => ['foo' => 'bar'], + 'id' => 'poly1', + ], $array); + } + + public function testFromArrayThrowsExceptionIfPointsMissing() + { + $this->expectException(InvalidArgumentException::class); + Polygon::fromArray(['invalid' => 'No points']); + } +}