On Google Earth, can I vary the altitude on a lineString/LinearRing programmatically? - google-earth

I am using the Google Earth plugin on an HTML page. In this context, say you have a line string or polygon like this
// Create the placemark
var lineStringPlacemark = ge.createPlacemark('');
// Create the LineString
var lineString = ge.createLineString('');
lineStringPlacemark.setGeometry(lineString);
// Add LineString points
lineString.getCoordinates().pushLatLngAlt(48.754, -121.835, 0);
lineString.getCoordinates().pushLatLngAlt(48.764, -121.828, 0);
// Add the feature to Earth
ge.getFeatures().appendChild(lineStringPlacemark);
I got the sample from https://developers.google.com/earth/documentation/geometries
Now, say you would like to vary the altitude (height) programmatically, after you append the lineString, how would you do it?
I saw you can retrieve the features through ge.getFeatures(). However, the returned object can not be inspected and I am struggling with the syntax to change the altitude.
I could remove the whole object and redraw it but that is hacky and the user can see the redraw. This is the code to remove
var features = ge.getFeatures();
while (features.getFirstChild())
features.removeChild(features.getFirstChild());
I got the code from https://developers.google.com/earth/documentation/containers
Does someone know the right syntax?

If you have a reference to the LineString (you can hold on to it, or walk the KML DOM and get it again), you can change the altitude of the entire LineString via
lineString.setAltitudeOffset(offsetFromCurrentAltitude);
If you want to change the altitude on a per coordinate basis, you can access them basically as you constructed it above. lineString.getCoordinates() returns the KmlCoordArray, and then you can read values from individual coordinates from there. One kind of awkward thing about KmlCoordArray is that it returns copies of its KmlCoord children, not its children directly. So you can do lineString.getCoordinates().get(0) and then read the lat/lng/alt values from the KmlCoord it returns, but if you set those values on that coordinate, it won't automatically be reflected in the LineString. Instead, you have to readd that KmlCoord to the KmlCoordArray. It's somewhat awkward, but useable.
So you might do something like this, if you're usually only altering one altitude at a time:
function setNewAltitude(lineString, coordIndex, altitude) {
var coords = lineString.getCoordinates();
if (coordIndex >= 0 && coordIndex < coords.getLength()) {
var coord = coords.get(coordIndex);
coord.setAltitude(altitude);
coords.set(coordIndex, coord);
}
}
Check out the KmlCoordArray reference page for its other methods to see if they would be more helpful for the exact use case you have in mind.

I found the answer. My insight was requesting the type as I navigated through the objects. See below
// read the number of features in GE
var length = ge.getFeatures().getChildNodes().getLength();
// get the first feature
var feature = ge.getFeatures().getFirstChild();
// for debugging get type - expecting KmlPlacemark
var featureType = feature.getType();
console.log(featureType);
// get KmlPlacemark geometry
var geometry = feature.getGeometry();
// for debugging get type - expecting KmlLineString
var geometryType = geometry.getType();
console.log(geometryType);
// get KmlLineString coordinates
var coordinates = geometry.getCoordinates();
// for debugging get type - expecting KmlCoordArray
var coordinatesType = coordinates.getType();
console.log(coordinatesType);
var altitude = Math.random()*10000;
var coordinatesLength = coordinates.getLength();
for(var i=0; i< coordinatesLength; i++){
var coordinate = coordinates.get(i);
console.log(coordinate.getType());
coordinate.setAltitude(altitude);
coordinates.set(i,coordinate)
}
for(var i=0; i< coordinatesLength; i++){
var coordinate = coordinates.get(i);
console.log(coordinate.getAltitude());
}

Related

vscode API: get Position of last character of line

Following up on this still unanswered question regarding VS Code Extensions with the VS Code API. I didn't answer it because it specifically asked for a solution using the with method of the Position object. I couldn't make that work, nor was I able to loop through the object to get the last character. Trying to manipulate the selection with vscode.commands.executeCommand didn't work either, because vscode.window.activeTextEditor doesn't appear to reflect the actual selection in the window as soon as the Execution Development Host starts running. The only solution I could find was the hoop-jumping exercise below, which gets the first character of one line and the first character of the next line, sets a Range, gets the text of that Range, then reduces the length of that text string by 1 to get the last character of the previous line.
function getCursorPosition() {
const position = editor.selection.active;
curPos = selection.start;
return curPos;
}
curPos = getCursorPosition();
var curLineStart = new vscode.Position(curPos.line, 0);
var nextLineStart = new vscode.Position(curPos.line + 1, 0);
var rangeWithFirstCharOfNextLine = new vscode.Range( curLineStart, nextLineStart);
var contentWithFirstCharOfNextLine = editor.document.getText(rangeWithFirstCharOfNextLine);
var firstLineLength = contentWithFirstCharOfNextLine.length - 1;
var curLinePos = new vscode.Position(curPos.line, firstLineLength);
var curLineEndPos = curLinePos.character;
console.log('curLineEndPos :>> ' + curLineEndPos);
I'm obviously missing something - it can't be impossible to get the last character of a line using the VSCode API without mickey-mousing the thing like this. So the question is simply, what is the right way to do this?
Once you have the cursor Position the TextDocument.lineAt() function returns a TextLine. From which you can get its range and that range.end.character will give you the character number of the last character. - not including the linebreak which if you want to include that see TextLine.rangeIncludingLineBreak().
const editor = vscode.window.activeTextEditor;
const document = editor.document;
const cursorPos = editor.selection.active;
document.lineAt(cursorPos).range.end.character;
Note (from [TextDocument.lineAt documentation][1] ):
Returns a text line denoted by the position. Note that the returned
object is not live and changes to the document are not reflected.

