Cesium: "fan out" overlapping polylines? - javascript

I am using Cesium and am looking to visually represent multiple polylines between the same two entities. For example, a green polyline from entity A to entity B, and also a blue polyline from entity A to entity B. I would like them not to overlap or blend, so I am imagining a fanning out as more lines are drawn, so that each line and what it represents can be visualized. I've included a crude drawing of what I'm trying to explain with the fanning out rather than overlapping.
I have a functional data structure keeping track of the lines I want to represent, as well as a Cesium map that they are already being programatically drawn on. I guess at this point I'm looking for the technical explanation of how to programatically bend the polylines on the map, and also any suggestions for polyline management in order to recognize overlapping lines so I can apply the bends.
Thanks for any help!

Here's one method. This sample code will "spread" the lines along longitude only, so works best on North/South lines and not on East/West lines. But I think it should convey the right idea, you just have to figure out a more general-purpose way of "moving" the midpoint to a visually pleasing location.
I'm using time-based paths here, to gain access to Cesium's interpolation logic. But I've selected a reference time far in the past, and I'm only showing the finished paths on the viewer. So, the user is none the wiser that time is playing any role here.
var viewer = new Cesium.Viewer('cesiumContainer', {
navigationInstructionsInitiallyVisible: false,
animation: false,
timeline: false,
// These next 5 lines are just to avoid the Bing Key error message.
imageryProvider : Cesium.createTileMapServiceImageryProvider({
url : Cesium.buildModuleUrl('Assets/Textures/NaturalEarthII')
}),
baseLayerPicker : false,
geocoder : false,
// This next line fixes another Stack Snippet error, you may omit
// this setting from production code as well.
infoBox : false
});
var numberOfArcs = 5;
var startLon = -105;
var startLat = 39.7;
var stopLon = -98.4;
var stopLat = 29.4;
var spread = 5;
var referenceTime = Cesium.JulianDate.fromIso8601('2001-01-01T00:00:00Z');
var midTime = Cesium.JulianDate.addSeconds(referenceTime, 43200, new Cesium.JulianDate());
var stopTime = Cesium.JulianDate.addSeconds(referenceTime, 86400, new Cesium.JulianDate());
for (var i = 0; i < numberOfArcs; ++i) {
var color = Cesium.Color.fromRandom({
alpha : 1.0
});
// Create a straight-line path.
var property = new Cesium.SampledPositionProperty();
var startPosition = Cesium.Cartesian3.fromDegrees(startLon, startLat, 0);
property.addSample(referenceTime, startPosition);
var stopPosition = Cesium.Cartesian3.fromDegrees(stopLon, stopLat, 0);
property.addSample(stopTime, stopPosition);
// Find the midpoint of the straight path, and move it.
var spreadAmount = (spread / (numberOfArcs - 1)) * i - (spread / 2);
var midPoint = Cesium.Cartographic.fromCartesian(property.getValue(midTime));
midPoint.longitude += Cesium.Math.toRadians(spreadAmount);
var midPosition = viewer.scene.globe.ellipsoid.cartographicToCartesian(
midPoint, new Cesium.Cartesian3());
// Redo the path to be the new arc.
property = new Cesium.SampledPositionProperty();
property.addSample(referenceTime, startPosition);
property.addSample(midTime, midPosition);
property.addSample(stopTime, stopPosition);
// Create an Entity to show the arc.
var arcEntity = viewer.entities.add({
position : property,
// This path shows the arc as a polyline.
path : {
resolution : 1200,
material : new Cesium.PolylineGlowMaterialProperty({
glowPower : 0.16,
color : color
}),
width : 10,
leadTime: 1e11,
trailTime: 1e11
}
});
// This is where it becomes a smooth path.
arcEntity.position.setInterpolationOptions({
interpolationDegree : 5,
interpolationAlgorithm : Cesium.LagrangePolynomialApproximation
});
}
// Optionally, add start and stop points, mostly for easy zoomTo().
viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(startLon, startLat),
point : {
pixelSize : 8,
color : Cesium.Color.WHITE
}
});
viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(stopLon, stopLat),
point : {
pixelSize : 8,
color : Cesium.Color.WHITE
}
});
viewer.zoomTo(viewer.entities);
html, body, #cesiumContainer {
width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden;
font-family: sans-serif;
}
<link href="http://cesiumjs.org/releases/1.30/Build/Cesium/Widgets/widgets.css"
rel="stylesheet"/>
<script src="http://cesiumjs.org/releases/1.30/Build/Cesium/Cesium.js">
</script>
<div id="cesiumContainer"></div>

