2017-10-12 11:14:23 +00:00
|
|
|
import cgi
|
|
|
|
import datetime
|
2017-10-12 11:59:10 +00:00
|
|
|
import configparser
|
2017-10-12 11:14:23 +00:00
|
|
|
|
|
|
|
import pymysql
|
|
|
|
|
2017-11-29 16:46:47 +00:00
|
|
|
GJSON = 0
|
|
|
|
|
2017-10-12 11:59:10 +00:00
|
|
|
def getDatabase(path = "config.cfg"):
|
|
|
|
config = configparser.RawConfigParser()
|
|
|
|
config.read(path)
|
|
|
|
|
|
|
|
host = config.get("Database", "host")
|
|
|
|
user = config.get("Database", "user")
|
|
|
|
pwd = config.get("Database", "pass")
|
|
|
|
name = config.get("Database", "name")
|
|
|
|
|
|
|
|
conn = pymysql.connect(host, user, pwd, name)
|
2017-10-12 12:44:38 +00:00
|
|
|
cur = conn.cursor(pymysql.cursors.DictCursor)
|
2017-10-12 11:59:10 +00:00
|
|
|
|
|
|
|
return conn, cur
|
|
|
|
|
2017-11-29 16:46:47 +00:00
|
|
|
def buildGJSON(data):
|
|
|
|
output = """{
|
|
|
|
"type": "FeatureCollection",
|
|
|
|
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
|
|
|
|
"features": [
|
|
|
|
{ "type": "Feature",
|
|
|
|
"properties": {
|
|
|
|
"Name": null,
|
|
|
|
"description": null,
|
|
|
|
"timestamp": null,
|
|
|
|
"begin": null,
|
|
|
|
"end": null,
|
|
|
|
"altitudeMode": null,
|
|
|
|
"tessellate": -1,
|
|
|
|
"extrude": 0,
|
|
|
|
"visibility": -1,
|
|
|
|
"drawOrder": null,
|
|
|
|
"icon": null,
|
|
|
|
"styleUrl": "#style",
|
|
|
|
"styleHash": "1a1ac94e",
|
|
|
|
"stroke": "#ffff00",
|
|
|
|
"stroke_opacity": "0.4980392156862745",
|
|
|
|
"stroke_width": "4",
|
|
|
|
"fill": "#00ff00",
|
|
|
|
"fill_opacity": "0.4980392156862745"
|
|
|
|
},
|
|
|
|
"geometry": {
|
|
|
|
"type": "LineString",
|
|
|
|
"coordinates": [
|
|
|
|
"""
|
|
|
|
|
|
|
|
for row in data:
|
|
|
|
output += " [ %s, %s ],\n" % (row["lon"], row["lat"])
|
|
|
|
|
|
|
|
output = "".join(output.rsplit(",", 1))
|
|
|
|
|
|
|
|
output += """ ]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}"""
|
|
|
|
|
|
|
|
headers = [["Content-Type", "application/vnd.geo+json"], ['Content-Disposition', 'attachment; filename="export.geojson"']]
|
|
|
|
return headers, output
|
|
|
|
|
|
|
|
def buildGPX(data):
|
|
|
|
output = """<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
|
|
|
<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="pygps"
|
|
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
|
|
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
|
|
|
<metadata>
|
|
|
|
<name>export.gpx</name>
|
|
|
|
<desc>GPS Data Export</desc>
|
|
|
|
<author>
|
|
|
|
<name>pygps</name>
|
|
|
|
</author>
|
|
|
|
</metadata>
|
|
|
|
<trk>
|
|
|
|
<trkseg>
|
|
|
|
"""
|
|
|
|
|
|
|
|
for row in data:
|
|
|
|
output += """ <trkpt lat="%s" lon="%s">
|
|
|
|
<time>%s</time>
|
|
|
|
</trkpt>
|
|
|
|
""" % (row["lat"], row["lon"], row["ts"].isoformat())
|
|
|
|
|
|
|
|
|
|
|
|
output += """ </trkseg>
|
|
|
|
</trk>
|
|
|
|
</gpx>
|
|
|
|
"""
|
|
|
|
|
|
|
|
headers = [["Content-Type", "application/gpx+xml"], ['Content-Disposition', 'attachment; filename="export.gpx"']]
|
|
|
|
return headers, output
|
|
|
|
|
2017-12-09 22:27:33 +00:00
|
|
|
def buildKML(data):
|
|
|
|
output = """<?xml version="1.0" encoding="UTF-8"?>
|
2017-12-09 23:45:18 +00:00
|
|
|
<kml xmlns="http://www.opengis.net/kml/2.2"
|
|
|
|
xmlns:gx="http://www.google.com/kml/ext/2.2">
|
|
|
|
<Document>
|
|
|
|
<Placemark>
|
|
|
|
<gx:Track>"""
|
2017-12-09 22:27:33 +00:00
|
|
|
|
|
|
|
for row in data:
|
2017-12-09 23:45:18 +00:00
|
|
|
output += """
|
|
|
|
<when>%s</when>
|
|
|
|
<gx:coord>%s %s %s</gx:coord>""" % (row["ts"].isoformat(), row["lon"], row["lat"], row["alt"] or "0")
|
2017-12-09 22:27:33 +00:00
|
|
|
|
|
|
|
output += """
|
2017-12-09 23:45:18 +00:00
|
|
|
</gx:Track>
|
|
|
|
</Placemark>
|
|
|
|
</Document>
|
2017-12-09 22:27:33 +00:00
|
|
|
</kml>
|
|
|
|
"""
|
|
|
|
|
|
|
|
headers = [["Content-Type", "application/vnd.google-earth.kml+xml"], ['Content-Disposition', 'attachment; filename="export.kml"']]
|
|
|
|
return headers, output
|
|
|
|
|
2017-10-12 11:14:23 +00:00
|
|
|
def application(env, re):
|
|
|
|
if env["REQUEST_METHOD"] == "POST":
|
|
|
|
args = cgi.parse_qs(env['wsgi.input'].readline().decode(), True)
|
|
|
|
elif env["REQUEST_METHOD"] == "GET":
|
|
|
|
args = cgi.parse_qs(env['QUERY_STRING'], True)
|
|
|
|
else:
|
|
|
|
re("405 Method Not Allowed", [])
|
|
|
|
return
|
|
|
|
|
2017-10-12 12:44:38 +00:00
|
|
|
if env["PATH_INFO"] in ("/endpoint", "/endpoint.php"):
|
2017-10-12 11:14:23 +00:00
|
|
|
try:
|
|
|
|
device = args["device"][0]
|
|
|
|
except:
|
|
|
|
re("400 Bad Request", [])
|
|
|
|
yield "<h1>400 Bad Request</h1>".encode()
|
|
|
|
yield "device is required.".encode()
|
2017-10-12 12:44:38 +00:00
|
|
|
return
|
2017-10-12 11:14:23 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
latitude = float(args["lat"][0].replace(",", "."))
|
|
|
|
longitude = float(args["lon"][0].replace(",", "."))
|
2017-10-12 12:44:38 +00:00
|
|
|
except Exception:
|
2017-10-12 11:14:23 +00:00
|
|
|
re("400 Bad Request", [])
|
|
|
|
yield "<h1>400 Bad Request</h1>".encode()
|
|
|
|
yield "lat and lon are required.".encode()
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
altitude = float(args["alt"][0].replace(",", "."))
|
|
|
|
except:
|
|
|
|
altitude = None
|
|
|
|
|
|
|
|
try:
|
|
|
|
timestamp = datetime.datetime.fromtimestamp(float(args["t"][0]) / 1000)
|
|
|
|
except:
|
|
|
|
timestamp = datetime.datetime.now()
|
|
|
|
|
|
|
|
timestr = timestamp.strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
|
2017-10-12 11:59:10 +00:00
|
|
|
conn, cur = getDatabase()
|
|
|
|
sql = "INSERT INTO tracker(ts, device, lat, lon, alt) VALUES (%s, %s, %s, %s, %s);"
|
|
|
|
|
|
|
|
cur.execute(sql, (timestr, device, str(latitude), str(longitude), str(altitude) if altitude != None else None))
|
|
|
|
conn.commit()
|
2017-10-12 15:07:13 +00:00
|
|
|
cur.close()
|
|
|
|
conn.close()
|
2017-10-12 12:44:38 +00:00
|
|
|
|
|
|
|
re("200 OK", [])
|
|
|
|
yield "OK".encode()
|
|
|
|
return
|
|
|
|
|
|
|
|
if env["PATH_INFO"] in ("/location", "/location.php"):
|
|
|
|
try:
|
|
|
|
device = args["device"][0]
|
|
|
|
except:
|
|
|
|
re("400 Bad Request", [])
|
|
|
|
yield "<h1>400 Bad Request</h1>".encode()
|
|
|
|
yield "device is required.".encode()
|
|
|
|
return
|
|
|
|
|
|
|
|
conn, cur = getDatabase()
|
|
|
|
|
|
|
|
cur.execute("SELECT * FROM tracker WHERE device = %s ORDER BY ts DESC LIMIT 1;", device)
|
|
|
|
row = cur.fetchone()
|
|
|
|
|
2017-10-12 15:07:13 +00:00
|
|
|
cur.close()
|
|
|
|
conn.close()
|
|
|
|
|
2017-10-12 12:44:38 +00:00
|
|
|
re("200 OK", [["Content-Type", "text/html"]])
|
|
|
|
yield ("""<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset=utf-8>
|
|
|
|
<title>Current Location</title>
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="https://npmcdn.com/leaflet@1.0.0-rc.3/dist/leaflet.css" />
|
|
|
|
<script src="https://npmcdn.com/leaflet@1.0.0-rc.3/dist/leaflet.js"></script>
|
|
|
|
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
|
|
|
|
<h1>My location at %s</h1>
|
|
|
|
<p>(last known position where I had a GPS signal, a network connection, and some battery power)</p>
|
|
|
|
|
|
|
|
<div id="map" style="height:500px;"></div>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
var mymap = L.map("map").setView([%s, %s], 12);
|
|
|
|
L.tileLayer("https://b.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(mymap);
|
|
|
|
var marker = L.marker([%s, %s]).addTo(mymap);
|
|
|
|
</script>
|
|
|
|
|
|
|
|
</body>
|
|
|
|
</html>""" % (row["ts"], row["lat"], row["lon"], row["lat"], row["lon"])).encode()
|
|
|
|
return
|
2017-10-12 15:07:13 +00:00
|
|
|
|
|
|
|
if env["PATH_INFO"] in ("/access", "/access.php", "/export", "/export.php"):
|
|
|
|
try:
|
|
|
|
device = args["device"][0]
|
|
|
|
except:
|
|
|
|
re("400 Bad Request", [])
|
|
|
|
yield "<h1>400 Bad Request</h1>".encode()
|
|
|
|
yield "device is required.".encode()
|
|
|
|
return
|
|
|
|
|
2017-10-12 17:40:43 +00:00
|
|
|
on = args["on"][0] if "on" in args else None
|
|
|
|
frm = on if on else args["from"][0] if "from" in args else None
|
|
|
|
to = on if on else args["to"][0] if "to" in args else None
|
2017-10-12 15:07:13 +00:00
|
|
|
|
2017-12-09 22:27:33 +00:00
|
|
|
if not "format" in args or args["format"][0] in ("json", "gjson", "geojson", "gj") or not args["format"]:
|
2017-11-29 16:46:47 +00:00
|
|
|
builder = buildGJSON
|
2017-12-09 22:27:33 +00:00
|
|
|
elif args["format"][0] == "gpx":
|
|
|
|
builder = buildGPX
|
|
|
|
elif args["format"][0] == "kml":
|
|
|
|
builder = buildKML
|
|
|
|
else:
|
|
|
|
re("400 Bad Request", [])
|
|
|
|
yield "<h1>400 Bad Request</h1>".encode()
|
|
|
|
yield "Unknown format: %s" % args["format"]
|
|
|
|
return
|
2017-11-29 16:46:47 +00:00
|
|
|
|
2017-10-19 14:23:36 +00:00
|
|
|
frm = frm or "2000-01-01"
|
2017-10-12 15:07:13 +00:00
|
|
|
to = to or datetime.datetime.now().strftime('%Y-%m-%d')
|
|
|
|
|
|
|
|
for time in frm, to:
|
|
|
|
try:
|
2017-10-12 17:40:43 +00:00
|
|
|
datetime.datetime.strptime(time, "%Y-%m-%d")
|
2017-10-12 15:07:13 +00:00
|
|
|
except:
|
|
|
|
re("400 Bad Request", [])
|
|
|
|
yield "<h1>400 Bad Request</h1>".encode()
|
|
|
|
yield "Dates must be in YYYY-MM-DD format.".encode()
|
|
|
|
return
|
|
|
|
|
|
|
|
conn, cur = getDatabase()
|
|
|
|
|
2017-12-09 22:27:33 +00:00
|
|
|
sql = "SELECT * FROM tracker WHERE device=%s AND DATE(ts)>=%s and DATE(ts)<=%s ORDER BY ts ASC;";
|
2017-10-12 15:07:13 +00:00
|
|
|
cur.execute(sql, (device, frm, to))
|
|
|
|
|
2017-11-29 16:46:47 +00:00
|
|
|
data = cur.fetchall()
|
2017-10-12 15:07:13 +00:00
|
|
|
|
2017-11-29 16:46:47 +00:00
|
|
|
headers, output = builder(data)
|
2017-10-12 15:07:13 +00:00
|
|
|
|
2017-11-29 16:46:47 +00:00
|
|
|
re("200 OK", headers)
|
2017-10-12 15:07:13 +00:00
|
|
|
|
|
|
|
cur.close()
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
yield output.encode()
|
|
|
|
return
|
|
|
|
|