Enhancing Shared Mobility Apps with RabbitMQ

The Rise and Rise of Shared Mobility

A Simple Frontend

yarn add express socket.io leaflet amqplib alertify.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Mobility App</title>
<link rel="stylesheet" href="leaflet.css" />
<link rel="stylesheet" href="css/alertify.css" />
<style>
html, body, #map {
height:100%;
width: 100%;
z-index: 0;
}
body{
margin: 0;
padding: 0;
font-size: 14px;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="leaflet.js"></script>
<script src="socket.io.js"></script>
<script src="js/alertify.js"></script>
<script src="app.js"></script>
</body>
</html>
// Start with the Map Themes
function getMapTheme(theme) {
let mapTheme;
// Default Theme
mapTheme = 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png';
if ('light_all' === theme) {
mapTheme = 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png';
}
if ('dark_all' === theme) {
mapTheme = 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png';
}
return mapTheme;
}
// Add Map Attribution
let mapAttribution = '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="http://cartodb.com/attributions">CartoDB</a>';
let lighttheme = L.tileLayer(getMapTheme('light_all'), { attribution: mapAttribution });
let darktheme = L.tileLayer(getMapTheme('dark_all'), { attribution: mapAttribution });
// Add Themes to selectable Map Layers
let baseLayers = {
"Light Theme": lighttheme,
"Dark Theme": darktheme
};
// Setup a Riders' Marker Group
let ridersMarkers = L.layerGroup();
let overlayMaps = {
"Riders": ridersMarkers
};
// Initialize and show the map
let map = L.map('map', {
attributionControl: true,
zoom: 16,
layers: [lighttheme]
}).fitWorld();
// Add selectable controls to it
L.control.layers(baseLayers, overlayMaps).addTo(map);
// Create custom marker icons for riders other than myself
let customIcon = L.Icon.extend({
options: {
shadowUrl: "/img/marker-shadow.png",
iconSize: [25, 39],
iconAnchor: [12, 36],
shadowSize: [41, 41],
shadowAnchor: [12, 38],
popupAnchor: [0, -30]
}
});
let yellowIcon = new customIcon({ iconUrl: "/img/marker-yellow.png" });
// Function to add these custom markers
function setMarker(data) {
for (i = 0; i < data.coords.length; i++) {
// let marker = L.marker([data.coords[i].lat, data.coords[i].lng], { icon: yellowIcon }).addTo(map);
let marker = L.marker([data.coords[i].lat, data.coords[i].lng], { icon: yellowIcon });
marker.bindPopup("A ride is here!");
// add marker
ridersMarkers.addLayer(marker);
alertify.success("A nearby rider ID " + data.id + " has just connected!");
}
}
// Socket IO Client Initialization
let connects = {};
let socket = io('http://127.0.0.1:80');
socket.on('receive', function(data) {
alertify.log("New Socket Event Received");
if (!(data.id in connects)) {
setMarker(data);
}
connects[data.id] = data;
});
// placeholders for the L.marker and L.circle representing user's current position and accuracy
let current_position, current_accuracy;
function onLocationFound(e) {
// if position defined, then remove the existing position marker and accuracy circle from the map
if (current_position) {
map.removeLayer(current_position);
map.removeLayer(current_accuracy);
}
let radius = e.accuracy / 2; current_position = L.marker(e.latlng)
.addTo(map)
.bindPopup('Your current position and a ' + radius + ' meters radius').openPopup();
current_accuracy = L.circle(e.latlng, radius).addTo(map); let data = {
id: userId,
coords: [{
lat: e.latitude,
lng: e.longitude,
acr: e.accuracy
}]
}
socket.emit("send", data);
}
let errors = {
1: "Geolocation Permission Denied",
2: "Network Error",
3: "Connection Timeout"
};
function onLocationError(error) {
// confirm dialog
alertify.confirm("We could not get your current location, reason : " + errors[error.code] + "! Would you like to reload the page and try again?", function () {
// user clicked "ok"
location.reload();
}, function() {
// user clicked "cancel"
alertify.log("Please Note You Might Be Getting Stale Data!");
});
console.log(
'code: ' + error.code + '\n' +
'message: ' + error.message + '\n',
'Geo-Location Error'
);
}
map.on('locationfound', onLocationFound);
map.on('locationerror', onLocationError);
map.on('moveend', function(e) {
let bounds = map.getBounds();
let sw = bounds.getSouthWest();
let ne = bounds.getNorthEast();
let sw_latitude = sw.lat;
let sw_longitude = sw.lng;
let ne_latitude = ne.lat;
let ne_longitude = ne.lng;
let zoom = map.getZoom(); if( zoom < 16 ){
// remove all the current layers
ridersMarkers.clearLayers();
// Show Toast to zoom in
alertify.log("Please zoom in to view nearby riders");
} else {
// To Do - Query DB for nearby rides
}
});
map.stopLocate();// Start Mobility App function
let startApp = function (){
// check whether browser supports geolocation api
if (navigator.geolocation) {
map.locate({
watch: true,
setView: true,
maxZoom: 16,
timeout: 60000,
maximumAge: 60000,
enableHighAccuracy: false
});
} else {
alertify.alert("Sorry, your browser does not support geolocation!");
}
}
// Simulate Rider Authorization by asking for hypothetical ID
let userId;
let defaultUserId = "9999";
alertify
.defaultValue("9999")
.prompt("Please Enter A Hypothetical User ID : ",
function (val, ev) {
ev.preventDefault();
alertify.success("You've clicked OK and typed: " + val);
userId = val;
startApp();
}, function(ev) {
ev.preventDefault();
alertify.error("You've clicked Cancel, default ID 9999 used");
userId = defaultUserId;
startApp();
}
);
let express = require('express')
let amqplib = require('amqplib')
let app = express()
let server = require('http').createServer(app)
let io = require('socket.io')(server)
app.use(express.static('public'))
app.use(express.static('node_modules/leaflet/dist'))
app.use(express.static('node_modules/alertify.js/dist'))
app.use(express.static('node_modules/socket.io-client/dist'))
let open = amqplib.connect('amqp://localhost')// Create the RabbitMQ Exchange, Queues and Bindings
open.then(function(conn) {
return conn.createChannel()
}).then(function(ch) {
return ch.assertExchange('geolocation-exchange', 'fanout').then(function(geolocationExchange) {
ch.assertQueue('analytics-queue').then(function(analyticsQueue) {
ch.bindQueue(analyticsQueue.queue, geolocationExchange.exchange)
})
ch.assertQueue('map-queue').then(function(mapQueue) {
ch.bindQueue(mapQueue.queue, geolocationExchange.exchange)
})
ch.assertQueue('redis-queue').then(function(redisQueue) {
ch.bindQueue(redisQueue.queue, geolocationExchange.exchange)
})
})
}).catch(console.warn)
io.sockets.on('connection', function (socket) {
socket.on('send', function (data) {
socket.broadcast.emit('send', data);
// Send to data to RabbitMQ
open.then(function(conn) {
return conn.createChannel()
}).then(function(ch) {
return ch.assertExchange('geolocation-exchange', 'fanout').then(function(geolocationExchange) {
ch.publish(geolocationExchange.exchange, '', Buffer.from(JSON.stringify(data)))
})
}).catch(console.warn)
})
socket.on('receive', function (data) {
socket.broadcast.emit('receive', data);
})
})
server.listen(80, '127.0.0.1', function() {
console.log('Mobility App running on Localhost')
})
let amqplib = require('amqplib')
let io = require('socket.io-client')
let socket = io.connect('http://127.0.0.1:80', { reconnect: true });
let queue = 'map-queue'
let open = amqplib.connect('amqp://localhost')
// Update Client Map Consumer
open.then(function(conn) {
return conn.createChannel();
}).then(function(ch) {
return ch.assertQueue(queue).then(function() {
return ch.consume(queue, function(msg) {
if (msg !== null) {
console.log(msg.content.toString());
// Tell socket.io server to update clients with new geolocation details
let data = msg.content.toString()
socket.emit('receive', JSON.parse(data));
ch.ack(msg);
}
});
});
}).catch(console.warn);
let amqplib = require('amqplib')
let queue = 'analytics-queue'
let open = amqplib.connect('amqp://localhost')
// Log & Analytics Consumer
open.then(function(conn) {
return conn.createChannel();
}).then(function(ch) {
return ch.assertQueue(queue).then(function(ok) {
return ch.consume(queue, function(msg) {
if (msg !== null) {
console.log(msg.content.toString());
// To Do - Persist new Geolocation coordinates in Analytics store like Elasticsearch
ch.ack(msg);
}
});
});
}).catch(console.warn);
let amqplib = require('amqplib')
let queue = 'redis-queue'
let open = amqplib.connect('amqp://localhost')
// Key-Value Store Consumer
open.then(function(conn) {
return conn.createChannel();
}).then(function(ch) {
return ch.assertQueue(queue).then(function(ok) {
return ch.consume(queue, function(msg) {
if (msg !== null) {
console.log(msg.content.toString());
// To Do - Persist new Geolocation Key-Value store like Redis
ch.ack(msg);
}
});
});
}).catch(console.warn);

Running the Demo

node server.jsnode consumers\map.jsnode consumers\analytics.jsnode consumers\redis.js
var data = {
id: 5678,
coords: [{
lat: -1.2355076,
lng: 36.8850185,
acr: 30
}]
}
socket.emit("send", data);

--

--

--

Follow me to keep abreast with the latest technology news, industry insights, and developer trends. Alibaba Cloud website:https://www.alibabacloud.com

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Set Another Place for Plato

Aspect-Oriented Programming (Spring AOP)

Cover message step nearly.

Databases in Python Made Easy with SQLAlchemy

Nested Serializer With ReadOnlyField | Django Rest Framework

Postmortem: Deleting data from a Production database

Technical Debt — the silent villain of web development | Accesto Blog

How to Connect windows to aws with aws sdk php

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alibaba Cloud

Alibaba Cloud

Follow me to keep abreast with the latest technology news, industry insights, and developer trends. Alibaba Cloud website:https://www.alibabacloud.com

More from Medium

Steps to Containerize your app locally and deploy it on Cloud Run

Sonar Setup with SAP Commerce

In-Memory API Deployment

Announcing GraphQL Security for Everyone