added websocket spike

This commit is contained in:
Simon Tokumine 2012-04-10 12:53:38 +01:00
parent 0264d9daaa
commit ee026ee212
8 changed files with 762 additions and 0 deletions

56
spike/app.js Normal file
View File

@ -0,0 +1,56 @@
var express = require('express')
, app = express.createServer(
express.logger({
buffer: true,
format: '[:date] :req[X-Real-IP] \033[90m:method\033[0m \033[36m:req[Host]:url\033[0m \033[90m:status :response-time ms -> :res[Content-Type]\033[0m'
}))
, Step = require('step')
, _ = require('underscore');
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));
app.enable('jsonp callback');
var io = require('socket.io');
io = io.listen(app);
io.configure('development', function(){
io.set('log level', 1);
io.set('origins', '*:*');
});
app.listen(8080);
// hacked postgres setup
//var pg = require('pg');
var pg = require('pg').native //native libpq bindings = `
var conString = "tcp://postgres@localhost/cartodb_dev_user_2_db";
var client = new pg.Client(conString);
client.connect();
io.sockets.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
socket.on('sql_query', function(data){
var query = client.query(data.sql);
var id = data.id;
query.on('row', function(row) {
socket.emit("sql_result", {r:row, id:id, state:1})
});
query.on('end',function(){
socket.emit("sql_result", {id:id, state:0});
});
query.on('error', function(row){
socket.emit("sql_result", {r:row, id:id, state:-1})
});
});
});

View File

@ -0,0 +1,150 @@
function Point(x, y) {
this.x = x;
this.y = y;
}
/** return a copy of this point with coordinates as int */
Point.prototype.floor = function() {
return new Point(this.x>>0, this.y>>0);
}
function LatLng(lat, lng) {
this.lat = lat;
this.lng = lng;
}
LatLng.prototype.clone = function() {
return new LatLng(this.lat, this.lng);
}
var TILE_SIZE = 256;
MercatorProjection.prototype.TILE_SIZE = TILE_SIZE;
function bound(value, opt_min, opt_max) {
if (opt_min != null) value = Math.max(value, opt_min);
if (opt_max != null) value = Math.min(value, opt_max);
return value;
}
function degreesToRadians(deg) {
return deg * (Math.PI / 180);
}
function radiansToDegrees(rad) {
return rad / (Math.PI / 180);
}
function MercatorProjection() {
this.pixelOrigin_ = new Point(TILE_SIZE / 2,
TILE_SIZE / 2);
this.pixelsPerLonDegree_ = TILE_SIZE / 360;
this.pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI);
}
MercatorProjection.prototype.fromLatLngToPixel = function(latLng, zoom) {
var p = this.fromLatLngToPoint(latLng);
return this.toPixelCoordinate(p, zoom);
};
MercatorProjection.prototype.fromLatLngToPoint = function(latLng,
opt_point) {
var me = this;
var point = opt_point || new Point(0, 0);
var origin = me.pixelOrigin_;
point.x = origin.x + latLng.lng * me.pixelsPerLonDegree_;
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
// 89.189. This is about a third of a tile past the edge of the world
// tile.
var siny = bound(Math.sin(degreesToRadians(latLng.lat)), -0.9999,
0.9999);
point.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) *
-me.pixelsPerLonRadian_;
return point;
};
MercatorProjection.prototype.fromPointToLatLng = function(point) {
var me = this;
var origin = me.pixelOrigin_;
var lng = (point.x - origin.x) / me.pixelsPerLonDegree_;
var latRadians = (point.y - origin.y) / -me.pixelsPerLonRadian_;
var lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) -
Math.PI / 2);
return new LatLng(lat, lng);
};
MercatorProjection.prototype.tileBBox = function(x, y, zoom) {
var numTiles = 1 << zoom;
var inc = TILE_SIZE/numTiles;
var px = x*TILE_SIZE/numTiles;
var py = y*TILE_SIZE/numTiles;
return [
this.fromPointToLatLng(new Point(px, py + inc)),
this.fromPointToLatLng(new Point(px + inc, py))
];
};
MercatorProjection.prototype.tilePoint = function(x, y, zoom) {
var numTiles = 1 << zoom;
var px = x*TILE_SIZE;
var py = y*TILE_SIZE;
return [px, py];
}
MercatorProjection.prototype.fromPixelToLatLng = function(pixel, zoom) {
var numTiles = 1 << zoom;
var p = new Point(
pixel.x/numTiles,
pixel.y/numTiles);
return this.fromPointToLatLng(p);
}
MercatorProjection.prototype.toPixelCoordinate = function(worldCoordinate, zoom) {
var numTiles = 1 << zoom;
return new Point(
worldCoordinate.x * numTiles,
worldCoordinate.y * numTiles);
}
MercatorProjection.prototype.latLngToTilePoint = function(latLng, x, y, zoom) {
var numTiles = 1 << zoom;
var projection = this;
var worldCoordinate = projection.fromLatLngToPoint(latLng);
var pixelCoordinate = new Point(
worldCoordinate.x * numTiles,
worldCoordinate.y * numTiles);
var tp = this.tilePoint(x, y, zoom);
return new Point(
Math.floor(pixelCoordinate.x - tp[0]),
Math.floor(pixelCoordinate.y - tp[1]));
}
MercatorProjection.prototype.pixelToTile = function(pixelCoordinate) {
return new Point(
Math.floor(pixelCoordinate.x / TILE_SIZE),
Math.floor(pixelCoordinate.y / TILE_SIZE));
};
MercatorProjection.prototype.pointToTile = function(point, zoom) {
var numTiles = 1 << zoom;
var pixelCoordinate = new Point(
point.x * numTiles,
point.y * numTiles);
return this.pixelToTile(pixelCoordinate);
};
MercatorProjection.prototype.latLngToTile = function(latLng, zoom) {
var numTiles = 1 << zoom;
var projection = this;
var worldCoordinate = projection.fromLatLngToPoint(latLng);
var pixelCoordinate = new Point(
worldCoordinate.x * numTiles,
worldCoordinate.y * numTiles);
return new Point(
Math.floor(pixelCoordinate.x / TILE_SIZE),
Math.floor(pixelCoordinate.y / TILE_SIZE));
}

