× Project Code

/* Kepler Visualization 2011 - new data added in 2012 blprnt@blprnt.com You can toggle between view modes with the keys 4,3,2,1,` */ boolean hideSmall = true; // Import libraries PFont label; // Here's the big list that will hold all of our planets ArrayList<ExoPlanet> planets = new ArrayList(); // Conversion constants float ER = 1; // Earth Radius, in pixels float AU = 1500; // Astronomical Unit, in pixels float YEAR = 50000; // One year, in frames // Max/Min numbers float maxTemp = 3257; float minTemp = 3257; float yMax = 10; float yMin = 0; float maxSize = 0; float minSize = 1000000; // Axis labels String xLabel = "Semi-major Axis (Astronomical Units)"; String yLabel = "Temperature (Kelvin)"; // Rotation Vectors - control the main 3D space PVector rot = new PVector(); PVector trot = new PVector(); // Master zoom float zoom = 0; float tzoom = 0.3; // This is a zero-one weight that controls whether the planets are flat on the // plane (0) or not (1) float flatness = 0; float tflatness = 0; // add controls (e.g. zoom, sort selection) Controls controls; int showControls; boolean draggingZoomSlider = false; void setup() { size(1500, 700, P3D); background(0); smooth(); label = createFont("Helvetica", 96); textFont(label, 96); init(); } void init() { planets.clear(); // Because NASA released their data from 2011 and 2012 in somewhat // different formats, there are two functions to load the data and populate // the 'galaxy'. getPlanets("https://content.ktbyte.com/images/Kepler2012/data/KeplerData.csv", false); println(planets.size()); getPlanets("https://content.ktbyte.com/images/Kepler2012/data/planets2012_2.csv", true); println(planets.size()); addMarkerPlanets(); updatePlanetColors(); controls = new Controls(); showControls = 1; } void getPlanets(String url, boolean is2012) { // Here, the data is loaded and a planet is made from each line. String[] pArray = loadStrings(url); int start = is2012 ? 0 : 1; // skip header on 2011 data int planetsHidden = 0; for (int i = start; i < pArray.length; i++) { ExoPlanet p; if (is2012) { p = new ExoPlanet().fromCSV2012(split(pArray[i], ",")).init(); } else { p = new ExoPlanet().fromCSV(split(pArray[i], ",")).init(); } if(!hideSmall || p.radius > 5*ER) { planets.add(p); }else { planetsHidden++; } maxSize = max(p.radius, maxSize); minSize = min(p.radius, minSize); // These are two planets from the 2011 data set that I wanted to feature. if (p.KOI.equals("326.01") || p.KOI.equals("314.02")) { p.feature = true; p.label = p.KOI; } } println("Hid "+planetsHidden+" / "+pArray.length + " planets"); } void updatePlanetColors() { // Calculate overall min/max temps (will include the marker planets this way) for (int i = 0; i < planets.size(); i++) { ExoPlanet p = planets.get(i); maxTemp = max(p.temp, maxTemp); minTemp = min(abs(p.temp), minTemp); } colorMode(HSB); for (int i = 0; i < planets.size(); i++) { ExoPlanet p = planets.get(i); if (0 < p.temp) { float h = map(sqrt(p.temp), sqrt(minTemp), sqrt(maxTemp), 200, 0); p.col = color(h, 255, 255); } else { // What should we do with planets that have a negative temp in kelvin? p.col = color(200, 255, 255); } } colorMode(RGB); } void addMarkerPlanets() { // Now, add the solar system planets ExoPlanet mars = new ExoPlanet(); mars.period = 686; mars.radius = 0.533; mars.axis = 1.523; mars.temp = 212; mars.feature = true; mars.label = "Mars"; mars.init(); planets.add(mars); ExoPlanet earth = new ExoPlanet(); earth.period = 365; earth.radius = 1; earth.axis = 1; earth.temp = 254; earth.feature = true; earth.label = "Earth"; earth.init(); planets.add(earth); ExoPlanet jupiter = new ExoPlanet(); jupiter.period = 4331; jupiter.radius = 11.209; jupiter.axis = 5.2; jupiter.temp = 124; jupiter.feature = true; jupiter.label = "Jupiter"; jupiter.init(); planets.add(jupiter); ExoPlanet mercury = new ExoPlanet(); mercury.period = 87.969; mercury.radius = 0.3829; mercury.axis = 0.387; mercury.temp = 434; mercury.feature = true; mercury.label = "Mercury"; mercury.init(); planets.add(mercury); } void draw() { // Ease rotation vectors, zoom zoom += (tzoom - zoom) * 0.01; if (zoom < 0) { zoom = 0; } else if (zoom > 3.0) { zoom = 3.0; } controls.updateZoomSlider(zoom); rot.x += (trot.x - rot.x) * 0.1; rot.y += (trot.y - rot.y) * 0.1; rot.z += (trot.z - rot.z) * 0.1; // Ease the flatness weight flatness += (tflatness - flatness) * 0.1; // MousePress - Controls Handling if (mousePressed) { if ((showControls == 1) && controls.isZoomSliderEvent(mouseX, mouseY)) { draggingZoomSlider = true; zoom = controls.getZoomValue(mouseY); tzoom = zoom; } // MousePress - Rotation Adjustment else if (!draggingZoomSlider) { trot.x += (pmouseY - mouseY) * 0.01; trot.z += (pmouseX - mouseX) * 0.01; } } background(10); // show controls if (showControls == 1) { controls.render(); } // We want the center to be in the middle and slightly down when flat, and to the left and down when raised translate(width/2 - (width * flatness * 0.4), height/2 + (160 * rot.x)); rotateX(rot.x); rotateZ(rot.z); scale(zoom); // Draw the sun fill(255 - (255 * flatness)); noStroke(); ellipse(0, 0, 10, 10); // Draw Rings: strokeWeight(2); noFill(); // Draw a 2 AU ring stroke(255, 100 - (90 * flatness)); ellipse(0, 0, AU * 2, AU * 2); // Draw a 1 AU ring stroke(255, 50 - (40 * flatness)); ellipse(0, 0, AU, AU); // Draw a 10 AU ring ellipse(0, 0, AU * 10, AU * 10); // Draw the Y Axis stroke(255, 100); pushMatrix(); rotateY(-PI/2); line(0, 0, 500 * flatness, 0); // Draw Y Axis max/min pushMatrix(); fill(255, 100 * flatness); rotateZ(PI/2); textFont(label); textSize(12); text(round(yMin), -textWidth(str(yMin)), 0); text(round(yMax), -textWidth(str(yMax)), -500); popMatrix(); // Draw Y Axis Label fill(255, flatness * 255); text(yLabel, 250 * flatness, -10); popMatrix(); // Draw the X Axis if we are not flat pushMatrix(); rotateZ(PI/2); line(0, 0, 1500 * flatness, 0); if (flatness > 0.5) { pushMatrix(); rotateX(PI/2); line(AU * 1.06, -10, AU * 1.064, 10); line(AU * 1.064, -10, AU * 1.068, 10); popMatrix(); } // Draw X Axis Label fill(255, flatness * 255); rotateX(-PI/2); text(xLabel, 50 * flatness, 17); // Draw X Axis min/max fill(255, 100 * flatness); text(1, AU, 17); text("0.5", AU/2, 17); popMatrix(); // Render the planets for (int i = 0; i < planets.size(); i++) { ExoPlanet p = planets.get(i); if (p.vFlag < 4) { p.update(); p.render(); } } } void sortBySize() { // Raise the planets off of the plane according to their size for (int i = 0; i < planets.size(); i++) { planets.get(i).tz = map(planets.get(i).radius, 0, maxSize, 0, 500); } } void sortByTemp() { // Raise the planets off of the plane according to their temperature for (int i = 0; i < planets.size(); i++) { planets.get(i).tz = map(planets.get(i).temp, minTemp, maxTemp, 0, 500); } } void unSort() { // Put all of the planets back onto the plane for (int i = 0; i < planets.size(); i++) { planets.get(i).tz = 0; } } void keyPressed() { if (key == 'c') { showControls = -1 * showControls; } if (key == ' ') { hideSmall = !hideSmall; init(); } if (keyCode == UP) { tzoom += 0.025; } else if (keyCode == DOWN) { tzoom -= 0.025; } if (key == '1') { sortBySize(); toggleFlatness(1); yLabel = "Planet Size (Earth Radii)"; yMax = maxSize; yMin = 0; } else if (key == '2') { sortByTemp(); trot.x = PI/2; yLabel = "Temperature (Kelvin)"; //toggleFlatness(1); yMax = maxTemp; yMin = minTemp; } else if (key == '`') { unSort(); toggleFlatness(0); } else if (key == '3') { trot.x = 1.5; } else if (key == '4') { tzoom = 1; } if (key == 'f') { tflatness = (tflatness == 1) ? (0):(1); toggleFlatness(tflatness); } } // // MouseWheel - zoom controller (auto-triggered on event: mousewheel) // void mouseWheel(MouseEvent event) { // float e = event.getCount(); // float tempzoom = zoom; // if (tempzoom >= controls.minZoomValue && tempzoom <= controls.maxZoomValue) { // if (tempzoom >0.15) { // tempzoom += (e*(0.05*tempzoom)); // } else { //tempzoom >= 0.15 // tempzoom += (e*0.0075); // } // } // if (tempzoom < controls.maxZoomValue && tempzoom > controls.minZoomValue) { // tzoom = tempzoom + (e * (0.112*tempzoom)); // zoom = tempzoom; // } // } void toggleFlatness(float f) { tflatness = f; if (tflatness == 1) { trot.x = PI/2; trot.z = -PI/2; } else { trot.x = 0; } } void mouseReleased() { draggingZoomSlider = false; } /* Kepler Visualization - Controls GUI controls added by Lon Riesberg, Laboratory for Atmospheric and Space Physics lon@ieee.org April, 2012 Current release consists of a vertical slider for zoom control. The slider can be toggled on/off by pressing the 'c' key. Slide out controls that map to the other key bindings is currently being implemented and will be released soon. */ class Controls { int barWidth; int barX; // x-coordinate of zoom control int minY, maxY; // y-coordinate range of zoom control float minZoomValue, maxZoomValue; // values that map onto zoom control float valuePerY; // zoom value of each y-pixel int sliderY; // y-coordinate of current slider position float sliderValue; // value that corresponds to y-coordinate of slider int sliderWidth, sliderHeight; int sliderX; // x-coordinate of left-side slider edge Controls () { barX = 40; barWidth = 15; minY = 40; maxY = minY + height/3 - sliderHeight/2; minZoomValue = 0.0; maxZoomValue = 3.0; // 300 percent valuePerY = (maxZoomValue - minZoomValue) / (maxY - minY); sliderWidth = 25; sliderHeight = 10; sliderX = (barX + (barWidth/2)) - (sliderWidth/2); sliderValue = minZoomValue; sliderY = minY; } void render() { strokeWeight(0.5); stroke(105, 105, 105); // zoom control bar fill(0, 0, 0, 0); rect(barX, minY, barWidth, maxY-minY); // slider fill(105, 105, 105); rect(sliderX, sliderY, sliderWidth, sliderHeight); } float getZoomValue(int y) { if ((y >= minY) && (y <= (maxY - sliderHeight/2))) { sliderY = (int) (y - (sliderHeight/2)); if (sliderY < minY) { sliderY = minY; } sliderValue = (y - minY) * valuePerY + minZoomValue; } return sliderValue; } void updateZoomSlider(float value) { int tempY = (int) (value / valuePerY) + minY; if ((tempY >= minY) && (tempY <= (maxY-sliderHeight))) { sliderValue = value; sliderY = tempY; } } boolean isZoomSliderEvent(int x, int y) { int slop = 50; // number of pixels above or below slider that's acceptable. provided for ease of use. int sliderTop = (int) (sliderY - (sliderHeight/2)) - slop; int sliderBottom = sliderY + sliderHeight + slop; return ((x >= sliderX) && (x <= (sliderX + sliderWidth)) && (y >= sliderTop) && (y <= sliderBottom) || draggingZoomSlider ); } } /* ExoPlanet Class blprnt@blprnt.com Spring, 2011 - new data added Spring 2012 There are two separate formats for the data - both are listed below. // 2011 Batch KOI, Dur, : [1] Transit duration, first contact to last contact - HOURS Depth, : [2] Transit depth at center of transit - PULSE POSITION MODULATION SNR, t0,t0_unc, Period,P_unc, : [6,7] Average interval between transits based on a linear fit to all observed transits and uncertainty - DAYS a/R*,a/R*_unc, r/R*,r/R*_unc, : [10,11] Ratio of planet radius to stellar radius and uncertainty b,b_unc, Rp, :[14] Radius of the planet - EARTH RADII a, :[15] Semi-major axis of orbit based - AU (?) Teq, :[16]Equilibrium temperature of the planet - KELVIN EB prob, V, :[18] Vetting flag: 1 Confirmed and published planet 2 Strong probability candidate, cleanly passes tests that were applied 3 Moderate probability candidate, not all tests cleanly passed but no definite test failures 4 Insufficient follow-up to perform full suite of vetting tests FOP, N, // 2012 Batch -------------------------------------------------------------------------------- 1- 7 F7.2 --- KOI Kepler Object of Interest number 9- 20 F12.7 d Per Average interval between transits (1) 22- 27 F6.2 --- Rad Planetary radius in Earth radii=6378 km (2) 29- 34 F6.3 AU a Semi-major axis of orbit (3) 36- 40 I5 K Teq Equilibrium temperature of planet (4) 42- 47 F6.2 --- O/E_1 Ratio of odd to even numbered transit depths (5) 49- 54 F6.2 --- O/E_2 Ratio of odd to even numbered transit depths (6) 56- 63 F8.2 --- Occ Relative flux level at phase=0.5 divided by noise 65- 71 F7.2 as dra Source position in RA relative to target (7) 73- 79 F7.2 as e_dra Uncertainty in source position 81- 87 F7.2 as ddec Source position in DEC relative to target (7) 89- 95 F7.2 as e_ddec Uncertainty in source position 97-102 F6.1 --- dist Distance to source position divided by noise 104-109 F6.1 --- MES Multiple Event Statistic (MES) (8) -------------------------------------------------------------------------------- */ class ExoPlanet { // Data from the imported files String KOI; float period; float radius; float temp; float axis; int vFlag = 1; // Real movement/render properties float theta = 0; float thetaSpeed = 0; float pixelRadius = 0; float pixelAxis; float z = 0; float tz = 0; color col; boolean feature = false; String label = ""; // Constructor function ExoPlanet() { }; // Load exoplanet data from a comma-delimited string (see key at top of class) ExoPlanet fromCSV2012(String[] sa) { KOI = sa[0]; period = Float.parseFloat(sa[1]); radius = Float.parseFloat(sa[2]); axis = Float.parseFloat(sa[3]); temp = Float.parseFloat(sa[4]); return(this); } // Load exoplanet data from a comma-delimited string (see key at top of class) ExoPlanet fromCSV(String[] sa) { KOI = sa[0]; period = Float.parseFloat(sa[6]); radius = Float.parseFloat(sa[14]); axis = Float.parseFloat(sa[15]); temp = Float.parseFloat(sa[16]); vFlag = Integer.parseInt(sa[18]); return(this); } // Initialize pixel-based motion data, color, etc. from exoplanet data ExoPlanet init() { pixelRadius = radius * ER; pixelAxis = axis * AU; float periodInYears = period/365; float periodInFrames = periodInYears * YEAR; theta = random(2 * PI); thetaSpeed = (2 * PI) / periodInFrames; return(this); } // Update void update() { theta += thetaSpeed; z += (tz - z) * 0.1; } // Draw void render() { float apixelAxis = pixelAxis; if (axis > 1.06 && feature) { apixelAxis = ((1.06 + ((axis - 1.06) * ( 1 - flatness))) * AU) + axis * 10; } float x = sin(theta * (1 - flatness)) * apixelAxis; float y = cos(theta * (1 - flatness)) * apixelAxis; pushMatrix(); translate(x, y, z); // Billboard rotateZ(-rot.z); rotateX(-rot.x); noStroke(); if (feature) { translate(0, 0, 1); stroke(255, 255); strokeWeight(2); noFill(); ellipse(0, 0, pixelRadius + 10, pixelRadius + 10); strokeWeight(1); pushMatrix(); if (label.equals("Earth")) { stroke(#01FFFD, 50); line(0, 0, -pixelAxis * flatness, 0); } rotate((1 - flatness) * PI/2); stroke(255, 100); float r = max(50, 100 + ((1 - axis) * 200)); r *= sqrt(1/zoom); if (zoom > 0.5 || label.charAt(0) != '3') { line(0, 0, 0, -r); translate(0, -r - 5); rotate(-PI/2); scale(1/zoom); fill(255, 200); text(label, 0, 4); } popMatrix(); } fill(col); noStroke(); ellipse(0, 0, pixelRadius, pixelRadius); popMatrix(); } }