Please help me with API interface of google maps in google sheet - javascript

I am trying to get the distance between two locations in google using google Maps direction API through the script.
However, following YouTube video and researching on my own in google API documentation.
I am not able to resolve errors in my following code.
Error
"Cannot read property 'legs' of undefined"
function GT() {
var app = SpreadsheetApp;
var ss1 = app.getActiveSpreadsheet();
var mapsheet = ss1.getActiveSheet();
var start = mapsheet.getRange('B1').getValue;
var end = mapsheet.getRange('B2').getValue();
var directions = Maps.newDirectionFinder()
.setOrigin(start)
.setDestination(end)
.setMode(Maps.DirectionFinder.Mode.DRIVING)
.getDirections();
var routes = directions.routes[0]
mapsheet.getRange('A6:D10').clear();
var nextrow = mapsheet.getLastRow() +1 ;
const ro = directions.routes[0];
for (var i = 0; i < 2; i++)
{
var ea = ro.legs[1].end_address;
var sa = ro.legs[1].start_address;
var distance = ro.legs[1].distance.text;
var duration = ro.legs[1].duration.text;
mapsheet.getRange(nextrow,1).setValue(sa);
mapsheet.getRange(nextrow,1).setValue(ea);
mapsheet.getRange(nextrow,1).setValue(distance);
mapsheet.getRange(nextrow,1).setValue(duration);
}

Issue:
You forgot the () when retrieving the value for start:
var start = mapsheet.getRange('B1').getValue;
Therefore, your are retrieving the method getValue(), and not the cell value.
Because of this, ro is undefined (the origin has to be a string corresponding to an address) and, therefore, you cannot retrieve its legs property.
Solution:
Add the () for getValue():
var start = mapsheet.getRange('B1').getValue();
This should fix your issue, assuming that cells B1 and B2 contain proper addresses.

Related

How can I make my Google Apps Script function work on cell input?

I have a project that I've been working on for a bit. I've received some excellent help here, and I think I'm almost done and just need one more bit of help to get it working.
The script looks at a Google Sheet and takes a place name entered in Column A and uses the Google Places API to find requested information about it (address, phone number, etc.)
The last bit of help that I need will be able to implement the cell input component. The last user to help me said that
function writeToSheet(){
var ss = SpreadsheetApp.getActiveSheet();
var data = COMBINED2("Food");
var placeCid = data[4];
var findText = ss.createTextFinder(placeCid).findAll();
if(findText.length == 0){
ss.getRange(ss.getLastRow()+1,1,1, data.length).setValues([data])
}
}
would be able use TextFinder to check if the place url exists in the Sheet. If the result of TextFinder is 0, it will call COMBINED2() to get the place information and populate the Sheet with writeToSheet()
They noted that
You can use a cell input in your COMBINED2 by using
ss.getRange(range).getValue()
Not having a coding background, I have been able to stitch most of this together on my own, but I could use a bit of help in adding that capability to my code. Any help or guidance would be great.
Here is the code in full:
// This location basis is used to narrow the search -- e.g. if you were
// building a sheet of bars in NYC, you would want to set it to coordinates
// in NYC.
// You can get this from the url of a Google Maps search.
const LOC_BASIS_LAT_LON = "40.74516247433546, -73.98621366765816"; // e.g. "37.7644856,-122.4472203"
function COMBINED2(text) {
var API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx';
var baseUrl = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json';
var queryUrl = baseUrl + '?input=' + text + '&inputtype=textquery&key=' + API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var placeId = JSON.parse(json);
var ID = placeId.candidates[0].place_id;
var fields = 'name,formatted_address,formatted_phone_number,website,url,types,opening_hours';
var baseUrl2 = 'https://maps.googleapis.com/maps/api/place/details/json?placeid=';
var queryUrl2 = baseUrl2 + ID + '&fields=' + fields + '&key='+ API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
if (ID == '') {
return 'Give me a Google Places URL...';
}
var response2 = UrlFetchApp.fetch(queryUrl2);
var json2 = response2.getContentText();
var place = JSON.parse(json2).result;
var weekdays = '';
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
var data = [
place.name,
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim()
];
return data;
}
function getColumnLastRow(range){
var ss = SpreadsheetApp.getActiveSheet();
var inputs = ss.getRange(range).getValues();
return inputs.filter(String).length;
}
function writeToSheet(){
var ss = SpreadsheetApp.getActiveSheet();
var data = COMBINED2("Food");
var placeCid = data[4];
var findText = ss.createTextFinder(placeCid).findAll();
if(findText.length == 0){
ss.getRange(ss.getLastRow()+1,1,1, data.length).setValues([data])
}
}
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("Custom Menu")
.addItem("Get place info","writeToSheet")
.addToUi();
}
Update
Here is a link to a Shared Sheet in case anyone wants to work on it with me.
https://docs.google.com/spreadsheets/d/1KGsk6nkin1CUgpjfHU_AdhF17T_Eh41_g4MLb1CG_Tk/edit#gid=2100307022
Here is what I might not have articulated properly.
I wanted to be able to enter the names of places in Column A
Then, I want to be able to run the function with the custom menu feature. If TextFinder does not find the Place URL for the given place, it will look up the data and write it to the Sheet.
I wanted to limit the number of API calls with this and to make sure the data was written to the Sheet so that it does not need to be pulled each time the Sheet is reopened.
Finished Product
Big thanks to Lamblichus for sticking this out with me. I hope this helps other people some day.
Here is the finished code:
// This location basis is used to narrow the search -- e.g. if you were
// building a sheet of bars in NYC, you would want to set it to coordinates
// in NYC.
// You can get this from the url of a Google Maps search.
const LOC_BASIS_LAT_LON = "ENTER_GPS_COORDINATES_HERE"; // e.g. "37.7644856,-122.4472203"
function COMBINED2(text) {
var API_KEY = 'ENTER_API_KEY_HERE';
var baseUrl = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json';
var queryUrl = baseUrl + '?input=' + text + '&inputtype=textquery&key=' + API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var placeId = JSON.parse(json);
var ID = placeId.candidates[0].place_id;
var fields = 'name,formatted_address,formatted_phone_number,website,url,types,opening_hours';
var baseUrl2 = 'https://maps.googleapis.com/maps/api/place/details/json?placeid=';
var queryUrl2 = baseUrl2 + ID + '&fields=' + fields + '&key='+ API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
if (ID == '') {
return 'Give me a Google Places URL...';
}
var response2 = UrlFetchApp.fetch(queryUrl2);
var json2 = response2.getContentText();
var place = JSON.parse(json2).result;
var weekdays = '';
if (place.opening_hours && place.opening_hours.weekday_text) {
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
}
var data = [
place.name,
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim()
];
return data;
}
function writeToSheet() {
const sheet = SpreadsheetApp.getActiveSheet();
const FIRST_ROW = 2;
const sourceData = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 6)
.getValues().filter(row => String(row[0]));
for (let i = 0; i < sourceData.length; i++) {
const sourceRow = sourceData[i];
if (sourceRow[4] === "") {
const text = sourceRow[0];
const data = COMBINED2(text);
sheet.getRange(FIRST_ROW+i, 2, 1, data.length).setValues([data]);
}
}
}
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("Custom Menu")
.addItem("Get place info","writeToSheet")
.addToUi();
}
Desired goal:
If I understand you correctly, for each value in column A, you want to retrieve some related data from Maps API and paste it to columns B-F, if column E is not currently populated.
Issues:
You are only providing the last value from column A to COMBINED2, but you want to loop through all values in column A and fetch the desired information for all of them (as long as the Place URL -column E- is not already populated).
If you want to avoid calling Maps API if the Place URL is not populated, using TextFinder after calling Maps API doesn't make sense; you don't limit your calls to the API if you do that. If you just want to check whether the Place URL column is populated, I'd suggest checking whether the cell is empty or not, and calling Maps API if it's empty.
Proposed workflow:
Retrieve all values from the sheet, including not just column A but also E (for practical purposes, all 6 columns are fetched in the sample below, since it can be done in one call), using Range.getValues().
Iterate through the rows (for example, using for), and for each row, check that the cell in E is populated.
If the cell in E (Place URL) is empty, use the value in A as the parameter for COMBINED2 and write the resulting data to columns B-F, as you are currently doing.
Code sample:
function writeToSheet() {
const sheet = SpreadsheetApp.getActiveSheet();
const FIRST_ROW = 2;
const sourceData = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 6)
.getValues().filter(row => String(row[0]));
for (let i = 0; i < sourceData.length; i++) {
const sourceRow = sourceData[i];
if (sourceRow[4] === "") {
const text = sourceRow[0];
const data = COMBINED2(text);
sheet.getRange(FIRST_ROW+i, 2, 1, data.length).setValues([data]);
}
}
}
Update:
For names in which Places API doesn't return opening_hours, consider checking if this exists first:
function COMBINED2(text) {
// ... REST OF YOUR FUNCTION ...
var weekdays = '';
if (place.opening_hours && place.opening_hours.weekday_text) {
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
}
var data = [
place.name,
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim()
];
return data;
}
By using the event trigger function...
function onEdit(e){
SpreadsheetApp.getActiveSheet().getRange(insert your range in A1 format).setValue("anything you want to add into the cell")
}
function onEdit(e){
var ss = SpreadsheetApp.getActiveSheet();
var data = COMBINED2("Food");
var placeCid = data[4];
var findText = ss.createTextFinder(placeCid).findAll();
if(findText.length == 0){
ss.getRange(ss.getLastRow()+1,1,1, data.length).setValues([data])
}
}
u need to specifically tell google apps script that the function is as such so that your function will execute when a event object known as e has happened.
You can read more about it on Simple Triggers

