From 2e8ea7cbc170ec59d4b481996a8ca5660023830f Mon Sep 17 00:00:00 2001 From: Israel Figueroa Date: Mon, 3 Feb 2025 02:36:54 -0300 Subject: [PATCH] test --- tpmc_qr.py | 6 ++ tpmcqr_service/__init__.py | 44 ++++++++++++ tpmcqr_service/api/__init__.py | 0 tpmcqr_service/api/parada.py | 13 ++++ tpmcqr_service/api/utils.py | 106 +++++++++++++++++++++++++++++ tpmcqr_service/config.py | 13 ++++ tpmcqr_service/content/__init__.py | 0 tpmcqr_service/content/paleta.py | 7 ++ tpmcqr_service/error/__init__.py | 0 tpmcqr_service/error/handlers.py | 20 ++++++ tpmcqr_service/models/__init__.py | 0 tpmcqr_service/models/gtfs.py | 91 +++++++++++++++++++++++++ tpmcqr_service/requirements.txt | 31 +++++++++ 13 files changed, 331 insertions(+) create mode 100644 tpmc_qr.py create mode 100644 tpmcqr_service/__init__.py create mode 100644 tpmcqr_service/api/__init__.py create mode 100644 tpmcqr_service/api/parada.py create mode 100644 tpmcqr_service/api/utils.py create mode 100644 tpmcqr_service/config.py create mode 100644 tpmcqr_service/content/__init__.py create mode 100644 tpmcqr_service/content/paleta.py create mode 100644 tpmcqr_service/error/__init__.py create mode 100644 tpmcqr_service/error/handlers.py create mode 100644 tpmcqr_service/models/__init__.py create mode 100644 tpmcqr_service/models/gtfs.py create mode 100644 tpmcqr_service/requirements.txt diff --git a/tpmc_qr.py b/tpmc_qr.py new file mode 100644 index 0000000..c7692c0 --- /dev/null +++ b/tpmc_qr.py @@ -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') diff --git a/tpmcqr_service/__init__.py b/tpmcqr_service/__init__.py new file mode 100644 index 0000000..8e45ff5 --- /dev/null +++ b/tpmcqr_service/__init__.py @@ -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 diff --git a/tpmcqr_service/api/__init__.py b/tpmcqr_service/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tpmcqr_service/api/parada.py b/tpmcqr_service/api/parada.py new file mode 100644 index 0000000..48e3ba0 --- /dev/null +++ b/tpmcqr_service/api/parada.py @@ -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/') +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 diff --git a/tpmcqr_service/api/utils.py b/tpmcqr_service/api/utils.py new file mode 100644 index 0000000..688e125 --- /dev/null +++ b/tpmcqr_service/api/utils.py @@ -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 diff --git a/tpmcqr_service/config.py b/tpmcqr_service/config.py new file mode 100644 index 0000000..77db5d1 --- /dev/null +++ b/tpmcqr_service/config.py @@ -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') diff --git a/tpmcqr_service/content/__init__.py b/tpmcqr_service/content/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tpmcqr_service/content/paleta.py b/tpmcqr_service/content/paleta.py new file mode 100644 index 0000000..fb644ad --- /dev/null +++ b/tpmcqr_service/content/paleta.py @@ -0,0 +1,7 @@ +from flask import Blueprint, jsonify + +paleta = Blueprint('paleta', __name__) + +@paleta.route('/') +def parada(): + return jsonify(message="Placeholder for JS page") diff --git a/tpmcqr_service/error/__init__.py b/tpmcqr_service/error/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tpmcqr_service/error/handlers.py b/tpmcqr_service/error/handlers.py new file mode 100644 index 0000000..6cf93f4 --- /dev/null +++ b/tpmcqr_service/error/handlers.py @@ -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 + + diff --git a/tpmcqr_service/models/__init__.py b/tpmcqr_service/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tpmcqr_service/models/gtfs.py b/tpmcqr_service/models/gtfs.py new file mode 100644 index 0000000..e3aaef0 --- /dev/null +++ b/tpmcqr_service/models/gtfs.py @@ -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 ) diff --git a/tpmcqr_service/requirements.txt b/tpmcqr_service/requirements.txt new file mode 100644 index 0000000..869b59e --- /dev/null +++ b/tpmcqr_service/requirements.txt @@ -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 +