gfw-api/gfw-area

View on GitHub
app/src/services/download.service.js

Summary

Maintainability
A
2 hrs
Test Coverage
B
86%
const logger = require('logger');
const GeostoreNotFound = require('errors/geostore-not-found.error');
const rimraf = require('rimraf');
const { RWAPIMicroservice } = require('rw-api-microservice-node');
const tilebelt = require('@mapbox/tilebelt');
const tmp = require('tmp');
const fs = require('fs');
const zip = require('zip-a-folder');
const request = require('request');

const CONCURRENCY = 30;

class DownloadService {

    static async getBBox(geostoreId, apiKey) {
        try {
            const result = await RWAPIMicroservice.requestToMicroservice({
                uri: `/v1/geostore/${geostoreId}`,
                method: 'GET',
                headers: {
                    'x-api-key': apiKey
                }
            });
            return result.data.attributes.bbox;
        } catch (err) {
            logger.error(err);
            throw new GeostoreNotFound(`Geostore with id ${geostoreId} not found`);
        }
    }

    static calculateCoordinates(bbox, minZoom, maxZoom) {
        logger.debug('Calculating coordinates');
        const zooms = [];
        const tilesArray = [];
        logger.debug(minZoom, maxZoom);
        for (let i = minZoom; i <= maxZoom; i++) {
            zooms.push(i);
        }
        logger.debug('zooms', zooms);
        zooms.forEach((zoom) => {
            const pointTiles = [];
            pointTiles.push(tilebelt.pointToTile(bbox[0], bbox[1], zoom));
            pointTiles.push(tilebelt.pointToTile(bbox[2], bbox[3], zoom));
            logger.debug(pointTiles);
            const tiles = {
                x: [Math.min(pointTiles[0][0], pointTiles[1][0]), Math.max(pointTiles[0][0], pointTiles[1][0])],
                y: [Math.min(pointTiles[0][1], pointTiles[1][1]), Math.max(pointTiles[0][1], pointTiles[1][1])]
            };
            logger.debug('tiles', tiles);
            for (let x = tiles.x[0], xLength = tiles.x[1]; x <= xLength; x++) {
                for (let y = tiles.y[0], yLength = tiles.y[1]; y <= yLength; y++) {
                    tilesArray.push([x, y, zoom]);
                }
            }
        });

        return tilesArray;

    }

    static async downloadImage(layerUrl, z, x, y, tempDir, useExtension) {
        logger.debug('Downloading image ', layerUrl.replace('{z}', z).replace('{x}', x).replace('{y}', y));
        const url = layerUrl.replace('{z}', z).replace('{x}', x).replace('{y}', y);

        return new Promise((resolve, reject) => {
            request.get(url)
                .on('response', (res) => {
                    let filename = `${z}x${x}x${y}`;

                    if (useExtension) {
                        if (res.headers['content-type'] === 'image/png') {
                            filename += '.png';
                        } else {
                            filename += '.jpg';
                        }
                    }
                    const fws = fs.createWriteStream(`${tempDir}/${filename}`);
                    // setup piping
                    res.pipe(fws);
                    res.on('end', () => {
                        resolve();
                    });
                    res.on('error', () => {
                        reject();
                    });
                });
        });
    }

    static async zipFolder(folder, zipDir) {
        logger.debug(`Zipping folder ${folder}`);
        return zip.zip(folder, zipDir, { compression: 1 });
    }

    static async removeFolder(path) {
        return new Promise((resolve, reject) => {
            rimraf(path, (err) => {
                if (err) {
                    logger.error(`Error removing folder ${path}: ${err}`);
                    reject(err);
                    return;
                }
                resolve();
            });
        });
    }

    static async downloadAndZipCoordinates(coordinates, layerUrl, useExtension) {
        logger.debug('Downloading coordinates', coordinates);
        const tmpobj = tmp.dirSync();
        const tmpDownload = tmp.dirSync();
        let promises = [];
        try {
            for (let i = 0, { length } = coordinates; i < length; i++) {
                promises.push(DownloadService.downloadImage(layerUrl, coordinates[i][2], coordinates[i][0], coordinates[i][1], tmpobj.name, useExtension));
                if (promises.length === CONCURRENCY) {
                    await Promise.all(promises);
                    promises = [];
                }
            }
            if (promises.length > 0) {
                await Promise.all(promises);
                promises = null;
            }
            // eslint-disable-next-line no-empty
        } catch (err) {
        }
        await DownloadService.zipFolder(tmpobj.name, `${tmpDownload.name}/download.zip`);
        logger.info('Removing file ', tmpobj.name);
        await DownloadService.removeFolder(tmpobj.name);
        return `${tmpDownload.name}/download.zip`;
    }

    static async getTilesZip(geostoreId, minZoom, maxZoom, layerUrl, apiKey, useExtension = true) {
        const bbox = await DownloadService.getBBox(geostoreId, apiKey);
        const coordinates = DownloadService.calculateCoordinates(bbox, minZoom, maxZoom);
        logger.debug('Coordinates', coordinates);
        return DownloadService.downloadAndZipCoordinates(coordinates, layerUrl, useExtension);
    }

}

module.exports = DownloadService;