All the files

This commit is contained in:
Kyle Gabriel 2019-02-25 22:04:56 -05:00
parent b57831c541
commit 9dd7c1e2f0
43 changed files with 28817 additions and 1 deletions

View file

@ -1 +1,40 @@
# ttn-tracker
## The Things Network Tracker (TTN-Tracker)
For use with [kizniche/ttgo-tbeam-ttnmapper](https://github.com/kizniche/ttgo-tbeam-ttnmapper) for the T-Beam TTGO that transmits data to [The Things Network](https://thethingsnetwork.org) (TTN) for [TTN Mapper](https://ttnmapper.org/). This is the tracker node.
This Flask app hosts a wen-enabled front end using [gunicorn](https://github.com/benoitc/gunicorn) and [nginx](http://nginx.org/). It pulls coordinate data acquired from the tracker node that's been stored on TTN. It stores these coordinates in an SQLite database and displays the coordinates on a map ([leaflet](https://github.com/Leaflet/Leaflet)) in your web browser. This is useful for testing the signal range from gateways while driving, so you can see when and where your signal was able to reach a gateway.
This is very similar to the TTN Mapper frontend, however TTN Mapper takes a long time to update the data points on its map. This software runs locally on your own hardware and responds instantly to new data on TTN, making it a good companion in your vehicle if you want to get instant updates as to whether your tracker node successfully communicated its coordinates or not.
Features include:
- Multiple map layers, including satellite, topology, and streets (No API keys required)
- Measuring tool to measure distances between points
- Map stays focused on the same point across page refreshes (refreshing brings in new data)
- Clicking gateway or data point markers pops up information about them
### Setup:
I've succesfully set this up on a Raspberry Pi, but these instructions should work for any debian-variant operating system. Other systems you may have to adapt how you install the prerequisites.
```
git clone https://github.com/kizniche/ttn-tracker.git
cd ttn-tracker
sudo pip install virtualenv --upgrade
PYTHON_BINARY_SYS_LOC="$(python3.5 -c "import os; print(os.environ['_'])")"
virtualenv --system-site-packages -p ${PYTHON_BINARY_SYS_LOC} ./env
./env/bin/pip install -r requirements.txt
sudo ln -s /home/pi/ttn-tracker/flask_nginx.conf /etc/nginx/sites-enabled/ttn-tracker_nginx.config
sudo service nginx restart
sudo systemctl enable /home/pi/ttn-tracker/ttn-tracker.service
```
Make sure you have your application set up on The Things Network with the integration "Data Storage". Edit config.py with your application API Key, application ID, Device ID(s), and gateway location(s). The integration "TTN Mapper" is optional but is recommended to be able to provide signal data to the public.
### Run
sudo service ttn-tracker start
### Web Address
http://127.0.0.1:5500/dsf673bh

0
__init__.py Normal file
View file

150
app.py Normal file
View file

@ -0,0 +1,150 @@
import datetime
import logging
from math import atan2
from math import cos
from math import radians
from math import sin
from math import sqrt
import requests
from apscheduler.schedulers.background import BackgroundScheduler
from flask import Flask
from flask import render_template
from flask_sqlalchemy import SQLAlchemy
from config import app_key
from config import application
from config import devices
from config import gateway_locations
from config import path_db
from config import start_lat
from config import start_lon
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
logger = logging.getLogger(__name__)
app = Flask(__name__, template_folder="./templates")
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///{db}'.format(db=path_db)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
def schedule_get_new_data():
get_new_data()
scheduler = BackgroundScheduler()
job = scheduler.add_job(schedule_get_new_data, 'interval', days=1)
scheduler.start()
class Location(db.Model):
__tablename__ = "location"
id = db.Column(db.Integer, primary_key=True)
device_id = db.Column(db.String(250))
last_data_date = db.Column(db.DateTime)
raw = db.Column(db.String(250))
datetime = db.Column(db.String(250))
latitude = db.Column(db.String(250))
longitude = db.Column(db.String(250))
altitude = db.Column(db.Integer)
hdop = db.Column(db.Float)
def __repr__(self):
return '<ID %r>' % self.id
class LastAcquisition(db.Model):
__tablename__ = "last_data"
id = db.Column(db.Integer, primary_key=True)
last_datetime = db.Column(db.DateTime)
def __repr__(self):
return '<ID %r>' % self.id
@app.route('/dsf673bh')
def hello_world():
get_new_data()
return render_template('map.html',
gateway_locations=gateway_locations,
location_data=Location.query.all(),
start_lat=start_lat,
start_lon=start_lon)
def get_new_data():
last_seconds = seconds_from_last()
if last_seconds:
past_seconds = int(last_seconds) + 1
else:
past_seconds = 604800 # 7 days, max The Things Network storage allows
for each_device in devices:
endpoint = "https://{app}.data.thethingsnetwork.org/api/v2/query/{dev}?last={time}".format(
app=application, dev=each_device, time="{}s".format(past_seconds))
logger.info(endpoint)
headers = {"Authorization": app_key}
response = requests.get(endpoint, headers=headers)
try:
for each_resp in response.json():
if (not Location.query.filter(Location.datetime == each_resp['time']).first() and
-90 < float(each_resp['latitude']) <= 90 and -120 <= float(each_resp['longitude']) <= 80):
logger.info("{}, {}".format(each_resp['latitude'], each_resp['longitude']))
new_location = Location(
device_id=each_resp['device_id'],
raw=each_resp['raw'],
datetime=each_resp['time'],
latitude=each_resp['latitude'],
longitude=each_resp['longitude'],
altitude=each_resp['altitude'],
hdop=each_resp['hdop'])
db.session.add(new_location)
db.session.commit()
except:
pass
set_date_now()
def set_date_now():
date_last = LastAcquisition.query.first()
if not date_last:
new_last = LastAcquisition(last_datetime=datetime.datetime.now())
db.session.add(new_last)
db.session.commit()
else:
date_last.last_datetime = datetime.datetime.now()
db.session.commit()
def seconds_from_last():
date_last = LastAcquisition.query.first()
if date_last:
return (datetime.datetime.now() - date_last.last_datetime).total_seconds()
def distance_coordinates(lat1, lon1, lat2, lon2):
# approximate radius of earth in km
R = 6373.0
lat1 = radians(lat1)
lon1 = radians(lon1)
lat2 = radians(lat2)
lon2 = radians(lon2)
dlon = lon2 - lon1
dlat = lat2 - lat1
a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
c = 2 * atan2(sqrt(a), sqrt(1 - a))
distance = R * c
return distance # km
if __name__ == '__main__':
db.create_all()
app.run(debug=True, host='0.0.0.0', port=5500)

24
config.py Normal file
View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Where the map initially loads
start_lat = 35.978781
start_lon = -77.855346
# Where to store SQLite database
path_db = '/home/pi/ttn-tracker/ttnmapper_retrieve.db'
# TTN Application
application = "ttn_application"
app_key = "key ttn-account-TTN_APP_KEY"
# Application devices
devices = [
"device_01",
"device_02"
]
# Where to place gateway markers
gateway_locations = [
('Gateway 01', 35.978781, -77.855346),
('Gateway 02', 35.978781, -77.655346)
]

15
flask_nginx.conf Normal file
View file

@ -0,0 +1,15 @@
server {
listen 5500;
client_max_body_size 30M;
location / {
include proxy_params;
proxy_pass http://unix:/var/run/ttn_tracker_flask.sock;
}
error_page 502 /502.html;
location = /502.html {
root /home/pi/ttn-tracker/templates;
}
}

5
requirements.txt Normal file
View file

@ -0,0 +1,5 @@
apscheduler
gunicorn
Flask==0.10.1
flask_sqlalchemy
requests

5
static/esri-leaflet.js Normal file

File diff suppressed because one or more lines are too long

1
static/leaflet-bing-layer.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

(image error) Size: 397 B

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<path d="M12,0C5.37,0 4.44089e-16,5.37 4.44089e-16,12C4.44089e-16,18.63 5.37,24 12,24C18.63,24 24,18.63 24,12C24,5.37 18.63,0 12,0ZM18,16.302L16.302,18L12,13.698L7.698,18L6,16.302L10.302,12L6,7.698L7.698,6L12,10.302L16.302,6L18,7.698L13.698,12Z" fill="#5E66CC"></path>
</svg>

After

(image error) Size: 588 B

Binary file not shown.

After

(image error) Size: 762 B

Binary file not shown.

After

(image error) Size: 387 B

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<path d="M12,0C5.37,0 4.44089e-16,5.37 4.44089e-16,12C4.44089e-16,18.63 5.37,24 12,24C18.63,24 24,18.63 24,12C24,5.37 18.63,0 12,0ZM9.66667,17.7846L5,13.1179L6.65083,11.4671L9.66667,14.4829L17.3492,6.80042L19,8.45125Z" fill="#5E66CC"/>
</svg>

After

(image error) Size: 522 B

Binary file not shown.

After

(image error) Size: 692 B

Binary file not shown.

After

(image error) Size: 326 B

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<path fill="#5E66CC" d="M5 15h-2v4c0 1.105 0.895 2 2 2h4v-2h-4v-4zM5 5h4v-2h-4c-1.105 0-2 0.895-2 2v4h2v-4zM19 3h-4v2h4v4h2v-4c0-1.105-0.895-2-2-2zM19 19h-4v2h4c1.105 0 2-0.895 2-2v-4h-2v4zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zM12 14c-1.105 0-2-0.895-2-2s0.895-2 2-2 2 0.895 2 2-0.895 2-2 2z"></path>
</svg>

After

(image error) Size: 607 B

Binary file not shown.

After

(image error) Size: 462 B

Binary file not shown.

After

(image error) Size: 595 KiB

Binary file not shown.

After

(image error) Size: 192 B

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32">
<path d="M31.703,0.297C31.9,0.495,32,0.729,32,1v20c0,0.271-0.1,0.505-0.297,0.703C31.505,21.9,31.271,22,31,22s-0.505-0.1-0.703-0.297C30.1,21.505,30,21.271,30,21V2H11c-0.271,0-0.505-0.1-0.703-0.297C10.099,1.505,10,1.271,10,1s0.099-0.505,0.297-0.703C10.495,0.1,10.729,0,11,0h20C31.271,0,31.505,0.1,31.703,0.297z M30,24h2v6c0,0.541-0.199,1.01-0.594,1.406C31.011,31.802,30.542,32,30,32H2c-0.542,0-1.01-0.199-1.406-0.594C0.198,31.011,0,30.542,0,30V2c0-0.563,0.199-1.037,0.594-1.422C0.989,0.192,1.458,0,2,0h6v2H7C6.729,2,6.495,2.1,6.297,2.297C6.099,2.495,6,2.729,6,3s0.099,0.505,0.297,0.703C6.495,3.9,6.729,4,7,4h1v2H5C4.729,6,4.495,6.1,4.297,6.297C4.099,6.495,4,6.729,4,7s0.099,0.505,0.297,0.703C4.495,7.9,4.729,8,5,8h3v2H7c-0.271,0-0.505,0.1-0.703,0.297C6.099,10.495,6,10.729,6,11s0.099,0.505,0.297,0.703C6.495,11.899,6.729,12,7,12h1v2H5c-0.271,0-0.505,0.101-0.703,0.297C4.099,14.495,4,14.729,4,15s0.099,0.505,0.297,0.703C4.495,15.901,4.729,16,5,16h3v2H7c-0.271,0-0.505,0.1-0.703,0.297C6.099,18.495,6,18.729,6,19s0.099,0.505,0.297,0.703C6.495,19.9,6.729,20,7,20h1v1v1H5c-0.271,0-0.505,0.1-0.703,0.297C4.099,22.495,4,22.729,4,23s0.099,0.505,0.297,0.703C4.495,23.9,4.729,24,5,24h3v3c0,0.271,0.099,0.505,0.297,0.703C8.495,27.9,8.729,28,9,28s0.505-0.1,0.703-0.297C9.901,27.505,10,27.271,10,27v-3h1h1v1c0,0.271,0.099,0.505,0.297,0.703C12.495,25.9,12.729,26,13,26s0.505-0.1,0.703-0.297C13.901,25.505,14,25.271,14,25v-1h2v3c0,0.271,0.099,0.505,0.297,0.703C16.495,27.9,16.729,28,17,28s0.505-0.1,0.703-0.297C17.9,27.505,18,27.271,18,27v-3h2v1c0,0.271,0.1,0.505,0.297,0.703C20.495,25.9,20.729,26,21,26s0.505-0.1,0.703-0.297C21.9,25.505,22,25.271,22,25v-1h2v3c0,0.271,0.1,0.505,0.297,0.703C24.495,27.9,24.729,28,25,28s0.505-0.1,0.703-0.297C25.9,27.505,26,27.271,26,27v-3h2v1c0,0.271,0.1,0.505,0.297,0.703C28.495,25.9,28.729,26,29,26s0.505-0.1,0.703-0.297C29.9,25.505,30,25.271,30,25V24z"/>
</svg>

After

(image error) Size: 2.1 KiB

Binary file not shown.

After

(image error) Size: 277 B

Binary file not shown.

After

(image error) Size: 491 B

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<path d="M9.6,17.4L16.8,12L9.6,6.6ZM12,0C5.37,0 4.44089e-16,5.37 4.44089e-16,12C4.44089e-16,18.63 5.37,24 12,24C18.63,24 24,18.63 24,12C24,5.37 18.63,0 12,0ZM12,21.6C6.708,21.6 2.4,17.292 2.4,12C2.4,6.708 6.708,2.4 12,2.4C17.292,2.4 21.6,6.708 21.6,12C21.6,17.292 17.292,21.6 12,21.6Z" fill="#5E66CC"></path>
</svg>

After

(image error) Size: 628 B

Binary file not shown.

After

(image error) Size: 1,003 B

Binary file not shown.

After

(image error) Size: 279 B

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="22px" height="24px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 22 24">
<path fill="#5E66CC" d="M8,9.5L8,18.5C8,18.646 7.953,18.7657 7.859,18.859C7.765,18.9523 7.64533,18.9993 7.5,19L6.5,19C6.354,19 6.23433,18.953 6.141,18.859C6.04767,18.765 6.00067,18.6453 6,18.5L6,9.5C6,9.354 6.047,9.23433 6.141,9.141C6.235,9.04767 6.35467,9.00067 6.5,9L7.5,9C7.646,9 7.76567,9.047 7.859,9.141C7.95233,9.235 7.99933,9.35467 8,9.5ZM12,9.5L12,18.5C12,18.646 11.953,18.7657 11.859,18.859C11.765,18.9523 11.6453,18.9993 11.5,19L10.5,19C10.354,19 10.2343,18.953 10.141,18.859C10.0477,18.765 10.0007,18.6453 10,18.5L10,9.5C10,9.354 10.047,9.23433 10.141,9.141C10.235,9.04767 10.3547,9.00067 10.5,9L11.5,9C11.646,9 11.7657,9.047 11.859,9.141C11.9523,9.235 11.9993,9.35467 12,9.5ZM16,9.5L16,18.5C16,18.646 15.953,18.7657 15.859,18.859C15.765,18.9523 15.6453,18.9993 15.5,19L14.5,19C14.354,19 14.2343,18.953 14.141,18.859C14.0477,18.765 14.0007,18.6453 14,18.5L14,9.5C14,9.354 14.047,9.23433 14.141,9.141C14.235,9.04767 14.3547,9.00067 14.5,9L15.5,9C15.646,9 15.7657,9.047 15.859,9.141C15.9523,9.235 15.9993,9.35467 16,9.5ZM18,20.813L18,6.001L4,6.001L4,20.813C4,21.0423 4.03633,21.2533 4.109,21.446C4.18167,21.6387 4.25733,21.7793 4.336,21.868C4.41467,21.9567 4.46933,22.001 4.5,22.001L17.5,22.001C17.5313,22.001 17.586,21.9567 17.664,21.868C17.742,21.7793 17.8177,21.6387 17.891,21.446C17.9643,21.2533 18.0007,21.0423 18,20.813ZM7.5,4L14.5,4L13.75,2.172C13.6773,2.078 13.5887,2.02067 13.484,2L8.531,2C8.427,2.02067 8.33833,2.078 8.265,2.172ZM22,4.5L22,5.5C22,5.646 21.953,5.76567 21.859,5.859C21.765,5.95233 21.6453,5.99933 21.5,6L20,6L20,20.812C20,21.6767 19.7553,22.424 19.266,23.054C18.7767,23.684 18.188,23.999 17.5,23.999L4.5,23.999C3.81267,23.999 3.224,23.6943 2.734,23.085C2.244,22.4757 1.99933,21.7387 2,20.874L2,5.999L0.5,5.999C0.354,5.999 0.234333,5.952 0.141,5.858C0.0476667,5.764 0.000666667,5.64433 2.77556e-17,5.499L2.77556e-17,4.499C2.77556e-17,4.353 0.047,4.23333 0.141,4.14C0.235,4.04667 0.354667,3.99967 0.5,3.999L5.328,3.999L6.422,1.39C6.578,1.00467 6.85933,0.676667 7.266,0.406C7.67267,0.135333 8.084,0 8.5,0L13.5,0C13.9167,0 14.328,0.135333 14.734,0.406C15.14,0.676667 15.4213,1.00467 15.578,1.39L16.672,3.999L21.5,3.999C21.646,3.999 21.7657,4.046 21.859,4.14C21.9523,4.234 21.9993,4.35367 22,4.499Z"/>
</svg>

After

(image error) Size: 2.5 KiB

Binary file not shown.

After

(image error) Size: 460 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

(image error) Size: 1.2 KiB

Binary file not shown.

After

(image error) Size: 696 B

Binary file not shown.

After

(image error) Size: 2.4 KiB

Binary file not shown.

After

(image error) Size: 1.4 KiB

Binary file not shown.

After

(image error) Size: 618 B

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

13930
static/leaflet/leaflet-src.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

635
static/leaflet/leaflet.css Normal file
View file

@ -0,0 +1,635 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg,
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-tile {
will-change: opacity;
}
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline: 0;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-container a.leaflet-active {
outline: 2px solid orange;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a,
.leaflet-bar a:hover {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path {
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.7);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover {
text-decoration: underline;
}
.leaflet-container .leaflet-control-attribution,
.leaflet-container .leaflet-control-scale {
font-size: 11px;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 19px;
line-height: 1.4;
}
.leaflet-popup-content p {
margin: 18px 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
padding: 4px 4px 0 0;
border: none;
text-align: center;
width: 18px;
height: 14px;
font: 16px/14px Tahoma, Verdana, sans-serif;
color: #c3c3c3;
text-decoration: none;
font-weight: bold;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover {
color: #999;
}
.leaflet-popup-scrolled {
overflow: auto;
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-clickable {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

110
templates/map.html Normal file
View file

@ -0,0 +1,110 @@
<!doctype html>
<html lang="en">
<head>
<title>TTNMapper Retriever, Biatch!</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="stylesheet" href="{{url_for('static', filename='leaflet/leaflet.css')}}"/>
<link rel="stylesheet" href="{{url_for('static', filename='leaflet-measure/leaflet-measure.css')}}" />
<script src="{{url_for('static', filename='leaflet/leaflet.js')}}"></script>
<script src="{{url_for('static', filename='leaflet-measure/leaflet-measure.js')}}"></script>
<script src="{{url_for('static', filename='leaflet-bing-layer.min.js')}}"></script>
<script src="{{url_for('static', filename='esri-leaflet.js')}}"></script>
</head>
<body>
<div id="mapid" style="height: 98vh; width: 100%"></div>
<script>
var osm_Link = '<a href="http://openstreetmap.org">OpenStreetMap</a>',
otm_Link = '<a href="http://opentopomap.org/">OpenTopoMap</a>',
bing_Link = '<a href="http://bing.com/">Bing</a>',
google_Link = '<a href="http://google.com/">Google</a>';
var osm_Url = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
osm_Attrib = '&copy; ' + osm_Link + ' Contributors',
otm_Url = 'http://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
otm_Attrib = '&copy; ' + otm_Link + ' Contributors ',
bing_Attrib = '&copy; ' + bing_Link + ' Contributors',
google_Url = 'http://mt.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',
google_Attrib = '&copy; ' + google_Link + ' Contributors';
var osm_Map = L.tileLayer(osm_Url, {attribution: osm_Attrib}),
esri_Map = L.esri.basemapLayer("Topographic"),
otm_Map = L.tileLayer(otm_Url, {attribution: otm_Attrib}),
google_Map = L.tileLayer(google_Url, {attribution: google_Attrib}),
bing_dark = L.tileLayer.bing({bingMapsKey: 'Ahpy5iReID6QJSUjazLaJbAkUMg2R990DMsYlbxMbf3irXoOVgFb0eyV3JPntW2Q', imagerySet: 'CanvasDark', attribution: bing_Attrib}),
bing_sat = L.tileLayer.bing({bingMapsKey: 'Ahpy5iReID6QJSUjazLaJbAkUMg2R990DMsYlbxMbf3irXoOVgFb0eyV3JPntW2Q', imagerySet: 'Aerial', attribution: bing_Attrib}),
bing_sat_labels = L.tileLayer.bing({bingMapsKey: 'Ahpy5iReID6QJSUjazLaJbAkUMg2R990DMsYlbxMbf3irXoOVgFb0eyV3JPntW2Q', imagerySet: 'AerialWithLabels', attribution: bing_Attrib});
//Create a map that remembers where it was zoomed to
function boundsChanged () {
localStorage.setItem('bounds', JSON.stringify(map.getBounds()));
default_zoom = false;
}
var map;
var default_zoom = true;
b = JSON.parse(localStorage.getItem('bounds'));
if (b == null)
{
map = L.map('mapid', {layers: [esri_Map]}).setView([{{start_lat}}, {{start_lon}}], 15);
}
else {
map = L.map('mapid', {layers: [esri_Map]});
try {
map.fitBounds([[b._southWest.lat%90,b._southWest.lng%180],[b._northEast.lat%90,b._northEast.lng%180]]);
default_zoom = false;
} catch (err) {
map.setView([{{start_lat}}, {{start_lon}}], 15);
}
}
map.on('dragend', boundsChanged);
map.on('zoomend', boundsChanged);
//disable inertia because it is irritating and slow
map.options.inertia=false;
var measureControl = L.control.measure({
activeColor: '#FF0000',
completedColor: '#FF8000',
primaryLengthUnit: 'miles',
secondaryLengthUnit: 'kilometers'
});
measureControl.addTo(map);
var baseLayers = {
"Topographic": esri_Map,
"OpenStreetMap": osm_Map,
"OpenTopoMap": otm_Map,
"Google": google_Map,
"Bing (Dark)": bing_dark,
"Bing Satellite": bing_sat,
"Bing Satellite (w Labels)": bing_sat_labels
};
L.control.layers(baseLayers).addTo(map);
map.on('click', function(e) {
console.log("Clicked: " + e.latlng.lat + ", " + e.latlng.lng);
});
{% for each_location in location_data %}
var node = L.circleMarker([{{each_location.latitude}}, {{each_location.longitude}}], {
color: 'red',
radius: 5
}).bindPopup('Node: {{each_location.device_id}}<br />{{each_location.datetime}}<br />Lat/Lon: {{each_location.latitude|float|round(6)}}, {{each_location.longitude|float|round(6)}}<br />Altitude: {{each_location.altitude}} m, hdop: {{each_location.hdop}}').addTo(map);
{% endfor %}
{% for each_gateway in gateway_locations %}
var gateway = L.marker([{{each_gateway[1]}}, {{each_gateway[2]}}]).bindPopup('Gateway: {{each_gateway[0]}}<br />Lat/Lon: {{each_gateway[1]}}, {{each_gateway[2]}}').addTo(map);
{% endfor %}
</script>
</body>
</html>

21
ttn-tracker.service Normal file
View file

@ -0,0 +1,21 @@
[Unit]
Description=gunicorn daemon for ttn-mapper
After=network.target
[Service]
User=root
Group=pi
WorkingDirectory=/home/pi/ttn-tracker
ExecStart=/home/pi/ttn-tracker/env/bin/gunicorn \
--workers 1 \
--worker-class gthread \
--threads 2 \
--timeout 300 \
--pid /var/lock/ttn_tracker_flask.pid \
--bind unix:/var/run/ttn_tracker_flask.sock app:app
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
[Install]
WantedBy=multi-user.target