Tutorial: Geotagtext, a Free Geotagging Web Application

Written by robert-l.-read | Published 2021/08/03
Tech Story Tags: free-software | geolocation | mapbox | mapping | heroku | firebase | nodejs | expressjs

TLDR Geotagtext is a web application made by Public Invention volunteers that allows users to geographically mark, or geotag, places and things that are important to them with a browser running on a smartphone or larger computer. Users simply create their own map application and can instantly start working on their own set of special geo markers. These map applications are all public, and can be shared with others by simply giving the name of the application. Using builtin geolocating features of the browser, the user's current position can be recorded as a geotagging. This article teaches you how to build a zero-deployment, browser-delivered web application by composing widely available gratis (zero cost) tools.via the TL;DR App

— Robert L. Read, Diego Aspinwall, and Neil Martis

Example of geotags using geotagtext

Geotagtext is a web application made by Public Invention volunteers that allows users to geographically mark, or geotag, places and things that are important to them with a browser running on a smartphone or larger computer. Users simply create their own map application and can instantly start working on their own set of special geo markers, which can include short text snippets. If you click on a point in geotagtext, it will display the message associated with that geotag. Using builtin geolocating features of the browser, the user's current position can be recorded as a geotag. These map applications are all public, and can be shared with others by simply giving the name of the application.

