|
|
|
@ -1,22 +1,35 @@
|
|
|
|
|
import { NgFor } from '@angular/common';
|
|
|
|
|
import { Component, OnInit, ViewContainerRef } from '@angular/core';
|
|
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
|
|
|
import * as L from 'leaflet';
|
|
|
|
|
import 'leaflet/dist/leaflet.css';
|
|
|
|
|
import { Pin } from '../../model/Pin';
|
|
|
|
|
import { AutocompleteService } from '../../services/auto-complete/auto-complete.service';
|
|
|
|
|
import { PinService } from '../../services/pin/pin.service';
|
|
|
|
|
import { PinMarkerComponent } from '../pin-marker/pin-marker.component';
|
|
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'app-leaflet-map',
|
|
|
|
|
templateUrl: './leaflet-map.component.html',
|
|
|
|
|
standalone: true,
|
|
|
|
|
imports: [NgFor, FormsModule],
|
|
|
|
|
})
|
|
|
|
|
export class LeafletMapComponent implements OnInit {
|
|
|
|
|
private map!: L.Map;
|
|
|
|
|
private markersMap: { [key: string]: L.Marker } = {};
|
|
|
|
|
private allPins: Pin[] = [];
|
|
|
|
|
private pinCountries: { [pinId: string]: string } = {};
|
|
|
|
|
|
|
|
|
|
availableCountries: string[] = [];
|
|
|
|
|
availablePersons: string[] = [];
|
|
|
|
|
selectedCountry: string = '';
|
|
|
|
|
selectedPerson: string = '';
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
private viewContainerRef: ViewContainerRef,
|
|
|
|
|
private pinsService: PinService,
|
|
|
|
|
private autocompleteService: AutocompleteService,
|
|
|
|
|
private route: ActivatedRoute,
|
|
|
|
|
private router: Router
|
|
|
|
|
) {}
|
|
|
|
@ -26,67 +39,128 @@ export class LeafletMapComponent implements OnInit {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private initializeMap(): void {
|
|
|
|
|
// Initialize the map
|
|
|
|
|
this.map = L.map('map', {
|
|
|
|
|
maxBounds: L.latLngBounds(
|
|
|
|
|
L.latLng(-90, -180), // South-West
|
|
|
|
|
L.latLng(90, 180) // North-East
|
|
|
|
|
),
|
|
|
|
|
maxBoundsViscosity: 1.0, // Prevent dragging the map out of bounds
|
|
|
|
|
minZoom: 2, // Prevent zooming out too much
|
|
|
|
|
maxBounds: L.latLngBounds(L.latLng(-90, -180), L.latLng(90, 180)),
|
|
|
|
|
maxBoundsViscosity: 1.0,
|
|
|
|
|
minZoom: 2,
|
|
|
|
|
}).setView([46.603354, 1.888334], 6);
|
|
|
|
|
|
|
|
|
|
// Add OpenStreetMap tiles
|
|
|
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
|
|
|
attribution: '',
|
|
|
|
|
}).addTo(this.map);
|
|
|
|
|
this.map.attributionControl.setPrefix('');
|
|
|
|
|
|
|
|
|
|
// Define custom icons
|
|
|
|
|
this.pinsService.getPins().subscribe((pins: Pin[]) => {
|
|
|
|
|
this.allPins = pins;
|
|
|
|
|
this.extractPersons(pins);
|
|
|
|
|
|
|
|
|
|
const countrySet = new Set<string>();
|
|
|
|
|
const requests = pins.map((pin) =>
|
|
|
|
|
this.autocompleteService
|
|
|
|
|
.getAddressFromCoordinates(pin.location[0], pin.location[1])
|
|
|
|
|
.toPromise()
|
|
|
|
|
.then((res: any) => {
|
|
|
|
|
const address = res?.address;
|
|
|
|
|
const country =
|
|
|
|
|
address?.country ||
|
|
|
|
|
this.extractLastFromDisplayName(res?.display_name);
|
|
|
|
|
if (country) {
|
|
|
|
|
this.pinCountries[pin.id] = country;
|
|
|
|
|
countrySet.add(country);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch((err: any) => {
|
|
|
|
|
console.error(
|
|
|
|
|
'Erreur lors de la récupération du pays pour le pin',
|
|
|
|
|
pin.id,
|
|
|
|
|
err
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Promise.all(requests).then(() => {
|
|
|
|
|
this.availableCountries = Array.from(countrySet).sort();
|
|
|
|
|
this.renderPins();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private extractLastFromDisplayName(displayName: string): string {
|
|
|
|
|
if (!displayName) return '';
|
|
|
|
|
const parts = displayName.split(',');
|
|
|
|
|
return parts[parts.length - 1].trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private extractPersons(pins: Pin[]): void {
|
|
|
|
|
const personsSet = new Set<string>();
|
|
|
|
|
const regex = /@(\w+)/g;
|
|
|
|
|
pins.forEach((pin) => {
|
|
|
|
|
const desc = pin.description || '';
|
|
|
|
|
let match;
|
|
|
|
|
while ((match = regex.exec(desc)) !== null) {
|
|
|
|
|
personsSet.add(match[1]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.availablePersons = Array.from(personsSet).sort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onCountryChange(country: string) {
|
|
|
|
|
this.selectedCountry = country;
|
|
|
|
|
this.renderPins();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onPersonChange(person: string) {
|
|
|
|
|
this.selectedPerson = person;
|
|
|
|
|
this.renderPins();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderPins(): void {
|
|
|
|
|
// Remove existing markers
|
|
|
|
|
Object.values(this.markersMap).forEach((marker) =>
|
|
|
|
|
this.map.removeLayer(marker)
|
|
|
|
|
);
|
|
|
|
|
this.markersMap = {};
|
|
|
|
|
|
|
|
|
|
const visitedIcon = this.createDivIcon(`
|
|
|
|
|
<svg class="w-6 h-6 text-gray-800" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path fill-rule="evenodd" d="M11.906 1.994a8.002 8.002 0 0 1 8.09 8.421 7.996 7.996 0 0 1-1.297 3.957.996.996 0 0 1-.133.204l-.108.129c-.178.243-.37.477-.573.699l-5.112 6.224a1 1 0 0 1-1.545 0L5.982 15.26l-.002-.002a18.146 18.146 0 0 1-.309-.38l-.133-.163a.999.999 0 0 1-.13-.202 7.995 7.995 0 0 1 6.498-12.518ZM15 9.997a3 3 0 1 1-5.999 0 3 3 0 0 1 5.999 0Z" clip-rule="evenodd"/>
|
|
|
|
|
</svg>
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
const notVisitedIcon = this.createDivIcon(`
|
|
|
|
|
<svg class="w-6 h-6 text-gray-800" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke="currentColor" stroke-width="3" d="M11.083 5.104c.35-.8 1.485-.8 1.834 0l1.752 4.022a1 1 0 0 0 .84.597l4.463.342c.9.069 1.255 1.2.556 1.771l-3.33 2.723a1 1 0 0 0-.337 1.016l1.03 4.119c.214.858-.71 1.552-1.474 1.106l-3.913-2.281a1 1 0 0 0-1.008 0L7.583 20.8c-.764.446-1.688-.248-1.474-1.106l1.03-4.119A1 1 0 0 0 6.8 14.56l-3.33-2.723c-.698-.571-.342-1.702.557-1.771l4.462-.342a1 1 0 0 0 .84-.597l1.753-4.022Z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
this.pinsService.getPins().subscribe((pins: Pin[]) => {
|
|
|
|
|
// Add markers
|
|
|
|
|
pins.forEach((pin: Pin) => {
|
|
|
|
|
//const icon = pin.visited ? visitedIcon : notVisitedIcon;
|
|
|
|
|
const icon = visitedIcon;
|
|
|
|
|
|
|
|
|
|
const marker = L.marker(pin.location as [number, number], {
|
|
|
|
|
icon,
|
|
|
|
|
}).addTo(this.map);
|
|
|
|
|
const filteredPins = this.allPins.filter((pin) => {
|
|
|
|
|
const pinCountry = this.pinCountries[pin.id];
|
|
|
|
|
const matchesCountry = this.selectedCountry
|
|
|
|
|
? pinCountry === this.selectedCountry
|
|
|
|
|
: true;
|
|
|
|
|
const matchesPerson = this.selectedPerson
|
|
|
|
|
? pin.description?.includes(`@${this.selectedPerson}`)
|
|
|
|
|
: true;
|
|
|
|
|
return matchesCountry && matchesPerson;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
marker.on('popupclose', () => {
|
|
|
|
|
this.router.navigate(['/map']);
|
|
|
|
|
});
|
|
|
|
|
filteredPins.forEach((pin) => {
|
|
|
|
|
const marker = L.marker(pin.location as [number, number], {
|
|
|
|
|
icon: visitedIcon,
|
|
|
|
|
}).addTo(this.map);
|
|
|
|
|
|
|
|
|
|
marker.on('popupopen', () => {
|
|
|
|
|
this.router.navigate(['/map'], { queryParams: { pin: pin.id } });
|
|
|
|
|
});
|
|
|
|
|
marker.on('popupclose', () => {
|
|
|
|
|
this.router.navigate(['/map']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Dynamically create Angular component and attach it to popup
|
|
|
|
|
const popupDiv = document.createElement('div');
|
|
|
|
|
const componentRef =
|
|
|
|
|
this.viewContainerRef.createComponent(PinMarkerComponent);
|
|
|
|
|
marker.on('popupopen', () => {
|
|
|
|
|
this.router.navigate(['/map'], { queryParams: { pin: pin.id } });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
componentRef.instance.pin = pin;
|
|
|
|
|
componentRef.instance.marker = marker;
|
|
|
|
|
popupDiv.appendChild(componentRef.location.nativeElement);
|
|
|
|
|
const popupDiv = document.createElement('div');
|
|
|
|
|
const componentRef =
|
|
|
|
|
this.viewContainerRef.createComponent(PinMarkerComponent);
|
|
|
|
|
componentRef.instance.pin = pin;
|
|
|
|
|
componentRef.instance.marker = marker;
|
|
|
|
|
popupDiv.appendChild(componentRef.location.nativeElement);
|
|
|
|
|
|
|
|
|
|
marker.bindPopup(popupDiv, { closeButton: false, minWidth: 150 });
|
|
|
|
|
marker.bindPopup(popupDiv, { closeButton: false, minWidth: 150 });
|
|
|
|
|
|
|
|
|
|
// Stocker les marqueurs par ID
|
|
|
|
|
this.markersMap[pin.id] = marker;
|
|
|
|
|
});
|
|
|
|
|
this.markersMap[pin.id] = marker;
|
|
|
|
|
|
|
|
|
|
// Ouvrir automatiquement la pop-up si un ID est passé dans l'URL
|
|
|
|
|
this.route.queryParams.subscribe((params) => {
|
|
|
|
|