Service Invoked too many times for one day: geocode ARRAY FORMULA - google-apps-script

I hope somebody can help me I have this problem in Google sheets. I have a column Address (I) ,a column Coordinates (J) and a Backup Coordinates column (K).
I'm using a script to create a custom formula to calculate the coordinates from the address using Google localization services. In my Coordinates column (J) I have an array formula like this
=ARRAYFORMULA(IF(LEN(K2:K) > 0,K2:K,GEOCODE_GOOGLE(I2:I)))
So, if K has a value then I copy that value to my column else I use the GEOCODE custom formula. My problem is that I'm gettis this error:
Service Invoked too many times for one day: geocode. (line 5)
I know there are limits for the use of this service but my sheet only have like 20 or 30 new rows per day, so I think maybe the problem is in my arrayformula?, my sheet have 200 rows right now, so maybe the formula is executing 200 times every time a new row is inserted?

I was using this custom function inside an array formula so on every new row on my sheet the formula was calculating coordinates for all the rows, So I had to found a way to validate if a row was already calculated. I have added a text to my address column. For example if the address is "123 Mapple Street" now it says "123 Mapple Street..DoNotLocate.." if it has already been located so my script won't geolocate it.
Here is my script with the validation:
function GEOCODE_GOOGLE(address) {
if (address.map) {
return address.map(GEOCODE_GOOGLE)
} else {
var n = address.search("…DoNotLocate…");
if (n == -1) {
var r = Maps.newGeocoder().geocode(address)
for (var i = 0; i < r.results.length; i++) {
var res = r.results[i]
return res.geometry.location.lat + ", " + res.geometry.location.lng
}
}
}
}

Related

Using for and if loops in Google Apps Script

Dear programming Community,
at first I need to state, that I am not quite experienced in VBA and programming in general.
What is my problem? I have created a topic list in google sheets in order to collect topics for our monthly meeting among members in a little dance club. That list has a few columns (A: date of creation of topic; B: topic; C: Name of creator; ...). Since it is hard to force all the people to use the same format for the date (column A; some use the year, others not, ...), I decided to lock the entire column A (read-only) and put a formular there in all cells that looks in the adjacent cell in column B and sets the current date, if someone types in a new topic (=if(B2="";"";Now()). Here the problem is, that google sheets (and excel) does then always update the date, when you open the file a few days later again. I tried to overcome this problem by using a circular reference, but that doesn't work either. So now I am thinking of creating a little function (macro) that gets triggered when the file is closed.
Every cell in Column B (Topic) in the range from row 2 to 1000 (row 1 is headline) shall be checked if someone created a new topic (whether or not its empty). If it is not empty, the Date in the adjacent cell (Column A) shall be copied and reinserted just as the value (to get rid of the formular in that cell). Since it also can happen, that someone has created a topic, but a few days later decides to delete it again, in that case the formular for the date shall be inserted again. I thought to solve this with an If-Then-Else loop (If B is not empty, then copy/paste A, else insert formula in A) in a For loop (checking rows 1 - 1000). This is what I have so far, but unfortunately does not work. Could someone help me out here?
Thanks in advance and best regards,
Harry
function NeuerTest () {
var ss=SpreadsheetApp.getActive();
var s=ss.getSheetByName('Themenspeicher');
var thema = s.getCell(i,2);
var datum = s.getCell(i,1);
for (i=2;i<=100;i++) {
if(thema.isBlank){
}
else {
datum.copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}}
}
The suggested approach is to limit the calls to the Spreadsheet API, therefore instead of getting every cell, get all the data at once.
// this gets all the data in the Sheet
const allRows = s.getDataRange().getValues()
// here we will store what is written back into the sheet
const output = []
// now go through each row
allRows.forEach( (row, ind) => {
const currentRowNumber = ind+1
// check if column b is empty
if( !row[1] || row[1]= "" ){
// it is, therefore add a row with a formula
output.push( ["=YOUR_FORMULA_HERE"] )
} else {
// keep the existing value
output.push( [row[0]] )
}
})
Basically it could be something like this:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Themenspeicher');
var range = sheet.getRange('A2:B1000');
var data = range.getValues(); // <---- or: range.getDisplayValues();
for (let row in data) {
var formula = '=if(B' + (+row+2) + '="";"";Now())';
if (data[row][1] == '') data[row][0] = formula;
}
range.setValues(data);
}
But actual answer depends on what exactly you have, how your formula looks like, etc. It would be better if you show a sample of your sheet (a couple of screenshots would be enough) 'before the script' and 'after the script'.

How To Add The Ordinal Count To Values Set In Horizontal Non-Contiguous Ranges In Google Sheets With A Formula Or A Script? EDIT ADDRESS FUNCTION

