This article was sponsored by WRLD 3D. Thank you for supporting the partners who make SitePoint possible.
“Anywhere in the city? Anywhere in the city: I’ll tell you the best public toilet.”
These are the words of George Costanza to Jerry Seinfeld in 1991. In that episode of Seinfeld; the visionary George invented an app before his time - the bathroom finder! If you’re a frequent traveller, a parent, or just someone who knows the importance of a clean and well-maintained space for some "serenity", you'll understand the utility of this idea.
So, this time in the second tutorial of our WRLD series we’re going to build a… let's call it a "facility finder app".
It’s not the first time someone has tried this mind you. In 2010, bathroomreview.ca did just that (as described in Forbes). But the site is no longer operational.
We covered quite a bit of ground in the last tutorial, this time round, we’re going to reuse some of that learning. For instance, we’ll use ParcelJS to build our static files, but we won’t go into too much detail about how to set it up again. We’ll also highlight buildings and set the appropriate weather conditions and time of day, depending on what they are for the user. If you’re unsure about how these work, refer back to the previous tutorial.
In this tutorial, we’re going to cover these topics:
- Creating a simple AdonisJS server-side API (to cache location data and handle CORS requests).
- Requesting public facilities data, from refugerestrooms.org, if there are no cached locations within 10 meters of the user. We’ll use the Google Distance Matrix API to calculate the distance between points of interest.
- Highlighting buildings with public facilities, colored to match their rating. Green for good, red for bad. Each building will have an info card for extra info (like how to reach the bathroom).
At the end, we’ll talk a bit about how to turn this kind of app into a viable business. That’s really the point of this isn't it? The WRLD APIs provide tools to visualise real-world data in a map of the real world. Our job is to work out how to use this technology for commercial applications!
The code for this tutorial can be found on Github. It has been tested with a modern versions or Firefox, Node, and macOS.
Getting Facility Data
Let’s begin by learning how to get the facility data, and the form we get it in. We’re going to use refugerestrooms.org as a source of data. We learn that we can search by latitude and longitude, by looking at the documentation. In fact, we can make the following request, and see a set of facilities close to my location:
curl http://ift.tt/2B67V5u? ↵
lat=-33.872571799999996&lng=18.6339362
There are a few other parameters we could be specifying (like whether to filter by accessible and/or unisex facilities), but the main thing this gives us is a way to plug coordinates into a search and get close-by locations.
We can’t just call this from the browser, though. There are all sorts of security reasons why this is disallowed. There are also performance reasons. What if 10 people made the same request, standing 10 meters apart from each other? It would be a waste to fire off 10 requests to the same remote server, when we could serve it faster from a caching proxy.
Instead, we’re going to set up a simple AdonisJS caching API. Our browser app will send requests to the AdonisJS API, and if there’s no “nearby” data; it will send a request to the Refuge API. We can’t spend too much time on the details of AdonisJS, so you’ll have to check out the documentation for details.
I’m also just about done writing a book about it, so that’s the best place to learn how it works!
The easiest way, to create a new AdonisJS app, is to install the command-line tool:
npm install --global @adonisjs/cli
This enables the adonis
command-line globally. We can use it to create a new application skeleton:
adonis new proxy
This takes a little while, since it’s installed a few things. When it finishes, you should see a message to run the development server. This can be done with:
adonis serve --dev
Open up http://127.0.0.1:3333 in your browser, and you should be greeted by this beauty:
Creating Migrations and Models
Let’s story the search data in a database. AdonisJS supports a few different engines, but we’ll use SQLite for the sake of simplicity. We can install the appropriate driver, using:
npm install --save sqlite3
Next, let’s make a migration and a model. We’re only interested in the coordinates used to search, and the returned JSON. If the coordinates are close enough to where a user is searching for, we’ll reuse the existing search response instead of re-requesting the search data.
We can use the adonis
command-line utility to create migrations and models:
adonis make:migration search
adonis make:model search
That creates a couple files. The first is a migration, to which we can add three fields:
"use strict"
const Schema = use("Schema")
class SearchSchema extends Schema {
up() {
this.create("searches", table => {
table.increments()
table.string("latitude")
table.string("longitude")
table.text("response")
table.timestamps()
})
}
down() {
this.drop("searches")
}
}
module.exports = SearchSchema
This is from
proxy/database/migrations/x_search_schema.js
We’ve added the latitude
, longitude
, and response
fields. The first two make sense as string
even though they contain float data, because we want to do sub-string searches with them.
Next, let’s create a single API endpoint:
"use strict"
const Route = use("Route")
// we don't need this anymore...
// Route.on("/").render("welcome")
Route.get("search", ({ request, response }) => {
const { latitude, longitude } = request.all()
// ...do something with latitude and longitude
})
This is from
proxy/start/routes.js
Each AdonisJS route is defined in the routes.js
file. Here, we’ve commented out the initial “welcome” route, and added a new “search” route. The closure is called with a context object; which has access to the request
and request
objects.
We can expect search requests to provide latitude
and longitude
query string parameters; and we can get these with request.all
. We should check to see if we have any vaguely related coordinates. We can do this by using the Search
model:
const Search = use("App/Models/Search")
const searchablePoint = (raw, characters = 8) => {
const abs = Math.abs(parseFloat(raw))
return parseFloat(abs.toString().substr(0, characters))
}
Route.get("search", async ({ request, response }) => {
const { latitude, longitude } = request.all()
const searchableLatitude = searchablePoint(latitude)
const searchableLongitude = searchablePoint(longitude)
// console.log(searchableLatitude, searchableLongitude)
const searches = await Search.query()
.where("latitude", "like", `%${searchableLatitude}%`)
.where("longitude", "like", `%${searchableLongitude}%`)
.fetch()
// console.log(searches.toJSON())
response.send("done")
// ...do something with latitude and longitude
})
This is from
proxy/start/routes.js
We begin by importing the Search
model. This is a code representation of the database table we created (using the migration). We’ll use this to query the database for “nearby” searches.
Before we can do that, we need a way to search for nearly coordinates. The searchablePoint
function takes a raw coordinate string and creates an absolute float value, removing the optional -
from the front of the string. Then, it returns the first 8
characters of the coordinate string. This will shorten -33.872527399999996
to 33.872527
. We can then use these 8 characters in a SQL “where like” clause, to return all searches with similar coordinate strings.
AdonisJS uses the async
and await
keywords to great effect. Methods like Search.query
return promises, so we can await
their results while still writing 100% asynchronous code.
I’m skipping a lot of AdonisJS details, which I really don’t like doing. If you’re struggling with this part; speak to me on Twitter, and I’ll point you in the right direction.
Matching Nearby Locations
Now that we’ve got the “nearby” locations, we can compare their relative distances to where the user is standing. If you don’t yet have a Google API key, refer back to the previous tutorial for how to get one. We’re about to be the Google Distance Matrix service:
http://ift.tt/1zFyAP1? ↵
mode=walking& ↵
units=metric& ↵
origins=-33.872527399999996,18.6339164& ↵
destinations=-33.872527399999997,18.6339165& ↵
key=YOUR_API_KEY
The Distance Matrix service actually allows multiple origins, so we can combine all of your previous searches into a longish origin string:
const reduceSearches = (acc, search) => {
const { latitude, longitude } = search
return `${acc}|${latitude},${longitude}`
}
Route.get("search", async ({ request, response }) => {
const { latitude, longitude } = request.all()
// ...get searches
const origins = searches
.toJSON()
.reduce(reduceSearches, "")
.substr(1)
// console.log(origins)
response.send("done")
// ...do something with latitude and longitude
})
This is from
proxy/start/routes.js
We can convert the search results into an array of objects. This is useful because we can reduce the array, combining each search’s latitude and longitude into a string. That string will begin with a |
, so we need to get the string starting at index 1
.
Continue reading %Build George Costanza’s Bathroom Finder using WRLD%
by Christopher Pitt via SitePoint
No comments:
Post a Comment