Display certain posts based on User Location - javascript

I'm trying to figure out how to display certain mongoDB posts depending on the user's location. I have set up a search functionality that lets people search for MongoDB posts. I also have figured out how to get a user's location and find a big city near them.
Let's say you're in Washington DC. I want only posts containing Washington DC in the title to show up on a "show" page. I've been unable to figure this out.
Any advice?
Thanks!
Node & MongoDB set up to handle search request:
router.get("/", function(req, res){
if (req.query.search) {
const regex = new RegExp(req.query.search, 'i');
Deals.find({ "name": regex }, function(err, founddeals) {
if(err){
console.log(err);
} else {
res.render("deals/index",{deals:founddeals});
}
});
}
Set up to get the user's location.
This also returns the city nearest to the user
// Get User's Coordinate from their Browser
window.onload = function() {
// HTML5/W3C Geolocation
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(UserLocation);
}
// Default to Washington, DC
else
NearestCity(38.8951, -77.0367);
}
// Callback function for asynchronous call to HTML5 geolocation
function UserLocation(position) {
NearestCity(position.coords.latitude, position.coords.longitude);
}
// Convert Degress to Radians
function Deg2Rad(deg) {
return deg * Math.PI / 180;
}
function PythagorasEquirectangular(lat1, lon1, lat2, lon2) {
lat1 = Deg2Rad(lat1);
lat2 = Deg2Rad(lat2);
lon1 = Deg2Rad(lon1);
lon2 = Deg2Rad(lon2);
var R = 6371; // km
var x = (lon2 - lon1) * Math.cos((lat1 + lat2) / 2);
var y = (lat2 - lat1);
var d = Math.sqrt(x * x + y * y) * R;
return d;
}
var lat = 20; // user's latitude
var lon = 40; // user's longitude
var cities = [
["ATL", 33.740231, -84.394521],
["NYC", 40.748163, -73.985946],
["Vegas", 34.825425, -82.545665]
];
function NearestCity(latitude, longitude) {
var mindif = 99999;
var closest;
for (index = 0; index < cities.length; ++index) {
var dif = PythagorasEquirectangular(latitude, longitude, cities[index][1], cities[index][2]);
if (dif < mindif) {
closest = index;
mindif = dif;
}
}
// echo the nearest city
alert(cities[closest]);
console.log(closest)
}

I Hope you are using mongoose. Please try this code in your server. I have used $regex and made a small modification for your code.
router.get("/", function(req, res){
if (req.query.search) {
Deals.find({ "name":{ $regex: req.query.search } }, function(err, founddeals) {
if(err){
console.log(err);
} else {
res.render("deals/index",{deals:founddeals});
}
});
}

Related

Google Sheets Script "Exceeded maximum execution time"

I'm having issues trying to deal with the "Exceeded maximum execution time" error I get when running my script in Google sheets. I've found a few solutions on here that I couldn't get working with my script. Any help would be greatly appreciated, here is the script I am trying to modify:
function getGeocodingRegion() {
return PropertiesService.getDocumentProperties().getProperty('GEOCODING_REGION') || 'au';
}
function addressToPosition() {
// Select a cell with an address and two blank spaces after it
var sheet = SpreadsheetApp.getActiveSheet();
var cells = sheet.getActiveRange();
var addressColumn = 1;
var addressRow;
var latColumn = addressColumn + 1;
var lngColumn = addressColumn + 2;
var API_KEY = "xxx";
var options = {
muteHttpExceptions: true,
contentType: "application/json",
};
for (addressRow = 1; addressRow <= cells.getNumRows(); ++addressRow) {
var address = cells.getCell(addressRow, addressColumn).getValue();
var serviceUrl = "https://maps.googleapis.com/maps/api/geocode/json?address=" + address + "&key=" + API_KEY;
// Logger.log(address);
// Logger.log(serviceUrl);
var response = UrlFetchApp.fetch(serviceUrl, options);
if (response.getResponseCode() == 200) {
var location = JSON.parse(response.getContentText());
// Logger.log(response.getContentText());
if (location["status"] == "OK") {
//return coordinates;
var lat = location["results"][0]["geometry"]["location"]["lat"];
var lng = location["results"][0]["geometry"]["location"]["lng"];
cells.getCell(addressRow, latColumn).setValue(lat);
cells.getCell(addressRow, lngColumn).setValue(lng);
}
}
}
};
function positionToAddress() {
var sheet = SpreadsheetApp.getActiveSheet();
var cells = sheet.getActiveRange();
// Must have selected 3 columns (Address, Lat, Lng).
// Must have selected at least 1 row.
if (cells.getNumColumns() != 3) {
Logger.log("Must select at least 3 columns: Address, Lat, Lng columns.");
return;
}
var addressColumn = 1;
var addressRow;
var latColumn = addressColumn + 1;
var lngColumn = addressColumn + 2;
//Maps.setAuthentication("acqa-test1", "AIzaSyBzNCaW2AQCCfpfJzkYZiQR8NHbHnRGDRg");
var geocoder = Maps.newGeocoder().setRegion(getGeocodingRegion());
var location;
for (addressRow = 1; addressRow <= cells.getNumRows(); ++addressRow) {
var lat = cells.getCell(addressRow, latColumn).getValue();
var lng = cells.getCell(addressRow, lngColumn).getValue();
// Geocode the lat, lng pair to an address.
location = geocoder.reverseGeocode(lat, lng);
// Only change cells if geocoder seems to have gotten a
// valid response.
Logger.log(location.status);
if (location.status == 'OK') {
var address = location["results"][0]["formatted_address"];
cells.getCell(addressRow, addressColumn).setValue(address);
}
}
};
function generateMenu() {
var entries = [{
name: "Geocode Selected Cells (Address to Lat, Long)",
functionName: "addressToPosition"
}, {
name: "Geocode Selected Cells (Address from Lat, Long)",
functionName: "positionToAddress"
}];
return entries;
}
function updateMenu() {
SpreadsheetApp.getActiveSpreadsheet().updateMenu('Geocode', generateMenu())
};
/**
* Adds a custom menu to the active spreadsheet, containing a single menu item
* for invoking the readRows() function specified above.
* The onOpen() function, when defined, is automatically invoked whenever the
* spreadsheet is opened.
*
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function onOpen() {
SpreadsheetApp.getActiveSpreadsheet().addMenu('Geocode', generateMenu());
};
Or, any other script you may know of that does geocode in Google sheets and already properly handles max execution time that would be OK too, I'm not tied to this specific script, just getting the outcome I need!
This error's cause is due to script running more than 6 minutes.
A possible solution is to limit the time-consuming part of your script (which is the for loop) to only 5 minutes. Then create a trigger and continue the loop into another instance if it still isn't done.
Script:
function addressToPosition() {
// Select a cell with an address and two blank spaces after it
var sheet = SpreadsheetApp.getActiveSheet();
...
var options = {
muteHttpExceptions: true,
contentType: "application/json",
};
// if lastRow is set, get value, else 0
var continueRow = ScriptProperties.getProperty("lastRow") || 0;
var startTime = Date.now();
var resume = true;
for (addressRow = ++continueRow; addressRow <= cells.getNumRows(); ++addressRow) {
var address = cells.getCell(addressRow, addressColumn).getValue();
...
// if 5 minutes is done
if ((Date.now() - startTime) >= 300000) {
// save what's the last row you processed then exit loop
ScriptProperties.setProperty("lastRow", addressRow)
break;
}
// if you reached last row, assign flag as false to prevent triggering the next run
else if (addressRow == cells.getNumRows())
resume = false;
}
// if addressRow is less than getNumRows()
if (resume) {
// after execution of loop, prepare the trigger for the same function
var next = ScriptApp.newTrigger("addressToPosition").timeBased();
// run script after 1 second to continue where you left off (on another instance)
next.after(1000).create();
}
}
Do the same thing with your other functions.

Invalid argument: latitude. Should be of type: number (file Code.gs, line 22) - Googel Ads Script

I am currently trying to import latitude and longitude values from a sheet, into my Google ads campaign through a script that looks like this:
function main() {
var SPREADSHEET_URL = 'https://docs.google.com/spreadsheets/d/17w74flZ3AD7r7wIbAoYYkffUuJfxGB0-a9lhjBStzW4/edit#gid=0';
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var sheet = spreadsheet.getActiveSheet();
var data = sheet.getRange("A:E").getValues();
for (i in data) {
if (i == 0) {
continue;
}
var [CampaignName, latitude, longitude, radius, unit] = data[i];
if (CampaignName == "") {
break;
}
else {
var campaignIterator = AdWordsApp.campaigns()
.withCondition("CampaignName CONTAINS_IGNORE_CASE '" + CampaignName +"'")
.get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
campaign.addProximity(latitude, longitude, radius, unit);
}
}
}
}
However, when running the script, I keep getting the error "Invalid argument: latitude. Should be of type: number (file Code.gs, line 22)" What am I doing wrong?
(also the sheet link is open for anyone, and its a back up so no worries).
Filter and format the data properly, with this should be enough:
function main() {
var SPREADSHEET_URL = 'https://docs.google.com/spreadsheets/d/17w74flZ3AD7r7wIbAoYYkffUuJfxGB0-a9lhjBStzW4/edit#gid=0';
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var sheet = spreadsheet.getActiveSheet();
//Note you get only relevant data ranges
var range = sheet.getDataRange();
var data = range.getValues();
//Now we got only non blank cells
for (i in data) {
if (i == 0) {
continue;
}
var [CampaignName, latitude, longitude, radius, unit] = data[i];
// Parse strings to float
latitude = parseFloat(latitude);
longitude = parseFloat(longitude);
radius = parseFloat(radius);
// End of data conversion
if (CampaignName == "") {
break;
}
else {
var campaignIterator = AdWordsApp.campaigns()
.withCondition("CampaignName CONTAINS_IGNORE_CASE '" + CampaignName +"'")
.get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
campaign.addProximity(latitude, longitude, radius, unit);
}
}
}
}
I've added two things:
This getDataRange() function gets only non trivial data, so you are not getting a lot of blank rows.
I've kept your if(CampaignName == "") condition to avoid errors in case you have a blank row in between your data rows.
Second one is parseFloat, it converts your string to numbers and solves your initial problem.
As a tip:
Formating your spreadsheet numeric cells as numbers help on the importation process, because you'll import them as such, and you won't need to convert it.

Geolocation, latitude and longitude not defined after first call

I have a button that calls a function called HomeTest(). Within that function I call geoFindMe(), a simple geolocation function. This geoFindMe() gives me a var latitude and var longitude. Within the HomeTest() I use these variables to see whether I am in a certain polygon.
If I am in the right polygon, the button should change to another .html-file
My problem is, that I have to press the button TWICE to make the site load on the new .html-file, as it doesn't seem to get the latitude & longitude variables with the first try, even though I'm calling the geoFindMe() before using the variables. I'm a little new to js so I'm not quite sure why I don't get relocated to the new .html-file after one click when I am in the correct area.
Somebody got any idea?
function geoFindMe() {
if (!navigator.geolocation){
output.innerHTML = "<p>Your browser doesn't support geolocation.</p>";
return;
}
function success(position) {
latitude = position.coords.latitude;
longitude = position.coords.longitude;
};
function error() {
output.innerHTML = "The site was not able to locate you";
alert("Please use another browser.");
};
navigator.geolocation.getCurrentPosition(success, error);
}
function HomeTest(){
geoFindMe();
var polygon = [ [ longitude1, latitude1], [ longitude2, latitude2], [ longitude3, latitude3], [ longitude4, latitude4] ];
insideTest([ longitude, latitude ], polygon); // true -> point coordinates that are searched
//alert("is inside is " + isInsideTest + " " + latitude + " " + longitude);
//Test_LockButton();
if(isInsideTest){
location.href = './html/testhome.html';
}
}
This is the function that checks, wether the latitude&longitude are within the 4 points of the polygon (see above)
function insideTest(point, vs) {
var x = point[0], y = point[1];
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0], yi = vs[i][1];
var xj = vs[j][0], yj = vs[j][1];
var intersect = ((yi > y) != (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) isInsideTest = !isInsideTest;
}
return isInsideTest;
}
It's a simple enough issue that's causing you this grief, the getCurrentPosition function is asynchronous, meaning that you don't have the result by the time you call insideTest. When you click the second time, the variables have been stored and everything works as expected. If you put a delay of a few hundred milliseconds before calling insideTest everything would be OK, however this is not very good practice, it's better to define a callback function to call when the position is available, here's an example:
function insideTest(point, vs) {
var isInsideTest = false;
var x = point[0], y = point[1];
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0], yi = vs[i][1];
var xj = vs[j][0], yj = vs[j][1];
var intersect = ((yi > y) != (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) isInsideTest = !isInsideTest;
}
return isInsideTest;
}
function geoFindMe(successCallback) {
if (!navigator.geolocation){
output.innerHTML = "<p>Your browser doesn't support geolocation.</p>";
return;
}
function success(position) {
latitude = position.coords.latitude;
longitude = position.coords.longitude;
successCallback();
};
function error() {
output.innerHTML = "The site was not able to locate you";
alert("Please use another browser.");
};
navigator.geolocation.getCurrentPosition(success, error);
}
function homeTestCallback() {
var polygon = [ [ longitude1, latitude1], [ longitude2, latitude2], [ longitude3, latitude3], [ longitude4, latitude4] ];
var isInsidePolygon = insideTest([ longitude, latitude ], polygon);
if(isInsidePolygon) {
location.href = './html/testhome.html';
}
}
function HomeTest() {
// Pass in a function to call when the position is ready
geoFindMe(homeTestCallback);
}

JavaScript Async Loop does not return result

I was having some logic problem when trying to do a function in JavaScript. Basically I have a pointArr to store the coordinates along the route. Then I got a moveNext() which takes in each coordinates along the route and plot onto the map. Then inside the moveNext(), I got another array which is busList. If the coordinates along the route match the coordinates of busList, then I minus the totalBusStopLeft by one. Here is the code where I call the moveNext():
getAllBusLoc(function(busList) {
//At first I set the totalBusLeft by the length of busList which is 13 in this case
var totalBusLoc = busList.length;
document.getElementById("busStopLeft").innerHTML = totalBusLoc;
//Then I set the value to busList for the minus calculation
busLeft = totalBusLoc;
timeout = 1500;
pointArr.forEach(function(coord,index){
setTimeout(function(){
moveNext(coord.x, coord.y, index, busList, busLeft);
}, timeout * index);
});
});
function moveNext(coordx, coordy, k, busList, busLeft){
document.getElementById("busStopLeft").innerHTML = busLeft;
//pointToCompare is the coordinates in the route but not the coordinates of busList
var pointToCompare = coordx + "," + coordy;
//If the coordinates in route matches the coordinate in busList, I minus busLeft by one
if(busList.indexOf(pointToCompare) > -1){
parseFloat(busLeft--);
}
//Code to Add marker
}
However, with this code, my Html component busStopLeft keep showing 13 which is the totalBusLoc but not the busLeft. Any ideas?
Thanks in advance.
EDIT
totalBusLoc = busList.length;
document.getElementById("busStopLeft").innerHTML = totalBusLoc;
timeout = 1500;
pointArr.forEach(function(coord,index){
setTimeout(function(busLeft){
moveNext(coord.x, coord.y, index, busList, totalBusLoc);
}, timeout * index);
});
});
function moveNext(coordx, coordy, k, busList, totalBusLoc, callback){
var pointToCompare = coordx + "," + coordy;
if(busList.indexOf(pointToCompare) > -1){
parseFloat(totalBusLoc--);
document.getElementById("busStopLeft").innerHTML = totalBusLoc;
callback(totalBusLoc);
}
}

AngularJS - orderBy distance function

I'm extremely new to AngularJS so go easy on me... :-) I am in the process of building a new PhoneGap app with Ionic Framework and AngularJS. I have a list of locations that outputs in a list view and a function that will look up the user's location and get the distance between their current location and the location in the list. These currently both work correctly and I can see my list, sort by normal fields (name, etc).
What I would like to do is to have a preferences screen where the user will be able to set their preferred sorting option. I have already setup my basic preference controller as well that currently only is storing the preference to sort by 'name' but I'd like it to sort by distance that is calculated by a function seen below. But since the distance function seems to be in this controller, I don't know how to make it sort by that? Do I need to make a filter that runs the distance function?
Again, I'm new so any help would be greatly appreciated!
Here is my controller:
.controller('LocationsCtrl', function($scope, $ionicLoading, $ionicPopup, LocationsService, SettingsService) {
$scope.locations = {};
$scope.navTitle = "List of Locations";
$scope.rightButtons = [{
type: 'button-icon button-clear ion-more',
tap: function(e) {
$scope.openSortModal();
}
}];
// Method called on infinite scroll
// Receives a "done" callback to inform the infinite scroll that we are done
$scope.loadMore = function() {
$timeout(function() {
// Placeholder for later
$scope.$broadcast('scroll.infiniteScrollComplete');
}, 1000);
}
$scope.loading = $ionicLoading.show({
content: 'Getting current location...',
showBackdrop: false
});
navigator.geolocation.getCurrentPosition(function(pos) {
var coords = $scope.currentLocation = [pos.coords.longitude, pos.coords.latitude];
$scope.locations = LocationsService.allSync();
$scope.sortLoc = SettingsService.get('sortLocBy');
$ionicLoading.hide();
}, function(error) {
$ionicPopup.alert({
title: 'Unable to get location: ' + error.message
}).then(function(res) {
$ionicLoading.hide();
// not working
});
});
$scope.distanceFromHere = function (_item, _startPoint) {
var start = null;
var radiansTo = function (start, end) {
var d2r = Math.PI / 180.0;
var lat1rad = start.latitude * d2r;
var long1rad = start.longitude * d2r;
var lat2rad = end.latitude * d2r;
var long2rad = end.longitude * d2r;
var deltaLat = lat1rad - lat2rad;
var deltaLong = long1rad - long2rad;
var sinDeltaLatDiv2 = Math.sin(deltaLat / 2);
var sinDeltaLongDiv2 = Math.sin(deltaLong / 2);
// Square of half the straight line chord distance between both points.
var a = ((sinDeltaLatDiv2 * sinDeltaLatDiv2) +
(Math.cos(lat1rad) * Math.cos(lat2rad) *
sinDeltaLongDiv2 * sinDeltaLongDiv2));
a = Math.min(1.0, a);
return 2 * Math.asin(Math.sqrt(a));
};
if ($scope.currentLocation) {
start = {
longitude: $scope.currentLocation[0],
latitude: $scope.currentLocation[1]
};
}
start = _startPoint || start;
var end = {
longitude: _item.location.lng,
latitude: _item.location.lat
};
var num = radiansTo(start, end) * 3958.8;
return Math.round(num * 100) / 100;
}
})
And here is my template:
<ion-view title="{{navTitle}}" left-buttons="leftButtons">
<ion-content header-shrink scroll-event-interval="5">
<ion-list class="locations-list">
<ion-item class="location-item" ng-repeat="loc in locations | orderBy:sortLoc" type="item-text-wrap" href="#/location/{{loc.id}}/details" style="background-image:url('{{loc.photos.0.url}}');">
<div class="location-title">
<p>{{loc.name}}</p>
<span class="distance-indicator"><span class="digit">{{distanceFromHere(loc)}}</span><span class="unit">mi</span></span>
</div>
</ion-item>
</ion-list>
</ion-content>
</ion-view>
See the orderBy docs. As the orderBy expression, you can use a function which generates a value to use for sorting. All you should need to do is to put the distanceFromHere function in your sortLoc scope variable (or change the filter to orderBy:distanceFromHere).

Categories