Problem:
I'm trying to add each ordinal reference to a set of repeating values in each cells just above each value.
The values are organized in horizontal and non-contiguous order.
The illustration example I show below is simple for testing purposes, but the end use should be for hundreds of values/ranges, so it would be optimal to use a script or a simplified version of the formula I found.
Illustration Example:
Other Related Question and Solution:
I found that question and answers that address the same question but for vertical and contiguous values using the following formula as solution:
=COUNTIF(A$1:A1,A1)
=COUNTIF(A$1:A1,A1)&MID("thstndrdth",MIN(9,2*RIGHT(COUNTIF(A$1:A1,A1))*(MOD(COUNTIF(A$1:A1,A1)-11,100)>2)+1),2)
Calculate ordinal number of replicates
My Formula So Far:
=TRANSPOSE(INDIRECT($P$21&(SUM(Q21))&":"&$P$21&(SUM(Q21,I22)-1)))
=TRANSPOSE(INDIRECT($P$21&(SUM(Q21,I22))&":"&$P$21&(SUM(Q21,I22,I26)-1)))
=TRANSPOSE(INDIRECT($P$21&(SUM(Q21,I22,I26))&":"&$P$21&(SUM(Q21,I22,I26,I30)-1)))
I use the above formula and need to copy-paste it in the cell immediately above the 1st cell of each horizontal range.
I need to reference each cell in the SUM Functions part because the spreadsheet will act as a template, with new data sets that will be different each time.
Therefore the cells need to return output in some dynamic way (can't hardcode them).
The formula problem is it requires an ever growing number of cells reference as we get to new ranges. It becomes difficult for hundreds of horizontal ranges, because of the growing inline cells to add to the SUM Functions.
It is also prone to errors. And possibly it can break if rows or columns are added afterwards.
Trials:
I originally didn't think of using the INDIRECT Function (I never needed before). But I don't know any other Google Sheets function able to achieve the end results in a simpler way.
Questions:
What way to avoid the SUM Function method for the same result would you suggest, for a formula solution?
For a formula, what simpler function-s than the INDIRECT and/or SUM would be more efficient?
I also thought of using a script for doing that, but I can't put the whole idea into a manageable script concept process. What would you suggest if a script would be more appropriate?
Many thanks for your help!
The Sample Sheet:
Sample Sheet
EDIT:
I just found about the ADDRESS Function from this answer by Player0 (to help greatly simplify the INDIRECT function row and column references):
Google Sheets: INDIRECT() with a Range
References:
Excel INDIRECT Function
Google Sheets ADDRESS Function
I was able to create a script to show the ordinal number of the replicates but is only respective to one range. EDIT: I have modified it to also accept multiple row ranges. See updated answer below:
Script:
function showOrdinal(range) {
range = "A4:E12";
var values = SpreadsheetApp.getActiveSheet().getRange(range).getValues();
var output = [];
var order, subTotal;
values.forEach((x, i) => {
if(x.flat().filter(String).length) {
subTotal = values.slice(0, i + 1).flat().filter(String);
order = x.filter(String);
if (order[0] != '-') {
var row = order.map(x => {
return getNumberWithOrdinal(subTotal.filter(e => e == x).length);
})
row = [...row, ...Array(x.length - row.length)];
output.push(row);
if(output.length > 2)
output.splice(output.length - 2, 1);
}
order = [...order, ...Array(x.length - order.length)];
output.push(order)
}
else
output.push(x);
});
// console.log(output);
SpreadsheetApp.getActiveSheet().getRange(3, 21, output.length, output[0].length).setValues(output);
// return output;
}
// get ordinal number
// https://stackoverflow.com/a/31615643/14606045
function getNumberWithOrdinal(n) {
var s = ["th", "st", "nd", "rd"],
v = n % 100;
return n + (s[(v - 20) % 10] || s[v] || s[0]);
}
Output:
Note:
The range that is to be passed assumes that the first row should be the first range, not a blank one. And also the last row of the range should contain the last row of the data.
Any unrelated data should be starting with - on the first column so it can be allowed without processing it.

Google App Script - compare and copy data (URL Redirect list)

Please, do you have any idea how to solve the following work-flow in Google Spreadsheets or in JavaScript?
1) I have a list(sheet) of old URLs (1 URL per row)
2) I have a list(sheet) of new URLs (1 URL per row) which I want to pair to old similar URLs (for 301 redirect)
3) I have selected from URLs part of string which could be used for search and pair
4) I need to search the string in the list of new URLs and if there is a match, I need to copy this new URL and copy and paste it to the new column (D) next to the old URL.
I have tried google and lots of Google Spreadsheets Add-ons, but I cannot find anything that works.
I have the idea of the algorithm, but no clue how to write it in Google App Script (and the documentation didn't help)
1) for loop to select data from each row in column B
2) another for loop to select each row in column F
3) compare, if the data from step 1 is inside any cell in column F (for loop from step 2)
4) if there is a match, copy the cell from column F to column D, next to the substring which it found
Please, any ideas how to make it work?
Thank you for any tips.
function redirectSearch()
{
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sht=ss.getSheetByName('Redirect');
var rng=sht.getRange(2,1,sht.getLastRow(),sht.getLastColumn());
var rngA=rng.getValues();
var substringA=[];
for(var i=0;i<rngA.length;i++)//load the substring Array
{
substringA.push(rngA[i][1]);
}
for(var i=0;i<rngA.length;i++)
{
for(var j=0;j<substringA.length;j++)
{
if(String(rngA[i][3]).indexOf(substringA[j])>-1)//look for substring in all of the new urls
{
rngA[j][2]=rngA[i][3];//if found copy them to column C next to substring
}
}
}
rng.setValues(rngA);//reload results and original data
}

