27

Calculate and Visualize Navigation Routes with HERE and Vue.js

 5 years ago
source link: https://www.tuicool.com/articles/hit/uqARJjA
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.

When you think of maps, you might think of turn-by-turn navigation, like those features offered in your car, on your phone, or even within your smart watch, to get you from point A to point B. Creating an algorithm for routing between two points is difficult, but it is substantially more difficult when you try to calculate the fastest route given different circumstances, like being on foot versus being in a car.

The good news is that route calculation isn’t difficult when you’re using the HERE Routing API for JavaScript . In fact, to accomplish the task, you only really need to provide a set of points, whether that be the start and finish point, or other waypoints in-between.

We’re going to see how to use the Vue.js JavaScript framework to build a web application that allows a user to calculate a route between points and display that route, along with turn-by-turn directions on the map.

Take a look at the following animated image to get an idea on what we hope to accomplish:

AryIjar.gif

The user will be able to provide two addresses. Behind the scenes, those addresses will be converted into coordinates and a route is established. Using the route information, lines are created and rendered at the paths and the turn-by-turn directions associated are displayed. Excluding all the boilerplate code for Vue.js, all of this will happen in just a few lines of code.

Getting Started with Vue.js and HERE Location Services

This isn’t the first time that I’ve discussed using the HERE Location Services (HLS) with Vue.js. I’ve written Showing a HERE Map with the Vue.js JavaScript Framework for displaying maps, and I’ve written Searching for Points of Interest with the HERE Places API in a Vue.js Application for searching maps. Much of this tutorial will take from the previous tutorials, so to save us some time and explanation, the deeper details from the previous won’t be revisited. I encourage you to view the previous two tutorials if you want the deeper details on displaying a map or placing markers. Our focus is around routing for this tutorial.

Assuming you have the Vue CLI installed and a free HERE developer account , execute the following to create a new project:

vue create here-routing-project

The above command will start the CLI wizard and create a new project. In reality, this is a simple project, so go ahead and use the defaults when prompted by the CLI.

With the project created, we need to import the HERE JavaScript libraries. Open the project’s public/index.html file and include the following:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        <title>HERE Maps with Vue</title>
    </head>
    <body>
        <noscript>
            <strong>We're sorry but vue-map doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
        </noscript>
        <div id="app"></div>
        <!-- built files will be auto injected -->
        <script src="https://js.api.here.com/v3/3.0/mapsjs-core.js" type="text/javascript" charset="utf-8"></script>
        <script src="https://js.api.here.com/v3/3.0/mapsjs-service.js" type="text/javascript" charset="utf-8"></script>
    </body>
</html>

The mapsjs-core and mapsjs-service libraries were included in the above HTML markup. In an effort to continue getting up to speed from the previous tutorials, we can work on our maps component.

Create a src/components/HereMap.vue file within your project that contains the following:

<template>
    <div class="here-map">
        <div ref="map" v-bind:style="{ width: width + '%', height: height }" style="float: left"></div>
        <ol v-bind:style="{ width: (100 - width - 5) + '%'}" style="float: right; min-height: 530px; margin-left: 20px; margin-top: 0">
            <li></li>
        </ol>
    </div>
</template>

<script>
    export default {
        name: "HereMap",
        data() {
            return {
                map: {},
                platform: {},
                geocoder: {}
            }
        },
        props: {
            appId: String,
            appCode: String,
            lat: String,
            lng: String,
            width: String,
            height: String
        },
        created() {
            this.platform = new H.service.Platform({
                "app_id": this.appId,
                "app_code": this.appCode
            });
            this.geocoder = this.platform.getGeocodingService();
        },
        mounted() {
            this.map = new H.Map(
                this.$refs.map,
                this.platform.createDefaultLayers().normal.map,
                {
                    zoom: 10,
                    center: { lng: this.lng, lat: this.lat }
                }
            );
        },
        methods: { }
    }
</script>

<style scoped></style>

The above code is more or less where we left off at with the previous tutorials for the component, with the exception of the <template> block. In the <template> block, we have the following:

<template>
    <div class="here-map">
        <div ref="map" v-bind:style="{ width: width + '%', height: height }" style="float: left"></div>
        <ol v-bind:style="{ width: (100 - width - 5) + '%'}" style="float: right; min-height: 530px; margin-left: 20px; margin-top: 0">
            <li></li>
        </ol>
    </div>
