Select multiple entities in a datasource with Cesium - cesiumjs

I am trying to write a function where I can select multiple buildings of GeoJSON and/or CZML in Cesium
for example with the ctrl button held down, I will be able to create a list to select several buildings, and also change the color of the selected building as well.
I can change the color of one selected building now, and was told I should use the (entity.push) to add to the a list. but I'm still stuck!
Here is my initial code
*var scene = viewer.scene;
var handler = viewer.screenSpaceEventHandler;
handler.setInputAction(function(click) {
var pickedObject = scene.pick(click.position);
if (Cesium.defined(pickedObject) ) {
highlightedEntity= pickedObject.id;
// console.log (pickedObject.id);
} else{
highlightedEntity = undefined;
}
},Cesium.ScreenSpaceEventType.LEFT_CLICK);*
can someone help me out in completing it?
Here you can find a sample of the geojson

Related

Is It possible to move the avatar programmatically or specify position in Autodesk.AEC.Minimap3DExtension

Autodesk.AEC.Minimap3DExtension provides a 3d and 2d sync forge viewer where a user can use 2d viewer and move the avatar to navigate. In order to navigate user has to move the avatar or a dot icon in 2d viewer to navigate in 3d model.
My question is where is there any possibility where I can send the set of coordinates which are with me and can I move the avatar programmatically so that user don't have to do so.
here is any example why I am asking so,
I have a geometry on my forge viewer which is on 2D and I am looking to make a first person view over that geometry.
So If I have all the points of geometry I want to make it use with Autodesk.AEC.Minimap3DExtension so that I can move first person to different views
here is the sample I am rereferring to link
I had been following this wonderful blog link
with the help of this I am aware little bit aware how to work with 2dto3d
here in above link
const worldPos = sheetToWorld(intersection.intersectPoint, viewer2D.model,
viewer3D.model);
the above line of code gives me worldPos with which a geometric point is
created in the 3d viewer in spite of creating a geometry how can I use to
show view of that particular place
basically in this below line of code which transits camera from one position to another
viewer.navigation.setRequestTransition(true, newPos, newTarget,
viewer.navigation.getHorizontalFov());
I'm glad you like the blog post :)
After reading the question I have the impression that you have already answered it yourself. If you have some kind of a geometry, let's say a polyline, overlaid on top of the 2D drawing, you can use the same logic explained in the blog post, but when calling viewer.hitTest, instead of passing in some mouse coordinates, you would just supply one of the points of your polyline.
Instead of:
viewer2D.container.addEventListener('click', function (ev) {
const intersection = viewer2D.hitTest(ev.clientX, ev.clientY);
viewer3D.isolate([]);
if (intersection) {
viewer3D.isolate(intersection.dbId);
const worldPos = sheetToWorld(intersection.intersectPoint, viewer2D.model, viewer3D.model);
if (worldPos) {
let mesh = new THREE.Mesh(geometry, material);
mesh.position.set(worldPos.x, worldPos.y, worldPos.z);
viewer3D.overlays.addMesh(mesh, 'indicator-scene');
}
}
});
It could look something like this:
function moveCameraToPointOnSheet(x, y) {
const intersection = viewer2D.hitTest(x, y);
if (intersection) {
const worldPos = sheetToWorld(intersection.intersectPoint, viewer2D.model, viewer3D.model);
if (worldPos) {
const eyeVec = viewer3D.navigation.getEyeVector();
const newPos = worldPos;
const newTarget = newPos.clone().add(eyeVec);
const fov = viewer3D.navigation.getHorizontalFov();
viewer3D.navigation.setRequestTransition(true, newPos, newTarget, fov);
}
}
});

Wrong code in tutorial for event listeners

