Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Classes/Helper/SettingsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public function getPreparedSettings(array $settings, ServerRequestInterface $req
$settings['forceZoom'] = (bool)($settings['forceZoom'] ?? false);

$this->prepareMapTileForOpenStreetMap($settings);
$this->prepareMarkerClusterer($settings);
$this->prepareImagePathForMarkerClusterer($settings, $request);

return $settings;
Expand All @@ -77,6 +78,27 @@ protected function prepareMapTileForOpenStreetMap(array &$settings): void
}
}

/**
* The FlexForm of the maps2 plugin offers "markerClusterer.override" to enable ("1") or
* disable ("0") marker clustering per content element for all map providers. An empty
* value keeps the value of the site setting / TypoScript, which stays the source of truth.
*/
protected function prepareMarkerClusterer(array &$settings): void
{
if (!is_array($settings['markerClusterer'] ?? null)) {
return;
}

$override = (string)($settings['markerClusterer']['override'] ?? '');
unset($settings['markerClusterer']['override']);

if ($override === '0' || $override === '1') {
$settings['markerClusterer']['enable'] = $override;
}

$settings['markerClusterer']['enable'] = (int)($settings['markerClusterer']['enable'] ?? 0);
}

protected function prepareImagePathForMarkerClusterer(array &$settings, ServerRequestInterface $request): void
{
if (
Expand Down
23 changes: 23 additions & 0 deletions Configuration/FlexForms/Maps2.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,29 @@
<default>1</default>
</config>
</settings.fullScreenControl>
<settings.markerClusterer.override>
<label>LLL:EXT:maps2/Resources/Private/Language/FlexForms.xlf:markerClusterer.override</label>
<description>LLL:EXT:maps2/Resources/Private/Language/FlexForms.xlf:markerClusterer.override.description</description>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<default></default>
<items type="array">
<numIndex index="0" type="array">
<label>LLL:EXT:maps2/Resources/Private/Language/FlexForms.xlf:markerClusterer.override.default</label>
<value></value>
</numIndex>
<numIndex index="1" type="array">
<label>LLL:EXT:maps2/Resources/Private/Language/FlexForms.xlf:markerClusterer.override.enabled</label>
<value>1</value>
</numIndex>
<numIndex index="2" type="array">
<label>LLL:EXT:maps2/Resources/Private/Language/FlexForms.xlf:markerClusterer.override.disabled</label>
<value>0</value>
</numIndex>
</items>
</config>
</settings.markerClusterer.override>
</el>
</ROOT>
</sMapOptions>
Expand Down
6 changes: 0 additions & 6 deletions Configuration/Sets/GoogleMaps/settings.definitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ categories:
parent: Maps2

settings:
maps2.enableMarkerClusterer:
label: 'Enable Marker Clusterer'
category: Maps2.googlemaps
description: 'If you have a lot of POIs, some POIs will be joined to a single POI with the amount of joined POIs as label'
type: bool
default: false
maps2.googleMapsJavaScriptApiKey:
label: 'JavaScript API Key'
category: Maps2.googlemaps
Expand Down
8 changes: 0 additions & 8 deletions Configuration/Sets/GoogleMaps/setup.typoscript
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
# Prevent including both static maps of map providers on one page
page.includeJSFooterlibs.markerClustererForMaps2 >

[isRequestToMapProviderAllowed()]
page.includeJSFooterlibs {
markerClustererForMaps2 = EXT:maps2/Resources/Public/JavaScript/MarkerClusterer.js
markerClustererForMaps2.if.isTrue = {$maps2.enableMarkerClusterer}
}
[ELSE]
# Requests to Google Maps are not allowed. Inform the user about cookies and request permissions for 3rd party.
tt_content.maps2_maps2.20.pluginName = Overlay
Expand All @@ -24,7 +17,6 @@ plugin.tx_maps2 {
googleMapsMapId = {$maps2.googleMapsMapId}
mapTypeId = google.maps.MapTypeId.ROADMAP
markerClusterer {
enable = {$maps2.enableMarkerClusterer}
imagePath = EXT:maps2/Resources/Public/Icons/MarkerClusterer/m
}
}
Expand Down
10 changes: 10 additions & 0 deletions Configuration/Sets/Maps2/settings.definitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ categories:
Maps2.pages:
label: 'Pages'
parent: 'Maps2'
Maps2.markerClusterer:
label: 'Marker Clusterer'
parent: 'Maps2'

settings:
maps2.templateRootPath:
Expand Down Expand Up @@ -55,3 +58,10 @@ settings:
description: 'Folder that stores all POIs'
type: int
default: 0

maps2.enableMarkerClusterer:
label: 'Enable Marker Clusterer'
category: 'Maps2.markerClusterer'
description: 'If you have a lot of POIs, nearby POIs will be joined to a single cluster icon with the amount of joined POIs as label. Applies to Google Maps and OpenStreetMap.'
type: bool
default: false
3 changes: 3 additions & 0 deletions Configuration/Sets/Maps2/setup.typoscript
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ plugin.tx_maps2 {
settings {
zoom = 10
zoomControl = 1
markerClusterer {
enable = {$maps2.enableMarkerClusterer}
}
overlay {
link {
addSection = 1
Expand Down
9 changes: 6 additions & 3 deletions Configuration/Sets/OpenStreetMap/setup.typoscript
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# Prevent including both static maps of map providers on one page
page.includeJSFooterlibs.markerClustererForMaps2 >

[isRequestToMapProviderAllowed()]
[ELSE]
# Requests to OpenStreetMap are not allowed. Inform the user about cookies and request permissions for 3rd party.
Expand All @@ -14,5 +11,11 @@ plugin.tx_maps2 {
mapProvider = osm
mapTile = {$maps2.mapTile}
mapTileAttribution = {$maps2.mapTileAttribution}
markerClusterer {
# Theme stylesheet for the cluster icons of Leaflet.markercluster.
# Set your own stylesheet or clear the value to style the
# ".marker-cluster" CSS classes within your own site package.
styleSheet = EXT:maps2/Resources/Public/Css/Leaflet/MarkerCluster.Default.css
}
}
}
19 changes: 19 additions & 0 deletions Documentation/ChangeLog/Index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@
ChangeLog
=========

Version 13.2.0
==============

* FEATURE: Add marker clustering for OpenStreetMap based on
Leaflet.markercluster
* FEATURE: Add FlexForm option to enable or disable marker clustering per
content element for Google Maps and OpenStreetMap
* FEATURE: Add TypoScript setting `markerClusterer.styleSheet` to replace the
cluster icon theme of Leaflet.markercluster
* TASK: Move site setting `maps2.enableMarkerClusterer` from site set
`Maps2 - Google Maps` into `Maps2 - Default Set`. The setting key is
unchanged.
* BUGFIX: Fix marker clustering for Google Maps. The outdated MarkerClusterer
v1 does not support `AdvancedMarkerElement` and was replaced by the official
library `@googlemaps/markerclusterer`. The cluster images of
`markerClusterer.imagePath` are still used.
* TASK: Load the Google Maps marker clusterer on demand via AssetCollector
in partial LoadAssets instead of `page.includeJSFooterlibs`

Version 13.1.1
==============

Expand Down
17 changes: 17 additions & 0 deletions Documentation/Configuration/Plugins/Index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ Default: true

Show control to toggle between normal and full screen mode.

.. _plugins-show-map-marker-clusterer:

markerClusterer.override
------------------------

Default: Use default (site settings / TypoScript)

Enable or disable marker clustering for this content element only. This option
applies to Google Maps and OpenStreetMap. With the default option the value of
site setting `maps2.enableMarkerClusterer` (TypoScript
`plugin.tx_maps2.settings.markerClusterer.enable`) is used.

The required JavaScript libraries are loaded automatically, if at least one map
on the page uses marker clustering. The cluster icons are configured with
TypoScript `markerClusterer.imagePath` (Google Maps) and
`markerClusterer.styleSheet` (OpenStreetMap).

mapTypeId
---------

Expand Down
51 changes: 47 additions & 4 deletions Documentation/Configuration/TypoScript/Index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,36 @@ Set the maximum height of images within the InfoWindow PopUp
markerClusterer.enable
----------------------

Only available for Google Maps
Available for Google Maps and OpenStreetMap

Default: 0

Example: `plugin.tx_maps2.settings.markerClusterer.enable = 1`

This value is configurable through TypoScript Constants Editor
This value is configurable through the site setting
`maps2.enableMarkerClusterer` ("Enable Marker Clusterer") of site set
`Maps2 - Default Set`.

If you work with a lot of poi collection records you can activate the marker
clusterer. The marker clusterer will merge multiple poi collections to 1 icon
with the contains amount of records.
clusterer. The marker clusterer will merge multiple nearby poi collections to
1 icon showing the amount of contained records. The markers separate as the
visitor zooms in.

For Google Maps the library `MarkerClusterer` is used. For OpenStreetMap the
official Leaflet plugin
`Leaflet.markercluster <https://github.com/Leaflet/Leaflet.markercluster>`__
is used. With OpenStreetMap only point markers are clustered. Areas, routes and
radius records are always shown as they are. While editing a marker in
frontend, clustering is not used.

This TypoScript value is the source of truth. It also applies to maps
which are rendered by foreign extensions through the maps2 Fluid partials.

.. hint::

Editors can override this value per content element in the "Map Options"
tab of the maps2 plugin. See
:ref:`markerClusterer.override <plugins-show-map-marker-clusterer>`.

markerClusterer.imagePath
-------------------------
Expand All @@ -170,6 +189,30 @@ Example: `plugin.tx_maps2.settings.markerClusterer.imagePath = EXT:my_sitepackag
If you don't like the icons of Marker Clusterer you can choose a different path for your own
images.

markerClusterer.styleSheet
--------------------------

Only available for OpenStreetMap

Default: `EXT:maps2/Resources/Public/Css/Leaflet/MarkerCluster.Default.css`

Example: `plugin.tx_maps2.settings.markerClusterer.styleSheet = EXT:my_sitepackage/Resources/Public/Css/MarkerCluster.css`

Leaflet.markercluster does not work with images like the Google Maps
MarkerClusterer. The cluster icons are HTML elements which are styled by CSS
classes `marker-cluster`, `marker-cluster-small` (less than 10 markers),
`marker-cluster-medium` (less than 100 markers) and `marker-cluster-large`.

By default, maps2 loads the default theme `MarkerCluster.Default.css` of
Leaflet.markercluster. If you don't like the cluster icons, copy that file to
your site package, adapt it (colors, sizes, background images, ...) and set
the path to your own file here. If you clear this value, no theme stylesheet
will be loaded at all and you can style these CSS classes within the
stylesheets of your site package.

The required base stylesheet `MarkerCluster.css` (animations) is always loaded,
if marker clustering is enabled.


.. _typoScript-local-lang:

Expand Down
80 changes: 69 additions & 11 deletions Resources/Private/Build/JavaScript/GoogleMaps2.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ class GoogleMaps2 {
allMarkers = [];
categorizedMarkers = {};
pointMarkers = [];
markerClusterer = null;
bounds = {};
infoWindow = {};
poiCollections = {};
Expand Down Expand Up @@ -47,16 +48,7 @@ class GoogleMaps2 {
}
} else {
await this.createPointByCollectionType(element, environment);
if (
typeof environment.settings.markerClusterer !== 'undefined'
&& environment.settings.markerClusterer.enable === 1
) {
new MarkerClusterer(
this.map,
this.pointMarkers,
{ imagePath: environment.settings.markerClusterer.imagePath }
);
}
this.createMarkerClusterer(environment.settings);

if (this.countObjectProperties(this.categorizedMarkers) > 1) {
this.showSwitchableCategories(element, environment);
Expand All @@ -70,6 +62,66 @@ class GoogleMaps2 {
}
}

/**
* Cluster the point markers with the official @googlemaps/markerclusterer library
*
* @param {Settings} settings
*/
createMarkerClusterer = settings => {
if (
this.editable
|| typeof settings.markerClusterer === 'undefined'
|| settings.markerClusterer.enable !== 1
|| typeof markerClusterer === 'undefined'
) {
return;
}

this.markerClusterer = new markerClusterer.MarkerClusterer({
map: this.map,
markers: this.pointMarkers,
renderer: this.getClusterRenderer(settings.markerClusterer.imagePath),
});
}

/**
* Render clusters with the images m1.png to m5.png of the configured imagePath.
* Image sizes and index calculation are taken from the former MarkerClusterer v1.
*
* @param {string} imagePath
* @return {object}
*/
getClusterRenderer = imagePath => {
const imageSizes = [53, 56, 66, 78, 90];

return {
render: ({ count, position }) => {
const index = Math.min(String(count).length, imageSizes.length);
const size = imageSizes[index - 1];
const content = document.createElement('div');

content.classList.add('maps2-marker-cluster');
content.textContent = String(count);
content.style.width = size + 'px';
content.style.height = size + 'px';
content.style.lineHeight = size + 'px';
content.style.backgroundImage = 'url("' + imagePath + index + '.png")';
content.style.textAlign = 'center';
content.style.fontSize = '11px';
content.style.fontWeight = 'bold';
content.style.color = '#000';
content.style.cursor = 'pointer';
content.style.transform = 'translateY(50%)';

return new google.maps.marker.AdvancedMarkerElement({
position: position,
content: content,
zIndex: 1000 + count,
});
},
};
}

/**
* Return a MapOptions object which can be assigned to the Map object of Google
*
Expand Down Expand Up @@ -350,7 +402,13 @@ class GoogleMaps2 {
let markers = this.getMarkersToChangeVisibilityFor(categoryUid, form, isChecked);

markers.forEach((marker) => {
if (typeof marker.setVisible === 'function') {
if (this.markerClusterer !== null && this.pointMarkers.includes(marker)) {
if (isChecked) {
this.markerClusterer.addMarker(marker);
} else {
this.markerClusterer.removeMarker(marker);
}
} else if (typeof marker.setVisible === 'function') {
marker.setVisible(isChecked);
} else if (typeof marker.setMap === 'function') {
marker.setMap(isChecked ? this.map : null);
Expand Down
Loading
Loading