maps
This commit is contained in:
commit
45b9c8036d
5 changed files with 352 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
._*
|
||||||
|
*~
|
||||||
|
*.gz
|
||||||
|
*.min.js
|
||||||
|
*.pyc
|
||||||
|
*.pyd
|
||||||
|
*.pyo
|
||||||
|
*.swp
|
||||||
|
__pycache__
|
||||||
|
config.json
|
138
server.py
Executable file
138
server.py
Executable file
|
@ -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()
|
7
static/css/app.css
Normal file
7
static/css/app.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#map {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
top: 0px;
|
||||||
|
}
|
14
static/index.html
Normal file
14
static/index.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Open Media Library</title>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<script>var config = {};</script>
|
||||||
|
<script src="https://api.mapbox.com/mapbox.js/v2.2.2/mapbox.js" type="text/javascript"></script>
|
||||||
|
<script src="/static/js/app.js?0" type="text/javascript"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://api.mapbox.com/mapbox.js/v2.2.2/mapbox.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/app.css" />
|
||||||
|
<meta name="google" value="notranslate"/>
|
||||||
|
</head>
|
||||||
|
<body><div id="map"></div></body>
|
||||||
|
</html>
|
183
static/js/app.js
Normal file
183
static/js/app.js
Normal file
|
@ -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;
|
||||||
|
})();
|
Loading…
Reference in a new issue