16

Publish Location Data from a Raspberry Pi to HERE XYZ and View it on a Map

 4 years ago
source link: https://www.tuicool.com/articles/NfEbErU
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

In aprevious tutorial, you’ll probably remember that I demonstrated how to collect GPS data on a Raspberry Pi Zero W with a NEO 6M GPS module and Node.js. In this example we simply collected the latitude and longitude position data, reverse geocoded it to addresses, and printed it out. While interesting, it wasn’t necessarily useful.

A more useful scenario might involve visualizing the data that was collected and sharing it with others. This can easily be accomplished by including HERE XYZ into the mix. HERE XYZ can act as a data storage layer for our location data and we can query that data using various API endpoints that are included with the service.

In this tutorial we’re going to collect position data on a Raspberry Pi, upload it to HERE XYZ as it comes in, and then later view it on a map.

Take a look at the following image which is a result of my experiment:

2IZVJrz.jpg!web

I removed street names and house numbers for privacy reasons, but it clearly shows a walk I did around my neighborhood. I had my Raspberry Pi Zero W hooked up to a portable battery with the GPS module attached. Since I don’t have an LTE adapter for my Raspberry Pi, I tethered it to my mobile phone for internet. As I walked around my neighborhood, the data was sent to HERE XYZ without any further backend or database required.

If you’re a long time follower of my content, you’ll probably remember a tutorial I wrote titled, Tracking a Raspberry Pi with WLAN and Golang, then Displaying the Results with HERE XYZ . This was a near identical experiment, but in the previous I used WLAN instead of GPS to track the position. I also didn’t make use of the HERE XYZ APIs, but instead manually uploaded my data.

nAb6Jr6.jpg!web

The above picture, from the previous tutorial also shows a less defined route. Only so much can be concluded from WLAN data versus GPS data.

This tutorial which you’re currently reading should be a step up, unless of course you’re interested in WLAN AP tracking instead of GPS.

In case you’re curious, my Raspberry Pi setup looks like the following:

YfYRjqM.jpg!web

I’m just using a simple USB battery pack, a Raspberry Pi Zero W and a NEO 6M GPS module.

Interacting with HERE XYZ from a Raspberry Pi Zero W

If you haven’t already, I strongly recommend you check out my tutorial titled, Read GPS Data with a Raspberry Pi Zero W and Node.js . It has the hardware and software requirements as well as more thorough details on the collecting of GPS data.

We’re going to modify the code from the previous tutorial to include HERE XYZ and exclude the HERE Reverse Geocoding API.

Create a new directory on your computer and execute the following commands:

npm init -y
npm install axios geojson serialport gps moment --save
touch app.js

If you don’t have the touch command, create the app.js file manually.

We’ll be using axios to make HTTP requests to the HERE XYZ API, geojson to parse our data into something HERE XYZ understands, serialport for interacting with the GPS module, gps for parsing our GPS data, and moment for making timestamp data easy to work with in JavaScript.

Inside the app.js file, include the following:

const SerialPort = require("serialport");
const SerialPortParser = require("@serialport/parser-readline");
const GPS = require("gps");
const Axios = require("axios");
const GeoJSON = require("geojson");
const OS = require("os");
const Moment = require("moment");

var SERIAL_PORT = "/dev/cu.SLAB_USBtoUART";

if(OS.platform() == "linux") {
    SERIAL_PORT = "/dev/ttyS0";
}

const port = new SerialPort(SERIAL_PORT, { baudRate: 9600 });
const gps = new GPS();

const parser = port.pipe(new SerialPortParser());

gps.on("data", data => {
    if(data.type == "GGA" || data.type == "RMC") {
        if(data.quality != null || data.status == "active") {
            console.log(data.lat + "," + data.lon);
        } else {
            console.log("no gps fix available");
        }
    }
});

parser.on("data", data => {
    try {
        gps.update(data);
    } catch (e) {
        throw e;
    }
});

The above code is more or less what we saw in the previous tutorial, with the exception of a few upgrades to make it more resistant to failure, not more features. If you want the details on what everything in the above tutorial does, please view the previous tutorial since it isn’t the focus for this one.

Now that we have GPS data coming in, we need to have it flow into HERE XYZ. However, before we can do that, we need to make sure we have the appropriate pieces configured.

My suggestion would be to use the XYZ CLI going forward. As a developer you might find it easier than clicking around in a portal. You will need a free account in order to proceed.

