F Setting up an SQL API
Last updated: 2018-11-11 20:44:59
F.1 PostgreSQL and PostGIS
The first step is to install the PostgreSQL database and enable the PostGIS extension.
The following instructions are based on -
http://trac.osgeo.org/postgis/wiki/UsersWikiPostGIS23UbuntuPGSQL96Apt
And suitable for Ubuntu 16.04 xenial.
First, add the respository to sources.list
-
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt xenial-pgdg main" >> /etc/apt/sources.list'
wget --quiet -O - http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update
Install the required packages -
sudo apt-get install postgresql-9.6
sudo apt-get install postgresql-9.6-postgis-2.3 postgresql-contrib-9.6 postgresql-9.6-postgis-scripts
sudo apt-get install postgis
# sudo apt-get install postgresql-9.6-pgrouting
Login to the database -
sudo -u postgres psql
Enable adminpack -
CREATE EXTENSION adminpack;
\q
Login once again -
sudo -u postgres psql
Create a database named gisdb
and enable extensions -
CREATE DATABASE gisdb;
\connect gisdb;
CREATE SCHEMA postgis;
ALTER DATABASE gisdb SET search_path=public, postgis, contrib;
<!-- \connect gisdb; -->
CREATE EXTENSION postgis SCHEMA postgis;
CREATE EXTENSION postgis_sfcgal SCHEMA postgis;
SELECT postgis_full_version();
<!-- CREATE EXTENSION pgrouting; -->
<!-- SELECT * FROM pgr_version(); -->
\q
Login -
sudo -u postgres psql
Open access to clients -
ALTER SYSTEM SET listen_addresses='*';
\q
Edit the pg_hba.conf
file -
sudo gedit /etc/postgresql/9.6/main/pg_hba.conf
Add this line -
host all all 0.0.0.0/0 md5
Then edit the postgresql.conf
file -
sudo gedit /etc/postgresql/9.6/main/postgresql.conf
And add this line -
listen_addresses = '*'
Restart the database -
sudo service postgresql restart
Login to the database -
sudo -u postgres psql
Create a superuser -
CREATE ROLE mysuperuser LOGIN PASSWORD 'whatever' SUPERUSER;
\q
Login as the new user -
psql -h localhost -U mysuperuser gisdb
https://dba.stackexchange.com/questions/35316/why-is-a-new-user-allowed-to-create-a-table
REVOKE ALL ON schema public FROM public;
GRANT ALL ON schema public TO mysuperuser;
F.2 Adding data
Now that the database is set, you need to add some data. There are many ways to do that. We will use R.
Read the plants.geojson
file (download) and import it to the database from R -
library(sf)
library(RPostgreSQL)
# Connect
con = dbConnect(PostgreSQL(), dbname = "gisdb", host = "localhost", port = 5432, user = "mysuperuser", password = "whatever")
# Import table to database
dat = st_read("plants.geojson", stringsAsFactors = FALSE)
st_write_db(con, dat, "plants", drop = TRUE)
# Disconnect
dbDisconnect(con)
F.3 Testing
Let’s test if the database works properly.
While logged in to the database, run the following query -
select id, name_lat, ST_AsText(geometry) from plants limit 5;
If all works well you should see a result such as the following -
id | name_lat | st_astext
--------+----------------+------------------------------------------
321432 | Iris haynei | POINT(35.6797610296996 32.7701334254926)
321433 | Iris haynei | POINT(35.6540048061212 32.7413715482281)
321456 | Iris atrofusca | POINT(35.1933666886313 31.447109876834)
321457 | Iris atrofusca | POINT(35.1891423219429 31.5147536901285)
321459 | Iris vartanii | POINT(35.1396955200257 31.4741486701326)
(5 rows)
F.4 Adding read-only user
CREATE ROLE readonlyuser WITH LOGIN PASSWORD 'readonlyuser1'
NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION VALID UNTIL 'infinity';
Assign permission to this read only user:
GRANT CONNECT ON DATABASE gisdb TO readonlyuser;
GRANT USAGE ON SCHEMA public TO readonlyuser;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonlyuser;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO readonlyuser;
Possibly need to update search path -
select e.extname,n.*
from pg_extension e, pg_namespace n
where e.extnamespace = n.oid and e.extname='postgis';
show search_path;
ALTER USER readonlyuser SET search_path TO "$user", public, postgis;
(https://serverfault.com/questions/60500/crate-a-new-read-only-user-in-postgres)
And/or move the postgis extension to shema public
-
UPDATE pg_extension
SET extrelocatable = TRUE
WHERE extname = 'postgis';
ALTER EXTENSION postgis
SET SCHEMA public;
(https://postgis.net/2017/11/07/tip-move-postgis-schema/)
Now, connect as the new user -
psql -h localhost -U readonlyuser gisdb
And try -
DROP TABLE PLANTS;
You should see an error -
ERROR: must be owner of relation plants
Also -
delete from plants where name_lat='Iris haynei';
ERROR: permission denied for relation plants
F.5 Node.js
Next, we need to install Node.js.
Run the following commands to get the required packages -
sudo apt-get update
sudo apt-get install nodejs
sudo apt-get install nodejs-legacy
sudo apt-get install npm
Check that Node.js is installed with the following command, which sould print the installed version -
nodejs -v
F.6 Server
We will use a Node.js script to create an SQL API for our database.
This is based on the postgis-preview example from here -
https://chriswhong.com/data-visualization/introducing-postgis-preview/
Create a new folder for your server app. Run the following commands to initialize the server and download the required dependencies -
mkdir node
cd node
npm init
npm install express --save
npm install pg-promise --save
npm install dbgeo --save
Now, inside the directory creare a new file named index.js
and paste the following content inside it -
nano index.js
// Dependencies
var express = require("express"),
pgp = require("pg-promise")(),
dbgeo = require("dbgeo");
// CORS
var allowCrossDomain = function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET");
res.header("Access-Control-Allow-Headers", "Content-Type");
next();
}
// Create express app and prepare db connection
var app = express(),
port = 4000,
connectionString = "postgres://readonlyuser:readonlyuser1@localhost:5432/gisdb",
db = pgp(connectionString);
app.use(allowCrossDomain);
// Expose sql endpoint, grab query as URL parameter and send it to the database
app.get("/sql", function (req, res) {
var sql = req.query.q;
console.log("Executing SQL: " + sql);
//query using pg-promise
db.any(sql)
.then(function (data) { //use dbgeo to convert WKB from PostGIS into topojson
return dbGeoParse(data);
})
.then(function (data) {
res.send(data);
})
.catch(function (err) { //send the error message if the query didn't work
var msg = err.message || err;
console.log("ERROR:", msg);
res.send({
error: msg
});
});
});
// Parse to GeoJSON
function dbGeoParse(data) {
return new Promise(function (resolve, reject) {
dbgeo.parse(data, {
outputFormat: "geojson"
}, function (err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
// Start the server
var server = app.listen(port, "0.0.0.0", function () {
var host = server.address().address;
var port = server.address().port;
console.log("App listening at http://%s:%s", host, port);
});
While still inside the directory, type the following command to start the server -
node index.js
May need to install a later version of node -
https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-16-04
F.7 Testing
Enter the following URL in your browser -
http://localhost:8000/sql?q=select id, name_lat, geometry AS geom from plants limit 2
Or on the test DigitalOcean server -
http://159.89.13.241:8000/sql?q=select id, name_lat, geometry AS geom from plants limit 2
If the SQL API is working properly, you should see the following GeoJSON content in the response -
{
"type":"FeatureCollection",
"features":[
{
"type":"Feature",
"geometry":{
"type":"Point",
"coordinates":[
35.389282569732835,
31.579570927419955
]
},
"properties":{
"id":326579,
"lat":"Cyperus jeminicus"
}
},
{
"type":"Feature",
"geometry":{
"type":"Point",
"coordinates":[
35.44762877883449,
31.739893751235616
]
},
"properties":{
"id":338947,
"lat":"Cyperus jeminicus"
}
}
]
}
F.8 Setup for production
Bassett, Lindsay. 2015. Introduction to Javascript Object Notation: A to-the-Point Guide to Json. “ O’Reilly Media, Inc.”
Crickard III, Paul. 2014. Leaflet. Js Essentials. Packt Publishing Ltd.
Dent, Borden D, Jeffrey S Torguson, and Thomas W Hodler. 2008. Cartography: Thematic Map Design. 6th ed. WCB/McGraw-Hill Boston.
Dincer, Alper, and Balkan Uraz. 2013. Google Maps Javascript Api Cookbook. Packt Publishing Ltd.
Farkas, Gábor. 2016. Mastering Openlayers 3. Packt Publishing Ltd.
Gratier, Thomas, Paul Spencer, and Erik Hazzard. 2015. OpenLayers 3: Beginner’s Guide. Packt Publishing Ltd.
Langley, Peter J, and Antonio Santiago Perez. 2016. OpenLayers 3. X Cookbook. 2nd ed. Packt Publishing Ltd.
Murray, Scott. 2017. Interactive Data Visualization for the Web: An Introduction to Designing with D3. “O’Reilly Media, Inc.”
Newton, Thomas, and Oscar Villarreal. 2014. Learning D3. Js Mapping. Packt Publishing Ltd.
Obe, R, and L Hsu. 2015. PostGIS in Action. 2nd ed. Manning Publications Co., USA. http://www.postgis.us/.
Rubalcava, Rene. 2015. ArcGIS Web Development. Manning Publications Company.
Tufte, Edward. 2001. The Quantitative Display of Information. 2nd ed. Graphics Press, Cheshire, Connecticut.