Using JSZip to extract multiple KML files for Leaflet VectorGrid - leaflet

The map uses KML files to generate a single geoJSON object to pass to VectorGrid's slicer function. To improve performance, the files are served as a single KMZ and extracted using the JSZip library. We then loop through each file (KML), parse it and convert to geoJSON. The features are concatenated to a separate array which is used to create a final geoJSON object (a cheap way of merging).
var vectorGrid;
JSZipUtils.getBinaryContent('/pathto/file.kmz', function (error, data) {
JSZip.loadAsync(data).then(function (zip) {
var featureArray = [];
zip.forEach(function (path, file) {
file.async('string').then(function (data) {
// convert to geoJSON, concatenate features array
featureArray = featureArray.concat(geoJSON.features);
}
}
var consolidatedGeoJSON = {
'type': 'FeatureCollection,
'features': featureArray
};
vectorGrid = L.vectorGrid.slicer(consolidatedGeoJSON, options);
}
}
The idea was that once that operation was complete, I could take the final geoJSON and simply pass it to the slicer. However, due to the nature of the promises, it was always constructing the slicer first and then parsing the files after.
To get around this, I was forced to put the slicer function inside the forEach, but inside an if statement checking if the current file is the last in the zip. This allows the vectors to be drawn on the map, but now I can't enable a hover effect on each layer separately (each KML contains a specific layer used as an area outline for interaction).
The vectorGrid slider options allows you to specify a getFeatureId function, but I don't understand how to pass that id to the setFeatureStyle function in the event handlers.

The basic problem is that you try to assign value to vactorGrid before you assigned value to featureArray. I think that you need to use Promise.all(..). Something like that:
var zips=[];
zip.forEach(function(path,file) {
zips.push(file.async('string');
});
Promise.all(zips).then(function(data){
return data.map(function(value){
return value.features;
});
}).then(function(featureArray) {
vectorGrid = L.vectorGrid.slicer(
{type:'FeatureCollection',feature:featureArray}, options);
});

Related

xPages REST Service Results into Combobox or Typeahead Text Field

I've read all the documentation I can find and watched all the videos I can find and don't understand how to do this. I have set up an xPages REST Service and it works well. Now I want to place the results of the service into either a combobox or typeahead text field. Ideally I would like to know how to do it for both types of fields.
I have an application which has a view containing a list of countries, another view containing a list of states, and another containing a list of cities. I would like the first field to only display the countries field from the list of data it returns in the XPages REST Service. Then, depending upon which country was selected, I would like the states for that country to be listed in another field for selection, etc.
I can see code for calling the REST Service results from a button, or from a dojo grid, but I cannot find how to call it to populate either of the types of fields identified above.
Where would I call the Service for the field? I had thought it would go in the Data area, but perhaps I've just not found the right syntax to use.
November 6, 2017:
I have been following your suggestion, but am still lost as can be. Here's what I currently have in my code:
x$( "#{id:ApplCountry}" ).select2({
placeholder: "select a country",
minimumInputLength: 2,
allowClear : true,
multiple: false,
ajax: {
dataType: 'text/plain',
url: "./Application.xsp/gridData",
quietMillis: 250,
data: function (params) {
return {
search:'[name=]*'+params.term+'*',
page: params.page
};
},
processResults: function (data, page) {
var data = $.map(data, function (obj) {
obj.id = obj.id || obj["#entityid"];
obj.text = obj.text || obj.name;
return obj;
});
},
return {results: data};
}
}
});
I'm using the dataType of 'text/plain' because that was what I understood I should use when gathering data from a domino application. I have tried changing this to json but it makes no difference.
I'm using processResults because I understand this is what should be used in version 4 of select2.
I don't understand the whole use of the hidden field, so I've stayed away from that.
No matter what I do, although my REST service works if I put it directly in the url, I cannot get any data to display in the field. All I want to display in the field is the country code of the document, which is in the field named "name" (not my choice, it's how it came before I imported the data from MySQL.
I have read documentation and watched videos, but still don't really understand how everything fits together. That was my problem with the REST service. If you use it in Dojo, you just put the name of the service in a field on the Dojo element and it's done, so I don't understand why all the additional coding for another type of domino element. Shouldn't it work the same way?
I should point out that at some points it does display the default message, so it does find the field. Just doesn't display the country selections.
I think the issue may be that you are not returning SelectItems to your select2, and that is what it is expecting. When I do something like you are trying, I actually use a bean to generate the selection choices. You may want to try that or I'm putting in the working part of my bean below.
The Utils.getItemValueAsString is a method I use to return either the string value of a field, or if it is not on the document/empty/null an empty string. I took out an if that doesn't relate to this, so there my be a mismatch, but I hope not.
You might be able to jump directly to populating the arrayList, but as I recall I needed to leverage the LinkedHashMap for something.
You should be able to do the same using SSJS, but since that renders to Java before executing, I find this more efficient.
For label/value pairs:
LinkedHashMap lhmap = new LinkedHashMap();
Document doc = null;
Document tmpDoc = null;
allObjects.addElement(doc);
if (dc.getCount() > 0) {
doc = dc.getFirstDocument();
while (doc != null) {
lhmap.put(Utils.getItemValueAsString(doc, LabelField, true), Utils.getItemValueAsString(doc, ValueField, true));
}
tmpDoc = dc.getNextDocument(doc);
doc.recycle();
doc = tmpDoc;
}
}
List<SelectItem> options = new ArrayList<SelectItem>();
Set set = lhmap.entrySet();
Iterator hsItr = set.iterator();
while (hsItr.hasNext()) {
Map.Entry me = (Map.Entry) hsItr.next();
// System.out.println("after: " + hStr);
SelectItem option = new SelectItem();
option.setLabel(me.getKey() + "");
option.setValue(me.getValue() + "");
options.add(option);
}
System.out.println("About to return from generating");
return options;
}
I ended up using straight up SSJS. Worked like a charm - very simple.

Updating Embedded Charts in Google Docs with Google Apps Script

TLDR; How do I use the Script Editor in Docs to update an embedded Sheets chart in the document?
I know there is a script that does this for Google Slides, but I'm trying to do it in Google Docs and can't find any documentation thereof.
https://developers.google.com/slides/how-tos/add-chart#refreshing_a_chart
To be specific, I have a Google Doc. This doc contains about thirty tables and embedded charts that are all linked to a separate Google Sheet. All thirty come from a single Google Sheet. Now, I can have our non-geeky people click on all thirty "Update" hover buttons every time the spreadsheet changes, but I expect the spreadsheet to change a lot, and I would like to idiot-proof the document to ensure it's always up-to-date. As far as I can tell, this isn't a feature Google Apps does out of the box, so I wanted to write a script to do it.
But I can't find any way to access an EmbeddedChart from a Google Doc.
If I could run something like this like you can in Sheets, I could probably figure it out, but I can't:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var charts = sheet.getCharts();
for (var i in charts) {
var chart = charts[i];
// Update the chart
}
}
Although docs has the following function, DocumentApp.getActiveDocument(), an object Document doesn't contain a function getCharts(). I believe they're considered to be Images, but Images don't have an update function.
Is it even possible to access/update an EmbeddedChart in Docs using a script? Maybe by running the script through the spreadsheet on edit and updating the doc from there? Seems weird that you can do it in Slides of all things, but not Docs.
in Google Docs DOM charts as InlineImages here is the script that logs images attributes. but in they looks to be read only and documentation for this method ends with dead end: "null if the element contains multiple values for this attribute." https://developers.google.com/apps-script/reference/document/inline-image#getLinkUrl()
function myFunction() {
var doc = DocumentApp.getActiveDocument()
var body = doc.getBody()
var pars = body.getParagraphs()
var atts = img.getAttributes();
// Log the paragraph attributes.
for (var att in atts) {
Logger.log(att + ":" + atts[att]);
}
for (var par in pars) {
Logger.log(pars[par].getText());
var chN = pars[par].getNumChildren()
Logger.log(chN);
if(chN>0){
var type = pars[par].getChild(0).getType()
Logger.log(type);
if(type=="INLINE_IMAGE"){
var atts = pars[par].getChild(0).getAttributes()
Logger.log(JSON.stringify(atts));
for (var att in atts) {
Logger.log(att + ":" + atts[att]);
}
}
}
}
return
}
The goal is to update the ranges of each chart to encompass the number of columns and rows of the sheet referenced by the range.
The approach is to enumerate all ranges within all charts within all sheets, clear the range and re-add it with the right size. The getDataRange() function is a convenient way to get the right-sized range. The only tricky part is that you don't update the chart directly -- you need to get its builder, modify that, rebuild it, and then update the chart with the built object.
for (const sheet of SpreadsheetApp.getActive().getSheets()) {
for (const chart of sheet.getCharts()) {
const embeddedChartBuilder = chart.modify();
const ranges = chart.getRanges();
embeddedChartBuilder.clearRanges();
for (const range of ranges) {
const dataRange = range.getSheet().getDataRange();
embeddedChartBuilder.addRange(dataRange);
}
const embeddedChart = embeddedChartBuilder.build();
sheet.updateChart(embeddedChart);
}
}
The code sample assumes that your ranges are intended to encompass all the columns and rows of the referenced sheet.

nvd3 stacked area chart looks glitchy how to fix?

My stacked area chart looks like this:
The data I used has the same number of values and is just like in the example. THe data I used is at : http://pastebin.com/D07hja76
The code I use is also almost similar appart from the selector:
var colors = d3.scale.category20();
keyColor = function(d, i) {return colors(d.key)};
nv.addGraph(function() {
chart = nv.models.stackedAreaChart()
.useInteractiveGuideline(true)
.x(function(d) { return d.t })
.y(function(d) { return d.v })
.color(keyColor)
.transitionDuration(300)
chart.xAxis
.tickFormat(function(d) { return d3.time.format('%x')(new Date(d)) });
chart.yAxis
.tickFormat(d3.format(',.0f'));
d3.select('#browserBreakdown')
.datum(browserchartdata)
.transition().duration(500)
.call(chart)
.each('start', function() {
setTimeout(function() {
d3.selectAll('#browserBreakdown *').each(function() {
if(this.__transition__)
this.__transition__.duration = 1;
})
}, 0)
})
nv.utils.windowResize(chart.update);
return chart;
});
How can I get the chart to look right?
The NVD3 chart doesn't sort your data points into a left-to-right order along your x axis, so you're getting the strange criss-crossing shape.
I assume there is some way to tell NVD3 to sort the data, but they have next to no documentation and I couldn't figure it out quickly. Instead, you can use this function to sort the data before you add it to the chart:
data.forEach(function(d,i){
d.values = d.values.sort(
function(a,b){
return +a.t -b.t;
}
);
});
How this works:
data is the array of objects from the JSON file (you would use browserchartdata);
the Javascript Array.forEach(function(){}) method calls the passed-in function for each element of the array, and passes that function the element of the array and its index;
the Javascript Array.sort() method creates a sorted version of an array using the passed-in function to determine how two elements (a and b) compare;
the sort function I created uses the .t variable (which you're using for the x-axis) from each element in your array to determine whether a is bigger than b (and therefore should go after it in the sorted array);
I call this sort function on the values array of each data line, and then write-over the unsorted values array, so that the objects in data all end up with their values sorted from smallest to largest according to t.
I tried it with your data on NVD3's "live code" site, and it looks fine.

concatenating jQuery objects

Below is the prototype of what I am trying to do.
var entry_set = $;
// start to loop data
for([])
{
// create HTML element to represent that data
$('<div></div>')
.data([])
.addClass([])
.on([])
.insertAfter(entry_set);
}
// modify DOM only once all the entries are consolidated to a single jQuery object
entry_set.appendTo('.entries');
The comments say it all. In short - the idea is to modify document DOM only once when inserting data. I would usually go HTML string approach (simply concatenating the same structure using a string), but I am interested whether anything similar to this might work as well.
You could create an empty DOM element and .append() to that
var entry_set = $("<div>"); //empty dom element
// start to loop data
var i = 4;
while(i--) {
// create HTML element to represent that data
var item = $('<div>', {
text: "test " + i
});
entry_set.append(item);
}
// modify DOM only once all the entries are consolidated to a single jQuery object
$("body").append(entry_set.children());​
working demo at: http://jsfiddle.net/F2J6g/1/
EDIT
You can also start with an empty collection and use .add()
var entry_set = $(); //empty collection
// start to loop data
var i = 4;
while(i--) {
// create HTML element to represent that data
var item = $('<div>', {
text: "test " + i
});
entry_set = entry_set.add(item);
}
// modify DOM only once all the entries are consolidated to a single jQuery object
$("body").append(entry_set);
http://jsfiddle.net/F2J6g/2/
#Guy, I don't think you will get the desired HTML output. try using "wrap".
Sample Syntax:
$('.OuterDiv').wrap('<div class="abc" />');
Unfortunately, because those objects aren't actually attached to any heirarchy, calling insertAfter doesn't actually mean anything. What you'll need to do is put them inside a containing div, maybe something like this:
var entry_set = $('<div>');
// start to loop data
for([])
{
// create HTML element to represent that data
$('<div></div>')
.data([])
.addClass([])
.on([])
.appendTo(entry_set);
}
// modify DOM only once all the entries are consolidated to a single jQuery object
entry_set.appendTo('.entries');
I haven't tested that, but I think it should work.

Flex/Air file dragging: how to restrict filetypes?

I'm using a <S:nativeDragDrop> and getting files dragged over a component like so:
var arr:Array = event.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;
I'm not sure how to restrict what type of files can be dragged. Is there a native control for this? The help documents mention the possibility of defining completely different ClipboardFormats, but I have no idea how to do that; I could run regex on the filenames as well, but that seems overcomplicated.
Wondering if there's a way like with FileReference.browse to specify specific file extensions
As far as I know, there is not a built-in way to filter dropped files. However, in your NATIVE_DRAG_ENTER handler, you could loop through the list of files and choose not accept the drag based on their file types. Or, you could merely ignore the unsupported types when you are processing the NATIVE_DRAG_DROP.
var validTypes:Object = {png : true, jpg : true, gif : true};
function nativeDragEnter(event:NativeDragEvent):void {
var files:Array = event.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array
for each(var file:File in files) {
if(!validTypes[file.extension.toLowerCase()]) // Don't accept drag if any of the dropped files aren't supported.
return;
}
DragManager.acceptDrag(InteractiveObject(event.target));
}
function nativeDragDrop(event:NativeDragEvent):void {
var files:Array = event.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array
for each(var file:File in files) {
if(validTypes[file.extension]) //accept only certain files
processFile(file);
}
}
As a side note, I assumed you are working on an AIR app here, but if you aren't, you'll have to use the FileReference class instead of File.