59
spike/public/index.html Normal file
View File

@ -0,0 +1,59 @@
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://localhost:8080/socket.io/socket.io.js"></script>
<link rel="stylesheet" href="http://code.leafletjs.com/leaflet-0.3.1/leaflet.css" />
<!--[if lte IE 8]>
<link rel="stylesheet" href="http://code.leafletjs.com/leaflet-0.3.1/leaflet.ie.css" />
<![endif]-->
<script src="http://code.leafletjs.com/leaflet-0.3.1/leaflet.js"></script>
<script>
$(document).ready(function () {
var map = new L.Map('map');
var cloudmade = new L.TileLayer('http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png', {
attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://cloudmade.com">CloudMade</a>',
maxZoom: 18
});
var london = new L.LatLng(51.505, -0.09);
map.setView(london, 3).addLayer(cloudmade);
var geojson = new L.GeoJSON();
map.addLayer(geojson);
var socket = io.connect('http://localhost:8080');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my:'data' });
});
$('#go').click(function () {
var sql = $("#sql_input").val();
socket.emit("sql_query", {sql:sql, id:'TUMADRE'});
});
socket.on('sql_result', function(data){
if (data.state == 1){
//console.log(data.r['the_geom']);
geojson.addGeoJSON(JSON.parse(data.r['the_geom']));
}
//$('#sql_result').append(JSON.stringify(data));
});
});
</script>
</head>
<body>
<input type="text" id="sql_input"></input>
<button type="submit" id="go">go</button>
<div id="map" style="height: 100%;width:100%;"></div>
</body>
</html>

266
spike/public/map.js Normal file
View File