How to dynamically update MGLPointFeature attribute value of MGLShapeSource for Mapbox on iOS?

My application stores data, which includes coordinates and other info, in a local database. Due to the number of data points, the application uses clustering to display the data with Mapbox on iOS. Some of the marker styling is based on data, which can change on the run. Map setup is:
// fetch data from DB
let dataArray: [MyData] = fetchData()
// build features from data array
var features = [MGLPointFeature]()
dataArray.forEach({ (data) in
let feature = MGLPointFeature()
feature.identifier = data.id
feature.coordinate = CLLocationCoordinate2D(latitude: data.lat, longitude: data.lng)
// our attributes
feature.attributes = [
"amount": data.amount
"marked": false
]
features.append(feature)
})
// make and add source
let source = MGLShapeSource(identifier: "MySourceId", features: features, options: [
.clustered: true
])
style.addSource(source)
// regular marker layer
let layer = MGLSymbolStyleLayer(identifier: "unclustered", source: source)
layer.iconImageName = NSExpression(forConstantValue: "MyIcon")
layer.text = NSExpression(forKeyPath: "amount")
layer.iconScale = NSExpression(forMGLConditional: NSPredicate(format: "%# == true", NSExpression(forKeyPath: "marked")), trueExpression: NSExpression(forConstantValue: 2.0), falseExpression: NSExpression(forConstantValue: 1.0))
layer.predicate = NSPredicate(format: "cluster != YES")
style.addLayer(layer)
// point_count layers
...
The above code is simplified to help illustrate the concept more clearly. Array of MGLPoint is used because data is stored in DB, so we don't have a GeoJSON file or a URL. MGLShapeSource is used because clustering is required, and that's what I found in the examples. MGLShapeSource constructor taking "features" as a parameter is used because that's the one that matches the data I have. The regular marker layer is setup to show different size icons based on the value of "marked" attribute in iconScale.
During runtime, the value of "marked" attribute can change (for example, when a marker is tapped), and the corresponding icon's size needs to be updated to reflect the change in "marker" value. However, I am unable to figure out how to change the marker attribute value. MGPShapeSource only shows access to shape and URL, neither of which is the features array I initialized the source with. I need to access the features array the source was constructed with, change the marker value, and have the marker icons updated.
I have thought about remaking the source on each data change. But with the number of markers involved, this would perform poorly. Plus I believe I would also need to remake all of the style layers, as they're constructed with the actual source object, which would make this perform even worse.
I need help figuring out how to change the attribute value of MGLPointFeature within MGLShapeSource during runtime and have the map updated.
I did not find a solution close to what I was hoping for, but I did find something that's better than remaking everything every time.
On feature attribute value change, instead of remaking everything, including the source and all the layers, I update the source's shape with the MGLPointFeature array. I do have to remake the MGLPointFeature array, but then I can make a MGLShapeCollectionFeature with this array and set it as existing source's shape. This way, the existing layers don't need to be changed, either. Something like:
// build features from data array
...same as in original question, but with updated feature.attributes values as needed.
// look for existing source and update it if found; otherwise, make a new source
if let existingSource = style.source(withIdentifier: "MySourceId") {
// update data
guard let shapeSource = existingSource as? MGLShapeSource else {
throw <some_error>
}
shapeSource.shape = MGLShapeCollectionFeature(shapes: features)
} else {
// make new (same as original post)
let source = MGLShapeSource(identifier: "MySourceId", features: features, options: [.clustered: true])
style.addSource(source)
}

SAPUI5 List Item context binding, get the next path

