user2862473
user2862473

Reputation:

Synchronous geocoding in NodeJS

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

Answers (2)

user2862473
user2862473

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

Daniel B
Daniel B

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

Related Questions