import express, { Request, Response } from 'express'; import fs from 'fs'; import path from 'path'; import axios from 'axios'; import multer from 'multer' import sharp from 'sharp'; import { pipeline } from 'stream'; import pump from 'pump' import md5 from 'md5' import bodyParser from 'body-parser'; import cors from 'cors' const app = express(); const PORT = process.env.EMS_PORT || 5000; const tileFolder = path.join(__dirname, '..', 'public', 'tile_data'); const uploadDir = path.join(__dirname, '..', 'public', 'uploads'); interface UploadProgress { receivedChunks: Set; totalChunks: number; } const uploadProgress: Record = {}; app.use(bodyParser.raw({ type: 'application/octet-stream', limit: '100mb' })) app.use(cors()) // Upload chunk handler app.post('/upload', (req: Request, res: Response) => { const chunkNumber = parseInt(req.headers['x-chunk-number'] as string, 10); const totalChunks = parseInt(req.headers['x-total-chunks'] as string, 10); const fileId = req.headers['x-file-id'] as string; if (isNaN(chunkNumber) || isNaN(totalChunks) || !fileId) { return res.status(400).send('Invalid headers'); } const chunkDir = path.join(uploadDir, fileId); if (!fs.existsSync(chunkDir)) { fs.mkdirSync(chunkDir, { recursive: true }); } // Save the chunk const chunkPath = path.join(chunkDir, `chunk-${chunkNumber}`); fs.writeFileSync(chunkPath, req.body); // Initialize or update upload progress if (!uploadProgress[fileId]) { uploadProgress[fileId] = { receivedChunks: new Set(), totalChunks }; } uploadProgress[fileId].receivedChunks.add(chunkNumber); // Check if all chunks have been received if (uploadProgress[fileId].receivedChunks.size === totalChunks) { assembleChunks(fileId, chunkDir) .then(() => { delete uploadProgress[fileId]; // Clean up progress tracking res.status(200).send('File assembled successfully'); }) .catch((error) => { console.error('Error assembling file:', error); res.status(500).send('Failed to assemble file'); }); } else { res.status(200).send('Chunk received'); } }); // Assemble chunks into final file async function assembleChunks(fileId: string, chunkDir: string): Promise { const finalPath = path.join(uploadDir, fileId); const chunks = fs.readdirSync(chunkDir).sort((a, b) => { const numA = parseInt(a.split('-')[1]); const numB = parseInt(b.split('-')[1]); return numA - numB; }); const writeStream = fs.createWriteStream(finalPath); for (const chunk of chunks) { const chunkPath = path.join(chunkDir, chunk); const data = fs.readFileSync(chunkPath); writeStream.write(data); fs.unlinkSync(chunkPath); // Remove chunk after writing } writeStream.end(() => { fs.rmdirSync(chunkDir); // Remove temporary chunk directory }); } const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, path.join(__dirname, '..', 'public', 'uploads')) }, filename: function (req, file, cb) { cb(null, file.originalname) } }) const upload = multer({ storage: storage }) const fetchTileFromAPI = async (provider: string, z: string, x: string, y: string): Promise => { const url = provider === 'google' ? `https://khms2.google.com/kh/v=984?x=${x}&y=${y}&z=${z}` : `https://core-sat.maps.yandex.net/tiles?l=sat&x=${x}&y=${y}&z=${z}&scale=1&lang=ru_RU`; const response = await axios.get(url, { responseType: 'arraybuffer' }); return response.data; } app.get('/tile/:provider/:z/:x/:y', async (req: Request, res: Response) => { const { provider, z, x, y } = req.params; if (!['google', 'yandex'].includes(provider)) { return res.status(400).send('Invalid provider'); } const tilePath = path.join(tileFolder, provider, z, x, `${y}.jpg`); if (fs.existsSync(tilePath)) { return res.sendFile(tilePath); } try { const tileData = await fetchTileFromAPI(provider, z, x, y); fs.mkdirSync(path.dirname(tilePath), { recursive: true }); fs.writeFileSync(tilePath, tileData); res.contentType('image/jpeg'); res.send(tileData); } catch (error) { console.error('Error fetching tile from API:', error); res.status(500).send('Error fetching tile from API'); } }); app.listen(PORT, () => console.log(`Server running on port ${PORT}`));