add alfred support and dynamic map creation
|
@ -1,2 +1,3 @@
|
||||||
|
build
|
||||||
__pycache__
|
__pycache__
|
||||||
.*.swp
|
.*.swp
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
## Installation
|
||||||
|
```
|
||||||
|
./install.sh
|
||||||
|
systemctl enable uwsgi-ffmap
|
||||||
|
systemctl enable uwsgi-tiles-links_and_routers
|
||||||
|
systemctl enable uwsgi-tiles-hoods
|
||||||
|
systemctl start uwsgi-ffmap
|
||||||
|
systemctl start uwsgi-tiles-links_and_routers
|
||||||
|
systemctl start uwsgi-tiles-hoods
|
||||||
|
# Then apply NGINX Config
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debian Dependencies
|
||||||
|
```
|
||||||
|
apt-get install python python3 mongodb python3-requests python3-lxml python3-pip python3-flask python3-dateutil python3-numpy python3-scipy python-mapnik python3-pip uwsgi-plugin-python uwsgi-plugin-python3 nginx
|
||||||
|
pip3 install pymongo
|
||||||
|
git clone https://github.com/asdil12/tilelite
|
||||||
|
cd tilelite
|
||||||
|
python setup.py install
|
||||||
|
```
|
||||||
|
|
||||||
|
## NGINX Config
|
||||||
|
```
|
||||||
|
...
|
||||||
|
location / {
|
||||||
|
include uwsgi_params;
|
||||||
|
uwsgi_pass 127.0.0.1:3031;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /tiles/links_and_routers {
|
||||||
|
include uwsgi_params;
|
||||||
|
uwsgi_pass 127.0.0.1:3032;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /tiles/hoods {
|
||||||
|
include uwsgi_params;
|
||||||
|
uwsgi_pass 127.0.0.1:3033;
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
|
@ -0,0 +1 @@
|
||||||
|
uwsgi_python3 -w ffmap.web.application:app --http-socket :9090 --catch-exceptions
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
import gzip
|
import gzip
|
||||||
|
|
|
@ -8,7 +8,7 @@ db = client.freifunk
|
||||||
# create db indexes
|
# create db indexes
|
||||||
db.hoods.create_index([("position", "2dsphere")])
|
db.hoods.create_index([("position", "2dsphere")])
|
||||||
|
|
||||||
db.hoods.insert_many([
|
hoods = [
|
||||||
{
|
{
|
||||||
"keyxchange_id": 1,
|
"keyxchange_id": 1,
|
||||||
"name": "default",
|
"name": "default",
|
||||||
|
@ -61,4 +61,7 @@ db.hoods.insert_many([
|
||||||
"name": "HassbergeSued",
|
"name": "HassbergeSued",
|
||||||
"net": "10.50.60.0/22",
|
"net": "10.50.60.0/22",
|
||||||
"position": {"type": "Point", "coordinates": [10.568013390003, 50.08]}
|
"position": {"type": "Point", "coordinates": [10.568013390003, 50.08]}
|
||||||
}])
|
}]
|
||||||
|
|
||||||
|
for hood in hoods:
|
||||||
|
db.hoods.insert_one(hood)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import routers
|
||||||
|
import chipsets
|
||||||
|
import hoods
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from pymongo import MongoClient
|
||||||
|
client = MongoClient()
|
||||||
|
|
||||||
|
db = client.freifunk
|
||||||
|
|
||||||
|
# create db indexes
|
||||||
|
db.routers.create_index("status")
|
||||||
|
db.routers.create_index("last_contact")
|
||||||
|
db.routers.create_index("netifs.mac")
|
||||||
|
db.routers.create_index([("position", "2dsphere")])
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from pymongo import MongoClient
|
||||||
|
|
||||||
|
class FreifunkDB(object):
|
||||||
|
client = None
|
||||||
|
db = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def handle(cls):
|
||||||
|
if not cls.client:
|
||||||
|
cls.client = MongoClient()
|
||||||
|
if not cls.db:
|
||||||
|
cls.db = cls.client.freifunk
|
||||||
|
return cls.db
|
|
@ -0,0 +1,114 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '..'))
|
||||||
|
|
||||||
|
from ffmap.dbtools import FreifunkDB
|
||||||
|
|
||||||
|
import math
|
||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import Voronoi
|
||||||
|
|
||||||
|
db = FreifunkDB().handle()
|
||||||
|
|
||||||
|
CONFIG = {
|
||||||
|
"csv_dir": "/var/lib/ffmap/csv"
|
||||||
|
}
|
||||||
|
|
||||||
|
def touch(fname, times=None):
|
||||||
|
with open(fname, 'a'):
|
||||||
|
os.utime(fname, times)
|
||||||
|
|
||||||
|
def update_mapnik_csv():
|
||||||
|
with open(os.path.join(CONFIG["csv_dir"], "routers.csv"), "w") as csv:
|
||||||
|
csv.write("lng,lat,status\n")
|
||||||
|
for router in db.routers.find({"position.coordinates": {"$exists": True}}):
|
||||||
|
csv.write("%f,%f,%s\n" % (
|
||||||
|
router["position"]["coordinates"][0],
|
||||||
|
router["position"]["coordinates"][1],
|
||||||
|
router["status"]
|
||||||
|
))
|
||||||
|
|
||||||
|
with open(os.path.join(CONFIG["csv_dir"], "links.csv"), "w") as csv:
|
||||||
|
csv.write("WKT,quality\n")
|
||||||
|
for router in db.routers.find({"position.coordinates": {"$exists": True}, "neighbours": {"$exists": True}}):
|
||||||
|
for neighbour in router["neighbours"]:
|
||||||
|
if "position" in neighbour:
|
||||||
|
csv.write("\"LINESTRING (%f %f,%f %f)\",%i\n" % (
|
||||||
|
router["position"]["coordinates"][0],
|
||||||
|
router["position"]["coordinates"][1],
|
||||||
|
neighbour["position"]["coordinates"][0],
|
||||||
|
neighbour["position"]["coordinates"][1],
|
||||||
|
neighbour["quality"]
|
||||||
|
))
|
||||||
|
|
||||||
|
with open(os.path.join(CONFIG["csv_dir"], "hood-points.csv"), "w", encoding="UTF-8") as csv:
|
||||||
|
csv.write("lng,lat,name\n")
|
||||||
|
for hood in db.hoods.find({"position": {"$exists": True}}):
|
||||||
|
csv.write("%f,%f,\"%s\"\n" % (
|
||||||
|
hood["position"]["coordinates"][0],
|
||||||
|
hood["position"]["coordinates"][1],
|
||||||
|
hood["name"]
|
||||||
|
))
|
||||||
|
|
||||||
|
with open(os.path.join(CONFIG["csv_dir"], "hoods.csv"), "w") as csv:
|
||||||
|
EARTH_RADIUS = 6378137.0
|
||||||
|
|
||||||
|
def merc_sphere(lng, lat):
|
||||||
|
x = math.radians(lng) * EARTH_RADIUS
|
||||||
|
y = math.log(math.tan(math.pi/4 + math.radians(lat)/2)) * EARTH_RADIUS
|
||||||
|
return (x,y)
|
||||||
|
|
||||||
|
def merc_sphere_inv(x, y):
|
||||||
|
lng = math.degrees(x / EARTH_RADIUS)
|
||||||
|
lat = math.degrees(2*math.atan(math.exp(y / 6378137.0)) - math.pi/2)
|
||||||
|
return (lng,lat)
|
||||||
|
|
||||||
|
csv.write("WKT\n")
|
||||||
|
hoods = []
|
||||||
|
for hood in db.hoods.find({"position": {"$exists": True}}):
|
||||||
|
# convert coordinates info marcator sphere as voronoi doesn't work with lng/lat
|
||||||
|
x, y = merc_sphere(hood["position"]["coordinates"][0], hood["position"]["coordinates"][1])
|
||||||
|
hoods.append([x, y])
|
||||||
|
|
||||||
|
points = np.array(hoods)
|
||||||
|
vor = Voronoi(points)
|
||||||
|
#mp = voronoi_plot_2d(vor)
|
||||||
|
#mp.show()
|
||||||
|
|
||||||
|
lines = [vor.vertices[line] for line in vor.ridge_vertices if -1 not in line]
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
x = [line[0][0], line[1][0]]
|
||||||
|
y = [line[0][1], line[1][1]]
|
||||||
|
for i in range(len(x)-1):
|
||||||
|
# convert mercator coordinates back into lng/lat
|
||||||
|
lng1, lat1 = merc_sphere_inv(x[i], y[i])
|
||||||
|
lng2, lat2 = merc_sphere_inv(x[i+1], y[i+1])
|
||||||
|
csv.write("\"LINESTRING (%f %f,%f %f)\"\n" % (lng1, lat1, lng2, lat2))
|
||||||
|
|
||||||
|
ptp_bound = np.array(merc_sphere(180, 360))
|
||||||
|
center = vor.points.mean(axis=0)
|
||||||
|
|
||||||
|
for pointidx, simplex in zip(vor.ridge_points, vor.ridge_vertices):
|
||||||
|
simplex = np.asarray(simplex)
|
||||||
|
if np.any(simplex < 0):
|
||||||
|
i = simplex[simplex >= 0][0] # finite end Voronoi vertex
|
||||||
|
|
||||||
|
t = vor.points[pointidx[1]] - vor.points[pointidx[0]] # tangent
|
||||||
|
t /= np.linalg.norm(t)
|
||||||
|
n = np.array([-t[1], t[0]]) # normal
|
||||||
|
|
||||||
|
midpoint = vor.points[pointidx].mean(axis=0)
|
||||||
|
direction = np.sign(np.dot(midpoint - center, n)) * n
|
||||||
|
far_point = vor.vertices[i] + direction * ptp_bound.max()
|
||||||
|
|
||||||
|
# convert mercator coordinates back into lng/lat
|
||||||
|
lng1, lat1 = merc_sphere_inv(vor.vertices[i,0], vor.vertices[i,1])
|
||||||
|
lng2, lat2 = merc_sphere_inv(far_point[0], far_point[1])
|
||||||
|
csv.write("\"LINESTRING (%f %f,%f %f)\"\n" % (lng1, lat1, lng2, lat2))
|
||||||
|
|
||||||
|
# touch mapnik XML files to trigger tilelite watcher
|
||||||
|
touch("/usr/share/ffmap/hoods.xml")
|
||||||
|
touch("/usr/share/ffmap/links_and_routers.xml")
|
|
@ -1,19 +1,22 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import netmon
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '..'))
|
||||||
|
|
||||||
|
from ffmap.dbtools import FreifunkDB
|
||||||
|
|
||||||
import lxml.etree
|
import lxml.etree
|
||||||
import datetime
|
import datetime
|
||||||
from pymongo import MongoClient
|
import requests
|
||||||
|
|
||||||
client = MongoClient()
|
db = FreifunkDB().handle()
|
||||||
db = client.freifunk
|
|
||||||
|
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"vpn_netif": "fffVPN",
|
"vpn_netif": "fffVPN",
|
||||||
}
|
}
|
||||||
|
|
||||||
def process_router_xml(mac, xml):
|
def load_nodewatcher_xml(mac, xml):
|
||||||
try:
|
try:
|
||||||
router = db.routers.find_one({"netifs.mac": mac.lower()})
|
router = db.routers.find_one({"netifs.mac": mac.lower()})
|
||||||
if router:
|
if router:
|
||||||
|
@ -25,6 +28,7 @@ def process_router_xml(mac, xml):
|
||||||
router_update = {
|
router_update = {
|
||||||
"status": tree.xpath("/data/system_data/status/text()")[0],
|
"status": tree.xpath("/data/system_data/status/text()")[0],
|
||||||
"hostname": tree.xpath("/data/system_data/hostname/text()")[0],
|
"hostname": tree.xpath("/data/system_data/hostname/text()")[0],
|
||||||
|
"last_contact": datetime.datetime.utcnow(),
|
||||||
"neighbours": [],
|
"neighbours": [],
|
||||||
"netifs": [],
|
"netifs": [],
|
||||||
"system": {
|
"system": {
|
||||||
|
@ -118,14 +122,15 @@ def process_router_xml(mac, xml):
|
||||||
if router:
|
if router:
|
||||||
# keep hood up to date
|
# keep hood up to date
|
||||||
router_update["hood"] = db.hoods.find_one({"position": {"$near": {"$geometry": router["position"]}}})["name"]
|
router_update["hood"] = db.hoods.find_one({"position": {"$near": {"$geometry": router["position"]}}})["name"]
|
||||||
db.routers.update_one({"netifs.mac": mac.lower()}, {"$set": router_update, "$currentDate": {"last_contact": True}})
|
db.routers.update_one({"netifs.mac": mac.lower()}, {"$set": router_update})
|
||||||
else:
|
else:
|
||||||
# new router
|
# new router
|
||||||
# fetch additional information from netmon as it is not yet contained in xml
|
# fetch additional information from netmon as it is not yet contained in xml
|
||||||
router_info = netmon.fetch_router_info(mac)
|
router_info = netmon_fetch_router_info(mac)
|
||||||
if router_info:
|
if router_info:
|
||||||
# keep hood up to date
|
# keep hood up to date
|
||||||
router_update["hood"] = db.hoods.find_one({"position": {"$near": {"$geometry": router_info["position"]}}})["name"]
|
router_update["hood"] = db.hoods.find_one({"position": {"$near": {"$geometry": router_info["position"]}}})["name"]
|
||||||
|
router_update["events"] = []
|
||||||
router_update.update(router_info)
|
router_update.update(router_info)
|
||||||
router_id = db.routers.insert_one(router_update).inserted_id
|
router_id = db.routers.insert_one(router_update).inserted_id
|
||||||
status = router_update["status"]
|
status = router_update["status"]
|
||||||
|
@ -133,7 +138,8 @@ def process_router_xml(mac, xml):
|
||||||
if router:
|
if router:
|
||||||
db.routers.update_one({"_id": router_id}, {"$set": {"status": "unknown"}})
|
db.routers.update_one({"_id": router_id}, {"$set": {"status": "unknown"}})
|
||||||
status = "unknown"
|
status = "unknown"
|
||||||
finally:
|
|
||||||
|
if router_id:
|
||||||
# fire events
|
# fire events
|
||||||
events = []
|
events = []
|
||||||
try:
|
try:
|
||||||
|
@ -169,3 +175,66 @@ def process_router_xml(mac, xml):
|
||||||
# calculate RRD statistics (rrdcache?)
|
# calculate RRD statistics (rrdcache?)
|
||||||
#FIXME: implementation
|
#FIXME: implementation
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def detect_offline_routers():
|
||||||
|
db.routers.update_many({
|
||||||
|
"last_contact": {"$lt": datetime.datetime.utcnow() - datetime.timedelta(minutes=10)},
|
||||||
|
"status": {"$ne": "offline"}
|
||||||
|
}, {
|
||||||
|
"$set": {"status": "offline"},
|
||||||
|
"$push": {"events": {
|
||||||
|
"$each": [{
|
||||||
|
"time": datetime.datetime.utcnow(),
|
||||||
|
"type": "offline"
|
||||||
|
}],
|
||||||
|
"$slice": -10
|
||||||
|
}}
|
||||||
|
})
|
||||||
|
|
||||||
|
def netmon_fetch_router_info(mac):
|
||||||
|
mac = mac.replace(":", "").lower()
|
||||||
|
tree = lxml.etree.fromstring(requests.get("https://netmon.freifunk-franken.de/api/rest/router/%s" % mac, params={"limit": 5000}).content)
|
||||||
|
|
||||||
|
for r in tree.xpath("/netmon_response/router"):
|
||||||
|
user_netmon_id = int(r.xpath("user_id/text()")[0])
|
||||||
|
user = db.users.find_one({"netmon_id": user_netmon_id})
|
||||||
|
if user:
|
||||||
|
user_id = user["_id"]
|
||||||
|
else:
|
||||||
|
user_id = db.users.insert({
|
||||||
|
"netmon_id": user_netmon_id,
|
||||||
|
"nickname": r.xpath("user/nickname/text()")[0]
|
||||||
|
})
|
||||||
|
user = db.users.find_one({"_id": user_id})
|
||||||
|
|
||||||
|
router = {
|
||||||
|
"netmon_id": int(r.xpath("router_id/text()")[0]),
|
||||||
|
"user": {"nickname": user["nickname"], "_id": user["_id"]}
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
lng = float(r.xpath("longitude/text()")[0])
|
||||||
|
lat = float(r.xpath("latitude/text()")[0])
|
||||||
|
assert lng != 0
|
||||||
|
assert lat != 0
|
||||||
|
|
||||||
|
router["position"] = {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [lng, lat]
|
||||||
|
}
|
||||||
|
|
||||||
|
# try to get comment
|
||||||
|
position_comment = r.xpath("location/text()")[0]
|
||||||
|
if position_comment != "undefined":
|
||||||
|
router["position"]["comment"] = position_comment
|
||||||
|
except (IndexError, AssertionError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
router["description"] = r.xpath("description/text()")[0]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
router["created"] = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
return router
|
|
@ -1,6 +1,8 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import nodewatcher
|
from ffmap.routertools import *
|
||||||
|
from ffmap.maptools import *
|
||||||
|
from ffmap.dbtools import FreifunkDB
|
||||||
|
|
||||||
from flask import Blueprint, request, make_response
|
from flask import Blueprint, request, make_response
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
|
@ -9,8 +11,7 @@ import json
|
||||||
|
|
||||||
api = Blueprint("api", __name__)
|
api = Blueprint("api", __name__)
|
||||||
|
|
||||||
client = MongoClient()
|
db = FreifunkDB().handle()
|
||||||
db = client.freifunk
|
|
||||||
|
|
||||||
@api.route('/get_nearest_router')
|
@api.route('/get_nearest_router')
|
||||||
def get_nearest_router():
|
def get_nearest_router():
|
||||||
|
@ -31,9 +32,12 @@ def alfred():
|
||||||
r = make_response(json.dumps(set_alfred_data))
|
r = make_response(json.dumps(set_alfred_data))
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
alfred_data = request.get_json()
|
alfred_data = request.get_json()
|
||||||
# load router status xml data
|
if alfred_data:
|
||||||
for mac, xml in alfred_data.get("64", {}).items():
|
# load router status xml data
|
||||||
nodewatcher.process_router_xml(mac, xml)
|
for mac, xml in alfred_data.get("64", {}).items():
|
||||||
r.headers['X-API-STATUS'] = "ALFRED data imported"
|
load_nodewatcher_xml(mac, xml)
|
||||||
|
r.headers['X-API-STATUS'] = "ALFRED data imported"
|
||||||
|
detect_offline_routers()
|
||||||
|
update_mapnik_csv()
|
||||||
r.mimetype = 'application/json'
|
r.mimetype = 'application/json'
|
||||||
return r
|
return r
|
|
@ -1,10 +1,14 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
|
|
||||||
from api import api
|
import os
|
||||||
from filters import filters
|
import sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '../..'))
|
||||||
|
|
||||||
|
from ffmap.web.api import api
|
||||||
|
from ffmap.web.filters import filters
|
||||||
|
from ffmap.dbtools import FreifunkDB
|
||||||
|
|
||||||
from flask import Flask, render_template, request, make_response
|
from flask import Flask, render_template, request, make_response
|
||||||
from pymongo import MongoClient
|
|
||||||
from bson.json_util import dumps as bson2json
|
from bson.json_util import dumps as bson2json
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
import json
|
import json
|
||||||
|
@ -13,12 +17,11 @@ app = Flask(__name__)
|
||||||
app.register_blueprint(api, url_prefix='/api')
|
app.register_blueprint(api, url_prefix='/api')
|
||||||
app.register_blueprint(filters)
|
app.register_blueprint(filters)
|
||||||
|
|
||||||
client = MongoClient()
|
db = FreifunkDB().handle()
|
||||||
db = client.freifunk
|
|
||||||
|
|
||||||
tileurls = {
|
tileurls = {
|
||||||
"links_and_routers": "http://localhost:8000",
|
"links_and_routers": "/tiles/links_and_routers",
|
||||||
"hoods": "http://localhost:8001",
|
"hoods": "/tiles/hoods",
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
@ -43,5 +46,10 @@ def router_list():
|
||||||
def router_info(dbid):
|
def router_info(dbid):
|
||||||
return render_template("router.html", router=db.routers.find_one({"_id": ObjectId(dbid)}), tileurls=tileurls)
|
return render_template("router.html", router=db.routers.find_one({"_id": ObjectId(dbid)}), tileurls=tileurls)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', debug=True)
|
app.run(host='0.0.0.0', debug=True)
|
||||||
|
else:
|
||||||
|
app.template_folder = "/usr/share/ffmap/templates"
|
||||||
|
app.static_folder = "/usr/share/ffmap/static"
|
||||||
|
#app.debug = True
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
|
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from dateutil import tz
|
from dateutil import tz
|
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 561 B |
Before Width: | Height: | Size: 518 B After Width: | Height: | Size: 518 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 592 B After Width: | Height: | Size: 592 B |
|
@ -52,8 +52,19 @@ map.on('click', function(pos) {
|
||||||
if (px_distance <= router_pointer_radius) {
|
if (px_distance <= router_pointer_radius) {
|
||||||
console.log("Click on '"+router.hostname+"' detected.");
|
console.log("Click on '"+router.hostname+"' detected.");
|
||||||
console.log(router);
|
console.log(router);
|
||||||
var has_neighbours = 'neighbours' in router && router.neighbours.length > 0;
|
|
||||||
var popup_html = "";
|
var popup_html = "";
|
||||||
|
var has_neighbours = 'neighbours' in router && router.neighbours.length > 0;
|
||||||
|
|
||||||
|
// avoid empty tables
|
||||||
|
if (has_neighbours) {
|
||||||
|
has_neighbours = false;
|
||||||
|
for (neighbour of router.neighbours) {
|
||||||
|
if ('_id' in neighbour) {
|
||||||
|
has_neighbours = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (has_neighbours) {
|
if (has_neighbours) {
|
||||||
popup_html += "<div class=\"popup-headline with-neighbours\">";
|
popup_html += "<div class=\"popup-headline with-neighbours\">";
|
||||||
}
|
}
|
||||||
|
@ -71,7 +82,7 @@ map.on('click', function(pos) {
|
||||||
popup_html += "<th>Outgoing Interface</th>";
|
popup_html += "<th>Outgoing Interface</th>";
|
||||||
popup_html += "</tr>";
|
popup_html += "</tr>";
|
||||||
for (neighbour of router.neighbours) {
|
for (neighbour of router.neighbours) {
|
||||||
// skip unknown neighbours (FIXME: avoid empty table)
|
// skip unknown neighbours
|
||||||
if ('_id' in neighbour) {
|
if ('_id' in neighbour) {
|
||||||
var tr_color = "#04ff0a";
|
var tr_color = "#04ff0a";
|
||||||
if (neighbour.quality < 105) { tr_color = "#ff1e1e"; }
|
if (neighbour.quality < 105) { tr_color = "#ff1e1e"; }
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
mkdir -vp /var/lib/ffmap/csv
|
||||||
|
#FIXME: create dummy csv files
|
||||||
|
chown -R www-data:www-data /var/lib/ffmap
|
||||||
|
|
||||||
|
mkdir -vp /usr/share/ffmap
|
||||||
|
cp -v mapnik/{hoods,links_and_routers}.xml /usr/share/ffmap
|
||||||
|
sed -i -e 's#>csv/#>/var/lib/ffmap/csv/#' /usr/share/ffmap/{hoods,links_and_routers}.xml
|
||||||
|
chown www-data:www-data /usr/share/ffmap/{hoods,links_and_routers}.xml
|
||||||
|
cp -v wsgi/{hoods,links_and_routers,web}.wsgi /usr/share/ffmap
|
||||||
|
cp -rv ffmap/web/static /usr/share/ffmap
|
||||||
|
cp -rv ffmap/web/templates /usr/share/ffmap
|
||||||
|
|
||||||
|
mkdir -vp /var/cache/tiles/{hoods,links_and_routers}
|
||||||
|
chown -R www-data:www-data /var/cache/tiles/
|
||||||
|
|
||||||
|
cp -v systemd/*.service /etc/systemd/system/
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
python3 setup.py install
|
|
@ -1,58 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import lxml.etree
|
|
||||||
import requests
|
|
||||||
import datetime
|
|
||||||
from pymongo import MongoClient
|
|
||||||
|
|
||||||
client = MongoClient()
|
|
||||||
db = client.freifunk
|
|
||||||
|
|
||||||
def fetch_router_info(mac):
|
|
||||||
mac = mac.replace(":", "").lower()
|
|
||||||
tree = lxml.etree.fromstring(requests.get("https://netmon.freifunk-franken.de/api/rest/router/%s" % mac, params={"limit": 5000}).content)
|
|
||||||
|
|
||||||
for r in tree.xpath("/netmon_response/router"):
|
|
||||||
user_netmon_id = int(r.xpath("user_id/text()")[0])
|
|
||||||
user = db.users.find_one({"netmon_id": user_netmon_id})
|
|
||||||
if user:
|
|
||||||
user_id = user["_id"]
|
|
||||||
else:
|
|
||||||
user_id = db.users.insert({
|
|
||||||
"netmon_id": user_netmon_id,
|
|
||||||
"nickname": r.xpath("user/nickname/text()")[0]
|
|
||||||
})
|
|
||||||
user = db.users.find_one({"_id": user_id})
|
|
||||||
|
|
||||||
router = {
|
|
||||||
"netmon_id": int(r.xpath("router_id/text()")[0]),
|
|
||||||
"user": {"nickname": user["nickname"], "_id": user["_id"]}
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
lng = float(r.xpath("longitude/text()")[0])
|
|
||||||
lat = float(r.xpath("latitude/text()")[0])
|
|
||||||
assert lng != 0
|
|
||||||
assert lat != 0
|
|
||||||
|
|
||||||
router["position"] = {
|
|
||||||
"type": "Point",
|
|
||||||
"coordinates": [lng, lat]
|
|
||||||
}
|
|
||||||
|
|
||||||
# try to get comment
|
|
||||||
position_comment = r.xpath("location/text()")[0]
|
|
||||||
if position_comment != "undefined":
|
|
||||||
router["position"]["comment"] = position_comment
|
|
||||||
except (IndexError, AssertionError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
router["description"] = r.xpath("description/text()")[0]
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
router["last_contact"] = datetime.datetime.utcnow()
|
|
||||||
router["created"] = datetime.datetime.utcnow()
|
|
||||||
|
|
||||||
return router
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE Map>
|
<!DOCTYPE Map>
|
||||||
<Map background-color="transparent">
|
<Map background-color="transparent" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
|
||||||
<Style name="hoodpoint">
|
<Style name="hoodpoint">
|
||||||
<Rule>
|
<Rule>
|
||||||
<TextSymbolizer face-name="DejaVu Sans Book" size="12" fill="#1e42ff" halo-radius="2" text-transform="capitalize">[name]</TextSymbolizer>
|
<TextSymbolizer face-name="DejaVu Sans Book" size="12" fill="#1e42ff" halo-radius="2" text-transform="capitalize">[name]</TextSymbolizer>
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE Map>
|
<!DOCTYPE Map>
|
||||||
<Map background-color="transparent">
|
<Map background-color="transparent" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
|
||||||
<Style name="routerpoint" filter-mode="first">
|
<Style name="routerpoint" filter-mode="first">
|
||||||
<Rule>
|
<Rule>
|
||||||
<Filter>([status] = 'online')</Filter>
|
<Filter>([status] = 'online')</Filter>
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -31,7 +31,7 @@ with open("csv/links.csv", "w") as csv:
|
||||||
neighbour["quality"]
|
neighbour["quality"]
|
||||||
))
|
))
|
||||||
|
|
||||||
with open("csv/hood-points.csv", "w") as csv:
|
with open("csv/hood-points.csv", "w", encoding="UTF-8") as csv:
|
||||||
csv.write("lng,lat,name\n")
|
csv.write("lng,lat,name\n")
|
||||||
for hood in db.hoods.find({"position": {"$exists": True}}):
|
for hood in db.hoods.find({"position": {"$exists": True}}):
|
||||||
csv.write("%f,%f,\"%s\"\n" % (
|
csv.write("%f,%f,\"%s\"\n" % (
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from distutils.core import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='ffmap',
|
||||||
|
version='0.0.1',
|
||||||
|
license='GPL',
|
||||||
|
description='FF-MAP',
|
||||||
|
author='Dominik Heidler',
|
||||||
|
author_email='dominik@heidler.eu',
|
||||||
|
url='http://github.com/asdil12/ff-map',
|
||||||
|
#requires=['flask', 'flup'],
|
||||||
|
packages=['ffmap', 'ffmap.web'],
|
||||||
|
#scripts=['bin/aurbs'],
|
||||||
|
#data_files=[
|
||||||
|
# ('/etc', ['templates/aurbs.yml']),
|
||||||
|
# ('/usr/share/aurbs/cfg', ['templates/gpg.conf']),
|
||||||
|
# ('/usr/share/doc/aurbs', ['templates/lighttpd.conf.sample']),
|
||||||
|
#],
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
[Unit]
|
||||||
|
Description=FF-MAP Web UI
|
||||||
|
After=syslog.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/uwsgi_python3 -s 127.0.0.1:3031 -w ffmap.web.application:app --master --processes 4 --enable-threads --uid www-data --gid www-data
|
||||||
|
Restart=always
|
||||||
|
KillSignal=SIGQUIT
|
||||||
|
Type=notify
|
||||||
|
StandardError=syslog
|
||||||
|
NotifyAccess=all
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -0,0 +1,14 @@
|
||||||
|
[Unit]
|
||||||
|
Description=FF-MAP Tiles: Hoods
|
||||||
|
After=syslog.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/uwsgi_python -s 127.0.0.1:3033 --wsgi-file /usr/share/ffmap/hoods.wsgi --uid www-data --gid www-data --enable-threads
|
||||||
|
Restart=always
|
||||||
|
KillSignal=SIGQUIT
|
||||||
|
Type=notify
|
||||||
|
StandardError=syslog
|
||||||
|
NotifyAccess=all
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -0,0 +1,14 @@
|
||||||
|
[Unit]
|
||||||
|
Description=FF-MAP Tiles: Links and Routers
|
||||||
|
After=syslog.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/uwsgi_python -s 127.0.0.1:3032 --wsgi-file /usr/share/ffmap/links_and_routers.wsgi --uid www-data --gid www-data --enable-threads
|
||||||
|
Restart=always
|
||||||
|
KillSignal=SIGQUIT
|
||||||
|
Type=notify
|
||||||
|
StandardError=syslog
|
||||||
|
NotifyAccess=all
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -0,0 +1,64 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
"""
|
||||||
|
To setup TileLite on a production server using Apache and ModWSGI
|
||||||
|
create a virtualhost or otherwise insert the WSGI configuration into
|
||||||
|
your Apache configuration like so:
|
||||||
|
|
||||||
|
WSGIScriptAlias /<url> /path/to/this/tilelite.wsgi
|
||||||
|
WSGIDaemonProcess <process name> user=<user> group=<group> processes=10 threads=1
|
||||||
|
WSGIProcessGroup <process name>
|
||||||
|
|
||||||
|
* 'tilelite.wsgi' is the name of the simple python script below that associates the
|
||||||
|
tilelite.Server instance with a Mapnik xml file. It can be named anything you like
|
||||||
|
but should end with either a '.wsgi' or '.py' extension.
|
||||||
|
|
||||||
|
* <url> can be either be '/' (to mount the script at http://yourserver.com/) or it can be
|
||||||
|
a path such as '/tiles' to mount the server at http://yourserver.com/tiles
|
||||||
|
|
||||||
|
* <process name> can be any unique name like 'tileliteserver'
|
||||||
|
|
||||||
|
* <user> and <group> should be a unix user that has permissions to the 'tilelite.wsgi'
|
||||||
|
|
||||||
|
* Note: this is a multiprocess (not threaded) server so you *can* set 'processes' >= 1
|
||||||
|
but threads *must be* == 1, otherwise this server will not work within Apache.
|
||||||
|
|
||||||
|
An example setup would be:
|
||||||
|
|
||||||
|
## TileLite sample setup ##
|
||||||
|
WSGIScriptAlias /tiles /home/mapnik/projects/tilelite/tilelite.wsgi
|
||||||
|
WSGIDaemonProcess tileliteserver user=www-data group=www-data processes=10 threads=1
|
||||||
|
WSGIProcessGroup tileliteserver
|
||||||
|
|
||||||
|
Next, edit the script code below and place it where the WSGIScriptAlias path points to.
|
||||||
|
|
||||||
|
Then test your apache configuration and restart. On debian linux this might look like:
|
||||||
|
|
||||||
|
$ sudo apache2ctl configtest
|
||||||
|
$ /etc/init.d/apache restart
|
||||||
|
|
||||||
|
Then go to:
|
||||||
|
|
||||||
|
http://yourserver.com/tiles/
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from tilelite import Server
|
||||||
|
|
||||||
|
options = {
|
||||||
|
'watch_mapfile': True,
|
||||||
|
'paletted': False,
|
||||||
|
'cache_force': False,
|
||||||
|
'format': 'png',
|
||||||
|
'max_failures': 6,
|
||||||
|
'max_zoom': 22,
|
||||||
|
'debug': True,
|
||||||
|
'cache_path': '/var/cache/tiles/hoods/',
|
||||||
|
'watch_interval': 2,
|
||||||
|
'caching': True,
|
||||||
|
'buffer_size': 128,
|
||||||
|
'size': 256
|
||||||
|
}
|
||||||
|
|
||||||
|
# note: this variable must be called 'application'
|
||||||
|
application = Server('/usr/share/ffmap/hoods.xml', options=options)
|
|
@ -0,0 +1,64 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
"""
|
||||||
|
To setup TileLite on a production server using Apache and ModWSGI
|
||||||
|
create a virtualhost or otherwise insert the WSGI configuration into
|
||||||
|
your Apache configuration like so:
|
||||||
|
|
||||||
|
WSGIScriptAlias /<url> /path/to/this/tilelite.wsgi
|
||||||
|
WSGIDaemonProcess <process name> user=<user> group=<group> processes=10 threads=1
|
||||||
|
WSGIProcessGroup <process name>
|
||||||
|
|
||||||
|
* 'tilelite.wsgi' is the name of the simple python script below that associates the
|
||||||
|
tilelite.Server instance with a Mapnik xml file. It can be named anything you like
|
||||||
|
but should end with either a '.wsgi' or '.py' extension.
|
||||||
|
|
||||||
|
* <url> can be either be '/' (to mount the script at http://yourserver.com/) or it can be
|
||||||
|
a path such as '/tiles' to mount the server at http://yourserver.com/tiles
|
||||||
|
|
||||||
|
* <process name> can be any unique name like 'tileliteserver'
|
||||||
|
|
||||||
|
* <user> and <group> should be a unix user that has permissions to the 'tilelite.wsgi'
|
||||||
|
|
||||||
|
* Note: this is a multiprocess (not threaded) server so you *can* set 'processes' >= 1
|
||||||
|
but threads *must be* == 1, otherwise this server will not work within Apache.
|
||||||
|
|
||||||
|
An example setup would be:
|
||||||
|
|
||||||
|
## TileLite sample setup ##
|
||||||
|
WSGIScriptAlias /tiles /home/mapnik/projects/tilelite/tilelite.wsgi
|
||||||
|
WSGIDaemonProcess tileliteserver user=www-data group=www-data processes=10 threads=1
|
||||||
|
WSGIProcessGroup tileliteserver
|
||||||
|
|
||||||
|
Next, edit the script code below and place it where the WSGIScriptAlias path points to.
|
||||||
|
|
||||||
|
Then test your apache configuration and restart. On debian linux this might look like:
|
||||||
|
|
||||||
|
$ sudo apache2ctl configtest
|
||||||
|
$ /etc/init.d/apache restart
|
||||||
|
|
||||||
|
Then go to:
|
||||||
|
|
||||||
|
http://yourserver.com/tiles/
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from tilelite import Server
|
||||||
|
|
||||||
|
options = {
|
||||||
|
'watch_mapfile': True,
|
||||||
|
'paletted': False,
|
||||||
|
'cache_force': False,
|
||||||
|
'format': 'png',
|
||||||
|
'max_failures': 6,
|
||||||
|
'max_zoom': 22,
|
||||||
|
'debug': True,
|
||||||
|
'cache_path': '/var/cache/tiles/links_and_routers/',
|
||||||
|
'watch_interval': 2,
|
||||||
|
'caching': True,
|
||||||
|
'buffer_size': 128,
|
||||||
|
'size': 256
|
||||||
|
}
|
||||||
|
|
||||||
|
# note: this variable must be called 'application'
|
||||||
|
application = Server('/usr/share/ffmap/links_and_routers.xml', options=options)
|