@ -0,0 +1,266 @@
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
function Event() {}
Event.prototype.on = function(evt, callback) {
var cb = this.callbacks = this.callbacks || {};
var l = cb[evt] || (cb[evt] = []);
l.push(callback);
};
Event.prototype.emit = function(evt) {
var c = this.callbacks && this.callbacks[evt];
for(var i = 0; c && i < c.length; ++i) {
c[i].apply(this, Array.prototype.slice.call(arguments, 1));
}
};
function MapModel(opts) {
opts = opts || {};
this.projection = new MercatorProjection();
this.setCenter(opts.center || new LatLng(0,0));
this.setZoom(opts.zoom || 1);
}
MapModel.prototype = new Event();
MapModel.prototype.setCenter = function(center) {
this.center = new LatLng(center.lat, center.lng);
this.center_pixel = this.projection.fromLatLngToPixel(this.center, this.zoom).floor();
this.emit('center_changed', this.center);
};
MapModel.prototype.setZoom = function(zoom) {
this.zoom = zoom;
this.center_pixel = this.projection.fromLatLngToPixel(this.center, this.zoom).floor();
this.emit('zoom_changed', this.center);
};
MapModel.prototype.getCenterPixel = function() {
var center_point = this.projection.fromLatLngToPixel(this.center, this.zoom);
return center_point;
}
MapModel.prototype.getTopLeft = function(width, height) {
var center_point = this.projection.fromLatLngToPixel(this.center, this.zoom);
var widthHalf = width / 2;
var heightHalf = height / 2;
center_point.x -= widthHalf;
center_point.y -= heightHalf;
return center_point;
}
MapModel.prototype.getBBox = function(width, height) {
var center_point = this.projection.fromLatLngToPixel(this.center, this.zoom);
var widthHalf = width / 2;
var heightHalf = height / 2;
center_point.x -= widthHalf;
center_point.y += heightHalf;
var bottomleft = this.projection.fromPixelToLatLng(center_point, this.zoom);
center_point.x += width;
center_point.y -= height;
var topRight = this.projection.fromPixelToLatLng(center_point, this.zoom);
return [bottomleft, topRight]
}
/**
* return a list of tiles inside the spcified zone
* the center will be placed on the center of that zone
*/
MapModel.prototype.visibleTiles = function(width, height) {
var self = this;
var widthHalf = width / 2;
var heightHalf = height / 2;
var center_point = self.projection.fromLatLngToPixel(self.center, self.zoom);
center_point.x -= widthHalf;
center_point.y -= heightHalf;
var tile = this.projection.pixelToTile(center_point, self.zoom);
var offset_x = center_point.x%this.projection.TILE_SIZE;
var offset_y = center_point.y%this.projection.TILE_SIZE;
var num_tiles_x = Math.ceil((width + offset_x)/this.projection.TILE_SIZE);
var num_tiles_y = Math.ceil((height + offset_y)/this.projection.TILE_SIZE);
var tiles = [];
for(var i = 0; i < num_tiles_x; ++i) {
for(var j = 0; j < num_tiles_y; ++j) {
var tile_x = tile.x + i;
var tile_y = tile.y + j;
tiles.push({
x: tile_x * this.projection.TILE_SIZE,
y: tile_y * this.projection.TILE_SIZE,
zoom: self.zoom,
i: tile_x,
j: tile_y
});
}
}
return tiles;
}
function dragger(el) {
var self = this;
var dragging = false;
var x, y;
el.ontouchstart = el.onmousedown = function(e) {
dragging = true;
if (e.touches) {
var p = e.touches[0];
x = p.pageX;
y = p.pageY;
} else {
x = e.clientX;
y = e.clientY;
}
self.emit('startdrag', x, y);
};
el.ontouchmove = el.onmousemove = function(e) {
var xx, yy;
if(!dragging) return;
if (e.touches) {
var p = e.touches[0];
xx = p.pageX;
yy = p.pageY;
} else {
xx = e.clientX;
yy = e.clientY;
}
self.emit('move', xx - x, yy - y);
return false;
};
el.ontouchend = el.onmouseup = function(e) {
dragging = false;
self.emit('enddrag', x, y);
};
}
dragger.prototype = new Event();
function CanvasRenderer(el, map) {
var self = this;
this.el = el;
this.tiles = {};
this.width = el.offsetWidth >> 0;
this.height = el.offsetHeight >> 0;
var widthHalf = (this.width / 2) >> 0;
var heightHalf = (this.height / 2) >> 0;
var canvas = this.canvas = document.createElement('canvas');
canvas.style.padding = '0';
canvas.style.margin= '0';
canvas.style.position = 'absolute';
canvas.width = this.width;
canvas.height = this.height;
var context = canvas.getContext( '2d' );
context.translate( widthHalf, heightHalf );
this.context = context;
var div = document.createElement('div');
div.style.width = this.width + "px";
div.style.height= this.height + "px";
div.style.position = 'relative';
div.appendChild(canvas);
el.appendChild(div);
this.center_init = null;
this.target_center = new LatLng();
this.drag = new dragger(div);
this.drag.on('startdrag', function() {
self.center_init = map.center.clone();
});
this.drag.on('enddrag', function() {
map.emit('end_move');
});
function go_to_target() {
var c = map.center;
var t = self.target_center;
var dlat = t.lat - c.lat;
var dlon = t.lng - c.lng;
t.lat += dlat*0.0001;
t.lng += dlon*0.0001;
map.setCenter(t);
if(Math.abs(dlat) + Math.abs(dlon) > 0.001) {
requestAnimFrame(go_to_target);
} else {
//map.emit('end_move');
}
}
this.drag.on('move', function(dx, dy) {
var t = 1 << map.zoom;
var s = 1/t;
s = s/map.projection.pixelsPerLonDegree_;
self.target_center.lat = self.center_init.lat + dy*s;
self.target_center.lng = self.center_init.lng - dx*s;
requestAnimFrame(go_to_target);
});
}
CanvasRenderer.prototype.renderTile = function(tile, at) {
var self = this;
var key = at.x + '_' + at.y
if(a=self.tiles[key]) {
self.context.drawImage(a, at.x, at.y);
return;
}
//var layer = 'http://a.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{{z}}/{{x}}/{{y}}.png';
var layer = 'http://b.tiles.mapbox.com/v3/mapbox.mapbox-light/{{z}}/{{x}}/{{y}}.png64';
var url = layer.replace('{{z}}', tile.zoom).replace('{{x}}', tile.i).replace('{{y}}', tile.j);
var img = new Image();
img.src = url;
img.onload = function() {
self.context.drawImage(img, at.x, at.y);
self.tiles[key] = img;
};
}
CanvasRenderer.prototype.renderTiles = function(tiles, center) {
for(var i = 0; i < tiles.length; ++i) {
var tile = tiles[i];
var p = new Point(tile.x, tile.y);
p.x -= center.x;
p.y -= center.y;
this.renderTile(tile, p);
}
}
function Map(el, opts) {
opts = opts || {};
var self = this;
this.model = new MapModel({
center: opts.center || new LatLng(41.69, -4.83),
zoom: opts.zoom || 1
});
this.view = new CanvasRenderer(el, this.model);
/*function render() {
var tiles = self.model.visibleTiles(self.view.width, self.view.height);
self.view.renderTiles(tiles, this.center_pixel);
}
this.model.on('center_changed', render);
this.model.on('zoom_changed', render);
this.model.emit('center_changed');
*/
}

