Skip to content

Commit

Permalink
Add particles layer and use it on satellites example
Browse files Browse the repository at this point in the history
  • Loading branch information
vasturiano committed Jan 19, 2025
1 parent 1ff4251 commit 32942a6
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 165 deletions.
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const myGlobe = new Globe(myDOMElement)
* [Hex Bin Layer](#hex-bin-layer)
* [Hexed Polygons Layer](#hexed-polygons-layer)
* [Tiles Layer](#tiles-layer)
* [Particles Layer](#particles-layer)
* [Rings Layer](#rings-layer)
* [Labels Layer](#labels-layer)
* [HTML Elements Layer](#html-elements-layer)
Expand Down Expand Up @@ -326,6 +327,28 @@ new Globe(<domElement>, { configOptions })
| <b>onTileRightClick</b>(<i>fn</i>) | Callback function for tile right-clicks. The tile object, the event object and the clicked coordinates are included as arguments: `onTileRightClick(tile, event, { lat, lng, altitude })`. | - |
| <b>onTileHover</b>(<i>fn</i>) | Callback function for tile mouse over events. The tile object (or `null` if there's no tile under the mouse line of sight) is included as the first argument, and the previous tile object (or `null`) as second argument: `onTileHover(tile, prevTile)`. | - |

### Particles Layer

<p align="center">
<a href="//vasturiano.github.io/globe.gl/example/satellites/"><img width="70%" src="https://vasturiano.github.io/globe.gl/example/satellites/preview.png"></a>
</p>

| Method | Description | Default |
| --- | --- | :--: |
| <b>particlesData</b>([<i>array</i>]) | Getter/setter for the list of particle sets to represent in the particles map layer. Each particle set is displayed as a group of [Points](https://threejs.org/docs/#api/en/objects/Points). Each point in the group is a geometry vertex and can be individually positioned anywhere relative to the globe. | `[]` |
| <b>particlesList</b>([<i>str</i> or <i>fn</i>]) | Particle set accessor function or attribute for the list of particles in the set. By default, the data structure is expected to be an array of arrays of individual particle objects. | `d => d` |
| <b>particleLabel</b>([<i>str</i> or <i>fn</i>]) | Particle object accessor function or attribute for label (shown as tooltip). Supports plain text, HTML string content or an [HTML element](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement). | `name` |
| <b>particleLat</b>([<i>num</i>, <i>str</i> or <i>fn</i>]) | Particle object accessor function, attribute or a numeric constant for the latitude coordinate. | `lat` |
| <b>particleLng</b>([<i>num</i>, <i>str</i> or <i>fn</i>]) | Particle object accessor function, attribute or a numeric constant for the longitude coordinate. | `lng` |
| <b>particleAltitude</b>([<i>num</i>, <i>str</i> or <i>fn</i>]) | Particle object accessor function, attribute or a numeric constant for the altitude in terms of globe radius units. | 0.01 |
| <b>particlesSize</b>([<i>num</i>, <i>str</i> or <i>fn</i>]) | Particle set accessor function, attribute or a numeric constant for the size of all the particles in the group. | `0.5` |
| <b>particlesSizeAttenuation</b>([<i>boolean</i>, <i>str</i> or <i>fn</i>]) | Particle set accessor function, attribute or a boolean constant for whether the size of each particle on the screen should be attenuated according to the distance to the camera. | `true` |
| <b>particlesColor</b>([<i>str</i> or <i>fn</i>]) | Particle set accessor function or attribute for the color of all the particles in the group. This setting will be ignored if `particlesTexture` is defined. | `white` |
| <b>particlesTexture</b>([<i>str</i> or <i>fn</i>]) | Particle set accessor function or attribute for the [Texture](https://threejs.org/docs/#api/en/textures/Texture) to be applied to all the particles in the group. | - |
| <b>onParticleClick</b>(<i>fn</i>) | Callback function for particle (left-button) clicks. The particle object, the event object and the clicked coordinates are included as arguments: `onParticleClick(particle, event, { lat, lng, altitude })`. | - |
| <b>onParticleRightClick</b>(<i>fn</i>) | Callback function for particle right-clicks. The particle object, the event object and the clicked coordinates are included as arguments: `onParticleRightClick(particle, event, { lat, lng, altitude })`. | - |
| <b>onParticleHover</b>(<i>fn</i>) | Callback function for particle mouse over events. The particle object (or `null` if there's no particle under the mouse line of sight) is included as the first argument, and the previous particle object (or `null`) as second argument: `onParticleHover(particle, prevParticle)`. | - |

### Rings Layer

<p align="center">
Expand Down Expand Up @@ -388,10 +411,6 @@ new Globe(<domElement>, { configOptions })

### 3D Objects Layer

<p align="center">
<a href="//vasturiano.github.io/globe.gl/example/satellites/"><img width="70%" src="https://vasturiano.github.io/globe.gl/example/satellites/preview.png"></a>
</p>

| Method | Description | Default |
| --- | --- | :--: |
| <b>objectsData</b>([<i>array</i>]) | Getter/setter for the list of custom 3D objects to represent in the objects layer. Each object is rendered according to the `objectThreeObject` method. | `[]` |
Expand Down Expand Up @@ -431,7 +450,7 @@ new Globe(<domElement>, { configOptions })
| <b>resumeAnimation</b>() | Resumes the rendering cycle of the component, and re-enables the user interaction. This method can be used together with `pauseAnimation` for performance optimization purposes. | |
| <b>enablePointerInteraction</b>([<i>boolean</i>]) | Getter/setter for whether to enable the mouse tracking events. This activates an internal tracker of the canvas mouse position and enables the functionality of object hover/click and tooltip labels, at the cost of performance. If you're looking for maximum gain in your globe performance it's recommended to switch off this property. | `true` |
| <b>pointerEventsFilter</b>([<i>fn</i>]) | Getter/setter for the filter function which defines whether a particular object can be the target of pointer interactions. In general, objects that are closer to the camera get precedence in capturing pointer events. This function allows having ignored object layers so that pointer events can be passed through to deeper objects in the various globe layers. The ThreeJS object and its associated data (if any) are passed as arguments: `pointerEventsFilter(obj, data)`. The function should return a boolean value. | `() => true` |
| <b>lineHoverPrecision</b>([<i>num</i>]) | Getter/setter for the precision to use when detecting hover events over [Line](https://threejs.org/docs/#api/objects/Line) objects, such as arcs and paths. | 0.2 |
| <b>lineHoverPrecision</b>([<i>num</i>]) | Getter/setter for the precision to use when detecting hover events over [Line](https://threejs.org/docs/#api/objects/Line) and [Points](https://threejs.org/docs/#api/objects/Points) objects, such as arcs, paths or particles. | 0.2 |
| <b>onZoom</b>(<i>fn</i>) | Callback function for point-of-view changes by zooming or rotating the globe using the orbit controls. The current point of view (with the syntax `{ lat, lng, altitude }`) is included as sole argument. | |
| <b>lights</b>([<i>array</i>]) | Getter/setter for the list of lights to use in the scene. Each item should be an instance of [Light](https://threejs.org/docs/#api/en/lights/Light). | [AmbientLight](https://threejs.org/docs/?q=ambient#api/en/lights/AmbientLight) + [DirectionalLight](https://threejs.org/docs/#api/en/lights/DirectionalLight) (from above) |
| <b>scene</b>() | Access the internal ThreeJS [Scene](https://threejs.org/docs/#api/scenes/Scene). Can be used to extend the current scene with additional objects not related to globe.gl. | |
Expand Down
20 changes: 6 additions & 14 deletions example/satellites/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,21 @@

<script type="module">
import * as satellite from 'https://esm.sh/satellite.js';
import * as THREE from 'https://esm.sh/three';

const EARTH_RADIUS_KM = 6371; // km
const SAT_SIZE = 100; // km
const TIME_STEP = 3 * 1000; // per frame

const timeLogger = document.getElementById('time-log');

const world = new Globe(document.getElementById('chart'))
.globeImageUrl('//unpkg.com/three-globe/example/img/earth-blue-marble.jpg')
.objectLat('lat')
.objectLng('lng')
.objectAltitude('alt')
.objectFacesSurface(false)
.objectLabel('name');
.particleLat('lat')
.particleLng('lng')
.particleAltitude('alt')
.particlesColor(() => 'palegreen');

setTimeout(() => world.pointOfView({ altitude: 3.5 }));

const satGeometry = new THREE.OctahedronGeometry(SAT_SIZE * world.getGlobeRadius() / EARTH_RADIUS_KM / 2, 0);
const satMaterial = new THREE.MeshLambertMaterial({ color: 'palegreen', transparent: true, opacity: 0.7 });
world.objectThreeObject(() => new THREE.Mesh(satGeometry, satMaterial));

fetch('../datasets/space-track-leo.txt').then(r => r.text()).then(rawData => {
const tleData = rawData.replace(/\r/g, '')
.split(/\n(?=[^12])/)
Expand All @@ -57,8 +50,7 @@
name: name.trim().replace(/^0 /, '')
}))
// exclude those that can't be propagated
.filter(d => !!satellite.propagate(d.satrec, new Date()).position)
.slice(0, 2000);
.filter(d => !!satellite.propagate(d.satrec, new Date()).position);

// time ticker
let time = new Date();
Expand All @@ -80,7 +72,7 @@
}
});

world.objectsData(satData);
world.particlesData([satData.filter(d => !isNaN(d.lat) && !isNaN(d.lng) && !isNaN(d.alt))]);
})();
});
</script>
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
"accessor-fn": "1",
"kapsule": "^1.16",
"three": ">=0.154 <1",
"three-globe": "^2.40",
"three-render-objects": "^1.35"
"three-globe": "^2.41",
"three-render-objects": "^1.37"
},
"devDependencies": {
"@babel/core": "^7.26.0",
Expand Down
42 changes: 33 additions & 9 deletions src/globe.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ const linkedGlobeProps = Object.assign(...[
'tileMaterial',
'tileCurvatureResolution',
'tilesTransitionDuration',
'particlesData',
'particlesList',
'particleLat',
'particleLng',
'particleAltitude',
'particlesSize',
'particlesSizeAttenuation',
'particlesColor',
'particlesTexture',
'ringsData',
'ringLat',
'ringLng',
Expand Down Expand Up @@ -224,6 +233,10 @@ export default Kapsule({
onTileClick: { triggerUpdate: false },
onTileRightClick: { triggerUpdate: false },
onTileHover: { triggerUpdate: false },
particleLabel: { default: 'name', triggerUpdate: false },
onParticleClick: { triggerUpdate: false },
onParticleRightClick: { triggerUpdate: false },
onParticleHover: { triggerUpdate: false },
labelLabel: { triggerUpdate: false },
onLabelClick: { triggerUpdate: false },
onLabelRightClick: { triggerUpdate: false },
Expand All @@ -244,7 +257,10 @@ export default Kapsule({
lineHoverPrecision: {
default: 0.2,
triggerUpdate: false,
onChange: (val, state) => state.renderObjs.lineHoverPrecision(val)
onChange: (val, state) => {
state.renderObjs.lineHoverPrecision(val);
state.renderObjs.pointsHoverPrecision(val);
}
},
...linkedGlobeProps,
...linkedRenderObjsProps
Expand Down Expand Up @@ -336,6 +352,7 @@ export default Kapsule({
this.hexBinPointsData([]);
this.hexPolygonsData([]);
this.tilesData([]);
this.particlesData([]);
this.labelsData([]);
this.htmlElementsData([]);
this.objectsData([]);
Expand Down Expand Up @@ -430,6 +447,7 @@ export default Kapsule({
hexbin: d => d,
hexPolygon: d => d,
tile: d => d,
particles: (d, intersection) => !intersection || !intersection.hasOwnProperty('index') || d.length <= intersection.index ? d : d[intersection.index],
label: d => d,
object: d => d,
custom: d => d
Expand All @@ -445,7 +463,7 @@ export default Kapsule({
const isBackground = o => !o; // || o.__globeObjType === 'globe' || o.__globeObjType === 'atmosphere';
return isBackground(aObj) - isBackground(bObj);
})
.tooltipContent(obj => {
.tooltipContent((obj, intersection) => {
const objAccessors = {
point: state.pointLabel,
arc: state.arcLabel,
Expand All @@ -454,6 +472,7 @@ export default Kapsule({
hexbin: state.hexLabel,
hexPolygon: state.hexPolygonLabel,
tile: state.tileLabel,
particles: state.particleLabel,
label: state.labelLabel,
object: state.objectLabel,
custom: state.customLayerLabel
Expand All @@ -463,10 +482,10 @@ export default Kapsule({
const objType = globeObj && globeObj.__globeObjType;

return globeObj && objType && objAccessors.hasOwnProperty(objType) && dataAccessors.hasOwnProperty(objType)
? accessorFn(objAccessors[objType])(dataAccessors[objType](globeObj.__data)) || ''
? accessorFn(objAccessors[objType])(dataAccessors[objType](globeObj.__data, intersection)) || ''
: '';
})
.onHover(obj => {
.onHover((obj, _, intersection) => {
// Update tooltip and trigger onHover events
const hoverObjFns = {
point: state.onPointHover,
Expand All @@ -477,6 +496,7 @@ export default Kapsule({
hexbin: state.onHexHover,
hexPolygon: state.onHexPolygonHover,
tile: state.onTileHover,
particles: state.onParticleHover,
label: state.onLabelHover,
object: state.onObjectHover,
custom: state.onCustomLayerHover
Expand All @@ -492,6 +512,7 @@ export default Kapsule({
hexbin: state.onHexClick,
hexPolygon: state.onHexPolygonClick,
tile: state.onTileClick,
particles: state.onParticleClick,
label: state.onLabelClick,
object: state.onObjectClick,
custom: state.onCustomLayerClick
Expand All @@ -504,12 +525,12 @@ export default Kapsule({

if (hoverObj !== state.hoverObj) {
const prevObjType = state.hoverObj ? state.hoverObj.__globeObjType : null;
const prevObjData = state.hoverObj ? dataAccessors[prevObjType](state.hoverObj.__data) : null;
const prevObjData = state.hoverData;
const objType = hoverObj ? hoverObj.__globeObjType : null;
const objData = hoverObj ? dataAccessors[objType](hoverObj.__data) : null;
const objData = hoverObj ? dataAccessors[objType](hoverObj.__data, intersection) : null;
if (prevObjType && prevObjType !== objType) {
// Hover out
hoverObjFns[prevObjType] && hoverObjFns[prevObjType](null, prevObjData);
hoverObjFns[prevObjType] && hoverObjFns[prevObjType](null, prevObjData || null);
}
if (objType) {
// Hover in
Expand All @@ -520,6 +541,7 @@ export default Kapsule({
state.renderObjs.renderer().domElement.classList[(objType && clickObjFns[objType]) ? 'add' : 'remove']('clickable');

state.hoverObj = hoverObj;
state.hoverData = objData;
}
})
.onClick((obj, ev, intersection) => {
Expand All @@ -536,6 +558,7 @@ export default Kapsule({
hexbin: state.onHexClick,
hexPolygon: state.onHexPolygonClick,
tile: state.onTileClick,
particles: state.onParticleClick,
label: state.onLabelClick,
object: state.onObjectClick,
custom: state.onCustomLayerClick
Expand All @@ -555,7 +578,7 @@ export default Kapsule({
args.push(this.toGeoCoords(point));
}

dataAccessors.hasOwnProperty(objType) && args.unshift(dataAccessors[objType](globeObj.__data));
dataAccessors.hasOwnProperty(objType) && args.unshift(dataAccessors[objType](globeObj.__data, intersection));
objFns[objType](...args);
}
})
Expand All @@ -573,6 +596,7 @@ export default Kapsule({
hexbin: state.onHexRightClick,
hexPolygon: state.onHexPolygonRightClick,
tile: state.onTileRightClick,
particles: state.onParticleRightClick,
label: state.onLabelRightClick,
object: state.onObjectRightClick,
custom: state.onCustomLayerRightClick
Expand All @@ -592,7 +616,7 @@ export default Kapsule({
args.push(this.toGeoCoords(point));
}

dataAccessors.hasOwnProperty(objType) && args.unshift(dataAccessors[objType](globeObj.__data));
dataAccessors.hasOwnProperty(objType) && args.unshift(dataAccessors[objType](globeObj.__data, intersection));
objFns[objType](...args);
}
});
Expand Down
5 changes: 5 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ interface GlobeGenericInstance<ChainableInstance>
hexLabel(textAccessor: Accessor<HexBin, Label>): ChainableInstance;
tileLabel(): ObjAccessor<Label>;
tileLabel(textAccessor: ObjAccessor<Label>): ChainableInstance;
particleLabel(): ObjAccessor<Label>;
particleLabel(textAccessor: ObjAccessor<Label>): ChainableInstance;
labelLabel(): ObjAccessor<Label>;
labelLabel(textAccessor: ObjAccessor<Label>): ChainableInstance;
objectLabel(): ObjAccessor<Label>;
Expand Down Expand Up @@ -91,6 +93,9 @@ interface GlobeGenericInstance<ChainableInstance>
onHexPolygonHover(callback: (polygon: object | null, prevPolygon: object | null) => void): ChainableInstance;
onTileClick(callback: (tile: object, event: MouseEvent, coords: { lat: number, lng: number, altitude: number }) => void): ChainableInstance;
onTileRightClick(callback: (tile: object, event: MouseEvent, coords: { lat: number, lng: number, altitude: number }) => void): ChainableInstance;
onParticleHover(callback: (particle: object | null, prevParticle: object | null) => void): ChainableInstance;
onParticleClick(callback: (particle: object, event: MouseEvent, coords: { lat: number, lng: number, altitude: number }) => void): ChainableInstance;
onParticleRightClick(callback: (particle: object, event: MouseEvent, coords: { lat: number, lng: number, altitude: number }) => void): ChainableInstance;
onTileHover(callback: (tile: object | null, prevTile: object | null) => void): ChainableInstance;
onLabelClick(callback: (label: object, event: MouseEvent, coords: { lat: number, lng: number, altitude: number }) => void): ChainableInstance;
onLabelRightClick(callback: (label: object, event: MouseEvent, coords: { lat: number, lng: number, altitude: number }) => void): ChainableInstance;
Expand Down
Loading

0 comments on commit 32942a6

Please sign in to comment.