Tweaking publicly-available Travel Time script to allow Waypoints - google-apps-script

I am trying to add to a publicly-available script to give it a bit more functionality, but I keep getting an "invalid argument: address" error.
The original script, which does what it advertises:
/**
* 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";
}
}
My tweaked version, which simply adds the arguments for up to 4 waypoints and then adds them into the mapObj variable used to get the directions.
function GOOGLEMAPS(start_address,end_address, return_type, waypoint1, waypoint2, waypoint3, waypoint4) {
// https://www.chicagocomputerclasses.com/
// Nov 2017
// improvements needed
var mapObj = Maps.newDirectionFinder();
mapObj.setOrigin(start_address);
mapObj.setDestination(end_address);
if(waypoint1 !== null ){mapObj.addWaypoint(waypoint1);}
if(waypoint2 !== null ){mapObj.addWaypoint(waypoint2);}
if(waypoint3 !== null ){mapObj.addWaypoint(waypoint3);}
if(waypoint4 !== null ){mapObj.addWaypoint(waypoint4);}
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";
}
}
The line giving the error is this added one. The if clause is my attempt at making the argument optional, as I'll be using this on lists of waypoints of varying length.
if(waypoint1 !== null ){mapObj.addWaypoint(waypoint1);}

For example, in your script, when the value of waypoint1 is not given as the argument of GOOGLEMAPS, the value becomes undefined. In this case, waypoint1 !== null is true. I thought that this might be the reason for your issue of invalid argument: address because in this case, mapObj.addWaypoint(waypoint1) is run with undefined.
If you want to run mapObj.addWaypoint(waypoint1) when waypoint1 has the value, how about the following modification?
From:
if(waypoint1 !== null ){mapObj.addWaypoint(waypoint1);}
if(waypoint2 !== null ){mapObj.addWaypoint(waypoint2);}
if(waypoint3 !== null ){mapObj.addWaypoint(waypoint3);}
if(waypoint4 !== null ){mapObj.addWaypoint(waypoint4);}
To:
if (waypoint1 !== undefined) { mapObj.addWaypoint(waypoint1); }
if (waypoint2 !== undefined) { mapObj.addWaypoint(waypoint2); }
if (waypoint3 !== undefined) { mapObj.addWaypoint(waypoint3); }
if (waypoint4 !== undefined) { mapObj.addWaypoint(waypoint4); }
or
if (waypoint1) { mapObj.addWaypoint(waypoint1); }
if (waypoint2) { mapObj.addWaypoint(waypoint2); }
if (waypoint3) { mapObj.addWaypoint(waypoint3); }
if (waypoint4) { mapObj.addWaypoint(waypoint4); }
References:
null
undefined

Related

Importing Twilight Data to google sheets

I'm going to be honest, I know very little about any of this and the last time I did any form of programming was in high school 12 years ago.
I am needing to create a schedule for a low budget shoot, however, that is happening in August and I need to send out a daily schedule for the upcoming days as it changes.
I've been trying to work out how to potentially amend this so that it could include the other twilight times as well, but it keeps giving me an error:
// for an idiot, what am I doing wrong? Ideally it would be constructed in such a way that I can use it on future projects as well, placing a screen grab - it would be great if the formula can make reference to other cells and update as those cells update:
screengrab of google sheets
function SolarTimes(lat,long,date,type) {
var response = UrlFetchApp.fetch("https://api.sunrise-sunset.org/json?lat="+lat+"&lng="+long+"&date="+date);
var json = response.getContentText();
var data = JSON.parse(json);
var sunrise = data.results.sunrise;
var sunset = data.results.sunset;
var civil_dawn = data.results.civil_twilight_begin;
var civil_dusk = data.results.civil_twilight_end;
var nautical_dawn = data.results.nautical_twilight_begin;
var nautical_dusk = data.results.nautical_twilight_end;
var day_length = data.results.day_length;
{ if (type == "Sunrise")
return sunrise;
else if (type == "Sunset")
return sunset;
else if (type = "Civil_Dawn")
return civildawn;
else if (type == "Civil_Dusk")
return civildusk;
else if (type == "Nautical_Dawn")
return nauticaldawn;
else if (type == "Nautical_Dusk")
return nauticaldusk;
else
return day_length};
}
Here's an implementation that handles numeric dates and validates arguments.
/**
* Gets the sunrise or sunset time or day length at a location on a date.
*
* #param {36.7201600} latitude The north–south position to use.
* #param {-4.4203400} longitude The east-west position to use.
* #param {"sunrise"} type One of "all", "sunrise", "sunset", "civil_dawn", "civil_dusk", "nautical_dawn", "nautical_dusk" or "day_length".
* #param {D2} date Optional. Defaults to the current date.
* #return {String|String[][]} The requested time as a text string. With "all", an array of types and times.
* #customfunction
*/
function Daylight(latitude, longitude, type, date) {
// see https://stackoverflow.com/a/72675674/13045193
// note: api.sunrise-sunset.org/json does not handle polar night nor midnight sun correctly
'use strict';
const [lat, lng, key, dateString] = _validate(arguments);
const url = `https://api.sunrise-sunset.org/json?lat=${lat}&lng=${lng}&date=${dateString}`;
const { results, status } = JSON.parse(UrlFetchApp.fetch(url).getContentText());
if (key === 'all') {
return Object.keys(results).map(key => [key, results[key]]);
}
return results[key] ?? NaN;
/**
* Validates function arguments.
*/
function _validate(args) {
if (args.length < 3 || args.length > 4) {
throw new Error(`Wrong number of arguments to Daylight. Expected 3 or 4 arguments, but got ${args.length} arguments.`);
}
const lat = Number(latitude);
const lng = Number(longitude);
if (latitude === '' || Number.isNaN(lat) || lat > 90 || lat < -90 || longitude === '' || Number.isNaN(lng) || lng > 180 || lng < -180) {
throw new Error(`Daylight expected a numeric latitude [-90, 90] and longitude [-180, 180], but got the ${typeof latitude} '${latitude}' and the ${typeof longitude} '${longitude}'.`);
}
return [
lat,
lng,
type.toLowerCase().replace('dawn', 'twilight_begin').replace('dusk', 'twilight_end'),
_dateToISO8601(date),
];
}
/**
* Parses a date or string to an ISO8601 date string.
*/
function _dateToISO8601(date) {
if (date === '' || (date == null)) {
date = new Date();
}
if (typeof date === 'string') {
date = new Date(Date.parse(date));
}
if (Object.prototype.toString.call(date) !== '[object Date]') {
throw new Error(`Daylight expected a date, but '${date}' is a ${typeof date}.`);
}
return Utilities.formatDate(date, SpreadsheetApp.getActive().getSpreadsheetTimeZone(), 'yyyy-MM-dd');
}
}
The code looks more or less fine. But the cell contains the little error I think. It should be thusly:
=Solartimes($G$3,$H$3,text($B$6,"yyyy-mm-dd"),C6)
As for the code I'd propose to use switch/case for this case:
function SolarTimes(lat,long,date,type) {
var response = UrlFetchApp.fetch("https://api.sunrise-sunset.org/json?lat="+lat+"&lng="+long+"&date="+date);
var json = response.getContentText();
var {results} = JSON.parse(json);
switch (type.toLowerCase()) {
case ('sunrise'): return results.sunrise;
case ('sunset'): return results.sunset;
case ('civil_dawn'): return results.civil_twilight_begin;
case ('civil_dusk'): return results.civil_twilight_end;
case ('nautical_dawn'): return results.nautical_twilight_begin;
case ('nautical_dusk'): return results.nautical_twilight_end;
case ('day_length'): return results.day_length;
}
return '--';
}
It works about the the same way but looks cleaner.
Just in case. The line:
var {results} = JSON.parse(json);
is the same as:
var data = JSON.parse(json);
var results = data.results;
See:
Destructuring assignment

