Parsing Google Maps JSON data for Geocoding in JQ (Not JQuery) - json

I am trying to get the Country and City names from Lat and Long values with JQ.
Here is the full example JSON
https://maps.googleapis.com/maps/api/geocode/json?latlng=55.397563,10.39870099999996&sensor=false
I pasted returned JSON in jqplay,
Tried to select Country and City names, but the closest I get is
.results[0].address_components[].short_name
How can I specify just bring the nodes where "types" : [ "country", "political" ] ?
Thanks

It's unclear to me what exactly you're looking for. Each result has a set of types, each address component also has a set of types. Which one did you want? We can write a filter that will match what you attempted but considering the data, it will be completely useless to you. The only item that contains the types you listed is just a country name.
Anyway, assuming you wanted to get a result object that had the types "country" and "political", use the contains() filter.
.results | map(
select(
.types | contains(["country","political"])
)
)
Otherwise you'll need to clarify what exactly you wanted from this data set. An example of the expected results...

I wrote a function to do this.
/**
* geocodeResponse is an object full of address data.
* This function will "fish" for the right value
*
* example: type = 'postal_code' =>
* geocodeResponse.address_components[5].types[1] = 'postal_code'
* geocodeResponse.address_components[5].long_name = '1000'
*
* type = 'route' =>
* geocodeResponse.address_components[1].types[1] = 'route'
* geocodeResponse.address_components[1].long_name = 'Wetstraat'
*/
function addresComponent(type, geocodeResponse, shortName) {
for(var i=0; i < geocodeResponse.address_components.length; i++) {
for (var j=0; j < geocodeResponse.address_components[i].types.length; j++) {
if (geocodeResponse.address_components[i].types[j] == type) {
if (shortName) {
return geocodeResponse.address_components[i].short_name;
}
else {
return geocodeResponse.address_components[i].long_name;
}
}
}
}
return '';
}
The way to use it; an example:
...
myGeocoder.geocode({'latLng': marker.getPosition()}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK && results[1]) {
var country = addresComponent('country', results[1], true);
var postal_code = addresComponent('postal_code', results[1], true);
...
}
});
...
I used it here: saving marker data into db

Assign the json to results variable, var results = {your json}.
Then try this :
for( var idx in results.results)
{
var address = results.results[idx].address_components;
for(var elmIdx in address)
{
if(address[elmIdx].types.indexOf("country") > -1 &&
address[elmIdx].types.indexOf("political") > -1)
{
address[elmIdx].short_name //this is the country name
address[elmIdx].long_name //this is the country name
}
}
}

Related

How to use a for loop with .createChoice in Google Apps Script to create a quiz from a sheet?

