diff --git a/dev/user-frontend-ionic/angular.json.dist b/dev/user-frontend-ionic/angular.json.dist index 03f56984d..cc87f3525 100644 --- a/dev/user-frontend-ionic/angular.json.dist +++ b/dev/user-frontend-ionic/angular.json.dist @@ -69,7 +69,8 @@ "styles": [ "src/global.scss", "node_modules/swiper/swiper-bundle.min.css", - "node_modules/shepherd.js/dist/css/shepherd.css" + "node_modules/shepherd.js/dist/css/shepherd.css", + "node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css" ], "scripts": [ "node_modules/swiper/swiper-bundle.min.js" diff --git a/dev/user-frontend-ionic/package-lock.json b/dev/user-frontend-ionic/package-lock.json index 81ac33116..f19248733 100644 --- a/dev/user-frontend-ionic/package-lock.json +++ b/dev/user-frontend-ionic/package-lock.json @@ -56,6 +56,7 @@ "geolib": "^3.3.4", "ionicons": "^7.4.0", "leaflet": "^1.9.4", + "leaflet.markercluster": "^1.5.3", "localforage": "^1.10.0", "ng2-dragula": "^5.1.0", "ngx-matomo": "^2.0.0", @@ -81,6 +82,7 @@ "@types/jasmine": "~3.6.0", "@types/jasminewd2": "~2.0.13", "@types/leaflet": "^1.9.20", + "@types/leaflet.markercluster": "^1.5.6", "@types/node": "^20.19.1", "@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/parser": "^7.17.0", @@ -6803,6 +6805,15 @@ "@types/geojson": "*" } }, + "node_modules/@types/leaflet.markercluster": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.6.tgz", + "integrity": "sha512-I7hZjO2+isVXGYWzKxBp8PsCzAYCJBc29qBdFpquOCkS7zFDqUsUvkEOyQHedsk/Cy5tocQzf+Ndorm5W9YKTQ==", + "dev": true, + "dependencies": { + "@types/leaflet": "^1.9" + } + }, "node_modules/@types/lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", @@ -14036,6 +14047,14 @@ "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", "license": "BSD-2-Clause" }, + "node_modules/leaflet.markercluster": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz", + "integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==", + "peerDependencies": { + "leaflet": "^1.3.1" + } + }, "node_modules/less": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", diff --git a/dev/user-frontend-ionic/package.json b/dev/user-frontend-ionic/package.json index 3f011b6fc..58b8c0c4f 100644 --- a/dev/user-frontend-ionic/package.json +++ b/dev/user-frontend-ionic/package.json @@ -71,6 +71,7 @@ "geolib": "^3.3.4", "ionicons": "^7.4.0", "leaflet": "^1.9.4", + "leaflet.markercluster": "^1.5.3", "localforage": "^1.10.0", "ng2-dragula": "^5.1.0", "ngx-matomo": "^2.0.0", @@ -96,6 +97,7 @@ "@types/jasmine": "~3.6.0", "@types/jasminewd2": "~2.0.13", "@types/leaflet": "^1.9.20", + "@types/leaflet.markercluster": "^1.5.6", "@types/node": "^20.19.1", "@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/parser": "^7.17.0", diff --git a/dev/user-frontend-ionic/projects/map/README.md b/dev/user-frontend-ionic/projects/map/README.md index 4d2909566..f4e210a4a 100644 --- a/dev/user-frontend-ionic/projects/map/README.md +++ b/dev/user-frontend-ionic/projects/map/README.md @@ -4,4 +4,52 @@ Module permettant l'affichage d'une carte avec points d'intérêts et géolocali ## Configuration du module map - `defaultMapLocation.longitude` : Longitude de la position par défaut sur la carte. -- `defaultMapLocation.latitude` : Latitude de la position par défaut sur la carte. \ No newline at end of file +- `defaultMapLocation.latitude` : Latitude de la position par défaut sur la carte. + +## Personnalisation du clustering des marqueurs + +### Paramétrage du cluster +Il est possible de paramétrer le fonctionnement du cluster lors de son initialisation avec les options suivantes : +- `disableClusteringAtZoom` : Désactive le cluster à partir du niveau de zoom défini. Par défaut, il est fixé à 18 ce qui correspond à l'avant dernier niveau de zoom. +- `showCoverageOnHover` : Active la visualisation du polygone montrant la zone du cluster. Par défaut, il est fixé à `false`. +- `zoomToBoundsOnClick` : Permet de centrer et zoomer la vue sur le cluster lorsque l'utilisateur clique dessus. Par défaut, il est fixé à `true`. +- `maxClusterRadius` : Rayon maximal de regroupement des points dans le cluster, en pixel. Par défaut, il est fixé à 80 pixels. +- `removeOutsideVisibleBounds` : Supprime les clusters et marqueurs de la carte lorsqu'ils ne sont pas visibles. Par défaut, il est fixé à `true`. + +Beaucoup d'autres options sont disponibles, vous pouvez les retrouver sur cette page : https://github.com/Leaflet/Leaflet.markercluster + +### Alternative : Cluster par catégorie +Dans la version fournie, un seul cluster est appliqué pour l'ensemble des points indépendamment de leur catégorie. +Il est possible de modifier la logique pour permettre aux clusters de ne contenir que des points d'une même catégorie. +Pour cela, modifiez les éléments suivants dans `map.page.ts` : +1. Remplacez `private clusterGroup: Leaflet.MarkerClusterGroup;` par `private clusterGroupByCategory: Map = new Map();` +2. Dans `refreshMapWithSelectedCategories()` ajoutez au début de la fonction les lignes suivantes : +```PHP +this.clusterGroupByCategory.forEach((clusterGroup, category) => { + this.map.removeLayer(clusterGroup); + if (categories.length === 0 || categories.includes(category)) { + this.map.addLayer(clusterGroup); + } +}); +``` +3. Dans `initMarkers()` ajoutez à la fin les lignes suivantes : +```PHP +this.clusterGroupByCategory.forEach((clusterGroup, category) => { + this.map.addLayer(clusterGroup); + }); +``` +4. Dans `initMarker()` ajoutez dans le `if` après `this.markersByCategory.set(m.category, []);` les lignes suivantes : +```PHP +this.clusterGroupByCategory.set(m.category, Leaflet.markerClusterGroup({ + disableClusteringAtZoom: 18, // Désactive le cluster à partir du zoom 18 + showCoverageOnHover: false, // Désactive la visualisation du polygone montrant la zone du cluster + zoomToBoundsOnClick: true, // Lors du clic, zoom sur le cluster + maxClusterRadius: 80, // Rayon maximum du cluster en pixel + removeOutsideVisibleBounds: true, // Retirer les clusters en dehors de la zone visible sur la carte +})); +``` +5. Dans `initMarker()` ajoutez après `const markersInCategory = this.markersByCategory.get(m.category);` les lignes suivantes : +```PHP +const clusterGroup = this.clusterGroupByCategory.get(m.category)!; +``` +6. Dans `initMarker()` remplacez `this.clusterGroup.addLayer(marker);` par `clusterGroup.addLayer(marker);` diff --git a/dev/user-frontend-ionic/projects/map/src/lib/map.page.ts b/dev/user-frontend-ionic/projects/map/src/lib/map.page.ts index 6c19b15c7..b6c542a19 100644 --- a/dev/user-frontend-ionic/projects/map/src/lib/map.page.ts +++ b/dev/user-frontend-ionic/projects/map/src/lib/map.page.ts @@ -47,6 +47,7 @@ import { finalize, take } from 'rxjs/operators'; import { Marker, markersList$, setMarkers } from './map.repository'; import { MapService } from './map.service'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import 'leaflet.markercluster'; const CATEGORIES = [ 'presidences_points', @@ -74,9 +75,10 @@ export class MapPage { protected readonly categories = CATEGORIES; private map: Leaflet.Map; private markersByCategory: Map = new Map(); - private layerGroupByCategory: Map = new Map(); private positionLayerGroup: Leaflet.LayerGroup; private destroyRef = inject(DestroyRef) + private clusterGroup: Leaflet.MarkerClusterGroup; + constructor( private mapService: MapService, @@ -103,6 +105,15 @@ export class MapPage { this.isLoading = true; await this.loadMarkersInNetworkAvailable(); await this.leafletMapInit(); + //On initialise le cluster unique + this.clusterGroup = Leaflet.markerClusterGroup({ + disableClusteringAtZoom: 18, // Désactive le cluster à partir du zoom 18 + showCoverageOnHover: false, // Désactive la visualisation du polygone montrant la zone du cluster + zoomToBoundsOnClick: true, // Lors du clic, zoom sur le cluster + maxClusterRadius: 80, // Rayon maximum du cluster en pixel + removeOutsideVisibleBounds: true, // Retirer les clusters en dehors de la zone visible sur la carte + }); + this.map.addLayer(this.clusterGroup); markersList$ .pipe( take(1), @@ -161,11 +172,19 @@ export class MapPage { } private refreshMapWithSelectedCategories(categories: string[]) { - this.layerGroupByCategory.forEach((layerGroup, category) => { - layerGroup.removeFrom(this.map); - if (categories.length === 0 || categories.indexOf(category) >= 0) { - layerGroup.addTo(this.map); - } + this.clusterGroup.clearLayers(); + markersList$.pipe(take(1)).subscribe(markers => { + markers + .filter(m => categories.length === 0 || categories.includes(m.category)) + .forEach(m => { + const icon = this.buildIconForCategory(m.category); + const marker = Leaflet.marker([m.latitude, m.longitude], { icon }) + .bindPopup(` +

