Skip to content

Instantly share code, notes, and snippets.

@brett-miller
Forked from marcopompili/TrackballControls.js
Created April 17, 2017 18:49
Show Gist options
  • Save brett-miller/9e706e56f0087715457827b7c38c08b2 to your computer and use it in GitHub Desktop.
Save brett-miller/9e706e56f0087715457827b7c38c08b2 to your computer and use it in GitHub Desktop.
GeoJSON Test
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ThreeGeoJSON polygons</title>
<style media="screen">
html, body {
margin: 0; padding: 0
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r82/three.js" charset="utf-8"></script>
<script src="threeGeoJSON.js" charset="utf-8"></script>
<script src="TrackballControls.js" charset="utf-8"></script>
</head>
<body>
<script type="text/javascript">
var width = window.innerWidth;
var height = window.innerHeight;
// Earth params
var radius = 9.99;
var segments = 32;
var rotation = 0;
//New scene and camera
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(55, width / height, 0.01, 1000);
camera.position.z = 1;
camera.position.x = -.2;
camera.position.y = .5;
//New Renderer
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
var canvas = renderer.domElement;
canvas.style.display = "block";
document.body.appendChild(canvas);
//Add lighting
scene.add(new THREE.AmbientLight(0x333333));
var light = new THREE.DirectionalLight(0xe4eef9, .7);
light.position.set(12, 12, 8);
scene.add(light);
//Create a sphere to make visualization easier.
var geometry = new THREE.SphereGeometry(10, 32, 32);
var material = new THREE.MeshPhongMaterial({
//wireframe: true,
//transparent: true
});
//Draw the GeoJSON
var test_json = $.getJSON("countries_states.geojson", function(data) {
drawThreeGeo(data, 10, 'sphere', {
color: 'red'
})
});
//Set the camera position
camera.position.z = 30;
//Enable controls
var controls = new THREE.TrackballControls(camera);
//Render the image
function render() {
controls.update();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
</script>
</body>
</html>
/* Draw GeoJSON
Iterates through the latitude and longitude values, converts the values to XYZ coordinates,
and draws the geoJSON geometries.
*/
var x_values = [];
var y_values = [];
var z_values = [];
function drawThreeGeo(json, radius, shape, options) {
var json_geom = createGeometryArray(json);
//An array to hold the feature geometries.
var convertCoordinates = getConversionFunctionName(shape);
//Whether you want to convert to spherical or planar coordinates.
var coordinate_array = [];
//Re-usable array to hold coordinate values. This is necessary so that you can add
//interpolated coordinates. Otherwise, lines go through the sphere instead of wrapping around.
for (var geom_num = 0; geom_num < json_geom.length; geom_num++) {
if (json_geom[geom_num].type == 'Point') {
convertCoordinates(json_geom[geom_num].coordinates, radius);
drawParticle(y_values[0], z_values[0], x_values[0], options);
} else if (json_geom[geom_num].type == 'MultiPoint') {
for (var point_num = 0; point_num < json_geom[geom_num].coordinates.length; point_num++) {
convertCoordinates(json_geom[geom_num].coordinates[point_num], radius);
drawParticle(y_values[0], z_values[0], x_values[0], options);
}
} else if (json_geom[geom_num].type == 'LineString') {
coordinate_array = createCoordinateArray(json_geom[geom_num].coordinates);
for (var point_num = 0; point_num < coordinate_array.length; point_num++) {
convertCoordinates(coordinate_array[point_num], radius);
}
drawLine(y_values, z_values, x_values, options);
} else if (json_geom[geom_num].type == 'Polygon') {
for (var segment_num = 0; segment_num < json_geom[geom_num].coordinates.length; segment_num++) {
coordinate_array = createCoordinateArray(json_geom[geom_num].coordinates[
segment_num]);
for (var point_num = 0; point_num < coordinate_array.length; point_num++) {
convertCoordinates(coordinate_array[point_num], radius);
}
drawLine(y_values, z_values, x_values, options);
}
} else if (json_geom[geom_num].type == 'MultiLineString') {
for (var segment_num = 0; segment_num < json_geom[geom_num].coordinates.length; segment_num++) {
coordinate_array = createCoordinateArray(json_geom[geom_num].coordinates[
segment_num]);
for (var point_num = 0; point_num < coordinate_array.length; point_num++) {
convertCoordinates(coordinate_array[point_num], radius);
}
drawLine(y_values, z_values, x_values, options);
}
} else if (json_geom[geom_num].type == 'MultiPolygon') {
for (var polygon_num = 0; polygon_num < json_geom[geom_num].coordinates.length; polygon_num++) {
for (var segment_num = 0; segment_num < json_geom[geom_num].coordinates[
polygon_num].length; segment_num++) {
coordinate_array = createCoordinateArray(json_geom[geom_num].coordinates[
polygon_num][segment_num]);
for (var point_num = 0; point_num < coordinate_array.length; point_num++) {
convertCoordinates(coordinate_array[point_num], radius);
}
drawLine(y_values, z_values, x_values, options);
}
}
} else {
throw new Error('The geoJSON is not valid.');
}
}
}
function createGeometryArray(json) {
var geometry_array = [];
if (json.type == 'Feature') {
geometry_array.push(json.geometry);
} else if (json.type == 'FeatureCollection') {
for (var feature_num = 0; feature_num < json.features.length; feature_num++) {
geometry_array.push(json.features[feature_num].geometry);
}
} else if (json.type == 'GeometryCollection') {
for (var geom_num = 0; geom_num < json.geometries.length; geom_num++) {
geometry_array.push(json.geometries[geom_num]);
}
} else {
throw new Error('The geoJSON is not valid.');
}
//alert(geometry_array.length);
return geometry_array;
}
function getConversionFunctionName(shape) {
var conversionFunctionName;
if (shape == 'sphere') {
conversionFunctionName = convertToSphereCoords;
} else if (shape == 'plane') {
conversionFunctionName = convertToPlaneCoords;
} else {
throw new Error('The shape that you specified is not valid.');
}
return conversionFunctionName;
}
function createCoordinateArray(feature) {
//Loop through the coordinates and figure out if the points need interpolation.
var temp_array = [];
var interpolation_array = [];
for (var point_num = 0; point_num < feature.length; point_num++) {
var point1 = feature[point_num];
var point2 = feature[point_num - 1];
if (point_num > 0) {
if (needsInterpolation(point2, point1)) {
interpolation_array = [point2, point1];
interpolation_array = interpolatePoints(interpolation_array);
for (var inter_point_num = 0; inter_point_num < interpolation_array.length; inter_point_num++) {
temp_array.push(interpolation_array[inter_point_num]);
}
} else {
temp_array.push(point1);
}
} else {
temp_array.push(point1);
}
}
return temp_array;
}
function needsInterpolation(point2, point1) {
//If the distance between two latitude and longitude values is
//greater than five degrees, return true.
var lon1 = point1[0];
var lat1 = point1[1];
var lon2 = point2[0];
var lat2 = point2[1];
var lon_distance = Math.abs(lon1 - lon2);
var lat_distance = Math.abs(lat1 - lat2);
if (lon_distance > 5 || lat_distance > 5) {
return true;
} else {
return false;
}
}
function interpolatePoints(interpolation_array) {
//This function is recursive. It will continue to add midpoints to the
//interpolation array until needsInterpolation() returns false.
var temp_array = [];
var point1, point2;
for (var point_num = 0; point_num < interpolation_array.length - 1; point_num++) {
point1 = interpolation_array[point_num];
point2 = interpolation_array[point_num + 1];
if (needsInterpolation(point2, point1)) {
temp_array.push(point1);
temp_array.push(getMidpoint(point1, point2));
} else {
temp_array.push(point1);
}
}
temp_array.push(interpolation_array[interpolation_array.length - 1]);
if (temp_array.length > interpolation_array.length) {
temp_array = interpolatePoints(temp_array);
} else {
return temp_array;
}
return temp_array;
}
function getMidpoint(point1, point2) {
var midpoint_lon = (point1[0] + point2[0]) / 2;
var midpoint_lat = (point1[1] + point2[1]) / 2;
var midpoint = [midpoint_lon, midpoint_lat];
return midpoint;
}
function convertToSphereCoords(coordinates_array, sphere_radius) {
var lon = coordinates_array[0];
var lat = coordinates_array[1];
x_values.push(Math.cos(lat * Math.PI / 180) * Math.cos(lon * Math.PI / 180) *
sphere_radius);
y_values.push(Math.cos(lat * Math.PI / 180) * Math.sin(lon * Math.PI / 180) *
sphere_radius);
z_values.push(Math.sin(lat * Math.PI / 180) * sphere_radius);
}
function convertToPlaneCoords(coordinates_array, radius) {
var lon = coordinates_array[0];
var lat = coordinates_array[1];
z_values.push((lat / 180) * radius);
y_values.push((lon / 180) * radius);
}
function drawParticle(x, y, z, options) {
var particle_geom = new THREE.Geometry();
particle_geom.vertices.push(new THREE.Vector3(x, y, z));
var particle_material = new THREE.ParticleSystemMaterial(options);
var particle = new THREE.ParticleSystem(particle_geom, particle_material);
scene.add(particle);
clearArrays();
}
function drawLine(x_values, y_values, z_values, options) {
// container
var obj = new THREE.Object3D();
// lines
var line_geom = new THREE.Geometry();
createVertexForEachPoint(line_geom, x_values, y_values, z_values);
var line_material = new THREE.LineBasicMaterial({
color: 'yellow'
});
var line = new THREE.Line(line_geom, line_material);
obj.add(line);
// mesh
var mesh_geom = new THREE.Geometry();
createVertexForEachPoint(mesh_geom, x_values, y_values, z_values);
var mesh_material = new THREE.MeshBasicMaterial({
color: 'blue',
side: THREE.DoubleSide
});
var mesh = new THREE.Mesh(mesh_geom, mesh_material);
obj.add(mesh);
scene.add(obj);
clearArrays();
}
function createVertexForEachPoint(object_geometry, values_axis1, values_axis2,
values_axis3) {
for (var i = 0; i < values_axis1.length; i++) {
object_geometry.vertices.push(new THREE.Vector3(values_axis1[i],
values_axis2[i], values_axis3[i]));
object_geometry.faces.push(new THREE.Face3(0, i + 1, i)); // <- add faces
}
}
function clearArrays() {
x_values.length = 0;
y_values.length = 0;
z_values.length = 0;
}
/**
* @author Eberhard Graether / http://egraether.com/
*/
THREE.TrackballControls = function ( object, domElement ) {
var _this = this;
var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 };
this.object = object;
this.domElement = ( domElement !== undefined ) ? domElement : document;
// API
this.enabled = true;
this.screen = { width: 0, height: 0, offsetLeft: 0, offsetTop: 0 };
this.radius = ( this.screen.width + this.screen.height ) / 4;
this.rotateSpeed = 1.0;
this.zoomSpeed = 1.2;
this.panSpeed = 0.3;
this.noRotate = false;
this.noZoom = false;
this.noPan = false;
this.staticMoving = false;
this.dynamicDampingFactor = 0.2;
this.minDistance = 0;
this.maxDistance = Infinity;
this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
// internals
this.target = new THREE.Vector3();
var lastPosition = new THREE.Vector3();
var _state = STATE.NONE,
_prevState = STATE.NONE,
_eye = new THREE.Vector3(),
_rotateStart = new THREE.Vector3(),
_rotateEnd = new THREE.Vector3(),
_zoomStart = new THREE.Vector2(),
_zoomEnd = new THREE.Vector2(),
_touchZoomDistanceStart = 0,
_touchZoomDistanceEnd = 0,
_panStart = new THREE.Vector2(),
_panEnd = new THREE.Vector2();
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.up0 = this.object.up.clone();
// events
var changeEvent = { type: 'change' };
// methods
this.handleResize = function () {
this.screen.width = window.innerWidth;
this.screen.height = window.innerHeight;
this.screen.offsetLeft = 0;
this.screen.offsetTop = 0;
this.radius = ( this.screen.width + this.screen.height ) / 4;
};
this.handleEvent = function ( event ) {
if ( typeof this[ event.type ] == 'function' ) {
this[ event.type ]( event );
}
};
this.getMouseOnScreen = function ( clientX, clientY ) {
return new THREE.Vector2(
( clientX - _this.screen.offsetLeft ) / _this.radius * 0.5,
( clientY - _this.screen.offsetTop ) / _this.radius * 0.5
);
};
this.getMouseProjectionOnBall = function ( clientX, clientY ) {
var mouseOnBall = new THREE.Vector3(
( clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft ) / _this.radius,
( _this.screen.height * 0.5 + _this.screen.offsetTop - clientY ) / _this.radius,
0.0
);
var length = mouseOnBall.length();
if ( length > 1.0 ) {
mouseOnBall.normalize();
} else {
mouseOnBall.z = Math.sqrt( 1.0 - length * length );
}
_eye.copy( _this.object.position ).sub( _this.target );
var projection = _this.object.up.clone().setLength( mouseOnBall.y );
projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) );
projection.add( _eye.setLength( mouseOnBall.z ) );
return projection;
};
this.rotateCamera = function () {
var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
if ( angle ) {
var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize();
quaternion = new THREE.Quaternion();
angle *= _this.rotateSpeed;
quaternion.setFromAxisAngle( axis, -angle );
_eye.applyQuaternion( quaternion );
_this.object.up.applyQuaternion( quaternion );
_rotateEnd.applyQuaternion( quaternion );
if ( _this.staticMoving ) {
_rotateStart.copy( _rotateEnd );
} else {
quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
_rotateStart.applyQuaternion( quaternion );
}
}
};
this.zoomCamera = function () {
if ( _state === STATE.TOUCH_ZOOM ) {
var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
_touchZoomDistanceStart = _touchZoomDistanceEnd;
_eye.multiplyScalar( factor );
} else {
var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
if ( factor !== 1.0 && factor > 0.0 ) {
_eye.multiplyScalar( factor );
if ( _this.staticMoving ) {
_zoomStart.copy( _zoomEnd );
} else {
_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
}
}
}
};
this.panCamera = function () {
var mouseChange = _panEnd.clone().sub( _panStart );
if ( mouseChange.lengthSq() ) {
mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x );
pan.add( _this.object.up.clone().setLength( mouseChange.y ) );
_this.object.position.add( pan );
_this.target.add( pan );
if ( _this.staticMoving ) {
_panStart = _panEnd;
} else {
_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
}
}
};
this.checkDistances = function () {
if ( !_this.noZoom || !_this.noPan ) {
if ( _this.object.position.lengthSq() > _this.maxDistance * _this.maxDistance ) {
_this.object.position.setLength( _this.maxDistance );
}
if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
_this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
}
}
};
this.update = function () {
_eye.subVectors( _this.object.position, _this.target );
if ( !_this.noRotate ) {
_this.rotateCamera();
}
if ( !_this.noZoom ) {
_this.zoomCamera();
}
if ( !_this.noPan ) {
_this.panCamera();
}
_this.object.position.addVectors( _this.target, _eye );
_this.checkDistances();
_this.object.lookAt( _this.target );
if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) {
_this.dispatchEvent( changeEvent );
lastPosition.copy( _this.object.position );
}
};
this.reset = function () {
_state = STATE.NONE;
_prevState = STATE.NONE;
_this.target.copy( _this.target0 );
_this.object.position.copy( _this.position0 );
_this.object.up.copy( _this.up0 );
_eye.subVectors( _this.object.position, _this.target );
_this.object.lookAt( _this.target );
_this.dispatchEvent( changeEvent );
lastPosition.copy( _this.object.position );
};
// listeners
function keydown( event ) {
if ( _this.enabled === false ) return;
window.removeEventListener( 'keydown', keydown );
_prevState = _state;
if ( _state !== STATE.NONE ) {
return;
} else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) {
_state = STATE.ROTATE;
} else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) {
_state = STATE.ZOOM;
} else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) {
_state = STATE.PAN;
}
}
function keyup( event ) {
if ( _this.enabled === false ) return;
_state = _prevState;
window.addEventListener( 'keydown', keydown, false );
}
function mousedown( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
if ( _state === STATE.NONE ) {
_state = event.button;
}
if ( _state === STATE.ROTATE && !_this.noRotate ) {
_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
} else if ( _state === STATE.ZOOM && !_this.noZoom ) {
_zoomStart = _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
} else if ( _state === STATE.PAN && !_this.noPan ) {
_panStart = _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
}
document.addEventListener( 'mousemove', mousemove, false );
document.addEventListener( 'mouseup', mouseup, false );
}
function mousemove( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
if ( _state === STATE.ROTATE && !_this.noRotate ) {
_rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
} else if ( _state === STATE.ZOOM && !_this.noZoom ) {
_zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
} else if ( _state === STATE.PAN && !_this.noPan ) {
_panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
}
}
function mouseup( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
_state = STATE.NONE;
document.removeEventListener( 'mousemove', mousemove );
document.removeEventListener( 'mouseup', mouseup );
}
function mousewheel( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
var delta = 0;
if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9
delta = event.wheelDelta / 40;
} else if ( event.detail ) { // Firefox
delta = - event.detail / 3;
}
_zoomStart.y += delta * 0.01;
}
function touchstart( event ) {
if ( _this.enabled === false ) return;
switch ( event.touches.length ) {
case 1:
_state = STATE.TOUCH_ROTATE;
_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;
case 2:
_state = STATE.TOUCH_ZOOM;
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
break;
case 3:
_state = STATE.TOUCH_PAN;
_panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;
default:
_state = STATE.NONE;
}
}
function touchmove( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
switch ( event.touches.length ) {
case 1:
_rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;
case 2:
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy )
break;
case 3:
_panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;
default:
_state = STATE.NONE;
}
}
function touchend( event ) {
if ( _this.enabled === false ) return;
switch ( event.touches.length ) {
case 1:
_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;
case 2:
_touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
break;
case 3:
_panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;
}
_state = STATE.NONE;
}
this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
this.domElement.addEventListener( 'mousedown', mousedown, false );
this.domElement.addEventListener( 'mousewheel', mousewheel, false );
this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
this.domElement.addEventListener( 'touchstart', touchstart, false );
this.domElement.addEventListener( 'touchend', touchend, false );
this.domElement.addEventListener( 'touchmove', touchmove, false );
window.addEventListener( 'keydown', keydown, false );
window.addEventListener( 'keyup', keyup, false );
this.handleResize();
};
THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment