Automotive
Hands On

Integrate the Smartcar API in Your Next HERE Location Services Web Application

By Nic Raboy | 17 July 2019

In preparation for the HackMobility event coming up in San Francisco, I thought I’d take a moment to familiarize myself with some of the themes and organizations making an appearance at the event. This lead me to Smartcar, a service that lets you connect your applications to your vehicles with no additional hardware.

Smartcar offers APIs that work with quite a large selection of major vehicle brands. These APIs can access make and model information, vehicle identification numbers (VIN), the latitude and longitude location of your vehicle, and other things. These happens through the software and hardware that’s already built into many vehicle models.

With this in mind, I thought it would be interesting to find the location of a vehicle on a map alongside information about that particular vehicle. This is where HERE comes into play.

We’re going to see how to use the Smartcar JavaScript SDK to get vehicle information and integrate it into a web application that uses the HERE JavaScript SDK to give us the visual component.

To get an idea of what we’re going to accomplish, take a look at the following animated image:

smartcar-here-example

I’m not using my own vehicle because I don’t have a supported model, but the Smartcar API let’s you use simulated data like what’s being presented in the above image. After signing into the application and linking your vehicle, it takes you to a map where markers represent the vehicle location and when clicked it will show information about that vehicle.

A frontend and backend are necessary to be successful with this project.

Building the Backend with Node.js and the Smartcar JavaScript SDK

The first step of this project is to create a backend. There is a Smartcar JavaScript SDK that works in Node.js projects and client facing applications. Because the Smartcar API has an OAuth login flow that works with a secret key and spits out a refresh token, it makes the most sense not to implement everything in the client facing browser-based application. You wouldn’t want the refresh token or secret key to end up out of your control.

Assuming you have Node.js installed, execute the following to create a new project:

mkdir backend
cd backend
npm init -y
touch app.js

The mkdir command will create a new directory and the cd command will navigate into it. A new package.json file will be created as well as an app.js file through the touch command. If mkdir, cd, or touch don’t exist on your operating system, go ahead and do this manually.

The backend will have a few dependencies which can be installed through the following commands:

npm install express --save
npm install cors --save
npm install smartcar --save

Since we’re creating our own API, we’ll be using Express Framework and the cross-origin resource sharing (CORS) middleware. We need CORS because our client facing application won’t be on the same port which might cause errors. More information on CORS with Express can be found in a previous tutorial I wrote.

Before we jump into the code, you will need a free Smartcar account. Within your account, you’ll need to obtain your client id and client secret, as well as define a callback URL for the OAuth process. The URL should be a route within your application, for example http://localhost:3000/exchange.

Open the project’s app.js file and include the following boilerplate code:

const Express = require("express");
const Cors = require("cors");
const Smartcar = require("smartcar");

var app = Express();

app.use(Cors());

const SMARTCAR_CLIENT_ID = "";
const SMARTCAR_CLIENT_SECRET = "";
const SMARTCAR_REDIRECT_URL = "http://localhost:3000/exchange";

var smartcar_access_token = "";

const client = new Smartcar.AuthClient({
    clientId: SMARTCAR_CLIENT_ID,
    clientSecret: SMARTCAR_CLIENT_SECRET,
    redirectUri: SMARTCAR_REDIRECT_URL,
    scope: ["read_vehicle_info", "read_location", "read_vin"],
    testMode: true,
});

var server = app.listen("3000", () => {
    console.log(`Listening at ${server.address().address}${server.address().port}...`);
});

The above code configures the packages that we previously downloaded, and this includes the Smartcar package for OAuth. Make sure to drop your client id and client secret into each of the variables.

Since this particular example won’t use a database, the application will save the users access token, after sign-in, into memory and it will be used until you stop the application or until the access token expires. Generally the client would store this, but we’re not going to add that complexity to this example.

Because we’re using OAuth, we need to define the scope of what should be accessible by our application. In this example we want to be able to get the vehicle info which includes make and model, gather the last known location, and get the VIN. We’re not including things like unlocking and locking of vehicle doors.

Before we start adding our own logic endpoints, let’s do create the /exchange and /login endpoints:

app.get("/login", (request, response) => {
    const link = client.getAuthUrl();
    response.redirect(link);
});

app.get("/exchange", (request, response) => {
    const code = request.query.code;
    client.exchangeCode(code).then(access => {
        smartcar_access_token = access.accessToken;
        response.redirect("http://localhost:2015");
    });
});

The /login endpoint is going to be the users first point of access. When triggered, the OAuth flow will start and end at the /exchange endpoint. Rather than returning the tokens to the user, the access token will be set in memory and the client will be redirected to their frontend application which we’re going to assume is running on port 2015.

Let’s think of our first endpoint that contains useful logic. We might want information about the vehicle such as make, model, and VIN. This information doesn’t exist as part of a single SDK function, so we have to do some work. Take the following for example:

app.get("/vehicles", (request, response) => {
    Smartcar.getVehicleIds(smartcar_access_token).then(data => {
        return data.vehicles;
    }).then(ids => {
        return vehicles = ids.map(async id => {
            let vehicle = new Smartcar.Vehicle(id, smartcar_access_token);
            return vehicle.info().then(info => {
                return vehicle.vin().then(vin => {
                    info.vin = vin;
                    return info;
                });
            });
        });
    }).then(vehicles => {
        Promise.all(vehicles).then(result => {
            response.send(result);
        });        
    });
});

In the above /vehicles endpoint we get all of the vehicle id values based on our access token. This is a remote request, hence the use of our promise. When we have the vehicle id information, we can chain our promise to get the vehicle info which is also a remote request. Using further promise chaining, we can get the VIN information from each of the id values and construct our own custom object that contains VIN as well as make and model information.

Finally, since we’ve exchanged id information with promises that contain full information, we need to execute all of our promises through the Promise.all in JavaScript. The information we return will look something like this:

[
    {
        "id": "1234",
        "make": "Ford",
        "model": "Ranger",
        "year": 1998,
        "vin": "1N3T000000"
    }
]

We’re going to consume this information in our frontend application that uses the HERE JavaScript SDK, but not quite yet.

We have information about our vehicles, but not the location. We can create another endpoint for that:

app.get("/vehicle/:id/location", function (request, response) {
    let vehicle = new Smartcar.Vehicle(request.params.id, smartcar_access_token);
    vehicle.location().then(result => {
        response.send(result);
    });
});

Assuming the above endpoint is accessed with an actual vehicle id, we can use it along with the stored access token to get the latitude and longitude information about that vehicle.

This is all the information we need for the backend of our current example. We can now take it to a map with the HERE JavaScript SDK.

Displaying Your Car Location and Information on a Map with the HERE JavaScript SDK

We’re going to create a separate client facing application to accomplish the user interface component. Execute the following commands somewhere on your computer to get us started:

mkdir frontend
cd frontend
touch index.html

We’re creating a new directory with an index.html file in it. Remember, you can do this manually if you don’t have the mkdir, cd, or touch commands on your operating system’s command line.

Open the project’s index.html file and add the following boilerplate code to get us started:

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" type="text/css" href="http://js.api.here.com/v3/3.0/mapsjs-ui.css" />
    </head>
    <body style="margin: 0">
        <div id="map" style="width: 100vw; height: 100vh;"></div>
        <script src="http://js.api.here.com/v3/3.0/mapsjs-core.js" type="text/javascript" charset="utf-8"></script>
        <script src="http://js.api.here.com/v3/3.0/mapsjs-service.js" type="text/javascript" charset="utf-8"></script>
        <script src="http://js.api.here.com/v3/3.0/mapsjs-mapevents.js" type="text/javascript" charset="utf-8"></script>
        <script src="http://js.api.here.com/v3/3.0/mapsjs-ui.js" type="text/javascript" charset="utf-8"></script>
        <script>
            const platform = new H.service.Platform({
                "app_id": "HERE_APP_ID",
                "app_code": "HERE_APP_CODE"
            });
            const defaultLayers = platform.createDefaultLayers();
            const map = new H.Map(
                document.getElementById("map"),
                defaultLayers.normal.map,
                {
                    zoom: 6,
                    center: { lat: 37.7397, lng: -121.4252 }
                }
            );
            const ui = H.ui.UI.createDefault(map, defaultLayers);
            const mapEvent = new H.mapevents.MapEvents(map);
            const behavior = new H.mapevents.Behavior(mapEvent);
        </script>
    </body>