How to prevent error throwing in Google Apps Script?

Please see the code herein under:
function binanceOrderBook() {
try {
muteHttpExceptions = true;
var workSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var mySheet = workSpreadsheet.getSheetByName('Order Books');
if(mySheet == 'Sheet'){
mySheet.activate();
} else {
mySheet = workSpreadsheet.insertSheet('Order Books', 1).activate();
}
var ui = SpreadsheetApp.getUi();
var string = 'https://api.binance.com/api/v3/depth?';
var symbolResponse = ui.prompt('Pair Name', 'Please enter the pair symbol.\n\nExamples: BTCUSDT or ETHBTC:', ui.ButtonSet.OK_CANCEL);
var symbolButton = symbolResponse.getSelectedButton();
if(symbolButton == ui.Button.CANCEL){return}
var mySymbol = symbolResponse.getResponseText();
mySymbol = mySymbol.toUpperCase();
string = string + "symbol=" + mySymbol;
var limitResponse = ui.prompt('Limit:', 'Please enter Limit (Period Quantity).\nValid limits are:5, 10, 20, 50, 100, 500, 1000. \n Default limit is 100.\n You can leave it blank and simply click OK.', ui.ButtonSet.OK_CANCEL);
if(limitResponse.getSelectedButton() == ui.Button.CANCEL){return}
var myLimit = Number(limitResponse.getResponseText());
if(myLimit != 5 && myLimit != 10 && myLimit != 20 && myLimit != 50 && myLimit != 100 && myLimit != 500 && myLimit != 1000){myLimit = 100;}
string = string + "&limit=" + myLimit;
var myDate = new Date().toUTCString();
var jsonOrderBookData = JSON.parse(UrlFetchApp.fetch('https://api.binance.com/api/v3/depth?symbol=' + mySymbol + '&limit=' + myLimit));
reporter(jsonOrderBookData);
} catch (e){
exceptionHandler(e)
}
}
The problem I have is to run UrlFetchApp.fetch again when it encounters an error. I need to run it several times to get the result. So, I need to prevent the script from stopping when an error (code -1003) occurs, but how can I do that?
EDIT: There is a function windows.onerror in javascript which can be set to prevent the program from stopping. Is it useable in GAS? if yes, how? if No, is there a similar solution for GAS?
You could call binanceOrderBook() from within your catch statement. E.g.
...
} catch (e){
binanceOrderBook()
exceptionHandler(e)
}
Of course you probably should have some condition that exits the function if a certain error occurs, or if you know that the function needs to run no more than x number of times you could check that it has run less than x times before executing. For example,
const maxValue = 10 // whatever the max number of executions should be
function binanceOrderBook(executions) {
if (executions >= maxValue) return;
try {
...
} catch(e) {
binanceOrderBook((executions || 0) + 1));
exceptionHandler(e); // note that I am including this here because it's in your original example, but as it is written now, exception handler won't be called until binanceOrderBook executes without an error.
}
}
[Edit] To answer your second question, there is no equivalent to window.onerror that I know of in GAS. However, window.onerror is a global event handler and so would affect errors thrown by any functions defined in your project. To address a concern with a single function call like this, you are better off using a try catch statement as you have.