1
spike/public/run_server.sh Executable file
View File

@ -0,0 +1 @@
python -m SimpleHTTPServer 8000

111
spike/public/test.html Normal file
View File

@ -0,0 +1,111 @@
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
<style>
html, body, #map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#map {
width: 100%;
height: 100%;
}
.button {
position: absolute;
background-color: #888;
font: bold 16px Helvetica;
padding: 5px 10px;
width: 10px;
color: white;
text-decoration: none;
border-radius: 3px;
display: inline-block;
text-align: center;
}
#plus {
top: 40px;
left: 40px;
}
#minus {
top: 40px;
left: 73px;
}
.button:hover {
background-color: #BBB;
}
</style>
</head>
<body>
<div id="map"></div>
<a id="plus" href="#" class="button">+</a>
<a id="minus" href="#" class="button">-</a>
</body>
<script src="gmaps_mercator.js"></script>
<script src="map.js"></script>
<script src="http://localhost:8080/socket.io/socket.io.js"></script>
<script>
map = new Map(document.getElementById('map'), {
center: new LatLng(41.69, -4.83),
zoom: 4
});
document.getElementById('plus').onclick = function() {
map.model.setZoom(map.model.zoom + 1);
return false;
}
document.getElementById('minus').onclick = function() {
map.model.setZoom(map.model.zoom - 1);
return false;
}
function render() {
//var tiles = self.model.visibleTiles(self.view.width, self.view.height);
//self.view.renderTiles(tiles, this.center_pixel);
}
var socket = io.connect('http://localhost:8080');
function get_sql() {
var bbox = map.model.getBBox(map.view.width, map.view.height);
var sql = "select st_asgeojson(the_geom,5) as the_geom_geojson from places WHERE the_geom && ST_SetSRID(ST_MakeBox2D(";
sql += "ST_Point(" + bbox[0].lng + "," + bbox[0].lat +"),";
sql += "ST_Point(" + bbox[1].lng + "," + bbox[1].lat +")), 4326)";
return sql ;
}
map.model.on('end_move', jajaja=function() {
var s = get_sql();
//console.log(s);
//setTimeout(function() {
socket.emit("sql_query", {sql: s, id:'LOVELY'});
//}, 1000);
var tiles = map.model.visibleTiles(map.view.width, map.view.height);
map.view.renderTiles(tiles, this.center_pixel);
});
function add_point(p) {
var p = new LatLng(p[1], p[0]);
var px = map.model.projection.fromLatLngToPixel(p, map.model.zoom);
var centerpx= map.model.getCenterPixel(map.view.width, map.view.height);
map.view.context.fillStyle = '#000';
map.view.context.fillRect(px.x - centerpx.x , px.y - centerpx.y, 2, 2);
}
socket.on('sql_result', function(data){
if (data.state == 1){
geo = JSON.parse(data.r['the_geom_geojson']);
add_point(geo.coordinates);
}
});
map.model.on('zoom_changed', jajaja);
map.model.emit('center_changed');
</script>
</html>

