Building a Place Locator using GeoJson and Javascript
GeoJson is a conventional method of storing geographic points in a database using JavaScript Object Notation format. Being based on JSON, GeoJson easily integrates with JavaScript. In addition, GeoJson allows the encoding of Geographic features like Latitude, Longitude, line strings, and Polygons. <!--more--> This article will walk the reader through the use of GeoJson to build a place locator application using Javascript. In the article, we will develop an application that adds a location to a MongoDB database, Geocodes it to obtain the latitude, longitude, and a readable address of the location then displays the latitudinal and longitudinal mapping of the locations on a map.
This project explores several APIs that work together with GeoJson. The APIs used are Mapbox to render a map on a web page, mapquest to send location requests, and geocode the locations' names into matching latitudes and longitudes.
Prerequisites
To follow along with this article, you need to have the following:
- A code editor. I prefer VS Code for its availability of helpful extensions for web development.
- Mapquest Account for the API Key.
- Mapbox account to have the mapquest API key.
- Understanding of Javascript.
- A working installation of Node.js.
Project environmental setup
Run npm init -y to create an empty package.json
file to get started. This file contains all the dependencies required to develop and run the application. It is essential to include the dependencies in this file so that when the project is executed anywhere apart from the local development environment, the user executes a single command to get the project running.
Next, execute the command below to install the required packages.
npm install express cors node-geocoder mongoose dotenv
After successfully installing the dependencies, we need to set up a mongo DB database for the project. One can follow these steps to set up a database and obtain a connection URL that he will use for the project.
In the next step, log in or create a new account with Mapbox to get an API key to generate a map on the project website. Then, the same procedure should be done with Mapquest.
However, the key needs to be global with mapquest, while the Mapbox API key comes with a startup code that we will use in the JavaScript file in the website folder.
Both Mapbox and Mapquest are used to map location data and geocode place names to latitudes and longitudes, respectively. Mapquest ensures that given a place name, it returns all the information related to that place, including the formatted address, latitude, longitude, street name, zip code, and the country. However, we only need the latitude and longitude for our project to locate the place on a map provided by Mapbox.
Folder organization
We are going to use the MVC development pattern. However, the size of the project will deviate from it a bit. Therefore, we will separate our models, controller, and routes into separate folders. Additionally, we will have a website folder that contains the files required to render the data on the user interface. Lastly, we will have a utility folder to contain the utility files required for the project.
The final folder organization for the project is as shown below:
developer-locator
┣ config
┣ controllers
┣ models
┣ routes
┣ utilities
┣ website
┗ package.json
Each of the folders above will have files that we will discuss in a moment.
Setting up the project server
Before setting up the server file, we need to store the global variables in a config file. So, create a new file named config.env
and add the following snippet in the config folder.
PORT=5000
MODE=development
MONGO_URI='YOUR MONGO URI'
PROVIDER=mapquest
GEOCODER_API_KEY='YOUR GEOCODER API KEY'
Create a new file called server.js
in the application's root folder. This file will contain all the server configurations for a project ranging from the port to run on, the required dependencies, and global variables. Add the following snippet to the server.js
file.
const path = require('path');
const express = require('express')
const dotenv = require('dotenv')
const cors = require('cors')
///load environmental variables into the project
dotenv.config({ path: './config/config.env' });
//initialize the application
const app = express()
//uloading the bosy parser middleware
app.use(express.json())
//using cors
app.use(cors())
const PORT = process.env.PORT || 5000;
app.listen(PORT, () =>{
console.log(`Server running in ${process.env.MODE} mode on port ${PORT}`);
})
Setting the database connection
We need the database to store a developer's information, including the name, location, and date registered to the system. First, we need to set up a database connection to the remote mongo DB database that we created earlier. In the config
folder, create a new file called database.js
and at the snippet below:
const mongoose = require('mongoose');
const connectDatabase = async() => {
try {
const connection = await mongoose.connect(YOUR MONGO URI, {
useUnifiedTopology: true,
});
console.log(`Database connected to ${connection.connection.host}`)
} catch (error) {
console.log(error);
process.exit(1);
}
}
module.exports = connectDatabase;
The next step is to load the database connection function in the server file. Then, add the following code snippet into the server.js
file to facilitate the database connection.
const connectDatabase = require('./config/database')
connectDatabase();
Creating the developer model
A model is like a scaffolding that provides a way of representing data in the database. It specifies all the properties of a given data element and the data type of each property.
We will store the developer's name, address, location, and the date he is registered for our case. Create a file named Developer.js
in the models
folder. The following piece of code represents the schema for a developer.
const mongoose = require('mongoose');
const DeveloperSchema = new mongoose.Schema(
{
name:{
type: String,
required: [true, 'Please add developer name']
},
address: {
type: String,
required: [true, 'Please add developer address']
},
location: {
type: {
type: String,
enum: ['Point']
},
coordinates: {
type: [Number],
index: '2dsphere'
},
readableAddress: String
},
dateRegistered: {
type: Date,
default: Date.now
}
}
);
After creating the developer schema, we need to add a middleware that picks up the location string provided by the user and geocodes it into latitude, longitude, and a readable address, which are then stored in the respective properties in the location object.
All these are done before the developer object is saved into the database. We also set this.location
to undefined to prevent the geocoded location data from being saved into the database.
//geocode and create location
DeveloperSchema.pre('save', async function(next){
const locate = await geocoder.geocode(this.address);
this.location = {
type: 'Point',
coordinates: [locate[0].latitude, locate[0].longitude],
readableAddress: locate[0].formattedAddress
}
this.address = undefined;
next();
});
For the middleware above to work, we need to add the geocoder utility, including the service provider and the API key. This utility is obtained from the node-geocoder
dependency that we downloaded.
In the utilities
folder, create a new file called geocoder.js
, then add the following code snippet.
const nodeGeocoder = require('node-geocoder');
const options = {
provider: process.env.PROVIDER,
httpAdapter: 'https',
apiKey: process.env.GEOCODER_API_KEY,
formatter: null
};
const geocoder = nodeGeocoder(options);
module.exports = geocoder;
Working on the controller
The controller determines how data is obtained and entered into the database. We will have two methods that do the functions, respectively. First, in the controllers
folder, create a new file called developer.js
, then add the code snippet below:
const Developer = require('../models/Developer')
exports.getDevelopers = async (req, res, next) =>{
try {
const developers = await Developer.find();
return res.status(200).json({
data: developers
})
} catch (error) {
res.status(500).json({
error: error,
})
}
}
exports.addDeveloper = async (req, res, next) =>{
try {
const developer = await Developer.create(req.body);
return res.status(200).json({
data: developer
})
} catch (error) {
res.status(500).json({
error: error.message,
})
}
}
Passing the data to the router
Routing is necessary to specify where to channel a request and where the URL does the fetch data goes. Our route file took the functions from the controller and applied the necessary function depending on the request.
We will instantiate a router and pass the functions from the controller, then export the file to the server file, which is the applications entry point.
const express = require('express')
const {getDevelopers, addDeveloper} = require('../controllers/developers')
const router = express.Router()
router.route('/').get(getDevelopers).post(addDeveloper);
module.exports = router
Working on the website
Our website will have a title and a map area showing the developers available in a given locality.
Additionally, we will have a form to add the developers to the system. The form will submit the data to invoke the addDeveloper
function in the controller
file.
Next, we have a JavaScript file that renders the Map on the browser. This code snippet is provided once you create the API key in Mapbox.
mapboxgl.accessToken = 'yourmapboxapikey;
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11', //,ap style
zoom: 9, //zoom level
center: [ 36.82194619999996, -1.2920659] //the center of the map
})
The snippet specifies the zoom level, map style, and the center, which is chosen based on where you want to map out the developers.
Getting developers and displaying them on the Map
In this step, we want to collect all the developers in the database then display them on the Map depending on the specific latitude and longitude.
We first need a function that will fetch all the developers from the database. Next, we will convert the developers into an array that we modify to fit the style specified by Mapbox.
async function getDevelopers() {
const res = await fetch('/api/developers');
const data = await res.json();
const developers = data.data.map(developer => {
return {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
developer.location.coordinates[0], //latitude
developer.location.coordinates[1] // longitude
]
},
properties: {
name: developer.name, //developer name
icon: 'house' //icon to display
}
};
});
loadMap(developers); //load map with developers
}
The following function loads the Map with the developers mapped into their respective latitudes and longitudes. Next, we pass the array of developers from the above function to the loadMap()
function. We also set the icon size and added the developer name.
Lastly, we call the getDevelopers
function, which calls the loadMap
function.
//Load map with devs
function loadMap(developers) {
map.on('load', function() {
map.addLayer({
id: 'points',
type: 'symbol',
source: {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: developers //add the develpers array
}
},
layout: {
'icon-image': '{icon}-15',
'icon-size': 1.5,
'text-field': '{name}',
'text-anchor': 'top'
}
});
});
}
getDevelopers()
Testing the application
To run the application, use npm start
to start your server then head to the localhost on the port you specified in the config.env
file to access your site.
Your site should be as below before adding the developers.
However, after adding the developers, the Map should look as shown below:
Conclusion
This article covered how to use Mapbox with GeoJson to build an application that locates developers on a map. We explored how to geocode the location names into latitudes and longitudes that we then stored in a MongoDB database.
The stored locations of the developers were then pinned onto the Map using Mapbox. Exploring GeoJson, Mapbox, and Mapquest in this project can be a great starting point for using maps and Geocoding.
Peer Review Contributions by: Mercy Meave