When values are being written to google sheets from the nodemcu, the heart beat sensor (MAX30100) does not read values. How can I fix this error?

I am setting up a device which will monitor the temperature and heartbeat of a person. The temperature and heartbeat values are instantly uploaded to google sheets using a script. The temperature values work fine, but the heartbeat values show 0. When I run the code without uploading data, the vales work fine in the Arduino serial plotter.
All values obtained when displayed on Arduino serial plotter. But unfortunately, when using the postData() the heartbeat values show 0.
#include <ESP8266WiFi.h>
#include "HTTPSRedirect.h"
#include <Wire.h>
#include "MAX30100_PulseOximeter.h"
#define REPORTING_PERIOD_MS 1000
PulseOximeter pox;
uint32_t tsLastReport = 0;
const char* ssid = "";
const char* password = "";
// The ID below comes from Google Sheets.
// Towards the bottom of this page, it will explain how this can be obtained
const char *GScriptId = "gscriptid";
// Push data on this interval
//const int dataPostDelay = 900000; // 15 minutes = 15 * 60 * 1000
const char* host = "script.google.com";
const char* googleRedirHost = "script.googleusercontent.com";
const int httpsPort = 443;
HTTPSRedirect client(httpsPort);
// Prepare the url (without the varying data)
//String url = String("/macros/s/") + GScriptId + "/exec?";
const char* fingerprint = "some fingerprint";
// We will take analog input from A0 pin
const int AnalogIn = A0;
// Callback (registered below) fired when a pulse is detected
void onBeatDetected()
{
Serial.println("Beat!");
}
void setup() {
Serial.begin(115200);
Serial.println("Connecting to wifi: ");
Serial.println(ssid);
Serial.flush();
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" IP address: ");
Serial.println(WiFi.localIP());
Serial.print(String("Connecting to "));
Serial.println(host);
bool flag = false;
for (int i=0; i<5; i++){
int retval = client.connect(host, httpsPort);
if (retval == 1) {
flag = true;
break;
}
else
Serial.println("Connection failed. Retrying...");
}
// Connection Status, 1 = Connected, 0 is not.
Serial.println("Connection Status: " + String(client.connected()));
Serial.flush();
if (!flag){
Serial.print("Could not connect to server: ");
Serial.println(host);
Serial.println("Exiting...");
Serial.flush();
return;
}
// Data will still be pushed even certification don't match.
if (client.verify(fingerprint, host)) {
Serial.println("Certificate match.");
} else {
Serial.println("Certificate mis-match");
}
Serial.print("Initializing pulse oximeter..");
// Initialize the PulseOximeter instance
// Failures are generally due to an improper I2C wiring, missing power supply
// or wrong target chip
if (!pox.begin()) {
Serial.println("FAILED");
for (;;);
} else {
Serial.println("SUCCESS");
}
pox.setOnBeatDetectedCallback(onBeatDetected);
}
// This is the main method where data gets pushed to the Google sheet
void postData(float temp, float bpm){
if (!client.connected()){
Serial.println("Connecting to client again...");
client.connect(host, httpsPort);
}
String urlFinal = String("/macros/s/") + GScriptId + "/exec?temperature=" + String(temp) + "&bpm=" + String(bpm);
client.printRedir(urlFinal, host, googleRedirHost);
}
// Continue pushing data at a given interval
void loop() {
int analogValue = analogRead(A0);
float millivolts = (analogValue/1024.0) * 3300; //3300 is the voltage provided by NodeMCU
float celsius = millivolts/10;
// Serial.print("in DegreeC= ");
// Serial.println(celsius);
// // Post these information
// Serial.println("/");
// postData(celsius);
// Serial.println("/");
// delay (10000);
// Make sure to call update as fast as possible
pox.update();
// Asynchronously dump heart rate and oxidation levels to the serial
// For both, a value of 0 means "invalid"
if (millis() - tsLastReport > REPORTING_PERIOD_MS) {
Serial.print("Heart rate:");
float bpm = pox.getHeartRate();
Serial.print(bpm);
// Serial.print("bpm / SpO2:");
// float spo2 = pox.getSpO2();
// Serial.print(spo2);
// Serial.println("%");
Serial.println();
Serial.print("TEMPRATURE = ");
Serial.print(celsius);
Serial.print("*C");
postData(celsius, bpm);
Serial.println();
tsLastReport = millis();
}
}
I expect the code to write the temperature and heartbeat values into the formatted google sheets.
Google Sheets image
//Google script code
function doGet(e) {
Logger.log( JSON.stringify(e) ); // view parameters
var result = 'Ok'; // assume success
if (e.parameter == 'undefined') {
result = 'No Parameters';
}
else {
var sheet_id = 'sheetid'; // Spreadsheet ID
var sheet = SpreadsheetApp.openById(sheet_id).getActiveSheet(); // get Active sheet
var newRow = sheet.getLastRow() + 1;
var rowData = [];
rowData[0] = new Date(); // Timestamp in column A
for (var param in e.parameter) {
Logger.log('In for loop, param=' + param);
var value = stripQuotes(e.parameter[param]);
Logger.log(param + ':' + e.parameter[param]);
switch (param) {
case 'temperature': //Parameter
rowData[1] = value; //Value in column B
result = 'Written on column B';
break;
case 'bpm': //Parameter
rowData[2] = value; //Value in column C
result += ' ,Written on column C';
break;
default:
result = "unsupported parameter";
}
}
Logger.log(JSON.stringify(rowData));
// Write new row below
var newRange = sheet.getRange(newRow, 1, 1, rowData.length);
newRange.setValues([rowData]);
}
// Return result of operation
return ContentService.createTextOutput(result);
}
/**
* Remove leading and trailing single or double quotes
*/
function stripQuotes( value ) {
return value.replace(/^["']|['"]$/g, "");
}
// End of file

