OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade',
+ subdomains: ['otile1', 'otile2', 'otile3', 'otile4'],
+ maxZoom: 18
+ });
+
+ var london = new L.LatLng(51.505, -0.09);
+ map.setView(london, 13)
+
+ map.addLayer(cloudmade);
+
+ // map.locate(setView=true);
+
+ map.addLayer(geojsonLayer);
+
+ // events
+ map.on('click', onMapClick);
+
+ geojsonLayer.on("featureparse", function (e) {
+ // Initialize popups with feature properties
+ if (!e.properties) {
+ e.properties = {};
+ }
+ //e.properties['_id'] = e.id;
+ e.layer.bindPopup(buildPopupContent(e.properties));
+ // Keep a reference on Marker
+ //markers[e.id] = e.layer;
+ //initMarker(e.layer, e.properties);
+ });
+}
+
+function getAllFeatures() {
+ $.getJSON(API.features, function(data, status) {
+ // geojsonLayer.on("featureparse", function (e) {
+
+ // });
+ if (data) {
+ geojsonLayer.addGeoJSON(data);
+ }
+ });
+
+}
+
+function onMapClick(e) {
+ popup = new L.Popup();
+ var template = $('#template_feature_form').html();
+ var content = Mustache.to_html(template, {'lon': e.latlng.lng.toFixed(4),
+ 'lat': e.latlng.lat.toFixed(4)});
+ popup.setLatLng(e.latlng);
+ popup.setContent(content);
+ map.openPopup(popup);
+}
+
+function addFeature(form) {
+ console.log($(form).serialize());
+ $.post(API.features, $(form).serialize());
+ map.closePopup(popup);
+ return false;
+ // $.post(API.features, {lon: latlng.lng, lat: latlng.lat});
+}
+
+function buildPopupContent(properties) {
+ var template = $('#template_feature').html();
+ return Mustache.to_html(template, properties);
+}
diff --git a/mappy/templates/map.html b/mappy/templates/map.html
new file mode 100644
index 0000000..df604f6
--- /dev/null
+++ b/mappy/templates/map.html
@@ -0,0 +1,82 @@
+
+
+
+
+ Mappy
+
+
+
+
+
+
+
+
+
+
+
+
+ {% if config.MOBILE %}
+
+
+
+ {% endif %}
+
+ {% if config.MOBILE and config.DEBUG and config.JSCONSOLE_KEY %}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+ {% raw %}
+
+
+
Title: {{title}}
+
Info: {{info}}
+
Urgency: {{urgency}}
+
+ {% endraw %}
+
+
+
+
+
\ No newline at end of file
diff --git a/mappy/utils.py b/mappy/utils.py
new file mode 100644
index 0000000..cd3cad4
--- /dev/null
+++ b/mappy/utils.py
@@ -0,0 +1,79 @@
+import gzip as gzip2
+from cStringIO import StringIO
+from decorator import decorator
+from datetime import datetime
+
+from flask import Response, make_response, json
+from pymongo.objectid import ObjectId
+import requests
+import geojson
+
+class MongoEncoder(json.JSONEncoder):
+ def default(self, obj):
+ if hasattr(obj, isoformat):
+ return obj.isoformat()
+ elif isinstance(obj, ObjectId):
+ return str(obj)
+ else:
+ return json.JSONEncoder.default(self, obj)
+
+def features_to_geojson(fs):
+ features = []
+ for f in fs:
+ _id = f['_id']
+ geom = geojson.Point(f['pos'])
+ for k in ['_id', 'pos']: del f[k]
+ f['date_creation'] = str(f['date_creation'])
+ f = geojson.Feature(id=str(_id),
+ geometry=geom,
+ properties=f)
+ features.append(f)
+
+ if len(features) > 1:
+ feature_collection = geojson.FeatureCollection(features)
+ return geojson.dumps(feature_collection)
+ elif features:
+ return geojson.dumps(features[0])
+ else:
+ return None
+
+def broadcast_message(pub_key, sub_key, channel, msg):
+ url = "https://pubsub.pubnub.com/%s/%s/%s/%s/%s/%s/%s" % (
+ 'publish',
+ pub_key,
+ sub_key,
+ 0,
+ channel,
+ 0,
+ msg)
+
+ resp = requests.get(url)
+ return resp.ok
+
+@decorator
+def gzip(f, *args, **kwargs):
+ """GZip Flask Response Decorator."""
+
+ resp = f(*args, **kwargs)
+
+ if not isinstance(resp, Response):
+ resp = make_response(resp)
+
+ content = resp.data
+
+ gzip_buffer = StringIO()
+ gzip_file = gzip2.GzipFile(
+ mode='wb',
+ compresslevel=4,
+ fileobj=gzip_buffer
+ )
+ gzip_file.write(content)
+ gzip_file.close()
+
+ gzip_data = gzip_buffer.getvalue()
+
+ resp.data = gzip_data
+ resp.headers['Content-Encoding'] = 'gzip'
+ resp.headers['Content-Length'] = str(len(resp.data))
+
+ return resp
\ No newline at end of file
diff --git a/mappy/views.py b/mappy/views.py
new file mode 100644
index 0000000..b05730b
--- /dev/null
+++ b/mappy/views.py
@@ -0,0 +1,44 @@
+import datetime
+
+from flask import Flask, request, render_template, jsonify, make_response, abort
+from mongokit import ValidationError, RequireFieldError
+
+from .utils import gzip, broadcast_message, features_to_geojson
+from . import app, db
+
+@app.route('/')
+@gzip
+def viewmap():
+ return render_template('map.html')
+
+@app.route('/features', methods=['GET', 'POST'])
+def features():
+ if request.method == 'GET':
+ features_geojson = features_to_geojson(db.Feature.find())
+
+ if features_geojson:
+ response = make_response(features_geojson)
+ response.headers['content-type'] = 'application/json'
+ return response
+ else:
+ return make_response(None, 204)
+
+ elif request.method == 'POST':
+ feature = db.Feature()
+
+ feature['title'] = request.values.get('title')
+ feature['info'] = request.values.get('info')
+ feature['urgency'] = int(request.values.get('urgency'))
+ feature['pos'] = map(float, request.values.get('coords').split(','))
+
+ try:
+ print feature
+ feature.save()
+ except (ValidationError, RequireFieldError), e:
+ return jsonify(error=e)
+ else:
+ r = broadcast_message(app.config['PUBNUB_PUB_KEY'],
+ app.config['PUBNUB_SUB_KEY'],
+ app.config['PUBNUB_CHANNELS']['features'],
+ features_to_geojson([feature]))
+ return make_response(None, 204)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..45d9994
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,15 @@
+Flask==0.8
+Flask-Script==0.3.2
+Jinja2==2.6
+Werkzeug==0.8.3
+anyjson==0.3.1
+argparse==1.2.1
+certifi==0.0.8
+chardet==1.0.1
+decorator==3.3.2
+geojson==1.0.1
+mongokit==0.7.2
+pymongo==2.1.1
+requests==0.10.8
+simplejson==2.4.0
+wsgiref==0.1.2
diff --git a/run.py b/run.py
new file mode 100644
index 0000000..be578fb
--- /dev/null
+++ b/run.py
@@ -0,0 +1,3 @@
+from mappy import app
+
+app.run(host=app.config['HOST'], port=app.config['PORT'])
\ No newline at end of file