Geospatial Javascript is More Powerful Than You Think
When it comes to geospatial programming, newcomers are encouraged to learn languages like Python and SQL (maybe even R). These languages are great at data processing, but they're not well-suited for creating visualizations, a key aspect of location intelligence. Instead, languages such as HTML, CSS, and Javascript create visualizations such as maps and other interactive visualizations.
It's common knowledge among GIS professionals that geospatial processing operations in web applications (reprojections, buffers, merging, or spatial joins) are in the realm of server-side tools and languages like Python and SQL. But what if I told you that powerful Javascript-based geoprocessing solutions exist today?
Atwood's Law states that anything that can be written in Javascript eventually will be written in Javascript, and geospatial tools are no exception to that rule. Projects like Turf and Mapshaper are robust and have well-written libraries that offer many geospatial vector processing operations. Raster-based solutions aren't as prevalent, but they are becoming more advanced, as demonstrated by GeoBlaze. At CartoLab, we use these tools like these regularly (disclosure: we use Python and SQL too!). The lightweight nature of these libraries, in addition to the asynchronous behavior of Javascript, makes deploying geoprocessing APIs, and other similar activities, quick and easy.
One interesting thing about Javascript-based libraries is that they can generally run both server and client-side. Whereas solutions designed with Python or SQL must always communicate with a backend, those written with Javascript libraries can run completely client-side, without data ever needing to travel to a server. There are many potential benefits to client-side geoprocessing, such as data privacy and potential cost savings by using client processing power rather than servers. We can even push the limits of processing by offloading operations to web workers so that costly operations don't freeze applications. Here's a draft example of putting Turf.js functionality into a web worker:
// Because Web Workers run in their own global scope, they are responsible for
// ensuring its dependencies are available. `importScripts` is used to import
// scripts into the global scope. `importScripts` runs synchronously.
// Logging works in web-workers. The message shows in the main thread's console.
console.log('[TurfWebWorker]: Loading Dependencies...');
importScripts('https://cdn.jsdelivr.net/npm/@turf/turf@5/turf.min.js');
console.log('[TurfWebWorker]: Turf JS loaded.');
function onError(evt) {
console.error(evt);
}
/**
* @param {MessageEvent} evt - The message event.
* @param {Object} evt.data - The message-based data.
* @param {string} evt.data.type - The type of the message sent. Must be one of
* `'request'`. (Only one type expected at the moment.)
* @param {*} evt.data.... - Remaining data based on type of message.
*/
function onMessage(evt) {
var message = evt.data;
if (message.type === 'request') {
handleRequest(message);
} else {
console.warn(
'[TurfWebWorker]: Received a message of unexpected type: "' +
message.type +
'".'
);
}
}
/**
* @param {Object} req - A request message.
* @param {string} req.action - The action of the request.
* @param {Object} req.params - The parameters based on the action.
*/
function handleRequest(req) {
var action, params;
action = req.action;
params = req.params;
console.log(
'[TurfWebWorker]: Request received: action: "' +
action +
'" params: "' +
JSON.stringify(params) +
'"'
);
switch (action) {
case 'randomData':
var randomData, geometry, count;
geometry = params.geometry;
count = params.count;
if (geometry === 'point') {
randomData = turf.randomPoint(count);
} else if (geometry === 'line') {
randomData = turf.randomLineString(count);
} else if (geometry === 'polygon') {
randomData = turf.randomPolygon(count);
} else {
console.warn('Geometry type ', geometry, ' not recognized');
}
postMessage({
type: 'response',
action: 'randomData',
data: randomData
});
break;
case 'buffer':
var buffered, distance, geojson, units;
distance = params.distance;
geojson = params.geojson;
units = params.units;
buffered = turf.buffer(geojson, distance, { units: units });
postMessage({
type: 'response',
action: 'buffer',
data: buffered
});
break;
default:
console.warn(
'[TurfWebWorker]: Received an unexpected request: "' + action + '".'
);
}
}
addEventListener('error', onError);
addEventListener('message', onMessage);
At CartoLab, we're already taking advantage of many Javascript-based tools for mapping and GIS operations, and we are excited about what the future has in store for geospatial Javascript.