I am fetching and processing images with nodejs, using node-fetch and canvas. So far, things have been working well. I have a series of image urls, and I fetch them all in parallel using Promise.all
:
import { loadImage } from 'canvas';
await Promise.all<CanvasImageSource>(
urls.map((url: string) => loadImage(url)) // <--- return array of promises
)
.then((images: CanvasImageSource[]): void => {
// do some stuff with the images
})
.catch((e) => { throw e });
This has been working great. But last night I tried a certain image source url that I want to use, and I'm getting the following error:
Error: read ECONNRESET
at TLSWrap.onStreamRead (internal/stream_base_commons.js:205:27)
---------------------------------------------
at TLSSocket.Readable.on (_stream_readable.js:838:35)
at tickOnSocket (_http_client.js:696:10)
at onSocketNT (_http_client.js:747:5)
at processTicksAndRejections (internal/process/task_queues.js:84:21)
---------------------------------------------
at ClientRequest.onSocket (_http_client.js:735:11)
at setRequestSocket (_http_agent.js:396:7)
at handleSocketCreation_Inner (_http_agent.js:389:7)
at oncreate (_http_agent.js:262:5)
at Agent.createSocket (_http_agent.js:267:5)
at Agent.addRequest (_http_agent.js:224:10)
at new ClientRequest (_http_client.js:296:16)
at Object.request (https.js:314:10)
at simpleGet ../../etc
I read How do I debug error ECONNRESET in Node.js?
, and the answer there seems to suggest that this is an error from the server side. However, when I print the urls I'm passing to loadImage
and then access them in the browser, I am able to get an image back just fine, in the browser. One such url is
https://apps.fs.usda.gov/arcx/rest/services/RDW_Wildfire/ProbabilisticWildfireRisk/MapServer/export?bbox=-13795354.864908611%2C6095394.383573096%2C-13785570.92528811%2C6085610.443952593&size=256%2C256&format=png32&bboxSR=102100&imageSR=102100&f=image&layers=show%3A0
which is a GIS raster image service from the US forestry department. Going that url is my browser returns the image no problem.
I thought that perhaps I might be blasting their server with too many requests at once, as the array of urls usually has 6-10 image urls in it for any given call of the function that runs this code, but I reduced the number of urls to 1 so as to only make 1 request, but no change. Still an error. One thing I noticed when accessing these urls in the browser is that the response is a bit slow. Might that have something to do with it?
A similar (but not the same) government image service url works just fine with this code, retrieving many images in parallel without problem. A sample url for one that works is:
https://landfire.cr.usgs.gov/arcgis/rest/services/Landfire/US_200/MapServer/export?bbox=-13814922.744149616%2C6095394.383573096%2C-13805138.804529114%2C6085610.443952593&size=256%2C256&format=png32&bboxSR=102100&imageSR=102100&f=image&layers=show%3A25
(Edit: the landfire servers are down right now, so that url won't work until they're back up)
I tried using longjohn, as suggested in the other question, to get a more verbose printout of the error, but import 'longjohn'
in my code seems to change nothing.
Why would these new urls be throwing this error in node, but not in the browser? I know there are other questions with this subject matter, but they don't seem to helpd me debug my specific issue.
Going further - using the answer
@DipakC wrote a great answer that utilizes axios to fetch images. Using his downloadImage
, I am able to fetch images as part of a Promise.all, like so:
await Promise.all(
tilenames.map((tilename: string) => {
const url = `${baseurl}/${tilename}.png` // baseurl is remote esri url
return downloadImage({ url });
})
);
Ultimately, I need these images to be converted into ImageData so their pixel data can be used. This is easily achieved using some canvas methods. Currently, I use the above code, and then reuse my original code, but this time, instead of using the remote urls for the images, I just use the pathname to the images which I've just downloaded:
// immediately after the Promise.all above:
await Promise.all(
tilenames.map((tilename: string) => {
const url = `${localpath}/${tilename}.png` // localpath is where image was downloaded to
return loadImage(url);
})
)
.then((images: Image[]) => {
const canvas: Canvas = createCanvas(256, 256);
const ctx: RenderingContext = canvas.getContext('2d');
ctx.drawImage(image, 0, 0, 256, 256);
const imageData = ctx.getImageData(0, 0, 256, 256);
// do something with imageData, more processing
});
Question: Is there any way to avoid this 2 stage process? Meaning, instead of using downloadImage
to download the image as a file, can I write its image data directly to a variable, as I'm doing in the second step with loadImage
?
Addendum:
This is the source code for 'loadImage' from the node-canvas src code
function loadImage (src) {
return new Promise((resolve, reject) => {
const image = new Image()
function cleanup () {
image.onload = null
image.onerror = null
}
image.onload = () => { cleanup(); resolve(image) }
image.onerror = (err) => { cleanup(); reject(err) }
image.src = src
})
}