</html>

The above markup will load the HERE JavaScript SDK and display an interactive map on the screen, assuming that you’ve added your free HERE developer credentials found in the HERE Developer Portal.

I’ve written about including interactive maps in a few tutorials, but to get a great idea about what everything’s doing, check out Developer Waypoints on YouTube where I demonstrate step-by-step on what’s happening.

This is where things get interesting. We need to make an HTTP request to our backend endpoints for information. We might do something like the following:

fetch("http://localhost:3000/vehicles")
    .then(response => response.json())
    .then(vehicles => {
        let vehiclePromises = vehicles.map(vehicle => {
            return fetch(`http://localhost:3000/vehicle/${vehicle.id}/location`)
                .then(response => response.json())
                .then(location => {
                    vehicle.location = location.data;
                    return vehicle;
                });
        });
        return Promise.all(vehiclePromises);
    }).then(result => {
        let markerGroup = new H.map.Group();
        result.forEach(vehicle => {
            let marker = new H.map.Marker({ lat: vehicle.location.latitude, lng: vehicle.location.longitude });
            markerGroup.addObject(marker);
        });
        map.addObject(markerGroup);
        map.setViewBounds(markerGroup.getBounds());
    });

Again, we’re going to use promise chaining here.

First we’re going to make a request for all of our vehicles which include vehicle information. When we get that information, we’re going to loop through each vehicle, and reconstruct our information by making a request for the location. In reality, we want something like this in the end:

[
    {
        "id": "1234",
        "make": "Ford",
        "model": "Ranger"
        "year": 1998,
        "vin": "1N2345000"
        "location": {
            "latitude": 37,
            "longitude": -121
        }
    }
]

When we have a promise for all of this information, we can do a Promise.all go get an array of that data. The result of that Promise.all is chained so that way we can render the data when it’s ready.

let markerGroup = new H.map.Group();
result.forEach(vehicle => {
    let marker = new H.map.Marker({ lat: vehicle.location.latitude, lng: vehicle.location.longitude });
    markerGroup.addObject(marker);
});
map.addObject(markerGroup);
map.setViewBounds(markerGroup.getBounds());

We can create a group of markers based on the latitude and longitude of each vehicle, add the group to the map, and zoom in on them so they’re all within view. In this scenario, we’re only drawing markers based on position. We’ve essentially lost the vehicle information.

To put the vehicle information to use, we can create InfoBubble components on each of our markers. Take a look at the following revision to our code:

let markerGroup = new H.map.Group();
result.forEach(vehicle => {
    let marker = new H.map.Marker({ lat: vehicle.location.latitude, lng: vehicle.location.longitude });
    marker.setData(`<p>${vehicle.year} ${vehicle.make} ${vehicle.model}<br /><span style="font-size: 10pt">VIN: ${vehicle.vin}</span></p>`);
    marker.addEventListener('tap', event => {
        var bubble = new H.ui.InfoBubble(event.target.getPosition(), {
            content: event.target.getData()
        });
        ui.addBubble(bubble);
    }, false);
    markerGroup.addObject(marker);
});
map.addObject(markerGroup);
map.setViewBounds(markerGroup.getBounds());

When we create markers for our marker group, we are setting the data on the marker to be shown through an InfoBubble that is accessible through a tap listener. The data and tap listeners are created on a per marker basis so each marker will show different information.

Conclusion

You just saw how to use the Smartcar JavaScript SDK with the HERE JavaScript SDK. This example was a simple one because all we did was get simulated vehicle information and display it on a map. However, you could come up with much more extravagant use-cases, for example ride-sharing, fleet management, or even enhanced user experience for vehicle servicing.

If you’ve got a use-case, I’d love to hear about it in the comments!