diff --git a/src/Map/CHANGELOG.md b/src/Map/CHANGELOG.md
index de37e53e988..3883fe057d4 100644
--- a/src/Map/CHANGELOG.md
+++ b/src/Map/CHANGELOG.md
@@ -9,6 +9,7 @@
- Add `` Twig component
- The importmap entry `@symfony/ux-map/abstract-map-controller` can be removed
from your importmap, it is no longer needed.
+- Add `Polygon` support
## 2.19
diff --git a/src/Map/assets/dist/abstract_map_controller.d.ts b/src/Map/assets/dist/abstract_map_controller.d.ts
index 142c2f697b7..f7f9ffd8096 100644
--- a/src/Map/assets/dist/abstract_map_controller.d.ts
+++ b/src/Map/assets/dist/abstract_map_controller.d.ts
@@ -3,11 +3,12 @@ export type Point = {
lat: number;
lng: number;
};
-export type MapView = {
+export type MapView = {
center: Point | null;
zoom: number | null;
fitBoundsToMarkers: boolean;
markers: Array>;
+ polygons: Array>;
options: Options;
};
export type MarkerDefinition = {
@@ -17,6 +18,13 @@ export type MarkerDefinition = {
rawOptions?: MarkerOptions;
extra: Record;
};
+export type PolygonDefinition = {
+ infoWindow?: Omit, 'position'>;
+ points: Array;
+ title: string | null;
+ rawOptions?: PolygonOptions;
+ extra: Record;
+};
export type InfoWindowDefinition = {
headerContent: string | null;
content: string | null;
@@ -26,15 +34,16 @@ export type InfoWindowDefinition = {
rawOptions?: InfoWindowOptions;
extra: Record;
};
-export default abstract class extends Controller {
+export default abstract class extends Controller {
static values: {
providerOptions: ObjectConstructor;
view: ObjectConstructor;
};
- viewValue: MapView;
+ viewValue: MapView;
protected map: Map;
protected markers: Array;
protected infoWindows: Array;
+ protected polygons: Array;
connect(): void;
protected abstract doCreateMap({ center, zoom, options, }: {
center: Point | null;
@@ -42,14 +51,19 @@ export default abstract class): Marker;
+ createPolygon(definition: PolygonDefinition): Polygon;
protected abstract doCreateMarker(definition: MarkerDefinition): Marker;
- protected createInfoWindow({ definition, marker, }: {
- definition: MarkerDefinition['infoWindow'];
- marker: Marker;
+ protected abstract doCreatePolygon(definition: PolygonDefinition): Polygon;
+ protected createInfoWindow({ definition, element, }: {
+ definition: MarkerDefinition['infoWindow'] | PolygonDefinition['infoWindow'];
+ element: Marker | Polygon;
}): InfoWindow;
- protected abstract doCreateInfoWindow({ definition, marker, }: {
+ protected abstract doCreateInfoWindow({ definition, element, }: {
definition: MarkerDefinition['infoWindow'];
- marker: Marker;
+ element: Marker;
+ } | {
+ definition: PolygonDefinition['infoWindow'];
+ element: Polygon;
}): InfoWindow;
protected abstract doFitBoundsToMarkers(): void;
protected abstract dispatchEvent(name: string, payload: Record): void;
diff --git a/src/Map/assets/dist/abstract_map_controller.js b/src/Map/assets/dist/abstract_map_controller.js
index 9d2e3024024..83cc772e76a 100644
--- a/src/Map/assets/dist/abstract_map_controller.js
+++ b/src/Map/assets/dist/abstract_map_controller.js
@@ -5,18 +5,21 @@ class default_1 extends Controller {
super(...arguments);
this.markers = [];
this.infoWindows = [];
+ this.polygons = [];
}
connect() {
- const { center, zoom, options, markers, fitBoundsToMarkers } = this.viewValue;
+ const { center, zoom, options, markers, polygons, fitBoundsToMarkers } = this.viewValue;
this.dispatchEvent('pre-connect', { options });
this.map = this.doCreateMap({ center, zoom, options });
markers.forEach((marker) => this.createMarker(marker));
+ polygons.forEach((polygon) => this.createPolygon(polygon));
if (fitBoundsToMarkers) {
this.doFitBoundsToMarkers();
}
this.dispatchEvent('connect', {
map: this.map,
markers: this.markers,
+ polygons: this.polygons,
infoWindows: this.infoWindows,
});
}
@@ -27,10 +30,17 @@ class default_1 extends Controller {
this.markers.push(marker);
return marker;
}
- createInfoWindow({ definition, marker, }) {
- this.dispatchEvent('info-window:before-create', { definition, marker });
- const infoWindow = this.doCreateInfoWindow({ definition, marker });
- this.dispatchEvent('info-window:after-create', { infoWindow, marker });
+ createPolygon(definition) {
+ this.dispatchEvent('polygon:before-create', { definition });
+ const polygon = this.doCreatePolygon(definition);
+ this.dispatchEvent('polygon:after-create', { polygon });
+ this.polygons.push(polygon);
+ return polygon;
+ }
+ createInfoWindow({ definition, element, }) {
+ this.dispatchEvent('info-window:before-create', { definition, element });
+ const infoWindow = this.doCreateInfoWindow({ definition, element });
+ this.dispatchEvent('info-window:after-create', { infoWindow, element });
this.infoWindows.push(infoWindow);
return infoWindow;
}
diff --git a/src/Map/assets/src/abstract_map_controller.ts b/src/Map/assets/src/abstract_map_controller.ts
index 802b6477875..bae763cc529 100644
--- a/src/Map/assets/src/abstract_map_controller.ts
+++ b/src/Map/assets/src/abstract_map_controller.ts
@@ -2,11 +2,12 @@ import { Controller } from '@hotwired/stimulus';
export type Point = { lat: number; lng: number };
-export type MapView = {
+export type MapView = {
center: Point | null;
zoom: number | null;
fitBoundsToMarkers: boolean;
markers: Array>;
+ polygons: Array>;
options: Options;
};
@@ -27,6 +28,14 @@ export type MarkerDefinition = {
extra: Record;
};
+export type PolygonDefinition = {
+ infoWindow?: Omit, 'position'>;
+ points: Array;
+ title: string | null;
+ rawOptions?: PolygonOptions;
+ extra: Record;
+};
+
export type InfoWindowDefinition = {
headerContent: string | null;
content: string | null;
@@ -54,20 +63,23 @@ export default abstract class<
Marker,
InfoWindowOptions,
InfoWindow,
+ PolygonOptions,
+ Polygon,
> extends Controller {
static values = {
providerOptions: Object,
view: Object,
};
- declare viewValue: MapView;
+ declare viewValue: MapView;
protected map: Map;
protected markers: Array = [];
protected infoWindows: Array = [];
+ protected polygons: Array = [];
connect() {
- const { center, zoom, options, markers, fitBoundsToMarkers } = this.viewValue;
+ const { center, zoom, options, markers, polygons, fitBoundsToMarkers } = this.viewValue;
this.dispatchEvent('pre-connect', { options });
@@ -75,6 +87,8 @@ export default abstract class<
markers.forEach((marker) => this.createMarker(marker));
+ polygons.forEach((polygon) => this.createPolygon(polygon));
+
if (fitBoundsToMarkers) {
this.doFitBoundsToMarkers();
}
@@ -82,6 +96,7 @@ export default abstract class<
this.dispatchEvent('connect', {
map: this.map,
markers: this.markers,
+ polygons: this.polygons,
infoWindows: this.infoWindows,
});
}
@@ -106,18 +121,29 @@ export default abstract class<
return marker;
}
+ createPolygon(definition: PolygonDefinition): Polygon {
+ this.dispatchEvent('polygon:before-create', { definition });
+ const polygon = this.doCreatePolygon(definition);
+ this.dispatchEvent('polygon:after-create', { polygon });
+ this.polygons.push(polygon);
+ return polygon;
+ }
+
protected abstract doCreateMarker(definition: MarkerDefinition): Marker;
+ protected abstract doCreatePolygon(definition: PolygonDefinition): Polygon;
protected createInfoWindow({
definition,
- marker,
+ element,
}: {
- definition: MarkerDefinition['infoWindow'];
- marker: Marker;
+ definition:
+ | MarkerDefinition['infoWindow']
+ | PolygonDefinition['infoWindow'];
+ element: Marker | Polygon;
}): InfoWindow {
- this.dispatchEvent('info-window:before-create', { definition, marker });
- const infoWindow = this.doCreateInfoWindow({ definition, marker });
- this.dispatchEvent('info-window:after-create', { infoWindow, marker });
+ this.dispatchEvent('info-window:before-create', { definition, element });
+ const infoWindow = this.doCreateInfoWindow({ definition, element });
+ this.dispatchEvent('info-window:after-create', { infoWindow, element });
this.infoWindows.push(infoWindow);
@@ -126,11 +152,16 @@ export default abstract class<
protected abstract doCreateInfoWindow({
definition,
- marker,
- }: {
- definition: MarkerDefinition['infoWindow'];
- marker: Marker;
- }): InfoWindow;
+ element,
+ }:
+ | {
+ definition: MarkerDefinition['infoWindow'];
+ element: Marker;
+ }
+ | {
+ definition: PolygonDefinition['infoWindow'];
+ element: Polygon;
+ }): InfoWindow;
protected abstract doFitBoundsToMarkers(): void;
diff --git a/src/Map/assets/test/abstract_map_controller.test.ts b/src/Map/assets/test/abstract_map_controller.test.ts
index 0beadef2ee2..c9e0e38aeba 100644
--- a/src/Map/assets/test/abstract_map_controller.test.ts
+++ b/src/Map/assets/test/abstract_map_controller.test.ts
@@ -20,14 +20,28 @@ class MyMapController extends AbstractMapController {
const marker = { marker: 'marker', title: definition.title };
if (definition.infoWindow) {
- this.createInfoWindow({ definition: definition.infoWindow, marker });
+ this.createInfoWindow({ definition: definition.infoWindow, element: marker });
}
return marker;
}
- doCreateInfoWindow({ definition, marker }) {
- return { infoWindow: 'infoWindow', headerContent: definition.headerContent, marker: marker.title };
+ doCreatePolygon(definition) {
+ const polygon = { polygon: 'polygon', title: definition.title };
+
+ if (definition.infoWindow) {
+ this.createInfoWindow({ definition: definition.infoWindow, element: polygon });
+ }
+ return polygon;
+ }
+
+ doCreateInfoWindow({ definition, element }) {
+ if (element.marker) {
+ return { infoWindow: 'infoWindow', headerContent: definition.headerContent, marker: element.title };
+ }
+ if (element.polygon) {
+ return { infoWindow: 'infoWindow', headerContent: definition.headerContent, polygon: element.title };
+ }
}
doFitBoundsToMarkers() {
@@ -47,12 +61,61 @@ describe('AbstractMapController', () => {
beforeEach(() => {
container = mountDOM(`
+ data-testid="map"
+ data-controller="map"
+ style="height: 700px; margin: 10px;"
+ data-map-provider-options-value="{}"
+ data-map-view-value='{
+ "center": { "lat": 48.8566, "lng": 2.3522 },
+ "zoom": 4,
+ "fitBoundsToMarkers": true,
+ "options": {},
+ "markers": [
+ {
+ "position": { "lat": 48.8566, "lng": 2.3522 },
+ "title": "Paris",
+ "infoWindow": null
+ },
+ {
+ "position": { "lat": 45.764, "lng": 4.8357 },
+ "title": "Lyon",
+ "infoWindow": {
+ "headerContent": "Lyon",
+ "content": "The French town in the historic Rhône-Alpes region, located at the junction of the Rhône and Saône rivers.",
+ "position": null,
+ "opened": false,
+ "autoClose": true
+ }
+ }
+ ],
+ "polygons": [
+ {
+ "coordinates": [
+ { "lat": 48.858844, "lng": 2.294351 },
+ { "lat": 48.853, "lng": 2.3499 },
+ { "lat": 48.8566, "lng": 2.3522 }
+ ],
+ "title": "Polygon 1",
+ "infoWindow": null
+ },
+ {
+ "coordinates": [
+ { "lat": 45.764043, "lng": 4.835659 },
+ { "lat": 45.750000, "lng": 4.850000 },
+ { "lat": 45.770000, "lng": 4.820000 }
+ ],
+ "title": "Polygon 2",
+ "infoWindow": {
+ "headerContent": "Polygon 2",
+ "content": "A polygon around Lyon with some additional info.",
+ "position": null,
+ "opened": false,
+ "autoClose": true
+ }
+ }
+ ]
+ }'>
+
`);
});
@@ -60,7 +123,7 @@ describe('AbstractMapController', () => {
clearDOM();
});
- it('connect and create map, marker and info window', async () => {
+ it('connect and create map, marker, polygon and info window', async () => {
const div = getByTestId(container, 'map');
expect(div).not.toHaveClass('connected');
@@ -73,12 +136,21 @@ describe('AbstractMapController', () => {
{ marker: 'marker', title: 'Paris' },
{ marker: 'marker', title: 'Lyon' },
]);
+ expect(controller.polygons).toEqual([
+ { polygon: 'polygon', title: 'Polygon 1' },
+ { polygon: 'polygon', title: 'Polygon 2' },
+ ]);
expect(controller.infoWindows).toEqual([
{
headerContent: 'Lyon',
infoWindow: 'infoWindow',
marker: 'Lyon',
},
+ {
+ headerContent: 'Polygon 2',
+ infoWindow: 'infoWindow',
+ polygon: 'Polygon 2',
+ },
]);
});
});
diff --git a/src/Map/doc/index.rst b/src/Map/doc/index.rst
index a6d70a80d68..be16614c0ed 100644
--- a/src/Map/doc/index.rst
+++ b/src/Map/doc/index.rst
@@ -118,8 +118,21 @@ A map is created by calling ``new Map()``. You can configure the center, zoom, a
),
)
;
-
- // 3. And inject the map in your template to render it
+
+ // 3. You can also add Polygons, which represents an area enclosed by a series of `Point` instances
+ $map->addPolygon(new Polygon(
+ points: [
+ new Point(48.8566, 2.3522),
+ new Point(45.7640, 4.8357),
+ new Point(43.2965, 5.3698),
+ new Point(44.8378, -0.5792),
+ ],
+ infoWindow: new InfoWindow(
+ content: 'Paris, Lyon, Marseille, Bordeaux',
+ ),
+ ));
+
+ // 4. And inject the map in your template to render it
return $this->render('contact/index.html.twig', [
'my_map' => $myMap,
]);
diff --git a/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts b/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts
index 8a2d2abca23..5095762fc07 100644
--- a/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts
+++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts
@@ -1,8 +1,8 @@
import AbstractMapController from '@symfony/ux-map';
-import type { Point, MarkerDefinition } from '@symfony/ux-map';
+import type { Point, MarkerDefinition, PolygonDefinition } from '@symfony/ux-map';
import type { LoaderOptions } from '@googlemaps/js-api-loader';
type MapOptions = Pick;
-export default class extends AbstractMapController {
+export default class extends AbstractMapController {
static values: {
providerOptions: ObjectConstructor;
};
@@ -15,9 +15,10 @@ export default class extends AbstractMapController): google.maps.marker.AdvancedMarkerElement;
- protected doCreateInfoWindow({ definition, marker, }: {
- definition: MarkerDefinition['infoWindow'];
- marker: google.maps.marker.AdvancedMarkerElement;
+ protected doCreatePolygon(definition: PolygonDefinition): google.maps.Polygon;
+ protected doCreateInfoWindow({ definition, element, }: {
+ definition: MarkerDefinition['infoWindow'] | PolygonDefinition['infoWindow'];
+ element: google.maps.marker.AdvancedMarkerElement | google.maps.Polygon;
}): google.maps.InfoWindow;
private createTextOrElement;
private closeInfoWindowsExcept;
diff --git a/src/Map/src/Bridge/Google/assets/dist/map_controller.js b/src/Map/src/Bridge/Google/assets/dist/map_controller.js
index d35e6512d3b..30fbe283118 100644
--- a/src/Map/src/Bridge/Google/assets/dist/map_controller.js
+++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.js
@@ -6,18 +6,21 @@ let default_1$1 = class default_1 extends Controller {
super(...arguments);
this.markers = [];
this.infoWindows = [];
+ this.polygons = [];
}
connect() {
- const { center, zoom, options, markers, fitBoundsToMarkers } = this.viewValue;
+ const { center, zoom, options, markers, polygons, fitBoundsToMarkers } = this.viewValue;
this.dispatchEvent('pre-connect', { options });
this.map = this.doCreateMap({ center, zoom, options });
markers.forEach((marker) => this.createMarker(marker));
+ polygons.forEach((polygon) => this.createPolygon(polygon));
if (fitBoundsToMarkers) {
this.doFitBoundsToMarkers();
}
this.dispatchEvent('connect', {
map: this.map,
markers: this.markers,
+ polygons: this.polygons,
infoWindows: this.infoWindows,
});
}
@@ -28,10 +31,17 @@ let default_1$1 = class default_1 extends Controller {
this.markers.push(marker);
return marker;
}
- createInfoWindow({ definition, marker, }) {
- this.dispatchEvent('info-window:before-create', { definition, marker });
- const infoWindow = this.doCreateInfoWindow({ definition, marker });
- this.dispatchEvent('info-window:after-create', { infoWindow, marker });
+ createPolygon(definition) {
+ this.dispatchEvent('polygon:before-create', { definition });
+ const polygon = this.doCreatePolygon(definition);
+ this.dispatchEvent('polygon:after-create', { polygon });
+ this.polygons.push(polygon);
+ return polygon;
+ }
+ createInfoWindow({ definition, element, }) {
+ this.dispatchEvent('info-window:before-create', { definition, element });
+ const infoWindow = this.doCreateInfoWindow({ definition, element });
+ this.dispatchEvent('info-window:after-create', { infoWindow, element });
this.infoWindows.push(infoWindow);
return infoWindow;
}
@@ -92,11 +102,26 @@ class default_1 extends default_1$1 {
map: this.map,
});
if (infoWindow) {
- this.createInfoWindow({ definition: infoWindow, marker });
+ this.createInfoWindow({ definition: infoWindow, element: marker });
}
return marker;
}
- doCreateInfoWindow({ definition, marker, }) {
+ doCreatePolygon(definition) {
+ const { points, title, infoWindow, rawOptions = {} } = definition;
+ const polygon = new _google.maps.Polygon({
+ ...rawOptions,
+ paths: points,
+ map: this.map,
+ });
+ if (title) {
+ polygon.set('title', title);
+ }
+ if (infoWindow) {
+ this.createInfoWindow({ definition: infoWindow, element: polygon });
+ }
+ return polygon;
+ }
+ doCreateInfoWindow({ definition, element, }) {
const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;
const infoWindow = new _google.maps.InfoWindow({
headerContent: this.createTextOrElement(headerContent),
@@ -104,22 +129,34 @@ class default_1 extends default_1$1 {
...otherOptions,
...rawOptions,
});
- if (definition.opened) {
- infoWindow.open({
- map: this.map,
- shouldFocus: false,
- anchor: marker,
+ if (element instanceof google.maps.marker.AdvancedMarkerElement) {
+ element.addListener('click', () => {
+ if (definition.autoClose) {
+ this.closeInfoWindowsExcept(infoWindow);
+ }
+ infoWindow.open({ map: this.map, anchor: element });
});
- }
- marker.addListener('click', () => {
- if (definition.autoClose) {
- this.closeInfoWindowsExcept(infoWindow);
+ if (definition.opened) {
+ infoWindow.open({ map: this.map, anchor: element });
}
- infoWindow.open({
- map: this.map,
- anchor: marker,
+ }
+ else if (element instanceof google.maps.Polygon) {
+ element.addListener('click', (event) => {
+ if (definition.autoClose) {
+ this.closeInfoWindowsExcept(infoWindow);
+ }
+ infoWindow.setPosition(event.latLng);
+ infoWindow.open(this.map);
});
- });
+ if (definition.opened) {
+ const bounds = new google.maps.LatLngBounds();
+ element.getPath().forEach((point) => {
+ bounds.extend(point);
+ });
+ infoWindow.setPosition(bounds.getCenter());
+ infoWindow.open({ map: this.map, anchor: element });
+ }
+ }
return infoWindow;
}
createTextOrElement(content) {
diff --git a/src/Map/src/Bridge/Google/assets/src/map_controller.ts b/src/Map/src/Bridge/Google/assets/src/map_controller.ts
index 7eed733fc9c..05116d80253 100644
--- a/src/Map/src/Bridge/Google/assets/src/map_controller.ts
+++ b/src/Map/src/Bridge/Google/assets/src/map_controller.ts
@@ -8,7 +8,7 @@
*/
import AbstractMapController from '@symfony/ux-map';
-import type { Point, MarkerDefinition } from '@symfony/ux-map';
+import type { Point, MarkerDefinition, PolygonDefinition } from '@symfony/ux-map';
import type { LoaderOptions } from '@googlemaps/js-api-loader';
import { Loader } from '@googlemaps/js-api-loader';
@@ -33,8 +33,12 @@ let _google: typeof google;
export default class extends AbstractMapController<
MapOptions,
google.maps.Map,
+ google.maps.marker.AdvancedMarkerElementOptions,
google.maps.marker.AdvancedMarkerElement,
- google.maps.InfoWindow
+ google.maps.InfoWindowOptions,
+ google.maps.InfoWindow,
+ google.maps.PolygonOptions,
+ google.maps.Polygon
> {
static values = {
providerOptions: Object,
@@ -121,21 +125,45 @@ export default class extends AbstractMapController<
});
if (infoWindow) {
- this.createInfoWindow({ definition: infoWindow, marker });
+ this.createInfoWindow({ definition: infoWindow, element: marker });
}
return marker;
}
+ protected doCreatePolygon(
+ definition: PolygonDefinition
+ ): google.maps.Polygon {
+ const { points, title, infoWindow, rawOptions = {} } = definition;
+
+ const polygon = new _google.maps.Polygon({
+ ...rawOptions,
+ paths: points,
+ map: this.map,
+ });
+
+ if (title) {
+ polygon.set('title', title);
+ }
+
+ if (infoWindow) {
+ this.createInfoWindow({ definition: infoWindow, element: polygon });
+ }
+
+ return polygon;
+ }
+
protected doCreateInfoWindow({
definition,
- marker,
+ element,
}: {
- definition: MarkerDefinition<
- google.maps.marker.AdvancedMarkerElementOptions,
- google.maps.InfoWindowOptions
- >['infoWindow'];
- marker: google.maps.marker.AdvancedMarkerElement;
+ definition:
+ | MarkerDefinition<
+ google.maps.marker.AdvancedMarkerElementOptions,
+ google.maps.InfoWindowOptions
+ >['infoWindow']
+ | PolygonDefinition['infoWindow'];
+ element: google.maps.marker.AdvancedMarkerElement | google.maps.Polygon;
}): google.maps.InfoWindow {
const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;
@@ -146,24 +174,35 @@ export default class extends AbstractMapController<
...rawOptions,
});
- if (definition.opened) {
- infoWindow.open({
- map: this.map,
- shouldFocus: false,
- anchor: marker,
+ if (element instanceof google.maps.marker.AdvancedMarkerElement) {
+ element.addListener('click', () => {
+ if (definition.autoClose) {
+ this.closeInfoWindowsExcept(infoWindow);
+ }
+ infoWindow.open({ map: this.map, anchor: element });
});
- }
- marker.addListener('click', () => {
- if (definition.autoClose) {
- this.closeInfoWindowsExcept(infoWindow);
+ if (definition.opened) {
+ infoWindow.open({ map: this.map, anchor: element });
}
-
- infoWindow.open({
- map: this.map,
- anchor: marker,
+ } else if (element instanceof google.maps.Polygon) {
+ element.addListener('click', (event: any) => {
+ if (definition.autoClose) {
+ this.closeInfoWindowsExcept(infoWindow);
+ }
+ infoWindow.setPosition(event.latLng);
+ infoWindow.open(this.map);
});
- });
+
+ if (definition.opened) {
+ const bounds = new google.maps.LatLngBounds();
+ element.getPath().forEach((point: google.maps.LatLng) => {
+ bounds.extend(point);
+ });
+ infoWindow.setPosition(bounds.getCenter());
+ infoWindow.open({ map: this.map, anchor: element });
+ }
+ }
return infoWindow;
}
diff --git a/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts b/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts
index 1db8edfff7b..f1b08abba5c 100644
--- a/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts
+++ b/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts
@@ -41,7 +41,7 @@ describe('GoogleMapsController', () => {
data-controller="check google"
style="height: 700px; margin: 10px"
data-google-provider-options-value="{"version":"weekly","libraries":["maps","marker"],"apiKey":""}"
- data-google-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":4,"fitBoundsToMarkers":true,"options":{"mapId":"YOUR_MAP_ID","gestureHandling":"auto","backgroundColor":null,"disableDoubleClickZoom":false,"zoomControl":true,"zoomControlOptions":{"position":22},"mapTypeControl":true,"mapTypeControlOptions":{"mapTypeIds":[],"position":14,"style":0},"streetViewControl":true,"streetViewControlOptions":{"position":22},"fullscreenControl":true,"fullscreenControlOptions":{"position":20}},"markers":[{"position":{"lat":48.8566,"lng":2.3522},"title":"Paris","infoWindow":null},{"position":{"lat":45.764,"lng":4.8357},"title":"Lyon","infoWindow":{"headerContent":"<b>Lyon<\/b>","content":"The French town in the historic Rh\u00f4ne-Alpes region, located at the junction of the Rh\u00f4ne and Sa\u00f4ne rivers.","position":null,"opened":false,"autoClose":true}}]}"
+ data-google-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":4,"fitBoundsToMarkers":true,"options":{"mapId":"YOUR_MAP_ID","gestureHandling":"auto","backgroundColor":null,"disableDoubleClickZoom":false,"zoomControl":true,"zoomControlOptions":{"position":22},"mapTypeControl":true,"mapTypeControlOptions":{"mapTypeIds":[],"position":14,"style":0},"streetViewControl":true,"streetViewControlOptions":{"position":22},"fullscreenControl":true,"fullscreenControlOptions":{"position":20}},"markers":[{"position":{"lat":48.8566,"lng":2.3522},"title":"Paris","infoWindow":null},{"position":{"lat":45.764,"lng":4.8357},"title":"Lyon","infoWindow":{"headerContent":"<b>Lyon<\/b>","content":"The French town in the historic Rh\u00f4ne-Alpes region, located at the junction of the Rh\u00f4ne and Sa\u00f4ne rivers.","position":null,"opened":false,"autoClose":true}}],"polygons":[]}"
>
`);
});
diff --git a/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
index db011e30998..32ca96df600 100644
--- a/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
+++ b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
@@ -29,26 +29,26 @@ public function provideTestRenderMap(): iterable
->zoom(12);
yield 'simple map, with minimum options' => [
- 'expected_render' => '',
+ 'expected_render' => '',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
'map' => $map,
];
yield 'with every options' => [
- 'expected_render' => '',
+ 'expected_render' => '',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key', id: 'gmap', language: 'fr', region: 'FR', nonce: 'abcd', retries: 10, url: 'https://maps.googleapis.com/maps/api/js', version: 'quarterly'),
'map' => $map,
];
yield 'with custom attributes' => [
- 'expected_render' => '',
+ 'expected_render' => '',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
'map' => $map,
'attributes' => ['data-controller' => 'my-custom-controller', 'class' => 'map'],
];
yield 'with markers and infoWindows' => [
- 'expected_render' => '',
+ 'expected_render' => '',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
'map' => (clone $map)
->addMarker(new Marker(new Point(48.8566, 2.3522), 'Paris'))
@@ -56,7 +56,7 @@ public function provideTestRenderMap(): iterable
];
yield 'with controls enabled' => [
- 'expected_render' => '',
+ 'expected_render' => '',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
'map' => (clone $map)
->options(new GoogleOptions(
@@ -68,7 +68,7 @@ public function provideTestRenderMap(): iterable
];
yield 'without controls enabled' => [
- 'expected_render' => '',
+ 'expected_render' => '',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
'map' => (clone $map)
->options(new GoogleOptions(
diff --git a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
index 04e533b4bd9..6b32a8df45b 100644
--- a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
+++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
@@ -1,8 +1,8 @@
import AbstractMapController from '@symfony/ux-map';
-import type { Point, MarkerDefinition } from '@symfony/ux-map';
+import type { Point, MarkerDefinition, PolygonDefinition } from '@symfony/ux-map';
import 'leaflet/dist/leaflet.min.css';
import * as L from 'leaflet';
-import type { MapOptions as LeafletMapOptions, MarkerOptions, PopupOptions } from 'leaflet';
+import type { MapOptions as LeafletMapOptions, MarkerOptions, PopupOptions, PolygonOptions } from 'leaflet';
type MapOptions = Pick & {
tileLayer: {
url: string;
@@ -10,7 +10,7 @@ type MapOptions = Pick & {
options: Record;
};
};
-export default class extends AbstractMapController {
+export default class extends AbstractMapController {
connect(): void;
protected dispatchEvent(name: string, payload?: Record): void;
protected doCreateMap({ center, zoom, options, }: {
@@ -19,9 +19,10 @@ export default class extends AbstractMapController this.createMarker(marker));
+ polygons.forEach((polygon) => this.createPolygon(polygon));
if (fitBoundsToMarkers) {
this.doFitBoundsToMarkers();
}
this.dispatchEvent('connect', {
map: this.map,
markers: this.markers,
+ polygons: this.polygons,
infoWindows: this.infoWindows,
});
}
@@ -29,10 +32,17 @@ class default_1 extends Controller {
this.markers.push(marker);
return marker;
}
- createInfoWindow({ definition, marker, }) {
- this.dispatchEvent('info-window:before-create', { definition, marker });
- const infoWindow = this.doCreateInfoWindow({ definition, marker });
- this.dispatchEvent('info-window:after-create', { infoWindow, marker });
+ createPolygon(definition) {
+ this.dispatchEvent('polygon:before-create', { definition });
+ const polygon = this.doCreatePolygon(definition);
+ this.dispatchEvent('polygon:after-create', { polygon });
+ this.polygons.push(polygon);
+ return polygon;
+ }
+ createInfoWindow({ definition, element, }) {
+ this.dispatchEvent('info-window:before-create', { definition, element });
+ const infoWindow = this.doCreateInfoWindow({ definition, element });
+ this.dispatchEvent('info-window:after-create', { infoWindow, element });
this.infoWindows.push(infoWindow);
return infoWindow;
}
@@ -78,19 +88,30 @@ class map_controller extends default_1 {
const { position, title, infoWindow, extra, rawOptions = {}, ...otherOptions } = definition;
const marker = L.marker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map);
if (infoWindow) {
- this.createInfoWindow({ definition: infoWindow, marker });
+ this.createInfoWindow({ definition: infoWindow, element: marker });
}
return marker;
}
- doCreateInfoWindow({ definition, marker, }) {
- const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;
- marker.bindPopup([headerContent, content].filter((x) => x).join('
'), { ...otherOptions, ...rawOptions });
+ doCreatePolygon(definition) {
+ const { points, title, infoWindow, rawOptions = {} } = definition;
+ const polygon = L.polygon(points, { ...rawOptions }).addTo(this.map);
+ if (title) {
+ polygon.bindPopup(title);
+ }
+ if (infoWindow) {
+ this.createInfoWindow({ definition: infoWindow, element: polygon });
+ }
+ return polygon;
+ }
+ doCreateInfoWindow({ definition, element, }) {
+ const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
+ element.bindPopup([headerContent, content].filter((x) => x).join('
'), { ...otherOptions, ...rawOptions });
if (definition.opened) {
- marker.openPopup();
+ element.openPopup();
}
- const popup = marker.getPopup();
+ const popup = element.getPopup();
if (!popup) {
- throw new Error('Unable to get the Popup associated to the Marker, this should not happens.');
+ throw new Error('Unable to get the Popup associated with the element.');
}
return popup;
}
diff --git a/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts b/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
index 3e342b77b60..12ed1f2922f 100644
--- a/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
+++ b/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
@@ -1,8 +1,8 @@
import AbstractMapController from '@symfony/ux-map';
-import type { Point, MarkerDefinition } from '@symfony/ux-map';
+import type { Point, MarkerDefinition, PolygonDefinition } from '@symfony/ux-map';
import 'leaflet/dist/leaflet.min.css';
import * as L from 'leaflet';
-import type { MapOptions as LeafletMapOptions, MarkerOptions, PopupOptions } from 'leaflet';
+import type { MapOptions as LeafletMapOptions, MarkerOptions, PopupOptions, PolygonOptions } from 'leaflet';
type MapOptions = Pick & {
tileLayer: { url: string; attribution: string; options: Record };
@@ -13,8 +13,10 @@ export default class extends AbstractMapController<
typeof L.Map,
MarkerOptions,
typeof L.Marker,
+ PopupOptions,
typeof L.Popup,
- PopupOptions
+ PolygonOptions,
+ typeof L.Polygon
> {
connect(): void {
L.Marker.prototype.options.icon = L.divIcon({
@@ -63,30 +65,48 @@ export default class extends AbstractMapController<
const marker = L.marker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map);
if (infoWindow) {
- this.createInfoWindow({ definition: infoWindow, marker });
+ this.createInfoWindow({ definition: infoWindow, element: marker });
}
return marker;
}
+ protected doCreatePolygon(definition: PolygonDefinition): L.Polygon {
+ const { points, title, infoWindow, rawOptions = {} } = definition;
+
+ const polygon = L.polygon(points, { ...rawOptions }).addTo(this.map);
+
+ if (title) {
+ polygon.bindPopup(title);
+ }
+
+ if (infoWindow) {
+ this.createInfoWindow({ definition: infoWindow, element: polygon });
+ }
+
+ return polygon;
+ }
+
protected doCreateInfoWindow({
definition,
- marker,
+ element,
}: {
- definition: MarkerDefinition['infoWindow'];
- marker: L.Marker;
+ definition: MarkerDefinition['infoWindow'] | PolygonDefinition['infoWindow'];
+ element: L.Marker | L.Polygon;
}): L.Popup {
- const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;
+ const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
+
+ element.bindPopup([headerContent, content].filter((x) => x).join('
'), { ...otherOptions, ...rawOptions });
- marker.bindPopup([headerContent, content].filter((x) => x).join('
'), { ...otherOptions, ...rawOptions });
if (definition.opened) {
- marker.openPopup();
+ element.openPopup();
}
- const popup = marker.getPopup();
+ const popup = element.getPopup();
if (!popup) {
- throw new Error('Unable to get the Popup associated to the Marker, this should not happens.');
+ throw new Error('Unable to get the Popup associated with the element.');
}
+
return popup;
}
diff --git a/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts b/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts
index e6aa9276e27..5a51bf5f8a0 100644
--- a/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts
+++ b/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts
@@ -36,12 +36,12 @@ describe('LeafletController', () => {
beforeEach(() => {
container = mountDOM(`
-
`);
});
diff --git a/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php b/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
index 6931f53abf6..d9ad391ca15 100644
--- a/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
+++ b/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
@@ -28,20 +28,20 @@ public function provideTestRenderMap(): iterable
->zoom(12);
yield 'simple map' => [
- 'expected_render' => '',
+ 'expected_render' => '',
'renderer' => new LeafletRenderer(new StimulusHelper(null)),
'map' => $map,
];
yield 'with custom attributes' => [
- 'expected_render' => '',
+ 'expected_render' => '',
'renderer' => new LeafletRenderer(new StimulusHelper(null)),
'map' => $map,
'attributes' => ['data-controller' => 'my-custom-controller', 'class' => 'map'],
];
yield 'with markers and infoWindows' => [
- 'expected_render' => '',
+ 'expected_render' => '',
'renderer' => new LeafletRenderer(new StimulusHelper(null)),
'map' => (clone $map)
->addMarker(new Marker(new Point(48.8566, 2.3522), 'Paris'))
diff --git a/src/Map/src/Map.php b/src/Map/src/Map.php
index d8fdf005e56..3ab240ae1e2 100644
--- a/src/Map/src/Map.php
+++ b/src/Map/src/Map.php
@@ -30,6 +30,11 @@ public function __construct(
* @var array
*/
private array $markers = [],
+
+ /**
+ * @var array
+ */
+ private array $polygons = [],
) {
}
@@ -83,6 +88,13 @@ public function addMarker(Marker $marker): self
return $this;
}
+ public function addPolygon(Polygon $polygon): self
+ {
+ $this->polygons[] = $polygon;
+
+ return $this;
+ }
+
public function toArray(): array
{
if (!$this->fitBoundsToMarkers) {
@@ -101,6 +113,7 @@ public function toArray(): array
'fitBoundsToMarkers' => $this->fitBoundsToMarkers,
'options' => (object) ($this->options?->toArray() ?? []),
'markers' => array_map(static fn (Marker $marker) => $marker->toArray(), $this->markers),
+ 'polygons' => array_map(static fn (Polygon $polygon) => $polygon->toArray(), $this->polygons),
];
}
@@ -109,6 +122,7 @@ public function toArray(): array
* center?: array{lat: float, lng: float},
* zoom?: float,
* markers?: list,
+ * polygons?: list,
* fitBoundsToMarkers?: bool,
* options?: object,
* } $map
@@ -133,6 +147,12 @@ public static function fromArray(array $map): self
}
$map['markers'] = array_map(Marker::fromArray(...), $map['markers']);
+ $map['polygons'] ??= [];
+ if (!\is_array($map['polygons'])) {
+ throw new InvalidArgumentException('The "polygons" parameter must be an array.');
+ }
+ $map['polygons'] = array_map(Polygon::fromArray(...), $map['polygons']);
+
return new self(...$map);
}
}
diff --git a/src/Map/src/Polygon.php b/src/Map/src/Polygon.php
new file mode 100644
index 00000000000..5d474346e7d
--- /dev/null
+++ b/src/Map/src/Polygon.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map;
+
+use Symfony\UX\Map\Exception\InvalidArgumentException;
+
+/**
+ * Represents a polygon on a map.
+ *
+ * @author [Pierre Svgnt]
+ */
+final readonly class Polygon
+{
+ /**
+ * @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 array $points,
+ private ?string $title = null,
+ private ?InfoWindow $infoWindow = null,
+ private array $extra = [],
+ ) {
+ }
+
+ /**
+ * Convert the polygon to an array representation.
+ */
+ public function toArray(): array
+ {
+ return [
+ 'points' => array_map(fn (Point $point) => $point->toArray(), $this->points),
+ 'title' => $this->title,
+ 'infoWindow' => $this->infoWindow?->toArray(),
+ 'extra' => (object) $this->extra,
+ ];
+ }
+
+ /**
+ * @param array{
+ * points: array,
+ * title: string|null,
+ * infoWindow: array|null,
+ * extra: object,
+ * } $polygon
+ *
+ * @internal
+ */
+ 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']);
+
+ if (isset($polygon['infoWindow'])) {
+ $polygon['infoWindow'] = InfoWindow::fromArray($polygon['infoWindow']);
+ }
+
+ return new self(...$polygon);
+ }
+}
diff --git a/src/Map/src/Twig/MapRuntime.php b/src/Map/src/Twig/MapRuntime.php
index 62e50be86da..cfb47560bd2 100644
--- a/src/Map/src/Twig/MapRuntime.php
+++ b/src/Map/src/Twig/MapRuntime.php
@@ -14,6 +14,7 @@
use Symfony\UX\Map\Map;
use Symfony\UX\Map\Marker;
use Symfony\UX\Map\Point;
+use Symfony\UX\Map\Polygon;
use Symfony\UX\Map\Renderer\RendererInterface;
use Twig\Extension\RuntimeExtensionInterface;
@@ -32,11 +33,13 @@ public function __construct(
/**
* @param array $attributes
* @param array $markers
+ * @param array $polygons
*/
public function renderMap(
?Map $map = null,
array $attributes = [],
?array $markers = null,
+ ?array $polygons = null,
?array $center = null,
?float $zoom = null,
): string {
@@ -52,6 +55,9 @@ public function renderMap(
foreach ($markers ?? [] as $marker) {
$map->addMarker(Marker::fromArray($marker));
}
+ foreach ($polygons ?? [] as $polygons) {
+ $map->addPolygon(Polygon::fromArray($polygons));
+ }
if (null !== $center) {
$map->center(Point::fromArray($center));
}
diff --git a/src/Map/src/Twig/UXMapComponent.php b/src/Map/src/Twig/UXMapComponent.php
index 94cb6407e8f..39e362b34b9 100644
--- a/src/Map/src/Twig/UXMapComponent.php
+++ b/src/Map/src/Twig/UXMapComponent.php
@@ -13,6 +13,7 @@
use Symfony\UX\Map\Marker;
use Symfony\UX\Map\Point;
+use Symfony\UX\Map\Polygon;
/**
* @author Simon André
@@ -29,4 +30,9 @@ final class UXMapComponent
* @var Marker[]
*/
public array $markers;
+
+ /**
+ * @var Polygon[]
+ */
+ public array $polygons;
}
diff --git a/src/Map/src/Twig/UXMapComponentListener.php b/src/Map/src/Twig/UXMapComponentListener.php
index 3a36e5587da..51034c53b4d 100644
--- a/src/Map/src/Twig/UXMapComponentListener.php
+++ b/src/Map/src/Twig/UXMapComponentListener.php
@@ -32,7 +32,7 @@ public function onPreCreateForRender(PreCreateForRenderEvent $event): void
}
$attributes = $event->getInputProps();
- $map = array_intersect_key($attributes, ['markers' => 0, 'center' => 1, 'zoom' => 2]);
+ $map = array_intersect_key($attributes, ['markers' => 0, 'polygons' => 0, 'center' => 1, 'zoom' => 2]);
$attributes = array_diff_key($attributes, $map);
$html = $this->mapRuntime->renderMap(...$map, attributes: $attributes);
diff --git a/src/Map/tests/MapFactoryTest.php b/src/Map/tests/MapFactoryTest.php
index a19e2d7996c..fcff3b0539c 100644
--- a/src/Map/tests/MapFactoryTest.php
+++ b/src/Map/tests/MapFactoryTest.php
@@ -32,6 +32,13 @@ public function testFromArray(): void
$this->assertSame($array['markers'][0]['title'], $markers[0]['title']);
$this->assertSame($array['markers'][0]['infoWindow']['headerContent'], $markers[0]['infoWindow']['headerContent']);
$this->assertSame($array['markers'][0]['infoWindow']['content'], $markers[0]['infoWindow']['content']);
+
+ $this->assertCount(1, $polygons = $map->toArray()['polygons']);
+ $this->assertEquals($array['polygons'][0]['points'], $polygons[0]['points']);
+ $this->assertEquals($array['polygons'][0]['points'], $polygons[0]['points']);
+ $this->assertSame($array['polygons'][0]['title'], $polygons[0]['title']);
+ $this->assertSame($array['polygons'][0]['infoWindow']['headerContent'], $polygons[0]['infoWindow']['headerContent']);
+ $this->assertSame($array['polygons'][0]['infoWindow']['content'], $polygons[0]['infoWindow']['content']);
}
public function testFromArrayWithInvalidCenter(): void
@@ -76,6 +83,30 @@ public function testFromArrayWithInvalidMarker(): void
Map::fromArray($array);
}
+ public function testFromArrayWithInvalidPolygons(): void
+ {
+ $array = self::createMapArray();
+ $array['polygons'] = 'invalid';
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The "polygons" parameter must be an array.');
+ Map::fromArray($array);
+ }
+
+ public function testFromArrayWithInvalidPolygon(): void
+ {
+ $array = self::createMapArray();
+ $array['polygons'] = [
+ [
+ 'invalid',
+ ],
+ ];
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The "points" parameter is required.');
+ Map::fromArray($array);
+ }
+
private static function createMapArray(): array
{
return [
@@ -97,6 +128,29 @@ private static function createMapArray(): array
],
],
],
+ 'polygons' => [
+ [
+ 'points' => [
+ [
+ 'lat' => 48.858844,
+ 'lng' => 2.294351,
+ ],
+ [
+ 'lat' => 48.853,
+ 'lng' => 2.3499,
+ ],
+ [
+ 'lat' => 48.8566,
+ 'lng' => 2.3522,
+ ],
+ ],
+ 'title' => 'Polygon 1',
+ 'infoWindow' => [
+ 'headerContent' => 'Polygon 1',
+ 'content' => 'Polygon 1',
+ ],
+ ],
+ ],
];
}
}
diff --git a/src/Map/tests/MapTest.php b/src/Map/tests/MapTest.php
index 99a4f1ebc37..95703724466 100644
--- a/src/Map/tests/MapTest.php
+++ b/src/Map/tests/MapTest.php
@@ -18,6 +18,7 @@
use Symfony\UX\Map\MapOptionsInterface;
use Symfony\UX\Map\Marker;
use Symfony\UX\Map\Point;
+use Symfony\UX\Map\Polygon;
class MapTest extends TestCase
{
@@ -55,6 +56,7 @@ public function testZoomAndCenterCanBeOmittedIfFitBoundsToMarkers(): void
'fitBoundsToMarkers' => true,
'options' => $array['options'],
'markers' => [],
+ 'polygons' => [],
], $array);
}
@@ -73,6 +75,7 @@ public function testWithMinimumConfiguration(): void
'fitBoundsToMarkers' => false,
'options' => $array['options'],
'markers' => [],
+ 'polygons' => [],
], $array);
}
@@ -105,11 +108,36 @@ public function toArray(): array
position: new Point(43.2965, 5.3698),
title: 'Marseille',
infoWindow: new InfoWindow(headerContent: 'Marseille', content: 'Marseille', position: new Point(43.2965, 5.3698), opened: true)
- ));
+ ))
+ ->addPolygon(new Polygon(
+ points: [
+ new Point(48.858844, 2.294351),
+ new Point(48.853, 2.3499),
+ new Point(48.8566, 2.3522),
+ ],
+ title: 'Polygon 1',
+ infoWindow: null,
+ ))
+ ->addPolygon(new Polygon(
+ points: [
+ new Point(45.764043, 4.835659),
+ new Point(45.75, 4.85),
+ new Point(45.77, 4.82),
+ ],
+ title: 'Polygon 2',
+ infoWindow: new InfoWindow(
+ headerContent: 'Polygon 2',
+ content: 'A polygon around Lyon with some additional info.',
+ position: new Point(45.764, 4.8357),
+ opened: true,
+ autoClose: true,
+ ),
+ ))
+ ;
$array = $map->toArray();
- self::assertSame([
+ self::assertEquals([
'center' => ['lat' => 48.8566, 'lng' => 2.3522],
'zoom' => 6.0,
'fitBoundsToMarkers' => true,
@@ -155,6 +183,35 @@ public function toArray(): array
'extra' => $array['markers'][2]['extra'],
],
],
+ 'polygons' => [
+ [
+ 'points' => [
+ ['lat' => 48.858844, 'lng' => 2.294351],
+ ['lat' => 48.853, 'lng' => 2.3499],
+ ['lat' => 48.8566, 'lng' => 2.3522],
+ ],
+ 'title' => 'Polygon 1',
+ 'infoWindow' => null,
+ 'extra' => $array['polygons'][0]['extra'],
+ ],
+ [
+ 'points' => [
+ ['lat' => 45.764043, 'lng' => 4.835659],
+ ['lat' => 45.75, 'lng' => 4.85],
+ ['lat' => 45.77, 'lng' => 4.82],
+ ],
+ 'title' => 'Polygon 2',
+ 'infoWindow' => [
+ 'headerContent' => 'Polygon 2',
+ 'content' => 'A polygon around Lyon with some additional info.',
+ 'position' => ['lat' => 45.764, 'lng' => 4.8357],
+ 'opened' => true,
+ 'autoClose' => true,
+ 'extra' => $array['polygons'][1]['infoWindow']['extra'],
+ ],
+ 'extra' => $array['polygons'][1]['extra'],
+ ],
+ ],
], $array);
self::assertSame('roadmap', $array['options']->mapTypeId);