Error when trying to set Google Forms quiz score

I'm trying to change the grade of a response based on its answer.
Here's the code I'm using:
function myFunction() {
var form = FormApp.openById('formID123456');
// For a question with options: "1", "2", "3", and "4",
// award points for responses that correlate with their answers.
var formResponses = FormApp.getActiveForm().getResponses();
// Go through each form response
for (var i = 0; i < formResponses.length; i++) {
var response = formResponses[i];
var items = FormApp.getActiveForm().getItems();
// Assume it's the first item
var item = items[0];
var itemResponse = response.getGradableResponseForItem(item);
// Give 4 points for "4".
if (itemResponse != null && itemResponse.getResponse() == '4') {
var points = item.asScaleItem().getPoints();
itemResponse.setScore(points == 4);
}
// Give 3 points for "3".
else if (itemResponse != null && itemResponse.getResponse() == '3') {
var points = item.asScaleItem().getPoints();
itemResponse.setScore(points == 3);
}
// Give 2 points for "2".
else if (itemResponse != null && itemResponse.getResponse() == '2') {
var points = item.asScaleItem().getPoints();
itemResponse.setScore(points == 2);
}
// Give 1 points for "1".
else if (itemResponse != null && itemResponse.getResponse() == '1') {
var points = item.asScaleItem().getPoints();
itemResponse.setScore(points == 1);
// This saves the grade, but does not submit to Forms yet.
response.withItemGrade(itemResponse);
}
}
// Grades are actually submitted to Forms here.
FormApp.getActiveForm().submitGrades(formResponses);
}
This returns the error:
We're sorry, a server error occurred. Please wait a bit and try again. (line 23, file "Code")
It seemed like it was having issues changing the score of the response, but it didn't return a specific error, so I tried to isolate the part that changes the score.
Here, the script attempts only to change the score of the response.
function myFunction() {
var form = FormApp.openById('formID123456');
var formResponses = FormApp.getActiveForm().getResponses();
// Go through each form response
for (var i = 0; i < formResponses.length; i++) {
var response = formResponses[i];
var items = FormApp.getActiveForm().getItems();
// Assume it's the first item
var item = items[0];
var itemResponse = response.getGradableResponseForItem(item);
// Set Score to 3
var points = item.asScaleItem().getPoints();
itemResponse.setScore(points == 3);
}}
Again, it returned the same error, which confirms my suspicions. Why am I having this problem and how can I fix it? Any help would be much appreciated. Thanks!
As I mentioned in comments, your posted code erroneously uses a Boolean value in the call to ItemResponse#setScore, when the method expects to receive an Integer value.
Resolving the internal server error can be done by changing your entire if-elseif chain from this:
if (itemResponse != null && itemResponse.getResponse() == '4') {
var points = item.asScaleItem().getPoints();
itemResponse.setScore(points == 4); //<--- 'points == 4' evaluates to True or False
}
// Give 3 points for "3".
else if (...
to this:
// Skip changing the score if there was no answer or the answer is "falsey"
if (!itemResponse || !itemResponse.getResponse())
continue;
var answer = itemResponse.getResponse();
var newPoints = answer *1; // Convert "2" to 2, etc.
// Assumption: newPoints <= maximum possible points.
itemResponse.setScore(newPoints);
response.withItemGrade(itemResponse);
The below code is an example of how to set all graded items in all responses to a form to their maximum possible value.
function everyonePassesForTrying() {
var form = FormApp.getActiveForm();
var responses = form.getResponses();
responses.forEach(function (fr) {
fr.getGradableItemResponses().forEach(function (gr) {
if (gr.getResponse()) {
var maxPoints = getPointValue_(gr.getItem());
if (gr.getScore() !== maxPoints) {
// Re-grade the item's response.
gr.setScore(maxPoints);
// Update the form response with the new grade.
fr.withItemGrade(gr);
}
}
else { /* They didn't even try, so no change */ }
});
});
// Submit the altered scores.
form.submitGrades(responses);
}
var itemCache = {};
function getPointValue_(item) {
var id = item.getId();
// Use a pseudo-cache of the item's point values to avoid constantly determining what
// type it is, casting to that type, and getting the max points.
if(!itemCache[id]) {
// Haven't seen it yet, so cast and cache.
item = castToType_(item);
itemCache[id] = {maxPoints: item.getPoints() *1};
}
return itemCache[id].maxPoints;
}
function castToType_(item) {
// Cast the generic item to its type.
var CHECKBOX = FormApp.ItemType.CHECKBOX,
DATE = FormApp.ItemType.DATE,
DATETIME = FormApp.ItemType.DATETIME,
DURATION = FormApp.ItemType.DURATION,
LIST = FormApp.ItemType.LIST,
MULTICHOICE = FormApp.ItemType.MULTIPLE_CHOICE,
PARAGRAPH = FormApp.ItemType.PARAGRAPH_TEXT,
SCALE = FormApp.ItemType.SCALE,
TEXT = FormApp.ItemType.TEXT,
TIME = FormApp.ItemType.TIME;
switch (item.getType()) {
case CHECKBOX: item = item.asCheckboxItem();
break;
case DATE: item = item.asDateItem();
break;
case DATETIME: item = item.asDateTimeItem();
break;
case DURATION: item = item.asDurationItem();
break;
case LIST: item = item.asListItem();
break;
case MULTICHOICE: item = item.asMultipleChoiceItem();
break;
case PARAGRAPH: item = item.asParagraphTextItem();
break;
case SCALE: item = item.asScaleItem();
break;
case TEXT: item = item.asTextItem();
break;
case TIME: item = item.asTimeItem();
break;
default:
throw new Error("Unhandled gradable item type '" + item.getType() + "'");
break;
}
return item;
}

How can I use a custom function with FILTER?

I have a custom function defined that extracts part of an address from a string:
/*
* Return the number preceding 'N' in an address
* '445 N 400 E' => '445'
* '1083 E 500 N' => '500'
*/
function NorthAddress(address) {
if (!address) return null;
else {
var North = new RegExp('([0-9]+)[\\s]+N');
var match = address.match(North);
if (match && match.length >= 2) {
return match[1];
}
return null;
}
}
I want to use this function as one of the conditions in a call to FILTER(...) in the spreadsheet where I have these addresses stored:
=FILTER('Sheet 1'!A:A, NorthAddress('Sheet 1'!B:B) >= 450))
But when I call NorthAddress like this, it gets an array of all the values in column B and I can't for the life of me find any documentation as to how I need to handle that. The most obvious way (to me) doesn't seem to work: iterate over the array calling NorthAddress on each value, and return an array of the results.
What does my function need to return for FILTER to work as expected?
When a custom function is called passing a multi-cell range, it receives a matrix of values (2d array), it's doesn't matter if the range is a single column or a single row, it's always a matrix. And you should return a matrix as well.
Anyway, I would not use a custom function to this, as there is already the native spreadsheet formulas: RegexMatch, RegexExtract and RegexReplace formulas. To get the "if match" behavior, just wrap them in a IfError formula.
It doesn't work because address is, if you pass only one cell as arg a string, a range, a matrix of string.
So you return a string, FILTER use a boolean array to filter data, so the condition of your filter is string < number.
You just have to convert the string to a number when you returning a value
/*
* Return the number preceding 'N' in an address
* '445 N 400 E' => '445'
* '1083 E 500 N' => '500'
*/
function NorthAddress(address) {
if(typeof address == "string"){
if (!address) return "#N/A";
else {
var North = new RegExp('([0-9]+)[\\s]+N');
var match = address.match(North);
if (match && match.length >= 2) {
return parseInt(match[1]);
}
return "#N/A";
}
} else {
var matrix = new Array();
for(var i = 0; i<address.length; i++){
matrix[i] = new Array();
for(var j = 0; j<address[i].length; j++){
var North = new RegExp('([0-9]+)[\\s]+N');
var match = address[i][j].match(North);
if (match && match.length >= 2) {
matrix[i].push(parseInt(match[1]));
}
}
}
return matrix;
}
}
Hope this will help.
I will add this as an answer, because I found the custom function returns an error if numerical values are passed in the referenced cell or range when toString() is not invoked:
function NorthAddress(address) {
if (!address) return null;
else {
if (address.constructor == Array) {
var result = address;
}
else {
var result = [[address]];
}
var north = new RegExp('([0-9]+)[\\s]+N');
var match;
for (var i = 0; i < result.length; i++) {
for (var j = 0; j < result[0].length; j++) {
match = result[i][j].toString().match(north);
if (match && match.length >= 2) {
result[i][j] = parseInt(match[1]);
}
else {
result[i][j] = null;
}
}
}
return result;
}
}