We believe Geotagtext is a useful tool worth learning about. More importantly, this article teaches you how to build a zero-deployment, browser-delivered web application by composing widely available gratis (zero cost) tools ([Firebase](https://We believe Geotagtext is a useful tool worth learning about. More importantly, this article teaches you how to build a zero-deployment, browser-delivered web application by composing widely available gratis (zero cost) tools (Firebase, Mapbox, Express and Heroku). It demonstrates the power of modern free software, both the free-as-in-free-lunch kind and the free-as-in-free-speech kind. Geotagtext itself is free software released under the GNU Affero General Public License.), Mapbox, Express and Heroku). It demonstrates the power of modern free software, both the free-as-in-free-lunch kind and the free-as-in-free-speech kind. The code for Geotagtext itself is free software released under the GNU Affero General Public License.

Although Firebase, Mapbox, Express and Heroku worked for us, these are by no means the only way to create something like Geotagtext. Although we’ll go into more depth later in the article, here’s a brief overview of each service we’ll be teaching about. Firebase is used as a remote database to store data on map applications. Geotagtext accesses the database to write new tags and apps. Mapbox is used to display information stored in the database on a map. The color and location of markers are read and then individually written on top of a map of the world. Heroku is a cloud platform that developers use to create applications. It allows us to keep database information secret by hiding keys and running each reference on a remote server. You’ll also learn how to test edits locally before pushing them to github. Node.js is used on our servers, and the back-end framework Express handles requests.

Although some of these components require payment when scaled up, the product we created comes at no cost other than time. Our application wouldn’t handle large volumes of traffic, however this proves that it’s possible to make a web development idea into reality with little more than free time. Additionally, most computers with updated browsers, such as chrome, can run our application, making it accessible to more than 2 billion people. The application relies heavily on javascript, which runs in the browser, meaning there is no need to download files to start using Geotagtext. Geotagtext is concise, with two separate html files and fewer than a thousand lines of code. Nevertheless, it is extremely flexible, meaning it can be used for anything from tracking invasive species to marking favorite travel locations.

This tutorial is of the moderately experienced programmer interested in composing tools to make a powerful website, or specifically for those interested in simple geotagging applications.

What Could Geotagtext be used for?

Since Geotagtext is a map application builder, it’s exact use isn’t set in stone. If a certain activity requires recording geotags with attached information and sharing them with others, chances are that Geotagtext is the perfect fit. In the “App Builder” page it’s also possible to customize your map app to a degree, and these settings will be applied to anyone who uses your app. Although Geotagtext can be used for many causes, we divided our favorite examples into Social, Informational and Emergency categories.

Social applications are for having fun or achieving personal goals.

  • Peak Marker: drop a geotag every time you summit a mountain. Over time you’ll have a sea of points covering your favorite mountain range. If you’re willing to invest time, you could develop your own way to store summit photos.
  • Sightseeing Map Builder: a list of the best places in a city (or anywhere) to visit relative to your location.
  • Tag Game: a friend of yours runs through the woods, setting markers. You follow him after a couple minutes, setting your own markers within 30 feet of his. If you get them all, you win!
  • Geocaching: find hidden caches in your area. You could even make a personal map with your favorite locations.
  • Personal Recreation Spots: make a map with your favorite places to bike, ski or catch a sunset.

Informational applications are for recording important data that you might like to share with friends or the public.

  • Interesting Things: track flowers, plants or animals that you’re interested in or doing research on.
  • Invasive Plant Marker: track plants, like the point above, but use your data to eradicate invasive species in your area. Share your app with a few dedicated friends or the whole town.
  • Trail/road Update: report the status of a road or trail in your area. If there’s damage or a pothole, drop your marker and let others know.
  • Location Update: give location updates for family and friends while on a hike or trip. GPS beacons are expensive and elaborate, but everyone has a phone.

Emergency applications are for quickly locating and helping people in need. Keep in mind that Geotagtext only works with a good cellular connection, and it shouldn’t be relied on at all times. These are theoretical applications, and should not be used in an actual emergency—we haven’t tested geotagtext enough to imply any fitness for any purpose, and may take it down at any time.

In a natural disaster or other emergency, there are all kinds of reasons to locate things on a map. An obvious example is where people in distress can’t be helped right away.. In general, one can imagine locating other problems or resources, such as sick people and clinics, injured people and first aid stations. Once could tag stalled cars, or stranded birds or whales. You can probably think of dozens of other examples.

However, the beauty of open source software and our approach is that you may think of completely unrelated uses for it. Please, be our guest in exploring ideas we would never have thought of!

Limitations and Warnings

Geotagtext is not so much a product as an example code base. Keep in mind that it provides neither privacy nor security. Every time you place a geotag, you are potentially telling the world exactly where you are.

When you build a custom app with Geotagtext, perhaps named after yourself or indicating your intended use, any other user can use your app and possibly corrupt your data by adding their own irrelevant geotags. However, Geotagtext is free-libre open-source code; if you want to build a separate version that is private, you are welcome to do so, so long as you obey the GNU Affero GPL license.

File Organization

The organization of the files in this project is straightforward:

  1. Index.html is the main entry point for the web application. This contains some JavaScript concerned specifically with the GUI, searching for the particular app to use, and visual configuration.
  2. Builder.html is the webpage that allows named applications to be built. It contains a JavaScript function which makes an AJAX call to create entirely new segregated databases.
  3. Tagscript.js is primarily responsible for manipulating the map functions.
  4. Server.js is an Express server that serves webpages, answers AJAX queries, and knows how to access firebase. The server.js file was added last in our development; it was unneeded until we moved to Heroku for security reasons. It is completely unneeded if you don’t mind your firebase keys being exposed.

Mapbox

The purpose of Geotagtext is to build maps, and Mapbox is therefore the heart of this web application. We used this tool first, before we could persist any data, just to show that we could use browser-empowered geolocation to set a marker on a map. Mapbox is one of the easiest ways to quickly create a website with a useful map, though no doubt there are other options.

In tagscript.js, you will find the following lines, which is our mapbox access token.

// It is possible this accessToken will someday reach a limit. We recommend you change it if that occurs.
const MAPBOXGL_ACCESSTOKEN  = 'pk.eyJ1Ijoicm9iZXJ0bHJlYWQiLCJhIjoiY2prcHdhbHFnMGpnbDNwbG12ZTFxNnRnOSJ9.1ilsD8zwoacBHbbeP0JLpQ';

If needed, you can get your own access token which is free for limited use from Mapbox. These next lines create a very simple map, which is placed in the DOM object named ‘map’.

function initMap(appname) {
    mapboxgl.accessToken = MAPBOXGL_ACCESSTOKEN;

  map = new mapboxgl.Map({
	container: 'map',
	style: 'mapbox://styles/mapbox/light-v9',
	center: [-96, 37.8],
	zoom: 3
    });

    if (appname){
	map.on('load', function () {

	    $.ajax({type : "GET",
		    url: "returnTags",
		    dataType: 'json',
		    data: {appName: appname},
		    success: function(result){
			var v = result;
			for(const prop in v) {
			    const n = parseInt(prop.substring("geotag".length));
			    gt = v[prop];
			    showLngLatOnMap(gt.longitude,gt.latitude,gt.color,n,gt.message);
			}
			},
			error : function(e) {
			    console.log("ERROR: ", e);
			}
		    });
	});
    }
}

This is all that is needed to create your first map, empty of data!

This file that contains functions such as:

showLngLatOnMap(lonDec,latDec,color,n,message)

showPositionOnPage(position,color,message,number)

Which utilize the documented Mapbox interface to add the colored markers and to associate with them JavaScript needed to accomplish the rendering of the text snippets.

Using only this, you could implement geotagging, but your data would not be permanent...it would be lost on a refresh. However, iterative and Agile development benefits from the ability to test in pieces.

Firebase

After demonstrating the ability to geotag and render markers on a map that were retained with the temporary storage of a single browser page, we needed the ability to persist data. Although we could have used client-side storage such as Web Storage, the point of Geotagtext was to create an application where several users could concurrently participate in and enjoy a dynamically updated map. This required a shared persistent store. We could, of course, have created our server and used any number of database tools there, but we chose to use Firebase, a web-delivered object store that is free-for-low usage. It has the advantage of supporting direct JSON storage rather easily. This allowed us to build an interesting app without having to implement a server at first.

Originally, we did not have a server---we did everything in the page as a serverless deployment. But the firebase access code did not change. Firebase was relatively easy to get working. The initialize code is (now server-side, but originally browser-side):

require("firebase/auth");
require('firebase/database');

const config = {
    apiKey: process.env.apiKey,
    authDomain: process.env.authDomain,
    databaseURL: process.env.databaseURL,
    messagingSenderId: process.env.messagingSenderId
};

firebase.initializeApp(config);

firebase.auth().signInAnonymously().catch(function(error) {
    var errorCode = error.code;
    var errorMessage = error.message;
    console.log(errorCode);
    console.log(errorMessage);
});

const ref = firebase.database().ref();

Our basic approach was to read all of the data as a single snapshot. This is no doubt inefficient, but premature optimization violates the YAGNI principle. The power of JavaScript and firebase made returning a snapshot very concise:

var returnFirebaseSnapshot = (req, ref, res) => {
    var appName = req.query.appName;
    firebase.database().ref('/apps/'+appName+ref).once('value')
        .then((snapshot) =>
	    {res.send(JSON.stringify(snapshot));}
	);
}

This code works inside Express,so it utilizes a web request (“req”) and response (“res”), without which the access to firebase would be a single line (53).

The code below is sufficient to add a new geotag to a database as a single AJAX call:

app.get('/writeTag', function (req, res) {
    var obj = req.query.taginfo;
    obj["latitude"] = parseFloat(obj.latitude);
    obj["longitude"] = parseFloat(obj.longitude);
    firebase.database().ref('/apps/' + req.query.appname + "/tags/" + req.query.tagId).set(obj,
                                                 function(error) {
                                                     if (error) {
                                                         console.log("ERROR:",error);
                                                     }
                                                 });
});

An App Building App

It seemed to us that the basic Geotagtext functionality was extremely versatile. We couldn’t decide on any one use that was obviously more appealing to a broad audience than others. The goal that Neil and I had begun with---to build a database of books availables in the “Little Library” sharing boxes had been abandoned when we realized that OCR image processing was insufficient for it. So, instead of deciding on a particular application, we decided to punt---we built an app that lets you build apps.

By simply entering a new name, we could segregate data between applications. Thus any user could build their own private database of geomarkers, and use it for whatever purpose. If access to the web app were also controlled, the database would be truly private.

The code below implements an AJAX call that creates the database. In combination with builder.html which collects the name of the “app” from the user, this suffices:

app.get('/actuallyCreate', function (req, res) {
    var config = req.query.obj;
    for (const property in config) {
	if (config[property] == "false") {config[property] = false;}
	if (config[property] == "true") {config[property] = true;}
    }
    config.tags = {};
    firebase.database().ref('apps/' + req.query.appname).set(config,
						   function(error) {
						       if (error) {
							   // The write failed...
							   console.log("ERROR:",error);
						       } else {
							   // Data saved successfully!
							   console.log("SUCCESS");
						       }
						   });
});

To complete this functionality, we needed index.html to either collect the name of the “app” to use, or it must be supplied on the URL. This was easily accomplished (in index.html):

    const params = (new URL(document.location)).searchParams;
    var GLOBAL_APPNAME = params.get('app');

Heroku: Modest Security

The combination of Mapbox and Firebase provided the functionality we were seeking with zero-deployment costs and zero dollars to us. We judged it interesting enough to want to write this article, but there was a minor problem: Firebase requires you to register a credit card with Google in case of exceeding the free usage limit. Our api keys were publicly exposed in the GitHub repo. Although nobody could do anything very naughty with this, a persistent individual could use our api keys to inappropriately get free service up to the limits that we had placed at Google. A better approach would be to not expose the keys. Of course, Geotagtext is meant to be free and open, so we wanted to retain a public repo that anyone could fork and a zero-deployment delivery via a browser.

The solution was to significantly complicate our deployment by creating a server. A server would allow us to hide our keys as configuration on the server, which would not be seen, but still offer a free web page that anyone could browse to. We want you, readers of this article, to be able to create your own semi-private geotag database with a push of a button. This way you can try out geotagtext before you commit to getting your own Firebase key. (Of course, you can always modify your code to use a different database if you prefer.)

Luckily, Heroku made this relatively simple, and again zero-dollar-cost at the level of traffic we have.

We created a live Heroku app and tied it to our Github repo. A minor code change removed our API keys from the code. Instead, we now read all the keys from the “environment” (“environment” has a special meaning to computer programmers more specific than the general word.) This “environment” consists of key-value pairs that could be specified in Heroku. They would be passed to the server-side process.

This required a major architecture change, because instead of accessing Firebase directly from the browser, the browser would now make an AJAX call to the server, which would access Firebase, get the data, and return it in the AJAX response. However, modern free tools (JQuery) make this quite simple.

Heroku has nice tutorials which we followed. The basic approach was to implement a simple web server which Heroku hosts automatically. The simplest and most common approach to this in Node.js is to use Express. Our file server.js is a simple Express application supporting five REST calls executed by AJAX. Additionally, the following line was sufficient to serve our .hmtl files (line 46, server.js):

app.use(express.static(__dirname));

The result is a secure-ish way to offer anyone the free use of our app. Of course, someone could waste a little bit of database space with a denial of service attack, but generally people are well-behaved. At no charge, with no download or installation of any software, you can test out Geotagtext with your phone. Perhaps you can think of a creative web application for placing and sharing geotags. If you become more serious, you can always fork our code and make modifications. If you have a need for more privacy or greater traffic, you can create a fork, get your own Firebase api key, and set up your own Heroku instance completely independent of ours. Note: As owners of the database, we can see whatever tags you create. In a sense, if you use Geotagtext right now you are revealing your location, so you may wish to think before doing so.

Basic Code-Test Loop

Software is generally written in an iterative code-and-test loop. With Heroku deployments, the basic iterative development process becomes:

  1. Code.
  2. Run locally.
  3. Test and eval locally.
  4. Git commit, git push.
  5. Deploy on Heroku.
  6. Test on the live Heroku site.

AJAX

AJAX and JQuery are so widely used it is easy to forget what a great service the authors of this free-libre tool have done for the community. AJAX makes it easy to make a call back to the Express server; here is the primary example for getting an “app” (not the data itself, which is done in tagscript.js.)

    function reconfigureFromApp(appName) {
	$.ajax({type : "GET",
		url: "reconfigureFromApp",
		dataType: 'json',
		data: {appName: appName},
		success: function(result){
		    reconfigureFromConfig(result);
		},
		error : function(e) {
		    console.log("ERROR: ", e);
		}
	       });
    }

Weaknesses

Software is rarely done. If you cannot think of some way to improve any significant body of code, you are probably suffering from a lack of imagination.

Our greatest failure is that we should be using a “POST” command for some of our “GET” commands in order to more fully follow the REST paradigm. We tried, and couldn’t get it to work, and gave up for now.

Modifying Geotagtext

Geotagtext was started by two of us (Neil and Rob), partially as a learning exercise. Diego revitalized the project, also partially for learning. It is not meant to do anything specific, but to be a foundational building block of geotagging apps. Like so much software in the modern world, it happens to be composed of other foundational building blocks!

This means that you may want to modify it in any number of ways, and you are free to do so, as long as you keep your distributed modifications free and open under the terms of the AGPL. For example, you could swap Firebase out with MongoDB or some other database. You could use a different hosting service, such as AWS, instead of Heroku. You could use a different mapping tool.

You can also add features specific to your own app. For example, we have played with allowing photos to be uploaded, and extracting geotag location from the photos (most photos taken with smartphones are geotagged.) Or, you could allow more information that just the location, text and a “red, blue, green” choice to be added---you could allow for comments to be added to other users’ geotags, for example.

We invite you to make what you will of Geotagtext.


Written by robert-l.-read | Head Coach of Public Invention.
Published by HackerNoon on 2021/08/03