All the files
41
README.md
|
@ -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
150
app.py
Normal 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
|
@ -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
|
@ -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
|
@ -0,0 +1,5 @@
|
|||
apscheduler
|
||||
gunicorn
|
||||
Flask==0.10.1
|
||||
flask_sqlalchemy
|
||||
requests
|
5
static/esri-leaflet.js
Normal file
1
static/leaflet-bing-layer.min.js
vendored
Normal file
BIN
static/leaflet-measure/assets/cancel.png
Executable file
After ![]() (image error) Size: 397 B |
6
static/leaflet-measure/assets/cancel.svg
Executable 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 |
BIN
static/leaflet-measure/assets/cancel_@2X.png
Executable file
After ![]() (image error) Size: 762 B |
BIN
static/leaflet-measure/assets/check.png
Executable file
After ![]() (image error) Size: 387 B |
5
static/leaflet-measure/assets/check.svg
Executable 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 |
BIN
static/leaflet-measure/assets/check_@2X.png
Executable file
After ![]() (image error) Size: 692 B |
BIN
static/leaflet-measure/assets/focus.png
Executable file
After ![]() (image error) Size: 326 B |
5
static/leaflet-measure/assets/focus.svg
Executable 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 |
BIN
static/leaflet-measure/assets/focus_@2X.png
Executable file
After ![]() (image error) Size: 462 B |
BIN
static/leaflet-measure/assets/leaflet-measure.png
Normal file
After ![]() (image error) Size: 595 KiB |
BIN
static/leaflet-measure/assets/rulers.png
Executable file
After ![]() (image error) Size: 192 B |
6
static/leaflet-measure/assets/rulers.svg
Executable 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 |
BIN
static/leaflet-measure/assets/rulers_@2X.png
Executable file
After ![]() (image error) Size: 277 B |
BIN
static/leaflet-measure/assets/start.png
Executable file
After ![]() (image error) Size: 491 B |
6
static/leaflet-measure/assets/start.svg
Executable 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 |
BIN
static/leaflet-measure/assets/start_@2X.png
Executable file
After ![]() (image error) Size: 1,003 B |
BIN
static/leaflet-measure/assets/trash.png
Executable file
After ![]() (image error) Size: 279 B |
5
static/leaflet-measure/assets/trash.svg
Normal 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 |
BIN
static/leaflet-measure/assets/trash_@2X.png
Executable file
After ![]() (image error) Size: 460 B |
1
static/leaflet-measure/leaflet-measure.css
Normal file
1
static/leaflet-measure/leaflet-measure.js
Normal file
BIN
static/leaflet/images/layers-2x.png
Normal file
After ![]() (image error) Size: 1.2 KiB |
BIN
static/leaflet/images/layers.png
Normal file
After ![]() (image error) Size: 696 B |
BIN
static/leaflet/images/marker-icon-2x.png
Normal file
After ![]() (image error) Size: 2.4 KiB |
BIN
static/leaflet/images/marker-icon.png
Normal file
After ![]() (image error) Size: 1.4 KiB |
BIN
static/leaflet/images/marker-shadow.png
Normal file
After ![]() (image error) Size: 618 B |
13838
static/leaflet/leaflet-src.esm.js
Normal file
1
static/leaflet/leaflet-src.esm.js.map
Normal file
13930
static/leaflet/leaflet-src.js
Normal file
1
static/leaflet/leaflet-src.js.map
Normal file
635
static/leaflet/leaflet.css
Normal 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;
|
||||
}
|
5
static/leaflet/leaflet.js
Normal file
1
static/leaflet/leaflet.js.map
Normal file
110
templates/map.html
Normal 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 = '© ' + osm_Link + ' Contributors',
|
||||
otm_Url = 'http://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
|
||||
otm_Attrib = '© ' + otm_Link + ' Contributors ',
|
||||
bing_Attrib = '© ' + bing_Link + ' Contributors',
|
||||
google_Url = 'http://mt.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',
|
||||
google_Attrib = '© ' + 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
|
@ -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
|