I am in the process of making an application for plotting running routes on a map and saving them to a mongodb database.
Currently, I am using puppeteer to visit a route in my application and passing the coordinates as a query string to the map component. Once the map is loaded, I take the screenshot, convert the returned Buffer into a base64 encoded string, save that to the database, and use the string to show the image on the frontend.
The middleware for the whole process looks like this:
exports.screenshotMap = async (req, res, next) => {
try {
const { lineFeatures } = req.body;
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
// open new browser
const page = await browser.newPage();
// create array of coordinates from geojson features
const coords = lineFeatures.map(line => line.geometry.coordinates);
// reduce to 2D array of [lat, lon] coords
const flattenedCoords = coords.reduce((accum, arr) => {
return accum.concat(arr);
}, []);
// Stringify coords before using them as query string
const coordsStr = JSON.stringify(flattenedCoords);
// goto page with map sending coordintaes along
await page.goto(`http://localhost:3000/test?coords=${coordsStr}`, {
waitUntil: 'networkidle0',
});
// wait for map to load, call onLoad callback, and set state to make the h1 visible
await page.waitForSelector('h1');
// wait one more second to make sure all tiles for the map are loaded. Longer routes can require significantly more tiles
await page.waitFor(1000);
const image = await page.screenshot({
type: 'jpeg',
quality: 100,
clip: {
x: 0,
y: 70,
width: 640,
height: 360,
},
omitBackground: true,
});
await browser.close();
// convert buffer to base64 string
const base64Image = await image.toString('base64');
// attach to request object to be used in the next middleware
req.image = base64Image;
next();
} catch (err) {
res.status(400).send(err);
}
};
This approach works, however I am wondering if there is a better way. I have read the storing the Buffer data is better for database memory purposes, as the base64 strings are very long. Would a better way be to save the Buffer data and convert it to an encoded string before sending it back to the client? Is there a recommended way for dealing with this kind of data? I am interested in hearing others thoughts and approaches.
The solution I came up with was to save the buffer returned from the screenshot to an S3 bucket and then store the unique identifier url to my data base.
Here is the middleware for screen taking the screenshot:
import puppeteer from 'puppeteer';
//import chromium from 'chrome-aws-lambda';
const takeMapImage = handler => async (req, res) => {
try {
const { lines } = req.body;
// should work for most node projects
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
// if youre using lambda functions
// const browser = await chromium.puppeteer.launch({
// executablePath: await chromium.executablePath,
// args: chromium.args,
// defaultViewport: chromium.defaultViewport,
// headless: chromium.headless,
// });
// open new browser
const page = await browser.newPage();
const url = 'https://yourwebstieurl.com';
await page.goto(
`${url}`,
{
waitUntil: 'networkidle0',
}
);
// provide some waiting time if needed
await page.waitFor(1000);
// image buffer returned from screenshot
const imageBuffer = await page.screenshot({
type: 'jpeg',
quality: 100,
clip: {
x: 0,
y: 0,
width: 640,
height: 360,
},
omitBackground: true,
});
// attach to request object to be used in the next middleware
req.buffer = imageBuffer;
next();
} catch (err) {
console.log(err);
return res
.status(422)
.send({ message: 'there was an error taking picture' });
}
};
Then the middleware for saving the image to s3. You will need to create a bucket and get credentials:
import AWS from 'aws-sdk';
import uuid from 'uuid/v1';
// create your s3instance with your credentials
const s3 = new AWS.S3({
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
});
const saveImageToS3 = handler => async (req, res) => {
try {
// use user id as identifier in S3 bucket
const { id } = req.user;
const { buffer } = req;
// s3 file name
const key = `${id}/${uuid()}.jpeg`;
const params = {
Bucket: 'your-bucket-name',
Key: key,
Body: buffer,
ContentEncoding: 'base64',
ContentType: 'image/jpeg',
};
// upload to bucket
const response = await s3.upload(params).promise();
// pass url to next middleware to save to db
req.image = response.Location;
next();
} catch (e) {
console.log(e);
return res.status(422).json({ message: 'user error saving image to s3' });
}
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With