Extracting Extended Data from KML using Javascript - kml

I have a placemark in a KML that looks something like this
<Placemark>
<id>test345</id>
<name>Images from KML file</name>
<ExtendedData>
<Data name="type">
<value>images</value>
</Data>
</ExtendedData>
<Point>
<coordinates>-122.448425,37.802907,0</coordinates>
</Point>
I'm attempting to extract the ExtendedData information out of this placemarker on a click event:
google.earth.addEventListener(kmlObject, 'click', function(event) {
event.preventDefault();
var kmlPlacemark = event.getTarget();
});
An alternative solution would be to get the kmlObject from the kmlPlacemarker, any ideas?

Given the placemark the Google Earth API provides two methods to access the ExtendedData element.
getBalloonHtml()
getBalloonHtmlUnsafe()
API Reference:
https://developers.google.com/earth/documentation/reference/interface_kml_feature
You can find a working example in the Google Code Playground here:
https://code.google.com/apis/ajax/playground/?exp=earth#extended_data_in_balloons
If you wanted to get the raw KML for extended data then you could fetch the KML representation and parse it as an XML document.
var output = placemark.getKml();

Just to say I posted about just this issue on the support forum for the plug-in: https://code.google.com/p/earth-api-samples/issues/detail?id=16
Here is a method I cobbled together to provide support for getExtendedData. It takes a string of Kml as the argument via 'feature.getKml();` It returns any extended data elements that have values in a key[value] object. It expects the extended data to be in the format:
<Data name="Foo">
<value>bar</value>
</Data>
Tested in XP - FF3.0, IE7, Chrome
function getExtendedData(kmlString) {
var xmlDoc = null;
var keyValue = [];
//Parse the kml
try {
//Internet Explorer
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async="false";
xmlDoc.loadXML(kmlString);
} catch(e) {
try {
//Firefox, etc.
var parser = new DOMParser();
xmlDoc = parser.parseFromString(kmlString,"text/xml");
}
catch(e) {
//Failed to parse
alert(e.message);
return;
}
}
// Get all the named elements
var data = xmlDoc.getElementsByTagName("Data");
// Iterate through the data elements
for(var i=0; i<data.length; i++) {
if(data[i].getAttribute("name") &&
data[i].getElementsByTagName("value").length > 0) {
// Get the name and value
var name = data[i].getAttribute("name");
var value = data[i].getElementsByTagName("value")[0].firstChild.data;
// Assign them to the keyValue object
keyValue[name] = value;
}
}
return keyValue;
}
Usage
// where 'feature' is the object with the extended data
var data = getExtendedData(feature.getKml());
for (var name in data) {
var value = data[name];
alert(name + '=' + value); // e.g. type=images
}

