gfw-api/gfw-subscription-api

View on GitHub
app/src/services/imageService.js

Summary

Maintainability
C
1 day
Test Coverage
F
16%
const logger = require('logger');
const Mustache = require('mustache');
const ctRegisterMicroservice = require('ct-register-microservice-node');
const JSONAPIDeserializer = require('jsonapi-serializer').Deserializer;
const request = require('request-promise-native');
const CartoDB = require('cartodb');
const config = require('config');
const explode = require('turf-explode');
const AWS = require('aws-sdk');

const geoQuery = require('services/imageService/geoQuery.json');
const viirsTemplate = require('services/imageService/template/viirs.json');
const formaAlertsTemplate = require('services/imageService/template/forma-alerts.json');
const imazonAlertsTemplate = require('services/imageService/template/imazon-alerts.json');

const LAYERS_PARAMS_MAP = {
    'viirs-active-fires': viirsTemplate,
    'forma-alerts': formaAlertsTemplate,
    'imazon-alerts': imazonAlertsTemplate
};

const executeThunk = (client, sql, params) => (
    new Promise((resolve, reject) => {
        client.execute(sql, params).done((data) => resolve(data)).error((err) => reject(err));
    }));

async function getQuery(subscription) {
    if (subscription.params.iso && subscription.params.iso.country) {
        if (!subscription.params.iso.region) {
            return Mustache.render(geoQuery.ISO, {
                iso: subscription.params.iso.country
            });
        }
        return Mustache.render(geoQuery.ID1, {
            iso: subscription.params.iso.country,
            id1: subscription.params.iso.region
        });

    }
    if (subscription.params.wdpaid) {
        return Mustache.render(geoQuery.WDPA, {
            wdpaid: subscription.params.wdpaid
        });
    }
    if (subscription.params.use) {
        return Mustache.render(geoQuery.USE, {
            use_table: subscription.params.use,
            pid: subscription.params.useid
        });
    }
    if (subscription.params.geostore) {
        try {
            const result = await ctRegisterMicroservice.requestToMicroservice({
                uri: `/geostore/${subscription.params.geostore}`,
                method: 'GET',
                json: true
            });

            const geostore = await new JSONAPIDeserializer({
                keyForAttribute: 'camelCase'
            }).deserialize(result);
            logger.debug(`Geostore geometry: ${JSON.stringify(geostore.geojson.features[0].geometry)}`);
            const renderedQuery = Mustache.render(geoQuery.WORLD, {
                geojson: JSON.stringify(geostore.geojson.features[0].geometry).replace(/"/g, '\\"')
            });

            logger.debug(`Query: ${renderedQuery}`);

            return renderedQuery;
        } catch (e) {
            logger.error(e);
            return null;
        }

    }

    return null;
}

async function getBBoxQuery(client, subscription) {
    if (subscription.params.iso && subscription.params.iso.country) {
        if (!subscription.params.iso.region) {

            const data = await executeThunk(client, geoQuery.ISO_BBOX, {
                iso: subscription.params.iso.country
            });
            return data.rows[0].bbox;
        }
        const data = await executeThunk(client, geoQuery.ID1_BBOX, {
            iso: subscription.params.iso.country,
            id1: subscription.params.iso.region
        });
        return data.rows[0].bbox;

    }
    if (subscription.params.wdpaid) {
        const data = await executeThunk(client, geoQuery.WDPA_BBOX, {
            wdpaid: subscription.params.wdpaid
        });
        return data.rows[0].bbox;
    }
    if (subscription.params.use) {
        const data = await executeThunk(client, geoQuery.USE_BBOX, {
            use_table: subscription.params.use,
            pid: subscription.params.useid
        });
        return data.rows[0].bbox;
    }
    if (subscription.params.geostore) {
        try {
            const result = await ctRegisterMicroservice.requestToMicroservice({
                uri: `/geostore/${subscription.params.geostore}`,
                method: 'GET',
                json: true
            });

            const geostore = await new JSONAPIDeserializer({
                keyForAttribute: 'camelCase'
            }).deserialize(result);
            const data = await executeThunk(client, geoQuery.WORLD_BBOX, {
                geojson: JSON.stringify(geostore.geojson.features[0].geometry)

            });
            return data.rows[0].bbox;
        } catch (e) {
            logger.error('Error obtaining geostore', e);
            return null;
        }
    }

    return null;
}

function getBBoxOfGeojson(geojson) {
    const points = explode(geojson);
    let minx = 360;
    let
        miny = 360;
    let maxx = -360;
    let
        maxy = -360;
    for (let i = 0, { length } = points.features; i < length; i++) {
        const point = points.features[i].geometry.coordinates;
        if (minx > point[0]) {
            [minx] = point;
        }
        if (maxx < point[0]) {
            [maxx] = point;
        }
        if (miny > point[1]) {
            [, miny] = point;
        }
        if (maxy < point[1]) {
            [, maxy] = point;
        }
    }
    return `${minx},${miny},${maxx},${maxy}`;
}

async function getCartoStaticImage(url) {
    return request({
        url,
        method: 'GET',
        encoding: null,
        headers: {
            'Content-Type': 'image/png'
        },
        resolveWithFullResponse: true
    });
}

async function getS3Url(imageKey, staticImage) {
    const s3 = new AWS.S3();

    return new Promise((fulfill) => {
        s3.upload({
            Bucket: 'gfw2stories',
            Key: `map_preview/${imageKey}`,
            ContentType: staticImage.headers['content-type'],
            ACL: 'public-read',
            Body: staticImage.body
        }, (err, data) => {
            if (err !== null) {
                fulfill(null);
            } else {
                fulfill(data.Location);
            }
        });
    });
}

async function getImageUrl(layergroupid, bbox) {
    const imageKey = `${layergroupid}_${bbox}.png`;
    const staticImage = await getCartoStaticImage(`http://${process.env.CARTODB_USER}.cartodb.com/api/v1/map/static/bbox/${layergroupid}/${bbox}/700/450.png`);
    return getS3Url(imageKey, staticImage);
}

class ImageService {

    constructor() {
        this.client = new CartoDB.SQL({
            user: config.get('cartoDB.user')
        });
    }

    async overviewImage(subscription, slug, begin, end) {
        const query = await getQuery(subscription);
        if (!query) {
            return null;
        }
        const mustacheConfig = {
            begin: begin.toISOString().slice(0, 10),
            end: end.toISOString().slice(0, 10),
            query
        };

        const template = Mustache.render(JSON.stringify(LAYERS_PARAMS_MAP[slug]), mustacheConfig).replace(/\s\s+/g, ' ').trim();
        const result = await request({
            url: `https://${process.env.CARTODB_USER}.cartodb.com/api/v1/map`,
            method: 'POST',
            body: template,
            headers: {
                'Content-Type': 'application/json'
            },
            resolveWithFullResponse: true
        });
        if (result.statusCode !== 200) {
            logger.info('Error obtaining layergroupid');
            logger.info(result.body);
            return null;
        }
        result.body = JSON.parse(result.body);
        if (result.body.layergroupid) {
            const queryBBox = await getBBoxQuery(this.client, subscription);
            const bbox = getBBoxOfGeojson(JSON.parse(queryBBox));
            return getImageUrl(result.body.layergroupid, bbox);
        }
        return null;

    }

}

module.exports = new ImageService();