main
ifiguero 2025-02-03 02:36:54 -03:00
parent c201ddce1b
commit 2e8ea7cbc1
13 changed files with 331 additions and 0 deletions

6
tpmc_qr.py 100644
View File

@ -0,0 +1,6 @@
from tpmcqr_service import create_app
iapp = create_app()
if __name__ == '__main__':
iapp.run(debug=True, host= '0.0.0.0')

View File

@ -0,0 +1,44 @@
# coding: utf-8
from flask import Flask
from flask.logging import default_handler
from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail
#import logging
import sys
from .config import Config
# Initialize extensions
db = SQLAlchemy()
mail = Mail()
redis_client = FlaskRedis()
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
db.init_app(app)
mail.init_app(app)
redis.init_app(app)
app = Flask(__name__)
app.config.from_object(config_class)
db.init_app(app)
mail.init_app(app)
redis_client.init_app(app)
from tpmcqr_service.api.parada import parada
from tpmcqr_service.api.mapa import mapa
from tpmcqr_service.content.paleta import paleta
# from tpmcqr_service.errors.handlers import errors
app.register_blueprint(parada)
app.register_blueprint(mapa)
app.register_blueprint(paleta)
# app.register_blueprint(errors)
return app

View File

View File

@ -0,0 +1,13 @@
from flask import Blueprint, jsonify
from tpmcqr_service import redis_client
from tpmcqr_service.api.util import calcula_distancias_parada
paleta = Blueprint('paleta', __name__)
@paleta.route('/api/<string:parada>')
def parada(parada=None):
current_file = redis_client.get('current_file')
status, salida_parada = calcula_distancias_parada(current_file, parada)
return jsonify(salida_parada), status

View File

@ -0,0 +1,106 @@
from tpmcqr_service import redis_client, db
from tpmcqr_service.models.gtfs import QRDev, Shapes, find_shape_position
import time
def calcula_distancias_parada(redis_id, parada_id):
salida_parada = dict()
devdb = db.query(QRDev).filter(QRDev.id_dispositivo==parada_id).one_or_none()
if not devdb:
salida_parada['debug'] = 'Invalid Request'
return 400, salida_parada
paradadb = obtiene_datos_parada(devdb.id_paradero)
if not paradadb:
salida_parada['debug'] = 'Parada {}'.format(devdb.id_paradero)
return 200, salida_parada
for key in ['stop_name', 'stop_lat', 'stop_lon']:
salida_parada[key] = getattr(paradadb, key)
salida_parada['lineas'] = []
for id_linea in obtiene_lineas_parada(devdb.id_paradero):
rt_linea = pickle.loads(redis_client.get(id_linea))
info_linea = dict()
for key in ['route_short_name', 'route_long_name', 'route_color', 'route_text_color', 'lur']:
info_linea[key] = rt_linea[key]
info_linea['servicios'] = []
info_linea['recientes'] = []
if 'id_shape' in rt_linea:
parada_pos, parada_distance = find_shape_position(rt_linea['id_shape'], salida_parada['stop_lat'], salida_parada['stop_lon'])
expediciones_enruta = []
expediciones_pasadas = []
for item in rt_linea['servicios'].items():
if item[0] < parada_distance:
expediciones_enruta.append(item)
else:
expediciones_pasadas.append(item)
if len(expediciones_enruta) > 0:
info_linea['servicios'].append( estima_llegada(parada_distance, expediciones_enruta.pop(-1)) )
if len(expediciones_enruta) > 0:
info_linea['servicios'].append( estima_llegada(parada_distance, expediciones_enruta.pop(-1)) )
if len(expediciones_pasadas) > 0:
info_linea['recientes'].append( estima_llegada(parada_distance, expediciones_pasadas.pop(0)) )
if len(expediciones_pasadas) > 0:
info_linea['recientes'].append( estima_llegada(parada_distance, expediciones_pasadas.pop(0)) )
salida_parada['lineas'].append(info_linea)
return 200, salida_parada
def estima_llegada(parada_distance, expedicion):
ts = int(time.time())
data_keys = ['trip_traveled', 'ppu', 'trip_lat', 'trip_lng', 'trip_pos', 'ts']
trip_info = dict(zip(data_keys, expedicion)))
trip_info['drift'] = int(trip_info['ts']) - ts
trip_info['trip_distance'] = parada_distance - int(trip_info['trip_traveled'])
estimator = int(trip_info['trip_distance'] / 5) # 18 Km/h promedio -> 5 m/s
trip_info['debug_estimator'] = estimator
if estimator > 1570: # 26:10 minutos
trip_info['trip_estimator'] = '25-30 minutos'
elif estimator > 1260: # #21 minutos
trip_info['trip_estimator'] = '20-25 minutos'
elif estimator > 950: #15:50 minutos
trip_info['trip_estimator'] = '15-20 minutos'
elif estimator > 640: #10:40 minutos
trip_info['trip_estimator'] = '10-15 minutos'
elif estimator > 330: # 5:30
trip_info['trip_estimator'] = '5-10 minutos'
elif estimator > 200: # 3:20 sec
trip_info['trip_estimator'] = '3 minutos'
elif estimator > 70: #
trip_info['trip_estimator'] = 'Llegando'
elif estimator > 0: #
trip_info['trip_estimator'] = 'En parada'
else: #
salida = ceil(estimator/60)
trip_info['trip_estimator'] = 'Hace {} minutos'.format(salida)
def obtiene_datos_parada(id_paradero):
parada = db.query(Paradero).filter(Paradero.id_paradero==id_paradero).one_or_none()
if parada is None:
return None
return parada
def obtiene_lineas_parada(id_paradero):
lineas = []
for linea in db.query(Trip).filter(Trip.id_paradero == id_paradero).distict(Trip.id_linea).all():
lineas.append(linea.id_linea)
return lineas

