Create arc from MultiPoints Topojson in d3.js - dictionary

I'm new to d3 and have following problem: I want to display positions and trails of all kind of ships. The data is in topojson format: Every ship has an array of positions in form of lat/lon. I want to connect these positions with an arc.
Thats my code displaying the ships at their last position, which works fine (see pic). To get the trails I have a nested for loop which is probably not the best option, although I am able to print out the correct lat/lon. I just have no clue how to proceed from there and draw an arc connecting theses positions.
Any help is appreciated!
d3.json("ships/container-topo.json", function(error, collection) {
//latest position as point (works)
ships.append("path")
.datum(topojson.feature(collection, collection.objects.collection))
.attr("class", "points")
.style("fill", "black")
.style( "opacity", ".9" )
.attr("d", path)
features = collection.objects.collection.geometries
for (var k = 0; k < features.length; k++){
for (var i = 0; i < features[i].coordinates.length0; i++){
cx = features[k].coordinates[i][0];
cy = features[k].coordinates[i][1];
var p_coords = projection( [cx, cy] );
console.log( cx );
//WHAT TO DO HERE?
}
}
});
The data is in following format
{
"type": "Topology",
"objects": {
"collection": {
"type": "GeometryCollection",
"geometries": [{
"type": "MultiPoint",
"coordinates": [
]

Related

Qt3D - Import gltf and play animation

I'm currently struggling to play the animation of a gltf-file with Qt3D/QML.
Model I want to use:
https://sketchfab.com/3d-models/plane-cedc8a07370747f7b0d14400cdf2faf9
Code I have so far:
Entity {
id: root
Transform {
id: planeTransform
scale: 0.1
}
components: [
planeClip,
planeTransform,
planeScene
]
SceneLoader{
id:planeScene
source: "qrc:/Modells/3DModelle/plane/scene.gltf"
}
AnimationClipLoader{
id: planeClipLoader
source: "qrc:/Modells/3DModelle/plane/scene.gltf"
}
ClipAnimator{
id: planeClip
clip: planeClipLoader
channelMapper: ChannelMapper {
mappings: [ ] // Dont know where to get this
}
loops: Animation.Infinite
running: true
}
}
I use the SceneLoader to check if the gltf can be even loaded (it works, I can see the plane).
But I'm struggling to start the animation because I don't know where I get the ChannelMapper from.
When I open the file with 3DViewer on Windows, it plays the Animation correctly, so I suppose it should work without much insider knowledge of the file itself.
Anyone here familiar with playing gltf animations?
Thanks for the help!
To do this you need to traverse the tree of Entity(s) build by SceneLoader and find the appropriate component to initialize ClipAnimator with.
It's going to look something like this:
SceneLoader {
id: planeScene
source: "qrc:/Modells/3DModelle/plane/scene.gltf"
onStatusChanged: {
console.log("SceneLoader status=" + status);
if (status != SceneLoader.Ready)
return;
// retrieve a list of all the Entity(s) in the scene
var entityNames = planeScene.entityNames();
console.log("SceneLoader: entityNames=" + entityNames);
// traverse the Entity tree
for (var i = 0; i < entityNames.length; ++i) {
var entityName = entityNames[i];
var entityVar = planeScene.entity(entityName);
console.log("SceneLoader: entity=" + entityName + " components.length=" + entityVar.components.length);
// iterate on the components of this Entity
for (var j = 0; j < entityVar.components.length; ++j) {
var cmp = entityVar.components[j];
if (!cmp)
continue;
var cmp_class = cmp.toString();
console.log("SceneLoader: -> " + cmp_class);
// compare the type of this component to whatever type
// you are looking for. On this silly example, its QPhongMaterial:
if (cmp_class.indexOf("QPhongMaterial") >= 0) {
// initialize ClipAnimator with this data
}
}
}
}
}
I'm sharing the generic procedure here as I don't have a way to test this right now and figure out the data type you need to initialize ClipAnimator correctly.

AFrame Text change rotation

I'm making a virtual tour using AFrame, with a <a-sky> for the 360° images, some <a-circle> for hotspots, and <a-text> below circles for indications.
My goal is to make texts always parallel to the screen. I already try the aframe-look-at-component on the camera, but it's not what I was looking for because they face a point instead of facing the screen.
So my next idea was to create an invisible cursor, and copy his rotation the the texts, but I'm not sure of this because I don't know if the cursor update his rotation or if it's only base on the cam rotation.
Anyway the main source of this problem was I don't know how to change the rotation of my text after creation, I tried mytext.object3D.rotation, mytext.setAttribute('rotation', newRotation), and also object3D.lookAt(), but either it didn't matter, or it wasn't what I was looking for.
What is the best way to achieve this ?
Here my hotspot component (which create the texts based on some props):
AFRAME.registerPrimitive('a-hotspot', {
defaultComponents: {
hotspot: {}
},
mappings: {
for: 'hotspot.for',
to: 'hotspot.to',
legend: 'hotspot.legend',
'legend-pos': 'hotspot.legend-pos',
'legend-rot': 'hotspot.legend-rot'
}
});
AFRAME.registerComponent('hotspot', {
schema: {
for: { type: 'string' },
to: { type: 'string' },
legend: { type: 'string' },
'legend-pos': { type: 'vec3', default: {x: 0, y: -0.5, z:0}},
'legend-rot': { type: 'number', default: 0 },
positioning: { type: 'boolean', default: false }
},
init: function () {
this.shiftIsPress = false
window.addEventListener('keydown', this.handleShiftDown.bind(this))
window.addEventListener('keyup', this.handleShiftUp.bind(this))
this.tour = document.querySelector('a-tour');
if (this.data.legend)
this.addText();
this.el.addEventListener('click', this.handleClick.bind(this));
},
// Creating the text, based on hotspots props
addText: function () {
var hotspot = this.el,
position = new THREE.Vector3(hotspot.object3D.position.x, hotspot.object3D.position.y, hotspot.object3D.position.z),
text = document.createElement('a-text'),
loadedScene = document.querySelector('a-tour').getAttribute('loadedScene')
position.x += this.data['legend-pos'].x
position.y += this.data['legend-pos'].y
position.z += this.data['legend-pos'].z
console.log(this.data['legend-rot'])
// Set text attributes
text.id = `text_${this.data.for}_to_${this.data.to}`
text.setAttribute('position', position)
text.setAttribute('color', '#BE0F34')
text.setAttribute('align', 'center')
text.setAttribute('value', this.data.legend)
text.setAttribute('for', this.data.for)
if (loadedScene && loadedScene !== this.data.for) text.setAttribute('visible', false)
// Insert text after hotspot
hotspot.parentNode.insertBefore(text, hotspot.nextSibling)
},
// This part is supposed to edit the rotation
// to always fit to my idea
tick: function () {
if (this.el.getAttribute('visible')) {
var cursorRotation = document.querySelector('a-cursor').object3D.getWorldRotation()
//document.querySelector(`#text_${this.data.for}_to_${this.data.to}`).object3D.lookAt(cursorRotation)
this.updateRotation(`#text_${this.data.for}_to_${this.data.to}`)
}
},
// This parts manage the click event.
// When shift is pressed while clicking on hotspot, it enable another component
// to stick a hotspot to the camera for help me to place it on the scene
// otherwise, it change the 360° image and enbable/disable hotspots.
handleShiftDown: function (e) {
if (e.keyCode === 16) this.shiftIsPress = true
},
handleShiftUp: function (e) {
if (e.keyCode === 16) this.shiftIsPress = false
},
handleClick: function (e) {
var target = 'target: #' + this.el.id
var tour = this.tour.components['tour']
if (this.shiftIsPress)
tour.el.setAttribute('hotspot-helper', target)
else
tour.loadSceneId(this.data.to, true);
}
});
I really don't know what to do..
EDIT: I found a part solution:
If I had geometry to my text (and material with alphaTest: 1 for hide it), setAttribute('rotation') work, and I base it on camera rotation. The problem is that after that, the camera is locked, don't understand why ^^
var cursorRotation = document.querySelector('a-camera').object3D.rotation
document.querySelector(`#text_${this.data.for}_to_${this.data.to}`).setAttribute('rotation', cursorRotation)
Thanks,
Navalex
I finally found the solution !
Instead of document.querySelector('a-camera').object3D.rotation, I used document.querySelector('a-camera').getAttribute('rotation') and it's work nice !
Be sure to check out the example here: https://stemkoski.github.io/A-Frame-Examples/sprites.html
The 'box' sign is always visible to user

Arangodb AQL recursive graph traversal

I have a graph with three collections which items can be connected by edges.
ItemA is a parent of itemB which in turn is a parent of itemC.
Elements only can be connected by edges in direction
"_from : child, _to : parent"
Currently I can get only "linear" result with this AQL query:
LET contains = (FOR v IN 1..? INBOUND 'collectionA/itemA' GRAPH 'myGraph' RETURN v)
RETURN {
"root": {
"id": "ItemA",
"contains": contains
}
}
And result looks like this:
"root": {
"id": "itemA",
"contains": [
{
"id": "itemB"
},
{
"id": "itemC"
}
]
}
But I need to get a "hierarchical" result of graph traversal like that:
"root": {
"id": "itemA",
"contains": [
{
"id": "itemB",
"contains": [
{
"id": "itemC"
}
}
]
}
So, can I get this "hierarchical" result running an aql query?
One more thing: traversal should run until leaf nodes will be encountered. So depth of the traversal is unknown in advance.
I have found solution. We decided to use UDF (user defined functions).
Here is a few steps to construct the proper hierarchical structure:
Register the function in arango db.
Run your aql query, that constructs a flat structure (vertex and corresponding path for this vertex). And pass result as input parameter of your UDF function.
Here my function just append each element to its parent
In my case:
1) Register the function in arango db.
db.createFunction(
'GO::LOCATED_IN::APPENT_CHILD_STRUCTURE',
String(function (root, flatStructure) {
if (root && root.id) {
var elsById = {};
elsById[root.id] = root;
flatStructure.forEach(function (element) {
elsById[element.id] = element;
var parentElId = element.path[element.path.length - 2];
var parentEl = elsById[parentElId];
if (!parentEl.contains)
parentEl.contains = new Array();
parentEl.contains.push(element);
delete element.path;
});
}
return root;
})
);
2) Run AQL with udf:
LET flatStructure = (FOR v,e,p IN 1..? INBOUND 'collectionA/itemA' GRAPH 'myGraph'
LET childPath = (FOR pv IN p.vertices RETURN pv.id_source)
RETURN MERGE(v, childPath))
LET root = {"id": "ItemA"}
RETURN GO::LOCATED_IN::APPENT_CHILD_STRUCTURE(root, flatStructure)
Note: Please don't forget the naming convention when implement your functions.
I also needed to know the answer to this question so here is a solution that works.
I'm sure the code will need to be customised for you and could do with some improvements, please comment accordingly if appropriate for this sample answer.
The solution is to use a Foxx Microservice that supports recursion and builds the tree. The issue I have is around looping paths, but I implemented a maximum depth limit that stops this, hard coded to 10 in the example below.
To create a Foxx Microservice:
Create a new folder (e.g. recursive-tree)
Create the directory scripts
Place the files manifest.json and index.js into the root directory
Place the file setup.js in the scripts directory
Then create a new zip file with these three files in it (e.g. Foxx.zip)
Navigate to the ArangoDB Admin console
Click on Services | Add Service
Enter an appropriate Mount Point, e.g. /my/tree
Click on Zip tab
Drag in the Foxx.zip file you created, it should create without issues
If you get an error, ensure the collections myItems and myConnections don't exist, and the graph called myGraph does not exist, as it will try to create them with sample data.
Then navigate to the ArangoDB admin console, Services | /my/tree
Click on API
Expand /tree/{rootId}
Provide the rootId parameter of ItemA and click 'Try It Out'
You should see the result, from the provided root id.
If the rootId doesn't exist, it returns nothing
If the rootId has no children, it returns an empty array for 'contains'
If the rootId has looping 'contains' values, it returns nesting up to depth limit, I wish there was a cleaner way to stop this.
Here are the three files:
setup.js (to be located in the scripts folder):
'use strict';
const db = require('#arangodb').db;
const graph_module = require("org/arangodb/general-graph");
const itemCollectionName = 'myItems';
const edgeCollectionName = 'myConnections';
const graphName = 'myGraph';
if (!db._collection(itemCollectionName)) {
const itemCollection = db._createDocumentCollection(itemCollectionName);
itemCollection.save({_key: "ItemA" });
itemCollection.save({_key: "ItemB" });
itemCollection.save({_key: "ItemC" });
itemCollection.save({_key: "ItemD" });
itemCollection.save({_key: "ItemE" });
if (!db._collection(edgeCollectionName)) {
const edgeCollection = db._createEdgeCollection(edgeCollectionName);
edgeCollection.save({_from: itemCollectionName + '/ItemA', _to: itemCollectionName + '/ItemB'});
edgeCollection.save({_from: itemCollectionName + '/ItemB', _to: itemCollectionName + '/ItemC'});
edgeCollection.save({_from: itemCollectionName + '/ItemB', _to: itemCollectionName + '/ItemD'});
edgeCollection.save({_from: itemCollectionName + '/ItemD', _to: itemCollectionName + '/ItemE'});
}
const graphDefinition = [
{
"collection": edgeCollectionName,
"from":[itemCollectionName],
"to":[itemCollectionName]
}
];
const graph = graph_module._create(graphName, graphDefinition);
}
mainfest.json (to be located in the root folder):
{
"engines": {
"arangodb": "^3.0.0"
},
"main": "index.js",
"scripts": {
"setup": "scripts/setup.js"
}
}
index.js (to be located in the root folder):
'use strict';
const createRouter = require('#arangodb/foxx/router');
const router = createRouter();
const joi = require('joi');
const db = require('#arangodb').db;
const aql = require('#arangodb').aql;
const recursionQuery = function(itemId, tree, depth) {
const result = db._query(aql`
FOR d IN myItems
FILTER d._id == ${itemId}
LET contains = (
FOR c IN 1..1 OUTBOUND ${itemId} GRAPH 'myGraph' RETURN { "_id": c._id }
)
RETURN MERGE({"_id": d._id}, {"contains": contains})
`);
tree = result._documents[0];
if (depth < 10) {
if ((result._documents[0]) && (result._documents[0].contains) && (result._documents[0].contains.length > 0)) {
for (var i = 0; i < result._documents[0].contains.length; i++) {
tree.contains[i] = recursionQuery(result._documents[0].contains[i]._id, tree.contains[i], depth + 1);
}
}
}
return tree;
}
router.get('/tree/:rootId', function(req, res) {
let myResult = recursionQuery('myItems/' + req.pathParams.rootId, {}, 0);
res.send(myResult);
})
.response(joi.object().required(), 'Tree of child nodes.')
.summary('Tree of child nodes')
.description('Tree of child nodes underneath the provided node.');
module.context.use(router);
Now you can invoke the Foxx Microservice API end point, providing the rootId it will return the full tree. It's very quick.
The example output of this for ItemA is:
{
"_id": "myItems/ItemA",
"contains": [
{
"_id": "myItems/ItemB",
"contains": [
{
"_id": "myItems/ItemC",
"contains": []
},
{
"_id": "myItems/ItemD",
"contains": [
{
"_id": "myItems/ItemE",
"contains": []
}
]
}
]
}
]
}
You can see that Item B contains two children, ItemC and ItemD, and then ItemD also contains ItemE.
I can't wait until ArangoDB AQL improves the handling of variable depth paths in the FOR v, e, p IN 1..100 OUTBOUND 'abc/def' GRAPH 'someGraph' style queries. Custom visitors were not recommended for use in 3.x but haven't really be replaced with something as powerful for handling wild card queries on the depth of a vertex in a path, or handling prune or exclude style commands on path traversal.
Would love to have comments/feedback if this can be simplified.

C# ASP.NET json objects to array

I am stuck on this error. This is my JSON data:
{
"date": "2016-08-26",
"time_of_day": "14:19",
"request_time": "2016-08-26T14:19:59+01:00",
"station_name": "Derby",
"station_code": "DBY",
"departures": {
"all": [
{
"mode": "train",
"service": "22152000",
"train_uid": "C65080"
},
{
"mode": "train",
"service": "22150000",
"train_uid": "C65145"
},
{
"mode": "train",
"service": "22180008",
"train_uid": "C70700"
}
]
}
}
What I am trying to do is add the service json object to an array, after this I want to increment through the service list and add each service to a separate API call. This is my code:
dynamic content = JsonConvert.DeserializeObject(json);
dynamic departures = content.departures;
dynamic DepartTimes = departures.all;
JArray items = ((JArray)DepartTimes["service"]);
int serviceLength = items.Count;
for (int i = 0; i < serviceLength; i++)
{
//next api call containing array increment
}
The error seems to be here :
JArray items = ((JArray)DepartTimes["service"]);
Any help is much appreciated, thank you!
One possible way is -
var json = "json";
dynamic d = JsonConvert.DeserializeObject(json);
JArray arr = new JArray();
foreach(JObject o in d.departures.all){
arr.Add(o["service"]);
}
Console.Write(arr.Count); //output : 3
Provided Json data is not proper. Second and third items must have open curly braces ({)
In addition, an example working code may be:
dynamic content = JsonConvert.DeserializeObject(json));
JArray items = (JArray)content.departures.all;
var newArray = items.Select(x=>x["service"].ToString()).ToArray();

Heatmap rendering issue at certain zoom with certain radius

Using GoogleMaps API v3.19, 3.20 or 3.exp, when I set a radius to a heatmap layer, at certain zoom level the render disappears and Chrome's console gives an error as
Uncaught TypeError: Cannot read property '84' of undefined, where '84' is sometimes different depending on the coordinate. Also, zooming in far from the area which the heatmap is applied, no error is shown, though navigating to the area even at greater zoom, I got the very same error and the layer just stuck like JPEG artifacts, which let me believe that there is something wrong with coordinates.
Zooming in and out does not solve the issue.
Any advise on this subject? Any known bug?
UPDATE (sharing some code of radius value change):
// heatmarkers is an array of WeightedLocation
heatmap = new google.maps.visualization.HeatmapLayer({
data : heatmarkers,
radius : 27
});
heatmap.setMap(myMap);
google.maps.event.addListener(myMap, 'zoom_changed', function(){
if (heatmap){
var zoom = myMap.getZoom();
if (zoom >= 14){
heatmap.set('radius', 90);
} else {
heatmap.set('radius', zoom * 2);
}
}
});
I just had exactly the same problem and solved it by reducing the number of decimals in the coordinates of my heatmap data, e.g.:
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
6.76483154296875,
51.226775403420554
]
}
}
was changed to:
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
6.7648315429,
51.22677540342
]
}
}
Hope this helps!

Resources