From a GAS, is it possible to call Maps API services such as DistanceMatrix?
I'd like to calculate distance between addresses stored in a spreadsheet.
I suppose displaying a map is required, to comply with terms of the Maps API.
Would something like the following be possible?
var service = new Maps.DistanceMatrixService();
service.getDistanceMatrix(
{
origins: [OAddresses],
destinations: [DAddresses],
travelMode: Maps.TravelMode.DRIVING,
unitSystem: Maps.UnitSystem.METRIC,
avoidHighways: false,
avoidTolls: false
}, callback);
This will help you..It is the complete code with file parsing.
It skips 1st row because of headings.
please change "sheet name" according to your need.
Check the screenshot for better understanding.
ScreenShot: https://docs.google.com/file/d/0B5nG8jqFwu2eMk9uajZJX2VhRjg/edit
function geoDistance() {
var sheetName = 'xyz';
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var selectedSheet = activeSpreadsheet.getSheetByName(sheetName);
var sheet = selectedSheet;
var lastRow = sheet.getLastRow();
var N = lastRow;
for (var i=2;i<=N;i++){
var startpoint = sheet.getRange(i, 1, 1, 1).getValue();
var endpoint = sheet.getRange(i, 2, 1, 1).getValue();
var directions = Maps.newDirectionFinder()
.setOrigin(startpoint)
.setDestination(endpoint)
.setMode(Maps.DirectionFinder.Mode.DRIVING)
.getDirections();
var print = sheet.getRange(i,3,1,1).setValue(directions.routes[0].legs[0].distance.text);
}}
Related
I'm a newbie to the field of javascript/angularJS, so please bear with me.I need a way to convert .xls/.xlsx files into .zip files by using jsZip library. I'm making use of alasql for generating the .xls file. I've looked all over for any possible solutions to create zip file of all xls files, but haven't come across any demo. (.txt and .doc files generate just fine, but .xls files does not open if jsZip is used). Any help would be appreciated!!
What I need is an xls file to be generated dynamically, and the same file to be compressed as zip
EDIT :-
Here's some of the code which I tried (but with no success)
var newExcelData = {'Name':'abc'};
//var res = alasql("SELECT * INTO XLSX('Summary.xlsx',{headers:true}) FROM ? ", [newExcelData]);
var zip = new JSZip();
zip.file(alasql("SELECT * INTO XLSX('Summary.xlsx',{headers:true}) FROM ? ", [newExcelData]));
zip.generateAsync({ type: "blob" })
.then(function (content) {
saveAs(content, "example.zip");
});
PS:- I'm able to make it work in case of generating .xls file.
Please refer below code:-
var newExcelData = {'Name':'abc', 'Age':'12'};
var zip = new JSZip();
zip.file("test.xls", [newExcelData]);
zip.generateAsync({ type: "blob" })
.then(function (content) {
saveAs(content, "example.zip");
});
But although excel sheet is generated, on opening excel sheet is blank.
Please help!!
Hi, here's an update :-
I've tried to make use of js-xlsx library - https://github.com/SheetJS/js-xlsx - to generate xls file and then zip it. Please refer the below code..
function Create_Zip() {
function datenum(v, date1904) {
if (date1904) v += 1462;
var epoch = Date.parse(v);
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
}
function sheet_from_array_of_arrays(data, opts) {
var ws = {};
var range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } };
for (var R = 0; R != data.length; ++R) {
for (var C = 0; C != data[R].length; ++C) {
if (range.s.r > R) range.s.r = R;
if (range.s.c > C) range.s.c = C;
if (range.e.r < R) range.e.r = R;
if (range.e.c < C) range.e.c = C;
var cell = { v: data[R][C] };
if (cell.v === null) continue;
var cell_ref = XLSX.utils.encode_cell({ c: C, r: R });
if (typeof cell.v === 'number') cell.t = 'n';
else if (typeof cell.v === 'boolean') cell.t = 'b';
else if (cell.v instanceof Date) {
cell.t = 'n'; cell.z = XLSX.SSF._table[14];
cell.v = datenum(cell.v);
}
else cell.t = 's';
ws[cell_ref] = cell;
}
}
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
return ws;
}
var data = [[1, 2, 3], [true, false, null, "sheetjs"], ["foo", "bar", new Date("2014-02-19T14:30Z"), "0.3"], ["baz", null, "qux"]];
var ws_name = "SheetJS";
function Workbook() {
if (!(this instanceof Workbook)) return new Workbook();
this.SheetNames = [];
this.Sheets = {};
}
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
/* add worksheet to workbook */
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: true, type: 'binary' });
function s2ab(s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
var jsonse = JSON.stringify([s2ab(wbout)]);
var testblob = new Blob([jsonse], { type: "application/json" });
console.log(testblob);
var zip = new JSZip();
zip.file("trial.xls", testblob);
var downloadFile = zip.generateAsync({ type: "blob" });
saveAs(downloadFile, 'test.zip');
}
But, the problem here is that I keep getting this error: 'The data of 'trial.xls' is in an unsupported format !' in the console :(. Is there any way I can make this work?
I'm at my wits end now :(
Not an answer (see below) but an explanation of what's going on:
To add a file, JSZip needs its binary content (as Blob, Uint8Array, etc). The line zip.file("test.xls", [newExcelData]); can't work for example: [newExcelData] is not a binary content but an array of js object.
What you need to figure out is how to get the content of the xlsx file. SELECT * INTO XLSX('Summary.xlsx') will trigger a download and return 1, it's not what you want. I searched on my side but can't find a way to do it with alasql.
Once/if you find the solution, the JSZip part looks correct.
Edit, following your switch to js-xlsx:
You use JSZip v2 (needed by js-xlsx) which doesn't support Blob inputs. However, wbout is a binary string which is supported:
zip.file("trial.xls", wbout, {binary: true});
Then, replace zip.generateAsync (added in JSZip v3):
var downloadFile = zip.generate({type: "blob" });
saveAs(downloadFile, 'test.zip');
Here is the solution I found using JSZip, XLSX and File Saver libraries.
Import:
import * as XLSX from "xlsx";
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
Here is an example of compressing a .xlsx inside a zip:
let zip = new JSZip();
const jsonData = [
{
"Product": "Red Velvet Cupcake",
"Price": "6",
"GluttenFree": "Yes",
},
{
"Product": "Cheesecake",
"Price": "15",
"GluttenFree": "No",
}
];
const workBook: XLSX.WorkBook = XLSX.utils.book_new();
const workSheet: XLSX.WorkSheet = XLSX.utils.json_to_sheet(jsonData);
XLSX.utils.book_append_sheet(workBook, workSheet, 'Bakery');
const workBookBuffer = XLSX.write(workBook, { bookType: 'xlsx', type: 'array' });
const fileData = new Blob([workBookBuffer], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'});
zip.file('Products.xlsx', fileData);
zip.generateAsync({type:"blob"}).then(function (blob) {
saveAs(blob, "WorkBooks.zip");
});
This code generates a zip file named 'WorkBooks.zip' that contains the file 'Products.xlsx'. This is how the excel looks like:
Some file-saver examples: https://www.tabnine.com/code/javascript/modules/file-saver.
Here is the JSZip method used:
https://stuk.github.io/jszip/documentation/api_jszip/file_data.html
I use a modal form to update/create my records "reloadGrid" method enables the grid enter image description here to refresh from within the modal "save" bouton, but
1) my form below does not get updated.
2) the last selected row on the jqGrid table gets not selected.
I tried to manualy force the row to be selected with
jQuery('#grid').jqGrid('setSelection', selRowID, true);
but selected row returns null.
Below is the function that saves the updated record and is suposed to trigger the grid and the controls update:
//function SauvegarderMessage(IDMessage) {
function ModifierDemandeur() {
//console.log("Modifier");
var xCodeDdeur = $('#txtCode_Demandeur').val();
var xAddDdeur = $('#txtAdresse_Demandeur').val();
var xIDVille = $('#cbxVille_Demandeur').val();
var xIDProv = $('#cbxProvince_Demandeur').val();
var xCPDdeur = $('#txtCodePostal_Demandeur').val();
var xTel1Ddeur = $('#txtTel1_Demandeur').val();
var xTel2Ddeur = $('#txtTel2_Demandeur').val();
var xTel3Ddeur = $('#txtTel3_Demandeur').val();
var xCour1Ddeur = $('#Courriel1_Demandeur').val();
var xCour2Ddeur = $('#Courriel2_Demandeur').val();
var xCour3Ddeur = $('#Courriel3_Demandeur').val();
var xIDSitMat = $('#cbxSitMat_Demandeur').val();
var xIDSexe = $('#cbxSexe_Demandeur').val();
var xDteNais = $('#txtDteNaissance').val();
var xRevDdeur = $('#txtrevenu_demandeur').val();
var xIDOcc = $('#cbxOccupation_Demandeur').val();
var xIDScol = $('#cbxScolarite_Demandeur').val();
var xIDStatLegal = $('#cbxStatutLegal_Demandeur').val();
var xIDComm = $('#cbxCommunaute_Demandeur').val();
var xIDSceInfo = $('#cbxSourceInformation_Demandeur').val();
var xHandicape = $('#cbHandi').val();
var xRef = $('#txtReference_Demandeur').val();
var xRemDdeur = $('#Remarques_Demandeur').val();
$.ajax({
type: "POST",
url: "../Conseiller/UpdateDemandeurs",
data: {
Code_Demandeur: xCodeDdeur,
Adresse_Demandeur: xAddDdeur,
ID_Ville: xIDVille,
ID_Province: xIDProv,
CodePostal_Demandeur: xCPDdeur,
Tel1_Demandeur: xTel1Ddeur,
Tel2_Demandeur: xTel2Ddeur,
Tel3_Demandeur: xTel3Ddeur,
Courriel1_Demandeur: xCour1Ddeur,
Courriel2_Demandeur: xCour2Ddeur,
Courriel3_Demandeur: xCour3Ddeur,
ID_SituationMatrimoniale: xIDSitMat,
ID_Sexe: xIDSexe,
Date_Naissance_Demandeur: xDteNais,
Revenu_Demandeur: xRevDdeur,
ID_Occupation: xIDOcc,
ID_Scolarite: xIDScol,
ID_StatutLegal: xIDStatLegal,
ID_Communaute: xIDComm,
ID_SourceInformation: xIDSceInfo,
Handicape: xHandicape,
Reference: xRef,
Remarques_Demandeur: xRemDdeur,
},
dataType: "json",
success: function (data) {
//console.log(data)
//console.log(data.rows);
$('#modifierProfilModal').modal('hide');
if (data != null) {
jQuery('#grid').jqGrid('clearGridData')
.jqGrid('setGridParam', { data: data, datatype: 'json' })
.trigger('reloadGrid')
for (var i = 0; i < data.Data.records; i += 1) {
var selRowID = data.Data.rows[i].Code_Demandeur;
var selectedRowID = $('#btnModifierProfile').val();
if (selRowID == selectedRowID) {
//jQuery('#grid').jqGrid('setSelection', selRowID, true);
var selr = jQuery('#grid').jqGrid('getGridParam', 'selrow');
console.log('Ligne sélectionnée: ' + selr);
return;
}
}
}
},
error: function (err) { console.log(err); }
});
I tried editRow action to force row selection, I tried to set focus to the grid, nothing would do.
A work around would be to identify each field with the last selected element's ID and assign each control appropriate value using document.getElementById('controlID').value = some_json_returned_data;
But this still not solve the grid row selection issue and does not seem to be a "best practice" way to do.
Coding the "loadComplete" method of qgrid resolved both issue:
loadComplete: function () {
if ($('#btnModifierProfile').val() != "")
jQuery('#grid').jqGrid('setSelection', $('#btnModifierProfile').val());
},
Desired row is selected and related data populated in other controls.
I am trying to publish forum replies to a specific thread, but I would like those reply documents to include extra information about the user that posted it.
I don't want to "save" that extra information on the reply itself but rather, publish an "improved" version of it.
I am doing something similar on client-side already with mycollection.find().map() and using the map function to embedded extra information on each of the returned documents, however, Meteor publish cannot seem to publish an array, only a Cursor, so the simple map function is off limits.
Is there a way to achieve this? Maybe a "map" function that returns a Cursor?
I am not using Meteor.methods so that I can have reactivity, because with them I could just return an array and use it as normal.
Here is an example of my code (that fails, but sends gives an idea of what I need):
Meteor.publish("forumthread", function(thread){
return forumReplies.find({thread: thread}).map(function(r){
// lets fill in additional data about each replies owner
var owner = Meteor.users.findOne({_id: r.owner});
if(!owner)
return; // no owner no reply..
if(!owner.forumStats){
owner.forumStats = {};
owner.forumStats.postCount = 0;
owner.forumStats.postLikes = 0;
owner.forumStats.title = "The Newbie";
owner.forumStats.tag = "Newbie";
Meteor.users.update({_id: owner._id}, {$set:{ forumStats:owner.forumStats }});
}
r.ownerid = owner._id;
r.ownerUsername = owner.username;
r.ownerPostCount = owner.forumStats.postCount;
r.ownerPostLikes = owner.forumStats.postLikes;
r.ownerTitle = owner.forumStats.title;
r.ownerTag = owner.forumStats.tag;
return r;
});
});
Thank you.
Ended up doing this (found out that Christian Fritz also suggested it):
Meteor.publish("serverforumthread", function(thread){
check(thread, String);
var replies = forumReplies.find({thread: thread});
var users = {};
replies.map(function(r){
users[r.owner] = r.owner;
});
var userids = _.map(users, function(value, key){ return value; });
var projectedFields = {_id:1, username:1, forumStats: 1, services: 0};
var usrs = Meteor.users.find({_id:{$in: userids}}, projectedFields);
var anyUpdateToUsers = false;
usrs.map(function(owner){
var changed = false;
if(!owner.username){
owner.username = owner.emails[0].address.split("#")[0];
changed = true;
}
//owner.forumStats = undefined;
if(!owner.forumStats){
owner.forumStats = {};
owner.forumStats.postCount = 0;
owner.forumStats.postLikes = 0;
owner.forumStats.title = "the newbie";
owner.forumStats.tag = "newbie";
owner.forumStats.img = "http://placehold.it/122x122";
changed = true;
}
if(changed){
anyUpdateToUsers = true;
Meteor.users.update({_id: owner._id}, {$set:{ forumStats:owner.forumStats }});
}
});
if(anyUpdateToUsers) // refresh it
usrs = Meteor.users.find({_id:{$in: userids}}, projectedFields);
usrs.map(function(owner){
console.log(owner);
});
return [replies, usrs];
});
It works great with the following client side:
Template.forumReplyOwner.helpers({
replyOwner: function(reply){
var owner = Meteor.users.findOne({_id: reply.owner});
console.log(reply, owner);
if(!owner || !owner.forumStats) return; // oh shait!
var r = {};
r.owner = owner._id;
r.ownerUsername = owner.username;
r.ownerPostCount = owner.forumStats.postCount;
r.ownerPostLikes = owner.forumStats.postLikes;
r.ownerTitle = owner.forumStats.title;
r.ownerTag = owner.forumStats.tag;
r.ownerImg = owner.forumStats.img;
return r;
},
ownerImgTab: function(){
return {src: this.ownerImg};
}
});
However, I am now facing another problem. Even tho I am restricting the fields I am publishing from the Users collection, it is still sending down the "services" field, that contains login data that shouldnt be sent, ideas?
I have a page at http://www.no1hastings.check.com.au/directions.html where visitors can get directions from anywhere to a fixed point. The destination that the Google geocoder recognises is a street address (1 Morwong Drive) but I would like it to display the name of the building there (No. 1 in Hastings Street) which Google doesn't recognise.
Is there way to set the street address as the destination but alias it to the building name when the result is displayed?
One option would be to modify the string in the response before sending it to the DirectionsRenderer:
function calcRoute() {
var request = {
origin: 'Brisbane QLD Australia Australia',
// origin: 'Brisbane Qld, Australia',
destination: '1 Morwong Drive, Noosa Heads Qld, Australia',
// waypoints:[{location: 'Bourke, NSW'}, {location: 'Broken Hill, NSW'}],
travelMode: google.maps.DirectionsTravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.METRIC
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var route = response.routes[0];
var lastleg = route.legs[route.legs.length-1];
response.routes[0].legs[route.legs.length-1].end_address = 'No. 1 in Hastings Street';
directionsDisplay.setDirections(response);
} else alert("Directions request failed: "+status);
});
}
To handle the case where the directions are changed by the directionsRenderer (i.e. for dragging the origin), you could do something like this (where changeEnd is a global and set to false), note that it will have undesireable results if the user drags the destination (you may want to prevent that: Google maps api v3 Two markers one fixed one dragged):
google.maps.event.addListener(directionsDisplay, 'directions_changed', function() {
computeTotalDistance(directionsDisplay.directions);
if (!changeEnd) {
var response = directionsDisplay.getDirections();
var route = response.routes[0];
var lastleg = route.legs[route.legs.length-1];
response.routes[0].legs[route.legs.length-1].end_address = 'No. 1 in Hastings Street';
changeEnd = true;
directionsDisplay.setDirections(response);
} else changeEnd = false;
});
I'm not sure this is the right group. If not, please let me know.
My dilemma:
I need to add Polylines to Google Earth from the results I get back from the Google Maps v3 DirectionsService. There seems to be nothing on the web to that extent. It has to be possible, because Roman does this in his driving simulator: http://earth-api-samples.googlecode.com/svn/trunk/demos/drive-simulator/index.html
Unfortunately, he is using Google Maps v2 there and I can't seem to figure out how to transfer this code into Google Maps v3.
If anyone is interested, here is how I managed to solve it:
function DrawLinesOnEarth() {
var sLat;
var sLon;
//var start = document.getElementById("start").value;
//var end = document.getElementById("end").value;
var request = {
origin: '40.306134,-74.05018',
destination: '40.313223,-74.043496',
travelMode: google.maps.TravelMode.WALKING
};
directionsService.route(request, function (result, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(result);
var steps = result.routes[0].legs[0].steps;
//Step through array of step legs and create polylines one by one
var lineStringPlacemark = IMC_ge.createPlacemark('');
var lineString = IMC_ge.createLineString('');
lineStringPlacemark.setGeometry(lineString);
// Add LineString points
for (var x in steps) {
for (var y in steps[x].path) {
sLat = steps[x].path[y].Na;
sLon = steps[x].path[y].Oa;
lineString.getCoordinates().pushLatLngAlt(sLat, sLon, 0);
}
}
// Add the feature to Earth
IMC_ge.getFeatures().appendChild(lineStringPlacemark);
}
});
}