View File

@ -0,0 +1,13 @@
import os
class Config:
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI')
MAIL_SERVER = os.getenv('MAIL_SERVER')
MAIL_PORT = os.getenv('MAIL_PORT')
MAIL_USE_TLS = os.getenv('MAIL_TLS')
APPLICATION_ROOT = os.getenv('API_BASE')
MAIL_USERNAME = os.environ.get('EMAIL_USER')
MAIL_PASSWORD = os.environ.get('EMAIL_PASS')
REDIS_URL = os.getenv('REDIS_URL', 'redis://localhost:6379/1')

View File

@ -0,0 +1,7 @@
from flask import Blueprint, jsonify
paleta = Blueprint('paleta', __name__)
@paleta.route('/')
def parada():
return jsonify(message="Placeholder for JS page")

View File

View File

@ -0,0 +1,20 @@
from flask import Blueprint, render_template
errors = Blueprint('errors', __name__)
@errors.app_errorhandler(404)
def error_404(error):
return render_template('errors/404.html'), 404
@errors.app_errorhandler(403)
def error_403(error):
return render_template('errors/403.html'), 403
@errors.app_errorhandler(500)
def error_500(error):
return render_template('errors/500.html'), 500

View File

View File

@ -0,0 +1,91 @@
from tpmcqr_service import db
from sqlalchemy.orm import aliased
from sqlalchemy import any_
from sqlalchemy import func
from geoalchemy2 import Geometry
from zoneinfo import ZoneInfo
class Lineas(db.Model):
__tablename__ = 'linea'
__table_args__ = { 'schema': 'public' }
id_linea = db.Column(db.String(150), primary_key=True )
id_operador = db.Column(db.String(150))
route_short_name = db.Column(db.String(150))
route_long_name = db.Column(db.String(150))
route_color = db.Column(db.String(150))
route_text_color = db.Column(db.String(150))
vigente = db.Column(db.Boolean)
class QRDev(db.Model):
__tablename__ = 'device'
__table_args__ = { 'schema': 'public' }
id_dispositivo = db.Column(db.String(100), primary_key=True )
id_paradero = db.Column(db.String(50))
class Paradero(db.Model):
__tablename__ = 'paredero'
__table_args__ = { 'schema': 'public' }
id_paradero = db.Column(db.String(50), primary_key=True )
stop_name = db.Column(db.String(50))
stop_lat = db.Column(db.Float)
stop_lon = db.Column(db.Float)
class Shapes(db.Model):
__tablename__ = 'gtfs_shape'
__table_args__ = { 'schema': 'public' }
id_gtfs_pk = db.Column(db.Integer, primary_key=True )
id_shape = db.Column(db.String(150))
shape_pt_lat = db.Column(db.Float)
shape_pt_lon = db.Column(db.Float)
shape_pt_sequence = db.Column(db.Integer)
shape_dist_traveled = db.Column(db.Float)
class Trips(Base):
__tablename__ = 'gtfs_trips'
__table_args__ = { 'schema': 'public' }
id_trip = Column(String(150), primary_key=True)
id_linea = Column(String(150))
id_shape = Column(String(150))
service_id = Column(String(50))
def find_shape_position(shape_id, lat, lng):
Shape1 = aliased(Shapes)
Shape2 = aliased(Shapes)
point = func.ST_SetSRID(func.ST_MakePoint(lng, lat), 4326) # Create PostGIS point
segmento = db.query(
Shape1.shape_pt_sequence.label("start_sequence"),
# Shape2.shape_pt_sequence.label("end_sequence"),
Shape1.shape_dist_traveled.label("traveled_start"),
Shape2.shape_dist_traveled.label("traveled_end"),
func.ST_Distance(
func.ST_MakeLine(
func.ST_SetSRID(func.ST_MakePoint(Shape1.shape_pt_lon, Shape1.shape_pt_lat), 4326),
func.ST_SetSRID(func.ST_MakePoint(Shape2.shape_pt_lon, Shape2.shape_pt_lat), 4326)
),
point
).label("distance"),
func.ST_LineLocatePoint( # Compute fractional position on the segment
func.ST_MakeLine(
func.ST_SetSRID(func.ST_MakePoint(Shape1.shape_pt_lon, Shape1.shape_pt_lat), 4326),
func.ST_SetSRID(func.ST_MakePoint(Shape2.shape_pt_lon, Shape2.shape_pt_lat), 4326)
),
point
).label("fraction")
).filter(
Shape1.id_shape == shape_id,
Shape2.id_shape == shape_id,
Shape2.shape_pt_sequence == Shape1.shape_pt_sequence + 1 # Ensure correct segment order
).order_by("distance").first()
avanzado = segmento.traveled_start + segmento.fraction * (segmento.traveled_end - segmento.traveled_start)
return ( segmento.start_sequence, avanzado )

View File

@ -0,0 +1,31 @@
bcrypt==3.1.4
blinker==1.4
certifi==2016.2.28
gunicorn
click==6.7
Flask==1.0
Flask-Bcrypt==0.7.1
Flask-Login==0.4.1
Flask-Mail==0.9.1
Flask-SQLAlchemy==2.3.2
Flask-WTF==0.14.2
itsdangerous==0.24
Jinja2==2.10
MarkupSafe
#Pillow==5.3.0
pycparser==2.18
six==1.11.0
SQLAlchemy==1.2.7
Werkzeug==0.14.1
WTForms==2.1
PyYAML==5.1.2
ua-parser==0.8.0
user-agents==2.0
netaddr==0.7.19
#psycopg2==2.8.4
pwnedpasswords==2.0.0
openpyxl==2.6.4
xlrd==1.2.0
unidecode==1.1.1
uuid==1.30