</template>

In the above we have a container for our map as well as an ordered list for our turn-by-turn instructions. Both HTML components have a dynamic style which isn’t too relevant to the final outcome of the tutorial. Basically, if a user assigns a certain width or height to the component as a whole, the two UI components will scale properly. You could easily just set a hard coded value for this tutorial instead.

Inside the <script> block we are defining our variables as well as component properties, initializing HLS, and displaying a map when the view has rendered. When it comes to routing, most of our magic will happen in the methods object.

Geocoding Addresses into Latitude and Longitude Coordinates

Now that we’re up to speed with our map component, we can start adding the logic that will be used towards routing. When trying to route between two or more waypoints, the HERE Routing API expects latitude and longitude coordinates. Chances are no human being is going to remember coordinates when they want to route between two points, so we’re going to have to geocode some addresses. For code optimization, we should probably create a function for this.

Within your project’s src/components/HereMap.vue file, include the following in the methods object:

methods: {
    geocode(query) {
        return new Promise((resolve, reject) => {
            this.geocoder.geocode({ searchText: query }, data => {
                if(data.Response.View[0].Result.length > 0) {
                    data = data.Response.View[0].Result.map(location => {
                        return {
                            lat: location.Location.DisplayPosition.Latitude,
                            lng: location.Location.DisplayPosition.Longitude
                        };
                    });
                    resolve(data);
                } else {
                    reject({ "message": "No data found" });
                }
            }, error => {
                reject(error);
            });
        });
    },
}

Remember, we had already initialized the geocoder in the previous step. Now we’re creating a geocode method that takes a query string that will be an address. We’re going to plan ahead and use promises rather than callbacks which will help us drastically. Using the geocoder we can pass our query string and return only a latitude and longitude if results exist. If no results exist, we’ll just reject the promise with an error.

Now we can take this information to create waypoints for our router.

Routing Between Two Points on a Map with Turn-by-Turn Directions

The HERE Routing API can calculate routes between two or more waypoints. For this example we have a start address and a destination address, meaning we have two waypoints. Often there are use-cases where you might need to add a “pitstop”, but we won’t worry about that here. However, adding an extra waypoint wouldn’t take more than a few lines of markup to work.

Before we start using the HERE Routing API, we need to initialize it in our application and add a few variables for holding data. Within your project’s src/components/HereMap.vue file, include the following in the data method:

data() {
    return {
        map: {},
        platform: {},
        router: {},
        geocoder: {},
        directions: []
    }
},

The router variable will hold our initialized router and the directions variable will hold our turn by turn directions. With those variables in place, we can initialize the router in the created method:

created() {
    this.platform = new H.service.Platform({
        "app_id": this.appId,
        "app_code": this.appCode
    });
    this.router = this.platform.getRoutingService();
    this.geocoder = this.platform.getGeocodingService();
},

With the router initialized, we can work on our method that will perform all the heavy lifting. Even though in reality, theHERE Routing API will be doing the heavy lifting remotely. We’re just parsing the results.

Within the methods object, add the following method:

route(start, finish) {
    var params = {
        "mode": "fastest;car",
        "representation": "display"
    }
    var waypoints = [];
    this.map.removeObjects(this.map.getObjects());
    this.directions = [];
    waypoints = [this.geocode(start), this.geocode(finish)];
    Promise.all(waypoints).then(result => {
        var markers = [];
        for(var i = 0; i < result.length; i++) {
            params["waypoint" + i] = result[i][0].lat + "," + result[i][0].lng;
            markers.push(new H.map.Marker(result[i][0]));
        }
        this.router.calculateRoute(params, data => {
            if(data.response) {
                for(var i = 0; i < data.response.route[0].leg.length; i++) {
                    this.directions = this.directions.concat(data.response.route[0].leg[i].maneuver);
                }
                data = data.response.route[0];
                var lineString = new H.geo.LineString();
                data.shape.forEach(point => {
                    var parts = point.split(",");
                    lineString.pushLatLngAlt(parts[0], parts[1]);
                });
                var routeLine = new H.map.Polyline(lineString, {
                    style: { strokeColor: "blue", lineWidth: 5 }
                });
                this.map.addObjects([routeLine, ...markers]);
                this.map.setViewBounds(routeLine.getBounds());
            }
        }, error => {
            console.error(error);
        });
    });
}