Concatenation in Column X of a Google Sheet, based in Column Y

I have a Google Sheet with addresses(talking about real life, street addresses) on one column, and status on those addresses (like VACANT, EVICTION, OCCUPIED) on another. I'm trying to create a formula that will allow me to make a new column with a concatenation of the address + a specific tag based on the status. For example, if I have address "11423 Whisper Sound Drive, FL", with the status "OCCUPIED", I wanna have another column that says "11423 Whisper Sound Drive, FL < green-dot >"
My current approach isn't working, I'm getting a parse error:
= function letsDoThis() {
var addressValue = getCell(D2);
var statusValue = getCell(G2);
if (statusValue == "OCCUPIED")
{
var newValue = addressValue + " <green-dot>";
getCell(O2).setValue(newValue);
}
}
You can try the following formula based solution:
Try the following formula in cell O2:
=if(G2="OCCUPIED";D2&" <green-dot>";)

Locating a cell's position in google sheets using a string or an integer

This problem seems so simple but I've been searching/trying for several hours to find a solution. I basically have a large spreadsheet (3k rows of ISBN's) which each have a corresponding author, title, genre etc.
My plan was to make a function that allows me to simply produce a small UI, which takes in an Isbn and an amount, has a submit button (all done) and then finds that ISBN and increments a value in that row. The big stumbling block is being able to use the isbn which has been stored as a variable and finding the match in the table, as I simply cannot find some sort of "getValue()" function in google sheets.
I've seen several examples of people loading their entire data into an array and comparing the desired value to each individual entry, but that seems ridiculously inefficient and slow to me? Surely there must be a simple, efficient way that I'm just overlooking. All I'm after is a simple way of searching for a value, finding that value in the sheet and returning that row.
Thanks in advance!
Your assumption that there must be a more efficient way to find an item in a spreadsheet is not true. There is no "find" method in spreadsheet service but array iteration is fast and bulk data reading is also very fast so you shouldn't be annoyed with the speed parameter even for very long lists.
Here are 2 code snippets that find the row number of a string in column 1.
In the first one I transposed the array so that I could search in a single column in a simple array.
Both show the time it takes in milliseconds.
function testISBN1(){
var time = new Date().getTime();
var isbn = 'test1990'; // just as a test... create your own Ui to enter the value to search for
Logger.log(findISBN(isbn)+' in '+(new Date().getTime()-time)+' mSec');// view result... false if no occurrence (do something with that value)
}
function findISBN(isbn){
var sh = SpreadsheetApp.getActive().getSheetByName('Sheet1');// select your sheet
var data = sh.getDataRange().getValues(); // get all data at once
var isbnColumn = transpose(data)[0];// if isbn is column 1 (it became row1 because of transpose
for(var n in isbnColumn){
if(isbnColumn[n] == isbn){ n++ ; return n }; // if found, increment to get the sheet row number
}
return false;// not found
}
function transpose(a) { // classical Array transpose JS function
return Object.keys(a[0]).map(
function (c) { return a.map(function (r) { return r[c]; }); }
)
}
And the second one, without transpose takes about the same time to execute (about 230 mS in a 2000 rows sheet)
function testISBN2(){
var time = new Date().getTime();
var isbn = 'test1990'; // just as a test... create your own Ui to enter the value to search for
Logger.log(findISBN2(isbn)+' in '+(new Date().getTime()-time)+' mSec');// view result... false if no occurrence (do something with that value)
}
function findISBN2(isbn){
var sh = SpreadsheetApp.getActive().getSheetByName('Sheet1');// select your sheet
var data = sh.getDataRange().getValues(); // get all data at once
for(var n in data){
if(data[n][0] == isbn){ n++ ; return n }; // if found (in column 1), increment to get the sheet row number
}
return false;// not found
}
Below is the execution transcript that shows the time taken by every line of code. As you can see, the total execution time is mainly used to get the sheet and get its values.