If matching content move to new sheet

I am writing a script that looks through a list of suppliers, then needs to create a new sheet for each of them and import the information for that supplier into that sheet.
So far, I have the following:
function getData() {
var masterSS = SpreadsheetApp.getActiveSpreadsheet() || SpreadsheetApp.openById("xxx");
var supSS = masterSS.getSheetByName("Suppliers");
masterSS.setActiveSheet(supSS)
var supSR = 2;
var supNoRows = supSS.getLastRow() - 1;
var supRange = supSS.getRange(supSR, 1, supNoRows, masterSS.getLastColumn());
var supData = supRange.getValues();
var orderSS = masterSS.getSheetByName("Orders");
var orderData = orderSS.getRange("A2:AB").getValues();
var templateSS = masterSS.getSheetByName("Template");
supData.forEach(function(name) {
var newSheet = masterSS.insertSheet(name, {template: templateSS});
console.log(name)
});
}
This is working as intended and is creating a sheet for each supplier listed on the Supplier sheet. I then need it to look through a range on a sheet called Orders, and if A2:A matches the supplier name used to create the new sheet, import that row to the newly created sheet.
I can't figure out quite how to get this working, as I would need to do it as each sheet is created. Anyone able to point me in the right direction?

Google Sheets App Script timing out and time based trigger not running