To install the XYZ CLI, execute the following:

npm install -g @here/cli

After you install the XYZ CLI, you’ll need to configure it to use your account. This should be done on your computer, not the Raspberry Pi. We’re only doing XYZ configurations, not using the CLI for interactions.

Execute the following:

here configure account

Use the username and password that you used when creating a HERE Developer Portal account. You’ll only need to do this one time. After configuring the CLI to use your account, you’ll want to create a new space to store your data. This can be done with the following:

here xyz create --title "raspberry-pi-project" --message "data from the raspberry pi"

Take note of the space id that is returned after executing the above command. We’ll need to use it in our Node.js code. In addition to a space id, you’ll also want to create a token with the correct permissions.

You can do that with the CLI, or by visiting the token dashboard foundhere.

67ZRvam.jpg!web

After the token is generated, make note of it because it, along with the space id, will be used in the Node.js application.

Before we get into the code, you should be aware of how to see your data outside of the CLI. This can be done in the Data Hub of XYZ Studio.

FryqAfm.jpg!web

Head over toHERE XYZ Studio, and choose the Data Hub tab. You can view your spaces and data in the Data Hub if you prefer over the CLI. Right now you’ll have no data for the space that we plan to use.

With that out of the way, let’s go back into our app.js file. We need to add the following near the top:

const XYZ_TOKEN = "TOKEN_HERE";
const XYZ_SPACE = "SPACE_ID_HERE";

The REST API will require both the token and space id. For code cleanliness, it is a good idea to just create a constant for this information. Now let’s make use of axios and the geojson libraries to make our request:

function uploadToXYZ(position) {
    const now = (new Date()).getTime();
    const date = Moment(now).format("YYYYMMDD");
    const data = [
        {
            lat: position.lat,
            lng: position.lng,
            timestamp: now.toString(),
            date: date,
            "@ns:com:here:xyz": {
                tags: [now.toString(), date]
            }
        }
    ];
    const dataGeoJSON = GeoJSON.parse(data, { Point: ["lat", "lng"] });
    return Axios({
        method: "PUT",
        url: "https://xyz.api.here.com/hub/spaces/" + XYZ_SPACE + "/features",
        headers: {
            "Authorization": "Bearer " + XYZ_TOKEN,
            "Content-Type": "application/geo+json"
        },
        data: JSON.stringify(dataGeoJSON)
    });
}

The first step is to create GeoJSON data to how HERE XYZ expects. This means defining the latitude and longitude as well as any properties or tag information. The properties, which are timestamp and date in this example, will be visible in the Data Hub . The tags will be searchable when making requests to read data. This would be useful for something like displaying all data for a given date, or similar.

Once we create our GeoJSON data, we can make an HTTP request to the API. We can make use of the uploadToXYZ function within the GPS parser like so:

gps.on("data", data => {
    if(data.type == "GGA" || data.type == "RMC") {
        if(data.quality != null || data.status == "active") {
            console.log(data.lat + "," + data.lon);
            uploadToXYZ({ lat: data.lat, lng: data.lon }).then(result => { }, error => console.log(error));
        } else {
            console.log("no gps fix available");
        }
    }
});

A potential problem with this is with the volume of requests. GPS data will be picked up quite frequently. To avoid burning through your API limits, it might be a good idea to throttle your requests. This can easily be accomplished by keeping track of the elapsed time between requests.

Near the top of your app.js file, include the following:

const THROTTLE_TIME = 2000;
var trackedTime = (new Date()).getTime();

In my example, I want to only make requests every 2000 milliseconds. Feel free to adjust that number or remove it completely. With those variables in place, we can update the GPS parser to the following:

gps.on("data", data => {
    if(data.type == "GGA" || data.type == "RMC") {
        if(data.quality != null || data.status == "active") {
            const now = (new Date()).getTime();
            if(now - trackedTime >= THROTTLE_TIME) {
                console.log(data.lat + "," + data.lon);
                uploadToXYZ({ lat: data.lat, lng: data.lon }).then(result => { }, error => console.log(error));
                trackedTime = now;
            }
        } else {
            console.log("no gps fix available");
        }
    }
});

At this point, if we were to run the application on the Raspberry Pi, it should push our GPS data to HERE XYZ. There are a few things to note:

  • The assumption is that the Raspberry Pi was configured for GPS collection via the previous tutorial.
  • The GPS module has a fix on the signal and is getting position data.
  • The HERE XYZ space id and token information is correct.
  • The Raspberry Pi has an internet connection.