I have a table of list items (questions) and I want to be able to re-arrange them. See screenshot.
Currently, on the button down press, I can get the current binding context and I am getting that sequence property (001). What I want to be able to do is also be able to get the path of the next list items binding context (002 in this case).
Current code...
// Move Question Down
onQuestionMoveDown: function (oEvent) {
// Get binding context
var source = oEvent.getSource().getBindingContext("view");
var path = source.getPath();
var object = source.getModel().getProperty(path);
var currentQuestionSequence = object.Sequence;
MessageToast.show("Current # " + currentQuestionSequence);
}
Then once I have that I can sort my updates logic.
A possible solution could be that you have an order value on your model and you update that order when the user clicks on the button.
If the list is sorted by that value you will achieve what you're looking for.
The items should be bound to the table via list binding, and so the data set would be an Array, the path for each line will be like ".../itemSet/0, .../itemSet/1, ...". So possible solution could be:
function getNextItem(oItem){
var oContext = oItem.getBindingContext("view"), // assumpe the model name is view
sPath = oContext.getPath(),
sSetPath = sPath.substr(0, sPath.lastIndexOf("/")),
iMaxLen = oContext.getProperty(sSetPath).length,
iCurIndex = parseInt(sPath.substr(sPath.lastIndexOf("/")+1));
// If it's already reach to be bottom, return undefined
return iCurIndex < iMaxLen -1 ? oContext.getProperty(sSetPath + "/" + ++iCurIndex) : undefined;
}
Regards,
Marvin

Get all outging connectors from a shape programmatically

I want to rename a connector after a shape has been dropped.
Lets say I have a shape1 and I dropped a shape2 connected with shape1.
I want the connector shape between shape1 and shape2 so that I can rename it.
I guess it depends on what stage you intercept the drop. If it's immediately, you might make some assumptions about how many connectors might be involved, but if if some time after the drop then you might want to determine how many connections are involved.
As an example, with the following shapes:
...you could approach this in a number of ways:
Use the GluedShapes method working back from ShapeTwo
Use the GluedShapes method including the 'from' shape
Iterate through the Connects collection of the Page
Iterate over the Connect objects in on your target shape (ShapeOne)
I would definitely try and use the GluedShapes method (which came into Visio in 2010) over the Connect objects, but I'm adding them here as they can be useful depending on what you're trying to achieve.
Here's an example using LINQPad:
void Main()
{
var vApp = MyExtensions.GetRunningVisio();
var vPag = vApp.ActivePage;
//For demo purposes I'm assuming the following shape IDs
//but in reality you'd get a reference by other methods
//such as Window.Selection, Page index or ID
var shpOne = vPag.Shapes.ItemFromID[1];
var shpTwo = vPag.Shapes.ItemFromID[2];
Array gluedIds;
Console.WriteLine("1) using GluedShapes with the 'to' shape only");
gluedIds = shpTwo.GluedShapes(Visio.VisGluedShapesFlags.visGluedShapesIncoming1D,"");
IterateByIds(vPag, gluedIds);
Console.WriteLine("\n2) using GluedShapes with the 'to' and 'from' shapes");
gluedIds = shpTwo.GluedShapes(Visio.VisGluedShapesFlags.visGluedShapesIncoming1D, "", shpOne);
IterateByIds(vPag, gluedIds);
Console.WriteLine("\n3) using the Connects collection on Page");
var pageConns = from c in vPag.Connects.Cast<Visio.Connect>()
where c.FromSheet.OneD != 0
group c by c.FromSheet into connectPair
where connectPair.Any(p => p.ToSheet.ID == shpOne.ID) && connectPair.Any(p => p.ToSheet.ID == shpTwo.ID)
select connectPair.Key.Text;
pageConns.Dump();
Console.WriteLine("\n4) using FromConnects and Linq to navigate from shpOne to shpTwo finding the connector in the middle");
var shpConns = from c in shpOne.FromConnects.Cast<Visio.Connect>()
where c.FromSheet.OneD != 0
let targetConnector = c.FromSheet
from c2 in targetConnector.Connects.Cast<Visio.Connect>()
where c2.ToSheet.Equals(shpTwo)
select targetConnector.Text;
shpConns.Dump();
}
private void IterateByIds(Visio.Page hostPage, Array shpIds)
{
if (shpIds.Length > 0)
{
for (int i = 0; i < shpIds.Length; i++)
{
//Report on the shape text (or change it as required)
Console.WriteLine(hostPage.Shapes.ItemFromID[(int)shpIds.GetValue(i)].Text);
}
}
}
Running the above will result in this output:
It's worth bearing in mind that the Connects code (3 and 4) makes the assumption that connector shape (1D) are being connected to the flowchart shapes (2D) and not the other way round (which is possible).
You can think of the connect objects as being analgous to connection points, so in the diagram, the three connector shapes generate six connect objects:
Anyway, hope that gets you unstuck.
UPDATE - Just to be clear (and to answer the original question properly), the code to get all outgoing connectors from ShapeOne would be:
Console.WriteLine("using GluedShapes to report outgoing connectors");
gluedIds = shpOne.GluedShapes(Visio.VisGluedShapesFlags.visGluedShapesOutgoing1D, "");
IterateByIds(vPag, gluedIds);

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.