Related

pts.js: Speed up animation of grid cells?

I'm using Pts.js to create a grid of cells and then color these cells depending on their distance to the mouse pointer. My code is largely based on a demo from the official Pts.js Website.
Pts.quickStart("#pt", "#123");
//
let pts = [];
var follower = new Pt();
space.add({
start: (bound) => {
pts = Create.gridCells(space.innerBound, 40, 20);
follower = space.center;
},
//
animate: (time, ftime) => {
follower = follower.add(space.pointer.$subtract(follower).divide(20));
form.stroke("#123");
//
let rects = pts.map((p) => {
let color;
let mag = follower.$subtract(Rectangle.center(p)).magnitude();
let r = Rectangle.fromCenter(Rectangle.center(p), Rectangle.size(p));
//
if (mag >= 100) {
color = "#000"
} else {
color = "#f00"
}
//
form.fill(color).rect(r);
})
//
form.fillOnly("#fff").point(space.pointer, 10, "circle");
}
})
//
space.bindMouse().bindTouch().play();
#pt {
width: 800px;
height: 600px;
margin: 30px auto 0;
}
<script src="https://unpkg.com/pts#0.9.4/dist/pts.min.js"></script>
<div id="pt"></div>
The implementation works absolutely fine. But I'd like to increase the speed with which the »coloring« of the cells »follows« the cursor, i.e. reduce the delay with which the red space around the cursor is animated. Ideally, I'd like to have no delay.
I'm new to Pts.js, so still wrapping my head around the docs, and I can't find an option or explanation for how to control the animation's speed. If anyone could point me to what I need to do here, that'd be great.
It seems that this line is what controls the behavior of the red grid area:
follower = follower.add(space.pointer.$subtract(follower).divide(20));
The value supplied to .divide() controls the speed at which the red area follows the cursor. Changing its argument from 20 to 1 (or even removing .divide(20) entirely) causes the "following" behavior to be immediate.
(Though, if you intend to remove the capability for that behavior, I suspect that entire line could be simplified.)

Responsive joint js diagram

Im using joint js library to create a diagram inside html, but i need it to be responsive as mi site.Thing is, i have it inside a div with a java class that open down and closes up with this code :
$('.open-down-up').click(function(){
var nameMore = $(this).attr("name");
var valMore = $(this).attr("value");
if(valMore == 0){
$(this).find('span').empty();
$(this).find('span').append("▼");
$('div[id='+nameMore+']').slideDown("normal");
$(this).attr("value","1");
}else if(valMore == 1){
$(this).find('span').empty();
$(this).find('span').append("►");
var n = nameMore.indexOf("process_process");
$('div[id='+nameMore+']').slideUp("normal", function(){
var n = nameMore.indexOf("process_process");
if(n > -1){
hide_panel_all();
}
});
$(this).attr("value","0");
}
});
SO, i already tried things like :
var graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({
el: $('#modelCanvas'),
gridSize: 10,
height: $('#modelCanvas').height(),
width: $('#modelCanvas').width(),
gridSize: 1,
model: graph,
});
But it doesn't work...any idea or approach i can apply ??
Thanks in advance.
I found a way so it may be helpfull for someone who need it (for a resizing responsive):
It's necessary to scale the entire paper along with the window, so, we create a javascript event on it:
window.onresize = function(event) {
paper.setDimensions($('#your_div_id').width());
paper.scaleContentToFit({minScaleX: 0.3, minScaleY: 0.3, maxScaleX: 1 , maxScaleY: 1});
};
Using joint js properties whe resize the canvas along with the div based on that event but only affecting width, then set a max scale for X and Y axis. You can of course, adapt it of make conditions on it as you may need.
Hope it helps as for me !

