commit 45b9c8036daaa35ded8af0c4a933c18670d911bd Author: j Date: Tue Jan 26 14:23:03 2016 +0530 maps diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fe7fe2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +._* +*~ +*.gz +*.min.js +*.pyc +*.pyd +*.pyo +*.swp +__pycache__ +config.json diff --git a/server.py b/server.py new file mode 100755 index 0000000..a7f1577 --- /dev/null +++ b/server.py @@ -0,0 +1,138 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 + +import os +import json +import uuid + +from tornado.httpserver import HTTPServer +from tornado.ioloop import IOLoop +from tornado.web import StaticFileHandler, Application, RequestHandler +from tornado.websocket import WebSocketHandler + +import logging +logger = logging.getLogger(__name__) + +DEBUG_HTTP = False +BASE_PATH = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) +STATIC_PATH = os.path.join(BASE_PATH, 'static') +VERSION = 1 + +config = {} +users = set() +rooms = {} + +class MainHandler(RequestHandler): + def initialize(self): + pass + + def get(self, path): + path = os.path.join(STATIC_PATH, 'index.html') + with open(path) as fd: + content = fd.read() + content = content.replace('.js?0', '.js?%s' % VERSION) + content = content.replace('config = {}', 'config = %s'%json.dumps(config)) + self.set_header('Content-Type', 'text/html') + self.set_header('Content-Length', str(len(content))) + self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate') + self.set_header('Pragma', 'no-cache') + self.set_header('Expires', '0') + self.write(content) + +class SocketHandler(WebSocketHandler): + + def open(self): + id = None + while id is None or id in users: + id = str(uuid.uuid1()).split('-')[0] + users.add(id) + self.id = id + + def on_message(self, message): + try: + data = json.loads(message) + event = data[0] + room = data[1] + except: + logger.error('failed', message) + return + if event == 'join': + if not room in rooms: + rooms[room] = [] + if not self in rooms[room]: + rooms[room].append(self) + for ws in rooms[room]: + ws.post(['join', self.id]) + elif event == 'leave': + if self in rooms.get(room, []): + rooms[room].remove(self) + for ws in rooms[room]: + ws.post(['leave', self.id]) + elif event == 'update': + if self in rooms[room]: + data[1] = self.id + for ws in rooms[room]: + if ws != self: + ws.post(data) + + def on_close(self): + for room in rooms: + if self in rooms[room]: + rooms[room].remove(self) + for ws in rooms[room]: + ws.post(['leave', self.id]) + users.remove(self.id) + + def post(self, data): + try: + message = json.dumps(data) + main = IOLoop.instance() + if self.ws_connection is None: + self.on_close() + else: + main.add_callback(lambda: self.write_message(message)) + except: + logger.debug('failed to send %s %s', self, data, exc_info=True) + +def log_request(handler): + if DEBUG_HTTP: + if handler.get_status() < 400: + log_method = logger.info + elif handler.get_status() < 500: + log_method = logger.warning + else: + log_method = logger.error + request_time = 1000.0 * handler.request.request_time() + log_method("%d %s %.2fms", handler.get_status(), + handler._request_summary(), request_time) + +if __name__ == '__main__': + if DEBUG_HTTP: + log_format='%(asctime)s:%(levelname)s:%(name)s:%(message)s' + logging.basicConfig(level=logging.DEBUG, format=log_format) + + options = { + 'debug': False, + 'log_function': log_request, + 'gzip': True + } + + handlers = [ + (r'/(favicon.ico)', StaticFileHandler, { + 'path': STATIC_PATH + }), + (r'/static/(.*)', StaticFileHandler, { + 'path': STATIC_PATH + }), + (r'/ws', SocketHandler), + (r"(.*)", MainHandler), + ] + with open(os.path.join(BASE_PATH, 'config.json')) as fd: + config = json.load(fd) + address = os.environ.get('ADDRESS', '') + port = int(os.environ.get('PORT', 8081)) + http_server = HTTPServer(Application(handlers, **options)) + http_server.listen(port, address) + main = IOLoop.instance() + main.start() diff --git a/static/css/app.css b/static/css/app.css new file mode 100644 index 0000000..f219e32 --- /dev/null +++ b/static/css/app.css @@ -0,0 +1,7 @@ +#map { + position: absolute; + left: 0px; + right: 0px; + bottom: 0px; + top: 0px; +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..b8e30e7 --- /dev/null +++ b/static/index.html @@ -0,0 +1,14 @@ + + + + Open Media Library + + + + + + + + +
+ diff --git a/static/js/app.js b/static/js/app.js new file mode 100644 index 0000000..8154d74 --- /dev/null +++ b/static/js/app.js @@ -0,0 +1,183 @@ +window.app = (function() { + var that = {}, + coords, + foundLocation = false, + map, + marker, + username = 'Anonymous', + users = [], + zoomLevel = 23; + + window.onload = setup(); + document.body && window.onload(); + + function createUser(data) { + var user = { + id: data.id, + name: data.name, + color: randomColor() + }; + users.push(user); + update({ + name: username, + coords: coords + }); + return user; + } + + function getUserById(id) { + return users.filter(function(user) { + return user.id == id; + })[0]; + } + + function initMap() { + navigator.geolocation.watchPosition(updateLocation, locationError); + } + + function joinRoom(room) { + if(that.room) { + post(['leave', that.room]); + } + users.forEach(function(user) { + if (user.marker) { + map.removeLayer(user.marker); + } + }); + users = []; + that.room = room; + post(['join', that.room]); + if (coords) { + update({ + name: username, + coords: coords + }); + } else { + initMap(); + } + } + + function locationError(err) { + console.log(err); + } + + function post(data) { + that.ws.send(JSON.stringify(data)); + } + + function randomColor() { + var letters = '0123456789ABCDEF'.split(''); + var color = '#'; + for (var i = 0; i < 6; i++ ) { + color += letters[Math.floor(Math.random() * (letters.length-1))]; + } + return color; + } + + function randomName() { + var name = ''; + while(name.length < 14) { + name += String.fromCharCode('A'.charCodeAt(0) + Math.floor(Math.random() * 25)); + } + return name; + } + + function removeUser(id) { + var user = getUserById(id); + if (user) { + if (user.marker) { + map.removeLayer(user.marker); + } + users = users.filter(function(user) { + user.id != id; + }); + } + } + + function setup() { + var host = (document.location.protocol == 'http:' ? 'ws' : 'wss') + '://' + document.location.host; + that.ws = new WebSocket(host + '/ws'); + that.ws.onopen = function () { + window.onhashchange = function() { + var name = document.location.hash.slice(1); + if (name) { + document.title = '#' + name; + joinRoom(name); + } else { + document.location.href = '#' + randomName(); + } + } + window.onhashchange(); + }; + that.ws.onmessage = function (event) { + var data = JSON.parse(event.data); + console.log(data); + if (data[0] == 'leave') { + removeUser(data[1]); + } else if (data[0] == 'update') { + data[2].id = data[1]; + updateUserLocation(data[2]); + } + }; + that.ws.onclose = function (event) { + setTimeout(function() { + setup(); + }, 1000); + }; + } + + function update(data) { + post(['update', that.room, data]); + } + + function updateLocation(loc) { + console.log(loc); + coords = [loc.coords.latitude, loc.coords.longitude]; + var options = { + title: username + }; + if (!foundLocation) { + if (!map) { + L.mapbox.accessToken = config.mapboxAccessToken; + map = L.mapbox.map('map', 'mapbox.streets') + .setView(coords, zoomLevel); + } + marker = L.marker(coords, options) + .bindPopup(options.title) + .addTo(map); + map.setView(coords, zoomLevel); + foundLocation = true; + } else { + map.removeLayer(marker); + marker = L.marker(coords, options) + .bindPopup(options.title) + .addTo(map); + } + update({ + name: username, + coords: coords + }); + } + + function updateUserLocation(data) { + var user = getUserById(data.id); + if (!user) { + user = createUser(data); + } + var options = { + 'title': user.name, + 'icon': L.mapbox.marker.icon({ + 'marker-color': user.color + }) + }; + user.coords = data.coords; + if (user.marker) { + map.removeLayer(user.marker); + } + user.marker = L.marker(user.coords, options) + .bindPopup(user.name) + .addTo(map); + } + + return that; +})();