Skip to content

Instantly share code, notes, and snippets.

@jpotts18
Last active December 21, 2015 19:49
Show Gist options
  • Save jpotts18/6356678 to your computer and use it in GitHub Desktop.
Save jpotts18/6356678 to your computer and use it in GitHub Desktop.
function readRows() {
// var SPREADSHEET_ID = "";
var FOLDER_ID = "";
var folder = DriveApp.getFolderById(FOLDER_ID);
moveOldKMLFilesToTrash(folder);
var ss = SpreadsheetApp.getActiveSpreadsheet();
// var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
// get the first sheet in the spreasdsheet at index [0]
var sheet = ss.setActiveSheet(ss.getSheets()[0]);
// turn every row into a json with the header as the key
var rowsData = getRowsData(sheet, sheet.getDataRange());
// find all the unique featureTypes
var featureTypes = getFeatureTypes(rowsData);
for(var key in featureTypes){
var featureList = featureTypes[key];
var kml = createLayerKML(key,featureList);
saveLayerKML(key,kml);
Utilities.sleep(500);
}
}
function moveOldKMLFilesToTrash(folder){
var oldFiles = folder.getFiles();
while(oldFiles.hasNext()){
var file = oldFiles.next();
file.setTrashed(true);
}
}
function createLayerKML(key,featureList){
var kmlString = initializeKML(key);
for(var i = 0; i < featureList.length; i++){
kmlString += buildKMLPlacemarkPoint(featureList[i]);
}
kmlString += finishKML();
return kmlString;
}
function saveLayerKML(featureName, kml){
var FOLDER_ID = "";
var rootFolder = DriveApp.getRootFolder();
var folder = DriveApp.getFolderById(FOLDER_ID);
var xmlFile = DriveApp.createFile(featureName, kml.toString(), MimeType.PLAIN_TEXT);
folder.addFile(xmlFile);
rootFolder.removeFile(xmlFile);
}
// TODO: Rework to make this function more object oriented and use Xml.parseJS() to stringify
// @david.morris - added support for KML LineStrings on Sunday August 4, 2013
function buildKMLPlacemarkPoint(feature){
var placemarkString = "";
if (validPlacemark(feature)) {
placemarkString += "<Placemark>"
+ "<name>" + (feature.name ? feature.name : "") + "</name>"
+ ((validExtendedData(feature)) ? getExtendedSchema(feature) : "" );
// handle DataType == "Point"
// TODO handle bad data with better conditional checks
if (feature.dataType == "" || feature.dataType == "Point") {
placemarkString += "<Point>"
+ "<coordinates>" + feature.longitude + "," + feature.latitude + ",0.000000</coordinates>"
+ "</Point>";
}
// handle DataType == "Line"
else if (feature.dataType == "Line") {
placemarkString += "<LineString>"
+ "<tessellate>1</tessellate>" // TODO add support for non-tesselation lines?
+ "<coordinates>"
+ feature.coordinates
+ "</coordinates>"
+ "</LineString>";
}
placemarkString += "</Placemark>";
}
return placemarkString ;
}
// 8/7/13 - Maribeth removed unecessary extended schema that had already been removed from the speadsheet (amenities, hours, canToggle, clickable, subtitle)
// and added if statement to attach customId to line features
function getExtendedSchema(feature){
var extendedSchema = "<ExtendedData>"
+ "<Data name='thumbnail'><value>" + feature.thumbnail +"</value></Data>"
+ "<Data name='image'><value>" + feature.image +"</value></Data>"
+ "<Data name='description'><value>" + feature.description +"</value></Data>"
+ "<Data name='url'><value>" + feature.url +"</value></Data>"
+ "<Data name='hasDetail'><value>" + feature.hasDetail +"</value></Data>"
+ "<Data name='folderIcon'><value>" + feature.folderIcon +"</value></Data>"
+ "<Data name='featureIcon'><value>" + feature.featureIcon +"</value></Data>";
if (feature.dataType === "Line") {
extendedSchema += "<Data name='customId'><value>" + feature.customId +"</value></Data>";
}
extendedSchema += "</ExtendedData>";
return extendedSchema;
}
function validPlacemark(feature){
if((feature.approved) && (feature.latitude) && (feature.longitude) && (feature.name) && (feature.approved)){
return true;
} else {
return false;
}
}
function validExtendedData(feature){
if((feature.group) && (feature.name)){
return true;
} else {
return false;
}
}
function initializeKML(name){
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><kml xmlns=\"http://www.opengis.net/kml/2.2\">"
+"<Document>"
+ "<name>" + name + "</name>";
}
function finishKML(){
return "</Document>"
+"</kml>";
}
function getFeatureTypes(features){
var featureTypes ={};
for (var i = 1; i < features.length; i++) {
var feature = features[i];
var featureKey = feature.featureType;
// if the featureType does not exist in the object add it
if(featureTypes[featureKey] === undefined){
delete feature.featureType;
featureTypes[featureKey] = [feature];
// if the featureType exists
} else {
var featureArray = featureTypes[featureKey];
delete feature.featureType;
featureArray.push(feature);
}
};
return featureTypes;
}
////////////////////////
// Example JSON Library from Google App Script documentation
// located @ https://developers.google.com/apps-script/guides/sheets#reading-2
///////////////////////
// getRowsData iterates row by row in the input range and returns an array of objects.
// Each object contains all the data for a given row, indexed by its normalized column name.
// Arguments:
// - sheet: the sheet object that contains the data to be processed
// - range: the exact range of cells where the data is stored
// This argument is optional and it defaults to all the cells except those in the first row
// or all the cells below columnHeadersRowIndex (if defined).
// - columnHeadersRowIndex: specifies the row number where the column names are stored.
// This argument is optional and it defaults to the row immediately above range;
// Returns an Array of objects.
function getRowsData(sheet, range) {
var headersIndex = 1;
var dataRange = range ||
sheet.getRange(headersIndex + 1, 1, sheet.getMaxRows() - headersIndex, sheet.getMaxColumns());
var numColumns = dataRange.getLastColumn() - dataRange.getColumn() + 1;
Logger.log(headersIndex + ' ' + dataRange.getColumn() + ' ' + 1 + ' ' + numColumns);
var headersRange = sheet.getRange(headersIndex, dataRange.getColumn(), 1, numColumns);
var headers = headersRange.getValues()[0];
return getObjects(dataRange.getValues(), normalizeHeaders(headers));
}
// For every row of data in data, generates an object that contains the data. Names of
// object fields are defined in keys.
// Arguments:
// - data: JavaScript 2d array
// - keys: Array of Strings that define the property names for the objects to create
function getObjects(data, keys) {
var objects = [];
for (var i = 0; i < data.length; ++i) {
var object = {};
var hasData = false;
for (var j = 0; j < data[i].length; ++j) {
var cellData = data[i][j];
if (isCellEmpty(cellData)) {
continue;
}
object[keys[j]] = cellData;
hasData = true;
}
if (hasData) {
objects.push(object);
}
}
return objects;
}
// Returns an Array of normalized Strings.
// Arguments:
// - headers: Array of Strings to normalize
function normalizeHeaders(headers) {
var keys = [];
for (var i = 0; i < headers.length; ++i) {
var key = normalizeHeader(headers[i]);
if (key.length > 0) {
keys.push(key);
}
}
return keys;
}
// Normalizes a string, by removing all non-alphanumeric characters and using mixed case
// to separate words. The output will always start with a lower case letter.
// This function is designed to produce JavaScript object property names.
// Arguments:
// - header: string to normalize
// Examples:
// "First Name" -> "firstName"
// "Market Cap (millions) -> "marketCapMillions
// "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored"
function normalizeHeader(header) {
var key = "";
var upperCase = false;
for (var i = 0; i < header.length; ++i) {
var letter = header[i];
if (letter == " " && key.length > 0) {
upperCase = true;
continue;
}
if (!isAlnum(letter)) {
continue;
}
if (key.length == 0 && isDigit(letter)) {
continue; // first character must be a letter
}
if (upperCase) {
upperCase = false;
key += letter.toUpperCase();
} else {
key += letter.toLowerCase();
}
}
return key;
}
// Returns true if the cell where cellData was read from is empty.
// Arguments:
// - cellData: string
function isCellEmpty(cellData) {
return typeof(cellData) == "string" && cellData == "";
}
// Returns true if the character char is alphabetical, false otherwise.
function isAlnum(char) {
return char >= 'A' && char <= 'Z' ||
char >= 'a' && char <= 'z' ||
isDigit(char);
}
// Returns true if the character char is a digit, false otherwise.
function isDigit(char) {
return char >= '0' && char <= '9';
}
@neurotech
Copy link

Found this via Google when trying to get 'getRowsData' to work with getDataRange();

Saved my life. Thanks mate!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment