Calling C++ from Node.js (Part 2: Where’s London?)
Last time we implemented a basic addTwo
function in C++ to call from Node. This time we’ll go one step further. Let’s make a function distanceToLondon
to take the coordinates anywhere on Earth and find out how far away we are from tea and crumpets.
Complete code for this exercise is here
Setup
Starting from part 1, your directory structure should look like this.
cbrown@Chriss-MacBook-Air performant-node % tree
.
├── binding.gyp
├── index.js
├── package-lock.json
├── package.json
└── src
└── main.cpp
If you’ve built the project, you’ll also have a node_modules
and a build
directory.
Now make sure you have the Boost library installed if you don’t have it already. On Mac with brew you can just use:
brew install boost
If you encounter compatibility issues make sure you get Boost version 1.85, to match what we use here.
Code
First of all, let’s rename main.cpp
to app.cpp
to make it more clear what we’re building (and also update binding.gyp to use the new name).
mv src/main.cpp src/app.cpp && sed -i '' 's/main.cpp/app.cpp/g' binding.gyp
Now, time to update our repo with the changes we need.
building.gyp
- Force valid exception handling using the
-fexceptions
flag (the above config forces on Linux, Mac, and Windows) - Add Boost version 1.85.0 to the include directories. The libraries we use are
.hpp
files so we just need to include the header and don’t need to worry about linking
app.cpp
- Update our module to export
distanceToLondon
- Use the input validation from the previous part to ensure we get two numbers (longitude, latitude)
- Create a point on the Earth for the input coordinates, and also one for London
- Call
boost::geometry::distance
to calculate the distance in meters, then divide by 1000 to get kilometers Our new function takes in two numbers and returns a number, same as before. So other than including in the new library, not much has changed 🙂
index.js
- Update
index.js
to call our new function. Here we use New York City but use anything you like.
Code
building.gyp
{
"targets": [
{
"target_name": "addon",
"cflags": [
"-fexceptions"
],
"cflags_cc": [
"-fexceptions"
],
"sources": [
"./src/app.cpp"
],
"include_dirs": [
"<!(node -p \"require('node-addon-api').include_dir\")",
"/opt/homebrew/Cellar/boost/1.85.0/include"
],
"dependencies": [
"<!(node -p \"require('node-addon-api').gyp\")"
],
"xcode_settings": {
"OTHER_CFLAGS": [
"-fexceptions"
],
"OTHER_CPLUSPLUSFLAGS": [
"-fexceptions"
]
},
"msvs_settings": {
"VCCLCompilerTool": {
"ExceptionHandling": 1
}
}
}
]
}
src/app.cpp
#include <napi.h>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point.hpp>
Napi::Number DistanceToLondon(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
double lon = info[0].As<Napi::Number>().DoubleValue();
double lat = info[1].As<Napi::Number>().DoubleValue();
boost::geometry::model::point<double, 2, boost::geometry::cs::geographic<boost::geometry::degree>> point1(lon, lat);
boost::geometry::model::point<double, 2, boost::geometry::cs::geographic<boost::geometry::degree>> london(-0.1276, 51.5074);
double distance = boost::geometry::distance(point1, london) / 1000; // distance in km
return Napi::Number::New(env, distance);
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "distanceToLondon"), Napi::Function::New(env, DistanceToLondon));
return exports;
}
NODE_API_MODULE(addon, Init)
index.js
const addon = require("./build/Release/addon");
const newYorkLongitude = -74.006;
const newYorkLatitude = 40.7128;
const nycToLondonKm = addon.distanceToLondon(newYorkLongitude, newYorkLatitude);
console.log(`Distance from NYC to London: ${nycToLondonKm.toFixed(2)} km`);
Demonstration
Now at this point, you can run your new function using
npx node-gyp && npx node-gyp configure build && node index.js
Output:
cbrown@Chriss-MacBook-Air performant-node2 % npx node-gyp && npx node-gyp configure build && node index.js
...
Distance from NYC to London: 5585.34 km
Exactly the answer we are hoping for.
Conclusion
We exposed a C++ function to Node that calculates the distance between two points on the globe. Boost provides an efficient implementation of Vincenty’s formula for determining the distance between two coordinates on Earth. In a real application, you would want to benchmark how much faster the C++ function is than anything you could find or write in Node.
Extension Ideas
- Update
index.js
to take in the name of a city, call an API to determine its coordinates, perform the distance calculation then output a nice formatted result - Compare accuracy and runtime for both Vincenty and Haverson algorithms
- Compare speed when delegating distance calculation to the Boost library vs a relevant npm library
Up next
Server-side programming is the beating heart of the average web app, but we can’t neglect what the client is doing. Coming up next, we’ll learn how to use web assembly to integrate C++ with JS on the client side.