Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/3.routes/8.tolls-and-ferries/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ This example will explain how to add ferries and toll roads to the journey overv

## Next steps

The last couple of examples explained most of the variables when it comes to routing. In the next step, the vector tile service is introduced.
In this example we saw how to add tolls and ferries onto our journey overview. In the next example we will try requesting certain amenities on charging stops.
4 changes: 4 additions & 0 deletions examples/3.routes/8.tolls-and-ferries/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import { drawRoutePolyline } from './map';
* - queries.js - The GraphQL queries used in the networking requests
*/

// Until the route loads, don't load the sidecard
const sideCard = document.getElementById('side-card');
sideCard.style.display = 'block';

getRoute(route => {
drawRoutePolyline(route);
renderRouteData(route);
Expand Down
21 changes: 21 additions & 0 deletions examples/3.routes/9.amenities-on-route/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add amenities to your route

I need coffee! The kids need a bathroom break! Feed me or I'll end it all! Common complaints heard whilst driving. With amenity-based-routing, your caffeine/bathroom/food/other needs can be catered too, all whilst you charge your vehicle.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I loved this but I would make this a bit less extreme and more focus on a lunch stop.

## Requirements

- [Chargetrip API key](https://account.chargetrip.com) - to plot routes outside this region
- [Mapbox API key](https://www.mapbox.com) - to display the map
- [URQL](https://formidable.com/open-source/urql/) - a lightweight graphQL client

## Steps to take

1. Plotting a route starts by executing the `newRoute` mutation. This mutation requires information about the car, origin and destination. After the mutation is finished executing a route `id` will be returned. Here the amenity preferences for the route should be added.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets change car to vehicle

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the newRoute mutation you can also add additional requirements such as a scheduled amenity stop.

2. This `id` can be used to request route updates through the `routeUpdatedById` subscription. This subscription receives dynamic updates.
3. After the subscription returns done as status, data can be rendered onto the screen. The `polyline` and the `legs` object will be used to display charge stations on the map. Total distance, duration of a trip, consumption are displayed on the side.
4. The amenities are also displayed underneath the relevant station for the end user to see.
5. Using the `route.leg.type` property, it's possible to check if a leg ends at an amenity.

## Next steps

This example shows how routing can be supplemented by adding amenities during certain charging stops. Next, let's move on from routing and dive in to the tile service.
88 changes: 88 additions & 0 deletions examples/3.routes/9.amenities-on-route/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { createClient, createRequest, defaultExchanges, subscriptionExchange } from '@urql/core';
import { pipe, subscribe } from 'wonka';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { createRouteQuery, routeUpdateSubscription, getAmenityListQuery } from './queries.js';

/**
* Example application of how to build a route with the Chargetrip API.
* Please have a look to Readme file in this repo for more details.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how to build a route with a scheduled amen city stop

*
* For the purpose of this example we use urgl - lightweights GraphQL client.
* To establish a connection with Chargetrip GraphQL API you need to have an API key.
* The key in this example is a public one and gives access only to a part of our extensive database.
* You need a registered `x-client-id` to access the full database.
* Read more about an authorisation in our documentation (https://docs.chargetrip.com/#authorisation).
*/
const headers = {
//Replace this x-client-id and app-id with your own to get access to more cars
'x-client-id': '5ed1175bad06853b3aa1e492',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vehicles and the complete station database

'x-app-id': '623998b2c35130073829b2d2',
};

const subscriptionClient = new SubscriptionClient('wss://api.chargetrip.io/graphql', {
reconnect: true,
connectionParams: headers,
});

const client = createClient({
url: 'https://api.chargetrip.io/graphql',
fetchOptions: {
method: 'POST',
headers,
},
exchanges: [
...defaultExchanges,
subscriptionExchange({
forwardSubscription(operation) {
return subscriptionClient.request(operation);
},
}),
],
});

/**
* To create a route you need:
*
* 1. Create a new route and receive back its ID;
* 2. Subscribe to route updates in order to receive its details.
*/
export const getRoute = callback => {
client
.mutation(createRouteQuery())
.toPromise()
.then(response => {
const routeId = response.data.newRoute;
if (!routeId) return Promise.reject('Could not retrieve Route ID. The response is not valid.');

const { unsubscribe } = pipe(
client.executeSubscription(createRequest(routeUpdateSubscription, { id: routeId })),
subscribe(result => {
const { status, route } = result.data.routeUpdatedById;

// you can keep listening to the route changes to update route information
// for this example we want to only draw the initial route
if (status === 'done' && route) {
unsubscribe();
callback(route);
}
}),
);
})
.catch(error => console.log(error));
};

/**
* Fetch the detail data of a specific station
* @param { string } id - the id of the station
*/
export const getAmenityData = id => {
return client
.query(getAmenityListQuery, {
stationId: id,
})
.toPromise()
.then(response => {
return response.data;
})
.catch(error => console.log(error));
};
24 changes: 24 additions & 0 deletions examples/3.routes/9.amenities-on-route/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getRoute, getAmenityData } from './client';
import { renderRouteData, renderAmenityData } from './interface';
import { drawRoutePolyline } from './map';

/**
* This project shows you how to fetch a car list and render the car details
* The project structure contains;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This project shows you how create a route with a scheduled amenity stop

*
* - client.js - All networking requests
* - interface.js - All interface rendering
* - map.js - All map rendering (including routes and waypoints)
* - queries.js - The GraphQL queries used in the networking requests
*/

getRoute(route => {
drawRoutePolyline(route);
renderRouteData(route);
// Check if the leg ends at an amenity
if (route && route.legs[0].type === 'stationAmenity') {
getAmenityData(route.legs[0].stationId).then(data => {
renderAmenityData(data.amenityList[0]);
});
}
});
57 changes: 57 additions & 0 deletions examples/3.routes/9.amenities-on-route/interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { getDurationString } from '../../../utils';

/**
* Render journey overview.
*
* @param data {object} route specification
*/
export const renderRouteData = data => {
// Show the side card once the route information has loaded
const sideCard = document.getElementById('side-card');
sideCard.style.display = 'block';

// the total duration of the journey (including charge time), in seconds
document.getElementById('duration').innerHTML = `${getDurationString(data.duration ?? 0)}`;

// the total distance of the route, in meters converted to km
const routeDistance = data.distance ? `${(data.distance / 1000).toFixed(0)} km` : 'Unknown';

// the total energy used of the route, in kWh
const routeEnergy = data.consumption ? `${data.consumption.toFixed(2)} kWh` : 'Unknown';

// the amount of stops in this route
const routeStops = `${data.charges ?? 0} stops`;

// A combined field containing several of the route meta data
document.getElementById('route-metadata').innerHTML = `${routeDistance} / ${routeStops} / ${routeEnergy}`;

const numberOfLegs = data.legs.length;
// Populate journey overview
if (data && data.legs) {
// Show first distance
document.getElementById('first-leg-distance').innerHTML = `Drive for ${(data.legs[0].distance / 1000).toFixed(
1,
)} km`;
document.getElementById('first-leg-duration').innerHTML = `Approx ${getDurationString(data.legs[0].duration)}`;
document.getElementById('charging-time').innerHTML = `Charge for ${getDurationString(data.legs[0].chargeTime)}utes`;
document.getElementById('last-leg-distance').innerHTML = `Drive for ${(
data.legs[numberOfLegs - 1].distance / 1000
).toFixed(1)} km`;
document.getElementById('last-leg-duration').innerHTML = `Approx ${getDurationString(
data.legs[numberOfLegs - 1].duration,
)}`;
}
};

/**
* Render a horizontal list of amenity icons
* @param { Object } amenities - an object that contains all amenities and their details
*/
export const renderAmenityData = amenityData => {
let amenityName = document.getElementById('restaurant-name');
let amenityAddress = document.getElementById('restaurant-address');
let amenityDistance = document.getElementById('restaurant-distance');
amenityName.innerHTML = amenityData.name;
amenityAddress.innerHTML = `${amenityData.address.formattedAddress[0]} | ${amenityData.address.country}`;
amenityDistance.innerHTML = `${amenityData.distance} meters from charging station`;
};
185 changes: 185 additions & 0 deletions examples/3.routes/9.amenities-on-route/map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import mapboxgl from 'mapbox-gl';
import { getDurationString } from '../../../utils';
import * as mapboxPolyline from '@mapbox/polyline';

mapboxgl.accessToken = 'pk.eyJ1IjoiY2hhcmdldHJpcCIsImEiOiJjazhpaG8ydTIwNWNpM21ud29xeXc2amhlIn0.rGKgR3JfG9Z5dCWjUI_oGA';

// Keep track of the charging time popups
let chargingTimePopUps = [];

const map = new mapboxgl.Map({
cooperativeGestures: true,
container: 'map',
style: 'mapbox://styles/chargetrip/ckgcbf3kz0h8819qki8uwhe0k',
zoom: 7,
center: [10, 55.0758916],
});

/**
* Draw a route on a map.
*
* Route object contains `polyline` data - the polyline encoded route (series of coordinates as a single string).
* We need to decode this information first. We use Mapbox polyline tool (https://www.npmjs.com/package/@mapbox/polyline) for this.
* As a result of decoding we get pairs [latitude, longitude].
* To draw a route on a map we use Mapbox GL JS. This tool uses the format [longitude,latitude],
* so we have to reverse every pair.
*
* @param data {object} route specification
*/
export const drawRoutePolyline = data => {
const decodedData = mapboxPolyline.decode(data.polyline);
const reversed = decodedData.map(item => item.reverse());

drawRoute(reversed, data.legs);
};

/**
* Draw route polyline and show charging stations on the map.
*
* @param coordinates {array} Array of coordinates
* @param legs {array} route legs (stops) - each leg represents either a charging station, or via point or final point
*/
const drawRoute = (coordinates, legs) => {
if (map.loaded()) {
drawPolyline(coordinates);
drawChargingTimes(legs);
showLegs(legs);
} else {
map.on('load', () => {
drawPolyline(coordinates);
drawChargingTimes(legs);
showLegs(legs);
});
}
};

/**
* Render the charging times at each station directly on top of it's marker.
* @param {array} legs - each leg represents either a charging station, or a via point or final point
*/
const drawChargingTimes = legs => {
legs.forEach((leg, idx) => {
if (idx == legs.length - 1) {
return;
}

const chargeTime = leg.chargeTime;
const hrs = ~~(chargeTime / 3600);
const mins = ~~((chargeTime % 3600) / 60);

const popup = new mapboxgl.Popup({ closeOnClick: false })
.setLngLat(leg.destination.geometry.coordinates)
.setHTML(`<small>${hrs}:${mins}</small>`)
.addTo(map);

// Add the popup to the chargingTimePopUps array
chargingTimePopUps.push(popup);
});
};

/**
* Draw route polyline on a map.
*
* @param coordinates {array} polyline coordinates
*/
const drawPolyline = coordinates => {
const geojson = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'LineString',
properties: {},
coordinates,
},
},
],
};

map.addSource('polyline-source', {
type: 'geojson',
lineMetrics: true,
data: geojson,
});

map.addLayer({
id: 'polyline',
type: 'line',
options: 'beforeLayer',
source: 'polyline-source',
layout: {
'line-join': 'round',
'line-cap': 'round',
},
paint: {
'line-color': '#0078FF',
'line-width': 3,
},
});
};

/**
* Show the charging station, origin and destination on the map.
*
* Last leg of the route is a destination point.
* All other legs are either charging stations or via points (if route has stops).
*
* @param legs {array} route legs
*/
const showLegs = legs => {
if (legs.length === 0) return;

let points = [];

// we want to show origin point on the map
// to do that we use the origin of the first leg
points.push({
type: 'Feature',
properties: {
icon: 'location_big',
},
geometry: legs[0].origin?.geometry,
});

legs.map((leg, index) => {
// add charging stations
if (index !== legs.length - 1) {
points.push({
type: 'Feature',
properties: {
description: `${getDurationString(leg.chargeTime)}`,
icon: 'restaurant',
},
geometry: leg.destination?.geometry,
});
} else {
// add destination point (last leg)
points.push({
type: 'Feature',
properties: {
icon: 'arrival',
},
geometry: leg.destination?.geometry,
});
}
});

// draw route points on a map
map.addLayer({
id: 'legs',
type: 'symbol',
layout: {
'icon-image': '{icon}',
'icon-allow-overlap': true,
'icon-offset': ['case', ['==', ['get', 'icon'], 'location_big'], ['literal', [0, 0]], ['literal', [0, -15]]],
},
source: {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: points,
},
},
});
};
Loading