mirror of
https://github.com/symfony/ux-map.git
synced 2026-03-23 23:42:07 +01:00
922 lines
33 KiB
ReStructuredText
922 lines
33 KiB
ReStructuredText
Symfony UX Map
|
|
==============
|
|
|
|
Symfony UX Map is a Symfony bundle integrating interactive Maps in
|
|
Symfony applications. It is part of `the Symfony UX initiative`_.
|
|
|
|
Installation
|
|
------------
|
|
|
|
Install the bundle using Composer and Symfony Flex:
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ composer require symfony/ux-map
|
|
|
|
Configuration
|
|
-------------
|
|
|
|
Configuration is done in your ``config/packages/ux_map.yaml`` file:
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/packages/ux_map.yaml
|
|
ux_map:
|
|
renderer: '%env(resolve:default::UX_MAP_DSN)%'
|
|
|
|
# Google Maps specific configuration
|
|
google_maps:
|
|
# Configure the default Map Id (https://developers.google.com/maps/documentation/get-map-id),
|
|
# without to manually configure it in each map instance (through "new GoogleOptions(mapId: 'your_map_id')").
|
|
default_map_id: null
|
|
|
|
The ``UX_MAP_DSN`` environment variable configure which renderer (Bridge) to use.
|
|
|
|
Map renderers
|
|
~~~~~~~~~~~~~
|
|
|
|
The Symfony UX Map bundle supports multiple renderers. A map renderer is a
|
|
service that provides the code and graphic assets required to render and
|
|
interact with a map in the browser.
|
|
|
|
Available renderers
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
UX Map ships with two renderers: `Google Maps`_ and `Leaflet`_.
|
|
|
|
============== ===============================================================
|
|
Renderer
|
|
============== ===============================================================
|
|
`Google Maps`_ **Install**: ``composer require symfony/ux-google-map`` \
|
|
**DSN**: ``UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default`` \
|
|
`Leaflet`_ **Install**: ``composer require symfony/ux-leaflet-map`` \
|
|
**DSN**: ``UX_MAP_DSN=leaflet://default`` \
|
|
============== ===============================================================
|
|
|
|
.. tip::
|
|
|
|
Read the `Symfony UX Map Leaflet bridge docs`_ and the
|
|
`Symfony UX Map Google Maps bridge docs`_ to learn about the configuration
|
|
options available for each renderer.
|
|
|
|
Create a map
|
|
------------
|
|
|
|
A map is created by calling ``new Map()``. You can configure the center, zoom, and add markers.
|
|
Start by creating a new map instance::
|
|
|
|
use Symfony\UX\Map\Map;
|
|
|
|
// Create a new map instance
|
|
$map = new Map();
|
|
|
|
Center and zoom
|
|
~~~~~~~~~~~~~~~
|
|
|
|
You can set the center and zoom of the map using the ``center()`` and ``zoom()`` methods::
|
|
|
|
use Symfony\UX\Map\Map;
|
|
use Symfony\UX\Map\Point;
|
|
|
|
$map
|
|
// Explicitly set the center and zoom
|
|
->center(new Point(46.903354, 1.888334))
|
|
->zoom(6)
|
|
|
|
// Or automatically fit the bounds to the markers
|
|
->fitBoundsToMarkers()
|
|
;
|
|
|
|
Min and max zooms
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
.. versionadded:: 2.28
|
|
|
|
The ability to set min and max zooms was added in UX Map 2.28.
|
|
|
|
You can set the minimum and maximum zoom levels of the map using the ``minZoom()`` and ``maxZoom()`` methods::
|
|
|
|
use Symfony\UX\Map\Map;
|
|
use Symfony\UX\Map\Point;
|
|
|
|
$map
|
|
->center(new Point(46.903354, 1.888334))
|
|
->zoom(6)
|
|
->minZoom(3) // Set the minimum zoom level
|
|
->maxZoom(10) // Set the maximum zoom level
|
|
;
|
|
|
|
.. warning::
|
|
|
|
Ensure ``zoom``, ``minZoom`` and ``maxZoom`` are compatible with each other (``minZoom <= zoom <= maxZoom``),
|
|
otherwise an exception will be thrown.
|
|
|
|
Add markers
|
|
~~~~~~~~~~~
|
|
|
|
You can add markers to a map using the ``addMarker()`` method::
|
|
|
|
$map
|
|
->addMarker(new Marker(
|
|
position: new Point(48.8566, 2.3522),
|
|
title: 'Paris'
|
|
))
|
|
|
|
// With an info window associated to the marker:
|
|
->addMarker(new Marker(
|
|
position: new Point(45.7640, 4.8357),
|
|
title: 'Lyon',
|
|
infoWindow: new InfoWindow(
|
|
headerContent: '<b>Lyon</b>',
|
|
content: 'The French town in the historic Rhône-Alpes region, located at the junction of the Rhône and Saône rivers.'
|
|
),
|
|
))
|
|
|
|
// You can also pass arbitrary data via the `extra` option in both the marker
|
|
// and the infoWindow; you can later use this data in your custom Stimulus controllers
|
|
->addMarker(new Marker(
|
|
position: new Point(45.7740, 4.8351),
|
|
extra: [
|
|
'icon_mask_url' => 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/tree_pinlet.svg',
|
|
],
|
|
infoWindow: new InfoWindow(
|
|
// ...
|
|
extra: [
|
|
'num_items' => 3,
|
|
'includes_link' => true,
|
|
],
|
|
),
|
|
))
|
|
;
|
|
|
|
Add Marker icons
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
.. versionadded:: 2.24
|
|
|
|
``Marker`` icon customization is available since UX Map 2.24.
|
|
|
|
A ``Marker`` can be customized with an ``Icon`` instance, which can either be an UX Icon, an URL, or a SVG content::
|
|
|
|
// It can be a UX Icon (requires `symfony/ux-icons` package)...
|
|
$icon = Icon::ux('fa:map-marker')->width(24)->height(24);
|
|
// ... or an URL pointing to an image
|
|
$icon = Icon::url('https://example.com/marker.png')->width(24)->height(24);
|
|
// ... or a plain SVG string
|
|
$icon = Icon::svg('<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">...</svg>');
|
|
|
|
$map->addMarker(new Marker(
|
|
// ...
|
|
icon: $icon
|
|
));
|
|
|
|
Add Polygons
|
|
~~~~~~~~~~~~
|
|
|
|
You can also add Polygons, which represents an area enclosed by a series of ``Point`` instances::
|
|
|
|
$map->addPolygon(new Polygon(
|
|
points: [
|
|
new Point(48.8566, 2.3522),
|
|
new Point(45.7640, 4.8357),
|
|
new Point(43.2965, 5.3698),
|
|
new Point(44.8378, -0.5792),
|
|
],
|
|
infoWindow: new InfoWindow(
|
|
content: 'Paris, Lyon, Marseille, Bordeaux',
|
|
),
|
|
));
|
|
|
|
.. versionadded:: 2.26
|
|
|
|
`Polygon` with holes is available since UX Map 2.26.
|
|
|
|
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 France map
|
|
$map->addPolygon(new Polygon(points: [
|
|
// First path, the outer boundary of the polygon
|
|
[
|
|
new Point(48.117266, -1.677792), // Rennes
|
|
new Point(50.629250, 3.057256), // Lille
|
|
new Point(48.573405, 7.752111), // Strasbourg
|
|
new Point(43.296482, 5.369780), // Marseille
|
|
new Point(44.837789, -0.579180), // Bordeaux
|
|
],
|
|
// Second path, making a hole in the first path
|
|
[
|
|
new Point(45.833619, 1.261105), // Limoges
|
|
new Point(45.764043, 4.835659), // Lyon
|
|
new Point(49.258329, 4.031696), // Reims
|
|
new Point(48.856613, 2.352222), // Paris
|
|
],
|
|
]));
|
|
|
|
Add Polylines
|
|
~~~~~~~~~~~~~
|
|
|
|
You can add Polylines, which represents a path made by a series of ``Point`` instances::
|
|
|
|
$map->addPolyline(new Polyline(
|
|
points: [
|
|
new Point(48.8566, 2.3522),
|
|
new Point(45.7640, 4.8357),
|
|
new Point(43.2965, 5.3698),
|
|
new Point(44.8378, -0.5792),
|
|
],
|
|
infoWindow: new InfoWindow(
|
|
content: 'A line passing through Paris, Lyon, Marseille, Bordeaux',
|
|
),
|
|
));
|
|
|
|
Add Circles
|
|
~~~~~~~~~~~
|
|
|
|
You can add Circles, which represents a circular area defined by a center point and a radius (in meters)::
|
|
|
|
$map->addCircle(new Circle(
|
|
center: new Point(48.8566, 2.3522),
|
|
radius: 5_000, // 5km
|
|
infoWindow: new InfoWindow(
|
|
content: 'A 5km radius circle centered on Paris',
|
|
),
|
|
));
|
|
|
|
Add Rectangles
|
|
~~~~~~~~~~~~~~
|
|
|
|
You can add Rectangles, which represents a rectangular area defined by two corner points::
|
|
|
|
$map->addRectangle(new Rectangle(
|
|
southWest: new Point(48.8566, 2.3522), // Paris
|
|
northEast: new Point(50.6292, 3.0573), // Lille
|
|
infoWindow: new InfoWindow(
|
|
content: 'A rectangle from Paris to Lille',
|
|
),
|
|
));
|
|
|
|
Remove elements from Map
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
It is possible to remove elements like ``Marker``, ``Polygon``, ``Polyline`` and ``Circle`` instances by using ``Map::remove*()`` methods.
|
|
It's useful when :ref:`using a Map inside a Live Component <map-live-component>`::
|
|
|
|
// 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->removeMarker($marker);
|
|
$map->removePolygon($polygon);
|
|
$map->removePolyline($polyline);
|
|
$map->removeCircle($circle);
|
|
$map->removeRectangle($rectangle);
|
|
|
|
If you haven't stored the element instance, you can still remove them by passing the identifier string::
|
|
|
|
$map = new Map(/* ... */);
|
|
// Add elements
|
|
$map->addMarker(new Marker(id: 'my-marker', /* ... */));
|
|
$map->addPolygon(new Polygon(id: 'my-polygon', /* ... */));
|
|
$map->addPolyline(new Polyline(id: 'my-marker', /* ... */));
|
|
$map->addCircle(new Circle(id: 'my-circle', /* ... */));
|
|
$map->addRectangle(new Rectangle(id: 'my-rectangle', /* ... */));
|
|
|
|
// And later, remove those elements
|
|
$map->removeMarker('my-marker');
|
|
$map->removePolygon('my-polygon');
|
|
$map->removePolyline('my-marker');
|
|
$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
|
|
------------
|
|
|
|
To render a map in your Twig template, use the ``ux_map`` Twig function, e.g.:
|
|
|
|
To be visible, the map must have a defined height:
|
|
|
|
.. code-block:: twig
|
|
|
|
{{ ux_map(my_map, { style: 'height: 300px' }) }}
|
|
|
|
You can add custom HTML attributes too:
|
|
|
|
.. code-block:: twig
|
|
|
|
{{ ux_map(my_map, { style: 'height: 300px', id: 'events-map', class: 'mb-3' }) }}
|
|
|
|
Twig Function ``ux_map()``
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
The ``ux_map()`` Twig function allows you to create and render a map in your Twig
|
|
templates. The function accepts the same arguments as the ``Map`` class:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
{{ ux_map(
|
|
center: [51.5074, 0.1278],
|
|
zoom: 3,
|
|
markers: [
|
|
{ position: [51.5074, 0.1278], title: 'London' },
|
|
{ position: [48.8566, 2.3522], title: 'Paris' },
|
|
{
|
|
position: [40.7128, -74.0060],
|
|
title: 'New York',
|
|
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 />``
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Alternatively, you can use the ``<twig:ux:map />`` component.
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<twig:ux:map
|
|
:center="[51.5074, 0.1278]"
|
|
zoom="3"
|
|
:markers='[
|
|
{"position": [51.5074, 0.1278], "title": "London"},
|
|
{"position": [48.8566, 2.3522], "title": "Paris"},
|
|
{
|
|
"position": [40.7128, -74.0060],
|
|
"title": "New York",
|
|
"infoWindow": {"content": "Welcome to <b>New York</b>"}
|
|
}
|
|
]'
|
|
:fitBoundsToMarkers="true",
|
|
class="foo"
|
|
style="height: 800px; width: 100%; border: 4px solid red; margin-block: 10vh;"
|
|
/>
|
|
|
|
The ``<twig:ux:map />`` component requires the `Twig Component`_ package.
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ 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
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Symfony UX Map allows you to extend its default behavior using a custom Stimulus controller:
|
|
|
|
.. code-block:: javascript
|
|
|
|
// assets/controllers/mymap_controller.js
|
|
|
|
import { Controller } from '@hotwired/stimulus';
|
|
|
|
export default class extends Controller {
|
|
connect() {
|
|
this.element.addEventListener('ux:map:pre-connect', this._onPreConnect);
|
|
this.element.addEventListener('ux:map:connect', this._onConnect);
|
|
this.element.addEventListener('ux:map:marker:before-create', this._onMarkerBeforeCreate);
|
|
this.element.addEventListener('ux:map:marker:after-create', this._onMarkerAfterCreate);
|
|
this.element.addEventListener('ux:map:info-window:before-create', this._onInfoWindowBeforeCreate);
|
|
this.element.addEventListener('ux:map:info-window:after-create', this._onInfoWindowAfterCreate);
|
|
this.element.addEventListener('ux:map:polygon:before-create', this._onPolygonBeforeCreate);
|
|
this.element.addEventListener('ux:map:polygon:after-create', this._onPolygonAfterCreate);
|
|
this.element.addEventListener('ux:map:polyline:before-create', this._onPolylineBeforeCreate);
|
|
this.element.addEventListener('ux:map:polyline:after-create', this._onPolylineAfterCreate);
|
|
this.element.addEventListener('ux:map:circle:before-create', this._onCircleBeforeCreate);
|
|
this.element.addEventListener('ux:map:circle:after-create', this._onCircleAfterCreate);
|
|
this.element.addEventListener('ux:map:rectangle:before-create', this._onRectangleBeforeCreate);
|
|
this.element.addEventListener('ux:map:rectangle:after-create', this._onRectangleAfterCreate);
|
|
}
|
|
|
|
disconnect() {
|
|
// You should always remove listeners when the controller is disconnected to avoid side effects
|
|
this.element.removeEventListener('ux:map:pre-connect', this._onPreConnect);
|
|
this.element.removeEventListener('ux:map:connect', this._onConnect);
|
|
this.element.removeEventListener('ux:map:marker:before-create', this._onMarkerBeforeCreate);
|
|
this.element.removeEventListener('ux:map:marker:after-create', this._onMarkerAfterCreate);
|
|
this.element.removeEventListener('ux:map:info-window:before-create', this._onInfoWindowBeforeCreate);
|
|
this.element.removeEventListener('ux:map:info-window:after-create', this._onInfoWindowAfterCreate);
|
|
this.element.removeEventListener('ux:map:polygon:before-create', this._onPolygonBeforeCreate);
|
|
this.element.removeEventListener('ux:map:polygon:after-create', this._onPolygonAfterCreate);
|
|
this.element.removeEventListener('ux:map:polyline:before-create', this._onPolylineBeforeCreate);
|
|
this.element.removeEventListener('ux:map:polyline:after-create', this._onPolylineAfterCreate);
|
|
this.element.removeEventListener('ux:map:circle:before-create', this._onCircleBeforeCreate);
|
|
this.element.removeEventListener('ux:map:circle:after-create', this._onCircleAfterCreate);
|
|
this.element.removeEventListener('ux:map:rectangle:before-create', this._onRectangleBeforeCreate);
|
|
this.element.removeEventListener('ux:map:rectangle:after-create', this._onRectangleAfterCreate);
|
|
}
|
|
|
|
/**
|
|
* This event is triggered when the map is not created yet
|
|
* You can use this event to configure the map before it is created
|
|
*/
|
|
_onPreConnect(event) {
|
|
// You can read or write the zoom level
|
|
console.log(event.detail.zoom);
|
|
|
|
// You can read or write the center of the map
|
|
console.log(event.detail.center);
|
|
|
|
// You can read or write map options, specific to the Bridge, it represents the normalized `*Options` PHP class (e.g. `GoogleOptions`, `LeafletOptions`)
|
|
console.log(event.detail.options);
|
|
|
|
// Finally, you can also set Bridge-specific options that will be used when creating the map.
|
|
event.detail.bridgeOptions = {
|
|
preferCanvas: true, // e.g. for Leaflet (https://leafletjs.com/reference.html#map-prefercanvas)
|
|
backgroundColor: '#f0f0f0', // e.g. for Google Maps (https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.backgroundColor)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This event is triggered when the map and all its elements (markers, info windows, ...) are created.
|
|
* The instances depend on the renderer you are using.
|
|
*/
|
|
_onConnect(event) {
|
|
console.log(event.detail.map);
|
|
console.log(event.detail.markers);
|
|
console.log(event.detail.infoWindows);
|
|
console.log(event.detail.polygons);
|
|
console.log(event.detail.polylines);
|
|
console.log(event.detail.circles);
|
|
console.log(event.detail.rectangles);
|
|
}
|
|
|
|
/**
|
|
* This event is triggered before creating a marker.
|
|
* You can use this event to fine-tune it before its creation.
|
|
*/
|
|
_onMarkerBeforeCreate(event) {
|
|
console.log(event.detail.definition);
|
|
// { title: 'Paris', position: { lat: 48.8566, lng: 2.3522 }, ... }
|
|
|
|
// Example: uppercase the marker title
|
|
event.detail.definition.title = event.detail.definition.title.toUpperCase();
|
|
}
|
|
|
|
/**
|
|
* This event is triggered after creating a marker.
|
|
* You can access the created marker instance, which depends on the renderer you are using.
|
|
*/
|
|
_onMarkerAfterCreate(event) {
|
|
// The marker instance
|
|
console.log(event.detail.marker);
|
|
}
|
|
|
|
/**
|
|
* This event is triggered before creating an info window.
|
|
* You can use this event to fine-tune the info window before its creation.
|
|
*/
|
|
_onInfoWindowBeforeCreate(event) {
|
|
console.log(event.detail.definition);
|
|
// { headerContent: 'Paris', content: 'The capital of France', ... }
|
|
}
|
|
|
|
/**
|
|
* This event is triggered after creating an info window.
|
|
* You can access the created info window instance, which depends on the renderer you are using.
|
|
*/
|
|
_onInfoWindowAfterCreate(event) {
|
|
// The info window instance
|
|
console.log(event.detail.infoWindow);
|
|
|
|
// The associated element instance is also available, e.g. a marker...
|
|
console.log(event.detail.marker);
|
|
// ... or a polygon
|
|
console.log(event.detail.polygon);
|
|
// ... or a polyline
|
|
console.log(event.detail.polyline);
|
|
// ... or a circle
|
|
console.log(event.detail.circle);
|
|
// ... or a rectangle
|
|
console.log(event.detail.rectangle);
|
|
}
|
|
|
|
/**
|
|
* This event is triggered before creating a polygon.
|
|
* You can use this event to fine-tune it before its creation.
|
|
*/
|
|
_onPolygonBeforeCreate(event) {
|
|
console.log(event.detail.definition);
|
|
// { points: [ { lat: 48.8566, lng: 2.3522 }, { lat: 45.7640, lng: 4.8357 }, { lat: 43.2965, lng: 5.3698 }, ... ], ... }
|
|
}
|
|
|
|
/**
|
|
* This event is triggered after creating a polygon.
|
|
* You can access the created polygon instance, which depends on the renderer you are using.
|
|
*/
|
|
_onPolygonAfterCreate(event) {
|
|
// The polygon instance
|
|
console.log(event.detail.polygon);
|
|
}
|
|
|
|
/**
|
|
* This event is triggered before creating a polyline.
|
|
* You can use this event to fine-tune it before its creation.
|
|
*/
|
|
_onPolylineBeforeCreate(event) {
|
|
console.log(event.detail.definition);
|
|
// { points: [ { lat: 48.8566, lng: 2.3522 }, { lat: 45.7640, lng: 4.8357 }, { lat: 43.2965, lng: 5.3698 }, ... ], ... }
|
|
}
|
|
|
|
/**
|
|
* This event is triggered after creating a polyline.
|
|
* You can access the created polyline instance, which depends on the renderer you are using.
|
|
*/
|
|
_onPolylineAfterCreate(event) {
|
|
// The polyline instance
|
|
console.log(event.detail.polyline);
|
|
}
|
|
|
|
_onCircleBeforeCreate(event) {
|
|
console.log(event.detail.definition);
|
|
// { center: { lat: 48.8566, lng: 2.3522 }, radius: 1000, ... }
|
|
}
|
|
|
|
_onCircleAfterCreate(event) {
|
|
// The circle instance
|
|
console.log(event.detail.circle);
|
|
}
|
|
|
|
_onRectangleBeforeCreate(event) {
|
|
console.log(event.detail.definition);
|
|
// { southWest: { lat: 48.8566, lng: 2.3522 }, northEast: { lat: 45.7640, lng: 4.8357 }, ... }
|
|
}
|
|
|
|
_onRectangleAfterCreate(event) {
|
|
// The rectangle instance
|
|
console.log(event.detail.rectangle);
|
|
}
|
|
}
|
|
|
|
Then, you can use this controller in your template:
|
|
|
|
.. code-block:: twig
|
|
|
|
{{ ux_map(my_map, { 'data-controller': 'mymap', style: 'height: 300px' }) }}
|
|
|
|
.. tip::
|
|
|
|
Read the `Symfony UX Map Leaflet bridge docs`_ and the
|
|
`Symfony UX Map Google Maps bridge docs`_ to learn about the exact code
|
|
needed to customize the markers.
|
|
|
|
Advanced: Low-level options
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
UX Map is renderer-agnostic and provides a high-level API to configure
|
|
maps and their elements. However, you might occasionally find this
|
|
abstraction limiting and need to configure low-level options directly.
|
|
|
|
Fortunately, you can customize these low-level options through the UX Map
|
|
events ``ux:map:*:before-create`` using the special ``bridgeOptions`` property:
|
|
|
|
.. deprecated:: 2.27
|
|
|
|
The ``rawOptions`` property was deprecated in UX Map 2.27, and will be removed in 3.0.
|
|
Use ``bridgeOptions`` instead, which better reflect the purpose of these options (options that are
|
|
specific to the renderer bridge).
|
|
|
|
.. code-block:: javascript
|
|
|
|
// assets/controllers/mymap_controller.js
|
|
|
|
import { Controller } from '@hotwired/stimulus';
|
|
|
|
export default class extends Controller {
|
|
connect() {
|
|
this.element.addEventListener('ux:map:marker:before-create', this._onMarkerBeforeCreate);
|
|
this.element.addEventListener('ux:map:info-window:before-create', this._onInfoWindowBeforeCreate);
|
|
this.element.addEventListener('ux:map:polygon:before-create', this._onPolygonBeforeCreate);
|
|
this.element.addEventListener('ux:map:polyline:before-create', this._onPolylineBeforeCreate);
|
|
this.element.addEventListener('ux:map:circle:before-create', this._onCircleBeforeCreate);
|
|
this.element.addEventListener('ux:map:rectangle:before-create', this._onRectangleBeforeCreate);
|
|
}
|
|
|
|
disconnect() {
|
|
this.element.removeEventListener('ux:map:marker:before-create', this._onMarkerBeforeCreate);
|
|
this.element.removeEventListener('ux:map:info-window:before-create', this._onInfoWindowBeforeCreate);
|
|
this.element.removeEventListener('ux:map:polygon:before-create', this._onPolygonBeforeCreate);
|
|
this.element.removeEventListener('ux:map:polyline:before-create', this._onPolylineBeforeCreate);
|
|
this.element.removeEventListener('ux:map:circle:before-create', this._onCircleBeforeCreate);
|
|
this.element.removeEventListener('ux:map:rectangle:before-create', this._onRectangleBeforeCreate);
|
|
}
|
|
|
|
_onMarkerBeforeCreate(event) {
|
|
// When using Google Maps, to configure a `google.maps.AdvancedMarkerElement`
|
|
event.detail.definition.bridgeOptions = {
|
|
gmpDraggable: true,
|
|
// ...
|
|
};
|
|
|
|
// When using Leaflet, to configure a `L.Marker` instance
|
|
event.detail.definition.bridgeOptions = {
|
|
riseOnHover: true,
|
|
// ...
|
|
};
|
|
}
|
|
|
|
_onInfoWindowBeforeCreate(event) {
|
|
// When using Google Maps, to configure a `google.maps.InfoWindow` instance
|
|
event.detail.definition.bridgeOptions = {
|
|
maxWidth: 200,
|
|
// ...
|
|
};
|
|
|
|
// When using Leaflet, to configure a `L.Popup` instance
|
|
event.detail.definition.bridgeOptions = {
|
|
direction: 'left',
|
|
// ...
|
|
};
|
|
}
|
|
|
|
_onPolygonBeforeCreate(event) {
|
|
// When using Google Maps, to configure a `google.maps.Polygon`
|
|
event.detail.definition.bridgeOptions = {
|
|
strokeColor: 'red',
|
|
// ...
|
|
};
|
|
|
|
// When using Leaflet, to configure a `L.Polygon`
|
|
event.detail.definition.bridgeOptions = {
|
|
color: 'red',
|
|
// ...
|
|
};
|
|
}
|
|
|
|
_onPolylineBeforeCreate(event) {
|
|
// When using Google Maps, to configure a `google.maps.Polyline`
|
|
event.detail.definition.bridgeOptions = {
|
|
strokeColor: 'red',
|
|
// ...
|
|
};
|
|
|
|
// When using Leaflet, to configure a `L.Polyline`
|
|
event.detail.definition.bridgeOptions = {
|
|
color: 'red',
|
|
// ...
|
|
};
|
|
}
|
|
|
|
_onCircleBeforeCreate(event) {
|
|
// When using Google Maps, to configure a `google.maps.Circle`
|
|
event.detail.definition.bridgeOptions = {
|
|
strokeColor: 'red',
|
|
// ...
|
|
};
|
|
|
|
// When using Leaflet, to configure a `L.Circle`
|
|
event.detail.definition.bridgeOptions = {
|
|
color: 'red',
|
|
// ...
|
|
};
|
|
}
|
|
|
|
_onRectangleBeforeCreate(event) {
|
|
// When using Google Maps, to configure a `google.maps.Rectangle`
|
|
event.detail.definition.bridgeOptions = {
|
|
strokeColor: 'red',
|
|
// ...
|
|
};
|
|
|
|
// When using Leaflet, to configure a `L.Rectangle`
|
|
event.detail.definition.bridgeOptions = {
|
|
color: 'red',
|
|
// ...
|
|
};
|
|
}
|
|
}
|
|
|
|
Advanced: Passing extra data from PHP to the Stimulus controller
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
For greater customization and extensibility, you can pass additional data from PHP
|
|
to the Stimulus controller. This can be useful when associating extra information
|
|
with a specific marker; for example, indicating the type of location it represents.
|
|
|
|
These additional data are defined and used exclusively by you; UX Map
|
|
only forwards them to the Stimulus controller.
|
|
|
|
.. versionadded:: 2.27
|
|
|
|
The ability to pass extra data to ``Map`` class was added in UX Map 2.27.
|
|
|
|
To pass extra data from PHP to the Stimulus controller, you must use the ``extra``
|
|
property available in ``Map``, ``Marker``, ``InfoWindow``, ``Polygon``, ``Polyline``,
|
|
``Circle`` and ``Rectangle``::
|
|
|
|
|
|
$map = new Map(extra: ['foo' => 'bar']);
|
|
// or
|
|
$map->extra(['foo' => 'bar']);
|
|
|
|
// And for other elements, like Marker, InfoWindow, etc.
|
|
$map->addMarker(new Marker(
|
|
position: new Point(48.822248, 2.337338),
|
|
title: 'Paris - Parc Montsouris',
|
|
extra: [
|
|
'type' => 'Park',
|
|
// ...
|
|
],
|
|
));
|
|
|
|
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:
|
|
|
|
.. code-block:: javascript
|
|
|
|
// Access extra data from the `Map` instance, through `event.detail.extra`
|
|
_onPreConnect(event) {
|
|
console.log(event.detail.extra);
|
|
}
|
|
|
|
_onConnect(event) {
|
|
console.log(event.detail.extra);
|
|
}
|
|
|
|
// Access extra data from the `Marker` (and other elements) instance, through `event.detail.definition.extra`
|
|
_onMarkerBeforeCreate(event) {
|
|
console.log(event.detail.definition.extra);
|
|
}
|
|
|
|
_onMarkerAfterCreate(event) {
|
|
console.log(event.detail.definition.extra);
|
|
}
|
|
|
|
// etc...
|
|
|
|
.. _map-live-component:
|
|
|
|
Usage with Live Components
|
|
--------------------------
|
|
|
|
.. versionadded:: 2.22
|
|
|
|
The ability to render and interact with a Map inside a Live Component was added in Map 2.22.
|
|
|
|
To use a Map inside a Live Component, you need to use the ``ComponentWithMapTrait`` trait
|
|
and implement the method ``instantiateMap`` to return a ``Map`` instance.
|
|
|
|
You can interact with the Map by using ``LiveAction`` attribute::
|
|
|
|
namespace App\Twig\Components;
|
|
|
|
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
|
|
use Symfony\UX\LiveComponent\Attribute\LiveAction;
|
|
use Symfony\UX\LiveComponent\DefaultActionTrait;
|
|
use Symfony\UX\Map\InfoWindow;
|
|
use Symfony\UX\Map\Live\ComponentWithMapTrait;
|
|
use Symfony\UX\Map\Map;
|
|
use Symfony\UX\Map\Marker;
|
|
use Symfony\UX\Map\Point;
|
|
|
|
#[AsLiveComponent]
|
|
final class MapLivePlayground
|
|
{
|
|
use DefaultActionTrait;
|
|
use ComponentWithMapTrait;
|
|
|
|
protected function instantiateMap(): Map
|
|
{
|
|
return (new Map())
|
|
->center(new Point(48.8566, 2.3522))
|
|
->fitBoundsToMarkers()
|
|
->addMarker(new Marker(position: new Point(48.8566, 2.3522), title: 'Paris', infoWindow: new InfoWindow('Paris')))
|
|
->addMarker(new Marker(position: new Point(45.75, 4.85), title: 'Lyon', infoWindow: new InfoWindow('Lyon')))
|
|
;
|
|
}
|
|
}
|
|
|
|
Then, you can render the map with ``ux_map()`` in your component template:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<div{{ attributes }}>
|
|
{{ ux_map(map, {style: 'height: 300px'}) }}
|
|
</div>
|
|
|
|
Then, you can define `Live Actions`_ to interact with the map from the client-side.
|
|
You can retrieve the map instance using the ``getMap()`` method, and change the map center, zoom, add markers, etc::
|
|
|
|
#[LiveAction]
|
|
public function doSomething(): void
|
|
{
|
|
// Change the map center
|
|
$this->getMap()->center(new Point(45.7640, 4.8357));
|
|
|
|
// Change the map zoom
|
|
$this->getMap()->zoom(6);
|
|
|
|
// To prevent the Map from automatically fitting the bounds to the markers after adding a new element, disable the option `fitBoundsToMarkers`:
|
|
$this->getMap()->fitBoundsToMarkers(false);
|
|
|
|
// Add a new marker
|
|
$this->getMap()->addMarker(new Marker(position: new Point(43.2965, 5.3698), title: 'Marseille', infoWindow: new InfoWindow('Marseille')));
|
|
|
|
// Add a new polygon
|
|
$this->getMap()->addPolygon(new Polygon(points: [
|
|
new Point(48.8566, 2.3522),
|
|
new Point(45.7640, 4.8357),
|
|
new Point(43.2965, 5.3698),
|
|
new Point(44.8378, -0.5792),
|
|
], infoWindow: new InfoWindow('Paris, Lyon, Marseille, Bordeaux')));
|
|
}
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<div{{ attributes.defaults() }}>
|
|
{{ ux_map(map, { style: 'height: 300px' }) }}
|
|
|
|
<button
|
|
type="button"
|
|
data-action="live#action"
|
|
data-live-action-param="doSomething"
|
|
>
|
|
Do something with the map
|
|
</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
|
|
------------------------------
|
|
|
|
This bundle aims at following the same Backward Compatibility promise as
|
|
the Symfony framework:
|
|
https://symfony.com/doc/current/contributing/code/bc.html
|
|
|
|
.. _`the Symfony UX initiative`: https://ux.symfony.com/
|
|
.. _`Google Maps`: https://github.com/symfony/ux-google-map
|
|
.. _`Leaflet`: https://github.com/symfony/ux-leaflet-map
|
|
.. _`Symfony UX Map Google Maps bridge docs`: https://github.com/symfony/ux/blob/2.x/src/Map/src/Bridge/Google/README.md
|
|
.. _`Symfony UX Map Leaflet bridge docs`: https://github.com/symfony/ux/blob/2.x/src/Map/src/Bridge/Leaflet/README.md
|
|
.. _`Twig Component`: https://symfony.com/bundles/ux-twig-component/current/index.html
|
|
.. _`Live Actions`: https://symfony.com/bundles/ux-live-component/current/index.html#actions
|