Below is all of my code from a function that I have been working on for a while. Essentially I am trying to create a load of class analysis packs (I am a teacher) and print a google sheet as a PDF and then change a drop down (effectively changing the page and data) and then print again until all of the class codes in the drop down have been completed. The function works really well but the issue I have is that it will take longer than 6 mins to run as there are about 150 packs to create. I have looked into triggers and created a time based trigger which should start a few minutes after it has timed out. It seems to successfully create the trigger but the trigger never actually runs. Is this the correct approach? If so can anybody spot why its not working? Any feedback would be amazing as this has been driving me crazy!
function CreateClassPacks() {
SpreadsheetApp.getUi() // Or DocumentApp or FormApp
var startTime= (new Date()).getTime();
var REASONABLE_TIME_TO_WAIT = 100000
var MAX_RUNNING_TIME = 340000
// Getting the date and putting it into the format we want
var d= new Date();
var dateStamp = d.getDate()+"/"+d.getMonth()+"/"+d.getYear();
// Getting a token which will give me the authorisation I need
var request = {
"method": "GET",
"headers":{"Authorization": "Bearer "+ScriptApp.getOAuthToken()},
"muteHttpExceptions": true
};
// This is the key for the spreadsheet I am working on and then it gets fetched
var ss = SpreadsheetApp.getActiveSpreadsheet();
var getKeys = ss.getSheetByName("Settings");
var mainSSKey= getKeys.getRange("B1").getValue();
// Key for the folder we will save the documents into
var folderCPKey = getKeys.getRange("B2").getValue();
var foldersave=DriveApp.getFolderById(folderCPKey);
var fetch='https://docs.google.com/spreadsheets/d/'+mainSSKey+'/export?format=pdf&size=A4&portrait=false'
// This section gets all of the class codes from whichever sheet we choose.
// The first variable will need changing to whichever number sheet holds the codes.
var classCodeSheetNum = 0
var classCodeSheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[classCodeSheetNum]
var maxRowNum = classCodeSheet.getLastRow()-1;
// This variable must contain the correct column for the class codes
var dataRange = classCodeSheet.getRange(1, 1, maxRowNum, 1);
var data = dataRange.getValues();
Logger.log(data)
// This must be the sheet number for the class analysis packs
var sheetNum = 4
var newTrig = false
// This will loop through my data variable which contains all the class codes
for (var r=0; r<(data.length)-1; r++) {
for (i in data[0]) {
var scriptProperties = PropertiesService.getScriptProperties();
var startRow= scriptProperties.getProperty('start_row');
var currTime = (new Date()).getTime();
if(currTime - startTime >= MAX_RUNNING_TIME) {
if (newTrig == false){
ScriptApp.newTrigger("CreateClassPacks")
.timeBased()
.at(new Date(currTime+REASONABLE_TIME_TO_WAIT))
.create();
newTrig = true
break;
}
} else {
// This sets the value of A2 on the analysis sheet to the value from the data structure
SpreadsheetApp.getActiveSpreadsheet().getSheets()[sheetNum].getRange('O1').setValue(data[r][i]);
var source = SpreadsheetApp.getActiveSpreadsheet();
var sheet = source.getSheets()[sheetNum];
// This gets the value from A2 and sorts out the name of the file
var classCode = data[r][i]
var name = classCode + " " + dateStamp + ".pdf";
// This checks if the file already exists which will hopefully fix any timeout issues
var file = DriveApp.getFilesByName(name)
var chk = file.hasNext()
if (chk === false) {
// This hides all the sheets except for the one I am printing
for(var w=0; w< sheetNum;w++)
{
sheet = source.getSheets()[w];
sheet.hideSheet();
}
// This PDFs the page and has a timeout delaying the access requests so I don't get the annoying errors
var pdf = UrlFetchApp.fetch(fetch, request);
pdf = pdf.getBlob().setName(name);
Utilities.sleep(4000);
var file = foldersave.createFile(pdf)
// This shows all the sheets that I previously hid
for(var q=0; q< sheetNum;q++)
{
sheet = source.getSheets()[q];
sheet.showSheet();
}
}
}
}
}
}
This shows that the trigger seems to be created even though it doesn't run the function again
Maybe I'm missing something here but I think your code could be a lot quicker. For one thing create the var ss=SpreadsheetApp.getActive() and then use ss everywhere else. All create var allSheets=ss.getSheets() and then use allSheets[i] instead of 'ss.getSheets()[i]`. That section for i in data[0] makes no sense to me. The data is one column wide. All I would hide all of the sheets before the loop and then if necessary show the one that I'm printing if you have to. Hiding and showing sheets takes time. Try not to do it. I have gone through the code a bit but admittedly I don't have any context so I doubt that it runs. But I tried to take as much out of the loop as possible and you should too. You may find that you can get it to run in the allotted time.
function CreateClassPacks() {
var dateStamp=Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "dd/MM/yyyy")
var ss=SpreadsheetApp.getActive();
var getKeys=ss.getSheetByName("Settings");
var mainSSKey=getKeys.getRange("B1").getValue();
var folderCPKey=getKeys.getRange("B2").getValue();
var foldersave=DriveApp.getFolderById(folderCPKey);
var fetch='https://docs.google.com/spreadsheets/d/'+mainSSKey+'/export?format=pdf&size=A4&portrait=false'
var classCodeSheetNum=0;
var classCodeSheet=SpreadsheetApp.getActiveSpreadsheet().getSheets()[classCodeSheetNum]
var maxRowNum=classCodeSheet.getLastRow()-1;
var dataRange=classCodeSheet.getRange(1, 1, maxRowNum, 1);
var data=dataRange.getValues();
var sheetNum=4;
var newTrig=false;
var allSheets=ss.getSheets();
for(var i=0;i<allSheets.length;i++){
allSheets[i].hideSheet();
}
var srcrng=allSheets[sheetNum].getRange('O1');
var sheet=allSheets()[sheetNum];
for (var r=0;r<data.length-1;r++){
srcrng.setValue(data[r][0]);
var classCode = data[r][i]
var name=classCode + " " + dateStamp + ".pdf";
var file=DriveApp.getFilesByName(name);
var chk=file.hasNext()
sheet.showSheet();
var pdf = UrlFetchApp.fetch(fetch, request);
pdf = pdf.getBlob().setName(name);
var file = foldersave.createFile(pdf);
sheet.hideSheet();
}
}

Google Form If Command Error

I've been trying to code a function for a Google form that will send an email to a particular teacher that is indicated by the form. The problem is that I keep getting an error on a simple if statement. The error says, "Missing ; before statement. (line 29, file "Code")". The code is based on this Google Apps tutorial: https://developers.google.com/apps-script/articles/mail_merge#section-5-full-code. This is what I have right now:
function sendEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheets()[0];
var dataRange = dataSheet.getRange(2, 1, dataSheet.getMaxRows() - 1, 4);
var templateSheet = ss.getSheets()[1];
var emailTemplate = templateSheet.getRange("A1").getValue();
// Create one JavaScript object per row of data.
var objects = getRowsData(dataSheet, dataRange);
// For every row object, create a personalized email from a template and send
// it to the appropriate person.
for (var i = 0; i < objects.length; ++i) {
// Get a row object
var rowData = objects[i];
// Generate a personalized email.
// Given a template string, replace markers (for instance ${"First Name"}) with
// the corresponding value in a row object (for instance rowData.firstName).
var emailText = fillInTemplateFromObject(emailTemplate, rowData);
var emailSubject = "Lab Visit Report";
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
for (var i = 0; i < data.length; i++) {
var teacher = data[i][10];
If (teacher == "Jake Nabasny") {MailApp.sendEmail("JakeN#school.edu", emailSubject, emailText);}
else if (teacher == "Dan S") {MailApp.sendEmail("DanS#school.edu", emailSubject, emailText);}
}
}
I've already made sure that the variables (such as teacher and emailText) contain data. The problem, as far as I can tell, is solely with the if statement. Can anyone please give me an idea of what is going wrong here?
You seem to have capitialised an If ...

Google spreadsheet ImportRange - need to improve my workaround

I am pretty new to this forum and also to the google scripts. I have never actually learned JavaScript or any other programming language, however I was forced to start using then since the time I chose the google apps as my main platform for my small business.
My problem is in google ImportRange function limitation to 50 on every spreadsheet. I created a model, where every customer has his own spreadsheet with his personal data, deadlines, etc. located in his own folder in google drive.
I also created a spreadsheet called "Organizer", Organizer has two functions -
1) create automatic correspondention using autoCrat script,
2) show deadlines for every costumer and sort them by priority.
So i need to be able to share / import / copy data from all customer spreadsheets to the "Organizer". Because there are 50+ costumer spreadsheets, I had to give up on ImportRange function and instead of that I am using a simple script to copy files from every spreadsheet directly:
function ImportDataRange() {
// rown number.1
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Databaze");
var range = sheet.getRange(2, 1)
var id = range.getValue()
var ssraw = SpreadsheetApp.openById(id);
var sheetraw = ssraw.getSheetByName("Raw");
var range = sheetraw.getRange("A2:AB2");
var data = range.getValues();
sheet.getRange("B2:AC2").setValues(data)
// row number.2
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Databaze");
var range = sheet.getRange(3, 1)
var id = range.getValue()
var ssraw = SpreadsheetApp.openById(id);
var sheetraw = ssraw.getSheetByName("Raw");
var range = sheetraw.getRange("A2:AB2");
var data = range.getValues();
sheet.getRange("B3:AC3").setValues(data)
}
This script actually works well, but problem is, when I want to add new costumer spreadsheet to "Organizer" using this method, I have to manually add new copy of whole code for every new row and also change the output range of imported data and location of source file ID in "Organizer".
Does anybody know some kind of workaroud, which will help me to add new rows / costumer data easier / automatically ?
Thank you for your help!
You are going to want to use a loop of some sort. I haven't tested the code below, but based on your example, I think this should work for you.
function ImportOtherSheets() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Databaze");
// Get the first column
// lastRow() - 1 because we're starting at row 2
var sheetIds = sheet.getRange(2, 1, sheet.getLastRow() - 1);
// For each ID in the id column
for (var i = 0; i < sheetIds.length; i++) {
var id = sheetIds[i]; // Get the id from the Array
// Get the new sheet, range and values
var ssraw = SpreadsheetApp.openById(id);
var sheetraw = ssraw.getSheetByName("Raw");
var range = sheetraw.getRange("A2:AB2");
var data = range.getValues();
// Get the local range, and write the values
sheet.getRange(i + 1, 2, 1, data[0].length).setValues(data)
}
}
As you learn more about GAS and JS, take advantage of MDN and the GAS Documentation
I took the fooby´s code and after long trial-and-error procedure I came with code that is working for me, so if anyone is interested, here it is:
function ShowDataFromOtherSheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Databaze");
var ids = sheet.getRange(2, 1, sheet.getLastRow() - 1).getValues();
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var ssraw = SpreadsheetApp.openById(id);
var sheetraw = ssraw.getSheetByName("Raw");
var range = sheetraw.getRange("A2:AB2");
var data = range.getValues();
sheet.getRange(i + 2, 2, 1, data[0].length).setValues(data)
}
}
And thank you fooby - i would not make it without your help!

Categories