I am using Google Apps Script to generate Google Forms from a Sheet. Questions are in rows and question choices are in columns.
Here is a link to the Google sheet if needed.
It is a straightforward task when using .setChoiceValues(values)
if (questionType == 'CHOICE') {
var choicesForQuestion = [];
for (var j = 4; j < numberColumns; j++)
if (data[i][j] != "")
choicesForQuestion.push(data[i][j]);
form.addMultipleChoiceItem()
.setChoiceValues(choicesForQuestion);
}
However, when I try to use .createChoice(value, isCorrect), the parameters call for value to be a string and isCorrect to be Boolean.
An example without a loop looks like this:
var item = FormApp.getActiveForm().addCheckboxItem();
item.setTitle(data[3][1]);
// Set options and correct answers
item.setChoices([
item.createChoice("chocolate", true),
item.createChoice("vanilla", true),
item.createChoice("rum raisin", false),
item.createChoice("strawberry", true),
item.createChoice("mint", false)
]);
I can not figure out how to add the loop. After reading over other posts, I have tried the following:
if (questionType == 'CHOICE') {
var questionInfo = [];
for (var j = optionsCol; j < maxOptions + 1; j++)
if (data[i][j] != "")
questionInfo.push( form.createChoice(data[i][j], data[i][j + maxOptions]) );
form.addMultipleChoiceItem()
.setChoices(questionInfo);
}
optionsCol is the first column of questions options
maxOptions is how many options are allowed by the sheet (currently 5). The isCorrect information is 5 columns to the right.
However, this not working because the array questionsInfo is empty.
What is the best way to do this?
Probably your issue is related to the method you reference--Form#createChoice--not existing. You need to call MultipleChoiceItem#createChoice, by first creating the item:
/**
* #param {Form} formObj the Google Form Quiz being created
* #param {any[]} data a 1-D array of data for configuring a multiple-choice quiz question
* #param {number} index The index into `data` that specifies the first choice
* #param {number} numChoices The maximum possible number of choices for the new item
*/
function addMCItemToForm_(formObj, data, index, numChoices) {
if (!formObj || !data || !Array.isArray(data)
|| Array.isArray(data[0]) || data.length < (index + 2 * numChoices))
{
console.error({message: "Bad args given", hasForm: !!formObj, info: data,
optionIndex: index, numChoices: numChoices});
throw new Error("Bad arguments given to `addMCItemToForm_` (view on StackDriver)");
}
const title = data[1];
// Shallow-copy the desired half-open interval [index, index + numChoices).
const choices = data.slice(index, index + numChoices);
// Shallow-copy the associated true/false data.
const correctness = data.slice(index + numChoices, index + 2 * numChoices);
const hasAtLeastOneChoice = choices.some(function (c, i) {
return (c && typeof correctness[i] === 'boolean');
});
if (hasAtLeastOneChoice) {
const mc = formObj.addMultipleChoiceItem().setTitle(title);
// Remove empty/unspecified choices.
while (choices[choices.length - 1] === "") {
choices.pop();
}
// Convert to choices for this specific MultipleChoiceItem.
mc.setChoices(choices.map(function (choice, i) {
return mc.createChoice(choice, correctness[i]);
});
} else {
console.warn({message: "Skipped bad mc-item inputs", config: data,
choices: choices, correctness: correctness});
}
}
You would use the above function as described by its JSDoc - pass it a Google Form object instance to create the quiz item in, an array of the details for the question, and the description of the location of choice information within the details array. For example:
function foo() {
const form = FormApp.openById("some id");
const data = SpreadsheetApp.getActive().getSheetByName("Form Initializer")
.getSheetValues(/*row*/, /*col*/, /*numRows*/, /*numCols*/);
data.forEach(function (row) {
var qType = row[0];
...
if (qType === "CHOICE") {
addMCItemToForm_(form, row, optionColumn, numOptions);
} else if (qType === ...
...
}
References
Array#slice
Array#forEach
Array#map
Array#some
I am sure the above answer is very good and works but I am just a beginner and needed a more obvious (plodding) method. I am generating a form from a spreadsheet. Question types can include: short answer (text item), long answer (paragraph), drop down (list item), multiple choice, grid item, and checkbox questions, as well as sections.
I had to be able to randomize the input from the spreadsheet for multiple choice and sort the input for drop downs. I am only allowing one correct answer at this time.
The columns in the question building area of the spreadsheet are: question type, question, is it required, does it have points, hint, correct answer, and unlimited choice columns.
qShtArr: getDataRange of the entire sheet
corrAnsCol: index within the above of the column with the correct answer
begChoiceCol: index within the above of first column with choices
I hope this helps other less skilled coders.
/**
* Build array of choices. One may be identified as correct.
* I have not tried to handle multiple correct answers.
*/
function createChoices(make, qShtArr, r, action) {
// console.log('Begin createChoices - r: ', r);
let retObj = {}, choiceArr = [], corrArr = [], aChoice, numCol, hasCorr;
numCol = qShtArr[r].length - 1; // arrays start at zero
if ((qShtArr[r][corrAnsCol] != '') && (qShtArr[r][corrAnsCol] != null)) {
hasCorr = true;
choiceArr.push([qShtArr[r][corrAnsCol], true]);
for (let c = begChoiceCol ; c < numCol ; c++) {
aChoice = qShtArr[r][c];
if ((aChoice != '') && (aChoice != null)) { /* skip all blank elements */
choiceArr.push([aChoice, false]);
}
} //end for loop for multiple choice options
} else {
hasCorr = false;
for (let c = begChoiceCol ; c < numCol ; c++) {
aChoice = qShtArr[r][c];
if ((aChoice != '') && (aChoice != null)) { /* skip all blank elements */
choiceArr.push(aChoice);
}
} //end for loop for multiple choice options
}
if (action == 'random')
choiceArr = shuffleArrayOrder(choiceArr);
if (action == 'sort')
choiceArr.sort();
console.log('choiceArr: ', JSON.stringify(choiceArr) );
let choices = [], correctArr = [] ;
if (hasCorr) {
for ( let i = 0 ; i < choiceArr.length ; i++ ) {
choices.push(choiceArr[i][0]);
// console.log('choices: ', JSON.stringify(choices) );
correctArr.push(choiceArr[i][1]);
// console.log('correctArr: ', JSON.stringify(correctArr) );
}
make.setChoices(choices.map(function (choice, i) {
return make.createChoice(choice, correctArr[i]);
}));
} else { // no correct answer
if (action == 'columns' ) {
make.setColumns(choiceArr);
} else {
make.setChoices(choiceArr.map(function (choice, i) {
return make.createChoice(choice);
}));
}
}
}

JSON contains array of nulls with three objects - expected only three objects

Using a standalone Google Apps Script and a Google Spreadsheet. I have this script which returns as JSON an array of nulls and three objects, but I expected only to get three objects. Its a search, and when a zipcode is searched, the script is to return any matches. The thing is, it returns the matches successfully, but it also returns a null for each row that was not a match, in the order the rows appear on the google sheet. To make it work, the function testDoGetWithZipcode() should be run.
I don't know if I'm supposed to get those nulls, if they matter, or how I can fix it. It doesn't seem to go with anything I've learned about JSON so far but before even asking this I did an hour and a half Lynda.com course on Javascript and JSON and read the JSON.org website and read the documentation on Mozilla about JSON. I've adjusted variables in all of the functions because at first I thought it was in the function formatOrganization() but now I'm completely stumped.
s = SpreadsheetApp.openById("1280aUAvFoUDP2rtpCFS2JYR7TuQNYcd5gm8QudukiGc");
var sheet = s.getSheetByName("RAP - Data");
var data = sheet.getDataRange().getValues();
var headings = data[0];
function zipcodeQuery(zipcode) {
zipcodeArray = [];
for (var i = 1; i < data.length; i++){
if (zipcode === data[i][4].toString()){
zipcodeArray.push(data[i]);
}
}
return zipcodeArray
}
function formatOrganization(rowData){
var organization = {}
for (var i = 0; i < headings.length; i++){
Logger.log('Headings: ' + headings[i]);
organization[headings[i].toString()] = rowData[i];
}
return organization
}
function executeZipcodeQuery(request) {
zipcodes = request.parameters.zipcode;
// The object to be returned as JSON
response = {
organizations : []
}
// Fill the organzations dictionary with requested organizations
for (var i = 0; i < zipcodes.length; i++) {
sheetData = zipcodeQuery(zipcodes[i]);
if(sheetData !== undefined) {
for (var orgIndex = 0; orgIndex < sheetData.length; orgIndex++) {
var org = formatOrganization(sheetData[orgIndex]);
if(org !== undefined) {
Logger.log('Org object: ' + org);
if(typeof org === 'object') {
//FIXME
var orgId = parseInt(org.Id);
Logger.log('Org Id: ' + orgId);
response.organizations[orgId] = org
//response.organizations.push({orgId : org});
}
}
}
}
}
if (response.organizations.length > 0)
{
return ContentService.createTextOutput(JSON.stringify(response.organizations));
}
else
{
return ContentService.createTextOutput('Invalid Request. zipcode(s) not found.');
}
}
function testDoGetWithZipcode() {
var testRequest = {"parameter":{"zipcode":"19132"},"contextPath":"","contentLength":-1,"queryString":"zipcode=19132","parameters":{"zipcode":["19132"]}};
var textResult = doGet(testRequest);
textResult.setMimeType(ContentService.MimeType.JSON);
Logger.log('Mime Type: ' + textResult.getMimeType());
Logger.log('Result content: ' + textResult.getContent());
}
The return I get is this (abridged because there's over a 180 rows in the spreadsheet and they're all represented in the return by either null or an object):
[
null,
....
null,
{
"Id":61,
"Category":"Day / Drop in Centers",
"Organization Name":"Philadelphia Recovery Community Center (PRCC)",
"Address":"1701 W Lehigh Ave, Philadelphia, PA 19132",
"Zip Code":19132,
"Days":"Mon, Tues, Thurs, Fri: 12-8pm, Wed: 9-5pm, Sat: 9-1pm",
"Time: Open":"",
"Time: Close":"",
"People Served":"Women, Men, Families",
"Description":"Case management, outpatient treatment, youth programs, training programs",
"Phone Number":"215-223-7700"
},
....
null,
{
"Id":81,
"Category":"Emergency Shelter",
"Organization Name":"Station House",
"Address":"2601 N Broad St, Philadelphia, PA 19132",
"Zip Code":19132,
"Days":"",
"Time: Open":"",
"Time: Close":"",
"People Served":"Men",
"Description":"After hours reception for single men\n 2601 N. Broad Street\n After 4 pm",
"Phone Number":"215-225-9230"
},
null,
...
]
Your original object is this:
response = {
organizations : []
}
The value of the key/value pair for organizations is an array. But you are using notation as if organizations was an object.
response.organizations[orgId] = org
You could push a value into the array with:
response.organizations.push(org);
I'd probably try something like this:
var tempObject = {}; //Reset every time
tempObject[orgId] = org;
response.organizations.push(tempObject);

ScriptDB object size calculation

I'm trying to estimate the limits of my current GAS project. I use ScriptDB to chunk out processing to get around the 6 min execution limit. If I have an object like
var userObj{
id: //user email address
count: //integer 1-1000
trigger: //trigger ID
label: //string ~30 char or less
folder: //Google Drive folder ID
sendto: //'true' or 'false'
shareto: //'true' or 'false'
}
How would I calculate the size that this object takes up in the DB? I would like to project how many of these objects can exist concurrently before I reach the 200MB limit for our domain.
Whenever you've got a question about google-apps-script that isn't about the API, try searching for javascript questions first. In this case, I found JavaScript object size, and tried out the accepted answer in apps-script. (Actually, the "improved" accepted answer.) I've made no changes at all, but have reproduced it here with a test function so you can just cut & paste to try it out.
Here's what I got with the test stud object, in the debugger.
Now, it's not perfect - for instance, it doesn't factor in the size of the keys you'll use in ScriptDB. Another answer took a stab at that. But since your object contains some potentially huge values, such as an email address which can be 256 characters long, the key lengths may be of little concern.
// https://stackoverflow.com/questions/1248302/javascript-object-size/11900218#11900218
function roughSizeOfObject( object ) {
var objectList = [];
var stack = [ object ];
var bytes = 0;
while ( stack.length ) {
var value = stack.pop();
if ( typeof value === 'boolean' ) {
bytes += 4;
}
else if ( typeof value === 'string' ) {
bytes += value.length * 2;
}
else if ( typeof value === 'number' ) {
bytes += 8;
}
else if
(
typeof value === 'object'
&& objectList.indexOf( value ) === -1
)
{
objectList.push( value );
for( i in value ) {
stack.push( value[ i ] );
}
}
}
return bytes;
}
function Marks()
{
this.maxMarks = 100;
}
function Student()
{
this.firstName = "firstName";
this.lastName = "lastName";
this.marks = new Marks();
}
function test () {
var stud = new Student();
var studSize = roughSizeOfObject(stud);
debugger;
}

MongoDB Map&Reduce much slower than MySQL group by

I am trying to evaluate which Databasesystem to use for a new Project.
At the moment I compare MySQL and MongoDB for the task at hand.
I have abotu 5 Million Recoreds with 350 Numeric fields, and I have to use this data to provide different granularity levels for some graph plotting.
I pumped the data into a MongoDB and into a Mysql, and on Mysql I generated some interim tables with 10/th, 100/th and 1000/th of the granularity. The application then chooses the correct table that matches best for the current task and then queries the data there.
With this technique I can get the data fast enough ( < 100 ms).
The SQL query I use is:
SELECT from_unixtime(CAST(FLOOR(MIN(STAMP/1000)) AS SIGNED INTEGER)),
MIN(RING),MIN(STATE),CAST(FLOOR(MIN(STAMP)) as SIGNED INTEGER),AVG(w21030401)
FROM project1 GROUP BY FLOOR((stamp - 1181589892000)/60000);
I use the identical query for creating the interim tables. The only difference is, tat there are 350 wXXXXXX fields.
INSERT INTO project1_10 (TTIME,RING,STATE,STAMP,w21030401,.........)
SELECT from_unixtime(CAST(FLOOR(MIN(STAMP/1000)) AS SIGNED INTEGER)),
MIN(RING),MIN(STATE),CAST(FLOOR(MIN(STAMP)) as SIGNED INTEGER),AVG(w21030401),.......
FROM project1 GROUP BY FLOOR((stamp - 1181589892000)/60000);
Then I tried to do the same thing with MongoDB.
I pumed all the data into MongoDB and got 4,8 Million documents in the Form:
{ "_id" : ObjectId("50040b3f0cf2872a8d3af90d"), "TTIME" :
ISODate("2008-11-30T06:40:07Z"), "STAMP" : NumberLong("1228027207000"),
"STATE" : 2531, "RING" : 1, "w13010096" : 34.991, "w13010097" : 1.432,
"w23010001" : 292, "w18030180" : 84, "w18030380" : 95, "w21030002" : 51.113,
"w21030005" : 60.321, "w21030004" : 274.662, "w21030008" : 149.629,
"w21030009" : 126.565, "w21030010" : 576.296, ........... }
Then I tried to generate the Interim Documents with the following mapReduce:
keylist = [ 'w21030401', 'w13011114', .... ];
m = function (){
var result = {};
result['STAMP'] = this['STAMP'];
result['RING'] = this['RING'];
result['TTIME'] = this['TTIME'];
result['STATE'] = this['STATE'];
for(var key in keylist){
if(key in this) {
result[key] = this[key];
result['cnt_' + key] = 1;
}
}
var zone = Math.floor((this['STAMP'] - 1171004118000) / 1000000);
emit( zone , result );
};
r = function (name, values){
var result = {};
result['STAMP'] = values[0]['STAMP'];
result['RING'] = values[0]['RING'];
result['TTIME'] = values[0]['TTIME'];
result['STATE'] = values[0]['STATE'];
for(var key in keylist) {
result[key] = 0;
result['cnt_' + key] = 0;
}
for ( var i=0; i<values.length; i++ ) {
if(values[i]['STAMP'] < result['STAMP']) {
result['STAMP'] = values[i]['STAMP'];
result['TTIME'] = values[i]['TTIME'];
}
if(values[i]['RING'] < result['RING']) {
result['RING'] = values[i]['RING'];
}
if(values[i]['STATE'] < result['STATE']) {
result['STATE'] = values[i]['STATE'];
}
for(var key in keylist) {
if(key in values[i]) {
result[key] += values[i][key];
result['cnt_' + key] += values[i]['cnt_' + key];
}
}
}
return result;
};
f = function(who, val){
var result = {};
result['STAMP'] = val['STAMP'];
result['RING'] = val['RING'];
result['TTIME'] = val['TTIME'];
result['STATE'] = val['STATE'];
for(var key in keylist) {
if(key in val) {
result[key] = val[key]/val['cnt_'+key];
}
}
return result;
};
db.project1.mapReduce( m, r, { finalize : f, scope: { keylist: keylist }, out : {replace : 'project1_100'} , jsMode : false });
MySQL used 210 Seconds for the creation fo the Interim Table, MongoDB used about 4 Hours.
My Question is:
Is MongoDB not suitable for my Problem, do I just need bigger Hardware for MongoDB than for MySQL, or did I do something wrong wih my MapReduce
Thanks
Peter

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;
}
}