I am following this tutorial to build a store locator page with a Mapbox map.
I don't want to add custom markers because I already have custom map labels (symbols?), which means I don't need the optional last section of the tutorial and stop right after Add Event Listeners.
Once this is completed, the page should react to clicks in the side panel list, as well as on the map (2 event listeners). However, in the demo provided in the tutorial for that particular step, you can tell the code for the second event listener, the one making the map clickable, is not functioning, which makes me believe there is a mistake in the provided code:
// Add an event listener for when a user clicks on the map
map.on('click', function(e) {
// Query all the rendered points in the view
var features = map.queryRenderedFeatures(e.point, { layers: ['locations'] });
if (features.length) {
var clickedPoint = features[0];
// 1. Fly to the point
flyToStore(clickedPoint);
// 2. Close all other popups and display popup for clicked store
createPopUp(clickedPoint);
// 3. Highlight listing in sidebar (and remove highlight for all other listings)
var activeItem = document.getElementsByClassName('active');
if (activeItem[0]) {
activeItem[0].classList.remove('active');
}
// Find the index of the store.features that corresponds to the clickedPoint that fired the event listener
var selectedFeature = clickedPoint.properties.address;
for (var i = 0; i < stores.features.length; i++) {
if (stores.features[i].properties.address === selectedFeature) {
selectedFeatureIndex = i;
}
}
// Select the correct list item using the found index and add the active class
var listing = document.getElementById('listing-' + selectedFeatureIndex);
listing.classList.add('active');
}
});
Would anyone be able to tell what is wrong with this code?
Turns out the code is incomplete in that the cursor doesn't change to a pointer as you hover over a map label/marker so it doesn't clue you into realising you can click on it, hence my assumption it wasn't working at all. I assume the general users who would then face the map would be equally deceived unless the pointer shows up. So in the tutorial, if you do go ahead and click the marker, it will have the expected behaviour and display the popup, although no pointer is shown.
Here is how to create the pointer, based on this fiddle: https://jsfiddle.net/Twalsh88/5j70wm8n/25/
map.on('mouseenter', 'locations', function(e) {
// Change the cursor style as a UI indicator.
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'locations', function() {
map.getCanvas().style.cursor = '';
});

After several times loading an object in THREE.js, the page crashes

I have a few buttons in my app which load a new object onto the scene when clicked. Below is the code being used for loading new objects:
character = new THREE.UCSCharacter();
var ucspath = "skinnedModel.json";
manager = new THREE.LoadingManager();
manager.onProgress = function(url, itemsLoaded, itemsTotal) {
console.log(url, itemsLoaded, itemsTotal);
};
var onProgress = function (xhr){
if(xhr.lengthComputable){
var bar = 150;
percentComplete = xhr.loaded / xhr.total * 100;
bar = Math.floor( xhr.loaded / xhr.total * bar );
document.getElementById("bar").style.width = bar + "px";
}
};
var loader = new THREE.XHRLoader(manager);
loader.crossOrigin = true;
loader.load(ucspath, function (text) {
var config = JSON.parse(text);
character.loadParts(config);
avatar = character.root;
avatar.name = "female";
scene.add(avatar);
}, onProgress);
After clicking the button 10~12 times, the loading becomes increasingly slow and eventually the page crashes. I could not detect any unsual behavior using Chrome DevTools.
Is there something that needs to be done in order to load as many objects as desired without overloading the browser?
[Update] Heap Snapshot
Thanks in advance.
It seems that you're facing a performance issue caused by your model file.
Fortunately, the problem of a having a huge model replicated with different traits is very common in the game industry so I can give you an hint though it's not that simple :
In your 3D editor, merge all of your different geometries into one single model and give to each of your model parts an easy name like hair_01, head_02, beard_03, ...
Load this model in your Three.js app just once. This is your template model.
Now, when you want to create a new model with a different appearance :
Clone the template model ;
Hide the parts you don't want ;
Show the parts you want.
Here's an example of the idea :
var copy = template.clone();
copy.getObjectByName('head_01').visible = false;
copy.getObjectByName('head_02').visible = true;
scene.add(copy);
Cloning the model prevents the engine from duplicating all geometry data (*).
This is a picture of a single 3D model file from an existing game that contains every character geometries and switch them in runtime to give a humanoid various appearances.
*See THREE.Mesh.clone()

How can I remove just geometry instances added to primitives in Cesium

I saw some similar questions like how to remove all primitives and so, but none of the answers help me. I am loading several CZML files on my cesium, and at the same time I need to add some geometry instances, like the code that I provided here to my cesium file, using data from my CZML.
There are two situation in my model, in one of them I want to delete all of my primitives, data sources and entities, for which I used following reset() method. When I tried to remove primitives and entities, I've got DeveloperError: This object was destroyed, i.e., destroy() was called.Error and when I just delete primitive, it said some of entities already exist on the collection, which means it cannot delete all of entities.
In the second case, I just want to delete those geometry instances that I created but I want to keep other entities and data sources. If I delete all the primitives, I will lose some entities that I need.
I have a method when I am loading CZML for creating geometry instances as follows:
function createMap(West,East,South,North,hNumber,VNumber){
var baseSouth=South;
var vdivid=(North-South)/VNumber;
var hdivid=(East-West)/hNumber;
for (var i=0;i<hNumber;i++){
for(var j=0;j<vNumber;j++){
var rectangleInstance = new Cesium.GeometryInstance({
geometry : new Cesium.RectangleGeometry({
rectangle : Cesium.Rectangle.fromDegrees((West+j*vdivid), baseSouth,(West+(j+1)*vdivid) , baseSouth+hdivid)
}),
id : 'rectangle'+i+'.'+j,
attributes : {
color : new Cesium.ColorGeometryInstanceAttribute.fromColor(getRandomColor(0.2))
}
});
scene.primitives.add(new Cesium.GroundPrimitive({
geometryInstance : rectangleInstance
}));
baseSouth=baseSouth+hdivid;
}
};
My reset method is as follows,
function reset(){
clock.multiplier = 1.0;
var primitives = scene.primitives.removeAll();
viewer.dataSources.removeAll();
viewer.entities.removeAll();
};
getRandomColor is a function for generating some specific Cesium colors randomly. I define this method because I want to generate some specific random colors (for example just GREEN, YELLOW, BLUE and RED)
Thanks for posting the reset function. I believe the problem here is that your call to scene.primitives.removeAll() is removing primitives that belong to a dataSource. But in this case, I think it's safe to simply re-order the removals.
Give this a try:
function reset() {
clock.multiplier = 1.0;
// First, remove all dataSources. Removing a dataSource will
// automatically remove its associated entities & primitives.
viewer.dataSources.removeAll();
// Next, remove any remaining entities that weren't part of a dataSource.
viewer.entities.removeAll();
// Finally, it is safe to remove any remaining primitives, as we can
// now be certain they did not belong to any dataSource or entity.
scene.primitives.removeAll();
};

Can i create a function to tell a button to open a movie clip of the same name

I am new to the actionscript side of flash,
I am working on a map that has say 20 popups(movieclips) and the countries are the buttons, i have just been informed i need to add 60 more.
Below is an example of the code i have been using
english_movie.visible=french_movie.visible=turkish_movie.visible=false
english_btn.addEventListener(MouseEvent.CLICK, englishButtonClick);
french_btn.addEventListener(MouseEvent.CLICK, frenchButtonClick);
turkish_btn.addEventListener(MouseEvent.CLICK, turkishButtonClick)
function englishButtonClick(event:MouseEvent):void {
english_movie.visible=true;
english_movie.play();
french_movie.visible=turkish_movie.visible=false
}
function frenchButtonClick(event:MouseEvent):void {
french_movie.visible=true;
french_movie.play();
english_movie.visible=turkish_movie.visible=false
}
function turkishButtonClick(event:MouseEvent):void {
turkish_movie.visible=true;
turkish_movie.play();
english_movie.visible=french_movie.visible=false
}
Im thinking there must be an easier way to do this than replicating the code over and over.
Any help would be much appreciated.
Here's how to simplify the whole thing with code: Each btn object is
related to one movie object. This can be achieved with a Dictionary.
var btnToMovieAssociation:Dictionary = new Dictionary();
btnToMovieAssociation[english_btn] = english_movie; // repeat this line for every btn/movie pair
Now you have to generalise your click handler. The key difference
between each function (apart from making one certain movie visible)
is that they all make certain other movies invisible. But actually,
it's sufficient to only make the previously visible movie invisble.
To do this, create a variable that keeps track of the current visible
movie.
var currentMovie:MovieClip = english_movie;
Initialising the variable with english_movie has no effect on the
program. you can pick any other of the movies. It will make things
easier in the following code if this variable is initialised.
Now your function does effectively this:
make movie of clicked button visible
play this movie
make last movie invisible
Here's the cool part. You only add one listener. Look up if something
is in the dictionary for the clicked thing and consider that the
movie you want to show next.
addEventListener(MouseEvent.CLICK, buttonClick);
function buttonClick(event:MouseEvent):void
{
var movie:MovieClip = btnToMovieAssociation[event.target]
if (movie == null)
return; // nothing in the dictionary, it wasn't a button that was clicked.
movie.visible=true;
movie.play();
currentMovie.visible = false;
currentMovie = movie;
}
There are problems with this solution:
You still have to declare every pair, which is still tedious and prone to erro. (you have to type every name twice)
If your buttons are made up of several objects, event.target might point to them instead of the button as a whole. But with only
the btns in the dictionary and not all their individual parts,
nothing would be found in the dictionary. This can be circumvented by
setting mouseChildren = false; on every btn.
i posted this question else where and got this response
var tl:MovieClip=this;
var mc:MovieClip;
var i:int;
var buttonA:Array=[english_btn,french_btn,turkish_btn];
for(i=0;i<buttonA.length;i++){
buttonA[i].addEventListener(MouseEvent.CLICK,buttonF);
tl[buttonA[i].name.split("_")[0]+"_movie"].visible = false;
}
function buttonF(e:MouseEvent):void{
for(i=0;i<buttonA.length;i++){
tl[buttonA[i].name.split("_")[0]+"_movie"].visible = false;
}
tl[e.currentTarget.name.split("_")[0]+"_movie"].visible=true;
}
Which works great.