import { languageInstance } from "@shared"; import { makeAutoObservable } from "mobx"; interface ApiRoute { id: number; route_number: string; path: [number, number][]; } interface ApiStation { id: number; name: string; latitude: number; longitude: number; } interface ApiSight { id: number; name: string; description: string; latitude: number; longitude: number; } // Допуск для сравнения координат, чтобы избежать ошибок с точностью чисел. const COORDINATE_PRECISION_TOLERANCE = 1e-9; // Вспомогательная функция, обновленная для сравнения с допуском. const arePathsEqual = ( path1: [number, number][], path2: [number, number][] ): boolean => { if (path1.length !== path2.length) { return false; } for (let i = 0; i < path1.length; i++) { if ( Math.abs(path1[i][0] - path2[i][0]) > COORDINATE_PRECISION_TOLERANCE || Math.abs(path1[i][1] - path2[i][1]) > COORDINATE_PRECISION_TOLERANCE ) { return false; } } return true; }; class MapStore { constructor() { makeAutoObservable(this); } routes: ApiRoute[] = []; stations: ApiStation[] = []; sights: ApiSight[] = []; getRoutes = async () => { const response = await languageInstance("ru").get("/route"); const routesIds = response.data.map((route: any) => route.id); for (const id of routesIds) { const route = await languageInstance("ru").get(`/route/${id}`); this.routes.push({ id: route.data.id, route_number: route.data.route_number, path: route.data.path, }); } const mappedRoutes: ApiRoute[] = response.data.map((route: any) => ({ id: route.id, route_number: route.route_number, path: route.path, })); this.routes = mappedRoutes.sort((a, b) => a.route_number.localeCompare(b.route_number) ); }; getStations = async () => { const stations = await languageInstance("ru").get("/station"); this.stations = stations.data.map((station: any) => ({ id: station.id, name: station.name, latitude: station.latitude, longitude: station.longitude, })); }; getSights = async () => { const sights = await languageInstance("ru").get("/sight"); this.sights = sights.data.map((sight: any) => ({ id: sight.id, name: sight.name, description: sight.description, latitude: sight.latitude, longitude: sight.longitude, })); }; deleteRecourse = async (recourse: string, id: number) => { await languageInstance("ru").delete(`/${recourse}/${id}`); if (recourse === "route") { this.routes = this.routes.filter((route) => route.id !== id); } else if (recourse === "station") { this.stations = this.stations.filter((station) => station.id !== id); } else if (recourse === "sight") { this.sights = this.sights.filter((sight) => sight.id !== id); } }; handleSave = async (json: string) => { const newSights: any[] = []; const newRoutes: any[] = []; const newStations: any[] = []; const updatedSights: any[] = []; const updatedRoutes: any[] = []; const updatedStations: any[] = []; const parsedJSON = JSON.parse(json); for (const feature of parsedJSON.features) { const { geometry, properties, id } = feature; const idParts = String(id).split("-"); if (idParts.length > 1) { const featureType = idParts[0]; const numericId = parseInt(idParts[1], 10); if (isNaN(numericId)) continue; if (featureType === "station") { const originalStation = this.stations.find((s) => s.id === numericId); if (!originalStation) continue; const currentStation = { name: properties.name || "", latitude: geometry.coordinates[1], longitude: geometry.coordinates[0], }; // ИЗМЕНЕНИЕ: Сравнение координат с допуском if ( originalStation.name !== currentStation.name || Math.abs(originalStation.latitude - currentStation.latitude) > COORDINATE_PRECISION_TOLERANCE || Math.abs(originalStation.longitude - currentStation.longitude) > COORDINATE_PRECISION_TOLERANCE ) { updatedStations.push({ id: numericId, ...currentStation }); } } else if (featureType === "route") { const originalRoute = this.routes.find((r) => r.id === numericId); if (!originalRoute) continue; const currentRoute = { route_number: properties.name || "", path: geometry.coordinates, }; // ИЗМЕНЕНИЕ: Используется новая функция arePathsEqual с допуском if ( originalRoute.route_number !== currentRoute.route_number || !arePathsEqual(originalRoute.path, currentRoute.path) ) { updatedRoutes.push({ id: numericId, ...currentRoute }); } } else if (featureType === "sight") { const originalSight = this.sights.find((s) => s.id === numericId); if (!originalSight) continue; const currentSight = { name: properties.name || "", description: properties.description || "", latitude: geometry.coordinates[1], longitude: geometry.coordinates[0], }; // ИЗМЕНЕНИЕ: Сравнение координат с допуском if ( originalSight.name !== currentSight.name || originalSight.description !== currentSight.description || Math.abs(originalSight.latitude - currentSight.latitude) > COORDINATE_PRECISION_TOLERANCE || Math.abs(originalSight.longitude - currentSight.longitude) > COORDINATE_PRECISION_TOLERANCE ) { updatedSights.push({ id: numericId, ...currentSight }); } } } else { if (properties.featureType === "station") { newStations.push({ name: properties.name || "", latitude: geometry.coordinates[1], longitude: geometry.coordinates[0], }); } else if (properties.featureType === "route") { newRoutes.push({ route_number: properties.name || "", path: geometry.coordinates, }); } else if (properties.featureType === "sight") { newSights.push({ name: properties.name || "", description: properties.description || "", latitude: geometry.coordinates[1], longitude: geometry.coordinates[0], }); } } } const requests: Promise[] = []; newStations.forEach((data) => requests.push(languageInstance("ru").post("/station", data)) ); newRoutes.forEach((data) => requests.push(languageInstance("ru").post("/route", data)) ); newSights.forEach((data) => requests.push(languageInstance("ru").post("/sight", data)) ); updatedStations.forEach(({ id, ...data }) => requests.push(languageInstance("ru").patch(`/station/${id}`, data)) ); updatedRoutes.forEach(({ id, ...data }) => requests.push(languageInstance("ru").patch(`/route/${id}`, data)) ); updatedSights.forEach(({ id, ...data }) => requests.push(languageInstance("ru").patch(`/sight/${id}`, data)) ); if (requests.length === 0) { return; } try { await Promise.all(requests); await Promise.all([ this.getRoutes(), this.getStations(), this.getSights(), ]); } catch (error) { console.error("Ошибка при сохранении данных:", error); throw error; } }; } export default new MapStore();