mirror of
https://github.com/symfony/ux-map.git
synced 2026-03-23 23:42:07 +01:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38ebd9a0ba | ||
|
|
e6163d034a | ||
|
|
f0ab9c5115 | ||
|
|
d105663254 | ||
|
|
dd94fdb28b | ||
|
|
4cd73c612d | ||
|
|
4e84564e21 | ||
|
|
74fbe59401 | ||
|
|
4e07cf3b2a | ||
|
|
9540d0e7ca | ||
|
|
4ce88895dc | ||
|
|
c14a0907be | ||
|
|
97e5fd66c6 | ||
|
|
bb30afa5f3 | ||
|
|
5762104d8d | ||
|
|
a5edf92aba | ||
|
|
9928a5cfec | ||
|
|
8bef1f2024 | ||
|
|
7d1349bac0 | ||
|
|
299ad7907f | ||
|
|
2165688ac9 | ||
|
|
6455f300f6 | ||
|
|
06a24f4728 | ||
|
|
974d11f751 | ||
|
|
2224131cd1 | ||
|
|
0a7f3a1b4e | ||
|
|
6dece8c2e9 | ||
|
|
63d48e24cd | ||
|
|
18c9f36ca7 | ||
|
|
dc96f81de9 | ||
|
|
31897dcf6d | ||
|
|
46964636ec | ||
|
|
e7af2b2770 | ||
|
|
7f4bd7b438 | ||
|
|
cf81723a24 | ||
|
|
211ed3c911 | ||
|
|
e824d0f34a | ||
|
|
1b7eb9819b |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -3,6 +3,7 @@
|
||||
/phpunit.xml.dist export-ignore
|
||||
/assets/src export-ignore
|
||||
/assets/test export-ignore
|
||||
/assets/vitest.config.js export-ignore
|
||||
/assets/playwright.config.ts export-ignore
|
||||
/assets/vitest.config.mjs export-ignore
|
||||
/doc export-ignore
|
||||
/tests export-ignore
|
||||
|
||||
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,5 +1,4 @@
|
||||
Please do not submit any Pull Requests here. They will be closed.
|
||||
---
|
||||
## Please do not submit any Pull Requests here. They will be closed.
|
||||
|
||||
Please submit your PR here instead:
|
||||
https://github.com/symfony/ux
|
||||
|
||||
30
.github/workflows/close-pull-request.yml
vendored
30
.github/workflows/close-pull-request.yml
vendored
@@ -1,20 +1,20 @@
|
||||
name: Close Pull Request
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: superbrothers/close-pull-request@v3
|
||||
with:
|
||||
comment: |
|
||||
Thanks for your Pull Request! We love contributions.
|
||||
|
||||
However, you should instead open your PR on the main repository:
|
||||
https://github.com/symfony/ux
|
||||
|
||||
This repository is what we call a "subtree split": a read-only subset of that main repository.
|
||||
We're looking forward to your PR there!
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: superbrothers/close-pull-request@v3
|
||||
with:
|
||||
comment: |
|
||||
Thanks for your Pull Request! We love contributions.
|
||||
|
||||
However, you should instead open your PR on the main repository:
|
||||
https://github.com/symfony/ux
|
||||
|
||||
This repository is what we call a "subtree split": a read-only subset of that main repository.
|
||||
We're looking forward to your PR there!
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
vendor
|
||||
composer.lock
|
||||
.phpunit.result.cache
|
||||
/config/reference.php
|
||||
/vendor
|
||||
/composer.lock
|
||||
/.phpunit.result.cache
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
branches: ["2.x"]
|
||||
maintained_branches: ["2.x"]
|
||||
doc_dir: "doc"
|
||||
branches: ['2.x']
|
||||
maintained_branches: ['2.x']
|
||||
doc_dir: 'doc'
|
||||
|
||||
94
CHANGELOG.md
94
CHANGELOG.md
@@ -1,17 +1,35 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.32
|
||||
|
||||
- Add `Map::removeAllMarkers()`, `Map::removeAllPolygons()`, `Map::removeAllPolylines()`, `Map::removeAllCircles()` and `Map::removeAllRectangles()` methods
|
||||
|
||||
## 2.31
|
||||
|
||||
- Add `fitBoundsToMarkers` parameter to `ux_map()` Twig function
|
||||
|
||||
## 2.30
|
||||
|
||||
- Ensure compatibility with PHP 8.5
|
||||
- Deprecate option `title` from `Polygon`, `Polyline`, `Rectangle` and `Circle` in favor of `infoWindow`
|
||||
|
||||
## 2.29.0
|
||||
|
||||
- Add Symfony 8 support
|
||||
- Add `Cluster` class and `ClusteringAlgorithmInterface` with two implementations `GridClusteringAlgorithm` and `MortonClusteringAlgorithm`
|
||||
|
||||
## 2.28
|
||||
|
||||
- Add `minZoom` and `maxZoom` options to `Map` to set the minimum and maximum zoom levels
|
||||
- The package is not experimental anymore
|
||||
- Add `minZoom` and `maxZoom` options to `Map` to set the minimum and maximum zoom levels
|
||||
|
||||
## 2.27
|
||||
|
||||
- The `fitBoundsToMarkers` option is not overridden anymore when using the `Map` LiveComponent, but now respects the value you defined.
|
||||
You may encounter unwanted behavior when adding/removing elements to the map.
|
||||
To use the previous behavior, you must call `$this->getMap()->fitBoundsToMarkers(false)` in your LiveComponent's live actions
|
||||
- The `fitBoundsToMarkers` option is not overridden anymore when using the `Map` LiveComponent, but now respects the value you defined.
|
||||
You may encounter unwanted behavior when adding/removing elements to the map.
|
||||
To use the previous behavior, you must call `$this->getMap()->fitBoundsToMarkers(false)` in your LiveComponent's live actions
|
||||
|
||||
- Add support for creating `Circle` by passing a `Point` and a radius (in meters) to the `Circle` constructor, e.g.:
|
||||
|
||||
- Add support for creating `Circle` by passing a `Point` and a radius (in meters) to the `Circle` constructor, e.g.:
|
||||
```php
|
||||
$map->addCircle(new Circle(
|
||||
center: new Point(48.856613, 2.352222), // Paris
|
||||
@@ -19,7 +37,8 @@ $map->addCircle(new Circle(
|
||||
));
|
||||
```
|
||||
|
||||
- Add support for creating `Rectangle` by passing two `Point` instances to the `Rectangle` constructor, e.g.:
|
||||
- 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
|
||||
@@ -27,8 +46,9 @@ $map->addRectangle(new Rectangle(
|
||||
));
|
||||
```
|
||||
|
||||
- Deprecate property `rawOptions` from `ux:map:*:before-create` events, in favor of `bridgeOptions` instead.
|
||||
- Map options can now be configured and overridden through the `ux:map:pre-connect` event:
|
||||
- Deprecate property `rawOptions` from `ux:map:*:before-create` events, in favor of `bridgeOptions` instead.
|
||||
- Map options can now be configured and overridden through the `ux:map:pre-connect` event:
|
||||
|
||||
```js
|
||||
this.element.addEventListener('ux:map:pre-connect', (event) => {
|
||||
// Override the map center and zoom
|
||||
@@ -44,11 +64,13 @@ this.element.addEventListener('ux:map:pre-connect', (event) => {
|
||||
};
|
||||
});
|
||||
```
|
||||
- Add `extra` data support to `Map`, which can be accessed in `ux:map:pre-connect` and `ux:map:connect` events
|
||||
|
||||
- Add `extra` data support to `Map`, which can be accessed in `ux:map:pre-connect` and `ux:map:connect` events
|
||||
|
||||
## 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.:
|
||||
- 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: [
|
||||
@@ -72,43 +94,43 @@ $map->addPolygon(new Polygon(points: [
|
||||
|
||||
## 2.25
|
||||
|
||||
- Downgrade PHP requirement from 8.3 to 8.1
|
||||
- Downgrade PHP requirement from 8.3 to 8.1
|
||||
|
||||
## 2.24
|
||||
|
||||
- Installing the package in a Symfony app using Flex won't add the `@symfony/ux-map` dependency to the `package.json` file anymore.
|
||||
- Add `Icon` to customize a `Marker` icon (URL or SVG content)
|
||||
- Add parameter `id` to `Marker`, `Polygon` and `Polyline` constructors
|
||||
- Add method `Map::removeMarker(string|Marker $markerOrId)`
|
||||
- Add method `Map::removePolygon(string|Polygon $polygonOrId)`
|
||||
- Add method `Map::removePolyline(string|Polyline $polylineOrId)`
|
||||
- Installing the package in a Symfony app using Flex won't add the `@symfony/ux-map` dependency to the `package.json` file anymore.
|
||||
- Add `Icon` to customize a `Marker` icon (URL or SVG content)
|
||||
- Add parameter `id` to `Marker`, `Polygon` and `Polyline` constructors
|
||||
- Add method `Map::removeMarker(string|Marker $markerOrId)`
|
||||
- Add method `Map::removePolygon(string|Polygon $polygonOrId)`
|
||||
- Add method `Map::removePolyline(string|Polyline $polylineOrId)`
|
||||
|
||||
## 2.23
|
||||
|
||||
- Add `DistanceUnit` to represent distance units (`m`, `km`, `miles`, `nmi`) and
|
||||
ease conversion between units.
|
||||
- Add `DistanceCalculatorInterface` interface and three implementations:
|
||||
`HaversineDistanceCalculator`, `SphericalCosineDistanceCalculator` and `VincentyDistanceCalculator`.
|
||||
- Add `CoordinateUtils` helper, to convert decimal coordinates (`43.2109`) in DMS (`56° 78' 90"`)
|
||||
- Add `DistanceUnit` to represent distance units (`m`, `km`, `miles`, `nmi`) and
|
||||
ease conversion between units.
|
||||
- Add `DistanceCalculatorInterface` interface and three implementations:
|
||||
`HaversineDistanceCalculator`, `SphericalCosineDistanceCalculator` and `VincentyDistanceCalculator`.
|
||||
- Add `CoordinateUtils` helper, to convert decimal coordinates (`43.2109`) in DMS (`56° 78' 90"`)
|
||||
|
||||
## 2.22
|
||||
|
||||
- Add method `Symfony\UX\Map\Renderer\AbstractRenderer::tapOptions()`, to allow Renderer to modify options before rendering a Map.
|
||||
- Add `ux_map.google_maps.default_map_id` configuration to set the Google ``Map ID``
|
||||
- Add `ComponentWithMapTrait` to ease maps integration in [Live Components](https://symfony.com/bundles/ux-live-component/current/index.html)
|
||||
- Add `Polyline` support
|
||||
- Add method `Symfony\UX\Map\Renderer\AbstractRenderer::tapOptions()`, to allow Renderer to modify options before rendering a Map.
|
||||
- Add `ux_map.google_maps.default_map_id` configuration to set the Google `Map ID`
|
||||
- Add `ComponentWithMapTrait` to ease maps integration in [Live Components](https://symfony.com/bundles/ux-live-component/current/index.html)
|
||||
- Add `Polyline` support
|
||||
|
||||
## 2.20
|
||||
|
||||
- Deprecate `render_map` Twig function (will be removed in 2.21). Use
|
||||
`ux_map` or the `<twig:ux:map />` Twig component instead.
|
||||
- Add `ux_map` Twig function (replaces `render_map` with a more flexible
|
||||
interface)
|
||||
- Add `<twig:ux:map />` 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
|
||||
- Deprecate `render_map` Twig function (will be removed in 2.21). Use
|
||||
`ux_map` or the `<twig:ux:map />` Twig component instead.
|
||||
- Add `ux_map` Twig function (replaces `render_map` with a more flexible
|
||||
interface)
|
||||
- Add `<twig:ux:map />` 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
|
||||
|
||||
- Component added
|
||||
- Component added
|
||||
|
||||
@@ -7,7 +7,7 @@ https://github.com/symfony/ux to create issues or submit pull requests.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://symfony.com/bundles/ux-map/current/index.html)
|
||||
- [Report issues](https://github.com/symfony/ux/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/ux/pulls)
|
||||
in the [main Symfony UX repository](https://github.com/symfony/ux)
|
||||
- [Documentation](https://symfony.com/bundles/ux-map/current/index.html)
|
||||
- [Report issues](https://github.com/symfony/ux/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/ux/pulls)
|
||||
in the [main Symfony UX repository](https://github.com/symfony/ux)
|
||||
|
||||
@@ -6,7 +6,7 @@ This package is private and is not intended to be published on npm or installed.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://symfony.com/bundles/ux-map/current/index.html)
|
||||
- [Report issues](https://github.com/symfony/ux/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/ux/pulls)
|
||||
in the [main Symfony UX repository](https://github.com/symfony/ux)
|
||||
- [Documentation](https://symfony.com/bundles/ux-map/current/index.html)
|
||||
- [Report issues](https://github.com/symfony/ux/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/ux/pulls)
|
||||
in the [main Symfony UX repository](https://github.com/symfony/ux)
|
||||
|
||||
355
assets/dist/abstract_map_controller.d.ts
vendored
355
assets/dist/abstract_map_controller.d.ts
vendored
@@ -1,197 +1,216 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
import { Controller } from "@hotwired/stimulus";
|
||||
type Point = {
|
||||
lat: number;
|
||||
lng: number;
|
||||
lat: number;
|
||||
lng: number;
|
||||
};
|
||||
type Identifier = string;
|
||||
type WithIdentifier<T extends Record<string, unknown>> = T & {
|
||||
'@id': Identifier;
|
||||
'@id': Identifier;
|
||||
};
|
||||
type ExtraData = Record<string, unknown>;
|
||||
declare const IconTypes: {
|
||||
readonly Url: "url";
|
||||
readonly Svg: "svg";
|
||||
readonly UxIcon: "ux-icon";
|
||||
readonly Url: "url";
|
||||
readonly Svg: "svg";
|
||||
readonly UxIcon: "ux-icon";
|
||||
};
|
||||
type Icon = {
|
||||
width: number;
|
||||
height: number;
|
||||
width: number;
|
||||
height: number;
|
||||
} & ({
|
||||
type: typeof IconTypes.UxIcon;
|
||||
name: string;
|
||||
_generated_html: string;
|
||||
type: typeof IconTypes.UxIcon;
|
||||
name: string;
|
||||
_generated_html: string;
|
||||
} | {
|
||||
type: typeof IconTypes.Url;
|
||||
url: string;
|
||||
type: typeof IconTypes.Url;
|
||||
url: string;
|
||||
} | {
|
||||
type: typeof IconTypes.Svg;
|
||||
html: string;
|
||||
type: typeof IconTypes.Svg;
|
||||
html: string;
|
||||
});
|
||||
type MapDefinition<MapOptions, BridgeMapOptions> = {
|
||||
center: Point | null;
|
||||
zoom: number | null;
|
||||
minZoom: number | null;
|
||||
maxZoom: number | null;
|
||||
options: MapOptions;
|
||||
bridgeOptions?: BridgeMapOptions;
|
||||
extra: ExtraData;
|
||||
center: Point | null;
|
||||
zoom: number | null;
|
||||
minZoom: number | null;
|
||||
maxZoom: number | null;
|
||||
options: MapOptions;
|
||||
bridgeOptions?: BridgeMapOptions;
|
||||
extra: ExtraData;
|
||||
};
|
||||
type MarkerDefinition<BridgeMarkerOptions, BridgeInfoWindowOptions> = WithIdentifier<{
|
||||
position: Point;
|
||||
title: string | null;
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
icon?: Icon;
|
||||
rawOptions?: BridgeMarkerOptions;
|
||||
bridgeOptions?: BridgeMarkerOptions;
|
||||
extra: ExtraData;
|
||||
position: Point;
|
||||
title: string | null;
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
icon?: Icon;
|
||||
rawOptions?: BridgeMarkerOptions;
|
||||
bridgeOptions?: BridgeMarkerOptions;
|
||||
extra: ExtraData;
|
||||
}>;
|
||||
type PolygonDefinition<BridgePolygonOptions, BridgeInfoWindowOptions> = WithIdentifier<{
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
points: Array<Point> | Array<Array<Point>>;
|
||||
title: string | null;
|
||||
rawOptions?: BridgePolygonOptions;
|
||||
bridgeOptions?: BridgePolygonOptions;
|
||||
extra: ExtraData;
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
points: Array<Point> | Array<Array<Point>>;
|
||||
title: string | null;
|
||||
rawOptions?: BridgePolygonOptions;
|
||||
bridgeOptions?: BridgePolygonOptions;
|
||||
extra: ExtraData;
|
||||
}>;
|
||||
type PolylineDefinition<BridgePolylineOptions, BridgeInfoWindowOptions> = WithIdentifier<{
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
points: Array<Point>;
|
||||
title: string | null;
|
||||
rawOptions?: BridgePolylineOptions;
|
||||
bridgeOptions?: BridgePolylineOptions;
|
||||
extra: ExtraData;
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
points: Array<Point>;
|
||||
title: string | null;
|
||||
rawOptions?: BridgePolylineOptions;
|
||||
bridgeOptions?: BridgePolylineOptions;
|
||||
extra: ExtraData;
|
||||
}>;
|
||||
type CircleDefinition<BridgeCircleOptions, BridgeInfoWindowOptions> = WithIdentifier<{
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
center: Point;
|
||||
radius: number;
|
||||
title: string | null;
|
||||
rawOptions?: BridgeCircleOptions;
|
||||
bridgeOptions?: BridgeCircleOptions;
|
||||
extra: ExtraData;
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
center: Point;
|
||||
radius: number;
|
||||
title: string | null;
|
||||
rawOptions?: BridgeCircleOptions;
|
||||
bridgeOptions?: BridgeCircleOptions;
|
||||
extra: ExtraData;
|
||||
}>;
|
||||
type RectangleDefinition<BridgeRectangleOptions, BridgeInfoWindowOptions> = WithIdentifier<{
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
southWest: Point;
|
||||
northEast: Point;
|
||||
title: string | null;
|
||||
rawOptions?: BridgeRectangleOptions;
|
||||
bridgeOptions?: BridgeRectangleOptions;
|
||||
extra: ExtraData;
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
southWest: Point;
|
||||
northEast: Point;
|
||||
title: string | null;
|
||||
rawOptions?: BridgeRectangleOptions;
|
||||
bridgeOptions?: BridgeRectangleOptions;
|
||||
extra: ExtraData;
|
||||
}>;
|
||||
type InfoWindowDefinition<BridgeInfoWindowOptions> = {
|
||||
headerContent: string | null;
|
||||
content: string | null;
|
||||
position: Point;
|
||||
opened: boolean;
|
||||
autoClose: boolean;
|
||||
rawOptions?: BridgeInfoWindowOptions;
|
||||
bridgeOptions?: BridgeInfoWindowOptions;
|
||||
extra: ExtraData;
|
||||
headerContent: string | null;
|
||||
content: string | null;
|
||||
position: Point;
|
||||
opened: boolean;
|
||||
autoClose: boolean;
|
||||
rawOptions?: BridgeInfoWindowOptions;
|
||||
bridgeOptions?: BridgeInfoWindowOptions;
|
||||
extra: ExtraData;
|
||||
};
|
||||
declare abstract class export_default<MapOptions, BridgeMapOptions, BridgeMap, BridgeMarkerOptions, BridgeMarker, BridgeInfoWindowOptions, BridgeInfoWindow, BridgePolygonOptions, BridgePolygon, BridgePolylineOptions, BridgePolyline, BridgeCircleOptions, BridgeCircle, BridgeRectangleOptions, BridgeRectangle> extends Controller<HTMLElement> {
|
||||
static values: {
|
||||
providerOptions: ObjectConstructor;
|
||||
center: ObjectConstructor;
|
||||
zoom: NumberConstructor;
|
||||
minZoom: NumberConstructor;
|
||||
maxZoom: NumberConstructor;
|
||||
fitBoundsToMarkers: BooleanConstructor;
|
||||
markers: ArrayConstructor;
|
||||
polygons: ArrayConstructor;
|
||||
polylines: ArrayConstructor;
|
||||
circles: ArrayConstructor;
|
||||
rectangles: ArrayConstructor;
|
||||
options: ObjectConstructor;
|
||||
extra: ObjectConstructor;
|
||||
};
|
||||
centerValue: Point | null;
|
||||
zoomValue: number | null;
|
||||
minZoomValue: number | null;
|
||||
maxZoomValue: number | null;
|
||||
fitBoundsToMarkersValue: boolean;
|
||||
markersValue: Array<MarkerDefinition<BridgeMarkerOptions, BridgeInfoWindowOptions>>;
|
||||
polygonsValue: Array<PolygonDefinition<BridgePolygonOptions, BridgeInfoWindowOptions>>;
|
||||
polylinesValue: Array<PolylineDefinition<BridgePolylineOptions, BridgeInfoWindowOptions>>;
|
||||
circlesValue: Array<CircleDefinition<BridgeCircleOptions, BridgeInfoWindowOptions>>;
|
||||
rectanglesValue: Array<RectangleDefinition<BridgeRectangleOptions, BridgeInfoWindowOptions>>;
|
||||
optionsValue: MapOptions;
|
||||
extraValue: Record<string, unknown>;
|
||||
hasCenterValue: boolean;
|
||||
hasZoomValue: boolean;
|
||||
hasMinZoomValue: boolean;
|
||||
hasMaxZoomValue: boolean;
|
||||
hasFitBoundsToMarkersValue: boolean;
|
||||
hasMarkersValue: boolean;
|
||||
hasPolygonsValue: boolean;
|
||||
hasPolylinesValue: boolean;
|
||||
hasCirclesValue: boolean;
|
||||
hasRectanglesValue: boolean;
|
||||
hasOptionsValue: boolean;
|
||||
hasExtraValue: boolean;
|
||||
protected map: BridgeMap;
|
||||
protected markers: Map<string, BridgeMarker>;
|
||||
protected polygons: Map<string, BridgePolygon>;
|
||||
protected polylines: Map<string, BridgePolyline>;
|
||||
protected circles: Map<string, BridgeCircle>;
|
||||
protected rectangles: Map<string, BridgeRectangle>;
|
||||
protected infoWindows: Array<BridgeInfoWindow>;
|
||||
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: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
element: BridgeMarker | BridgePolygon | BridgePolyline | BridgeCircle | BridgeRectangle;
|
||||
}): BridgeInfoWindow;
|
||||
abstract centerValueChanged(): void;
|
||||
abstract zoomValueChanged(): void;
|
||||
abstract minZoomValueChanged(): void;
|
||||
abstract maxZoomValueChanged(): void;
|
||||
markersValueChanged(): void;
|
||||
polygonsValueChanged(): void;
|
||||
polylinesValueChanged(): void;
|
||||
circlesValueChanged(): void;
|
||||
rectanglesValueChanged(): void;
|
||||
protected abstract doCreateMap({ definition }: {
|
||||
definition: MapDefinition<MapOptions, BridgeMapOptions>;
|
||||
}): BridgeMap;
|
||||
protected abstract doFitBoundsToMarkers(): void;
|
||||
protected abstract doCreateMarker({ definition }: {
|
||||
definition: MarkerDefinition<BridgeMarkerOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgeMarker;
|
||||
protected abstract doRemoveMarker(marker: BridgeMarker): void;
|
||||
protected abstract doCreatePolygon({ definition }: {
|
||||
definition: PolygonDefinition<BridgePolygonOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgePolygon;
|
||||
protected abstract doRemovePolygon(polygon: BridgePolygon): void;
|
||||
protected abstract doCreatePolyline({ definition }: {
|
||||
definition: PolylineDefinition<BridgePolylineOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgePolyline;
|
||||
protected abstract doRemovePolyline(polyline: BridgePolyline): void;
|
||||
protected abstract doCreateCircle({ definition }: {
|
||||
definition: CircleDefinition<BridgeCircleOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgeCircle;
|
||||
protected abstract doRemoveCircle(circle: BridgeCircle): void;
|
||||
protected abstract doCreateRectangle({ definition }: {
|
||||
definition: RectangleDefinition<BridgeRectangleOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgeRectangle;
|
||||
protected abstract doRemoveRectangle(rectangle: BridgeRectangle): void;
|
||||
protected abstract doCreateInfoWindow({ definition, element, }: {
|
||||
definition: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
element: BridgeMarker | BridgePolygon | BridgePolyline | BridgeCircle | BridgeRectangle;
|
||||
}): BridgeInfoWindow;
|
||||
protected abstract doCreateIcon({ definition, element }: {
|
||||
definition: Icon;
|
||||
element: BridgeMarker;
|
||||
}): void;
|
||||
private createDrawingFactory;
|
||||
private onDrawChanged;
|
||||
static values: {
|
||||
providerOptions: ObjectConstructor;
|
||||
center: ObjectConstructor;
|
||||
zoom: NumberConstructor;
|
||||
minZoom: NumberConstructor;
|
||||
maxZoom: NumberConstructor;
|
||||
fitBoundsToMarkers: BooleanConstructor;
|
||||
markers: ArrayConstructor;
|
||||
polygons: ArrayConstructor;
|
||||
polylines: ArrayConstructor;
|
||||
circles: ArrayConstructor;
|
||||
rectangles: ArrayConstructor;
|
||||
options: ObjectConstructor;
|
||||
extra: ObjectConstructor;
|
||||
};
|
||||
centerValue: Point | null;
|
||||
zoomValue: number | null;
|
||||
minZoomValue: number | null;
|
||||
maxZoomValue: number | null;
|
||||
fitBoundsToMarkersValue: boolean;
|
||||
markersValue: Array<MarkerDefinition<BridgeMarkerOptions, BridgeInfoWindowOptions>>;
|
||||
polygonsValue: Array<PolygonDefinition<BridgePolygonOptions, BridgeInfoWindowOptions>>;
|
||||
polylinesValue: Array<PolylineDefinition<BridgePolylineOptions, BridgeInfoWindowOptions>>;
|
||||
circlesValue: Array<CircleDefinition<BridgeCircleOptions, BridgeInfoWindowOptions>>;
|
||||
rectanglesValue: Array<RectangleDefinition<BridgeRectangleOptions, BridgeInfoWindowOptions>>;
|
||||
optionsValue: MapOptions;
|
||||
extraValue: Record<string, unknown>;
|
||||
hasCenterValue: boolean;
|
||||
hasZoomValue: boolean;
|
||||
hasMinZoomValue: boolean;
|
||||
hasMaxZoomValue: boolean;
|
||||
hasFitBoundsToMarkersValue: boolean;
|
||||
hasMarkersValue: boolean;
|
||||
hasPolygonsValue: boolean;
|
||||
hasPolylinesValue: boolean;
|
||||
hasCirclesValue: boolean;
|
||||
hasRectanglesValue: boolean;
|
||||
hasOptionsValue: boolean;
|
||||
hasExtraValue: boolean;
|
||||
protected map: BridgeMap;
|
||||
protected markers: Map<string, BridgeMarker>;
|
||||
protected polygons: Map<string, BridgePolygon>;
|
||||
protected polylines: Map<string, BridgePolyline>;
|
||||
protected circles: Map<string, BridgeCircle>;
|
||||
protected rectangles: Map<string, BridgeRectangle>;
|
||||
protected infoWindows: Array<BridgeInfoWindow>;
|
||||
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: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
element: BridgeMarker | BridgePolygon | BridgePolyline | BridgeCircle | BridgeRectangle;
|
||||
}): BridgeInfoWindow;
|
||||
abstract centerValueChanged(): void;
|
||||
abstract zoomValueChanged(): void;
|
||||
abstract minZoomValueChanged(): void;
|
||||
abstract maxZoomValueChanged(): void;
|
||||
markersValueChanged(): void;
|
||||
polygonsValueChanged(): void;
|
||||
polylinesValueChanged(): void;
|
||||
circlesValueChanged(): void;
|
||||
rectanglesValueChanged(): void;
|
||||
protected abstract doCreateMap({
|
||||
definition
|
||||
}: {
|
||||
definition: MapDefinition<MapOptions, BridgeMapOptions>;
|
||||
}): BridgeMap;
|
||||
protected abstract doFitBoundsToMarkers(): void;
|
||||
protected abstract doCreateMarker({
|
||||
definition
|
||||
}: {
|
||||
definition: MarkerDefinition<BridgeMarkerOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgeMarker;
|
||||
protected abstract doRemoveMarker(marker: BridgeMarker): void;
|
||||
protected abstract doCreatePolygon({
|
||||
definition
|
||||
}: {
|
||||
definition: PolygonDefinition<BridgePolygonOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgePolygon;
|
||||
protected abstract doRemovePolygon(polygon: BridgePolygon): void;
|
||||
protected abstract doCreatePolyline({
|
||||
definition
|
||||
}: {
|
||||
definition: PolylineDefinition<BridgePolylineOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgePolyline;
|
||||
protected abstract doRemovePolyline(polyline: BridgePolyline): void;
|
||||
protected abstract doCreateCircle({
|
||||
definition
|
||||
}: {
|
||||
definition: CircleDefinition<BridgeCircleOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgeCircle;
|
||||
protected abstract doRemoveCircle(circle: BridgeCircle): void;
|
||||
protected abstract doCreateRectangle({
|
||||
definition
|
||||
}: {
|
||||
definition: RectangleDefinition<BridgeRectangleOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgeRectangle;
|
||||
protected abstract doRemoveRectangle(rectangle: BridgeRectangle): void;
|
||||
protected abstract doCreateInfoWindow({
|
||||
definition,
|
||||
element
|
||||
}: {
|
||||
definition: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
element: BridgeMarker | BridgePolygon | BridgePolyline | BridgeCircle | BridgeRectangle;
|
||||
}): BridgeInfoWindow;
|
||||
protected abstract doCreateIcon({
|
||||
definition,
|
||||
element
|
||||
}: {
|
||||
definition: Icon;
|
||||
element: BridgeMarker;
|
||||
}): void;
|
||||
private createDrawingFactory;
|
||||
private onDrawChanged;
|
||||
}
|
||||
|
||||
export { type CircleDefinition, type Icon, IconTypes, type Identifier, type InfoWindowDefinition, type MapDefinition, type MarkerDefinition, type Point, type PolygonDefinition, type PolylineDefinition, type RectangleDefinition, type WithIdentifier, export_default as default };
|
||||
export { CircleDefinition, Icon, IconTypes, Identifier, InfoWindowDefinition, MapDefinition, MarkerDefinition, Point, PolygonDefinition, PolylineDefinition, RectangleDefinition, WithIdentifier, export_default as default };
|
||||
290
assets/dist/abstract_map_controller.js
vendored
290
assets/dist/abstract_map_controller.js
vendored
@@ -1,153 +1,145 @@
|
||||
// src/abstract_map_controller.ts
|
||||
import { Controller } from "@hotwired/stimulus";
|
||||
var IconTypes = {
|
||||
Url: "url",
|
||||
Svg: "svg",
|
||||
UxIcon: "ux-icon"
|
||||
const IconTypes = {
|
||||
Url: "url",
|
||||
Svg: "svg",
|
||||
UxIcon: "ux-icon"
|
||||
};
|
||||
var abstract_map_controller_default = class extends Controller {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.markers = /* @__PURE__ */ new Map();
|
||||
this.polygons = /* @__PURE__ */ new Map();
|
||||
this.polylines = /* @__PURE__ */ new Map();
|
||||
this.circles = /* @__PURE__ */ new Map();
|
||||
this.rectangles = /* @__PURE__ */ new Map();
|
||||
this.infoWindows = [];
|
||||
this.isConnected = false;
|
||||
}
|
||||
connect() {
|
||||
const extra = this.hasExtraValue ? this.extraValue : {};
|
||||
const mapDefinition = {
|
||||
center: this.hasCenterValue ? this.centerValue : null,
|
||||
zoom: this.hasZoomValue ? this.zoomValue : null,
|
||||
minZoom: this.hasMinZoomValue ? this.minZoomValue : null,
|
||||
maxZoom: this.hasMaxZoomValue ? this.maxZoomValue : null,
|
||||
options: this.optionsValue,
|
||||
extra
|
||||
};
|
||||
this.dispatchEvent("pre-connect", mapDefinition);
|
||||
this.createMarker = this.createDrawingFactory("marker", this.markers, this.doCreateMarker.bind(this));
|
||||
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({ definition: mapDefinition });
|
||||
this.markersValue.forEach((definition) => this.createMarker({ definition }));
|
||||
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();
|
||||
}
|
||||
this.dispatchEvent("connect", {
|
||||
map: this.map,
|
||||
markers: [...this.markers.values()],
|
||||
polygons: [...this.polygons.values()],
|
||||
polylines: [...this.polylines.values()],
|
||||
circles: [...this.circles.values()],
|
||||
rectangles: [...this.rectangles.values()],
|
||||
infoWindows: this.infoWindows,
|
||||
extra
|
||||
});
|
||||
this.isConnected = true;
|
||||
}
|
||||
//region Public API
|
||||
createInfoWindow({
|
||||
definition,
|
||||
element
|
||||
}) {
|
||||
this.dispatchEvent("info-window:before-create", { definition, element });
|
||||
const infoWindow = this.doCreateInfoWindow({ definition, element });
|
||||
this.dispatchEvent("info-window:after-create", { infoWindow, definition, element });
|
||||
this.infoWindows.push(infoWindow);
|
||||
return infoWindow;
|
||||
}
|
||||
markersValueChanged() {
|
||||
if (!this.isConnected) {
|
||||
return;
|
||||
}
|
||||
this.onDrawChanged(this.markers, this.markersValue, this.createMarker, this.doRemoveMarker);
|
||||
if (this.fitBoundsToMarkersValue) {
|
||||
this.doFitBoundsToMarkers();
|
||||
}
|
||||
}
|
||||
polygonsValueChanged() {
|
||||
if (!this.isConnected) {
|
||||
return;
|
||||
}
|
||||
this.onDrawChanged(this.polygons, this.polygonsValue, this.createPolygon, this.doRemovePolygon);
|
||||
}
|
||||
polylinesValueChanged() {
|
||||
if (!this.isConnected) {
|
||||
return;
|
||||
}
|
||||
this.onDrawChanged(this.polylines, this.polylinesValue, this.createPolyline, this.doRemovePolyline);
|
||||
}
|
||||
circlesValueChanged() {
|
||||
if (!this.isConnected) {
|
||||
return;
|
||||
}
|
||||
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`;
|
||||
return ({ definition }) => {
|
||||
this.dispatchEvent(eventBefore, { definition });
|
||||
if (typeof definition.rawOptions !== "undefined") {
|
||||
console.warn(
|
||||
`[Symfony UX Map] The event "${eventBefore}" added a deprecated "rawOptions" property to the definition, it will be removed in a next major version, replace it with "bridgeOptions" instead.`,
|
||||
definition
|
||||
);
|
||||
}
|
||||
const drawing = factory({ definition });
|
||||
this.dispatchEvent(eventAfter, { [type]: drawing, definition });
|
||||
draws.set(definition["@id"], drawing);
|
||||
return drawing;
|
||||
};
|
||||
}
|
||||
onDrawChanged(draws, newDrawDefinitions, factory, remover) {
|
||||
const idsToRemove = new Set(draws.keys());
|
||||
newDrawDefinitions.forEach((definition) => {
|
||||
idsToRemove.delete(definition["@id"]);
|
||||
});
|
||||
idsToRemove.forEach((id) => {
|
||||
const draw = draws.get(id);
|
||||
remover(draw);
|
||||
draws.delete(id);
|
||||
});
|
||||
newDrawDefinitions.forEach((definition) => {
|
||||
if (!draws.has(definition["@id"])) {
|
||||
factory({ definition });
|
||||
}
|
||||
});
|
||||
}
|
||||
//endregion
|
||||
var _Class = class extends Controller {
|
||||
constructor(..._args) {
|
||||
super(..._args);
|
||||
this.markers = /* @__PURE__ */ new Map();
|
||||
this.polygons = /* @__PURE__ */ new Map();
|
||||
this.polylines = /* @__PURE__ */ new Map();
|
||||
this.circles = /* @__PURE__ */ new Map();
|
||||
this.rectangles = /* @__PURE__ */ new Map();
|
||||
this.infoWindows = [];
|
||||
this.isConnected = false;
|
||||
}
|
||||
connect() {
|
||||
const extra = this.hasExtraValue ? this.extraValue : {};
|
||||
const mapDefinition = {
|
||||
center: this.hasCenterValue ? this.centerValue : null,
|
||||
zoom: this.hasZoomValue ? this.zoomValue : null,
|
||||
minZoom: this.hasMinZoomValue ? this.minZoomValue : null,
|
||||
maxZoom: this.hasMaxZoomValue ? this.maxZoomValue : null,
|
||||
options: this.optionsValue,
|
||||
extra
|
||||
};
|
||||
this.dispatchEvent("pre-connect", mapDefinition);
|
||||
this.createMarker = this.createDrawingFactory("marker", this.markers, this.doCreateMarker.bind(this));
|
||||
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({ definition: mapDefinition });
|
||||
this.markersValue.forEach((definition) => {
|
||||
this.createMarker({ definition });
|
||||
});
|
||||
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();
|
||||
this.dispatchEvent("connect", {
|
||||
map: this.map,
|
||||
markers: [...this.markers.values()],
|
||||
polygons: [...this.polygons.values()],
|
||||
polylines: [...this.polylines.values()],
|
||||
circles: [...this.circles.values()],
|
||||
rectangles: [...this.rectangles.values()],
|
||||
infoWindows: this.infoWindows,
|
||||
extra
|
||||
});
|
||||
this.isConnected = true;
|
||||
}
|
||||
createInfoWindow({ definition, element }) {
|
||||
this.dispatchEvent("info-window:before-create", {
|
||||
definition,
|
||||
element
|
||||
});
|
||||
const infoWindow = this.doCreateInfoWindow({
|
||||
definition,
|
||||
element
|
||||
});
|
||||
this.dispatchEvent("info-window:after-create", {
|
||||
infoWindow,
|
||||
definition,
|
||||
element
|
||||
});
|
||||
this.infoWindows.push(infoWindow);
|
||||
return infoWindow;
|
||||
}
|
||||
markersValueChanged() {
|
||||
if (!this.isConnected) return;
|
||||
this.onDrawChanged(this.markers, this.markersValue, this.createMarker, this.doRemoveMarker);
|
||||
if (this.fitBoundsToMarkersValue) this.doFitBoundsToMarkers();
|
||||
}
|
||||
polygonsValueChanged() {
|
||||
if (!this.isConnected) return;
|
||||
this.onDrawChanged(this.polygons, this.polygonsValue, this.createPolygon, this.doRemovePolygon);
|
||||
}
|
||||
polylinesValueChanged() {
|
||||
if (!this.isConnected) return;
|
||||
this.onDrawChanged(this.polylines, this.polylinesValue, this.createPolyline, this.doRemovePolyline);
|
||||
}
|
||||
circlesValueChanged() {
|
||||
if (!this.isConnected) return;
|
||||
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`;
|
||||
return ({ definition }) => {
|
||||
this.dispatchEvent(eventBefore, { definition });
|
||||
if (typeof definition.rawOptions !== "undefined") console.warn(`[Symfony UX Map] The event "${eventBefore}" added a deprecated "rawOptions" property to the definition, it will be removed in a next major version, replace it with "bridgeOptions" instead.`, definition);
|
||||
const drawing = factory({ definition });
|
||||
this.dispatchEvent(eventAfter, {
|
||||
[type]: drawing,
|
||||
definition
|
||||
});
|
||||
draws.set(definition["@id"], drawing);
|
||||
return drawing;
|
||||
};
|
||||
}
|
||||
onDrawChanged(draws, newDrawDefinitions, factory, remover) {
|
||||
const idsToRemove = new Set(draws.keys());
|
||||
newDrawDefinitions.forEach((definition) => {
|
||||
idsToRemove.delete(definition["@id"]);
|
||||
});
|
||||
idsToRemove.forEach((id) => {
|
||||
remover(draws.get(id));
|
||||
draws.delete(id);
|
||||
});
|
||||
newDrawDefinitions.forEach((definition) => {
|
||||
if (!draws.has(definition["@id"])) factory({ definition });
|
||||
});
|
||||
}
|
||||
};
|
||||
abstract_map_controller_default.values = {
|
||||
providerOptions: Object,
|
||||
center: Object,
|
||||
zoom: Number,
|
||||
minZoom: Number,
|
||||
maxZoom: Number,
|
||||
fitBoundsToMarkers: Boolean,
|
||||
markers: Array,
|
||||
polygons: Array,
|
||||
polylines: Array,
|
||||
circles: Array,
|
||||
rectangles: Array,
|
||||
options: Object,
|
||||
extra: Object
|
||||
};
|
||||
export {
|
||||
IconTypes,
|
||||
abstract_map_controller_default as default
|
||||
_Class.values = {
|
||||
providerOptions: Object,
|
||||
center: Object,
|
||||
zoom: Number,
|
||||
minZoom: Number,
|
||||
maxZoom: Number,
|
||||
fitBoundsToMarkers: Boolean,
|
||||
markers: Array,
|
||||
polygons: Array,
|
||||
polylines: Array,
|
||||
circles: Array,
|
||||
rectangles: Array,
|
||||
options: Object,
|
||||
extra: Object
|
||||
};
|
||||
export { IconTypes, _Class as default };
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Easily embed interactive maps in your Symfony application.",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"version": "2.28.1",
|
||||
"version": "2.34.0",
|
||||
"keywords": [
|
||||
"symfony-ux",
|
||||
"map",
|
||||
@@ -11,7 +11,7 @@
|
||||
"maps"
|
||||
],
|
||||
"homepage": "https://ux.symfony.com/map",
|
||||
"repository": "https://github.com/symfony/ux-map",
|
||||
"repository": "https://github.com/symfony/ux",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist"
|
||||
@@ -19,11 +19,10 @@
|
||||
"main": "dist/abstract_map_controller.js",
|
||||
"types": "dist/abstract_map_controller.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsx ../../../bin/build_package.ts .",
|
||||
"watch": "tsx ../../../bin/build_package.ts . --watch",
|
||||
"test": "../../../bin/test_package.sh .",
|
||||
"check": "biome check",
|
||||
"ci": "biome ci"
|
||||
"build": "node ../../../bin/build_package.ts .",
|
||||
"watch": "node ../../../bin/build_package.ts . --watch",
|
||||
"test": "pnpm run test:unit",
|
||||
"test:unit": "../../../bin/unit_test_package.sh ."
|
||||
},
|
||||
"symfony": {
|
||||
"needsPackageAsADependency": false,
|
||||
@@ -41,8 +40,7 @@
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"jsdom": "^26.1.0",
|
||||
"tslib": "^2.8.1",
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export type MarkerDefinition<BridgeMarkerOptions, BridgeInfoWindowOptions> = Wit
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
icon?: Icon;
|
||||
/**
|
||||
* @deprecated Use "bridgeOptions" instead.
|
||||
* @deprecated since Symfony UX Map 2.27, use "bridgeOptions" instead.
|
||||
* Raw options passed to the marker constructor, specific to the map provider (e.g.: `L.marker()` for Leaflet).
|
||||
*/
|
||||
rawOptions?: BridgeMarkerOptions;
|
||||
@@ -86,9 +86,12 @@ export type MarkerDefinition<BridgeMarkerOptions, BridgeInfoWindowOptions> = Wit
|
||||
export type PolygonDefinition<BridgePolygonOptions, BridgeInfoWindowOptions> = WithIdentifier<{
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
points: Array<Point> | Array<Array<Point>>;
|
||||
/**
|
||||
* @deprecated since Symfony UX Map 2.29, use "infoWindow" instead
|
||||
*/
|
||||
title: string | null;
|
||||
/**
|
||||
* @deprecated Use "bridgeOptions" instead.
|
||||
* @deprecated since Symfony UX Map 2.27, use "bridgeOptions" instead.
|
||||
* Raw options passed to the polygon constructor, specific to the map provider (e.g.: `L.polygon()` for Leaflet).
|
||||
*/
|
||||
rawOptions?: BridgePolygonOptions;
|
||||
@@ -109,9 +112,12 @@ export type PolygonDefinition<BridgePolygonOptions, BridgeInfoWindowOptions> = W
|
||||
export type PolylineDefinition<BridgePolylineOptions, BridgeInfoWindowOptions> = WithIdentifier<{
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
points: Array<Point>;
|
||||
/**
|
||||
* @deprecated since Symfony UX Map 2.29, use "infoWindow" instead
|
||||
*/
|
||||
title: string | null;
|
||||
/**
|
||||
* @deprecated Use "bridgeOptions" instead.
|
||||
* @deprecated since Symfony UX Map 2.27, use "bridgeOptions" instead.
|
||||
* Raw options passed to the polyline constructor, specific to the map provider (e.g.: `L.polyline()` for Leaflet).
|
||||
*/
|
||||
rawOptions?: BridgePolylineOptions;
|
||||
@@ -133,9 +139,12 @@ export type CircleDefinition<BridgeCircleOptions, BridgeInfoWindowOptions> = Wit
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
center: Point;
|
||||
radius: number;
|
||||
/**
|
||||
* @deprecated since Symfony UX Map 2.29, use "infoWindow" instead
|
||||
*/
|
||||
title: string | null;
|
||||
/**
|
||||
* @deprecated Use "bridgeOptions" instead.
|
||||
* @deprecated since Symfony UX Map 2.27, use "bridgeOptions" instead.
|
||||
* Raw options passed to the circle constructor, specific to the map provider (e.g.: `L.circle()` for Leaflet).
|
||||
*/
|
||||
rawOptions?: BridgeCircleOptions;
|
||||
@@ -157,9 +166,12 @@ export type RectangleDefinition<BridgeRectangleOptions, BridgeInfoWindowOptions>
|
||||
infoWindow?: Omit<InfoWindowDefinition<BridgeInfoWindowOptions>, 'position'>;
|
||||
southWest: Point;
|
||||
northEast: Point;
|
||||
/**
|
||||
* @deprecated since Symfony UX Map 2.29, use "infoWindow" instead
|
||||
*/
|
||||
title: string | null;
|
||||
/**
|
||||
* @deprecated Use "bridgeOptions" instead.
|
||||
* @deprecated since Symfony UX Map 2.27, use "bridgeOptions" instead.
|
||||
* Raw options passed to the rectangle constructor, specific to the map provider (e.g.: `L.rectangle()` for Leaflet).
|
||||
*/
|
||||
rawOptions?: BridgeRectangleOptions;
|
||||
@@ -184,7 +196,7 @@ export type InfoWindowDefinition<BridgeInfoWindowOptions> = {
|
||||
opened: boolean;
|
||||
autoClose: boolean;
|
||||
/**
|
||||
* @deprecated Use "bridgeOptions" instead.
|
||||
* @deprecated since Symfony UX Map 2.27, use "bridgeOptions" instead.
|
||||
* Raw options passed to the info window constructor, specific to the map provider (e.g.: `google.maps.InfoWindow()` for Google Maps).
|
||||
*/
|
||||
rawOptions?: BridgeInfoWindowOptions;
|
||||
@@ -270,11 +282,31 @@ export default abstract class<
|
||||
protected infoWindows: Array<BridgeInfoWindow> = [];
|
||||
|
||||
private isConnected = false;
|
||||
private createMarker: ({ definition }: { definition: MarkerDefinition<BridgeMarkerOptions, BridgeInfoWindowOptions> }) => BridgeMarker;
|
||||
private createPolygon: ({ definition }: { definition: PolygonDefinition<BridgePolygonOptions, BridgeInfoWindowOptions> }) => BridgePolygon;
|
||||
private createPolyline: ({ definition }: { definition: PolylineDefinition<BridgePolylineOptions, BridgeInfoWindowOptions> }) => BridgePolyline;
|
||||
private createCircle: ({ definition }: { definition: CircleDefinition<BridgeCircleOptions, BridgeInfoWindowOptions> }) => BridgeCircle;
|
||||
private createRectangle: ({ definition }: { definition: RectangleDefinition<BridgeRectangleOptions, BridgeInfoWindowOptions> }) => BridgeRectangle;
|
||||
private createMarker: ({
|
||||
definition,
|
||||
}: {
|
||||
definition: MarkerDefinition<BridgeMarkerOptions, BridgeInfoWindowOptions>;
|
||||
}) => BridgeMarker;
|
||||
private createPolygon: ({
|
||||
definition,
|
||||
}: {
|
||||
definition: PolygonDefinition<BridgePolygonOptions, BridgeInfoWindowOptions>;
|
||||
}) => BridgePolygon;
|
||||
private createPolyline: ({
|
||||
definition,
|
||||
}: {
|
||||
definition: PolylineDefinition<BridgePolylineOptions, BridgeInfoWindowOptions>;
|
||||
}) => BridgePolyline;
|
||||
private createCircle: ({
|
||||
definition,
|
||||
}: {
|
||||
definition: CircleDefinition<BridgeCircleOptions, BridgeInfoWindowOptions>;
|
||||
}) => BridgeCircle;
|
||||
private createRectangle: ({
|
||||
definition,
|
||||
}: {
|
||||
definition: RectangleDefinition<BridgeRectangleOptions, BridgeInfoWindowOptions>;
|
||||
}) => BridgeRectangle;
|
||||
|
||||
protected abstract dispatchEvent(name: string, payload: Record<string, unknown>): void;
|
||||
|
||||
@@ -294,14 +326,28 @@ 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.createRectangle = this.createDrawingFactory(
|
||||
'rectangle',
|
||||
this.rectangles,
|
||||
this.doCreateRectangle.bind(this)
|
||||
);
|
||||
|
||||
this.map = this.doCreateMap({ definition: mapDefinition });
|
||||
this.markersValue.forEach((definition) => this.createMarker({ definition }));
|
||||
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 }));
|
||||
this.markersValue.forEach((definition) => {
|
||||
this.createMarker({ definition });
|
||||
});
|
||||
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();
|
||||
@@ -396,27 +442,51 @@ export default abstract class<
|
||||
//endregion
|
||||
|
||||
//region Abstract factory methods to be implemented by the concrete classes, they are specific to the map provider
|
||||
protected abstract doCreateMap({ definition }: { definition: MapDefinition<MapOptions, BridgeMapOptions> }): BridgeMap;
|
||||
protected abstract doCreateMap({
|
||||
definition,
|
||||
}: {
|
||||
definition: MapDefinition<MapOptions, BridgeMapOptions>;
|
||||
}): BridgeMap;
|
||||
|
||||
protected abstract doFitBoundsToMarkers(): void;
|
||||
|
||||
protected abstract doCreateMarker({ definition }: { definition: MarkerDefinition<BridgeMarkerOptions, BridgeInfoWindowOptions> }): BridgeMarker;
|
||||
protected abstract doCreateMarker({
|
||||
definition,
|
||||
}: {
|
||||
definition: MarkerDefinition<BridgeMarkerOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgeMarker;
|
||||
|
||||
protected abstract doRemoveMarker(marker: BridgeMarker): void;
|
||||
|
||||
protected abstract doCreatePolygon({ definition }: { definition: PolygonDefinition<BridgePolygonOptions, BridgeInfoWindowOptions> }): BridgePolygon;
|
||||
protected abstract doCreatePolygon({
|
||||
definition,
|
||||
}: {
|
||||
definition: PolygonDefinition<BridgePolygonOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgePolygon;
|
||||
|
||||
protected abstract doRemovePolygon(polygon: BridgePolygon): void;
|
||||
|
||||
protected abstract doCreatePolyline({ definition }: { definition: PolylineDefinition<BridgePolylineOptions, BridgeInfoWindowOptions> }): BridgePolyline;
|
||||
protected abstract doCreatePolyline({
|
||||
definition,
|
||||
}: {
|
||||
definition: PolylineDefinition<BridgePolylineOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgePolyline;
|
||||
|
||||
protected abstract doRemovePolyline(polyline: BridgePolyline): void;
|
||||
|
||||
protected abstract doCreateCircle({ definition }: { definition: CircleDefinition<BridgeCircleOptions, BridgeInfoWindowOptions> }): BridgeCircle;
|
||||
protected abstract doCreateCircle({
|
||||
definition,
|
||||
}: {
|
||||
definition: CircleDefinition<BridgeCircleOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgeCircle;
|
||||
|
||||
protected abstract doRemoveCircle(circle: BridgeCircle): void;
|
||||
|
||||
protected abstract doCreateRectangle({ definition }: { definition: RectangleDefinition<BridgeRectangleOptions, BridgeInfoWindowOptions> }): BridgeRectangle;
|
||||
protected abstract doCreateRectangle({
|
||||
definition,
|
||||
}: {
|
||||
definition: RectangleDefinition<BridgeRectangleOptions, BridgeInfoWindowOptions>;
|
||||
}): BridgeRectangle;
|
||||
|
||||
protected abstract doRemoveRectangle(rectangle: BridgeRectangle): void;
|
||||
|
||||
@@ -432,11 +502,31 @@ export default abstract class<
|
||||
//endregion
|
||||
|
||||
//region Private APIs
|
||||
private createDrawingFactory(type: 'marker', draws: typeof this.markers, factory: typeof this.doCreateMarker): typeof this.doCreateMarker;
|
||||
private createDrawingFactory(type: 'polygon', draws: typeof this.polygons, factory: typeof this.doCreatePolygon): typeof this.doCreatePolygon;
|
||||
private createDrawingFactory(type: 'polyline', draws: typeof this.polylines, factory: typeof this.doCreatePolyline): typeof this.doCreatePolyline;
|
||||
private createDrawingFactory(type: 'circle', 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(
|
||||
type: 'marker',
|
||||
draws: typeof this.markers,
|
||||
factory: typeof this.doCreateMarker
|
||||
): typeof this.doCreateMarker;
|
||||
private createDrawingFactory(
|
||||
type: 'polygon',
|
||||
draws: typeof this.polygons,
|
||||
factory: typeof this.doCreatePolygon
|
||||
): typeof this.doCreatePolygon;
|
||||
private createDrawingFactory(
|
||||
type: 'polyline',
|
||||
draws: typeof this.polylines,
|
||||
factory: typeof this.doCreatePolyline
|
||||
): typeof this.doCreatePolyline;
|
||||
private createDrawingFactory(
|
||||
type: 'circle',
|
||||
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
|
||||
@@ -445,7 +535,11 @@ export default abstract class<
|
||||
| typeof this.doCreateCircle
|
||||
| typeof this.doCreateRectangle,
|
||||
Draw extends ReturnType<Factory>,
|
||||
>(type: 'marker' | 'polygon' | 'polyline' | 'circle' | 'rectangle', draws: globalThis.Map<WithIdentifier<any>, Draw>, factory: Factory): Factory {
|
||||
>(
|
||||
type: 'marker' | 'polygon' | 'polyline' | 'circle' | 'rectangle',
|
||||
draws: globalThis.Map<WithIdentifier<any>, Draw>,
|
||||
factory: Factory
|
||||
): Factory {
|
||||
const eventBefore = `${type}:before-create`;
|
||||
const eventAfter = `${type}:after-create`;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Application } from '@hotwired/stimulus';
|
||||
import { getByTestId, waitFor } from '@testing-library/dom';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { clearDOM, mountDOM } from '../../../../test/stimulus-helpers';
|
||||
import AbstractMapController from '../src/abstract_map_controller.ts';
|
||||
import { clearDOM, mountDOM } from '../../../../../test/stimulus-helpers';
|
||||
import AbstractMapController from '../../src/abstract_map_controller.ts';
|
||||
|
||||
class MyMapController extends AbstractMapController {
|
||||
protected dispatchEvent(name: string, payload: Record<string, unknown> = {}): void {
|
||||
3
assets/tsconfig.json
Normal file
3
assets/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.package.json"
|
||||
}
|
||||
4
assets/vitest.config.mjs
Normal file
4
assets/vitest.config.mjs
Normal file
@@ -0,0 +1,4 @@
|
||||
import { mergeConfig } from 'vitest/config';
|
||||
import configShared from '../../../vitest.config.base.mjs';
|
||||
|
||||
export default mergeConfig(configShared, {});
|
||||
@@ -36,13 +36,13 @@
|
||||
"symfony/stimulus-bundle": "^2.18.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/asset-mapper": "^6.4|^7.0",
|
||||
"symfony/framework-bundle": "^6.4|^7.0",
|
||||
"symfony/phpunit-bridge": "^7.2",
|
||||
"symfony/twig-bundle": "^6.4|^7.0",
|
||||
"symfony/ux-twig-component": "^2.18",
|
||||
"symfony/asset-mapper": "^6.4|^7.0|^8.0",
|
||||
"symfony/framework-bundle": "^6.4|^7.0|^8.0",
|
||||
"symfony/phpunit-bridge": "^7.2|^8.0",
|
||||
"symfony/twig-bundle": "^6.4|^7.0|^8.0",
|
||||
"symfony/ux-twig-component": "^2.18|^8.0",
|
||||
"symfony/ux-icons": "^2.18",
|
||||
"spatie/phpunit-snapshot-assertions": "^4.2.17",
|
||||
"spatie/phpunit-snapshot-assertions": "^4.2.17|^5.2.3",
|
||||
"phpunit/phpunit": "^9.6.22"
|
||||
},
|
||||
"conflict": {
|
||||
|
||||
@@ -191,9 +191,9 @@ You can also add Polygons, which represents an area enclosed by a series of ``Po
|
||||
|
||||
`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::
|
||||
Since UX Map 2.26, you can create polygons with holes by using an array of ``array<Point>``::
|
||||
|
||||
// Draw a polygon with a hole in it, on the French map
|
||||
// Draw a polygon with a hole in it, on the France map
|
||||
$map->addPolygon(new Polygon(points: [
|
||||
// First path, the outer boundary of the polygon
|
||||
[
|
||||
@@ -203,7 +203,7 @@ Since UX Map 2.26, you can also create polygons with holes in them, by passing a
|
||||
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
|
||||
// Second path, making a hole in the first path
|
||||
[
|
||||
new Point(45.833619, 1.261105), // Limoges
|
||||
new Point(45.764043, 4.835659), // Lyon
|
||||
@@ -237,7 +237,6 @@ You can add Circles, which represents a circular area defined by a center point
|
||||
$map->addCircle(new Circle(
|
||||
center: new Point(48.8566, 2.3522),
|
||||
radius: 5_000, // 5km
|
||||
title: 'Paris',
|
||||
infoWindow: new InfoWindow(
|
||||
content: 'A 5km radius circle centered on Paris',
|
||||
),
|
||||
@@ -251,7 +250,6 @@ You can add Rectangles, which represents a rectangular area defined by two corne
|
||||
$map->addRectangle(new Rectangle(
|
||||
southWest: new Point(48.8566, 2.3522), // Paris
|
||||
northEast: new Point(50.6292, 3.0573), // Lille
|
||||
title: 'Paris to Lille',
|
||||
infoWindow: new InfoWindow(
|
||||
content: 'A rectangle from Paris to Lille',
|
||||
),
|
||||
@@ -294,6 +292,22 @@ If you haven't stored the element instance, you can still remove them by passing
|
||||
$map->removeCircle('my-circle');
|
||||
$map->removeRectangle('my-rectangle');
|
||||
|
||||
To remove all instances of a certain element, you can use the `Map::removeAll*()` methods::
|
||||
|
||||
// Add elements
|
||||
$map->addMarker($marker = new Marker(/* ... */));
|
||||
$map->addPolygon($polygon = new Polygon(/* ... */));
|
||||
$map->addPolyline($polyline = new Polyline(/* ... */));
|
||||
$map->addCircle($circle = new Circle(/* ... */));
|
||||
$map->addRectangle($rectangle = new Rectangle(/* ... */));
|
||||
|
||||
// And later, remove those elements
|
||||
$map->removeAllMarkers();
|
||||
$map->removeAllPolygons();
|
||||
$map->removeAllPolylines();
|
||||
$map->removeAllCircles();
|
||||
$map->removeAllRectangles();
|
||||
|
||||
Render a map
|
||||
------------
|
||||
|
||||
@@ -331,12 +345,17 @@ templates. The function accepts the same arguments as the ``Map`` class:
|
||||
infoWindow: { content: 'Welcome to <b>New York</b>' }
|
||||
},
|
||||
],
|
||||
fitBoundsToMarkers: true,
|
||||
attributes: {
|
||||
class: 'foo',
|
||||
style: 'height: 800px; width: 100%; border: 4px solid red; margin-block: 10vh;',
|
||||
}
|
||||
) }}
|
||||
|
||||
.. versionadded:: 2.31
|
||||
|
||||
`fitBoundsToMarkers` option for the twig function is available since UX Map 2.31.
|
||||
|
||||
Twig Component ``<twig:ux:map />``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -356,6 +375,7 @@ Alternatively, you can use the ``<twig:ux:map />`` component.
|
||||
"infoWindow": {"content": "Welcome to <b>New York</b>"}
|
||||
}
|
||||
]'
|
||||
:fitBoundsToMarkers="true",
|
||||
class="foo"
|
||||
style="height: 800px; width: 100%; border: 4px solid red; margin-block: 10vh;"
|
||||
/>
|
||||
@@ -366,6 +386,10 @@ The ``<twig:ux:map />`` component requires the `Twig Component`_ package.
|
||||
|
||||
$ composer require symfony/ux-twig-component
|
||||
|
||||
.. versionadded:: 2.31
|
||||
|
||||
`fitBoundsToMarkers` option for the twig component is available since UX Map 2.31.
|
||||
|
||||
Interact with the map
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -504,7 +528,7 @@ Symfony UX Map allows you to extend its default behavior using a custom Stimulus
|
||||
*/
|
||||
_onPolygonBeforeCreate(event) {
|
||||
console.log(event.detail.definition);
|
||||
// { title: 'My polygon', points: [ { lat: 48.8566, lng: 2.3522 }, { lat: 45.7640, lng: 4.8357 }, { lat: 43.2965, lng: 5.3698 }, ... ], ... }
|
||||
// { points: [ { lat: 48.8566, lng: 2.3522 }, { lat: 45.7640, lng: 4.8357 }, { lat: 43.2965, lng: 5.3698 }, ... ], ... }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,7 +546,7 @@ Symfony UX Map allows you to extend its default behavior using a custom Stimulus
|
||||
*/
|
||||
_onPolylineBeforeCreate(event) {
|
||||
console.log(event.detail.definition);
|
||||
// { title: 'My polyline', points: [ { lat: 48.8566, lng: 2.3522 }, { lat: 45.7640, lng: 4.8357 }, { lat: 43.2965, lng: 5.3698 }, ... ], ... }
|
||||
// { points: [ { lat: 48.8566, lng: 2.3522 }, { lat: 45.7640, lng: 4.8357 }, { lat: 43.2965, lng: 5.3698 }, ... ], ... }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -536,7 +560,7 @@ Symfony UX Map allows you to extend its default behavior using a custom Stimulus
|
||||
|
||||
_onCircleBeforeCreate(event) {
|
||||
console.log(event.detail.definition);
|
||||
// { title: 'My circle', center: { lat: 48.8566, lng: 2.3522 }, radius: 1000, ... }
|
||||
// { center: { lat: 48.8566, lng: 2.3522 }, radius: 1000, ... }
|
||||
}
|
||||
|
||||
_onCircleAfterCreate(event) {
|
||||
@@ -546,7 +570,7 @@ Symfony UX Map allows you to extend its default behavior using a custom Stimulus
|
||||
|
||||
_onRectangleBeforeCreate(event) {
|
||||
console.log(event.detail.definition);
|
||||
// { title: 'My rectangle', southWest: { lat: 48.8566, lng: 2.3522 }, northEast: { lat: 45.7640, lng: 4.8357 }, ... }
|
||||
// { southWest: { lat: 48.8566, lng: 2.3522 }, northEast: { lat: 45.7640, lng: 4.8357 }, ... }
|
||||
}
|
||||
|
||||
_onRectangleAfterCreate(event) {
|
||||
@@ -727,7 +751,7 @@ property available in ``Map``, ``Marker``, ``InfoWindow``, ``Polygon``, ``Polyli
|
||||
));
|
||||
|
||||
On the JavaScript side, you can access these extra data by listening to ``ux:map:pre-connect``,
|
||||
``ux:map:connect``, ``ux:map:*:before-create``, ``ux:map:*:after-create`` events::
|
||||
``ux:map:connect``, ``ux:map:*:before-create``, ``ux:map:*:after-create`` events:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
@@ -842,6 +866,45 @@ You can retrieve the map instance using the ``getMap()`` method, and change the
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Advanced: Clusters
|
||||
------------------
|
||||
|
||||
.. versionadded:: 2.29
|
||||
|
||||
Clusters were added in UX Map 2.29.
|
||||
|
||||
A cluster is a group of points that are close to each other on a map.
|
||||
|
||||
Clustering reduces clutter and improves performance when displaying many points.
|
||||
This makes maps easier to read and faster to render.
|
||||
|
||||
UX Map supports two algorithms:
|
||||
|
||||
- **Grid**: Fast, divides map into cells.
|
||||
- **Morton**: Uses Z-order curves for spatial locality.
|
||||
|
||||
Create a clustering algorithm, cluster your points, and add cluster markers::
|
||||
|
||||
use Symfony\UX\Map\Cluster\GridClusteringAlgorithm;
|
||||
use Symfony\UX\Map\Cluster\MortonClusteringAlgorithm;
|
||||
use Symfony\UX\Map\Point;
|
||||
|
||||
// Initialize clustering algorithm
|
||||
$clusteringAlgorithm = new GridClusteringAlgorithm();
|
||||
// or
|
||||
// $clusteringAlgorithm = new MortonClusteringAlgorithm();
|
||||
|
||||
// Create clusters of points
|
||||
$points = [new Point(48.8566, 2.3522), new Point(45.7640, 4.8357), /* ... */];
|
||||
$clusters = $clusteringAlgorithm->cluster($points, zoom: 5.0);
|
||||
|
||||
// Iterate over each cluster
|
||||
foreach ($clusters as $cluster) {
|
||||
$cluster->getCenter(); // A Point, representing the cluster center
|
||||
$cluster->getPoints(); // A list of Point
|
||||
$cluster->count(); // The number of points in the cluster
|
||||
}
|
||||
|
||||
Backward Compatibility promise
|
||||
------------------------------
|
||||
|
||||
|
||||
38
phpunit11.dist.xml
Normal file
38
phpunit11.dist.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
colors="true"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
failOnDeprecation="true"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1"/>
|
||||
<env name="SHELL_VERBOSITY" value="-1"/>
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Symfony UX Map Test Suite">
|
||||
<directory>./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<source
|
||||
ignoreSuppressionOfDeprecations="true"
|
||||
ignoreIndirectDeprecations="true"
|
||||
restrictNotices="true"
|
||||
restrictWarnings="true"
|
||||
>
|
||||
<include>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
|
||||
<deprecationTrigger>
|
||||
<function>trigger_deprecation</function>
|
||||
</deprecationTrigger>
|
||||
</source>
|
||||
</phpunit>
|
||||
@@ -34,6 +34,10 @@ final class Circle implements Element
|
||||
public readonly array $extra = [],
|
||||
public readonly ?string $id = null,
|
||||
) {
|
||||
if (null !== $title) {
|
||||
trigger_deprecation('symfony/ux-map', '2.30', 'The "title" parameter is deprecated and will be removed in 3.0. Use "infoWindow" instead.');
|
||||
}
|
||||
|
||||
if ($radius <= 0) {
|
||||
throw new InvalidArgumentException(\sprintf('Radius must be greater than 0, "%s" given.', $radius));
|
||||
}
|
||||
|
||||
81
src/Cluster/Cluster.php
Normal file
81
src/Cluster/Cluster.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?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\Cluster;
|
||||
|
||||
use Symfony\UX\Map\Point;
|
||||
|
||||
/**
|
||||
* Cluster representation.
|
||||
*
|
||||
* @implements \IteratorAggregate<int, Point>
|
||||
*
|
||||
* @author Simon André <smn.andre@gmail.com>
|
||||
*/
|
||||
final class Cluster implements \Countable, \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var Point[]
|
||||
*/
|
||||
private array $points = [];
|
||||
|
||||
private float $sumLat = 0.0;
|
||||
private float $sumLng = 0.0;
|
||||
private int $count = 0;
|
||||
|
||||
/**
|
||||
* Initializes the cluster with an initial point.
|
||||
*/
|
||||
public function __construct(Point $initialPoint)
|
||||
{
|
||||
$this->addPoint($initialPoint);
|
||||
}
|
||||
|
||||
public function addPoint(Point $point): void
|
||||
{
|
||||
$this->points[] = $point;
|
||||
$this->sumLat += $point->getLatitude();
|
||||
$this->sumLng += $point->getLongitude();
|
||||
++$this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the center of the cluster as a Point.
|
||||
*/
|
||||
public function getCenter(): Point
|
||||
{
|
||||
return new Point($this->sumLat / $this->count, $this->sumLng / $this->count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<Point>
|
||||
*/
|
||||
public function getPoints(): array
|
||||
{
|
||||
return $this->points;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of points in the cluster.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Traversable<int, Point>
|
||||
*/
|
||||
public function getIterator(): \Traversable
|
||||
{
|
||||
return new \ArrayIterator($this->points);
|
||||
}
|
||||
}
|
||||
30
src/Cluster/ClusteringAlgorithmInterface.php
Normal file
30
src/Cluster/ClusteringAlgorithmInterface.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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\Cluster;
|
||||
|
||||
use Symfony\UX\Map\Point;
|
||||
|
||||
/**
|
||||
* Interface for various Clustering implementations.
|
||||
*/
|
||||
interface ClusteringAlgorithmInterface
|
||||
{
|
||||
/**
|
||||
* Clusters a set of points.
|
||||
*
|
||||
* @param Point[] $points List of points to be clustered
|
||||
* @param float $zoom The zoom level, determining grid resolution
|
||||
*
|
||||
* @return Cluster[] An array of clusters, each containing grouped points
|
||||
*/
|
||||
public function cluster(array $points, float $zoom): array;
|
||||
}
|
||||
67
src/Cluster/GridClusteringAlgorithm.php
Normal file
67
src/Cluster/GridClusteringAlgorithm.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?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\Cluster;
|
||||
|
||||
use Symfony\UX\Map\Point;
|
||||
|
||||
/**
|
||||
* Grid-based clustering algorithm for spatial data.
|
||||
*
|
||||
* This algorithm groups points into fixed-size grid cells based on the given zoom level.
|
||||
*
|
||||
* Best for:
|
||||
* - Fast, scalable clustering on large geographical datasets
|
||||
* - Real-time clustering where performance is critical
|
||||
* - Use cases where a simple, predictable grid structure is sufficient
|
||||
*
|
||||
* Slower for:
|
||||
* - Highly dynamic data that requires adaptive cluster sizes
|
||||
* - Scenarios where varying density should influence cluster sizes (e.g., DBSCAN-like approaches)
|
||||
* - Irregularly shaped clusters that do not fit a strict grid pattern
|
||||
*
|
||||
* @author Simon André <smn.andre@gmail.com>
|
||||
*/
|
||||
final class GridClusteringAlgorithm implements ClusteringAlgorithmInterface
|
||||
{
|
||||
/**
|
||||
* Clusters a set of points using a fixed grid resolution based on the zoom level.
|
||||
*
|
||||
* @param Point[] $points List of points to be clustered
|
||||
* @param float $zoom The zoom level, determining grid resolution
|
||||
*
|
||||
* @return Cluster[] An array of clusters, each containing grouped points
|
||||
*/
|
||||
public function cluster(iterable $points, float $zoom): array
|
||||
{
|
||||
$gridResolution = 1 << (int) $zoom;
|
||||
$gridSize = 360 / $gridResolution;
|
||||
$invGridSize = 1 / $gridSize;
|
||||
|
||||
$cells = [];
|
||||
|
||||
foreach ($points as $point) {
|
||||
$lng = $point->getLongitude();
|
||||
$lat = $point->getLatitude();
|
||||
$gridX = (int) (($lng + 180) * $invGridSize);
|
||||
$gridY = (int) (($lat + 90) * $invGridSize);
|
||||
$key = ($gridX << 16) | $gridY;
|
||||
|
||||
if (!isset($cells[$key])) {
|
||||
$cells[$key] = new Cluster($point);
|
||||
} else {
|
||||
$cells[$key]->addPoint($point);
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($cells);
|
||||
}
|
||||
}
|
||||
76
src/Cluster/MortonClusteringAlgorithm.php
Normal file
76
src/Cluster/MortonClusteringAlgorithm.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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\Cluster;
|
||||
|
||||
use Symfony\UX\Map\Point;
|
||||
|
||||
/**
|
||||
* Clustering algorithm based on Morton codes (Z-order curves).
|
||||
*
|
||||
* This approach is optimized for spatial data and preserves locality efficiently.
|
||||
*
|
||||
* Best for:
|
||||
* - Large-scale spatial clustering
|
||||
* - Hierarchical clustering with fast locality-based grouping
|
||||
* - Datasets where preserving spatial proximity is crucial
|
||||
*
|
||||
* Slower for:
|
||||
* - High-dimensional data (beyond 2D/3D) due to Morton code limitations
|
||||
* - Non-spatial or categorical data
|
||||
* - Scenarios requiring dynamic cluster adjustments (e.g., streaming data)
|
||||
*
|
||||
* @author Simon André <smn.andre@gmail.com>
|
||||
*/
|
||||
final class MortonClusteringAlgorithm implements ClusteringAlgorithmInterface
|
||||
{
|
||||
/**
|
||||
* @param Point[] $points
|
||||
*
|
||||
* @return Cluster[]
|
||||
*/
|
||||
public function cluster(iterable $points, float $zoom): array
|
||||
{
|
||||
$resolution = 1 << (int) $zoom;
|
||||
$clustersMap = [];
|
||||
|
||||
foreach ($points as $point) {
|
||||
$xNorm = ($point->getLatitude() + 180) / 360;
|
||||
$yNorm = ($point->getLongitude() + 90) / 180;
|
||||
|
||||
$x = (int) floor($xNorm * $resolution);
|
||||
$y = (int) floor($yNorm * $resolution);
|
||||
|
||||
$x &= 0xFFFF;
|
||||
$y &= 0xFFFF;
|
||||
|
||||
$x = ($x | ($x << 8)) & 0x00FF00FF;
|
||||
$x = ($x | ($x << 4)) & 0x0F0F0F0F;
|
||||
$x = ($x | ($x << 2)) & 0x33333333;
|
||||
$x = ($x | ($x << 1)) & 0x55555555;
|
||||
|
||||
$y = ($y | ($y << 8)) & 0x00FF00FF;
|
||||
$y = ($y | ($y << 4)) & 0x0F0F0F0F;
|
||||
$y = ($y | ($y << 2)) & 0x33333333;
|
||||
$y = ($y | ($y << 1)) & 0x55555555;
|
||||
|
||||
$code = ($y << 1) | $x;
|
||||
|
||||
if (!isset($clustersMap[$code])) {
|
||||
$clustersMap[$code] = new Cluster($point);
|
||||
} else {
|
||||
$clustersMap[$code]->addPoint($point);
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($clustersMap);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ use Symfony\UX\Map\Point;
|
||||
final class HaversineDistanceCalculator implements DistanceCalculatorInterface
|
||||
{
|
||||
/**
|
||||
* @const float The Earth's radius in meters.
|
||||
* @var float the Earth's radius in meters
|
||||
*/
|
||||
private const EARTH_RADIUS = 6371000.0;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use Symfony\UX\Map\Point;
|
||||
final class SphericalCosineDistanceCalculator implements DistanceCalculatorInterface
|
||||
{
|
||||
/**
|
||||
* @const float The Earth's radius in meters.
|
||||
* @var float the Earth's radius in meters
|
||||
*/
|
||||
private const EARTH_RADIUS = 6371000.0;
|
||||
|
||||
|
||||
@@ -27,18 +27,18 @@ abstract class Elements
|
||||
) {
|
||||
$this->elements = new \SplObjectStorage();
|
||||
foreach ($elements as $element) {
|
||||
$this->elements->attach($element);
|
||||
$this->add($element);
|
||||
}
|
||||
}
|
||||
|
||||
public function add(Element $element): static
|
||||
{
|
||||
$this->elements->attach($element, $element->id ?? $this->elements->getHash($element));
|
||||
$this->elements[$element] = $element->id ?? $this->elements->getHash($element);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getElement(string $id): ?Element
|
||||
private function getElementById(string $id): ?Element
|
||||
{
|
||||
foreach ($this->elements as $element) {
|
||||
if ($element->id === $id) {
|
||||
@@ -52,16 +52,21 @@ abstract class Elements
|
||||
public function remove(Element|string $elementOrId): static
|
||||
{
|
||||
if (\is_string($elementOrId)) {
|
||||
$elementOrId = $this->getElement($elementOrId);
|
||||
$element = $this->getElementById($elementOrId);
|
||||
} else {
|
||||
$element = $elementOrId;
|
||||
}
|
||||
|
||||
if (null === $elementOrId) {
|
||||
return $this;
|
||||
if (null !== $element && $this->elements->offsetExists($element)) {
|
||||
unset($this->elements[$element]);
|
||||
}
|
||||
|
||||
if ($this->elements->contains($elementOrId)) {
|
||||
$this->elements->detach($elementOrId);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAll(): static
|
||||
{
|
||||
$this->elements->removeAll($this->elements);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
35
src/Map.php
35
src/Map.php
@@ -133,6 +133,13 @@ final class Map
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAllMarkers(): self
|
||||
{
|
||||
$this->markers->removeAll();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addPolygon(Polygon $polygon): self
|
||||
{
|
||||
$this->polygons->add($polygon);
|
||||
@@ -147,6 +154,13 @@ final class Map
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAllPolygons(): self
|
||||
{
|
||||
$this->polygons->removeAll();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addPolyline(Polyline $polyline): self
|
||||
{
|
||||
$this->polylines->add($polyline);
|
||||
@@ -161,6 +175,13 @@ final class Map
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAllPolylines(): self
|
||||
{
|
||||
$this->polylines->removeAll();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addCircle(Circle $circle): self
|
||||
{
|
||||
$this->circles->add($circle);
|
||||
@@ -175,6 +196,13 @@ final class Map
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAllCircles(): self
|
||||
{
|
||||
$this->circles->removeAll();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addRectangle(Rectangle $rectangle): self
|
||||
{
|
||||
$this->rectangles->add($rectangle);
|
||||
@@ -189,6 +217,13 @@ final class Map
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAllRectangles(): self
|
||||
{
|
||||
$this->rectangles->removeAll();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $extra Extra data forwarded to the JavaScript side. It can be used in your custom
|
||||
* Stimulus controller to benefit from greater flexibility and customization.
|
||||
|
||||
@@ -33,6 +33,9 @@ final class Polygon implements Element
|
||||
private readonly array $extra = [],
|
||||
public readonly ?string $id = null,
|
||||
) {
|
||||
if (null !== $title) {
|
||||
trigger_deprecation('symfony/ux-map', '2.30', 'The "title" parameter is deprecated and will be removed in 3.0. Use "infoWindow" instead.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,8 +53,8 @@ final class Polygon implements Element
|
||||
{
|
||||
return [
|
||||
'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),
|
||||
? array_map(static fn (Point $point) => $point->toArray(), $this->points)
|
||||
: array_map(static fn (array $path) => array_map(static fn (Point $point) => $point->toArray(), $path), $this->points),
|
||||
'title' => $this->title,
|
||||
'infoWindow' => $this->infoWindow?->toArray(),
|
||||
'extra' => $this->extra,
|
||||
@@ -78,7 +81,7 @@ final class Polygon implements Element
|
||||
|
||||
$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']);
|
||||
: array_map(static fn (array $points) => array_map(Point::fromArray(...), $points), $polygon['points']);
|
||||
|
||||
if (isset($polygon['infoWindow'])) {
|
||||
$polygon['infoWindow'] = InfoWindow::fromArray($polygon['infoWindow']);
|
||||
|
||||
@@ -32,6 +32,9 @@ final class Polyline implements Element
|
||||
private readonly array $extra = [],
|
||||
public readonly ?string $id = null,
|
||||
) {
|
||||
if (null !== $title) {
|
||||
trigger_deprecation('symfony/ux-map', '2.30', 'The "title" parameter is deprecated and will be removed in 3.0. Use "infoWindow" instead.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +51,7 @@ final class Polyline implements Element
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'points' => array_map(fn (Point $point) => $point->toArray(), $this->points),
|
||||
'points' => array_map(static fn (Point $point) => $point->toArray(), $this->points),
|
||||
'title' => $this->title,
|
||||
'infoWindow' => $this->infoWindow?->toArray(),
|
||||
'extra' => $this->extra,
|
||||
|
||||
@@ -33,6 +33,9 @@ final class Rectangle implements Element
|
||||
public readonly array $extra = [],
|
||||
public readonly ?string $id = null,
|
||||
) {
|
||||
if (null !== $title) {
|
||||
trigger_deprecation('symfony/ux-map', '2.30', 'The "title" parameter is deprecated and will be removed in 3.0. Use "infoWindow" instead.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -87,7 +87,7 @@ abstract class AbstractRenderer implements RendererInterface
|
||||
|
||||
private function getMapAttributes(Map $map): array
|
||||
{
|
||||
$computeId = fn (array $array) => hash('xxh3', json_encode($array, \JSON_THROW_ON_ERROR));
|
||||
$computeId = static fn (array $array) => hash('xxh3', json_encode($array, \JSON_THROW_ON_ERROR));
|
||||
|
||||
$attrs = $map->toArray();
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ final class NullRenderer implements RendererInterface
|
||||
{
|
||||
$message = 'You must install at least one bridge package to use the Symfony UX Map component.';
|
||||
if ($this->availableBridges) {
|
||||
$message .= \PHP_EOL.'Try running '.implode(' or ', array_map(fn ($name) => \sprintf('"composer require %s"', $name), $this->availableBridges)).'.';
|
||||
$message .= \PHP_EOL.'Try running '.implode(' or ', array_map(static fn ($name) => \sprintf('"composer require %s"', $name), $this->availableBridges)).'.';
|
||||
}
|
||||
|
||||
throw new LogicException($message);
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Symfony\UX\Map\Test;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Spatie\Snapshots\Drivers\TextDriver;
|
||||
use Spatie\Snapshots\MatchesSnapshots;
|
||||
use Symfony\UX\Map\Elements;
|
||||
use Symfony\UX\Map\Map;
|
||||
@@ -40,7 +41,7 @@ abstract class RendererTestCase extends TestCase
|
||||
$rendered = $this->prettify($rendered);
|
||||
|
||||
$this->assertElementsHaveComputedId($rendered);
|
||||
$this->assertMatchesSnapshot($rendered);
|
||||
$this->assertMatchesSnapshot($rendered, new TextDriver());
|
||||
}
|
||||
|
||||
private function prettify(string $html): string
|
||||
@@ -77,9 +78,8 @@ abstract class RendererTestCase extends TestCase
|
||||
throw new \LogicException(\sprintf('Failed to parse the rendered HTML for property "%s".', $property));
|
||||
} elseif (0 === $matchesResult) {
|
||||
throw new \LogicException(\sprintf('It looks like the property "%s" is missing from "Map::toArray()" normalization.', $property));
|
||||
} else {
|
||||
$htmlAttributes[$property] = $matches[1];
|
||||
}
|
||||
$htmlAttributes[$property] = $matches[1];
|
||||
}
|
||||
|
||||
// Check that each property has a computed "@id" attribute
|
||||
|
||||
@@ -53,9 +53,10 @@ final class MapRuntime implements RuntimeExtensionInterface
|
||||
?array $rectangles = null,
|
||||
?float $minZoom = null,
|
||||
?float $maxZoom = null,
|
||||
?bool $fitBoundsToMarkers = null,
|
||||
): string {
|
||||
if ($map instanceof Map) {
|
||||
if (null !== $center || null !== $zoom || $markers || $polygons || $polylines || $circles || $rectangles || $minZoom || $maxZoom) {
|
||||
if (null !== $center || null !== $zoom || $markers || $polygons || $polylines || $circles || $rectangles || $minZoom || $maxZoom || null !== $fitBoundsToMarkers) {
|
||||
throw new \InvalidArgumentException('It is not allowed to pass both a Map object and other parameters (like "center", "zoom", "markers", etc...) to the "renderMap" method. Please use either a Map object or the individual parameters.');
|
||||
}
|
||||
|
||||
@@ -90,13 +91,16 @@ final class MapRuntime implements RuntimeExtensionInterface
|
||||
if (null !== $maxZoom) {
|
||||
$map->maxZoom($maxZoom);
|
||||
}
|
||||
if (null !== $fitBoundsToMarkers) {
|
||||
$map->fitBoundsToMarkers($fitBoundsToMarkers);
|
||||
}
|
||||
|
||||
return $this->renderer->renderMap($map, $attributes);
|
||||
}
|
||||
|
||||
public function render(array $args = []): string
|
||||
{
|
||||
$map = array_intersect_key($args, array_flip(['map', 'center', 'zoom', 'markers', 'polygons', 'polylines', 'circles', 'rectangles', 'minZoom', 'maxZoom']));
|
||||
$map = array_intersect_key($args, array_flip(['map', 'center', 'zoom', 'markers', 'polygons', 'polylines', 'circles', 'rectangles', 'minZoom', 'maxZoom', 'fitBoundsToMarkers']));
|
||||
$attributes = array_diff_key($args, $map);
|
||||
|
||||
return $this->renderMap(...$map, attributes: $attributes);
|
||||
|
||||
@@ -33,6 +33,8 @@ final class UXMapComponent
|
||||
|
||||
public ?Point $center;
|
||||
|
||||
public ?bool $fitBoundsToMarkers;
|
||||
|
||||
/**
|
||||
* @var Marker[]
|
||||
*/
|
||||
|
||||
@@ -69,7 +69,7 @@ final class UXMapBundle extends AbstractBundle
|
||||
if (str_starts_with($config['renderer'], 'null://')) {
|
||||
$container->services()
|
||||
->set('ux_map.renderer_factory.null', NullRendererFactory::class)
|
||||
->arg(0, array_map(fn ($name) => 'symfony/ux-'.$name.'-map', array_keys(self::$bridges)))
|
||||
->arg(0, array_map(static fn ($name) => 'symfony/ux-'.$name.'-map', array_keys(self::$bridges)))
|
||||
->tag('ux_map.renderer_factory');
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ class CircleTest extends TestCase
|
||||
$circle = new Circle(
|
||||
center: $center,
|
||||
radius: 500,
|
||||
title: 'Test Circle',
|
||||
infoWindow: $infoWindow,
|
||||
extra: ['foo' => 'bar'],
|
||||
id: 'circle1'
|
||||
@@ -37,7 +36,7 @@ class CircleTest extends TestCase
|
||||
self::assertSame([
|
||||
'center' => ['lat' => 1.1, 'lng' => 2.2],
|
||||
'radius' => 500.0,
|
||||
'title' => 'Test Circle',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => 'info content',
|
||||
'content' => null,
|
||||
@@ -56,7 +55,7 @@ class CircleTest extends TestCase
|
||||
$data = [
|
||||
'center' => ['lat' => 1.1, 'lng' => 2.2],
|
||||
'radius' => 500,
|
||||
'title' => 'Test Circle',
|
||||
'title' => null,
|
||||
'infoWindow' => ['content' => 'info content'],
|
||||
'extra' => ['foo' => 'bar'],
|
||||
'id' => 'circle1',
|
||||
@@ -70,7 +69,7 @@ class CircleTest extends TestCase
|
||||
self::assertSame([
|
||||
'center' => ['lat' => 1.1, 'lng' => 2.2],
|
||||
'radius' => 500.0,
|
||||
'title' => 'Test Circle',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => null,
|
||||
'content' => 'info content',
|
||||
|
||||
79
tests/Cluster/ClusterTest.php
Normal file
79
tests/Cluster/ClusterTest.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?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\Cluster;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Cluster\Cluster;
|
||||
use Symfony\UX\Map\Point;
|
||||
|
||||
class ClusterTest extends TestCase
|
||||
{
|
||||
public function testAddPointAndGetCenter(): void
|
||||
{
|
||||
$point1 = new Point(10.0, 20.0);
|
||||
$cluster = new Cluster($point1);
|
||||
|
||||
$this->assertEquals(10.0, $cluster->getCenter()->getLatitude());
|
||||
$this->assertEquals(20.0, $cluster->getCenter()->getLongitude());
|
||||
|
||||
$point2 = new Point(12.0, 22.0);
|
||||
$cluster->addPoint($point2);
|
||||
|
||||
$this->assertEquals(11.0, $cluster->getCenter()->getLatitude());
|
||||
$this->assertEquals(21.0, $cluster->getCenter()->getLongitude());
|
||||
}
|
||||
|
||||
public function testGetPoints(): void
|
||||
{
|
||||
$point1 = new Point(10.0, 20.0);
|
||||
$point2 = new Point(12.0, 22.0);
|
||||
$cluster = new Cluster($point1);
|
||||
$cluster->addPoint($point2);
|
||||
|
||||
$points = $cluster->getPoints();
|
||||
$this->assertCount(2, $points);
|
||||
$this->assertSame($point1, $points[0]);
|
||||
$this->assertSame($point2, $points[1]);
|
||||
}
|
||||
|
||||
public function testCount(): void
|
||||
{
|
||||
$cluster = new Cluster(new Point(10.0, 20.0));
|
||||
$this->assertCount(1, $cluster);
|
||||
|
||||
$cluster->addPoint(new Point(10.0, 20.0));
|
||||
$this->assertCount(2, $cluster);
|
||||
}
|
||||
|
||||
public function testIterator(): void
|
||||
{
|
||||
$point1 = new Point(10.0, 20.0);
|
||||
$point2 = new Point(12.0, 22.0);
|
||||
$cluster = new Cluster($point1);
|
||||
$cluster->addPoint($point2);
|
||||
|
||||
$points = iterator_to_array($cluster);
|
||||
$this->assertCount(2, $points);
|
||||
$this->assertSame($point1, $points[0]);
|
||||
$this->assertSame($point2, $points[1]);
|
||||
}
|
||||
|
||||
public function testCreateCluster(): void
|
||||
{
|
||||
$point1 = new Point(10.0, 20.0);
|
||||
$cluster = new Cluster($point1);
|
||||
|
||||
$this->assertCount(1, $cluster->getPoints());
|
||||
$this->assertEquals(10.0, $cluster->getCenter()->getLatitude());
|
||||
$this->assertEquals(20.0, $cluster->getCenter()->getLongitude());
|
||||
}
|
||||
}
|
||||
110
tests/Cluster/ClusteringPerformanceTest.php
Normal file
110
tests/Cluster/ClusteringPerformanceTest.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?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\Cluster;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Cluster\ClusteringAlgorithmInterface;
|
||||
use Symfony\UX\Map\Cluster\GridClusteringAlgorithm;
|
||||
use Symfony\UX\Map\Cluster\MortonClusteringAlgorithm;
|
||||
use Symfony\UX\Map\Point;
|
||||
|
||||
class ClusteringPerformanceTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var array<float>
|
||||
*/
|
||||
private const ZOOMS = [
|
||||
2.0,
|
||||
5.0,
|
||||
8.0,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
private const ALGORITHMS = [
|
||||
GridClusteringAlgorithm::class,
|
||||
MortonClusteringAlgorithm::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @return iterable<array{0: ClusteringAlgorithmInterface, 1: float}>
|
||||
*/
|
||||
public static function algorithmProvider(): iterable
|
||||
{
|
||||
foreach (self::ZOOMS as $zoom) {
|
||||
foreach (self::ALGORITHMS as $algorithm) {
|
||||
yield $algorithm.' '.$zoom => [new $algorithm(), $zoom];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario 1: Large number of points (50,000), concentrated area (Paris region).
|
||||
*
|
||||
* @dataProvider algorithmProvider
|
||||
*/
|
||||
public function testScenarioRegion50000(ClusteringAlgorithmInterface $algorithm, float $zoom)
|
||||
{
|
||||
$points = $this->generatePoints(50000, 48.8, 49, 2.2, 2.5);
|
||||
|
||||
$this->runPerformanceTest($algorithm, $points, $zoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario 2: Moderate number of points (5,000), broad area (France and surroundings).
|
||||
*
|
||||
* @dataProvider algorithmProvider
|
||||
*/
|
||||
public function testScenarioCountry5000(ClusteringAlgorithmInterface $algorithm, float $zoom)
|
||||
{
|
||||
$points = $this->generatePoints(5000, 30, 60, -10, 35);
|
||||
|
||||
$this->runPerformanceTest($algorithm, $points, $zoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario 3: Very large number of points (100,000), global distribution.
|
||||
*
|
||||
* @dataProvider algorithmProvider
|
||||
*/
|
||||
public function testScenarioWorld100000(ClusteringAlgorithmInterface $algorithm, float $zoom)
|
||||
{
|
||||
$points = $this->generatePoints(100000, -90, 90, -180, 180);
|
||||
|
||||
$this->runPerformanceTest($algorithm, $points, $zoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Point> $points
|
||||
*/
|
||||
private function runPerformanceTest(ClusteringAlgorithmInterface $algorithm, array $points, float $zoom): void
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$algorithm->cluster($points, $zoom);
|
||||
$elapsed = microtime(true) - $startTime;
|
||||
|
||||
$this->assertLessThan(2.0, $elapsed, $algorithm::class." took too long: {$elapsed} seconds (zoom {$zoom}, ".\count($points).' points)');
|
||||
}
|
||||
|
||||
private function generatePoints(int $count, float $latMin, float $latMax, float $lngMin, float $lngMax): array
|
||||
{
|
||||
$points = [];
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
$lat = random_int((int) ($latMin * 100), (int) ($latMax * 100)) / 100.0;
|
||||
$lng = random_int((int) ($lngMin * 100), (int) ($lngMax * 100)) / 100.0;
|
||||
$points[] = new Point($lat, $lng);
|
||||
}
|
||||
|
||||
return $points;
|
||||
}
|
||||
}
|
||||
98
tests/Cluster/GridClusteringAlgorithmTest.php
Normal file
98
tests/Cluster/GridClusteringAlgorithmTest.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?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\Cluster;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Cluster\Cluster;
|
||||
use Symfony\UX\Map\Cluster\GridClusteringAlgorithm;
|
||||
use Symfony\UX\Map\Point;
|
||||
|
||||
class GridClusteringAlgorithmTest extends TestCase
|
||||
{
|
||||
public function testSinglePointCreatesSingleCluster(): void
|
||||
{
|
||||
$point = new Point(10.0, 20.0);
|
||||
$algorithm = new GridClusteringAlgorithm();
|
||||
$clusters = $algorithm->cluster([$point], 1.0);
|
||||
|
||||
$this->assertCount(1, $clusters);
|
||||
|
||||
/** @var Cluster $cluster */
|
||||
$cluster = $clusters[0];
|
||||
|
||||
$this->assertEquals(10.0, $cluster->getCenter()->getLatitude());
|
||||
$this->assertEquals(20.0, $cluster->getCenter()->getLongitude());
|
||||
$this->assertCount(1, $cluster->getPoints());
|
||||
}
|
||||
|
||||
public function testPointsInSameGridAreClusteredTogether(): void
|
||||
{
|
||||
$point1 = new Point(10.0, 20.0);
|
||||
$point2 = new Point(10.1, 20.1);
|
||||
$algorithm = new GridClusteringAlgorithm();
|
||||
|
||||
$clusters = $algorithm->cluster([$point1, $point2], 1.0);
|
||||
|
||||
$this->assertCount(1, $clusters, 'One cluster should have been created due to the low zoom value.');
|
||||
|
||||
$cluster = $clusters[0];
|
||||
|
||||
$this->assertCount(2, $cluster->getPoints());
|
||||
$this->assertEqualsWithDelta(10.05, $cluster->getCenter()->getLatitude(), 0.0001);
|
||||
$this->assertEqualsWithDelta(20.05, $cluster->getCenter()->getLongitude(), 0.0001);
|
||||
}
|
||||
|
||||
public function testPointsInDifferentGridsAreNotClustered(): void
|
||||
{
|
||||
$point1 = new Point(10.0, 20.0);
|
||||
$point2 = new Point(-10.0, -20.0); // Far away
|
||||
$algorithm = new GridClusteringAlgorithm();
|
||||
|
||||
$clusters = $algorithm->cluster([$point1, $point2], 5.0);
|
||||
|
||||
$this->assertCount(2, $clusters, 'Two clusters should have created due to the high zoom value.');
|
||||
}
|
||||
|
||||
public function testEmptyPointsArray(): void
|
||||
{
|
||||
$algorithm = new GridClusteringAlgorithm();
|
||||
|
||||
// Empty points array
|
||||
$clusters = $algorithm->cluster([], 2.0);
|
||||
|
||||
$this->assertCount(0, $clusters);
|
||||
}
|
||||
|
||||
public function testLargeCoordinates(): void
|
||||
{
|
||||
$point1 = new Point(89.9, 179.9);
|
||||
$point2 = new Point(-89.9, -179.9);
|
||||
$algorithm = new GridClusteringAlgorithm();
|
||||
|
||||
$clusters = $algorithm->cluster([$point1, $point2], 3.0);
|
||||
|
||||
$this->assertGreaterThanOrEqual(1, \count($clusters));
|
||||
}
|
||||
|
||||
public function testZeroZoomLevel(): void
|
||||
{
|
||||
$point1 = new Point(10, 20);
|
||||
$point2 = new Point(30, 40);
|
||||
$algorithm = new GridClusteringAlgorithm();
|
||||
|
||||
// With zoom 0, everything should be in one big cluster.
|
||||
$clusters = $algorithm->cluster([$point1, $point2], 0.0);
|
||||
|
||||
$this->assertCount(1, $clusters);
|
||||
$this->assertCount(2, $clusters[0]->getPoints());
|
||||
}
|
||||
}
|
||||
83
tests/Cluster/MortonClusteringAlgorithmTest.php
Normal file
83
tests/Cluster/MortonClusteringAlgorithmTest.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?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\Cluster;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Cluster\Cluster;
|
||||
use Symfony\UX\Map\Cluster\MortonClusteringAlgorithm;
|
||||
use Symfony\UX\Map\Point;
|
||||
|
||||
class MortonClusteringAlgorithmTest extends TestCase
|
||||
{
|
||||
public function testSinglePointCreatesSingleCluster(): void
|
||||
{
|
||||
$point = new Point(10.0, 20.0);
|
||||
$algorithm = new MortonClusteringAlgorithm();
|
||||
$clusters = $algorithm->cluster([$point], 1.0);
|
||||
|
||||
$this->assertCount(1, $clusters);
|
||||
|
||||
/** @var Cluster $cluster */
|
||||
$cluster = $clusters[0];
|
||||
|
||||
$this->assertEquals(10.0, $cluster->getCenter()->getLatitude());
|
||||
$this->assertEquals(20.0, $cluster->getCenter()->getLongitude());
|
||||
$this->assertCount(1, $cluster->getPoints());
|
||||
}
|
||||
|
||||
public function testPointsWithSameMortonCodeAreClustered(): void
|
||||
{
|
||||
// These points should have the same Morton code at zoom level 1
|
||||
$point1 = new Point(45.0, 90.0);
|
||||
$point2 = new Point(45.1, 90.1);
|
||||
$algorithm = new MortonClusteringAlgorithm();
|
||||
|
||||
$clusters = $algorithm->cluster([$point1, $point2], 1.0);
|
||||
|
||||
$this->assertCount(1, $clusters);
|
||||
$this->assertCount(2, $clusters[0]->getPoints());
|
||||
}
|
||||
|
||||
public function testPointsWithDifferentMortonCodeAreNotClustered(): void
|
||||
{
|
||||
// These points will have different Morton codes at zoom level 5
|
||||
$point1 = new Point(45.0, 90.0);
|
||||
$point2 = new Point(-45.0, -90.0);
|
||||
$algorithm = new MortonClusteringAlgorithm();
|
||||
|
||||
$clusters = $algorithm->cluster([$point1, $point2], 5.0);
|
||||
|
||||
$this->assertCount(2, $clusters);
|
||||
}
|
||||
|
||||
public function testEmptyPointsArray(): void
|
||||
{
|
||||
$algorithm = new MortonClusteringAlgorithm();
|
||||
|
||||
$clusters = $algorithm->cluster([], 2.0);
|
||||
|
||||
$this->assertCount(0, $clusters);
|
||||
}
|
||||
|
||||
public function testZeroZoomLevel(): void
|
||||
{
|
||||
$point1 = new Point(10, 20);
|
||||
$point2 = new Point(30, 40);
|
||||
$algorithm = new MortonClusteringAlgorithm();
|
||||
|
||||
$clusters = $algorithm->cluster([$point1, $point2], 0.0);
|
||||
|
||||
// With zoom 0, everything should be in one big cluster
|
||||
$this->assertCount(1, $clusters);
|
||||
$this->assertCount(2, $clusters[0]->getPoints());
|
||||
}
|
||||
}
|
||||
@@ -69,8 +69,8 @@ class IconTest extends TestCase
|
||||
$refl = new \ReflectionClass(SvgIcon::class);
|
||||
$customizationMethods = array_diff(
|
||||
array_map(
|
||||
fn (\ReflectionMethod $method) => $method->name,
|
||||
array_filter($refl->getMethods(\ReflectionMethod::IS_PUBLIC), fn (\ReflectionMethod $method) => SvgIcon::class === $method->getDeclaringClass()->getName())
|
||||
static fn (\ReflectionMethod $method) => $method->name,
|
||||
array_filter($refl->getMethods(\ReflectionMethod::IS_PUBLIC), static fn (\ReflectionMethod $method) => SvgIcon::class === $method->getDeclaringClass()->getName())
|
||||
),
|
||||
['toArray', 'fromArray']
|
||||
);
|
||||
|
||||
@@ -33,7 +33,7 @@ trait AppKernelTrait
|
||||
$dir = sys_get_temp_dir().'/map_bundle/'.uniqid($type.'_', true);
|
||||
|
||||
if (!file_exists($dir)) {
|
||||
mkdir($dir, 0777, true);
|
||||
mkdir($dir, 0o777, true);
|
||||
}
|
||||
|
||||
return $dir;
|
||||
|
||||
@@ -32,10 +32,17 @@ class FrameworkAppKernel extends Kernel
|
||||
return [new FrameworkBundle(), new StimulusBundle(), new UXMapBundle()];
|
||||
}
|
||||
|
||||
public function registerContainerConfiguration(LoaderInterface $loader)
|
||||
public function registerContainerConfiguration(LoaderInterface $loader): void
|
||||
{
|
||||
$loader->load(function (ContainerBuilder $container) {
|
||||
$container->loadFromExtension('framework', ['secret' => '$ecret', 'test' => true, 'http_method_override' => false]);
|
||||
$loader->load(static function (ContainerBuilder $container) {
|
||||
$container->loadFromExtension('framework', [
|
||||
'secret' => '$ecret',
|
||||
'test' => true,
|
||||
'http_method_override' => false,
|
||||
...(self::VERSION_ID >= 70300 ? [
|
||||
'property_info' => ['with_constructor_extractor' => false],
|
||||
] : []),
|
||||
]);
|
||||
$container->loadFromExtension('ux_map', []);
|
||||
|
||||
$container->setAlias('test.ux_map.renderers', 'ux_map.renderers')->setPublic(true);
|
||||
|
||||
@@ -33,11 +33,24 @@ class TwigAppKernel extends Kernel
|
||||
return [new FrameworkBundle(), new StimulusBundle(), new TwigBundle(), new UXMapBundle()];
|
||||
}
|
||||
|
||||
public function registerContainerConfiguration(LoaderInterface $loader)
|
||||
public function registerContainerConfiguration(LoaderInterface $loader): void
|
||||
{
|
||||
$loader->load(function (ContainerBuilder $container) {
|
||||
$container->loadFromExtension('framework', ['secret' => '$ecret', 'test' => true, 'http_method_override' => false]);
|
||||
$container->loadFromExtension('twig', ['default_path' => __DIR__.'/templates', 'strict_variables' => true, 'exception_controller' => null]);
|
||||
$loader->load(static function (ContainerBuilder $container) {
|
||||
$container->loadFromExtension('framework', [
|
||||
'secret' => '$ecret',
|
||||
'test' => true,
|
||||
'http_method_override' => false,
|
||||
'php_errors' => [
|
||||
'log' => true,
|
||||
],
|
||||
...(self::VERSION_ID >= 60200 ? [
|
||||
'handle_all_throwables' => true,
|
||||
] : []),
|
||||
]);
|
||||
$container->loadFromExtension('twig', [
|
||||
'default_path' => __DIR__.'/templates',
|
||||
'strict_variables' => true,
|
||||
]);
|
||||
$container->loadFromExtension('ux_map', []);
|
||||
|
||||
$container->setAlias('test.ux_map.renderers', 'ux_map.renderers')->setPublic(true);
|
||||
|
||||
@@ -38,9 +38,9 @@ class TwigComponentKernel extends Kernel
|
||||
];
|
||||
}
|
||||
|
||||
public function registerContainerConfiguration(LoaderInterface $loader)
|
||||
public function registerContainerConfiguration(LoaderInterface $loader): void
|
||||
{
|
||||
$loader->load(function (ContainerBuilder $container) {
|
||||
$loader->load(static function (ContainerBuilder $container) {
|
||||
$container->loadFromExtension('framework', [
|
||||
'secret' => '$ecret',
|
||||
'test' => true,
|
||||
@@ -49,7 +49,6 @@ class TwigComponentKernel extends Kernel
|
||||
$container->loadFromExtension('twig', [
|
||||
'default_path' => __DIR__.'/templates',
|
||||
'strict_variables' => true,
|
||||
'exception_controller' => null,
|
||||
]);
|
||||
$container->loadFromExtension('twig_component', [
|
||||
'defaults' => [],
|
||||
|
||||
@@ -85,7 +85,6 @@ class MapFactoryTest extends TestCase
|
||||
new Point(48.853, 2.3499),
|
||||
new Point(48.8566, 2.3522),
|
||||
],
|
||||
title: 'Polygon 1',
|
||||
infoWindow: new InfoWindow('Polygon 1', 'Polygon 1', extra: ['color' => 'red']),
|
||||
extra: ['color' => 'blue'],
|
||||
));
|
||||
@@ -222,7 +221,7 @@ class MapFactoryTest extends TestCase
|
||||
'lng' => 2.3522,
|
||||
],
|
||||
],
|
||||
'title' => 'Polygon 1',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => 'Polygon 1',
|
||||
'content' => 'Polygon 1',
|
||||
@@ -245,7 +244,7 @@ class MapFactoryTest extends TestCase
|
||||
'lng' => 2.3522,
|
||||
],
|
||||
],
|
||||
'title' => 'Polyline 1',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => 'Polyline 1',
|
||||
'content' => 'Polyline 1',
|
||||
|
||||
@@ -135,7 +135,6 @@ class MapTest extends TestCase
|
||||
new Point(48.853, 2.3499),
|
||||
new Point(48.8566, 2.3522),
|
||||
],
|
||||
title: 'Polygon 1',
|
||||
infoWindow: null,
|
||||
))
|
||||
->addPolygon(new Polygon(
|
||||
@@ -144,7 +143,6 @@ class MapTest extends TestCase
|
||||
new Point(45.75, 4.85),
|
||||
new Point(45.77, 4.82),
|
||||
],
|
||||
title: 'Polygon 2',
|
||||
infoWindow: new InfoWindow(
|
||||
headerContent: '<b>Polygon 2</b>',
|
||||
content: 'A polygon around Lyon with some additional info.',
|
||||
@@ -159,7 +157,6 @@ class MapTest extends TestCase
|
||||
new Point(48.853, 2.3499),
|
||||
new Point(48.8566, 2.3522),
|
||||
],
|
||||
title: 'Polyline 1',
|
||||
infoWindow: null,
|
||||
))
|
||||
->addPolyline(new Polyline(
|
||||
@@ -168,7 +165,6 @@ class MapTest extends TestCase
|
||||
new Point(45.75, 4.85),
|
||||
new Point(45.77, 4.82),
|
||||
],
|
||||
title: 'Polyline 2',
|
||||
infoWindow: new InfoWindow(
|
||||
headerContent: '<b>Polyline 2</b>',
|
||||
content: 'A polyline around Lyon with some additional info.',
|
||||
@@ -180,7 +176,6 @@ class MapTest extends TestCase
|
||||
->addCircle(new Circle(
|
||||
center: new Point(48.8566, 2.3522),
|
||||
radius: 500,
|
||||
title: 'Circle around Paris',
|
||||
infoWindow: new InfoWindow(
|
||||
headerContent: '<b>Circle around Paris</b>',
|
||||
content: 'A circle with a radius of 500 meters around Paris.',
|
||||
@@ -192,7 +187,6 @@ class MapTest extends TestCase
|
||||
->addCircle(new Circle(
|
||||
center: new Point(45.764, 4.8357),
|
||||
radius: 300,
|
||||
title: 'Circle around Lyon',
|
||||
infoWindow: new InfoWindow(
|
||||
headerContent: '<b>Circle around Lyon</b>',
|
||||
content: 'A circle with a radius of 300 meters around Lyon.',
|
||||
@@ -204,7 +198,6 @@ class MapTest extends TestCase
|
||||
->addRectangle(new Rectangle(
|
||||
southWest: new Point(48.853, 2.3499),
|
||||
northEast: new Point(48.8566, 2.3522),
|
||||
title: 'Rectangle around Paris',
|
||||
infoWindow: new InfoWindow(
|
||||
headerContent: '<b>Rectangle around Paris</b>',
|
||||
content: 'A rectangle around Paris.',
|
||||
@@ -216,7 +209,6 @@ class MapTest extends TestCase
|
||||
->addRectangle(new Rectangle(
|
||||
southWest: new Point(45.75, 4.85),
|
||||
northEast: new Point(45.77, 4.82),
|
||||
title: 'Rectangle around Lyon',
|
||||
infoWindow: new InfoWindow(
|
||||
headerContent: '<b>Rectangle around Lyon</b>',
|
||||
content: 'A rectangle around Lyon.',
|
||||
@@ -297,7 +289,7 @@ class MapTest extends TestCase
|
||||
['lat' => 48.853, 'lng' => 2.3499],
|
||||
['lat' => 48.8566, 'lng' => 2.3522],
|
||||
],
|
||||
'title' => 'Polygon 1',
|
||||
'title' => null,
|
||||
'infoWindow' => null,
|
||||
'extra' => [],
|
||||
'id' => null,
|
||||
@@ -308,7 +300,7 @@ class MapTest extends TestCase
|
||||
['lat' => 45.75, 'lng' => 4.85],
|
||||
['lat' => 45.77, 'lng' => 4.82],
|
||||
],
|
||||
'title' => 'Polygon 2',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => '<b>Polygon 2</b>',
|
||||
'content' => 'A polygon around Lyon with some additional info.',
|
||||
@@ -328,7 +320,7 @@ class MapTest extends TestCase
|
||||
['lat' => 48.853, 'lng' => 2.3499],
|
||||
['lat' => 48.8566, 'lng' => 2.3522],
|
||||
],
|
||||
'title' => 'Polyline 1',
|
||||
'title' => null,
|
||||
'infoWindow' => null,
|
||||
'extra' => [],
|
||||
'id' => null,
|
||||
@@ -339,7 +331,7 @@ class MapTest extends TestCase
|
||||
['lat' => 45.75, 'lng' => 4.85],
|
||||
['lat' => 45.77, 'lng' => 4.82],
|
||||
],
|
||||
'title' => 'Polyline 2',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => '<b>Polyline 2</b>',
|
||||
'content' => 'A polyline around Lyon with some additional info.',
|
||||
@@ -356,7 +348,7 @@ class MapTest extends TestCase
|
||||
[
|
||||
'center' => ['lat' => 48.8566, 'lng' => 2.3522],
|
||||
'radius' => 500,
|
||||
'title' => 'Circle around Paris',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => '<b>Circle around Paris</b>',
|
||||
'content' => 'A circle with a radius of 500 meters around Paris.',
|
||||
@@ -371,7 +363,7 @@ class MapTest extends TestCase
|
||||
[
|
||||
'center' => ['lat' => 45.764, 'lng' => 4.8357],
|
||||
'radius' => 300,
|
||||
'title' => 'Circle around Lyon',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => '<b>Circle around Lyon</b>',
|
||||
'content' => 'A circle with a radius of 300 meters around Lyon.',
|
||||
@@ -388,7 +380,7 @@ class MapTest extends TestCase
|
||||
[
|
||||
'southWest' => ['lat' => 48.853, 'lng' => 2.3499],
|
||||
'northEast' => ['lat' => 48.8566, 'lng' => 2.3522],
|
||||
'title' => 'Rectangle around Paris',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => '<b>Rectangle around Paris</b>',
|
||||
'content' => 'A rectangle around Paris.',
|
||||
@@ -403,7 +395,7 @@ class MapTest extends TestCase
|
||||
[
|
||||
'southWest' => ['lat' => 45.75, 'lng' => 4.85],
|
||||
'northEast' => ['lat' => 45.77, 'lng' => 4.82],
|
||||
'title' => 'Rectangle around Lyon',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => '<b>Rectangle around Lyon</b>',
|
||||
'content' => 'A rectangle around Lyon.',
|
||||
|
||||
@@ -28,7 +28,6 @@ class PolygonTest extends TestCase
|
||||
|
||||
$polygon = new Polygon(
|
||||
points: [$point1, $point2],
|
||||
title: 'Test Polygon',
|
||||
infoWindow: $infoWindow,
|
||||
extra: ['foo' => 'bar'],
|
||||
id: 'poly1'
|
||||
@@ -37,7 +36,7 @@ class PolygonTest extends TestCase
|
||||
$array = $polygon->toArray();
|
||||
$this->assertSame([
|
||||
'points' => [['lat' => 1.1, 'lng' => 2.2], ['lat' => 3.3, 'lng' => 4.4]],
|
||||
'title' => 'Test Polygon',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => 'info content',
|
||||
'content' => null,
|
||||
@@ -80,7 +79,6 @@ class PolygonTest extends TestCase
|
||||
'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',
|
||||
@@ -93,7 +91,7 @@ class PolygonTest extends TestCase
|
||||
$array = $polygon->toArray();
|
||||
$this->assertSame([
|
||||
'points' => [['lat' => 1.1, 'lng' => 2.2], ['lat' => 3.3, 'lng' => 4.4]],
|
||||
'title' => 'Test Polygon',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => null,
|
||||
'content' => 'info content',
|
||||
@@ -114,7 +112,6 @@ class PolygonTest extends TestCase
|
||||
[['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',
|
||||
@@ -130,7 +127,7 @@ class PolygonTest extends TestCase
|
||||
[['lat' => 1.1, 'lng' => 2.2], ['lat' => 3.3, 'lng' => 4.4]],
|
||||
[['lat' => 5.5, 'lng' => 6.6]],
|
||||
],
|
||||
'title' => 'Test Polygon',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => null,
|
||||
'content' => 'info content',
|
||||
|
||||
@@ -26,13 +26,13 @@ class RectangleTest extends TestCase
|
||||
$southWest = new Point(1.0, 2.0);
|
||||
$northEast = new Point(3.0, 4.0);
|
||||
|
||||
$rectangle = new Rectangle($southWest, $northEast, 'Test Rectangle', $infoWindow, ['foo' => 'bar'], 'rect1');
|
||||
$rectangle = new Rectangle($southWest, $northEast, null, $infoWindow, ['foo' => 'bar'], 'rect1');
|
||||
|
||||
$array = $rectangle->toArray();
|
||||
self::assertSame([
|
||||
'southWest' => ['lat' => 1.0, 'lng' => 2.0],
|
||||
'northEast' => ['lat' => 3.0, 'lng' => 4.0],
|
||||
'title' => 'Test Rectangle',
|
||||
'title' => null,
|
||||
'infoWindow' => $infoWindow->toArray(),
|
||||
'extra' => ['foo' => 'bar'],
|
||||
'id' => 'rect1',
|
||||
@@ -44,7 +44,7 @@ class RectangleTest extends TestCase
|
||||
$data = [
|
||||
'southWest' => ['lat' => 1.0, 'lng' => 2.0],
|
||||
'northEast' => ['lat' => 3.0, 'lng' => 4.0],
|
||||
'title' => 'Test Rectangle',
|
||||
'title' => null,
|
||||
'infoWindow' => ['content' => 'Hello'],
|
||||
'extra' => ['foo' => 'bar'],
|
||||
'id' => 'rect1',
|
||||
@@ -56,7 +56,7 @@ class RectangleTest extends TestCase
|
||||
self::assertSame([
|
||||
'southWest' => ['lat' => 1.0, 'lng' => 2.0],
|
||||
'northEast' => ['lat' => 3.0, 'lng' => 4.0],
|
||||
'title' => 'Test Rectangle',
|
||||
'title' => null,
|
||||
'infoWindow' => [
|
||||
'headerContent' => null,
|
||||
'content' => 'Hello',
|
||||
|
||||
@@ -21,21 +21,21 @@ use Symfony\UX\Map\Renderer\RendererInterface;
|
||||
|
||||
final class NullRendererTest extends TestCase
|
||||
{
|
||||
public function provideTestRenderMap(): iterable
|
||||
public static function provideTestRenderMap(): iterable
|
||||
{
|
||||
yield 'no bridges' => [
|
||||
'expected_exception_message' => 'You must install at least one bridge package to use the Symfony UX Map component.',
|
||||
'expectedExceptionMessage' => 'You must install at least one bridge package to use the Symfony UX Map component.',
|
||||
'renderer' => new NullRenderer(),
|
||||
];
|
||||
|
||||
yield 'one bridge' => [
|
||||
'expected_exception_message' => 'You must install at least one bridge package to use the Symfony UX Map component.'
|
||||
'expectedExceptionMessage' => 'You must install at least one bridge package to use the Symfony UX Map component.'
|
||||
.\PHP_EOL.'Try running "composer require symfony/ux-leaflet-map".',
|
||||
'renderer' => new NullRenderer(['symfony/ux-leaflet-map']),
|
||||
];
|
||||
|
||||
yield 'two bridges' => [
|
||||
'expected_exception_message' => 'You must install at least one bridge package to use the Symfony UX Map component.'
|
||||
'expectedExceptionMessage' => 'You must install at least one bridge package to use the Symfony UX Map component.'
|
||||
.\PHP_EOL.'Try running "composer require symfony/ux-leaflet-map" or "composer require symfony/ux-google-map".',
|
||||
'renderer' => new NullRenderer(['symfony/ux-leaflet-map', 'symfony/ux-google-map']),
|
||||
];
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
namespace Symfony\UX\Map\Tests\Twig;
|
||||
|
||||
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\UX\Map\Map;
|
||||
use Symfony\UX\Map\Point;
|
||||
@@ -23,8 +22,6 @@ use Twig\Environment;
|
||||
|
||||
class MapExtensionTest extends KernelTestCase
|
||||
{
|
||||
use ExpectDeprecationTrait;
|
||||
|
||||
protected static function getKernelClass(): string
|
||||
{
|
||||
return TwigAppKernel::class;
|
||||
|
||||
Reference in New Issue
Block a user