How do I make Cesium points rotate with the map?

I'm building a cesium app and I need my points to indicate a rotational heading. I'm using billboards to display an image I created to show this heading; however, when I rotate the globe, the points don't rotate with it, making the heading indicators incorrect.
In other words, the rotation of my billboards stay constant with the screen space, but not with the globe.
Here's an aerial view of New York. The points in question are in the lower left corner of the image. Note that the heading indicators are pointing northeast.
Here's the same view, but rotated 180° so that up is South. Now, the heading indicators are still pointing to the northwest of the screen, but I need it to be pointing to the globe's northeast, i.e. towards Manhattan.
Here's the code that displays a point:
var entity = {
id: data.cluster_number + '_' + point.id,
name: point.id,
position: Cesium.Cartesian3.fromDegrees(point.lon, point.lat),
billboard: {
image: 'assets/img/point.png',
color: color,
rotation: -1 * toRadians(point.heading),
scale: point.size * 0.07
}
};
if (!angular.isDefined(viewer.entities.getById(entity.id))) {
viewer.entities.add(entity);
}
What's the easiest/most effective way to get the points to rotate with the globe/map?
You need to do a few things.
1 - Update the rotation property of the billboard to be a CesiumCallbackProperty.
rotation: new Cesium.CallbackProperty(
function () {
return -1 * toRadians(point.heading);
}, false);
2 - You'll need to update the heading property of the point based on the camera's heading.
point.heading = viewer.camera.heading;
3 - You'll want to update that point on an interval, either using the cesium clock:
mapContext.clock.onTick.addEventListener(function() {...});
or using JavaScripts setInterval
Once all that is set up here is how it works.
The billboard on the map continuosly pulls the rotation from the point, and if it changes, it will update the billboard on the map. Each time the interval or clock ticks, the value being used changes, based on the heading of the camera.
NOTE: The heading property on the camera is in radians.
Set the billboard's alignedAxis to Cesium.Cartesian3.UNIT_Z instead of the default screen up vector. Run following sample in SandCastle to demonstrate that it works:
var viewer = new Cesium.Viewer('cesiumContainer');
var position = Cesium.Cartesian3.fromDegrees(-111.572177, 40.582083);
var cesiumTerrainProviderMeshes = new Cesium.CesiumTerrainProvider(
{ url : '//assets.agi.com/stk-terrain/world' }
);
//viewer.terrainProvider = cesiumTerrainProviderMeshes;
//viewer.scene.globe.depthTestAgainstTerrain = true;
var ellipsoid = viewer.scene.globe.ellipsoid;
var billboardCollection = viewer.scene.primitives.add(new Cesium.BillboardCollection(
{ scene : viewer.scene }
));
billboardCollection.add(
{ position : position, image : '../images/facility.gif', //heightReference : Cesium.HeightReference.CLAMP_TO_GROUND, rotation: Cesium.Math.PI/4, alignedAxis : Cesium.Cartesian3.UNIT_Z }
);

SVG: Scale one element of group from specified anchor point

OK, so I have a group of four elements rotating 90 degrees as I want them to, around an origin point in the middle of the four elements.
I would like to scale the top left block before and after spinning as well, outward from said origin point, but I'm having much difficulty doing so.
Here is a fiddle for my sample (read: overly simplified) progress so far:
http://jsfiddle.net/Vac2Q/2843/
The fiddle's javascript:
/* create an svg drawing */
var draw = SVG('drawing')
/* draw rectangle */
var dial = draw.circle(60)
.move(125,125)
.fill('#0099ff')
var rect_yellow = draw.rect(50,50)
.move(100,100)
.fill('gold')
var rect_blue = draw.rect(50,50)
.move(160,100)
.fill('navy')
var rect_black = draw.rect(50,50)
.move(160,160)
.fill('black')
var rect_green = draw.rect(50,50)
.move(100,160)
.fill('green')
var blades = draw.group()
.add(rect_yellow)
.add(rect_blue)
.add(rect_black)
.add(rect_green)
.back()
var angle = 0
var rotation = 90
var spin = document.getElementById('spin')
var spun = 0
/* make rectangle jump and change color on mouse over */
spin.addEventListener('click', function() {
/* calculate new ending orientation for blades */
angle += rotation
var new_rotate = angle
/* rotate the blade group */
blades.animate(1000, '>')
.rotate(new_rotate, 155, 155)
++spun
})
And here is a slightly more glamorous example of what I'm trying to do re: scaling:
The first issue is being able to determine which blade is in the top left position after a given rotation. The second issue is scaling itself; I've gotten the blade to scale, but then it goes crazy and moves off the screen at the same time. I'm not sure how to get it to scale properly from the specified origin point (the middle of the center circle).
You can use the .after() function to chain animations.
I'm not sure if I am using svg.js correctly, but here's what I did:
var rects = [rect_yellow, rect_green, rect_black, rect_blue];
// define the animations
var enlarge_blade = function() {
rects[spun % 4].animate(250, '<')
.scale(1.25, 1.25)
.translate(-38,-38);
};
function spin_anim() {
rects[spun % 4].animate(250, '>')
.scale(1, 1)
.translate(0,0)
.after(rotate_blades);
};
var rotate_blades = function() {
blades.animate(1000, '>')
.rotate(angle, 155, 155)
.after(function() {
++spun;
enlarge_blade();
title.text('spun ' + spun + ' times');
});
};
// Pre-enlarge the first (yellow) rect
enlarge_blade();
/* make rectangle jump and change color on mouse over */
spin.addEventListener('click', function() {
angle += rotation
spin_anim();
})
Demo here
I see you're using svg.js. I don't know how it treats transforms internally. But in any case. To scale an element, you typically use its center point as a reference. Therefore you should find the center point of each rect and scale it using that point. (I assume svg.js performs the required translation internally).

How to avoid event's lag with KineticJS?

I am in the prototyping stage of making a 2d map builder for a hybrid web/text adventure game and so far KineticJS seems like an ideal fit. Only issue currently is that given enough velocity on mouse movement, it will skip over cells and never fire their mouseover event handler.
2 core functionality goals: When the user highlights a cell, it would be marked as "active". Additionally if they're holding the mouse down and move across the grid, cells would be flipped on or off ( this maybe refactored to set all on if not if the first cell is not active, or vice versa ).
My question: Is there a way to ensure all cells are triggered, regardless of mouse cursor velocity? If not, is there a better way to draw a line over the cells so that it consistently triggers all relevant cells?
The entire prototype has been put into jsFiddle ( http://jsfiddle.net/7ggS4/ ) but for future sake, the rest will be copied below as well.
<head>
<title>KineticJS</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/kineticjs/4.7.2/kinetic.min.js"></script>
</head>
<body>
<div id="canvas"></div>
</body>
<script defer="defer">
/**
Return's a KS layer
*/
function Grid(cells, stage) {
//Constants
// Illrelevant comment - It seriously pisses me off that canvas uses
// string color codes ( either name or string hexidecimal ) instead of taking an
// integer or actual hexidecimal 0xFFFF values. This just seems painfully inefficient.
this.activeCellColor = "green";
this.clearCellColor = "blue";
this.highlightCellColor = "red";
this.cells = cells,
this.layer = new Kinetic.Layer(),
this.grid = new Array(),
this.isMouseDown = false,
this.mouseLeft = false,
this.mouseRight = false,
this.adjRow = stage.getWidth() / cells,
this.adjCol = stage.getHeight() / cells;
this.generate();
stage.add(this.layer)
}
Grid.prototype.generate = function(){
var i, rx, ry, rect;
for (i = 0; i < this.cells * this.cells; i++) {
rx = Math.floor(i / this.cells) * this.adjRow;
ry = (i % this.cells) * this.adjCol;
rect = new Kinetic.Rect({
x: rx,
y: ry,
width: this.adjRow,
height: this.adjCol,
fill: this.clearCellColor,
stroke: 'black',
strokeWidth: .2,
cell: {x: Math.floor(i / this.cells), y: i % this.cells},
active: false,
grid: this //Just in case .bind(this) doesn't work right
});
rect.on('mouseenter', this.onMouseEnter.bind(this));
rect.on('mouseleave', this.onMouseLeave.bind(this));
rect.on('mousedown', this.onMouseDown.bind(this));
rect.on('mouseup', this.onMouseUp.bind(this));
this.grid.push(rect);
this.layer.add(rect);
}
}
Grid.prototype.onMouseEnter = function(evt) {
var src = evt.targetNode;
console.log(evt.type, this.isMouseDown, src.attrs.cell)
if (this.isMouseDown == true) {
src.attrs.active = ! src.attrs.active;
}
if (src.attrs.active == false) {
src.setFill(this.highlightCellColor);
} else {
src.setFill(this.activeCellColor);
}
this.layer.batchDraw();
}
Grid.prototype.onMouseLeave = function(evt) {
var src = evt.targetNode;
console.log(evt.type, this.isMouseDown, src.attrs.cell)
if (src.attrs.active == false) {
src.setFill(this.clearCellColor);
this.layer.batchDraw();
}
}
Grid.prototype.onMouseUp = function(evt){
var src = evt.targetNode;
console.log(evt.type, this.isMouseDown, src.attrs.cell)
this.isMouseDown = false;
}
Grid.prototype.onMouseDown = function(evt){
var src = evt.targetNode;
console.log(evt.type, this.isMouseDown, src.attrs.cell)
this.isMouseDown = true;
src.attrs.active = ! src.attrs.active;
if (src.attrs.active) {
src.setFill(this.activeCellColor);
} else {
src.setFill(this.clearCellColor);
}
this.layer.batchDraw();
}
var stage = new Kinetic.Stage({
container: 'canvas',
width: 600,
height: 600
}),
myGrid = new Grid(50, stage);
</script>
50x50=2500 active objects: that's too many for Kinetic to handle.
Remember that each "intelligent" Kinetic cell has a lot of overhead associated with it.
How about reducing your grid to 20x20?
Alternatively, you will have to separate the mouse handling from the cell handling to gain the required performance.
Mouse Handling
Your mouse handling would only involve capturing mouse points into an array of accumulated points. You can use this kind of code to capture mouse points on the stage:
$(stage.getContent()).on('click', function (event) {
myPointsArray.push(stage.getMousePosition());
});
Cell processing
The cell processing would involve applying those accumulated points to affect your grid cells. An effective place to do this code would be in a requestAnimationFrame (RAF) loop. You won't be doing animations, but RAF gives high performance because it is aware of the availability of system resources. An RAF loop would look like this:
function processPointsArray(array){
// request another loop even before we're done with this one
requestAnimationFrame(processPointsArray);
// process the points array and affect your cells here
}
A processing efficiency
RAF is called up to 60 times per second, so your user will probably navagate only a small portion of your grid during that time. You can increase performance by calculating the min/max x and y coordinates in the accumulated points array and only process those grid cells within that boundary.

Categories