Skip to content

[Map] Add Rectangle support #2845

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 4 commits into from
Jun 22, 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
10 changes: 10 additions & 0 deletions src/Map/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# CHANGELOG

## 2.29

- Add support for creating `Rectangle` by passing two `Point` instances to the `Rectangle` constructor, e.g.:
```php
$map->addRectangle(new Rectangle(
southWest: new Point(48.856613, 2.352222), // Paris
northEast: new Point(48.51238 2.21080) // Gare de Lyon (Paris)
));
```

## 2.28

- Add support for creating `Circle` by passing a `Point` and a radius (in meters) to the `Circle` constructor, e.g.:
Expand Down
24 changes: 21 additions & 3 deletions src/Map/assets/dist/abstract_map_controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ export type CircleDefinition<CircleOptions, InfoWindowOptions> = WithIdentifier<
rawOptions?: CircleOptions;
extra: Record<string, unknown>;
}>;
export type RectangleDefinition<RectangleOptions, InfoWindowOptions> = WithIdentifier<{
infoWindow?: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
southWest: Point;
northEast: Point;
title: string | null;
rawOptions?: RectangleOptions;
extra: Record<string, unknown>;
}>;
export type InfoWindowDefinition<InfoWindowOptions> = {
headerContent: string | null;
content: string | null;
Expand All @@ -66,7 +74,7 @@ export type InfoWindowDefinition<InfoWindowOptions> = {
extra: Record<string, unknown>;
};
export type InfoWindowWithoutPositionDefinition<InfoWindowOptions> = Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow, PolygonOptions, Polygon, PolylineOptions, Polyline, CircleOptions, Circle> extends Controller<HTMLElement> {
export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow, PolygonOptions, Polygon, PolylineOptions, Polyline, CircleOptions, Circle, RectangleOptions, Rectangle> extends Controller<HTMLElement> {
static values: {
providerOptions: ObjectConstructor;
center: ObjectConstructor;
Expand All @@ -76,6 +84,7 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
polygons: ArrayConstructor;
polylines: ArrayConstructor;
circles: ArrayConstructor;
rectangles: ArrayConstructor;
options: ObjectConstructor;
};
centerValue: Point | null;
Expand All @@ -85,6 +94,7 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
polygonsValue: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
polylinesValue: Array<PolylineDefinition<PolylineOptions, InfoWindowOptions>>;
circlesValue: Array<CircleDefinition<CircleOptions, InfoWindowOptions>>;
rectanglesValue: Array<RectangleDefinition<RectangleOptions, InfoWindowOptions>>;
optionsValue: MapOptions;
hasCenterValue: boolean;
hasZoomValue: boolean;
Expand All @@ -93,30 +103,34 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
hasPolygonsValue: boolean;
hasPolylinesValue: boolean;
hasCirclesValue: boolean;
hasRectanglesValue: boolean;
hasOptionsValue: boolean;
protected map: Map;
protected markers: globalThis.Map<string, Marker>;
protected polygons: globalThis.Map<string, Polygon>;
protected polylines: globalThis.Map<string, Polyline>;
protected circles: globalThis.Map<string, Circle>;
protected rectangles: globalThis.Map<string, Rectangle>;
protected infoWindows: Array<InfoWindow>;
private isConnected;
private createMarker;
private createPolygon;
private createPolyline;
private createCircle;
private createRectangle;
protected abstract dispatchEvent(name: string, payload: Record<string, unknown>): void;
connect(): void;
createInfoWindow({ definition, element, }: {
definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
element: Marker | Polygon | Polyline | Circle;
element: Marker | Polygon | Polyline | Circle | Rectangle;
}): InfoWindow;
abstract centerValueChanged(): void;
abstract zoomValueChanged(): void;
markersValueChanged(): void;
polygonsValueChanged(): void;
polylinesValueChanged(): void;
circlesValueChanged(): void;
rectanglesValueChanged(): void;
protected abstract doCreateMap({ center, zoom, options, }: {
center: Point | null;
zoom: number | null;
Expand All @@ -139,9 +153,13 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
definition: CircleDefinition<CircleOptions, InfoWindowOptions>;
}): Circle;
protected abstract doRemoveCircle(circle: Circle): void;
protected abstract doCreateRectangle({ definition, }: {
definition: RectangleDefinition<RectangleOptions, InfoWindowOptions>;
}): Rectangle;
protected abstract doRemoveRectangle(rectangle: Rectangle): void;
protected abstract doCreateInfoWindow({ definition, element, }: {
definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
element: Marker | Polygon | Polyline | Circle;
element: Marker | Polygon | Polyline | Circle | Rectangle;
}): InfoWindow;
protected abstract doCreateIcon({ definition, element, }: {
definition: Icon;
Expand Down
11 changes: 11 additions & 0 deletions src/Map/assets/dist/abstract_map_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class default_1 extends Controller {
this.polygons = new Map();
this.polylines = new Map();
this.circles = new Map();
this.rectangles = new Map();
this.infoWindows = [];
this.isConnected = false;
}
Expand All @@ -22,6 +23,7 @@ class default_1 extends Controller {
this.createPolygon = this.createDrawingFactory('polygon', this.polygons, this.doCreatePolygon.bind(this));
this.createPolyline = this.createDrawingFactory('polyline', this.polylines, this.doCreatePolyline.bind(this));
this.createCircle = this.createDrawingFactory('circle', this.circles, this.doCreateCircle.bind(this));
this.createRectangle = this.createDrawingFactory('rectangle', this.rectangles, this.doCreateRectangle.bind(this));
this.map = this.doCreateMap({
center: this.hasCenterValue ? this.centerValue : null,
zoom: this.hasZoomValue ? this.zoomValue : null,
Expand All @@ -31,6 +33,7 @@ class default_1 extends Controller {
this.polygonsValue.forEach((definition) => this.createPolygon({ definition }));
this.polylinesValue.forEach((definition) => this.createPolyline({ definition }));
this.circlesValue.forEach((definition) => this.createCircle({ definition }));
this.rectanglesValue.forEach((definition) => this.createRectangle({ definition }));
if (this.fitBoundsToMarkersValue) {
this.doFitBoundsToMarkers();
}
Expand All @@ -40,6 +43,7 @@ class default_1 extends Controller {
polygons: [...this.polygons.values()],
polylines: [...this.polylines.values()],
circles: [...this.circles.values()],
rectangles: [...this.rectangles.values()],
infoWindows: this.infoWindows,
});
this.isConnected = true;
Expand Down Expand Up @@ -78,6 +82,12 @@ class default_1 extends Controller {
}
this.onDrawChanged(this.circles, this.circlesValue, this.createCircle, this.doRemoveCircle);
}
rectanglesValueChanged() {
if (!this.isConnected) {
return;
}
this.onDrawChanged(this.rectangles, this.rectanglesValue, this.createRectangle, this.doRemoveRectangle);
}
createDrawingFactory(type, draws, factory) {
const eventBefore = `${type}:before-create`;
const eventAfter = `${type}:after-create`;
Expand Down Expand Up @@ -115,6 +125,7 @@ default_1.values = {
polygons: Array,
polylines: Array,
circles: Array,
rectangles: Array,
options: Object,
};

Expand Down
70 changes: 66 additions & 4 deletions src/Map/assets/src/abstract_map_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,24 @@ export type CircleDefinition<CircleOptions, InfoWindowOptions> = WithIdentifier<
extra: Record<string, unknown>;
}>;

export type RectangleDefinition<RectangleOptions, InfoWindowOptions> = WithIdentifier<{
infoWindow?: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
southWest: Point;
northEast: Point;
title: string | null;
/**
* Raw options passed to the rectangle constructor, specific to the map provider (e.g.: `L.rectangle()` for Leaflet).
*/
rawOptions?: RectangleOptions;
/**
* Extra data defined by the developer.
* They are not directly used by the Stimulus controller, but they can be used by the developer with event listeners:
* - `ux:map:rectangle:before-create`
* - `ux:map:rectangle:after-create`
*/
extra: Record<string, unknown>;
}>;

export type InfoWindowDefinition<InfoWindowOptions> = {
headerContent: string | null;
content: string | null;
Expand Down Expand Up @@ -136,6 +154,8 @@ export default abstract class<
Polyline,
CircleOptions,
Circle,
RectangleOptions,
Rectangle,
> extends Controller<HTMLElement> {
static values = {
providerOptions: Object,
Expand All @@ -146,6 +166,7 @@ export default abstract class<
polygons: Array,
polylines: Array,
circles: Array,
rectangles: Array,
options: Object,
};

Expand All @@ -156,6 +177,7 @@ export default abstract class<
declare polygonsValue: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
declare polylinesValue: Array<PolylineDefinition<PolylineOptions, InfoWindowOptions>>;
declare circlesValue: Array<CircleDefinition<CircleOptions, InfoWindowOptions>>;
declare rectanglesValue: Array<RectangleDefinition<RectangleOptions, InfoWindowOptions>>;
declare optionsValue: MapOptions;

declare hasCenterValue: boolean;
Expand All @@ -165,13 +187,15 @@ export default abstract class<
declare hasPolygonsValue: boolean;
declare hasPolylinesValue: boolean;
declare hasCirclesValue: boolean;
declare hasRectanglesValue: boolean;
declare hasOptionsValue: boolean;

protected map: Map;
protected markers = new Map<Identifier, Marker>();
protected polygons = new Map<Identifier, Polygon>();
protected polylines = new Map<Identifier, Polyline>();
protected circles = new Map<Identifier, Circle>();
protected rectangles = new Map<Identifier, Rectangle>();
protected infoWindows: Array<InfoWindow> = [];

private isConnected = false;
Expand All @@ -187,6 +211,9 @@ export default abstract class<
private createCircle: ({
definition,
}: { definition: CircleDefinition<CircleOptions, InfoWindowOptions> }) => Circle;
private createRectangle: ({
definition,
}: { definition: RectangleDefinition<RectangleOptions, InfoWindowOptions> }) => Rectangle;

protected abstract dispatchEvent(name: string, payload: Record<string, unknown>): void;

Expand All @@ -199,6 +226,11 @@ export default abstract class<
this.createPolygon = this.createDrawingFactory('polygon', this.polygons, this.doCreatePolygon.bind(this));
this.createPolyline = this.createDrawingFactory('polyline', this.polylines, this.doCreatePolyline.bind(this));
this.createCircle = this.createDrawingFactory('circle', this.circles, this.doCreateCircle.bind(this));
this.createRectangle = this.createDrawingFactory(
'rectangle',
this.rectangles,
this.doCreateRectangle.bind(this)
);

this.map = this.doCreateMap({
center: this.hasCenterValue ? this.centerValue : null,
Expand All @@ -209,6 +241,7 @@ export default abstract class<
this.polygonsValue.forEach((definition) => this.createPolygon({ definition }));
this.polylinesValue.forEach((definition) => this.createPolyline({ definition }));
this.circlesValue.forEach((definition) => this.createCircle({ definition }));
this.rectanglesValue.forEach((definition) => this.createRectangle({ definition }));

if (this.fitBoundsToMarkersValue) {
this.doFitBoundsToMarkers();
Expand All @@ -220,6 +253,7 @@ export default abstract class<
polygons: [...this.polygons.values()],
polylines: [...this.polylines.values()],
circles: [...this.circles.values()],
rectangles: [...this.rectangles.values()],
infoWindows: this.infoWindows,
});

Expand All @@ -232,7 +266,7 @@ export default abstract class<
element,
}: {
definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
element: Marker | Polygon | Polyline | Circle;
element: Marker | Polygon | Polyline | Circle | Rectangle;
}): InfoWindow {
this.dispatchEvent('info-window:before-create', { definition, element });
const infoWindow = this.doCreateInfoWindow({ definition, element });
Expand Down Expand Up @@ -286,6 +320,14 @@ export default abstract class<
this.onDrawChanged(this.circles, this.circlesValue, this.createCircle, this.doRemoveCircle);
}

public rectanglesValueChanged(): void {
if (!this.isConnected) {
return;
}

this.onDrawChanged(this.rectangles, this.rectanglesValue, this.createRectangle, this.doRemoveRectangle);
}

//endregion

//region Abstract factory methods to be implemented by the concrete classes, they are specific to the map provider
Expand Down Expand Up @@ -331,12 +373,20 @@ export default abstract class<

protected abstract doRemoveCircle(circle: Circle): void;

protected abstract doCreateRectangle({
definition,
}: {
definition: RectangleDefinition<RectangleOptions, InfoWindowOptions>;
}): Rectangle;

protected abstract doRemoveRectangle(rectangle: Rectangle): void;

protected abstract doCreateInfoWindow({
definition,
element,
}: {
definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
element: Marker | Polygon | Polyline | Circle;
element: Marker | Polygon | Polyline | Circle | Rectangle;
}): InfoWindow;
protected abstract doCreateIcon({
definition,
Expand Down Expand Up @@ -369,15 +419,21 @@ export default abstract class<
draws: typeof this.circles,
factory: typeof this.doCreateCircle
): typeof this.doCreateCircle;
private createDrawingFactory(
type: 'rectangle',
draws: typeof this.rectangles,
factory: typeof this.doCreateRectangle
): typeof this.doCreateRectangle;
private createDrawingFactory<
Factory extends
| typeof this.doCreateMarker
| typeof this.doCreatePolygon
| typeof this.doCreatePolyline
| typeof this.doCreateCircle,
| typeof this.doCreateCircle
| typeof this.doCreateRectangle,
Draw extends ReturnType<Factory>,
>(
type: 'marker' | 'polygon' | 'polyline' | 'circle',
type: 'marker' | 'polygon' | 'polyline' | 'circle' | 'rectangle',
draws: globalThis.Map<WithIdentifier<any>, Draw>,
factory: Factory
): Factory {
Expand Down Expand Up @@ -421,6 +477,12 @@ export default abstract class<
factory: typeof this.createCircle,
remover: typeof this.doRemoveCircle
): void;
private onDrawChanged(
draws: typeof this.rectangles,
newDrawDefinitions: typeof this.rectanglesValue,
factory: typeof this.createRectangle,
remover: typeof this.doRemoveRectangle
): void;
private onDrawChanged<Draw, DrawDefinition extends WithIdentifier<Record<string, unknown>>>(
draws: globalThis.Map<WithIdentifier<any>, Draw>,
newDrawDefinitions: Array<DrawDefinition>,
Expand Down
Loading
Loading