From 75fcfec070c919cbff885f92273043f377c1eb7a Mon Sep 17 00:00:00 2001 From: AnnasSalman Date: Mon, 7 Dec 2020 04:02:16 +0500 Subject: [PATCH] Distance calculations, Fuel prices scraping+updating and local places to visit added --- app.js | 17 ++++ classes/Coordinates.js | 42 +++++++++ classes/Hotel.js | 27 ++++++ classes/Interests.js | 92 ++++++++++++++++++ classes/Resources.js | 26 ++++++ classes/Route.js | 15 ++- models/resourceConstants.js | 16 ++++ package.json | 4 +- routes/tours.js | 179 ++++++++++++++++++++---------------- 9 files changed, 338 insertions(+), 80 deletions(-) create mode 100644 classes/Coordinates.js create mode 100644 classes/Hotel.js create mode 100644 classes/Interests.js create mode 100644 classes/Resources.js create mode 100644 models/resourceConstants.js diff --git a/app.js b/app.js index 828249d..a359cbc 100644 --- a/app.js +++ b/app.js @@ -13,6 +13,21 @@ var passport = require('passport'); var config = require('./config'); var cors = require('cors'); var authenticate = require('./middlewares/authenticate'); +var CronJob = require('cron').CronJob; +var Resources = require('./classes/Resources') + +const ResourceUpdates = new Resources() + +const updateFuelPrices = new CronJob('00 00 00 * * *', async () => { + try{ + await ResourceUpdates.updatePetrolPrice() + console.log('Updated Petrol Price') + } + catch(e){ + console.log('error updating petrol prices') + } +}, null, true, 'Asia/Karachi'); +updateFuelPrices.start(); if (process.env.NODE_ENV !== 'production') { require('dotenv').config({path: path.resolve( __dirname,'secrets.env')}); @@ -46,6 +61,8 @@ app.use(function(req, res, next) { next(createError(404)); }); + + // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development diff --git a/classes/Coordinates.js b/classes/Coordinates.js new file mode 100644 index 0000000..9ff53dc --- /dev/null +++ b/classes/Coordinates.js @@ -0,0 +1,42 @@ +const axios = require('axios') + +function arePointsNear(checkPoint, centerPoint, km) { + let ky = 40000 / 360; + let kx = Math.cos(Math.PI * centerPoint.lat / 180.0) * ky; + let dx = Math.abs(centerPoint.lng - checkPoint.lng) * kx; + let dy = Math.abs(centerPoint.lat - checkPoint.lat) * ky; + return Math.sqrt(dx * dx + dy * dy) <= km; +} + +class Coordinates { + + constructor(coordinates) { + this.coordinates = coordinates + } + + async getNearbyPlaces(query, radius, type){ + const places = await axios.request({ + method: 'get', + url: 'https://maps.googleapis.com/maps/api/place/textsearch/json', + params: { + query: query, + key: process.env.mapsKey, + location: this.coordinates.lat + ',' + this.coordinates.lng, + radius: 3500, + type: type + } + }) + const data = places.data.results + const placeResults = [] + data.forEach((searchResult)=>{ + const checkPoint = searchResult.geometry.location + const currentPoint = this.coordinates + if(arePointsNear(checkPoint, currentPoint, radius)){ + placeResults.push(searchResult) + } + }) + return placeResults + } +} + +module.exports = Coordinates diff --git a/classes/Hotel.js b/classes/Hotel.js new file mode 100644 index 0000000..5a81e7f --- /dev/null +++ b/classes/Hotel.js @@ -0,0 +1,27 @@ +const hotelSearch = require('../models/hotel') +const Booking = require('../models/booking') + +class Hotel{ + constructor() { + + } + + async searchHotel(){ + const hotels = await hotelSearch.find().getNearbyHotels(33.906528,73.393692,15000) + const hotelIdArray = hotels.map((hotelObject)=>( + hotelObject._id + )) + const available = await Booking.find({ + guestlimit: {$gt: 2}, + price: {$gte: 0, $lte: 10000}, + // bookings: { + // $not: { + // $elemMatch: {from: {$lt: req.query.to.substring(0,10)}, to: {$gt: req.query.from.substring(0,10)}} + // } + // } + }).where('hotelid').in(hotelIdArray).exec(); + return {leength: available.length} + } +} + +module.exports = Hotel diff --git a/classes/Interests.js b/classes/Interests.js new file mode 100644 index 0000000..4fb074b --- /dev/null +++ b/classes/Interests.js @@ -0,0 +1,92 @@ +const Coordinates = require('./Coordinates') + +const availableInterests = { + sightseeing: { + searchQuery: 'sightseeing', + searchType: 'tourist_attraction', + validTypes: ['tourist_attraction'], + invalidTypes: [], + }, + hiking: { + searchQuery: 'hiking trails', + searchType: '', + validTypes: ['point_of_interest', 'park'], + invalidTypes: ['tourist_attraction'] + }, + shopping: { + searchQuery: 'shopping mall', + searchType: 'shopping_mall', + validTypes: ['shopping_mall'], + invalidTypes: [], + }, + boating: { + searchQuery: 'boating', + searchType: 'point_of_interest', + validTypes: ['point_of_interest'], + invalidTypes: ['travel_agency', 'mosque'], + }, + historical: { + searchQuery: 'historical', + searchType: '', + validTypes: ['point_of_interest'], + invalidTypes: [], + }, + entertainment: { + searchQuery: 'entertainment', + searchType: '', + validTypes: ['point_of_interest'], + invalidTypes: [], + }, + wildlife: { + searchQuery: 'zoo', + searchType: '', + validTypes: ['zoo'], + invalidTypes: [], + }, + museums: { + searchQuery: 'museums', + searchType: 'museum', + validTypes: ['museum'], + invalidTypes: [], + }, + lakes: { + searchQuery: 'lakes', + searchType: '', + validTypes: ['natural_feature', 'park'], + invalidTypes: [] + } +} + +class Interests{ + + constructor(hobbies) { + this.hobbies = hobbies + } + + async getPlaceRecommendations(coordinates){ + const coordinate = new Coordinates(coordinates) + const recommendedPlaces = [] + // Find places of interest according to each hobby + for(const hobby of this.hobbies){ + const places = await coordinate.getNearbyPlaces(availableInterests[hobby].searchQuery, + 15, availableInterests[hobby].searchType) + const filteredPlaces = [] + places.forEach((place, index)=>{ + // Filter out places according to relevant place types. + if(place.types.some(r=> availableInterests[hobby].validTypes.indexOf(r) >= 0) && + !place.types.some(r=> availableInterests[hobby].invalidTypes.indexOf(r) >= 0) && + place.user_ratings_total > 3 && place.rating>2){ + filteredPlaces.push({...place, score: place.user_ratings_total*place.rating*(1/(index+1)), matches: hobby}) //Calculate places score by total ratings * rating. + } + }) + recommendedPlaces.push(...filteredPlaces) + } + // Sort Places according to their score. + recommendedPlaces.sort((a, b)=>{ + return b.score - a.score + }) + return recommendedPlaces + } +} + +module.exports = Interests diff --git a/classes/Resources.js b/classes/Resources.js new file mode 100644 index 0000000..350cfbb --- /dev/null +++ b/classes/Resources.js @@ -0,0 +1,26 @@ +const puppeteer = require('puppeteer') +const ResourceConstants = require('../models/resourceConstants') + +class Resources{ + + constructor() { + } + + async updatePetrolPrice () { + const browser = await puppeteer.launch() + const page = await browser.newPage() + await page.goto('https://psopk.com/en/product-and-services/product-prices/pol') + + const [el] = await page.$x('/html/body/div[2]/div[4]/div/div/div/div[2]/div/div[1]/table/tbody/tr[2]/td[2]') + const src = await el.getProperty('textContent') + const srcText = await src.jsonValue() + await ResourceConstants.updateOne( { resourceName : 'petrolPrice'}, {resourceName: 'petrolPrice', value : srcText, updatedAt: new Date() }, { upsert : true }) + } + + async getPetrolPrices () { + const price = await ResourceConstants.findOne({ resourceName: 'petrolPrice' }, 'value').exec(); + return parseFloat(price.value) + } +} + +module.exports = Resources diff --git a/classes/Route.js b/classes/Route.js index bf1832a..0e706c7 100644 --- a/classes/Route.js +++ b/classes/Route.js @@ -4,12 +4,25 @@ class Route{ //set of string coordinates separated by | //33.693852,73.065305|33.591368,73.053589|33.916725,73.396740|34.072142,73.386269 - //First one are the source coordinates + //First ones are the source coordinates constructor(coordinates) { this._coordinates = coordinates console.log(coordinates) } + async getDistance(){ + const distance = await axios.request({ + url:'https://maps.googleapis.com/maps/api/distancematrix/json', + method: 'get', + params: { + origins: this._coordinates, + destinations: this._coordinates, + key: process.env.mapsKey + } + }) + return distance.data.rows[0].elements[1].distance.value + } + async calculateShortestTrip(){ try{ const distances = await axios.request({ diff --git a/models/resourceConstants.js b/models/resourceConstants.js new file mode 100644 index 0000000..37f63ad --- /dev/null +++ b/models/resourceConstants.js @@ -0,0 +1,16 @@ +let mongoose = require('mongoose'); +let Schema = mongoose.Schema; + +let resourceConstants = new Schema({ + resourceName: { + type: 'String', + }, + value: { + type: 'String' + }, + updatedAt:{ + type: 'String' + } +}); + +module.exports = mongoose.model('resourceSources', resourceConstants); diff --git a/package.json b/package.json index 74b4251..3267dfc 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "axios": "^0.20.0", "cookie-parser": "~1.4.4", "cors": "^2.8.5", + "cron": "^1.8.2", "debug": "~2.6.9", "dotenv": "^8.2.0", "expo-secure-store": "^9.0.1", @@ -27,6 +28,7 @@ "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "passport-local-mongoose": "^6.0.1", - "path-to-regexp": "^6.1.0" + "path-to-regexp": "^6.1.0", + "puppeteer": "^5.5.0" } } diff --git a/routes/tours.js b/routes/tours.js index 76419f7..222ffbb 100644 --- a/routes/tours.js +++ b/routes/tours.js @@ -5,91 +5,46 @@ const Route = require('../classes/Route') const Plan = require('../classes/Plan') const Booking = require('../models/booking') const Hotel = require('../models/hotel') +const puppeteer = require('puppeteer') +const demo = require('../demo/demo') +const Interests = require('../classes/Interests') +const HotelSearch = require('../classes/Hotel') +const Resources = require('../classes/Resources') + +router.get('/getfuelprice', async (req, res)=>{ + const scrapeFuelPrice = async(url) => { + const browser = await puppeteer.launch() + const page = await browser.newPage() + await page.goto(url) + + const [el] = await page.$x('/html/body/div[2]/div[4]/div/div/div/div[2]/div/div[1]/table/tbody/tr[2]/td[2]') + const src = await el.getProperty('textContent') + const srcText = await src.jsonValue() + return srcText + } + try{ + // const Hotelsearch = new HotelSearch() + // const result = await Hotelsearch.searchHotel() + // res.send(result) + // const price = await scrapeFuelPrice('https://psopk.com/en/product-and-services/product-prices/pol') + // res.send(price) + } + catch(e){ + res.status(200).send(e) + } +}) router.get('/generatetour', async(req, res)=>{ - const {coordinates, dates, budget, hobbies} = req.query + const {coordinates, dates, budget, hobbies, fuelAverage} = req.query let coordinateString = '' coordinates.forEach((coordinate)=>{ coordinateString+=coordinate+'|' }) const route = new Route(coordinateString) - //const route = new Route('33.693852,73.065305|33.916725,73.396740|34.072142,73.386269|33.591368,73.053589|') try{ + + // GENERAL PLAN CREATION const shortestTrip = await route.calculateShortestTrip() - // const shortestTrip = [ - // { - // name: "Islamabad Expressway, Islamabad, Islamabad Capital Territory, Pakistan", - // type: "origin", - // distance: { - // text: "0 km", - // value: 0 - // }, - // duration: { - // text: "0 mins", - // value: 0 - // }, - // geometry: { - // coordinates: { - // lat: 33.693852, - // lng: 73.065305 - // } - // } - // }, - // { - // name: "Murree Rd, Rawalpindi, Punjab 46000, Pakistan", - // type: "origin", - // distance: { - // text: "13.3 km", - // value: 13274 - // }, - // duration: { - // text: "21 mins", - // value: 1277 - // }, - // geometry: { - // coordinates: { - // lat: 33.591368, - // lng: 73.053589 - // } - // } - // }, - // { - // name: "Kashmir Rd, Murree, Rawalpindi, Khyber Pakhtunkhwa, Pakistan", - // type: "origin", - // distance: { - // text: "59.7 km", - // value: 59695 - // }, - // duration: { - // text: "1 hour 45 mins", - // value: 6301 - // }, - // geometry: { - // coordinates: { - // lat: 33.916725, - // lng: 73.396740 - // } - // } - // }, - // { - // name: "Nathia Gali Rd, Nathia Gali, Abbottabad, Khyber Pakhtunkhwa, Pakistan", - // type: "origin", - // distance: { - // text: "32.2 km", - // value: 32192 - // }, - // duration: { - // text: "1 hour 14 mins", - // value: 4457 - // }, - // geometry: { - // coordinates: { - // lat: 34.072142, - // lng: 73.386269 - // } - // } - // } - // ] const plan = new Plan(dates.length,15000, shortestTrip) const tour = await plan.generateTour() let dateSchedule = {} @@ -100,9 +55,77 @@ router.get('/generatetour', async(req, res)=>{ temp.push(location) } }) - dateSchedule = {...dateSchedule, [date]: [{locationstoVisit: temp, index: index+1}]} + dateSchedule = {...dateSchedule, [date]: [{locationstoVisit: temp, index: index+1, date}]} + }) + + // PLACES TO VISIT DURING STAY + const placesToVisit = tour.route + // const placesToVisit = demo.tour.route + // const dateSchedule = demo.dateSchedule + const stays = [] + placesToVisit.forEach((place, index)=>{ + if (place.stayDuration > 1) { + stays.push({index, place}) + } }) - res.send({tour: tour, dateSchedule}) + console.log('Stays=='+ stays) + for(const stay of stays){ + const tourInterests = new Interests(hobbies) + const placesOfInterest = await tourInterests.getPlaceRecommendations(stay.place.geometry.coordinates) + placesToVisit[stay.index].availablePlaces = placesOfInterest + let selectedLocalLocationIndex = 0 + let tempFix = 0 + stay.place.tourDays.length>3?tempFix=2:tempFix=1 + for(let i = 1; i < stay.place.tourDays.length - tempFix; i++){ + dateSchedule[dates[stay.place.tourDays[i]]][0].localAvailableLocations = placesOfInterest + dateSchedule[dates[stay.place.tourDays[i]]][0].localSelectedLocations = placesOfInterest[selectedLocalLocationIndex] + selectedLocalLocationIndex+=1 + } + } + + // TOUR DISTANCE CALCULATION + let tourWithDistances = {tour: {route: placesToVisit}, dateSchedule} + + // Adding distance for all dates in DateSchedule (local trips + travels) + for(const date of dates){ + if(tourWithDistances.dateSchedule[date][0].locationstoVisit.length>1){ + let distanceCovered = 0 + for(let i = 0; i{ + totalTourDistance += tourWithDistances.dateSchedule[date][0].distanceCovered + if (tourWithDistances.dateSchedule[date][0].localSelectedLocations) { + totalTourDistance += tourWithDistances.dateSchedule[date][0].localSelectedLocations.distanceCovered + } + }) + tourWithDistances = {...tourWithDistances, totalTourDistance} + + // Get fuel prices and calculate total tour expenditure on fuel (approx) + const resources = new Resources() + const fuelPrice = await resources.getPetrolPrices() + const expenditureOnFuel = ((totalTourDistance/1000)/fuelAverage) * fuelPrice + + + res.send(tourWithDistances) + + } catch(e){ console.log(e)