Just to reiterate, definitely check out myprevious tutorial for getting everything configured on the Raspberry Pi.

Displaying HERE XYZ Data on a Map with Leaflet.js

Now that we have data in XYZ, we probably want to visualize it so it makes sense or others can make sense of it. To do this, we can use just about anything when it comes to a renderer. We can use the HERE JavaScript SDK with interactive maps, Leaflet.js, Tangram, etc. At the end of the day, we just want to show a map with polylines and markers.

For this example, I’m choosing to use Leaflet.js to render our data.

We need to create a new project. This time it will represent a web application, completely independent from the Raspberry Pi project that we had created. You’ll want to create a new index.html file with the following code:

<html>
    <head>
        <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
    </head>
    <body style="margin: 0">
        <div id="map" style="width: 100vw; height: 100vh"></div>
        <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/geojson/0.5.0/geojson.min.js"></script>
        <script src="heredev.js"></script>
        <script>
            const start = async () => {
                const tiles = "https://1.base.maps.api.here.com/maptile/2.1/maptile/newest/normal.day/{z}/{x}/{y}/512/png8?app_id={appId}&app_code={appCode}";
                const map = new L.Map("map", {
                    center: [37, -121],
                    zoom: 11,
                    layers: [L.tileLayer(tiles, { appId: "HERE_APP_ID", appCode: "HERE_APP_CODE" })]
                });
            }
            start();
        </script>
    </body>
</html>

The above code, assuming you’ve added your app id and app code, will display a map with Leaflet.js and use the HERE Map Tile API.

What we need to do is request our data from the HERE XYZ API, similar to how we added it on the Raspberry Pi. To do this, we can use axios to do the following:

const response = await axios({
    method: "GET",
    url: "https://xyz.api.here.com/hub/spaces/" + "HERE_SPACE_ID" + "/search",
    params: {
        access_token: "HERE_TOKEN"
    }
});

You’ll want to use the same space id and token that you used on the Raspberry Pi. In production, you may want to create another token that has less permissions, for example a read-only token.

To bring it all together, we can upgrade our client facing project to the following:

<html>
    <head>
        <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
    </head>
    <body style="margin: 0">
        <div id="map" style="width: 100vw; height: 100vh"></div>
        <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/geojson/0.5.0/geojson.min.js"></script>
        <script src="heredev.js"></script>
        <script>
            const start = async () => {
                const tiles = "https://1.base.maps.api.here.com/maptile/2.1/maptile/newest/normal.day/{z}/{x}/{y}/512/png8?app_id={appId}&app_code={appCode}";
                const map = new L.Map("map", {
                    center: [37, -121],
                    zoom: 11,
                    layers: [L.tileLayer(tiles, { appId: "HERE_APP_ID", appCode: "HERE_APP_CODE" })]
                });
                const response = await axios({
                    method: "GET",
                    url: "https://xyz.api.here.com/hub/spaces/" + "HERE_SPACE_ID" + "/search",
                    params: {
                        access_token: "HERE_TOKEN"
                    }
                });
                const polylineData = [];
                response.data.features.forEach(feature => {
                    let position = feature.geometry.coordinates;
                    polylineData.push({ lat: position[1], lng: position[0] });
                });
                const polyline = new L.Polyline(polylineData, { weight: 5 });
                polyline.addTo(map);
                const sourceMarker = new L.Marker(polylineData[0]);
                const destinationMarker = new L.Marker(polylineData[polylineData.length - 1]);
                sourceMarker.addTo(map);
                destinationMarker.addTo(map);
                const bounds = new L.LatLngBounds(polylineData);
                map.fitBounds(bounds);
            }
            start();
        </script>
    </body>
</html>

In the above code, we make our request for the data in HERE XYZ, obtain all of the geometry information to construct a polyline, and add the polyline to the map. At the start of our data and the end we drop a marker.

Conclusion

You just saw how to use the HERE XYZ APIs to send GPS position data from a Raspberry Pi and display it on a map without having to manually process the data. This was all accomplished using simple JavaScript and Node.js.

To be fair, this tutorial was part of a series. We didn’t really talk about how to interact with a GPS module on a Raspberry Pi with Node.js because the focus for this was XYZ. If you want to properly configure your Raspberry Pi and interact with the GPS module, view myprevious tutorial.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK