i use this script in google sheets to calculate my mileage charges.
/**
* Get Distance between 2 different addresses.
* #param start_address Address as string Ex. "300 N LaSalles St, Chicago, IL"
* #param end_address Address as string Ex. "900 N LaSalles St, Chicago, IL"
* #param return_type Return type as string Ex. "miles" or "kilometers" or "minutes" or "hours"
* #customfunction
*/
function GOOGLEMAPS(start_address,end_address,return_type) {
// https://www.chicagocomputerclasses.com/
// Nov 2017
// improvements needed
var mapObj = Maps.newDirectionFinder();
mapObj.setOrigin(start_address);
mapObj.setDestination(end_address);
var directions = mapObj.getDirections();
var getTheLeg = directions["routes"][0]["legs"][0];
var meters = getTheLeg["distance"]["value"];
switch(return_type){
case "miles":
return meters * 0.000621371;
break;
case "minutes":
// get duration in seconds
var duration = getTheLeg["duration"]["value"];
//convert to minutes and return
return duration / 60;
break;
case "hours":
// get duration in seconds
var duration = getTheLeg["duration"]["value"];
//convert to hours and return
return duration / 60 / 60;
break;
case "kilometers":
return meters / 1000;
break;
default:
return "Error: Wrong Unit Type";
}
}
that works fine, but I would like the script to choose the shortest route in distance and not in time, like in this example :
It gives me 22.7km but I would like it to give me the shortest distance each time, in this case 21.3km
What can I change in the script so that it takes the shortest route in distance and not in time? Thank you!
I believe your goal is as follows.
You want to retrieve the minimum distance.
You want to achieve this using a custom function of Google Apps Script.
In this case, in order to retrieve the multiple routes, I used setAlternatives(useAlternatives). And, the minimum distance is retrieved from the retrieved multiple routes.
When this is reflected in your script, it becomes as follows. Please modify your script as follows.
From:
var mapObj = Maps.newDirectionFinder();
mapObj.setOrigin(start_address);
mapObj.setDestination(end_address);
var directions = mapObj.getDirections();
var getTheLeg = directions["routes"][0]["legs"][0];
var meters = getTheLeg["distance"]["value"];
To:
var mapObj = Maps.newDirectionFinder().setAlternatives(true);
mapObj.setOrigin(start_address);
mapObj.setDestination(end_address);
var directions = mapObj.getDirections();
var getTheLeg = directions["routes"][0]["legs"][0];
var meters = Math.min(...directions.routes.map(({legs: [{distance: {value}}]}) => value));
If you want to use the value of getTheLeg from the same routes of meters, please modify as follows.
From
var mapObj = Maps.newDirectionFinder();
mapObj.setOrigin(start_address);
mapObj.setDestination(end_address);
var directions = mapObj.getDirections();
var getTheLeg = directions["routes"][0]["legs"][0];
var meters = getTheLeg["distance"]["value"];
To
var mapObj = Maps.newDirectionFinder().setAlternatives(true);
mapObj.setOrigin(start_address);
mapObj.setDestination(end_address);
var directions = mapObj.getDirections();
var {getTheLeg, meters} = directions.routes.map(({legs: [{duration, distance}]}) => ({duration, distance})).reduce((o, {duration, distance}, i) => {
if (i == 0 || o.meters > distance.value) {
o.meters = distance.value;
o.getTheLeg.duration.value = duration.value;
}
return o;
}, {getTheLeg: {duration: {value: 0}}, meters: 0});
Note:
In this case, please enable the V8 runtime of the Google Apps Script project, if you didn't enable it.
References:
setAlternatives(useAlternatives)
map()
Related
Per CallRail's documentation (https://apidocs.callrail.com/) I have been trying to write data to a Google Sheet using Google App Scripts. Currently, it is only pulling in 100 rows of data. I think it may have something to do with the fact that each page has 100 results, but I tried to create a loop of multiple requests and also increase the limit.
I tried the code below, expecting it to return the 5000 most recent records, but it still stops after writing 100 rows.
`function importCallData() {
const apiToken = "TOKEN_HERE";
const sheetId = "SHEET_ID_HERE";
const sheetName = "Data";
const daysToRetrieve = 30;
// Calculate the start and end times for the API request
const now = new Date();
const endTime = Math.floor(now.getTime() / 1000);
const startTime = Math.floor(now.getTime() / 1000) - (daysToRetrieve * 24 * 60 * 60);
// Fetch the data from the CallRail API
let data = [];
let offset = 0;
let url = `https://api.callrail.com/v3/a/ACCOUNT_ID_HERE/calls?fields=id,answered,start_time,duration,utm_source,utm_medium,utm_campaign&limit=100&offset=${offset}`;
let options = {
headers: {
Authorization: `Token token=${apiToken}`
}
};
let response = UrlFetchApp.fetch(url, options);
let newData = JSON.parse(response.getContentText()).calls;
data = data.concat(newData);
offset += newData.length;
while (newData.length === 100 && data.length < 5000) {
url = `https://api.callrail.com/v3/a/ACCOUNT_ID_HERE/calls?fields=id,answered,start_time,duration,utm_source,utm_medium,utm_campaign&created_after=${startTime}&created_before=${endTime}&limit=100&offset=${offset}`;
response = UrlFetchApp.fetch(url, options);
newData = JSON.parse(response.getContentText()).calls;
data = data.concat(newData);
offset += newData.length;
}
// Write the data to the Google Sheet
const sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName);
sheet.clearContents();
sheet.appendRow(["id", "answered", "start_time", "duration", "utm_source", "utm_medium", "utm_campaign"]);
data.forEach(call => {
const row = [
call.id,
call.answered,
call.start_time,
call.duration,
call.utm_source,
call.utm_medium,
call.utm_campaign
];
sheet.appendRow(row);
});
}`
The parameters (String,String,String,(class)) don't match the method signature for CalendarApp.Calendar.createEvent.
CreateEvent # Code.gs:26
and i couldnt find the root cause for it. Im new here and would appreciate if someone could point out my mistake. Thanks!
function CreateEvent() {
var spreadsheet = SpreadsheetApp.getActiveSheet();
var eventCal = CalendarApp.getCalendarById("c_9a1e4a32dab9bdfe572d8e7c07f8fcb9a95694354df6939d58df5dc01187894c#group.calendar.google.com");
var lr = spreadsheet.getLastRow();
var count = spreadsheet.getRange("A8:F"+lr+"").getValues();
var calsend = "CALSENT";
var row = spreadsheet.getDataRange().getValues();
for (x=0; x<count.length; x++) {
var shift = count[x];
var summary = shift[0];
var startTime = shift[1];
var endTime = shift[2];
var guests = shift[3];
var description = shift[4];
var location = shift[5];
var event = {
'location': location,
'description': description,
'guests':guests +',',
'sendInvites': 'True',
}
eventCal.createEvent(summary, startTime, endTime, event).addEmailReminder(120).addPopupReminder(120)
row.forEach(function(row, index){
if (index === 0) return;
if (row[7]) return;
if(event = true)
spreadsheet.getRange(index + 1, 7).setValue(calsend);
})
}
}
i tried to move to the bottom
eventCal.createEvent(summary, startTime, endTime, event).addEmailReminder(120).addPopupReminder(120)
but still no luck
From the documentation, the method you're trying to use expects proper Date object for start time and end time, I think you're passing strings instead.
Note that even if they look like dates, or are formatted like dates, in the cells of the sheet, Apps Script most likely see those as strings
var startTime = shift[1];
var endTime = shift[2];
You can confirm this by printing typeof(startTime) for example.
I am trying to export my Pipedrive data to a Google Sheet, in particular to make the link between two of my queries. So I first wrote this script:
function GetPipedriveDeals2() {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let sheets = ss.getSheets();
let sheet = ss.getActiveSheet();
//the way the url is build next step is to iterate between the end because api only allows a fixed number of calls (100) this way i can slowly fill the sheet.
let url = "https://laptop.pipedrive.com/v1/products:(id)?start=";
let limit = "&limit=500";
//let filter = "&filter_id=64";
let pipeline = 1; // put a pipeline id specific to your PipeDrive setup
let start = 1;
//let end = start+50;
let token = "&api_token=XXXXXXXXXXXXXXX";
let response = UrlFetchApp.fetch(url+start+limit+token); //
let dataAll = JSON.parse(response.getContentText());
let dataSet = dataAll;
//let prices = prices;
//create array where the data should be put
let rows = [], data;
for (let i = 0; i < dataSet.data.length; i++) {
data = dataSet.data[i];
rows.push([data.id,
GetPipedriveDeals4(data.id)
]);
}
Logger.log( 'function2' ,JSON.stringify(rows,null,8) ); // Log transformed data
return rows;
}
// Standard functions to call the spreadsheet sheet and activesheet
function GetPipedriveDeals4(idNew) {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let sheets = ss.getSheets();
let sheet = ss.getActiveSheet();
//the way the url is build next step is to iterate between the end because api only allows a fixed number of calls (100) this way i can slowly fill the sheet.
let url = "https://laptop.pipedrive.com/v1/products/"+idNew+"/deals:(id,d93b458adf4bf84fefb6dbce477fe77cdf9de675)?start=";
let limit = "&limit=500";
//let filter = "&filter_id=64";
let pipeline = 1; // put a pipeline id specific to your PipeDrive setup
let start = 1;
//let end = start+50;
let token = "&api_token=XXXXXXXXXXXXXXXXX"
let response = UrlFetchApp.fetch(url+start+limit+token); //
let dataAll = JSON.parse(response.getContentText());
let dataSet = dataAll;
//Logger.log(dataSet)
//let prices = prices;
//create array where the data should be put
let rows = [], data;
if(dataSet.data === null )return
else {
for (let i = 0; i < dataSet.data.length; i++) {
data = dataSet.data[i];
let idNew = data.id;
rows.push([data.id, data['d93b458adf4bf84fefb6dbce477fe77cdf9de675']]);
}
Logger.log( 'function4', JSON.stringify(rows,null,2) ); // Log transformed data
return rows;
}
}
But it is not optimized at all and takes about 60 seconds to run, and google script executes the custom functions only for 30 seconds... With help, I had this second function:
function getPipedriveDeals(apiRequestLimit){
//Make the initial request to get the ids you need for the details.
var idsListRequest = "https://laptop.pipedrive.com/v1/products:(id)?start=";
var start = 0;
var limit = "&limit="+apiRequestLimit;
var token = "&api_token=XXXXXXXXXXX";
var response = UrlFetchApp.fetch(idsListRequest+start+limit+token);
var data = JSON.parse(response.getContentText()).data;
//For every id in the response, construct a url (the detail url) and push to a list of requests
var requests = [];
data.forEach(function(product){
var productDetailUrl = "https://laptop.pipedrive.com/v1/products/"+product.id+"/deals:(id,d93b458adf4bf84fefb6dbce477fe77cdf9de675)?start=";
requests.push(productDetailUrl+start+limit+token)
})
//With the list of detail request urls, make one call to UrlFetchApp.fetchAll(requests)
var allResponses = UrlFetchApp.fetchAll(requests);
// logger.log(allResponses);
return allResponses;
}
But this time it's the opposite. I reach my request limit imposed by Pipedrive: https://pipedrive.readme.io/docs/core-api-concepts-rate-limiting (80 requests in 2 sec).
I confess I have no more idea I thought of putting OAuth2 in my script to increase my query limit, but it seems really long and complicated I'm not at all in my field.
In summary, I would just like to have a script that doesn't execute requests too fast but without exceeding the 30 seconds imposed by Google Apps Script.
---------------------EDIT---TEST---FOREACH80-------------------------------------
function getPipedriveProducts(){
//Make the initial request to get the ids you need for the details.
var idsListRequest = "https://laptop.pipedrive.com/v1/products:(id)?start=";
var start = 0;
var limit = "&limit=500";
var token = "&api_token=XXXXXXXXXXXXXXXXXXX";
var response = UrlFetchApp.fetch(idsListRequest+start+limit+token);
var data = JSON.parse(response.getContentText()).data;
//For every id in the response, construct a url (the detail url) and push to a list of requests
const batch = new Set;
let requests = [];
data.forEach(function(product){
var productDetailUrl = "https://laptop.pipedrive.com/v1/products/" + product.id + "/deals:(id,d93b458adf4bf84fefb6dbce477fe77cdf9de675)?start=";
requests.push(productDetailUrl+start+limit+token);
if(requests.length === 79) {
batch.add(requests);
requests = [];
}
})
const allResponses = [...batch].flatMap(requests => {
Utilities.sleep(2000);
return UrlFetchApp.fetchAll(requests);
Logger.log(allResponses)
});
}
Create Set of 80 requests each
Execute each set value using fetchAll
const batch = new Set;
let requests = [];
data.forEach(function(product){
var productDetailUrl = "https://example.com";
requests.push(productDetailUrl+start+limit+token);
if(requests.length === 80) {
batch.add(requests);
requests = [];
}
})
const allResponses = [...batch].flatMap(requests => {
Utilities.sleep(2000);
return UrlFetchApp.fetchAll(requests);
});
Chunking
One of the most important concepts in working with APIs is chunking as you need to avoid rate-limiting, accommodate request scheduling, parallelize CPU-heavy calculations, etc. There are countless ways to split an array in chunks (see half a hundred answers in this canonical Q&A just for JavaScript).
Here is a small configurable utility tailored to the situation where one wants to split a flat array into an array of arrays of a certain size/pattern (which is usually the case with request chunking):
/**
* #typedef {object} ChunkifyConfig
* #property {number} [size]
* #property {number[]} [limits]
*
* #summary splits an array into chunks
* #param {any[]} source
* #param {ChunkifyConfig}
* #returns {any[][]}
*/
const chunkify = (source, {
limits = [],
size
} = {}) => {
const output = [];
if (size) {
const {
length
} = source;
const maxNumChunks = Math.ceil((length || 1) / size);
let numChunksLeft = maxNumChunks;
while (numChunksLeft) {
const chunksProcessed = maxNumChunks - numChunksLeft;
const elemsProcessed = chunksProcessed * size;
output.push(source.slice(elemsProcessed, elemsProcessed + size));
numChunksLeft--;
}
return output;
}
const {
length
} = limits;
if (!length) {
return [Object.assign([], source)];
}
let lastSlicedElem = 0;
limits.forEach((limit, i) => {
const limitPosition = lastSlicedElem + limit;
output[i] = source.slice(lastSlicedElem, limitPosition);
lastSlicedElem = limitPosition;
});
const lastChunk = source.slice(lastSlicedElem);
lastChunk.length && output.push(lastChunk);
return output;
};
const sourceLimited = [1, 1, 2, 2, 2, 3];
const outputLimited = chunkify(sourceLimited, { limits: [2, 1] });
console.log({ source : sourceLimited, output : outputLimited });
const sourceSized = ["ES5", "ES6", "ES7", "ES8", "ES9"];
const outputSized = chunkify(sourceSized, { size: 2 });
console.log({ source : sourceSized, output : outputSized });
From there, the only thing you need is to traverse the array while waiting for each chunk to complete to make it applicable to your situation. Please beware that requests can fail for any number of reasons - you should persist last successfully processed chunk.
I am trying to build a simple "game" that will teach multiplication to my son. The script is below as a link to the screenshot.
Issue:
The multiplication does not seem to work. x1*x2 returns #NUM!. When I checked the spreadsheet, the numbers are not a text string.
What am I getting wrong here?
function multiplicationgame() {
var GameSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Multiplication");
var x1 = GameSheet.getRange("A2");
var x2 = GameSheet.getRange("B2");
// for generating a random number Math.floor((Math.random() * 10) + 1);
x1.setValue(Math.floor((Math.random() * 10) + 1));
x2.setValue(Math.floor((Math.random() * 10) + 1));
var ui = SpreadsheetApp.getUi();
var response = ui.prompt('Enter Answer Here:');
// creates a temp halt in speardsheet execution till the answer is entered
SpreadsheetApp.flush();
var Answer = GameSheet.getRange("A7").getValue();
var x3 = GameSheet.getRange("B7");
if (Answer == x1*x2) (x3.setValue('Thats correct! Well done'));
else (x3.setValue('Thats wrong! Better luck next time'))
}
Try this:
Multiplication process restored.
You were using the range as a value.
function multiplicationgame() {
var GameSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet71");
var x1 = GameSheet.getRange("A2");
var x2 = GameSheet.getRange("B2");
var x3 = GameSheet.getRange("A7");
var x4=GameSheet.getRange("B7");
// for generating a random number Math.floor((Math.random() * 10) + 1);
var m1=Math.floor((Math.random() * 10) + 1);
var m2=Math.floor((Math.random() * 10) + 1);
x1.setValue(m1);
x2.setValue(m2);
SpreadsheetApp.flush();
var ui = SpreadsheetApp.getUi();
var response = ui.prompt('Multiplication', Utilities.formatString('%s x %s = ?',m1,m2 ), ui.ButtonSet.OK);
// creates a temp halt in speardsheet execution till the answer is entered
x3.setValue(response.getResponseText());
SpreadsheetApp.flush();
var Answer = GameSheet.getRange("A7").getValue();
if (Answer == m1*m2) {
x4.setValue('Thats correct! Well done');
}else{
x4.setValue('Thats wrong! Better luck next time');
}
}
Not sure why this stopped working. I'm trying to get the latitude & longitude from an address. Was working at one point. Not sure what happened. I'm listing the address in column U.
function geocode(){
var tkhCopy = SpreadsheetApp.openById('xxx').getSheetByName('tkhCopy');
var range = tkhCopy.getRange('U2:U');
var addresses = range.getValues();
// Determine the first row and column to geocode
var row = range.getRow();
var column = range.getColumn();
// Set default destination columns
var destination = new Array();
destination[0] = 22; // column + 1;
destination[1] = 23; // column + 2;
// tkhCopy.insertColumnsAfter(column, 2);
var geocoder = Maps.newGeocoder();
var count = range.getHeight();
// Iterate through addresses and geocode
for(i in addresses) {
var location = geocoder.geocode(
addresses[i]).results[0].geometry.location);
tkhCopy.getRange(row, destination[0]).setValue(location.lat);
tkhCopy.getRange(row++, destination[1]).setValue(location.lng);
Utilities.sleep(200);
}
}
You could have hit a quota on the number of times you can use the Geocoder service. Wait a day and try again.