writing portable open source mapping applications
with
and
the Cloud
"what is it made of?"
The cloud is:
inputs:
Result:
inputs:
Result:
Providing standards-based, open source workflows that answer the question of:
"How do I
my solutions
on an
Open Cloud?"
Open platforms provide a peaceful environment for Developers AND Operations teams to work together in
Automatic support for dependency resolution using standard packaging, native to each language:
gems (ruby), eggs (python), and npm modules (node.js)
For nodejs:
npm install pg-query --save
brianc's pg-query
module makes working with PG exceedingly simple
Just map your queries to their related callback functions.
Resolve dependencies:
npm install
Fire up a local server:
npm start
Include a link to Leaflet's css stylesheet and javascript code in your index.html file:
<link rel="stylesheet" href="//cdn.leafletjs.com/leaflet-0.5.1/leaflet.css" />
<script src="//cdn.leafletjs.com/leaflet-0.5.1/leaflet.js"></script>
Initialize the map:
var map = L.map('map').setView([37.8, -122.3], 10);
var markerLayerGroup = L.layerGroup().addTo(map);
L.tileLayer('http://{s}.tile.stamen.com/terrain/{z}/{x}/{y}.png', {
maxZoom: 18,
minZoom: 5,
attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>.'
}).addTo(map);
Update the Map on load, drag, or zoom:
function getPins(e){
bounds = map.getBounds();
url = "parks/within?lat1=" + bounds.getSouthWest().lat + "&lon1=" + bounds.getSouthWest().lng + "&lat2=" + bounds.getNorthEast().lat + "&lon2=" + bounds.getNorthEast().lng;
$.get(url, pinTheMap, "json")
}
function pinTheMap(data){
//clear the current pins
map.removeLayer(markerLayerGroup);
//add the new pins
var markerArray = new Array(data.length)
for (var i = 0; i < data.length; i++){
park = data[i];
markerArray[i] = L.marker([park.lat, park.lon]).bindPopup(park.name);
}
markerLayerGroup = L.layerGroup(markerArray).addTo(map);
}
map.on('dragend', getPins);
map.on('zoomend', getPins);
map.whenReady(getPins);
var config = require('config'),
restify = require('restify'),
fs = require('fs'),
db = require('./bin/db.js')
var app = restify.createServer()
app.use(restify.queryParser())
app.use(restify.CORS())
app.use(restify.fullResponse())
// Routes
app.get('/parks/within', db.selectBox);
app.get('/parks', db.selectAll);
// Static assets
app.get(/\/(css|js|img)\/?.*/, restify.serveStatic({directory: './static/'}));
app.get('/', function (req, res, next)
{
var data = fs.readFileSync(__dirname + '/index.html');
res.status(200);
res.header('Content-Type', 'text/html');
res.end(data.toString().replace(/host:port/g, req.header('Host')));
});
app.listen(config.port, config.ip, function () {
console.log( "Listening on " + config.ip + ", port " + config.port )
});
rhc cartridge add postgres-8.4
or
rhc cartridge add postgres-9.2
done!
function select_box(req, res, next){
//clean our input variables before forming our DB query:
var query = req.query;
var limit = (typeof(query.limit) !== "undefined") ? query.limit : 40;
if(!(Number(query.lat1)
&& Number(query.lon1)
&& Number(query.lat2)
&& Number(query.lon2)
&& Number(limit)))
{
res.send(500, {http_status:400,error_msg: "this endpoint requires two pair of lat, long coordinates: lat1 lon1 lat2 lon2\na query 'limit' parameter can be optionally specified as well."});
return console.error('could not connect to postgres', err);
}
pg('SELECT gid,name,ST_X(the_geom) as lon,ST_Y(the_geom) as lat FROM ' + table_name+ ' t WHERE ST_Intersects( ST_MakeEnvelope('+query.lon1+", "+query.lat1+", "+query.lon2+", "+query.lat2+", 4326), t.the_geom) LIMIT "+limit+';', function(err, rows, result){
if(err) {
res.send(500, {http_status:500,error_msg: err})
return console.error('error running query', err);
}
res.send(rows);
return rows;
})
};
rhc app show rss
Or, while connected over ssh:
env | grep DB
OPENSHIFT_POSTGRESQL_DB_PASSWORD=lXcFVx4hIZgR
OPENSHIFT_POSTGRESQL_DB_SOCKET=/var/lib/openshift/523672f7e0b8cd02d70003bc/postgresql/socket/
OPENSHIFT_POSTGRESQL_DB_HOST=127.7.8.130
OPENSHIFT_POSTGRESQL_DB_PID=/var/lib/openshift/523672f7e0b8cd02d70003bc/postgresql/pid/postgres.pid
OPENSHIFT_POSTGRESQL_DB_USERNAME=adminpahue6e
OPENSHIFT_POSTGRESQL_DB_URL=postgresql://adminpahue6e:lXcFVx4hIZgR@127.7.8.130:5432
OPENSHIFT_POSTGRESQL_DB_PORT=5432
OPENSHIFT_POSTGRESQL_DB_LOG_DIR=/var/lib/openshift/523672f7e0b8cd02d70003bc/postgresql/log/
Persist configuration details,
while keeping your source clean:
module.exports = {
port: process.env.PORT || process.env.OPENSHIFT_NODEJS_PORT || 3000,
ip: process.env.OPENSHIFT_NODEJS_IP || '127.0.0.1',
pg_config: process.env.OPENSHIFT_POSTGRESQL_DB_URL || 'postgresql://127.0.0.1:5432',
table_name: process.env.OPENSHIFT_APP_NAME || process.env.PG_MAP_TABLE_NAME || 'parks'
}
Listing your custom env vars:
cd myapp
rhc env list
Setting a variable:
rhc env set SECRET_TOKEN="a1fdacc3b1d14d6a92ed1219ed304d02529f535085262a90c39f072ef6de0ee9fe3a3d0194f02a2a8eb3"
Help with configuration:
rhc help env
Or, supply additional keys during the app creation process:
rhc app create hydrant ruby-1.9 postgresql-8.4 --from=code=http://github.com/ryanj/adopt-a-hydrant.git --env SECRET_TOKEN="YOUR_SECRET_TOKEN"
https://github.com/ryanj/restify-postGIS/tree/master/.openshift/action_hooks
var config = require('config'),
pg = require('pg-query')
var pg_config = config.pg_config,
table_name = config.table_name;
pg.connectionParameters = pg_config + '/' +table_name;
var points = require('../parkcoord.json');
function initDB(){
pg('CREATE EXTENSION postgis;', createDBSchema);
}
function createDBSchema(err, rows, result) {
if(err && err.code == "ECONNREFUSED"){
return console.error("DB connection unavailable, see README notes for setup assistance\n", err);
}
var query = "CREATE TABLE "+table_name+
" ( gid serial NOT NULL, name character varying(240), the_geom geometry, CONSTRAINT "+table_name+ "_pkey PRIMARY KEY (gid), CONSTRAINT enforce_dims_geom CHECK (st_ndims(the_geom) = 2), CONSTRAINT enforce_geotype_geom CHECK (geometrytype(the_geom) = 'POINT'::text OR the_geom IS NULL),CONSTRAINT enforce_srid_geom CHECK (st_srid(the_geom) = 4326) ) WITH ( OIDS=FALSE );";
pg(query, addSpatialIndex);
};
function addSpatialIndex(err, rows, result) {
pg("CREATE INDEX "+table_name+"_geom_gist ON "+table_name+" USING gist (the_geom);", importMapPoints);
}
function importMapPoints(err, rows, result) {
var query = "Insert into "+table_name+" (name, the_geom) VALUES " + points.map(mapPinSQL).join(",") + ';';
pg(query, function(err, rows, result) {
var response = 'Data import completed!';
return response;
});
};
function mapPinSQL(pin) {
var query = '';
if(typeof(pin) == 'object'){
query = "('" + pin.Name.replace(/'/g,"''") + "', ST_GeomFromText('POINT(" + pin.pos[0] +" "+ pin.pos[1] + " )', 4326))";
}
return query;
};
Spin up a fresh app from the command line:
rhc app create myapp cart1 cart2 --from-code=http://github.com/user/repo.git
For our nodejs example:
rhc app create nodegis nodejs-0.10 postgres-9.2 --from-code=http://github.com/ryanj/restify-postGIS.git
Live Result
~ related content ~
Customizing OpenShift's Web-based App Creation Workflow
Instant Hosting of Open Source Projects with GitHub-style Ribbons
aka, where is my pg_hba.conf, and postgresql.conf?
Some PG tuning notes were recently posted in the OpenShift Online release announcement for April 2014
OPENSHIFT_POSTGRESQL_SHARED_BUFFERS
OPENSHIFT_POSTGRESQL_MAX_CONNECTIONS
More general tuning advice: https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server
Take a look at the latest from CrunchyData!
See their release announcement for additional details: crunchydatasolutions.com
Including support for LB, geographic failover, Master-slave replication, and more!
Check out the upstream source:
OpenShift Origin
Try our hosted solution (3 apps free):
OpenShift Online
Request an evaluation for:
OpenShift Enterprise
See my post on this topic for more info: Instant Mapping Applications with PostGIS and Nodejs
Link to these slides: bit.ly/1tpBqGA
See you next time!
--ryanj