diff --git a/app/api/v1/cruds.py b/app/api/v1/cruds.py index 11dca54..373770e 100644 --- a/app/api/v1/cruds.py +++ b/app/api/v1/cruds.py @@ -1,83 +1,68 @@ -import json import random -import mysql.connector as mydb import numpy as np from sqlalchemy.orm import Session -import httpx from app.api.v1 import models from app.api.v1 import schemas from app.api.v1.helpers import dist_on_sphere -from app.config import app_settings -# TODO: should be used with ORM -conn = mydb.connect( - host=app_settings.MYSQL_HOST, - user=app_settings.MYSQL_USER, - password=app_settings.MYSQL_PASSWORD, - database=app_settings.MYSQL_DATABASE, - charset="utf8" -) - - -def get_airports_from_db(db: Session) -> schemas.Airport: - return db.query(models.Airport).limit(5).all() - - -def get_destination(): +# Bind cruds functions for return results to router +def get_destination(db: Session, req: schemas.SearchRequestBody): #--- get ajax POST data - time_limit = httpx.json()["time_limit"] - expense_limit = httpx.json()["expense_limit"] - current_lat = httpx.json()["current_lat"] - current_lng = httpx.json()["current_lng"] - print("main.py ajax POST data - time_limit: " + time_limit) - print("main.py ajax POST data - expense_limit: " + expense_limit) - print("main.py ajax POST data - current_lat: " + current_lat) - print("main.py ajax POST data - current_lng: " + current_lng) + print(f'User conditions: {req}') #--- search and get near airport from MySQL (airport table) - near_airport_IATA = get_near_airport(current_lat,current_lng) - print("main.py get values - near_airport_IATA: " + near_airport_IATA) + near_airport_IATA = get_near_airport( + db=db, + lat=req.current_lat, + lng=req.current_lng + ) + print("near_airport_IATA: " + near_airport_IATA) #--- search and get reachable location (airport and country) from skyscanner api #--- exclude if time and travel expenses exceed the user input parameter #--- select a country at random - destination = get_destination_from_skyscanner_by_random(near_airport_IATA,time_limit,expense_limit) - return destination + destination = get_destination_from_skyscanner_by_random( + db=db, + iata=near_airport_IATA + ) + print('Destination: ') + print(destination) + return schemas.SearchResultResponseBody(**destination) -# --- search and get near airport from MySQL (airport table) -def get_near_airport(current_lat,current_lng): - conn.ping(reconnect=True) - cur = conn.cursor() - - print("gacha.py get values - current_lat: " + current_lat) - print("gacha.py get values - current_lng: " + current_lng) - - current = float(current_lat), float(current_lng) +# --- search and get near airport from MySQL (airport table) +def get_near_airport(db: Session, lat: float, lng: float) -> str: target = [] dist_result = [] search_key = [] count = 0 - cur.execute('select id,IATA,Name,Country,City,Latitude,Longitude from airport where not IATA="NULL"') - - for sql_result in cur.fetchall(): - target = sql_result[5], sql_result[6] - dist = dist_on_sphere(current, target) - dist_result.append([count,sql_result[0],sql_result[1],sql_result[2],sql_result[3],sql_result[4],dist]) + airports = db.query( + models.Airport.id, + models.Airport.IATA, + models.Airport.name, + models.Airport.country, + models.Airport.city, + models.Airport.latitude, + models.Airport.longitude + ).filter( + models.Airport.IATA != "NULL" + ).all() + + for airport in airports: + target = airport[5], airport[6] + dist = dist_on_sphere( + pos0=(lat, lng), + pos1=target + ) + dist_result.append([count,airport[0],airport[1],airport[2],airport[3],airport[4],dist]) search_key.append(dist) count = count + 1 - cur.close() - conn.close() - - #print(dist_result) - #print(search_key) - #--- return near airport IATA return dist_result[np.argmin(search_key)][2] @@ -85,67 +70,58 @@ def get_near_airport(current_lat,current_lng): #--- search and get reachable location (airport and country) from skyscanner api #--- exclude if time and travel expenses exceed the user input parameter #--- select a country at random -def get_destination_from_skyscanner_by_random(near_airport_IATA,time_limit,expense_limit): - - print("gacha.py get values - near_airport_IATA: " + near_airport_IATA) - print("gacha.py get values - time_limit: " + time_limit) - print("gacha.py get values - expense_limit: " + expense_limit) - +def get_destination_from_skyscanner_by_random(db: Session, iata: str) -> dict: # --- search and get reachable location (airport and country) from skyscanner api # --- exclude if time and travel expenses exceed the user input parameter - ########################################################################################## - ##################################### Update required ##################################### - ########################################################################################### - - #reachable_airport_IATA = ["TXL","YTD","CQS","NYR","QFG","NZE","IWK"] - - conn.ping(reconnect=True) - cur = conn.cursor() - - cur.execute('select IATA from airport where not IATA="NULL"') - reachable_airport_IATA = [] - for sql_result in cur.fetchall(): - reachable_airport_IATA.append(sql_result[0]) - - cur.close() - conn.close() - - ########################################################################################## - ########################################################################################## - ########################################################################################## + airport_codes = db.query(models.Airport.IATA).filter(models.Airport.IATA != "NULL").all() + reachable_airport_IATA = [airport_code[0] for airport_code in airport_codes] #--- select a country at random random_airport_IATA = random.choice(reachable_airport_IATA) #--- get lat/lng of near and selected airport from MySQL (airport table) - conn.ping(reconnect=True) - cur = conn.cursor() + transit_airports = db.query( + models.Airport.country, + models.Airport.city, + models.Airport.IATA, + models.Airport.name, + models.Airport.latitude, + models.Airport.longitude, + ).filter( + models.Airport.IATA == iata + ).all() - cur.execute('select Country,City,IATA,Name,Latitude,Longitude from airport where IATA="' + near_airport_IATA + '"') transit = [] - for sql_result in cur.fetchall(): - transit.append([sql_result[0],sql_result[1],sql_result[2],sql_result[3],sql_result[4],sql_result[5]]) + for airport in transit_airports: + transit.append([airport[0],airport[1],airport[2],airport[3],airport[4],airport[5]]) + + destination_airports = db.query( + models.Airport.country, + models.Airport.city, + models.Airport.IATA, + models.Airport.name, + models.Airport.latitude, + models.Airport.longitude, + ).filter( + models.Airport.IATA == random_airport_IATA + ).all() - cur.execute('select Country,City,IATA,Name,Latitude,Longitude from airport where IATA="' + random_airport_IATA + '"') destination = [] - for sql_result in cur.fetchall(): - destination.append([sql_result[0],sql_result[1],sql_result[2],sql_result[3],sql_result[4],sql_result[5]]) - - cur.close() - conn.close() - - return json.dumps({ - "tran_country":transit[0][0], - "tran_city":transit[0][1], - "tran_iata":transit[0][2], - "tran_airport":transit[0][3], - "tran_lat":transit[0][4], - "tran_lng":transit[0][5], - "dest_country":destination[0][0], - "dest_city":destination[0][1], - "dest_iata":destination[0][2], - "dest_airport":destination[0][3], - "dest_lat":destination[0][4], - "dest_lng":destination[0][5] - }) + for airport in destination_airports: + destination.append([airport[0],airport[1],airport[2],airport[3],airport[4],airport[5]]) + + return { + "tran_country": transit[0][0], + "tran_city": transit[0][1], + "tran_iata": transit[0][2], + "tran_airport": transit[0][3], + "tran_lat": transit[0][4], + "tran_lng": transit[0][5], + "dest_country": destination[0][0], + "dest_city": destination[0][1], + "dest_iata": destination[0][2], + "dest_airport": destination[0][3], + "dest_lat": destination[0][4], + "dest_lng": destination[0][5] + } diff --git a/app/api/v1/models.py b/app/api/v1/models.py index bfb008a..1d2e6e6 100644 --- a/app/api/v1/models.py +++ b/app/api/v1/models.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, String, Integer +from sqlalchemy import Column, String, Integer, Float from app.database import Base @@ -7,3 +7,16 @@ class Airport(Base): __tablename__ = 'airport' id: int = Column(Integer, primary_key=True) + name: str = Column(String(74)) + city: str = Column(String(35)) + country: str = Column(String(34)) + IATA: str = Column(String(3)) + ICAO: str = Column(String(4)) + latitude: float = Column(Float) + longitude: float = Column(Float) + altitude: int = Column(Integer) + tz_offset: float = Column(Float) + DST: str = Column(String(1)) + tz_dbtime: str = Column(String(32)) + types: str = Column(String(13)) + datasource: str = Column(String(13)) diff --git a/app/api/v1/routers.py b/app/api/v1/routers.py index 20700f5..ed3452e 100644 --- a/app/api/v1/routers.py +++ b/app/api/v1/routers.py @@ -6,37 +6,37 @@ from app.api.v1 import services from app.api.v1 import cruds from app.api.v1 import schemas -from app.database import get_db +from app.api.v1 import models +from app.database import get_db, engine router = APIRouter() +# Create table if not exists +models.Base.metadata.create_all(bind=engine) @router.get('/') def index(req: Request) -> schemas.RootResponse: - return { + return JSONResponse(content={ "path": req.url.path, "detail": "v1 API root" - } + }) -@router.get('/airports') -def get_airports(db: Session = Depends(get_db)) -> list[schemas.Airport]: - return cruds.get_airports_from_db(db=db) - - -@router.get('/search') -def get_flight_search_result(): - return cruds.get_destination() +@router.get('/fetch') +def fetch() -> Response: + return services.load_google_map() @router.post('/shuffle') -def get_random_country(): - return services.get_random_country() +def get_random_country( + payload: schemas.SearchRequestBody, + db: Session = Depends(get_db) +) -> schemas.SearchResultResponseBody: + country = services.get_random_country() + print(f'Randomly selected country: {country}') -@router.get('/fetch') -def fetch() -> Response: - return services.load_google_map() + return cruds.get_destination(db=db, req=payload) @router.post('/translate') diff --git a/app/api/v1/schemas.py b/app/api/v1/schemas.py index df0d747..112e1eb 100644 --- a/app/api/v1/schemas.py +++ b/app/api/v1/schemas.py @@ -14,12 +14,48 @@ class TranslateReqBody(BaseModel): country: str +class SearchRequestBody(BaseModel): + time_limit: int + expense_limit: int + current_lat: float + current_lng: float + + +class SearchResultResponseBody(BaseModel): + dest_country: str + dest_city: str + dest_iata: str + dest_airport: str + dest_lat: float + dest_lng: float + tran_country: str + tran_city: str + tran_iata: str + tran_airport: str + tran_lat: float + tran_lng: float + + +# Database Model class AirportBase(BaseModel): pass class Airport(AirportBase): id: int + name: str + city: str + country: str + IATA: str + ICAO: str + latitude: float + longitude: float + altitude: int + tz_offset: float + DST: str + tz_dbtime: str + types: str + datasource: str class Config: orm_mode = True diff --git a/app/api/v1/services.py b/app/api/v1/services.py index 6504204..de57fbf 100644 --- a/app/api/v1/services.py +++ b/app/api/v1/services.py @@ -41,8 +41,7 @@ def translate_county_name(txt: TranslateReqBody) -> str: return resp.get('data').get('translations')[0].get('translatedText') -def get_random_country(): - # import data +def get_random_country() -> str: try: url = 'https://restcountries.com/v3.1/all?fields=region,name' data = httpx.get(url).json() @@ -60,7 +59,6 @@ def get_random_country(): region.append(data[x]['region']) region_result = random.choice(list(set(region))) - print(f"Region selected: {region_result}") # Select country randomly country = [] @@ -69,6 +67,5 @@ def get_random_country(): country.append(data[x]['name']['official']) country_result = random.choice(country) - print(f"Country selected: {country_result}") return country_result diff --git a/app/static/css/style.css b/app/static/css/style.css index c992931..efb9b39 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -10,13 +10,13 @@ body { } .menu-wrapper { - top:3%; + top: 3%; } img.bot-icon { - width:40px; - margin-right:5px; - margin-bottom:5px; + width: 40px; + margin-right: 5px; + margin-bottom: 5px; border-radius: 50%; } @@ -33,12 +33,31 @@ h4.country { display:none; } +p#city { + color:dodgerblue; + display:none; +} + +p#search { + display:none; +} + +#detail { + margin-bottom: 10px; + display:none; +} + +.detailed-card { + text-align: left; + padding: 10px; +} + .user-opt { - padding:5px; + padding: 5px; } .shuffle-button { - padding:5px; + padding: 5px; } button.shuffle { diff --git a/app/static/js/script.js b/app/static/js/script.js index b73e230..45cfe19 100644 --- a/app/static/js/script.js +++ b/app/static/js/script.js @@ -10,6 +10,10 @@ window.onload = async function() { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( function(position) { + // Set current latitude/longitude to sessionStorage by Location Service + sessionStorage.setItem("latitude", position.coords.latitude); + sessionStorage.setItem("longitude", position.coords.longitude); + var mapLatLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude); var mapOptions = { zoom : 3, @@ -65,68 +69,127 @@ function executeShuffle(){ document.getElementById("country").innerHTML = ""; document.getElementById("country-ja").innerHTML = ""; + //--- get user input parameter + var time_limit = document.getElementById("time").value; + var expense_limit = document.getElementById("amount").value; + console.log("time_limit: " + time_limit + " hour"); + console.log("expense_limit: " + expense_limit + " $"); + + //--- get sessionStorage + var current_lat = sessionStorage.getItem('latitude'); + var current_lng = sessionStorage.getItem('longitude'); + + document.getElementById("city").style.display = "none"; + document.getElementById("search").style.display = "inline-block"; + document.getElementById("detail").style.display = "none"; + + document.getElementById("search").innerHTML = "Search..."; + + $('#submit').toggleClass('d-none').toggleClass('d-inline-block'); + $('#load').toggleClass('d-none').toggleClass('d-inline-block'); + + $.ajax({ type: 'POST', url: '/api/v1/shuffle', - data: '', + contentType:'application/json', + data: JSON.stringify({ + "time_limit":time_limit, + "expense_limit":expense_limit, + "current_lat":current_lat, + "current_lng":current_lng + }) }) .done(function(result) { + console.log(result); + + //--- display the location of random selected country on a google map. + var current_LatLng = new google.maps.LatLng(current_lat, current_lng); + var transit_LatLng = new google.maps.LatLng(result["tran_lat"], result["tran_lng"]); + var destination_LatLng = new google.maps.LatLng(result["dest_lat"], result["dest_lng"]); + + console.log("current: " + "[latlng]" + current_LatLng); + console.log("transit: " + "[country]" + result["tran_country"] + " [airport]" + result["tran_airport"] + " [latlng]" + transit_LatLng); + console.log("destination: " + "[country]" + result["dest_country"] + " [airport]" + result["dest_airport"] + " [latlng]" + destination_LatLng); + + //var bounds = new google.maps.LatLngBounds(); + + var mapOptions = { + zoom: 3, + center: destination_LatLng + }; + + var map = new google.maps.Map(document.getElementById("map"),mapOptions); + var marker = new google.maps.Marker({ + map : map, + position : current_LatLng + }); + //bounds.extend (marker.position); + var marker = new google.maps.Marker({ + map : map, + position : transit_LatLng + }); + //bounds.extend (marker.position); + var marker = new google.maps.Marker({ + map : map, + position : destination_LatLng + }); + //bounds.extend (marker.position); + var flightPath = new google.maps.Polyline({ + path: [ + current_LatLng, + transit_LatLng, + destination_LatLng + ], + geodesic: true, + strokeColor: '#ff0000', + strokeOpacity: 1.0, + strokeWeight: 2 + }); + flightPath.setMap(map); + //map.fitBounds (bounds); + + + //--- output the result of random selected country to pallet + document.getElementById("country").style.display = "inline-block"; + document.getElementById("city").style.display = "inline-block"; + document.getElementById("search").style.display = "none"; + document.getElementById("detail").style.display = "inline-block"; + + document.getElementById("country").innerHTML = result["dest_country"]; + document.getElementById("city").innerHTML = ' City ' + result["dest_city"]; + $('#collapseOne').removeClass('collapse show'); + $('#collapseOne').addClass('collapse'); + $("#detail-item").empty(); + $("#detail-item").append('
  • your location
    ' + current_LatLng + '
  • '); + $("#detail-item").append('
  • origin
    ' + 'Country: ' + result["tran_country"] + '
    City: ' + result["tran_city"] + '
    Airport: ' + '[' + result["tran_iata"] + '] ' + result["tran_airport"] + '
    Departure time: XX:XX' + '
  • '); + $("#detail-item").append('
  • destination
    ' + 'Country: ' + result["dest_country"] + '
    City: ' + result["dest_city"] + '
    Airport: ' + '[' + result["dest_iata"] + '] ' + result["dest_airport"] + '
    Arrival time : XX:XX' + '
  • '); + $("#detail-item").append('
  • travel hour
    ' + 'XXXXXX Hour' + '
  • '); + $("#detail-item").append('
  • travel expense
    ' + 'XXXXXX USD' + '
  • '); + + $('#submit').toggleClass('d-none').toggleClass('d-inline-block'); + $('#load').toggleClass('d-none').toggleClass('d-inline-block'); - // Output the result of selecting a country at random. - const output = result; - document.getElementById("country").innerHTML = output; const xhr = new XMLHttpRequest(); const url = '/api/v1/translate'; - xhr.open('POST', url, true); xhr.setRequestHeader("Content-Type", "application/json"); - xhr.send(JSON.stringify({"country": result})); + xhr.send(JSON.stringify({ + "country": result["dest_country"] + })); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { - document.getElementById("country-ja").innerHTML = " " + JSON.parse(xhr.responseText); + const countryTranslated = JSON.parse(xhr.responseText); + document.getElementById("country-ja").style.display = "inline-block"; + document.getElementById("country-ja").innerHTML = ' ' + countryTranslated; } } - - // Display the location of the selected country on a google map. - const geocoder = new google.maps.Geocoder(); - - geocoder.geocode({ - address: result - }, function(results, status) { - if (status == google.maps.GeocoderStatus.OK) { - - var bounds = new google.maps.LatLngBounds(); - - for (var i in results) { - if (results[0].geometry) { - var latlng = results[0].geometry.location; - var address = results[0].formatted_address; - var mapOptions = { - zoom : 3, - center : latlng - }; - var map = new google.maps.Map( - document.getElementById("map"), - mapOptions - ); - var marker = new google.maps.Marker({ - map : map, - position : latlng - }); - } - } - } else if (status == google.maps.GeocoderStatus.ZERO_RESULTS) { - alert("not found."); - } else { - console.log(status); - alert("error."); - } - }); }) - .fail(function(){ + .fail(function() { document.getElementById("country").innerHTML = "fail"; + $('#submit').toggleClass('d-none').toggleClass('d-inline-block'); + $('#load').toggleClass('d-none').toggleClass('d-inline-block'); }); } - diff --git a/app/templates/index.html.mako b/app/templates/index.html.mako index fb4f96b..3cf7204 100644 --- a/app/templates/index.html.mako +++ b/app/templates/index.html.mako @@ -33,7 +33,8 @@

    - +

    +
    @@ -55,9 +56,33 @@
    + +
    +
    + + +
    +
    +
    + +
    +
    +
      +
      +
      + diff --git a/sql/import.sql b/sql/import.sql index 6fe8bca..8fde6d2 100644 --- a/sql/import.sql +++ b/sql/import.sql @@ -2,22 +2,21 @@ CREATE DATABASE IF NOT EXISTS `rt` DEFAULT CHARACTER SET utf8; use `rt`; -CREATE TABLE `airport`( +CREATE TABLE IF NOT EXISTS `airport`( `id` int AUTO_INCREMENT, - `Name` varchar(74) DEFAULT NULL, - `City` varchar(35) DEFAULT NULL, - `Country` varchar(34) DEFAULT NULL, + `name` varchar(74) DEFAULT NULL, + `city` varchar(35) DEFAULT NULL, + `country` varchar(34) DEFAULT NULL, `IATA` varchar(16) DEFAULT NULL, `ICAO` varchar(6) DEFAULT NULL, - `Latitude` double(9,6) DEFAULT NULL, - `Longitude` double(9,6) DEFAULT NULL, - `Altitude` varchar(19) DEFAULT NULL, - `Timezone` varchar(5) DEFAULT NULL, + `latitude` double(9,6) DEFAULT NULL, + `longitude` double(9,6) DEFAULT NULL, + `altitude` varchar(19) DEFAULT NULL, + `tz_offset` varchar(5) DEFAULT NULL, `DST` varchar(3) DEFAULT NULL, - `Tz database time` varchar(32) DEFAULT NULL, - `zone` varchar(21) DEFAULT NULL, - `Type` varchar(13) DEFAULT NULL, - `Source` varchar(13) DEFAULT NULL, + `tz_dbtime` varchar(32) DEFAULT NULL, + `types` varchar(13) DEFAULT NULL, + `datasource` varchar(13) DEFAULT NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;