Reputation:
Is it possible to wait for an async call (geocode.reverseGeocode)? I want to loop through JPG-files in a directory, obtain the latitude & longitude from their EXIF-data and reverse geocode to get the country / city from where those pictures are taken. Finally it needs to add an OSX tag to the file. But I need to run the NodeJS-script multiple times to get all tags. (Because the file iterator doesn't wait for the geocode process). What is the best solution to wait for this request?
You can find my code here: https://github.com/Acoustics/jbTagify/blob/development/index.js
Thanks in advance
Upvotes: 0
Views: 1364
Reputation:
This is my code. As you can see, I need a collection of promises at root level. My objective is to obtain a collection of promises which contain the filename with its geocoded data.
var
_ = require('lodash'),
path = require('path'),
pkg = require(path.join(__dirname, 'package.json')),
fs = require('fs'),
promise = require('bluebird'),
exif = require('exif').ExifImage,
geocoder = promise.promisifyAll(require('geocoder')),
exec = require('child_process').exec,
config = require('nconf').file({
file: path.join(__dirname, 'config.json')
});
var geocodePromises = []; // I want to collect all promises here
fs.readdir(path.join(__dirname, 'files'), function (err, files) {
files.filter(function (file) {
var ext = file.split('.').pop(),
cond;
cond = file; // File needs to exist
cond = cond && file[0] != '.'; // Exclude hidden files
cond = cond && config.get("files:support").indexOf(ext) > -1; // File needs to be supported
return cond;
}).forEach(function (file) {
file = path.join(__dirname, 'files/') + file;
new exif({
image: file
}, function (err, data) {
if (!err) {
if (data.gps) {
var location = parseGPS(data.gps); // Obtain lat & lng
geocodePromises.push(geocoder.reverseGeocodeAsync(location.lat, location.lng));
} else {
console.log("Error: GPS data not available");
}
} else {
console.log("Error: " + err);
}
});
});
Promise.all(geocodePromises).then(function (result) {
console.log(result); // Returns an empty array []
// Each result should contain the filename and geocoded location
/*
Example:
{
"file": "1.jpg",
"location": {
<google reverseGeocode object>
}
}
*/
});
//exec('killall Finder');
});
function parseGPS(gps) {
var lat = gps.GPSLatitude,
lng = gps.GPSLongitude,
latRef = gps.GPSLatitudeRef || "N",
lngRef = gps.GPSLongitudeRef || "W";
// Convert coordinates to WGS84 decimal
lat = (lat[0] + lat[1] / 60 + lat[2] / 3600) * (latRef == "N" ? 1 : -1);
lng = (lng[0] + lng[1] / 60 + lng[2] / 3600) * (lngRef == "W" ? -1 : 1);
// Return lat, lng
return {
lat,
lng
};
};
Upvotes: 0
Reputation: 8879
An easy way to handle the multiple asynchronous calls would be to use promises, and there is no need to run them synchronously and wait for one to finish. Either you can use the native promises available in Node, or you can use a promise library such as bluebird that can promisify other libraries that perform asynchronous operations.
The most simple use case would look like
var Promise = require("bluebird");
var geocoder = Promise.promisifyAll(require('geocoder'))
geocoder.geocodeAsync("Atlanta, GA")
.then(function(data){
var lat = data.results[0].geometry.location.lat;
var lng = data.results[0].geometry.location.lng;
console.log("Coordinates for Atlanta, GA: " + lat + "," + lng);
});
where you use the promisified function geocodeAsync
(original function name + Async
) that returns a promise with the returned data as the resolved value.
In your case where you want to perform multiple asyncronous code, you could easily create an array of promises and then let them run in parallell and handle the result when all of the promises are resolved.
var citiesToGeocode = ["Miami, FL", "New York", "Orlando, FL", "Rio de Janeiro, Brazil"];
var geocodePromises = [];
for (var i = 0; i < citiesToGeocode.length-1; ++i) {
geocodePromises.push(geocoder.geocodeAsync(citiesToGeocode[i]));
}
Promise.all(geocodePromises).then(function(result) {
result.forEach(function(geocodeResponse){
console.log("Coordinates for " + geocodeResponse.results[0].formatted_address +": " + geocodeResponse.results[0].geometry.location.lat + "," + geocodeResponse.results[0].geometry.location.lng);
});
});
Using the same approach you can use reverseGeocodeAsync
to lookup information from coordinates.
Upvotes: 2