It is actually possible to access the ExtendedData elements via the DOM APIs, although they're not particularly well-documented anywhere.
I found them while grepping around inside some of the resource (.rcc) files packaged with the Plugin.
Assuming a simple Placemark sample similar to yours:
<Placemark id="testmark">
<!-- other stuff... -->
<ExtendedData>
<Data name="someDataUrl">
<displayName>URL Representing some Data</displayName>
<value>http://example.com/#hello</value>
</Data>
</ExtendedData>
</Placemark>
Then (once it's fetched/parsed/loaded into Earth, you can access it something like:
var mark = ge.getElementById('testmark');
var extDataObj = mark.getExtendedData();
var extDataOut = Array(extDataObj.getDataCount());
for (var i = 0; i < extDataObj.getDataCount(); i++) {
var item = extDataObj.getData(i);
var details = { name: item.getName(),
displayName: item.getDisplayName(),
value: item.getValue()
};
extDataOut[i] = details;
}
console.dir(extDataOut);
Haven't tested it for performance vs the .getKml() and feed to an external parser approach, and the lack of official documentation might mean it's not fully functional or supported, but in all testing so far it seems to do ok. I haven't yet found a way to access any of the more complicated SchemaData type structures, only the simple <data name=''><value>... form.

Related

JSON to XML conversion in Node JS service (using xml2js)

I need some help/advice with JSON to XML conversion in Node js.
I have a service that gets a JSON object in request body that needs to convert to XML. I am able to achieve this using node-xml2js for json inputs with maximum one level of nested objects. But, it gets way more complicated with nested objects having attribute values. Attributes should be identified first, prefixed with $ sign and enclosed in curly braces before parsing through xml2js to get correct xml.
Is there a better way of doing this where this complicated layer of reformatting the json input can be simplified?
xml2js can converts this:
{
"Level1":{ "$":{ "attribute":"value" },
"Level2": {"$":{"attribute1":"05/29/2020",
"attribute2":"10","attribute3":"Pizza"}}
}
to this:(which is correct):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Level1 attribute="value">
<Level2 attribute1="05/29/2020" attribute2="10" attribute3="Pizza"/>
</Level1>
But actual json input is this:
{
"Level1":{"attribute":"value",
"Level2": {"attribute1":"05/29/2020",
"attribute2":"10","attribute3":"Pizza"} }
}
Expected same result:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Level1 attribute="value">
<Level2 attribute1="05/29/2020" attribute2="10" attribute3="Pizza"/>
</Level1>
Please let me know if you have worked on similar requirements. Appreciate any help.
Thank you.
This would be a way to change the object back to the format expected in the library, although it assumes that all non object keys are supposed to be attributes (is that a valid assumption for your application?)
function groupChildren(obj) {
for(prop in obj) { // consider filtering for own properties (vs from prototype: for(prop of Object.keys(obj)) {
if (typeof obj[prop] === 'object') {
groupChildren(obj[prop]);
} else {
obj['$'] = obj['$'] || {};
obj['$'][prop] = obj[prop];
delete obj[prop];
}
}
return obj;
}
and then used like so:
var xml2js = require('xml2js');
var obj = {
Level1: {
attribute: 'value',
Level2: {
attribute1: '05/29/2020',
attribute2: '10',
attribute3: 'Pizza'
}
}
};
var builder = new xml2js.Builder();
var xml = builder.buildObject(groupChildren(obj));
which prints out:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Level1 attribute="value">
<Level2 attribute1="05/29/2020" attribute2="10" attribute3="Pizza"/>
</Level1>
you can use this library :nashwaan/xml-js
Like This:
let xmlJ=require('xml-js');
let parseToJson=(xml)=>{
return new Promise(resolve => {
let convert;
convert=xmlJ.xml2json(xml,{compact:true});
resolve(convert);
});
};

How to manipulate a xml file and save it to disk?

I have two xml files with exactly same structure. The only difference is the inner text of the tags.
I want to replace the value in the first file with the corresponding value in the second file.
I have tried using the xml2json but the problem is it removed all the comments which I need in the final output.
So currently I am using xmldom.
I am able to manipulate the text but the changes are lost when I try to save the file to the disk.
var DOMParser = require("xmldom").DOMParser;
var serializer = new (require('xmldom')).XMLSerializer;
var fs = require('fs');
let firstXML = `<root>
<!--This is just a comment-->
<string name="one">SOMETHING</string>
</root>`
let secondXML = `<root>
<string name="one">ELSE</string>
</root>`
var firstDoc = new DOMParser().parseFromString(firstXML, "text/xml");
var secondDoc = new DOMParser().parseFromString(secondXML, "text/xml");
let elementsFirst = firstDoc.getElementsByTagName("string");
let elementsSecond = secondDoc.getElementsByTagName("string");
for(let i = 0; i < elementsFirst.length; ++i) {
let el = elementsFirst[i];
let name = el.getAttribute("name");
for(let j = 0; j < elementsSecond.length; ++j) {
if(name = elementsSecond[j].getAttribute("name")) {
el.firstChild.nodeValue = elementsSecond[j].firstChild.nodeValue;
break;
}
}
}
fs.writeFileSync("output.xml", serializer.serializeToString(firstDocs));
//Required output
`<root>
<!--This is just a comment-->
<string name="one">ELSE</string>
</root>`
Please don't ask me why, but if you replace this line
el.firstChild.nodeValue = elementsSecond[j].firstChild.nodeValue;
with this one
el.firstChild.data = elementsSecond[j].firstChild.nodeValue;
it will work as expected.
P.S. You have a typo in your if statement, you need ===, a single = will usually return true
Update: after finding the solution - I found out that it is duplicate of this one

Multiple REST service controls on XPage

I need to provide a number of web services to supply data from a Notes application to an external website.
I have created an XAgent with multiple custom REST service controls. Each control has its own "pathInfo" property and code defined in "doGet" to return a JSON object containing the relevant data required by the website.
This all works well and the correct data is returned by calls to each web service. However, having added some debug messages to each "doGet" I see that all services on the XPage are being triggered by a call to any one of them.
I have a couple of additional REST service controls on the same XPage with code defined in "doPost", used by the website to create records in the Notes database. This also works well with the desired results, but the debug messages show that when a call to one of these services is made all of the "get" services are also triggered.
Any idea what is going on here? I could create a separate XAgent for each REST service control, but it seems overkill if it's not necessary.
UPDATE
Here is a cut down version of the XPage with just two of the "get" services. It doesn't matter which of these services is called, the log messages show that getHospitals is triggered first followed by getCustomerTypes. However, the correct resultset is always returned.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xe="http://www.ibm.com/xsp/coreex"
rendered="false">
<xp:this.beforePageLoad><![CDATA[#{javascript:var vwSettings:NotesView = database.getView("vwSettings");
var docSettings:NotesDocument = vwSettings.getFirstDocument();
sessionScope.put("App_DbPath", docSettings.getItemValueString("App_DbPath"));
sessionScope.put("WR_DbPath", docSettings.getItemValueString("WR_DbPath"));
sessionScope.put("logActivity", true);}]]></xp:this.beforePageLoad>
<xp:this.resources>
<xp:script src="/Utils.jss" clientSide="false"></xp:script>
<xp:script src="/OpenLogXPages.jss" clientSide="false"></xp:script>
</xp:this.resources>
<xe:restService id="restService1" pathInfo="getHospitals">
<xe:this.service>
<xe:customRestService contentType="application/json"
requestContentType="application/json" requestVar="hospital">
<xe:this.doGet><![CDATA[${javascript:try {
if (sessionScope.logActivity == true) {
log.logEvent("getHospitals");
}
var dbApp:NotesDatabase = sessionAsSigner.getDatabase(database.getServer(), sessionScope.App_DbPath);
var vwHospitals:NotesView = dbApp.getView("vhospitals.by.name");
var vecHospitals:NotesViewEntryCollection = vwHospitals.getAllEntries();
var eHospital:NotesViewEntry = vecHospitals.getFirstEntry()
var arrHospitals = new Array();
while (eHospital != null) {
var hospital = {};
hospital["Name"] = eHospital.getColumnValues()[0];
hospital["HCode"] = eHospital.getColumnValues()[1];
arrHospitals.push(hospital);
eHospital = vecHospitals.getNextEntry(eHospital);
}
log.logEvent("getHospitals - END");
return toJson(arrHospitals);
} catch(e) {
log.logError(e.toString());
}}]]></xe:this.doGet>
</xe:customRestService>
</xe:this.service>
</xe:restService>
<xe:restService id="restService2" pathInfo="getCustomerTypes">
<xe:this.service>
<xe:customRestService contentType="application/json"
requestContentType="application/json">
<xe:this.doGet><![CDATA[${javascript:try {
if (sessionScope.logActivity == true) {
log.logEvent("getCustomerTypes");
}
var dbApp:NotesDatabase = sessionAsSigner.getDatabase(database.getServer(), sessionScope.App_DbPath);
var vw:NotesView = dbApp.getView("vkeywords");
var vec:NotesViewEntryCollection = vw.getAllEntriesByKey("Customer Type");
var e:NotesViewEntry = vec.getFirstEntry()
var arrItems = new Array();
while (e != null) {
var item = {};
item["CustomerType"] = e.getColumnValues()[1];
arrItems.push(item);
e = vec.getNextEntry(e);
}
log.logEvent("getCustomerTypes - END");
return toJson(arrItems);
} catch(e) {
log.logError(e.toString());
}}]]></xe:this.doGet>
</xe:customRestService>
</xe:this.service>
</xe:restService>
And the services are called using
https://<<domain name>>/<<path name>>/ws.nsf/test.xsp/getHospitals
and
https://<<domain name>>/<<path name>>/ws.nsf/test.xsp/getCustomerTypes
The problem was that the "doGet" properties were set to "compute on page load" rather than "compute dynamically". Not sure how that crept in - probably a copy and paste error.

XPages - Is it possible to make view column header fixed?

Just checking to see if there is a very simple way to make the view header fixed so that as you page down in the view in XPages, the header stays where it is. position=Fixed is not a property of xp:viewColumnHeader.
If you want to add the attribute of position to xp:viewColumnHeader you can use the attrs property to do that (works on 8.5.3). You code would look something like this:
<xp:viewColumnHeader ......>
<xp:this.attrs>
<xp:attr name="position" value="fixed"></xp:attr>
</xp:this.attrs>
</xp:viewColumnHeader>
But I don't think that alone would do the trick. Some time back I created a CSS snippet to make floating Banner, Title Bar and Place Bar in Application Layout control of Extension Library. You can get some ideas from that.
yes, it is possible, but requires some JavaScript coding.
I solved it for a customer recently using with the following code. The basic idea is to geht the width of the columns out of the first line of TDs, then apply this with to the THs ad set the THs to fixed afterwards.
You need to run this function after a partial update, too. Good luck.
var fixTableHeaders = function() {
var thead = dojo.query("thead")[0];
if (!thead) return;
thead.style.position = "static";
var THs = dojo.query('.xspDataTable th');
var firstTDs = dojo.query('.xspDataTable tr:first-child td');
var secondTDs = null;
if (firstTDs.length < 2) {
// categorized view, first line is a category with only one cell
// -> we need the second line
secondTDs = dojo.query('.xspDataTable tr:nth-child(2) td');
}
var w = 0;
for (var i = 0; i < THs.length; i++) {
w = dojo.coords(THs[i], true).w;
// console.log(i+" w="+w);
THs[i].style.width = (w)+"px";
if (firstTDs[i]) {
//if (secondTDs && secondTDs[i]) secondTDs[i].style.width = w+"px";
//else firstTDs[i].style.width = w+"px";
firstTDs[i].style.paddingTop = "3em";
}
}
thead.style.position = "fixed";
}
dojo.addOnLoad(fixTableHeaders);
I saw some jQuery code the other day that could make a Table Header fixed. Don't remember where it was but something that can help you should be out there.

Showing a album cover

I was wondering how to add a album cover on my spotify-app?
With this code I can reveal the album title, but how can I show the album cover!?
function updatePageWithAlbumName() {
var header = document.getElementById("album");
// This will be null if nothing is playing.
var playerTrackInfo = sp.trackPlayer.getNowPlayingTrack();
if (playerTrackInfo == null) {
header.innerText = "Geen album!";
} else {
var track = playerTrackInfo.track;
header.innerText = track.album.name;
}
}
Please don't use any of the sp. APIs - they're private and going away soon.
The public API is documented here: http://developer.spotify.com/download/spotify-apps-api/preview/reference/
You can show an album cover like this:
<img src="[cover URI]">
Edit: To get the cover URI of the current track:
var sp = getSpotifyApi(1);
var models = sp.require('sp://import/scripts/api/models');
var currentTrackCoverUri = models.player.track.album.cover;
I would like to point out that the property seems to be called "image" and not "cover".
So the snippet provided by iKenndac would be rewritten as
var sp = getSpotifyApi(1);
var models = sp.require('sp://import/scripts/api/models');
var currentTrackCoverUri = models.player.track.album.image;
Not sure if this is dependant on my Spotify version, I'm running 0.8.10.3.g07d01e81.
The UX guidelines requires you to present an album cover with playability when appropriate. This can be done with the solution from another topic: Views.player album cover

Resources