145 lines
4.5 KiB
TypeScript
145 lines
4.5 KiB
TypeScript
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<number>;
|
|
totalChunks: number;
|
|
}
|
|
|
|
const uploadProgress: Record<string, UploadProgress> = {};
|
|
|
|
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<void> {
|
|
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<Buffer> => {
|
|
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}`));
|