import BaseService from './BaseService.js'; import TaxiMap from '../models/taxi/taxiMap.js'; import TaxiMapType from '../models/taxi/taxiMapType.js'; import TaxiMapTile from '../models/taxi/taxiMapTile.js'; import TaxiStreetName from '../models/taxi/taxiStreetName.js'; import TaxiMapTileStreet from '../models/taxi/taxiMapTileStreet.js'; import TaxiMapTileHouse from '../models/taxi/taxiMapTileHouse.js'; class TaxiMapService extends BaseService { constructor() { super(); } /** * Holt alle verfügbaren Map-Typen */ async getMapTypes() { try { const mapTypes = await TaxiMapType.findAll({ where: { isActive: true }, order: [['name', 'ASC']] }); return mapTypes; } catch (error) { console.error('Error getting map types:', error); throw error; } } /** * Holt alle verfügbaren Maps */ async getMaps() { try { const maps = await TaxiMap.findAll({ where: { isActive: true }, include: [ { model: TaxiMapType, as: 'mapType' }, { model: TaxiMapTile, as: 'tiles' }, { model: TaxiMapTileStreet, as: 'tileStreets', include: [ { model: TaxiStreetName, as: 'streetNameH' }, { model: TaxiStreetName, as: 'streetNameV' } ] }, { model: TaxiMapTileHouse, as: 'tileHouses' } ], order: [['name', 'ASC']] }); return maps; } catch (error) { console.error('Error getting maps:', error); throw error; } } /** * Holt eine spezifische Map */ async getMapById(mapId) { try { const map = await TaxiMap.findOne({ where: { id: mapId, isActive: true }, include: [ { model: TaxiMapType, as: 'mapType' }, { model: TaxiMapTile, as: 'tiles' }, { model: TaxiMapTileStreet, as: 'tileStreets', include: [ { model: TaxiStreetName, as: 'streetNameH' }, { model: TaxiStreetName, as: 'streetNameV' } ] }, { model: TaxiMapTileHouse, as: 'tileHouses' } ] }); return map; } catch (error) { console.error('Error getting map by ID:', error); throw error; } } /** * Holt die Standard-Map */ async getDefaultMap() { try { const map = await TaxiMap.findOne({ where: { isDefault: true, isActive: true }, include: [{ model: TaxiMapType, as: 'mapType' }] }); return map; } catch (error) { console.error('Error getting default map:', error); throw error; } } /** * Erstellt eine neue Map */ async createMap(mapData) { try { const { tiles, tileStreetNames, tileHouses, ...mapFields } = mapData; // mapData JSON ist entfernt – map erstellen ohne mapData const map = await TaxiMap.create(mapFields); // Tiles upsert (optional) if (Array.isArray(tiles) && tiles.length > 0) { await this.upsertTiles(map.id, tiles); } // Street names (optional) if (tileStreetNames && Object.keys(tileStreetNames).length > 0) { await this.upsertTileStreetNames(map.id, tileStreetNames); } if (tileHouses && Object.keys(tileHouses).length > 0) { await this.upsertTileHouses(map.id, tileHouses); } return await this.getMapById(map.id); } catch (error) { console.error('Error creating map:', error); throw error; } } /** * Aktualisiert eine Map */ async updateMap(mapId, updateData) { try { const { tiles, tileStreetNames, tileHouses, ...mapFields } = updateData; const [updatedRowsCount] = await TaxiMap.update(mapFields, { where: { id: mapId } }); if (updatedRowsCount === 0) { throw new Error('Map not found'); } // Tiles upsert (optional) if (Array.isArray(tiles)) { await this.upsertTiles(mapId, tiles); } if (tileStreetNames && Object.keys(tileStreetNames).length > 0) { await this.upsertTileStreetNames(mapId, tileStreetNames); } if (tileHouses && Object.keys(tileHouses).length > 0) { await this.upsertTileHouses(mapId, tileHouses); } return await this.getMapById(mapId); } catch (error) { console.error('Error updating map:', error); throw error; } } /** * Speichert Straßennamen-Belegungen für eine Map * @param {number} mapId * @param {{[cellKey:string]:{streetNameH?:string, streetNameV?:string}}} tileNames */ async upsertTileStreetNames(mapId, tileNames) { const entries = Object.entries(tileNames || {}); for (const [cellKey, data] of entries) { const [x, y] = cellKey.split(',').map(Number); const record = { mapId, x, y }; // Street names: findOrCreate if (data.streetNameH) { const [snH] = await TaxiStreetName.findOrCreate({ where: { name: data.streetNameH }, defaults: { name: data.streetNameH } }); record.streetNameHId = snH.id; } else { record.streetNameHId = null; } if (data.streetNameV) { const [snV] = await TaxiStreetName.findOrCreate({ where: { name: data.streetNameV }, defaults: { name: data.streetNameV } }); record.streetNameVId = snV.id; } else { record.streetNameVId = null; } const [row] = await TaxiMapTileStreet.findOrCreate({ where: { mapId, x, y }, defaults: record }); await row.update(record); } return { success: true }; } /** * Upsert Tiles (x,y,tileType,meta?) pro Map * Erwartet: tiles: Array<{x:number,y:number,tileType:string, meta?:object}> */ async upsertTiles(mapId, tiles) { for (const tile of tiles) { const { x, y, tileType, meta } = tile; if (typeof x !== 'number' || typeof y !== 'number' || !tileType) continue; const [row] = await TaxiMapTile.findOrCreate({ where: { mapId, x, y }, defaults: { mapId, x, y, tileType, meta: meta || null } }); // trafficLight kann in meta.trafficLight oder künftig in eigener Spalte liegen const trafficLight = !!(meta && meta.trafficLight); await row.update({ tileType, meta: meta && Object.keys(meta).length ? meta : null, trafficLight }); } return { success: true }; } /** * Speichert Häuser pro Tile (je Ecke eine Zeile) * @param {number} mapId * @param {{[cellKey:string]: {[corner:string]: number}}} tileHouses */ async upsertTileHouses(mapId, tileHouses) { // Löschen und neu anlegen pro Zelle ist am einfachsten und robust const entries = Object.entries(tileHouses || {}); for (const [cellKey, cornerMap] of entries) { const [x, y] = cellKey.split(',').map(Number); await TaxiMapTileHouse.destroy({ where: { map_id: mapId, x, y } }); for (const [corner, rotation] of Object.entries(cornerMap || {})) { await TaxiMapTileHouse.create({ mapId, x, y, corner, rotation: Number(rotation) || 0 }); } } return { success: true }; } /** * Löscht eine Map (soft delete) */ async deleteMap(mapId) { try { const [updatedRowsCount] = await TaxiMap.update( { isActive: false }, { where: { id: mapId } } ); if (updatedRowsCount === 0) { throw new Error('Map not found'); } return { success: true }; } catch (error) { console.error('Error deleting map:', error); throw error; } } /** * Setzt eine Map als Standard */ async setDefaultMap(mapId) { try { // Entferne Standard-Status von allen anderen Maps await TaxiMap.update( { isDefault: false }, { where: { isDefault: true } } ); // Setze neue Standard-Map const [updatedRowsCount] = await TaxiMap.update( { isDefault: true }, { where: { id: mapId } } ); if (updatedRowsCount === 0) { throw new Error('Map not found'); } return { success: true }; } catch (error) { console.error('Error setting default map:', error); throw error; } } /** * Initialisiert Standard-Map-Typen */ async initializeMapTypes() { try { const mapTypes = [ { name: 'Corner Bottom Left', tileType: 'cornerBottomLeft', description: 'Bottom left corner tile' }, { name: 'Corner Bottom Right', tileType: 'cornerBottomRight', description: 'Bottom right corner tile' }, { name: 'Corner Top Left', tileType: 'cornerTopLeft', description: 'Top left corner tile' }, { name: 'Corner Top Right', tileType: 'cornerTopRight', description: 'Top right corner tile' }, { name: 'Horizontal', tileType: 'horizontal', description: 'Horizontal road tile' }, { name: 'Vertical', tileType: 'vertical', description: 'Vertical road tile' }, { name: 'Cross', tileType: 'cross', description: 'Cross intersection tile' }, { name: 'Fuel Horizontal', tileType: 'fuelHorizontal', description: 'Horizontal road with fuel station' }, { name: 'Fuel Vertical', tileType: 'fuelVertical', description: 'Vertical road with fuel station' }, { name: 'T-Left', tileType: 'tLeft', description: 'T-junction facing left' }, { name: 'T-Right', tileType: 'tRight', description: 'T-junction facing right' }, { name: 'T-Up', tileType: 'tUp', description: 'T-junction facing up' }, { name: 'T-Down', tileType: 'tDown', description: 'T-junction facing down' } ]; for (const mapType of mapTypes) { await TaxiMapType.findOrCreate({ where: { name: mapType.name }, defaults: mapType }); } console.log('Taxi map types initialized'); } catch (error) { console.error('Error initializing map types:', error); throw error; } } /** * Erstellt eine Standard-Map */ async createDefaultMap() { } } export default TaxiMapService;