Using the App Script, I need to convert text:
Input:
POLYGON ((-111.705934405 32.76300316, -111.706191897 32.748205765,
-111.706191897 32.748205765, -111.706191897 32.748205765, -111.688682437 32.748566707, -111.688939929 32.733261511, -111.655036807 32.732828307, -111.655294299 32.740914437, -111.645938754 32.740842243, -111.646024585 32.748783271, -111.654607654 32.74914421, -111.654092669 32.763291866, -111.705934405 32.76300316))
Output:
[[{"lat":32.763597994888265,"lng":-111.70779863788822},{"lat":32.763670170794725,"lng":-111.65269533588626},{"lat":32.74887288656241,"lng":-111.65346781208255},{"lat":32.74880069866226,"lng":-111.64479891254642},{"lat":32.741004061134866,"lng":-111.64548555805423},{"lat":32.741004061134866,"lng":-111.65424028827884},{"lat":32.73356774344866,"lng":-111.65466944172123},{"lat":32.732990139407825,"lng":-111.70608202411869}]]
Using App script. I have converted text using formulas. Here is That sheet.
Polygon can have 200+ points. It is difficult to adjust those points in the formula.
And note that the latitude and longitude is reversed in the input.
In your situation, how about the following sample script?
Sample script:
const value = "POLYGON ((-111.705934405 32.76300316, -111.706191897 32.748205765, -111.706191897 32.748205765, -111.706191897 32.748205765, -111.688682437 32.748566707, -111.688939929 32.733261511, -111.655036807 32.732828307, -111.655294299 32.740914437, -111.645938754 32.740842243, -111.646024585 32.748783271, -111.654607654 32.74914421, -111.654092669 32.763291866, -111.705934405 32.76300316))";
const r1 = value.match(/\(\((.*)\)\)/);
const r2 = r1[1].split(",").map(e => {
const [lng, lat] = e.trim().split(" ");
return { lat: Number(lat), lng: Number(lng) };
});
const res = [r2];
console.log(res);
When this script is run, the following result is obtained.
[[{"lat":32.76300316,"lng":-111.705934405},{"lat":32.748205765,"lng":-111.706191897},{"lat":32.748205765,"lng":-111.706191897},{"lat":32.748205765,"lng":-111.706191897},{"lat":32.748566707,"lng":-111.688682437},{"lat":32.733261511,"lng":-111.688939929},{"lat":32.732828307,"lng":-111.655036807},{"lat":32.740914437,"lng":-111.655294299},{"lat":32.740842243,"lng":-111.645938754},{"lat":32.748783271,"lng":-111.646024585},{"lat":32.74914421,"lng":-111.654607654},{"lat":32.763291866,"lng":-111.654092669},{"lat":32.76300316,"lng":-111.705934405}]]
Note:
If you want to retrieve the values from column "A" of your sample Spreadsheet, how about the following sample script? In this case, from your showing the expected value, each element of the result array is a 2-dimensional array. Please be careful about this.
function myFunction() {
const sheetName = "Sheet1";
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const values = sheet.getRange("A2:A" + sheet.getLastRow()).getValues();
const res = values.map(([a]) => {
const r1 = a.match(/\(\((.*)\)\)/);
const r2 = r1[1].split(",").map(e => {
const [lng, lat] = e.trim().split(" ");
return { lat: Number(lat), lng: Number(lng) };
});
return [r2];
});
console.log(res);
}
Reference:
map()
Related
Hi I'm just new in Google Apps Script. I want to modify this formula below to Google Apps Script
=arrayFORMULA(iferror(VLOOKUP(J3,{'WO SR 22/23'!P:P,'WO SR 22/23'!B:B},2,FALSE)))
The lookup value is in wsPetitionStatusReport sheet. I tried code below but it always return null. Can you help me with this? Thank you so much
function vLookUpVALUE() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const wsWOSR = ss.getSheetByName("WO SR 22/23")
const wsPetitionStatusReport = ss.getSheetByName("Petition Status Report ")
const searchVALUES = wsPetitionStatusReport.getRange("J3:J").getValues()
const wsWOSRDATcolO = wsWOSR.getRange("B3:P" + wsWOSR.getLastRow()).getValues();
const matchDATAcolO = searchVALUES.map(searchROWcolO => {
const matchROWcolO = wsWOSRDATcolO.find (r => r[0] == searchROWcolO[0])
return matchROWcolO ? [matchROWcolO[0]] : [null]
})
console.log(matchDATAcolO)
}
You can evaluate the array formula for the whole column by replacing J3 with an open-ended reference like J3:J:
=arrayformula(iferror(vlookup(J3:J, { 'WO SR 22/23'!P:P, 'WO SR 22/23'!B:B }, 2, false)))
To do the same with Apps Script, use something like this:
function vlookupLookAlike() {
const ss = SpreadsheetApp.getActive();
const source = {
values: ss.getRange('WO SR 22/23!B3:P').getValues(),
keyColumn: 14, // =column(P3) - column(B3)
dataColumn: 0,
};
source.keys = source.values.map(row => row[source.keyColumn]);
source.data = source.values.map(row => row[source.dataColumn]);
const target = {
range: ss.getRange('Petition Status Report !J3:J'), // trailing space
};
target.keys = target.range.getValues().flat();
target.results = target.keys.map(key => [key && source.data[source.keys.indexOf(key)]]);
target.range.offset(0, 5).setValues(target.results);
}
I have a dataset which contains images in col C loaded via formula =IMAGE("") and the need is to refresh the data and have these formulas load the images at destination.
I tried the Spreadsheet API, but handling the data the way it's needed it still far to me - knowledge wise.
I try with the script below, but the column C shows as blank at destination:
function getOrdersData() {
const srcFile = SpreadsheetApp.openById('XXXXXXXXXXX');
const srcSht = srcFile.getSheetByName('Orders');
let srcData = srcSht.getRange(1, 1, srcSht.getLastRow(),
srcSht.getLastColumn()).getValues();
const orderHeaders = srcData[4]; //Colunm headers are actually in row 05
const imgCol = orderHeaders.indexOf('Image');//Whish is where the formulas loading the imgs are
const imgFormulas = srcSht.getRange(1, imgCol + 1, srcSht.getLastRow(), 1).getFormulas();
srcData.forEach(function (row) {
row.splice(imgCol, 1, imgFormulas);
});
const dstFile = SpreadsheetApp.openById('XXXXXXXXXXXXXXX');
const dstSht = dstFile.getSheetByName('Orders v2');
const dstShtLr = dstSht.getLastRow();
if (dstShtLr > 0) {
dstSht.getRange(1, 1, dstShtLr, dstSht.getLastColumn()).clearContent();
}
dstSht.getRange(1, 1, srcData.length, srcData[0].length).setValues(srcData);
}
What can I try next?
In your script, imgFormulas is a 2-dimensional array. In this case, by srcData.forEach(,,,), srcData is not 2 dimensional array. I thought that this might be the reason for your issue. When your script is modified, how about the following modification?
From:
srcData.forEach(function (row) {
row.splice(imgCol, 1, imgFormulas);
});
To:
srcData.forEach(function (row, i) {
if (i > 4) row.splice(imgCol, 1, imgFormulas[i][0]);
});
if (i > 4) was used for considering Colunm headers are actually in row 05.
Note:
In your situation, when Sheets API is used, the sample script is as follows. In this case, please enable Sheets API at Advanced Google services. When the number of cells are large, this might be useful.
function sample() {
const srcSSId = '###'; // Please set source Spreadsheet ID.
const dstSSId = '###'; // Please set destination Spreadsheet ID.
const srcSheetName = 'Orders';
const dstSheetName = 'Orders v2';
const srcValues = Sheets.Spreadsheets.Values.get(srcSSId, srcSheetName).values;
const srcFormulas = Sheets.Spreadsheets.Values.get(srcSSId, srcSheetName, { valueRenderOption: "FORMULA" }).values;
const data = [{ range: dstSheetName, values: srcValues }, { range: dstSheetName, values: srcFormulas }];
Sheets.Spreadsheets.Values.batchUpdate({ valueInputOption: "USER_ENTERED", data }, dstSSId);
}
References:
Method: spreadsheets.values.get
Method: spreadsheets.values.batchUpdate
I have many cells with multiple questions and answers in one cell like A1. When I run the apps script, I want the blue text formatting to be applied only to the answer line, like B1.
The algorithm I was thinking of is as follows.
Cut the questions and answers based on the newline character and make a list and condition processing within loop.
If the first character is -, it is a question, so pass or apply black format.
If the first character is ┗, it is an answer, so apply blue formatting.
But I'm new to apps script and google sheet api, so I don't know which way to go. Could you please write an example?
Try on active cell for instance
function formatCell() {
const range = SpreadsheetApp.getActiveRange()
// const range = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getDataRange() // for the whole sheet
const spec = { regex: /┗.*/gi, textColor: 'blue' };
const values = range.getDisplayValues();
let match;
const formattedText = values.map(row => row.map(value => {
const richText = SpreadsheetApp.newRichTextValue().setText(value);
const format = SpreadsheetApp.newTextStyle()
.setForegroundColor(spec.textColor)
.build();
while (match = spec.regex.exec(value)) {
richText.setTextStyle(match.index, match.index + match[0].length, format);
}
return richText.build();
}));
range.setRichTextValues(formattedText);
}
reference
Class RichTextValueBuilder
Use RichTextValue to set stylized text, and apply the corresponding TextStyle in lines that start with your desired character:
function changeAnswersColor() {
const sheet = SpreadsheetApp.getActiveSheet();
const textStyle = SpreadsheetApp.newTextStyle().setForegroundColor("blue").build();
const range = sheet.getRange("A1");
const values = range.getValues();
const richTextValues = values.map(row => {
return row.map(value => {
const valueLines = value.split("\n");
let builder = SpreadsheetApp.newRichTextValue().setText(value);
for (let i = 0; i < valueLines.length; i++) {
const vl = valueLines[i];
if (vl.trim()[0] === "┗") {
console.log(valueLines.slice(0,i).join("\n"))
const startOffset = valueLines.slice(0,i).join("\n").length;
const endOffset = startOffset + vl.length + 1;
builder.setTextStyle(startOffset, endOffset, textStyle);
}
}
const richTextValue = builder.build();
return richTextValue;
});
});
range.offset(0,range.getNumColumns()).setRichTextValues(richTextValues);
}
Continuing my previous question, #Tanaike proposed a solution to extract performance score from the following page:
This is the code snippet to get around it:
function CheckPageSpeed(url) {
const apiKey = "###"; // Please set your API key.
const apiEndpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?key=${apiKey}&url=${encodeURIComponent(url)}&category=performance`;
const strategy = ["mobile"];
const res = UrlFetchApp.fetchAll(strategy.map(e => ({ url: `${apiEndpoint}&strategy=${e}`, muteHttpExceptions: true })));
const values = res.reduce((o, r, i) => {
if (r.getResponseCode() == 200) {
const obj = JSON.parse(r.getContentText());
o[strategy[i]] = obj.lighthouseResult.categories.performance.score * 100;
} else {
o[strategy[i]] = null;
}
return o;
}, {});
return values.mobile;
}
As I am using it in Google sheets as custom formula, sometimes it takes so much time that the sheet throws the following error:
Is there any way that we can counter this error so that it starts calculating the score again instead of throwing an error? Thank you.
Issue and workaround:
From your showing image, your error of Exceed maximum execution time and your updated script, in this case, it is considered that the execution time of the script is over 30 seconds. (In the current stage, the maximum execution time of the custom function is 30 seconds. Ref) In this case, when the error of Exceed maximum execution time occurs, unfortunately, this cannot be used as the trigger. And also, in the current stage, UrlFetchApp cannot be stopped over time. And, for example, even when all URLs are retrieved and each value is retrieved from the API, I'm not sure whether the processing time is over 6 minutes. I'm worried about this.
From the above situation, how about manually rerunning only the custom functions which occur the error?
Sample script:
Before you use this script, please enable Sheets API at Advanced Google services. How about executing this function by a button on Spreadsheet and/or the custom menu?
function reCalculation() {
const sheetName = "Sheet1"; // Please set sheet name.
const formula = "=CheckPageSpeed"; // Please set the function name of your custom function.
const dummy = "=sample";
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ssId = ss.getId();
const sheet = ss.getSheetByName(sheetName);
const sheetId = sheet.getSheetId();
const values = sheet.getRange("B1:B" + sheet.getLastRow()).getDisplayValues();
const requests = values.reduce((ar, [a], i) => {
if (a == "#ERROR!") {
ar.push({ findReplace: { range: { sheetId, startRowIndex: i, endRowIndex: i + 1, startColumnIndex: 1, endColumnIndex: 2 }, find: `^${formula}`, replacement: dummy, includeFormulas: true, searchByRegex: true } }); // Modified
}
return ar;
}, []);
if (requests.length == 0) return;
Sheets.Spreadsheets.batchUpdate({ requests }, ssId);
SpreadsheetApp.flush();
requests.forEach(r => {
r.findReplace.find = dummy;
r.findReplace.replacement = formula;
r.findReplace.searchByRegex = false;
});
Sheets.Spreadsheets.batchUpdate({ requests }, ssId);
}
When this script is run, only the cells of #ERROR! in the column "B" are recalculated.
Note:
I thought that in this case, this function might be able to be executed by the time-driven trigger. But, in that case, it might affect the quotas (maximum execution time is 90 minutes/day) of the time-driven trigger. So, in this answer, I proposed to run this function using manual operation.
References:
Method: spreadsheets.batchUpdate
FindReplaceRequest
Added:
For example, in your situation, how about directly requesting the API endpoint using fetchAll method? The sample script is as follows. In this case, the URLs are retrieved from the column "A" and the values are retrieved and put to the column "C" in your sample Spreadsheet.
Sample script:
Please set your API key. And, please run this script with the script editor. By this, the values are retrieved using the API.
function reCalculation2() {
const apiKey = "###"; // Please set your API key.
const sheetName = "Sheet1"; // Please set sheet name.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(sheetName);
const values = sheet.getRange("A2:A" + sheet.getLastRow()).getValues();
const requests = values.map(([url]) => {
const apiEndpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?key=${apiKey}&url=${encodeURIComponent(url)}&category=performance&strategy=mobile`;
return { url: apiEndpoint, muteHttpExceptions: true };
});
const res = UrlFetchApp.fetchAll(requests);
const v = res.map(r => {
if (r.getResponseCode() == 200) {
const obj = JSON.parse(r.getContentText());
return [obj.lighthouseResult.categories.performance.score * 100];
}
return [null];
});
sheet.getRange(2, 3, v.length).setValues(v);
}
In this case, fetchAll method is used. By this, I thought that the error of Exceeded maximum execution might be able to be avoided.
I am new to Google App Scripts and am trying to modify this script to return the results to a spreadsheet instead of the logger.
function myFunction() {
const getFileList = (id, folders = []) => {
const f = DriveApp.getFolderById(id);
const fols = f.getFolders();
let temp = [];
while (fols.hasNext()) {
const fol = fols.next();
const files = fol.getFiles();
let fileList = [];
while (files.hasNext()) {
const file = files.next();
fileList.push({ name: file.getName(), id: file.getId() });
}
temp.push({
name: fol.getName(),
id: fol.getId(),
parent: id,
parentName: f.getName(),
files: fileList,
});
}
if (temp.length > 0) {
folders.push(temp);
temp.forEach((e) => getFileList(e.id, folders));
}
return folders;
};
const folderId = "###"; // Folder ID of the shared Drive.
const res = getFileList(folderId);
console.log(res);
}
I believe your goal as follows.
You want to put the values from getFileList(folderId) to the Spreadsheet.
The value returned from getFileList(folderId) is the array including JSON object. So, in this case, it is required to convert from JSON object to an array for each element. For this, I also think that Carlos M's comment is useful. But, for this, it is required to modify a little for putting values to Spreadsheet. Because the value from getFileList(folderId) is 2 dimensional array and each file in the subfolders is also included in an array. In this answer, I would like to propose the modified script for putting values to Spreadsheet.
When your script is modified, please modify as follows.
Modified script:
From:
const folderId = "###"; // Folder ID of the shared Drive.
const res = getFileList(folderId);
console.log(res);
To:
const folderId = "###"; // Folder ID of the shared Drive.
const res = getFileList(folderId);
console.log(res);
// I added below script.
const object = res.flat();
const headers = Object.keys(object[0]);
const values = object.flatMap(o => {
const temp = [];
headers.forEach(f => {
if (f != "files") temp.push(o[f]);
});
return o.files.map(({name, id}) => temp.concat(name, id));
});
headers.pop();
headers.push("file(name)", "file(id)");
values.unshift(headers);
SpreadsheetApp.getActiveSheet().getRange(1, 1, values.length, values[0].length).setValues(values);
When you run this modified script, the retrieved values are put to the active sheet.
Reference:
Related thread
How to write json data to a google sheet using javascript