There is a lot going on in the above method so we need to break it down to figure it out. First we are defining that we want to find the fastest route by car, rather than by foot or something else. Next we are removing all previous map objects such as lines and markers from the map to prevent any kind of overlap if multiple searches are performed. We’re also emptying the directions array to prevent overlap as well.

Remember, the HERE Routing API expects coordinates for waypoints, not addresses. The HERE Geocoder API is asynchronous and we wrapped it in a promise. We need to make sure all our waypoints have been calculated before we attempt to route between them. This is why we are creating a waypoints array of promises. By using the Promise.all method, we can execute all the promises in our array and only when they have finished, we proceed to calculating the routes.

When the coordinate data is available, we alter the params variable to include waypoints as well as markers for each of the source or destinations. With that information in hand, we can call the calculateRoute method of the router. The turn-by-turn instructions from the response are stored in the directions variable and lines are calculated from each of the parts of the route. Finally, all the map objects, whether it be lines, markers, etc., are added to the map and the map is re-centered on the visual route.

I think my explanation might sound more complicated than it actually is. Remember, we’re essentially just doing a lot of parsing of a response.

Before we can say that the component is complete, we need to update our <template> block. Our turn-by-turn instructions list is empty as of now. It should look more like the following:

<template>
    <div class="here-map">
        <div ref="map" v-bind:style="{ width: width + '%', height: height }" style="float: left"></div>
        <ol v-bind:style="{ width: (100 - width - 5) + '%'}" style="float: right; min-height: 530px; margin-left: 20px; margin-top: 0">
            <li v-for="direction in directions">
                <p v-html="direction.instruction"></p>
            </li>
        </ol>
    </div>
</template>

The instructions from the API are HTML so we need to bind the data as HTML to the list items. Otherwise we’re going to see a bunch of tags in our output.

Using the Map Component for Geocoding and Routing

With the map component ready to go for routing, it is time to bring everything together and try it out. We need to have a form for user data and we need to call the methods from our component.

Open the project’s src/App.vue file and include the following:

<template>
    <div id="app">
        <div style="padding: 10px 0">
            <div>
                <label style="display: inline-block; width: 60px; color: #FFF">Start</label>
                <input type="text" v-model="start" />
            </div>
            <div>
                <label style="display: inline-block; width: 60px; color: #FFF">Finish</label>
                <input type="text" v-model="finish" />
            </div>
            <button type="button" v-on:click="route()">Route</button>
        </div>
        <HereMap ref="map" appId="APP-ID-HERE" appCode="APP-CODE-HERE" lat="37.7397" lng="-121.4252" width="60" height="530px" />
    </div>
</template>

<script>
    import HereMap from "./components/HereMap.vue"

    export default {
        name: 'app',
        components: {
            HereMap
        },
        data() {
            return {
                start: "",
                finish: ""
            }
        },
        methods: {
            route() {
                this.$refs.map.route(this.start, this.finish);
            }
        }
    }
</script>

<style>
    body {
        margin: 20px;
        background-color: #1d232d;
    }
</style>

Let’s start by looking at the <script> block. We are defining two variables that will be bound to our form elements. We are also defining a route method which will call the route method of our map based on the reference we make in the HTML. It is not a requirement that the parent and child methods be named the same, it was just coincidence.

Looking at the HTML, we have our two form elements bound to our component variables. We also have a button that is bound to our method when clicked. The map component which includes a map as well as directions is added with a reference variable that we use in our method. If you were to try this in your own application, don’t forget to swap the app id and app code with your own.

Conclusion

You just saw how to calculate complex routes and display them on the map as polylines as well as turn-by-turn text instructions using the HERE Routing API for JavaScript along with Vue.js . It may seem like we did a lot in this particular tutorial, but much of it was around getting up to speed with previous tutorials or adding boilerplate Vue.js code.

In case you’d prefer to use Angular, I wrote a similar tutorial previously titled, Transportation Routing and Directions in an Angular Application with the HERE Routing API on the subject.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK