407 lines
13 KiB
TypeScript
407 lines
13 KiB
TypeScript
import { Component, inject, OnInit } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { OedbApi } from '../../services/oedb-api';
|
|
import { OsmAuth } from '../../services/osm-auth';
|
|
import { ActivatedRoute } from '@angular/router';
|
|
|
|
interface NominatimResult {
|
|
place_id: number;
|
|
display_name: string;
|
|
lat: string;
|
|
lon: string;
|
|
type: string;
|
|
importance: number;
|
|
address?: {
|
|
house_number?: string;
|
|
road?: string;
|
|
postcode?: string;
|
|
city?: string;
|
|
state?: string;
|
|
country?: string;
|
|
};
|
|
}
|
|
|
|
@Component({
|
|
selector: 'app-unlocated-events-page',
|
|
standalone: true,
|
|
imports: [CommonModule, FormsModule],
|
|
templateUrl: './unlocated-events.html',
|
|
styleUrl: './unlocated-events.scss'
|
|
})
|
|
export class UnlocatedEventsPage implements OnInit {
|
|
OedbApi = inject(OedbApi);
|
|
private osmAuth = inject(OsmAuth);
|
|
private route = inject(ActivatedRoute);
|
|
|
|
events: Array<any> = [];
|
|
unlocatedEvents: Array<any> = [];
|
|
isLoading = false;
|
|
selectedEvent: any = null;
|
|
isEditing = false;
|
|
newKey = '';
|
|
newValue = '';
|
|
|
|
// Géolocalisation
|
|
searchQuery = '';
|
|
nominatimResults: NominatimResult[] = [];
|
|
isSearchingLocation = false;
|
|
selectedLocation: NominatimResult | null = null;
|
|
|
|
ngOnInit() {
|
|
this.route.queryParamMap.subscribe(map => {
|
|
const id = (map.get('id') || '').trim();
|
|
const what = (map.get('what') || '').trim();
|
|
const limitParam = map.get('limit');
|
|
const limit = limitParam ? Number(limitParam) : null;
|
|
if (id) {
|
|
this.loadSingleEvent(id);
|
|
} else {
|
|
this.loadEvents({ what: what || undefined, limit: limit || undefined });
|
|
}
|
|
});
|
|
}
|
|
|
|
loadEvents(overrides: { what?: string; limit?: number } = {}) {
|
|
this.isLoading = true;
|
|
const today = new Date();
|
|
const endDate = new Date(today);
|
|
endDate.setDate(today.getDate() + 30); // Charger 30 jours pour avoir plus d'événements
|
|
|
|
const params: any = {
|
|
start: today.toISOString().split('T')[0],
|
|
end: endDate.toISOString().split('T')[0],
|
|
limit: overrides.limit ?? 1000
|
|
};
|
|
if (overrides.what) params.what = overrides.what;
|
|
|
|
this.OedbApi.getEvents(params).subscribe((events: any) => {
|
|
this.events = Array.isArray(events?.features) ? events.features : [];
|
|
this.filterUnlocatedEvents();
|
|
this.isLoading = false;
|
|
});
|
|
}
|
|
|
|
loadSingleEvent(id: string | number) {
|
|
this.isLoading = true;
|
|
this.OedbApi.getEventById(id).subscribe({
|
|
next: (feature: any) => {
|
|
const f = (feature && (feature as any).type === 'Feature') ? feature : (feature?.feature || null);
|
|
this.events = f ? [f] : [];
|
|
this.filterUnlocatedEvents();
|
|
this.isLoading = false;
|
|
},
|
|
error: () => {
|
|
this.events = [];
|
|
this.unlocatedEvents = [];
|
|
this.isLoading = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
filterUnlocatedEvents() {
|
|
this.unlocatedEvents = (this.events || []).filter(ev => {
|
|
// Vérifie si la géométrie est un point
|
|
if (!ev.geometry || ev.geometry.type !== 'Point') return false;
|
|
const coords = ev.geometry.coordinates;
|
|
// Vérifie si les coordonnées sont valides
|
|
if (!Array.isArray(coords) || coords.length !== 2) return true;
|
|
// Si les coordonnées sont [0,0], on considère comme non localisé
|
|
if (coords[0] === 0 && coords[1] === 0) return true;
|
|
// Si l'une des coordonnées est manquante ou nulle
|
|
if (coords[0] == null || coords[1] == null) return true;
|
|
return false;
|
|
});
|
|
}
|
|
|
|
selectEvent(event: any) {
|
|
this.selectedEvent = { ...event };
|
|
this.isEditing = true; // Ouvrir directement le formulaire d'édition
|
|
this.searchQuery = event?.properties?.where || '';
|
|
this.nominatimResults = [];
|
|
this.selectedLocation = null;
|
|
|
|
// S'assurer que l'événement a une géométrie valide
|
|
if (!this.selectedEvent.geometry) {
|
|
this.selectedEvent.geometry = {
|
|
type: 'Point',
|
|
coordinates: [0, 0]
|
|
};
|
|
}
|
|
|
|
// Si l'événement a une propriété 'where', proposer automatiquement une recherche
|
|
if (event?.properties?.where) {
|
|
this.searchLocation();
|
|
}
|
|
}
|
|
|
|
startEditing() {
|
|
this.isEditing = true;
|
|
}
|
|
|
|
cancelEditing() {
|
|
this.isEditing = false;
|
|
this.selectedEvent = null;
|
|
}
|
|
|
|
searchLocation() {
|
|
if (!this.searchQuery.trim()) {
|
|
this.nominatimResults = [];
|
|
return;
|
|
}
|
|
|
|
this.isSearchingLocation = true;
|
|
this.nominatimResults = [];
|
|
|
|
// Utiliser la propriété 'where' de l'événement si disponible, sinon utiliser la recherche manuelle
|
|
const searchTerm = this.selectedEvent?.properties?.where || this.searchQuery;
|
|
|
|
const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(searchTerm)}&limit=10&addressdetails=1&countrycodes=fr&extratags=1&accept-language=fr`;
|
|
|
|
fetch(url)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`Erreur HTTP: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then((data: NominatimResult[]) => {
|
|
this.nominatimResults = data;
|
|
this.isSearchingLocation = false;
|
|
console.log('Résultats Nominatim:', data);
|
|
})
|
|
.catch(error => {
|
|
console.error('Erreur lors de la recherche Nominatim:', error);
|
|
this.isSearchingLocation = false;
|
|
// Afficher un message d'erreur à l'utilisateur
|
|
this.nominatimResults = [];
|
|
});
|
|
}
|
|
|
|
selectLocation(location: NominatimResult) {
|
|
this.selectedLocation = location;
|
|
if (this.selectedEvent) {
|
|
// Mettre à jour la géométrie
|
|
this.selectedEvent.geometry = {
|
|
type: 'Point',
|
|
coordinates: [parseFloat(location.lon), parseFloat(location.lat)]
|
|
};
|
|
|
|
// Mettre à jour les propriétés de l'événement
|
|
if (!this.selectedEvent.properties) {
|
|
this.selectedEvent.properties = {};
|
|
}
|
|
|
|
// Mettre à jour la propriété 'where' avec le nom du lieu
|
|
this.selectedEvent.properties.where = location.display_name;
|
|
|
|
// Ajouter d'autres propriétés utiles si elles n'existent pas
|
|
if (!this.selectedEvent.properties.label && !this.selectedEvent.properties.name) {
|
|
this.selectedEvent.properties.label = location.display_name;
|
|
}
|
|
|
|
// Ajouter des informations géographiques détaillées
|
|
this.selectedEvent.properties.lat = location.lat;
|
|
this.selectedEvent.properties.lon = location.lon;
|
|
|
|
// Ajouter des informations détaillées de Nominatim
|
|
if (location.address) {
|
|
if (location.address.house_number) this.selectedEvent.properties.housenumber = location.address.house_number;
|
|
if (location.address.road) this.selectedEvent.properties.street = location.address.road;
|
|
if (location.address.postcode) this.selectedEvent.properties.postcode = location.address.postcode;
|
|
if (location.address.city) this.selectedEvent.properties.city = location.address.city;
|
|
if (location.address.state) this.selectedEvent.properties.region = location.address.state;
|
|
if (location.address.country) this.selectedEvent.properties.country = location.address.country;
|
|
}
|
|
|
|
if (location.type) this.selectedEvent.properties.place_type = location.type;
|
|
if (location.importance) this.selectedEvent.properties.place_importance = location.importance.toString();
|
|
|
|
// Ajouter une note sur la source de géolocalisation
|
|
this.selectedEvent.properties.geocoding_source = 'Nominatim';
|
|
this.selectedEvent.properties.geocoding_date = new Date().toISOString();
|
|
|
|
// S'assurer que les coordonnées sont bien mises à jour dans le formulaire
|
|
this.updateCoordinates();
|
|
}
|
|
}
|
|
|
|
clearSearch() {
|
|
this.searchQuery = '';
|
|
this.nominatimResults = [];
|
|
this.selectedLocation = null;
|
|
this.isSearchingLocation = false;
|
|
}
|
|
|
|
updateCoordinates() {
|
|
// Cette méthode est appelée quand les coordonnées sont modifiées dans le formulaire
|
|
// Elle s'assure que la géométrie est correctement mise à jour
|
|
if (this.selectedEvent && this.selectedEvent.geometry) {
|
|
const lat = parseFloat(this.selectedEvent.geometry.coordinates[1]);
|
|
const lon = parseFloat(this.selectedEvent.geometry.coordinates[0]);
|
|
|
|
if (!isNaN(lat) && !isNaN(lon)) {
|
|
this.selectedEvent.geometry.coordinates = [lon, lat];
|
|
}
|
|
}
|
|
}
|
|
|
|
clearCoordinates() {
|
|
if (this.selectedEvent) {
|
|
this.selectedEvent.geometry = {
|
|
type: 'Point',
|
|
coordinates: [0, 0]
|
|
};
|
|
this.selectedLocation = null;
|
|
|
|
// Remettre à zéro les propriétés de localisation
|
|
if (this.selectedEvent.properties) {
|
|
this.selectedEvent.properties.where = '';
|
|
// Ne pas effacer le label/name s'ils existent déjà
|
|
}
|
|
}
|
|
}
|
|
|
|
validateCoordinates() {
|
|
if (this.selectedEvent && this.selectedEvent.geometry) {
|
|
const lat = this.selectedEvent.geometry.coordinates[1];
|
|
const lon = this.selectedEvent.geometry.coordinates[0];
|
|
|
|
if (this.areCoordinatesValid()) {
|
|
console.log('Coordonnées validées:', { lat, lon });
|
|
this.selectedEvent.geometry.coordinates = [lon, lat];
|
|
this.updateCoordinates();
|
|
// Ici on pourrait ajouter une validation supplémentaire ou une notification
|
|
}
|
|
}
|
|
}
|
|
|
|
areCoordinatesValid(): boolean {
|
|
if (!this.selectedEvent || !this.selectedEvent.geometry) return false;
|
|
|
|
const lat = this.selectedEvent.geometry.coordinates[1];
|
|
const lon = this.selectedEvent.geometry.coordinates[0];
|
|
|
|
// Vérifier que les coordonnées sont des nombres valides
|
|
if (isNaN(lat) || isNaN(lon)) return false;
|
|
|
|
// Vérifier que les coordonnées sont dans des plages valides
|
|
if (lat < -90 || lat > 90) return false;
|
|
if (lon < -180 || lon > 180) return false;
|
|
|
|
// Vérifier que ce ne sont pas les coordonnées par défaut (0,0)
|
|
if (lat === 0 && lon === 0) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
addProperty() {
|
|
if (this.newKey.trim() && this.newValue.trim()) {
|
|
if (!this.selectedEvent.properties) {
|
|
this.selectedEvent.properties = {};
|
|
}
|
|
this.selectedEvent.properties[this.newKey.trim()] = this.newValue.trim();
|
|
this.newKey = '';
|
|
this.newValue = '';
|
|
}
|
|
}
|
|
|
|
removeProperty(key: string) {
|
|
if (this.selectedEvent?.properties) {
|
|
delete this.selectedEvent.properties[key];
|
|
}
|
|
}
|
|
|
|
updateEvent() {
|
|
if (!this.selectedEvent) return;
|
|
|
|
this.isLoading = true;
|
|
const eventId = this.selectedEvent.id || this.selectedEvent.properties?.id;
|
|
|
|
if (eventId) {
|
|
// Mettre à jour un événement existant
|
|
this.OedbApi.updateEvent(eventId, this.selectedEvent).subscribe({
|
|
next: (response) => {
|
|
console.log('Événement mis à jour:', response);
|
|
this.loadEvents();
|
|
this.selectedEvent = null;
|
|
this.isEditing = false;
|
|
this.isLoading = false;
|
|
},
|
|
error: (error) => {
|
|
console.error('Erreur lors de la mise à jour:', error);
|
|
this.isLoading = false;
|
|
}
|
|
});
|
|
} else {
|
|
// Créer un nouvel événement
|
|
const osmUsername = this.osmAuth.getUsername();
|
|
if (osmUsername) {
|
|
this.selectedEvent.properties.last_modified_by = osmUsername;
|
|
}
|
|
|
|
this.OedbApi.createEvent(this.selectedEvent).subscribe({
|
|
next: (response) => {
|
|
console.log('Événement créé:', response);
|
|
this.loadEvents();
|
|
this.selectedEvent = null;
|
|
this.isEditing = false;
|
|
this.isLoading = false;
|
|
},
|
|
error: (error) => {
|
|
console.error('Erreur lors de la création:', error);
|
|
this.isLoading = false;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
deleteEvent() {
|
|
if (!this.selectedEvent) return;
|
|
|
|
const eventId = this.selectedEvent.id || this.selectedEvent.properties?.id;
|
|
if (!eventId) return;
|
|
|
|
if (confirm('Êtes-vous sûr de vouloir supprimer cet événement ?')) {
|
|
this.isLoading = true;
|
|
this.OedbApi.deleteEvent(eventId).subscribe({
|
|
next: (response) => {
|
|
console.log('Événement supprimé:', response);
|
|
this.loadEvents();
|
|
this.selectedEvent = null;
|
|
this.isEditing = false;
|
|
this.isLoading = false;
|
|
},
|
|
error: (error) => {
|
|
console.error('Erreur lors de la suppression:', error);
|
|
this.isLoading = false;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
getEventTitle(event: any): string {
|
|
return event?.properties?.what ||
|
|
event?.properties?.label ||
|
|
event?.properties?.name ||
|
|
'Événement sans nom';
|
|
}
|
|
|
|
getEventDescription(event: any): string {
|
|
return event?.properties?.description ||
|
|
event?.properties?.where ||
|
|
'Aucune description';
|
|
}
|
|
|
|
getObjectKeys(obj: any): string[] {
|
|
return Object.keys(obj || {});
|
|
}
|
|
|
|
isGeocodingProperty(prop: string): boolean {
|
|
const geocodingProps = [
|
|
'lat', 'lon', 'place_type', 'place_importance', 'housenumber', 'street',
|
|
'postcode', 'city', 'region', 'country', 'geocoding_source', 'geocoding_date'
|
|
];
|
|
return geocodingProps.includes(prop);
|
|
}
|
|
}
|