Skip to content

Instantly share code, notes, and snippets.

@bwswedberg
Last active January 1, 2020 08:50
Show Gist options
  • Save bwswedberg/29bda412413335b705c434e8a0af1f50 to your computer and use it in GitHub Desktop.
Save bwswedberg/29bda412413335b705c434e8a0af1f50 to your computer and use it in GitHub Desktop.
Satellite Map Using Three.js with Zoom and Pan
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<style>
#map {
position: relative;
cursor: pointer;
}
.map__date-time {
position: absolute;
right: 10px;
bottom: 10px;
font-family: serif;
font-size: 14px;
z-index: 100;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8.1/dist/polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3.0/dist/fetch.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js"></script>
<script src="three-orbit-controls.js"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/satellite.js/3.0.0/satellite.min.js"></script>
</head>
<body>
<div id="map">
<div id="date-time" class="map__date-time"></div>
</div>
<script src="index.js"></script>
</body>
</html>
(function(THREE, OrbitControls, d3, topojson, satellite) {
/* =============================================== */
/* =============== CLOCK ========================= */
/* =============================================== */
/**
* Factory function for keeping track of elapsed time and rates.
*/
function clock() {
var rate = 60; // 1ms elapsed : 60sec simulated
var date = d3.now();
var elapsed = 0;
function clock() {}
clock.date = function(timeInMs) {
if (!arguments.length) return date + (elapsed * rate);
date = timeInMs;
return clock;
}
clock.elapsed = function(ms) {
if (!arguments.length) return date - d3.now(); // calculates elapsed
elapsed = ms;
return clock;
}
clock.rate = function(secondsPerMsElapsed) {
if (!arguments.length) return rate;
rate = secondsPerMsElapsed;
return clock;
}
return clock;
}
/* ==================================================== */
/* =============== CONVERSION ========================= */
/* ==================================================== */
function radiansToDegrees(radians) {
return radians * 180 / Math.PI;
}
function satrecToFeature(satrec, date, props) {
var properties = props || {};
var positionAndVelocity = satellite.propagate(satrec, date);
var gmst = satellite.gstime(date);
var positionGd = satellite.eciToGeodetic(positionAndVelocity.position, gmst);
properties.height = positionGd.height;
return {
type: 'Feature',
properties: properties,
geometry: {
type: 'Point',
coordinates: [
radiansToDegrees(positionGd.longitude),
radiansToDegrees(positionGd.latitude)
]
}
};
}
function satrecToXYZ(satrec, date) {
var positionAndVelocity = satellite.propagate(satrec, date);
var gmst = satellite.gstime(date);
var positionGd = satellite.eciToGeodetic(positionAndVelocity.position, gmst);
return [positionGd.longitude, positionGd.latitude, positionGd.height];
}
/* ==================================================== */
/* =============== TLE ================================ */
/* ==================================================== */
/**
* Factory function for working with TLE.
*/
function tle() {
var _properties;
var _date;
var _lines = function (arry) {
return arry.slice(0, 2);
};
function tle() {}
tle.satrecs = function (tles) {
return tles.map(function(d) {
return satellite.twoline2satrec.apply(null, _lines(d));
});
}
tle.features = function (tles) {
var date = _date || d3.now();
return tles.map(function(d) {
var satrec = satellite.twoline2satrec.apply(null, _lines(d));
return satrecToFeature(satrec, date, _properties(d));
});
}
tle.lines = function (func) {
if (!arguments.length) return _lines;
_lines = func;
return tle;
}
tle.properties = function (func) {
if (!arguments.length) return _properties;
_properties = func;
return tle;
}
tle.date = function (ms) {
if (!arguments.length) return _date;
_date = ms;
return tle;
}
return tle;
}
/* ==================================================== */
/* =============== PARSE ============================== */
/* ==================================================== */
/**
* Parses text file string of tle into groups.
* @return {string[][]} Like [['tle line 1', 'tle line 2'], ...]
*/
function parseTle(tleString) {
// remove last newline so that we can properly split all the lines
var lines = tleString.replace(/\r?\n$/g, '').split(/\r?\n/);
return lines.reduce(function(acc, cur, index) {
if (index % 2 === 0) acc.push([]);
acc[acc.length - 1].push(cur);
return acc;
}, []);
}
/* =============================================== */
/* =============== THREE MAP ===================== */
/* =============================================== */
// Approximate date the tle data was aquired from https://www.space-track.org/#recent
var TLE_DATA_DATE = new Date(2018, 0, 26).getTime();
var width = 960,
height = 500,
radius = 228,
scene = new THREE.Scene,
camera = new THREE.PerspectiveCamera(70, width / height, 1, 10000),
renderer = new THREE.WebGLRenderer({alpha: true}),
controls = new (OrbitControls(THREE))(camera, renderer.domElement),
dateElement = document.getElementById('date-time'),
satrecs,
satellites,
graticule,
countries,
activeClock ;
function init(parsedTles, topology) {
satrecs = tle()
.date(TLE_DATA_DATE)
.satrecs(parsedTles);
activeClock = clock()
.rate(1000)
.date(TLE_DATA_DATE);
camera.position.z = 1000;
camera.position.x = -200;
camera.position.y = 500;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
document.getElementById('map').appendChild(renderer.domElement);
var sphere = new THREE.Mesh(new THREE.SphereGeometry(radius - 0.5, 128, 128), new THREE.MeshBasicMaterial({color: 0xffffff}));
scene.add(sphere);
graticule = wireframe(graticule10(), new THREE.LineBasicMaterial({color: 0xaaaaaa}));
scene.add(graticule);
countries = wireframe(topojson.mesh(topology, topology.objects.land), new THREE.LineBasicMaterial({color: 0xff0000}));
scene.add(countries);
var satGeometry = new THREE.Geometry();
var date = new Date(activeClock .date());
satGeometry.vertices = satrecs.map(function(satrec) {return satelliteVector(satrec, date);});
satellites = new THREE.Points(satGeometry, new THREE.PointsMaterial( { color: 0x000000, size: 20 } ));
scene.add(satellites);
// Rotates 90 degrees. Must convert to radians.
graticule.rotation.x = countries.rotation.x = satellites.rotation.x = -90 / 180 * Math.PI;
d3.timer(animate);
}
function animate(t) {
var date = new Date(activeClock .elapsed(t).date());
for (let i = 0; i < satrecs.length; i++) {
satellites.geometry.vertices[i] = satelliteVector(satrecs[i], date);
}
satellites.geometry.verticesNeedUpdate = true;
dateElement.textContent = date;
controls.update();
renderer.render(scene, camera);
}
// Converts a point [longitude, latitude] in degrees to a THREE.Vector3.
function vertex(point) {
var lambda = point[0] * Math.PI / 180,
phi = point[1] * Math.PI / 180,
cosPhi = Math.cos(phi);
return new THREE.Vector3(
radius * cosPhi * Math.cos(lambda),
radius * cosPhi * Math.sin(lambda),
radius * Math.sin(phi)
);
}
function satelliteVector(satrec, date) {
var xyz = satrecToXYZ(satrec, date);
var lambda = xyz[0];
var phi = xyz[1];
var cosPhi = Math.cos(phi);
var r = ((xyz[2] + 6371) / 6371) * 228;
return new THREE.Vector3(
r * cosPhi * Math.cos(lambda),
r * cosPhi * Math.sin(lambda),
r * Math.sin(phi)
);
}
// Converts a GeoJSON MultiLineString in spherical coordinates to a THREE.LineSegments.
function wireframe(multilinestring, material) {
var geometry = new THREE.Geometry;
multilinestring.coordinates.forEach(function(line) {
d3.pairs(line.map(vertex), function(a, b) {
geometry.vertices.push(a, b);
});
});
return new THREE.LineSegments(geometry, material);
}
// See https://github.com/d3/d3-geo/issues/95
function graticule10() {
var epsilon = 1e-6,
x1 = 180, x0 = -x1, y1 = 80, y0 = -y1, dx = 10, dy = 10,
X1 = 180, X0 = -X1, Y1 = 90, Y0 = -Y1, DX = 90, DY = 360,
x = graticuleX(y0, y1, 2.5), y = graticuleY(x0, x1, 2.5),
X = graticuleX(Y0, Y1, 2.5), Y = graticuleY(X0, X1, 2.5);
function graticuleX(y0, y1, dy) {
var y = d3.range(y0, y1 - epsilon, dy).concat(y1);
return function(x) { return y.map(function(y) { return [x, y]; }); };
}
function graticuleY(x0, x1, dx) {
var x = d3.range(x0, x1 - epsilon, dx).concat(x1);
return function(y) { return x.map(function(x) { return [x, y]; }); };
}
return {
type: 'MultiLineString',
coordinates: d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X)
.concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y))
.concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) { return Math.abs(x % DX) > epsilon; }).map(x))
.concat(d3.range(Math.ceil(y0 / dy) * dy, y1 + epsilon, dy).filter(function(y) { return Math.abs(y % DY) > epsilon; }).map(y))
};
}
Promise.all([
d3.text('tles.txt'),
d3.json('https://unpkg.com/world-atlas@1/world/110m.json')
]).then(function(results) {
init(parseTle(results[0]), results[1]);
});
}(window.THREE, window.OrbitControls, window.d3, window.topojson, window.satellite))
(function() {
window.OrbitControls = function( THREE ) {
/**
* @author qiao / https://github.com/qiao
* @author mrdoob / http://mrdoob.com
* @author alteredq / http://alteredqualia.com/
* @author WestLangley / http://github.com/WestLangley
* @author erich666 / http://erichaines.com
*/
// This set of controls performs orbiting, dollying (zooming), and panning.
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
//
// Orbit - left mouse / touch: one finger move
// Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
// Pan - right mouse, or arrow keys / touch: three finter swipe
function OrbitControls( object, domElement ) {
this.object = object;
this.domElement = ( domElement !== undefined ) ? domElement : document;
// Set to false to disable this control
this.enabled = true;
// "target" sets the location of focus, where the object orbits around
this.target = new THREE.Vector3();
// How far you can dolly in and out ( PerspectiveCamera only )
this.minDistance = 0;
this.maxDistance = Infinity;
// How far you can zoom in and out ( OrthographicCamera only )
this.minZoom = 0;
this.maxZoom = Infinity;
// How far you can orbit vertically, upper and lower limits.
// Range is 0 to Math.PI radians.
this.minPolarAngle = 0; // radians
this.maxPolarAngle = Math.PI; // radians
// How far you can orbit horizontally, upper and lower limits.
// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
this.minAzimuthAngle = - Infinity; // radians
this.maxAzimuthAngle = Infinity; // radians
// Set to true to enable damping (inertia)
// If damping is enabled, you must call controls.update() in your animation loop
this.enableDamping = false;
this.dampingFactor = 0.25;
// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
// Set to false to disable zooming
this.enableZoom = true;
this.zoomSpeed = 1.0;
// Set to false to disable rotating
this.enableRotate = true;
this.rotateSpeed = 1.0;
// Set to false to disable panning
this.enablePan = true;
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
// Set to true to automatically rotate around the target
// If auto-rotate is enabled, you must call controls.update() in your animation loop
this.autoRotate = false;
this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
// Set to false to disable use of the keys
this.enableKeys = true;
// The four arrow keys
this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
// Mouse buttons
this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.zoom0 = this.object.zoom;
//
// public methods
//
this.getPolarAngle = function () {
return spherical.phi;
};
this.getAzimuthalAngle = function () {
return spherical.theta;
};
this.reset = function () {
scope.target.copy( scope.target0 );
scope.object.position.copy( scope.position0 );
scope.object.zoom = scope.zoom0;
scope.object.updateProjectionMatrix();
scope.dispatchEvent( changeEvent );
scope.update();
state = STATE.NONE;
};
// this method is exposed, but perhaps it would be better if we can make it private...
this.update = function() {
var offset = new THREE.Vector3();
// so camera.up is the orbit axis
var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
var quatInverse = quat.clone().inverse();
var lastPosition = new THREE.Vector3();
var lastQuaternion = new THREE.Quaternion();
return function update () {
var position = scope.object.position;
offset.copy( position ).sub( scope.target );
// rotate offset to "y-axis-is-up" space
offset.applyQuaternion( quat );
// angle from z-axis around y-axis
spherical.setFromVector3( offset );
if ( scope.autoRotate && state === STATE.NONE ) {
rotateLeft( getAutoRotationAngle() );
}
spherical.theta += sphericalDelta.theta;
spherical.phi += sphericalDelta.phi;
// restrict theta to be between desired limits
spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
// restrict phi to be between desired limits
spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
spherical.makeSafe();
spherical.radius *= scale;
// restrict radius to be between desired limits
spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
// move target to panned location
scope.target.add( panOffset );
offset.setFromSpherical( spherical );
// rotate offset back to "camera-up-vector-is-up" space
offset.applyQuaternion( quatInverse );
position.copy( scope.target ).add( offset );
scope.object.lookAt( scope.target );
if ( scope.enableDamping === true ) {
sphericalDelta.theta *= ( 1 - scope.dampingFactor );
sphericalDelta.phi *= ( 1 - scope.dampingFactor );
} else {
sphericalDelta.set( 0, 0, 0 );
}
scale = 1;
panOffset.set( 0, 0, 0 );
// update condition is:
// min(camera displacement, camera rotation in radians)^2 > EPS
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
if ( zoomChanged ||
lastPosition.distanceToSquared( scope.object.position ) > EPS ||
8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
scope.dispatchEvent( changeEvent );
lastPosition.copy( scope.object.position );
lastQuaternion.copy( scope.object.quaternion );
zoomChanged = false;
return true;
}
return false;
};
}();
this.dispose = function() {
scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
document.removeEventListener( 'mousemove', onMouseMove, false );
document.removeEventListener( 'mouseup', onMouseUp, false );
window.removeEventListener( 'keydown', onKeyDown, false );
//scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
};
//
// internals
//
var scope = this;
var changeEvent = { type: 'change' };
var startEvent = { type: 'start' };
var endEvent = { type: 'end' };
var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
var state = STATE.NONE;
var EPS = 0.000001;
// current position in spherical coordinates
var spherical = new THREE.Spherical();
var sphericalDelta = new THREE.Spherical();
var scale = 1;
var panOffset = new THREE.Vector3();
var zoomChanged = false;
var rotateStart = new THREE.Vector2();
var rotateEnd = new THREE.Vector2();
var rotateDelta = new THREE.Vector2();
var panStart = new THREE.Vector2();
var panEnd = new THREE.Vector2();
var panDelta = new THREE.Vector2();
var dollyStart = new THREE.Vector2();
var dollyEnd = new THREE.Vector2();
var dollyDelta = new THREE.Vector2();
function getAutoRotationAngle() {
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
}
function getZoomScale() {
return Math.pow( 0.95, scope.zoomSpeed );
}
function rotateLeft( angle ) {
sphericalDelta.theta -= angle;
}
function rotateUp( angle ) {
sphericalDelta.phi -= angle;
}
var panLeft = function() {
var v = new THREE.Vector3();
return function panLeft( distance, objectMatrix ) {
v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
v.multiplyScalar( - distance );
panOffset.add( v );
};
}();
var panUp = function() {
var v = new THREE.Vector3();
return function panUp( distance, objectMatrix ) {
v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix
v.multiplyScalar( distance );
panOffset.add( v );
};
}();
// deltaX and deltaY are in pixels; right and down are positive
var pan = function() {
var offset = new THREE.Vector3();
return function pan ( deltaX, deltaY ) {
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
if ( scope.object instanceof THREE.PerspectiveCamera ) {
// perspective
var position = scope.object.position;
offset.copy( position ).sub( scope.target );
var targetDistance = offset.length();
// half of the fov is center to top of screen
targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
// we actually don't use screenWidth, since perspective camera is fixed to screen height
panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
} else if ( scope.object instanceof THREE.OrthographicCamera ) {
// orthographic
panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
} else {
// camera neither orthographic nor perspective
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
scope.enablePan = false;
}
};
}();
function dollyIn( dollyScale ) {
if ( scope.object instanceof THREE.PerspectiveCamera ) {
scale /= dollyScale;
} else if ( scope.object instanceof THREE.OrthographicCamera ) {
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
scope.object.updateProjectionMatrix();
zoomChanged = true;
} else {
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
scope.enableZoom = false;
}
}
function dollyOut( dollyScale ) {
if ( scope.object instanceof THREE.PerspectiveCamera ) {
scale *= dollyScale;
} else if ( scope.object instanceof THREE.OrthographicCamera ) {
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
scope.object.updateProjectionMatrix();
zoomChanged = true;
} else {
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
scope.enableZoom = false;
}
}
//
// event callbacks - update the object state
//
function handleMouseDownRotate( event ) {
//console.log( 'handleMouseDownRotate' );
rotateStart.set( event.clientX, event.clientY );
}
function handleMouseDownDolly( event ) {
//console.log( 'handleMouseDownDolly' );
dollyStart.set( event.clientX, event.clientY );
}
function handleMouseDownPan( event ) {
//console.log( 'handleMouseDownPan' );
panStart.set( event.clientX, event.clientY );
}
function handleMouseMoveRotate( event ) {
//console.log( 'handleMouseMoveRotate' );
rotateEnd.set( event.clientX, event.clientY );
rotateDelta.subVectors( rotateEnd, rotateStart );
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
// rotating across whole screen goes 360 degrees around
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
// rotating up and down along whole screen attempts to go 360, but limited to 180
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
rotateStart.copy( rotateEnd );
scope.update();
}
function handleMouseMoveDolly( event ) {
//console.log( 'handleMouseMoveDolly' );
dollyEnd.set( event.clientX, event.clientY );
dollyDelta.subVectors( dollyEnd, dollyStart );
if ( dollyDelta.y > 0 ) {
dollyIn( getZoomScale() );
} else if ( dollyDelta.y < 0 ) {
dollyOut( getZoomScale() );
}
dollyStart.copy( dollyEnd );
scope.update();
}
function handleMouseMovePan( event ) {
//console.log( 'handleMouseMovePan' );
panEnd.set( event.clientX, event.clientY );
panDelta.subVectors( panEnd, panStart );
pan( panDelta.x, panDelta.y );
panStart.copy( panEnd );
scope.update();
}
function handleMouseUp( event ) {
//console.log( 'handleMouseUp' );
}
function handleMouseWheel( event ) {
//console.log( 'handleMouseWheel' );
if ( event.deltaY < 0 ) {
dollyOut( getZoomScale() );
} else if ( event.deltaY > 0 ) {
dollyIn( getZoomScale() );
}
scope.update();
}
function handleKeyDown( event ) {
//console.log( 'handleKeyDown' );
switch ( event.keyCode ) {
case scope.keys.UP:
pan( 0, scope.keyPanSpeed );
scope.update();
break;
case scope.keys.BOTTOM:
pan( 0, - scope.keyPanSpeed );
scope.update();
break;
case scope.keys.LEFT:
pan( scope.keyPanSpeed, 0 );
scope.update();
break;
case scope.keys.RIGHT:
pan( - scope.keyPanSpeed, 0 );
scope.update();
break;
}
}
function handleTouchStartRotate( event ) {
//console.log( 'handleTouchStartRotate' );
rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
}
function handleTouchStartDolly( event ) {
//console.log( 'handleTouchStartDolly' );
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
var distance = Math.sqrt( dx * dx + dy * dy );
dollyStart.set( 0, distance );
}
function handleTouchStartPan( event ) {
//console.log( 'handleTouchStartPan' );
panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
}
function handleTouchMoveRotate( event ) {
//console.log( 'handleTouchMoveRotate' );
rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
rotateDelta.subVectors( rotateEnd, rotateStart );
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
// rotating across whole screen goes 360 degrees around
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
// rotating up and down along whole screen attempts to go 360, but limited to 180
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
rotateStart.copy( rotateEnd );
scope.update();
}
function handleTouchMoveDolly( event ) {
//console.log( 'handleTouchMoveDolly' );
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
var distance = Math.sqrt( dx * dx + dy * dy );
dollyEnd.set( 0, distance );
dollyDelta.subVectors( dollyEnd, dollyStart );
if ( dollyDelta.y > 0 ) {
dollyOut( getZoomScale() );
} else if ( dollyDelta.y < 0 ) {
dollyIn( getZoomScale() );
}
dollyStart.copy( dollyEnd );
scope.update();
}
function handleTouchMovePan( event ) {
//console.log( 'handleTouchMovePan' );
panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
panDelta.subVectors( panEnd, panStart );
pan( panDelta.x, panDelta.y );
panStart.copy( panEnd );
scope.update();
}
function handleTouchEnd( event ) {
//console.log( 'handleTouchEnd' );
}
//
// event handlers - FSM: listen for events and reset state
//
function onMouseDown( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
if ( event.button === scope.mouseButtons.ORBIT ) {
if ( scope.enableRotate === false ) return;
handleMouseDownRotate( event );
state = STATE.ROTATE;
} else if ( event.button === scope.mouseButtons.ZOOM ) {
if ( scope.enableZoom === false ) return;
handleMouseDownDolly( event );
state = STATE.DOLLY;
} else if ( event.button === scope.mouseButtons.PAN ) {
if ( scope.enablePan === false ) return;
handleMouseDownPan( event );
state = STATE.PAN;
}
if ( state !== STATE.NONE ) {
document.addEventListener( 'mousemove', onMouseMove, false );
document.addEventListener( 'mouseup', onMouseUp, false );
scope.dispatchEvent( startEvent );
}
}
function onMouseMove( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
if ( state === STATE.ROTATE ) {
if ( scope.enableRotate === false ) return;
handleMouseMoveRotate( event );
} else if ( state === STATE.DOLLY ) {
if ( scope.enableZoom === false ) return;
handleMouseMoveDolly( event );
} else if ( state === STATE.PAN ) {
if ( scope.enablePan === false ) return;
handleMouseMovePan( event );
}
}
function onMouseUp( event ) {
if ( scope.enabled === false ) return;
handleMouseUp( event );
document.removeEventListener( 'mousemove', onMouseMove, false );
document.removeEventListener( 'mouseup', onMouseUp, false );
scope.dispatchEvent( endEvent );
state = STATE.NONE;
}
function onMouseWheel( event ) {
if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
event.preventDefault();
event.stopPropagation();
handleMouseWheel( event );
scope.dispatchEvent( startEvent ); // not sure why these are here...
scope.dispatchEvent( endEvent );
}
function onKeyDown( event ) {
if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
handleKeyDown( event );
}
function onTouchStart( event ) {
if ( scope.enabled === false ) return;
switch ( event.touches.length ) {
case 1: // one-fingered touch: rotate
if ( scope.enableRotate === false ) return;
handleTouchStartRotate( event );
state = STATE.TOUCH_ROTATE;
break;
case 2: // two-fingered touch: dolly
if ( scope.enableZoom === false ) return;
handleTouchStartDolly( event );
state = STATE.TOUCH_DOLLY;
break;
case 3: // three-fingered touch: pan
if ( scope.enablePan === false ) return;
handleTouchStartPan( event );
state = STATE.TOUCH_PAN;
break;
default:
state = STATE.NONE;
}
if ( state !== STATE.NONE ) {
scope.dispatchEvent( startEvent );
}
}
function onTouchMove( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
switch ( event.touches.length ) {
case 1: // one-fingered touch: rotate
if ( scope.enableRotate === false ) return;
if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?...
handleTouchMoveRotate( event );
break;
case 2: // two-fingered touch: dolly
if ( scope.enableZoom === false ) return;
if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?...
handleTouchMoveDolly( event );
break;
case 3: // three-fingered touch: pan
if ( scope.enablePan === false ) return;
if ( state !== STATE.TOUCH_PAN ) return; // is this needed?...
handleTouchMovePan( event );
break;
default:
state = STATE.NONE;
}
}
function onTouchEnd( event ) {
if ( scope.enabled === false ) return;
handleTouchEnd( event );
scope.dispatchEvent( endEvent );
state = STATE.NONE;
}
function onContextMenu( event ) {
event.preventDefault();
}
//
scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
window.addEventListener( 'keydown', onKeyDown, false );
// force an update at start
this.update();
};
OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
OrbitControls.prototype.constructor = OrbitControls;
Object.defineProperties( OrbitControls.prototype, {
center: {
get: function () {
console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
return this.target;
}
},
// backward compatibility
noZoom: {
get: function () {
console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
return ! this.enableZoom;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
this.enableZoom = ! value;
}
},
noRotate: {
get: function () {
console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
return ! this.enableRotate;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
this.enableRotate = ! value;
}
},
noPan: {
get: function () {
console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
return ! this.enablePan;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
this.enablePan = ! value;
}
},
noKeys: {
get: function () {
console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
return ! this.enableKeys;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
this.enableKeys = ! value;
}
},
staticMoving : {
get: function () {
console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
return ! this.enableDamping;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
this.enableDamping = ! value;
}
},
dynamicDampingFactor : {
get: function () {
console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
return this.dampingFactor;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
this.dampingFactor = value;
}
}
} );
return OrbitControls;
};
}());
1 13367U 82072A 19025.67382660 .00000448 00000-0 26967-4 0 9994
2 13367 98.2368 249.8468 0013264 8.7955 32.7108 15.16610100974056
1 14780U 84021A 19025.86363710 .00000116 00000-0 16831-4 0 9997
2 14780 98.0997 161.4728 0091105 36.1375 324.5953 14.91839885863480
1 23327U 94069A 19026.00535573 -.00000103 00000-0 00000+0 0 9995
2 23327 15.0768 11.9671 0006229 236.5550 311.7352 1.00256785 88794
1 23435U 94084A 19024.83932884 -.00000312 +00000-0 +00000-0 0 9990
2 23435 014.4382 016.6476 0000815 332.2555 027.7724 01.00274534005886
1 23522U 95011B 19024.67560573 -.00000079 +00000-0 +00000-0 0 9992
2 23522 013.0828 024.6289 0004086 030.5208 140.5982 00.99408123086756
1 24737U 97008A 19025.69976709 .00000015 00000-0 00000+0 0 9992
2 24737 13.2809 24.2633 0000764 351.4157 8.6214 1.00276141 4547
1 24883U 97037A 19025.92104745 .00000048 00000-0 33196-4 0 9998
2 24883 98.5737 137.9339 0002915 147.7438 212.3925 14.33487628136114
1 25682U 99020A 19026.06863801 -.00000075 00000-0 -66221-5 0 9999
2 25682 98.1462 96.7829 0001514 66.1136 294.0226 14.57146428 52206
1 26356U 00024A 19025.19833423 .00000086 00000-0 00000+0 0 9991
2 26356 11.1219 30.2370 0001922 243.8369 116.1978 1.00260483 4532
1 26382U 00032A 19024.86851399 -.00000268 +00000-0 +00000-0 0 9998
2 26382 010.6166 035.5053 0006959 274.2378 095.2784 01.00100162067904
1 26880U 01033A 19025.00737590 -.00000229 00000-0 00000+0 0 9992
2 26880 10.2805 33.8794 0001808 264.1991 95.8416 1.00565296 4545
1 27424U 02022A 19025.84727490 .00000068 00000-0 25207-4 0 9997
2 27424 98.2200 328.7722 0002723 100.8342 352.8074 14.57105658889854
1 27431U 02024B 19026.09716593 .00000072 00000-0 67455-4 0 9993
2 27431 99.1030 12.3417 0014560 240.7019 119.2696 14.09668664859239
1 27509U 02040B 19024.69653201 +.00000124 +00000-0 +00000-0 0 9994
2 27509 005.8452 056.1597 0001724 293.7646 066.2974 01.00260509060185
1 27640U 03001A 19025.89096844 -.00000021 00000-0 10909-4 0 9995
2 27640 98.7351 37.8806 0015021 61.0452 1.9643 14.18999185831294
1 28158U 04004A 19024.53176755 -.00000230 +00000-0 +00000-0 0 9993
2 28158 008.5150 042.1647 0000433 351.4919 008.5823 01.00275577005859
1 28451U 04042A 19024.90253603 -.00000199 +00000-0 +00000-0 0 9994
2 28451 008.2492 047.2507 0008246 287.9961 252.7179 00.98084409014688
1 28622U 05006A 19024.93728510 -.00000212 +00000-0 +00000-0 0 9998
2 28622 003.0450 080.3576 0020170 232.3197 100.3733 00.98913816017303
1 35491U 09033A 19025.94215816 -.00000107 00000-0 00000+0 0 9998
2 35491 0.0497 263.7297 0010165 338.7681 117.3692 1.00270541 35095
1 35865U 09049A 19026.07608278 .00000016 00000-0 25779-4 0 9996
2 35865 98.4084 30.8150 0001574 208.8813 151.2279 14.22181546485666
1 36585U 10022A 19025.89089603 -.00000068 00000-0 00000+0 0 9996
2 36585 55.6744 317.2611 0079151 47.6762 128.7805 2.00553159 63444
1 37849U 11061A 19025.85736228 .00000007 00000-0 23947-4 0 9994
2 37849 98.7234 326.0892 0000565 70.9996 354.7392 14.19555238375463
1 38552U 12035B 19024.63072767 +.00000048 +00000-0 +00000-0 0 9990
2 38552 000.9893 007.1089 0001363 293.7860 059.0647 01.00274915023811
1 38771U 12049A 19025.86637133 -.00000017 00000-0 12164-4 0 9993
2 38771 98.7252 87.0576 0001546 57.9352 30.5283 14.21479598329769
1 38833U 12053A 19023.02160365 .00000037 00000-0 00000+0 0 9993
2 38833 53.8355 254.4252 0079636 32.0767 328.4833 2.00556310 46144
1 39166U 13023A 19024.88023431 -.00000069 +00000-0 +00000-0 0 9995
2 39166 056.0598 016.9330 0064614 022.3880 337.9012 02.00568602041724
1 39260U 13052A 19026.16721979 -.00000003 00000-0 20289-4 0 9990
2 39260 98.6256 89.8045 0010154 232.1561 127.8704 14.15859509276699
1 39533U 14008A 19024.37829364 +.00000016 +00000-0 +00000-0 0 9991
2 39533 053.9711 259.7478 0036433 190.6262 169.3678 02.00567695035534
1 39741U 14026A 19024.16378102 -.00000041 00000-0 00000+0 0 9996
2 39741 55.7949 76.9733 0016044 288.6136 71.2806 2.00551076 34343
1 40105U 14045A 19024.74875596 +.00000061 +00000-0 +00000-0 0 9998
2 40105 054.5792 196.3261 0012539 098.7053 261.4161 02.00554590031942
1 40267U 14060A 19025.26401613 -.00000289 00000-0 00000+0 0 9999
2 40267 0.0201 68.4083 0001415 265.0675 26.5170 1.00270026 15796
1 40294U 14068A 19024.12109425 +.00000009 +00000-0 +00000-0 0 9997
2 40294 055.1331 137.1864 0017513 032.1169 327.9794 02.00572545031036
1 40534U 15013A 19024.76324260 -.00000059 +00000-0 +00000-0 0 9997
2 40534 054.6078 315.7983 0035536 002.2241 357.8177 02.00550117027667
1 40730U 15033A 19024.41704627 -.00000070 +00000-0 +00000-0 0 9991
2 40730 055.5866 016.3563 0042635 335.7673 024.0406 02.00562368025846
1 40732U 15034A 19024.65436339 -.00000008 +00000-0 +00000-0 0 9993
2 40732 000.9909 229.8338 0001690 110.3177 019.8112 01.00282602012967
1 41866U 16071A 19025.86201983 -.00000262 00000-0 00000+0 0 9994
2 41866 0.0169 116.2286 0000618 233.8853 9.8553 1.00271331 8046
1 43013U 17073A 19025.95010176 .00000007 00000-0 24241-4 0 9999
2 43013 98.7491 326.2096 0001018 62.2366 297.8913 14.19537735 61511
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment