/***************************************************************************** * Web3d.org Copyright (c) 2007 * Java Source * * This source is licensed under the BSD license. * Please read docs/BSD.txt for the text of the license. * * This software comes with the standard NO WARRANTY disclaimer for any * purpose. Use it at your own risk. If there's a problem you get to fix it. * ****************************************************************************/ import java.util.*; import org.web3d.x3d.sai.*; import java.io.*; import java.util.*; import java.net.URL; import org.apache.commons.math.stat.descriptive.moment.StandardDeviation; import org.apache.commons.math.stat.descriptive.moment.Mean; /** * Example of how to view US Census data. Shows ZCTA locations * * Source of data: Population Division, US Census Bureau * * Data layout * Columns 1-2: United States Postal Service State Abbreviation * Columns 3-66: Name (e.g. 35004 5-Digit ZCTA - there are no post office names) * Columns 67-75: Total Population (2000) * Columns 76-84: Total Housing Units (2000) * Columns 85-98: Land Area (square meters) - Created for statistical purposes only. * Columns 99-112: Water Area (square meters) - Created for statistical purposes only. * Columns 113-124: Land Area (square miles) - Created for statistical purposes only. * Columns 125-136: Water Area (square miles) - Created for statistical purposes only. * Columns 137-146: Latitude (decimal degrees) First character is blank or "-" denoting North or South latitude respectively * Columns 147-157: Longitude (decimal degrees) First character is blank or "-" denoting East or West longitude respectively * * @author Alan Hudson * @version */ public class USCensusZCTAViewer implements X3DPerFrameObserverScript, X3DFieldEventListener, Runnable { /** Marker Types */ public enum Markers { CENTER_BOX, CENTER_SPHERE, RECTANGLE }; /** The browser */ private Browser browser; /** The current scene */ private X3DExecutionContext scene; /** The data source */ private SFString dataURLField; /** The GeoOrigin to use */ private SFNode geoOrigin; /** What states should be shown. Comma delimeted list */ private MFString stateFilter; /** The minimum population to displat */ private SFInt32 minPopulationField; /** What to visualize */ private MFString visualizeField; /** What zips should be shown. Starts with logic */ private SFString zipFilterField; /** The zipFilter value */ private String zipFilter; /** Output label */ private MFString labelField; /** Output value */ private MFString valueField; /** Set of allowed states */ private HashSet statesShown; /** The data source */ private String dataURL; /** How to visualize the data */ private SFString marker; /** How large to make the marker */ private SFFloat markerSize; /** How high to place the marker */ private SFFloat height; /** The mapping loading thread */ private Thread mapLoader; /** Is the loading done */ private boolean loadFinished; /** Has the visualization been created */ private boolean visualCreated; /** The data */ private ArrayList data; /** The minimum population */ private int minPopulation; public USCensusZCTAViewer() { loadFinished = false; visualCreated = false; data = new ArrayList(10000); statesShown = new HashSet(); minPopulation = 0; } //---------------------------------------------------------- // Methods defined by X3DScriptImplementation //---------------------------------------------------------- public void setBrowser(Browser browser) { this.browser = browser; } public void setFields(X3DScriptNode externalView, Map fields) { // Get the fields we need for processing marker = (SFString) fields.get("marker"); markerSize = (SFFloat) fields.get("markerSize"); stateFilter = (MFString) fields.get("stateFilter"); zipFilterField = (SFString) fields.get("zipFilter"); height = (SFFloat) fields.get("height"); dataURLField = (SFString) fields.get("data"); geoOrigin = (SFNode) fields.get("geoOrigin"); visualizeField = (MFString) fields.get("visualize"); minPopulationField = (SFInt32) fields.get("minPopulation"); labelField = (MFString) fields.get("label"); valueField = (MFString) fields.get("value"); if (minPopulationField != null) minPopulation = minPopulationField.getValue(); } public void initialize() { scene = browser.getExecutionContext(); dataURL = dataURLField.getValue(); int len = stateFilter.getSize(); String val; for(int i=0; i < len; i++) { val = stateFilter.get1Value(i); System.out.println("Showing state: " + val); statesShown.add(val); } zipFilter = zipFilterField.getValue(); loadData(); } public void eventsProcessed() { } public void shutdown() { } //---------------------------------------------------------- // Methods defined by X3DPerFrameObserver //---------------------------------------------------------- public void prepareEvents() { if (!visualCreated && loadFinished) { int len = visualizeField.getSize(); String val; for(int i=0; i < len; i++) { val = visualizeField.get1Value(i); if (val.equals("POPULATION/HOUSING_UNITS")) { createPopHousingVisualization(); } else if (val.equals("LOCATION")) { labelField.setValue(1, new String[] {"Zip"}); valueField.setValue(1, new String[] {"NA"}); createLocationVisualization(); } else if (val.equals("POPULATION")) { labelField.setValue(1, new String[] {"Population"}); valueField.setValue(1, new String[] {"NA"}); createPopulationVisualization(); } } visualCreated = true; } } //---------------------------------------------------------- // Methods defined by X3DFieldEventListener //---------------------------------------------------------- public void readableFieldChanged(X3DFieldEvent evt) { Object udata = evt.getData(); if (udata != null) { valueField.setValue(1,new String[] {(String)udata}); } } /** * Load the ZCTA data. */ private void loadData() { mapLoader = new Thread(this, "Map Loader"); mapLoader.start(); } /** * Thread for loading data */ public void run() { System.out.print("Loading data table"); int num = 0; try { String worldUrl = scene.getWorldURL(); BufferedReader br = null; URL url; try { url = new URL(worldUrl + dataURL); Object content = url.getContent(); if (content instanceof InputStream) { br = new BufferedReader(new InputStreamReader((InputStream)content)); } else { System.out.println("Can't handle content: " + content); } } catch(Exception e) { br = new BufferedReader(new FileReader(new File(dataURL))); } String line = ""; while(line != null) { line = br.readLine(); if (line != null) addEntry(line); num++; if (num % 100 == 0) System.out.print("."); } } catch(Exception e) { e.printStackTrace(); } System.out.println("\nDone loading zip table"); loadFinished = true; } /* * Columns 1-2: United States Postal Service State Abbreviation * Columns 3-66: Name (e.g. 35004 5-Digit ZCTA - there are no post office names) * Columns 67-75: Total Population (2000) * Columns 76-84: Total Housing Units (2000) * Columns 85-98: Land Area (square meters) - Created for statistical purposes only. * Columns 99-112: Water Area (square meters) - Created for statistical purposes only. * Columns 113-124: Land Area (square miles) - Created for statistical purposes only. * Columns 125-136: Water Area (square miles) - Created for statistical purposes only. * Columns 137-146: Latitude (decimal degrees) First character is blank or "-" denoting North or South latitude respectively * Columns 147-157: Longitude (decimal degrees) First character is blank or "-" denoting East or West longitude respectively */ /** * Add an entry. * * @param line */ private void addEntry(String line) { try { String state = line.substring(0,2); if (statesShown.size() > 0 && !statesShown.contains(state)) { return; } String name = line.substring(2,7).trim(); //System.out.println("In State: " + name); //System.out.println("zipFilter: " + zipFilter + " startsWith: " + (name.startsWith(zipFilter))); if (zipFilter != null && !name.startsWith(zipFilter)) { //System.out.println("not showing: " + name); return; } int population = Integer.valueOf(line.substring(66,75).trim()); //System.out.println("name: " + name + " pop: " + population); int housing = Integer.valueOf(line.substring(76,83).trim()); float latitude = Float.valueOf(line.substring(136,145)); float longitude = Float.valueOf(line.substring(146,156)); data.add(new DataHolder(name, population, housing, latitude, longitude)); } catch(NumberFormatException e) { //System.out.println("Error parsing: " + line); //e.printStackTrace(); } } /** * Create the visualization. */ public void createPopHousingVisualization() { int len = data.size(); Markers markerType; X3DNode markerShape; X3DNode markerGeom = null; X3DNode location; X3DNode markerAppearance; X3DNode markerMaterial; float size = markerSize.getValue(); if (marker.getValue().equals("CENTER_BOX")) markerType = Markers.CENTER_BOX; else markerType = Markers.CENTER_SPHERE; switch(markerType) { case CENTER_SPHERE: markerGeom = scene.createNode("Sphere"); ((SFFloat)markerGeom.getField("radius")).setValue(size); break; case CENTER_BOX: markerGeom = scene.createNode("Box"); ((SFVec3f)markerGeom.getField("size")).setValue(new float[] {size,size,size}); break; } String[] geoSystem = new String[] {"GDC"}; double[] pos = new double[3]; DataHolder d; System.out.println("Creating markers: " + len); int start = 0; double maxBlueRatio = 0; double maxRedRatio = 0; float popHouseRatio; float logPopHouseRatio; StandardDeviation stdClass = new StandardDeviation(); Mean meanClass = new Mean(); for(int i=start; i < len; i++) { d = data.get(i); popHouseRatio = (float)d.population / d.housingUnits; logPopHouseRatio = (float) Math.log((float)d.population / d.housingUnits); meanClass.increment(logPopHouseRatio); stdClass.increment(logPopHouseRatio); } double std = stdClass.getResult(); double mean = meanClass.getResult(); double zdata; double absZData; float nval; //System.out.println("Calc min and max"); for(int i=start; i < len; i++) { d = data.get(i); popHouseRatio = (float)d.population / d.housingUnits; logPopHouseRatio = (float) Math.log((float)d.population / d.housingUnits); zdata = (logPopHouseRatio - mean) / std; absZData = Math.abs(zdata); //System.out.println(zdata); if (zdata < 0) { if (absZData > maxBlueRatio) maxBlueRatio = absZData; } else { if (absZData > maxRedRatio) maxRedRatio = absZData; } } meanClass.clear(); //System.out.println(" std: " + std + " mean: " + mean); for(int i=start; i < len; i++) { d = data.get(i); markerShape = scene.createNode("Shape"); ((SFNode)markerShape.getField("geometry")).setValue(markerGeom); location = scene.createNode("GeoLocation"); ((MFString)location.getField("geoSystem")).setValue(geoSystem.length,geoSystem); ((SFNode)location.getField("geoOrigin")).setValue(geoOrigin.getValue()); pos[0] = d.latitude; pos[1] = d.longitude; pos[2] = height.getValue(); popHouseRatio = (float)d.population / d.housingUnits; logPopHouseRatio = (float) (Math.log((float)d.population / d.housingUnits)); zdata = ((logPopHouseRatio - mean) / std); float[] color = new float[] {0, 0, 0}; if (zdata < 0) { color[0] = 1 - (float) (Math.abs(zdata) / maxBlueRatio); if (color[0] > 1) color[0] = 1; color[1] = color[0]; color[2] = 1; } if (zdata >= 0) { color[1] = 1 - (float)(Math.abs(zdata) / maxRedRatio); if (color[1] > 1) color[1] = 1; color[2] = color[1]; color[0] = 1; } //System.out.println(d.name + " ratio: " + popHouseRatio + " log: " + logPopHouseRatio + " zdata: " + zdata + " color: " + color[0] + " " + color[1] + " " + color[2]); meanClass.increment(zdata); nval = (float) 1; float[] diffuse = new float[] {0,0,0}; ((SFNode)markerShape.getField("appearance")).setValue(createColoredAppearance(diffuse, color, 0f)); ((SFVec3d)location.getField("geoCoords")).setValue(pos); ((MFNode)location.getField("children")).setValue(1, new X3DNode[] {markerShape}); ((X3DScene)scene).addRootNode(location); } System.out.println("Markers created"); } /** * Create the visualization. */ public void createPopulationVisualization() { int len = data.size(); Markers markerType; X3DNode markerShape; X3DNode markerGeom = null; X3DNode location; X3DNode markerAppearance; X3DNode markerMaterial; float marker_size = markerSize.getValue(); if (marker.getValue().equals("CENTER_BOX")) markerType = Markers.CENTER_BOX; else markerType = Markers.CENTER_SPHERE; String[] geoSystem = new String[] {"GDC"}; double[] pos = new double[3]; DataHolder d; System.out.println("Creating markers: " + len); int start = 0; int max = Integer.MIN_VALUE; int min = Integer.MAX_VALUE; for(int i=start; i < len; i++) { d = data.get(i); if (d.population > max) { max = d.population; } if (d.population < min) { min = d.population; } } System.out.println("min: " + min + " max: " + max); int range = max - min; float nval; float size; for(int i=start; i < len; i++) { d = data.get(i); if (d.population < minPopulation) continue; nval = (float) ((d.population - min)) / range; switch(markerType) { case CENTER_SPHERE: markerGeom = scene.createNode("Sphere"); size = marker_size + 4 * marker_size * nval; ((SFFloat)markerGeom.getField("radius")).setValue(size); break; case CENTER_BOX: markerGeom = scene.createNode("Box"); //size = marker_size; size = marker_size + 4 * marker_size * nval; ((SFVec3f)markerGeom.getField("size")).setValue(new float[] {size,size,size}); break; } X3DNode touch = scene.createNode("TouchSensor"); SFBool isOver = (SFBool) touch.getField("isOver"); isOver.setUserData(Integer.toString((d.population))); isOver.addX3DEventListener(this); markerShape = scene.createNode("Shape"); ((SFNode)markerShape.getField("geometry")).setValue(markerGeom); location = scene.createNode("GeoLocation"); ((MFString)location.getField("geoSystem")).setValue(geoSystem.length,geoSystem); ((SFNode)location.getField("geoOrigin")).setValue(geoOrigin.getValue()); pos[0] = d.latitude; pos[1] = d.longitude; pos[2] = height.getValue(); float[] diffuse = new float[] {0,0,0}; float[] color = new float[3]; color[0] = nval; color[1] = 0f; color[2] = 0f; //((SFNode)markerShape.getField("appearance")).setValue(createColoredAppearance(diffuse, color, 0f)); ((SFVec3d)location.getField("geoCoords")).setValue(pos); ((MFNode)location.getField("children")).setValue(2, new X3DNode[] {markerShape, touch}); ((X3DScene)scene).addRootNode(location); } } /** * Create the visualization. */ public void createLocationVisualization() { int len = data.size(); Markers markerType; X3DNode markerShape; X3DNode markerGeom = null; X3DNode location; X3DNode markerAppearance; X3DNode markerMaterial; float size = markerSize.getValue(); if (marker.getValue().equals("CENTER_BOX")) markerType = Markers.CENTER_BOX; else if (marker.getValue().equals("RECTANGLE")) markerType = Markers.RECTANGLE; else markerType = Markers.CENTER_SPHERE; switch(markerType) { case CENTER_SPHERE: markerGeom = scene.createNode("Sphere"); ((SFFloat)markerGeom.getField("radius")).setValue(size); break; case CENTER_BOX: markerGeom = scene.createNode("Box"); ((SFVec3f)markerGeom.getField("size")).setValue(new float[] {size,size,size}); break; case RECTANGLE: markerGeom = scene.createNode("Rectangle2D"); ((SFVec2f)markerGeom.getField("size")).setValue(new float[] {size,size}); break; } String[] geoSystem = new String[] {"GDC"}; double[] pos = new double[3]; DataHolder d; System.out.println("Creating markers: " + len); int start = 0; for(int i=start; i < len; i++) { d = data.get(i); X3DNode touch = scene.createNode("TouchSensor"); SFBool isOver = (SFBool) touch.getField("isOver"); isOver.setUserData(d.name); isOver.addX3DEventListener(this); markerShape = scene.createNode("Shape"); ((SFNode)markerShape.getField("geometry")).setValue(markerGeom); location = scene.createNode("GeoLocation"); ((MFString)location.getField("geoSystem")).setValue(geoSystem.length,geoSystem); ((SFNode)location.getField("geoOrigin")).setValue(geoOrigin.getValue()); pos[0] = d.latitude; pos[1] = d.longitude; pos[2] = height.getValue(); ((SFVec3d)location.getField("geoCoords")).setValue(pos); ((MFNode)location.getField("children")).setValue(2, new X3DNode[] {markerShape, touch}); ((X3DScene)scene).addRootNode(location); } System.out.println("Markers created"); } private X3DNode createColoredAppearance(float[] diffuse, float[] emissive, float transparency) { X3DNode markerAppearance = scene.createNode("Appearance"); X3DNode markerMaterial = scene.createNode("Material"); ((SFNode)markerAppearance.getField("material")).setValue(markerMaterial); ((SFColor)markerMaterial.getField("emissiveColor")).setValue(emissive); ((SFColor)markerMaterial.getField("diffuseColor")).setValue(diffuse); ((SFFloat)markerMaterial.getField("transparency")).setValue(transparency); return markerAppearance; } } class DataHolder { public String name; public int population; public int housingUnits; public float latitude; public float longitude; public DataHolder(String name, int pop, int housing, float latitude, float longitude) { this.name = name; this.population = pop; this.housingUnits = housing; this.latitude = latitude; this.longitude = longitude; } }