Skip to content

[Map] Add multipolygon support #2762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/Map/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# CHANGELOG

## 2.26

- Add support for creating `Polygon` with holes, by passing an array of `array<Point>` 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
Expand Down
2 changes: 1 addition & 1 deletion src/Map/assets/dist/abstract_map_controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export type MarkerDefinition<MarkerOptions, InfoWindowOptions> = WithIdentifier<
}>;
export type PolygonDefinition<PolygonOptions, InfoWindowOptions> = WithIdentifier<{
infoWindow?: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
points: Array<Point>;
points: Array<Point> | Array<Array<Point>>;
title: string | null;
rawOptions?: PolygonOptions;
extra: Record<string, unknown>;
Expand Down
2 changes: 1 addition & 1 deletion src/Map/assets/src/abstract_map_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export type MarkerDefinition<MarkerOptions, InfoWindowOptions> = WithIdentifier<

export type PolygonDefinition<PolygonOptions, InfoWindowOptions> = WithIdentifier<{
infoWindow?: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
points: Array<Point>;
points: Array<Point> | Array<Array<Point>>;
title: string | null;
/**
* Raw options passed to the marker constructor, specific to the map provider (e.g.: `L.marker()` for Leaflet).
Expand Down
35 changes: 30 additions & 5 deletions src/Map/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~
Expand All @@ -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)
Expand All @@ -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'
Expand Down Expand Up @@ -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),
Expand All @@ -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<Point>` 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),
Expand Down
16 changes: 11 additions & 5 deletions src/Map/src/Polygon.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
final class Polygon implements Element
{
/**
* @param array<string, mixed> $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side
* @param array<Point>|array<array<Point>> $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<string, mixed> $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,
Expand All @@ -36,7 +37,7 @@ public function __construct(
* Convert the polygon to an array representation.
*
* @return array{
* points: array<array{lat: float, lng: float}>,
* points: array<array{lat: float, lng: float}>|array<array{lat: float, lng: float}>,
* title: string|null,
* infoWindow: array<string, mixed>|null,
* extra: array,
Expand All @@ -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,
Expand All @@ -56,7 +59,7 @@ public function toArray(): array

/**
* @param array{
* points: array<array{lat: float, lng: float}>,
* points: array<array{lat: float, lng: float}|array<array{lat: float, lng: float}>>,
* title: string|null,
* infoWindow: array<string, mixed>|null,
* extra: array,
Expand All @@ -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']);
Expand Down
152 changes: 152 additions & 0 deletions src/Map/tests/PolygonTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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']);
}
}
Loading