View File

@ -0,0 +1,57 @@
describe("Event", function() {
before_each(function() {
this.evt = new Event();
});
it("call event binded", function() {
var c = 0;
function callback() {
c++;
}
this.evt.on('test', callback);
this.evt.emit('test');
assert(c == 1);
});
it("should works when call non existing event", function() {
this.evt.emit('test_no_exists');
});
});
describe("MapModel", function() {
before_each(function() {
this.map_model = new MapModel(new LatLng(0, 0));
});
it("center_changed should be called", function() {
var c = 0;
this.map_model.on('center_changed', function() {
++c;
});
this.map_model.setCenter(new LatLng(0, 0));
assert(c == 1);
});
it("zoom_changed should be called", function() {
var c = 0;
this.map_model.on('zoom_changed', function() {
++c;
});
this.map_model.setZoom(2);
assert(c == 1);
});
it("visibleTiles", function() {
var ts = this.map_model.projection.TILE_SIZE;
this.map_model.setZoom(10);
var tiles = this.map_model.visibleTiles(ts, ts);
assert(tiles.length == 4);
this.map_model.setCenter(new LatLng(0.3, 1.2));
tiles = this.map_model.visibleTiles(ts, ts);
assert(tiles.length == 4);
});
});

View File

@ -0,0 +1,62 @@
<!doctype html>
<html lang=en>
<head>
<title>quick'n'dirty js testing framework</title>
<meta charset=utf-8>
<style type="text/css" media="screen">
h1{ font: 1.5em Helvetica; }
p { font: .9em courier; padding: .5em 1em; margin: 0;}
.result { margin-top: 1px; }
.pass { color: #4F8A10; background: #DFF2BF }
.fail { color: #D8000C; background: #FFBABA }
.error{ color: white; background: red; font-weight: bold; }
.describe { border-bottom: 1px solid #ccc; padding: 20px; }
</style>
<script>
var setup_fn = null;
function describe(desc, fn) {
document.write('<div class="describe">');
document.write('<h1>' + desc +'</h1>');
fn();
document.write('</div>');
setup_fn = null;
}
function before_each(be) {
setup_fn = be;
}
var result = true;
function assert(cond, msg) {
if(result) {
if(!cond) {
result = false;
}
}
}
function it(desc, fn) {
var output = function(klass, msg){
document.write('<p class="result ' + klass + '">' + msg + '</p>')
}
try{
if (setup_fn != null)
setup_fn();
result = true;
fn()
} catch(err){ result = err }
if (typeof result === 'boolean'){
result ? output('pass', desc) : output('fail', desc)
}else{
output('error', ['ERROR:',result.message,'when testing',desc].join(' '))
}
}
</script>
<script src="../gmaps_mercator.js"> </script>
<script src="../map.js"> </script>
<script src="map.test.js"> </script>
</head>
<body></body>
</html>