${m.title}


+
${m.description}
+ `); + this.clusterGroup.addLayer(marker); + }); }); } @@ -219,12 +238,6 @@ export class MapPage { private initMarkers(markers: Marker[]) { markers.forEach(m => this.initMarker(m)); - - this.markersByCategory.forEach((markersInCategory, category) => { - const layerGroup = Leaflet.layerGroup(markersInCategory); - this.layerGroupByCategory.set(category, layerGroup); - layerGroup.addTo(this.map); - }); } private initMarker(m: Marker) { @@ -239,7 +252,8 @@ export class MapPage { `

${m.title}


${m.description}
` ); - + //Ajoute le marqueur au cluster unique + this.clusterGroup.addLayer(marker); markersInCategory.push(marker); } diff --git a/dev/user-frontend-ionic/src/theme/app-theme-dist/styles/map/map.page.scss b/dev/user-frontend-ionic/src/theme/app-theme-dist/styles/map/map.page.scss index 94354b5f0..a34225ee5 100644 --- a/dev/user-frontend-ionic/src/theme/app-theme-dist/styles/map/map.page.scss +++ b/dev/user-frontend-ionic/src/theme/app-theme-dist/styles/map/map.page.scss @@ -70,3 +70,27 @@ ion-item { .chip-item { background-color: transparent; } + + +// Style for clustering markers +::ng-deep{ + .marker-cluster-small, .marker-cluster-medium, .marker-cluster-large { + background-color: color-mix(in srgb, var(--ion-color-primary) 60%, transparent); + } + .marker-cluster-small div, .marker-cluster-medium div, .marker-cluster-large div { + background-color: color-mix(in srgb, var(--ion-color-primary) 60%, transparent); + } + .leaflet-oldie .marker-cluster-small, .leaflet-oldie .marker-cluster-medium, .leaflet-oldie .marker-cluster-large { + background-color: var(--ion-color-primary); + } + .leaflet-oldie .marker-cluster-small div, .leaflet-oldie .marker-cluster-medium div, .leaflet-oldie .marker-cluster-large div { + background-color: var(--ion-color-primary); + } + .marker-cluster span { + font-family: var(--app-font-primary-static-bold), sans-serif; + color: var(--app-font-fix-light-color); + font-size: 1rem; + font-weight: 600; + text-shadow: 0 0 1px var(--app-font-fix-light-color); + } +}