Java3Dで数値地図50mメッシュ(標高)を表示(その1)
いつの間にか、Java3Dが1.5.2にマイナーながらバージョンアップしていた。
リリースノートをみると、JOGLのレンダリングパイプラインが実装されたみたい。
最近、存在の薄いJava3Dを取り上げ、国土地理院発行の数値地図50mメッシュ(標高)をJava3Dで表示してみる。
なお、ソースコードが長くなるので、2〜3回にわけることにする。
■ソースコード
とりあえず、今日は、数値地図50mメッシュのパーサー部分とBranchGroupを構成する部分のコードを示す。
なお、本来はGeoToolsとかで緯度経度を座標変換するべきと思うが、簡略化する。
また、着色は、以前実装したGradientクラスを使用する。
・DemParser.java(数値地図ファイルをパースするクラス)
package test.dem50m; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class DemParser { private String[] header; private int[] format; private int[][] alt; private Parser parser; public double[][] readFile(File f) throws IOException,FileNotFoundException{ BufferedReader buf=new BufferedReader(new FileReader(f)); return parse(buf).getCoordinate(); } private Parser parse(BufferedReader buf) throws IOException{ format=getHeaderFormat(); int id=0; try{ String line=buf.readLine(); if(setHeader(line)){ int nsNum=Integer.parseInt(header[6]); int weNum=Integer.parseInt(header[5]); alt=new int[nsNum][weNum]; while((line=buf.readLine())!=null){ setWestToEastData(line,id,alt[id]); id++; } } buf.close(); buf=null; return createDem(); }catch(IOException ie){ throw ie; }finally{ if(buf!=null)buf.close(); } } private Parser createDem(){ int[] left,right; left=new int[]{getSexagesimal(header[7]),getSexagesimal(header[8])}; right=new int[]{getSexagesimal(header[9]),getSexagesimal(header[10])}; parser=new Parser(left,right,alt,null); return parser; } private boolean setHeader(String line){ try{ int id=0; int num=0; header=new String[64]; do{ header[id]=line.substring(num,format[id]+num); num +=format[id++]; }while(num<line.length()); return true; }catch(Exception e){ return false; } } private void setWestToEastData(String line,int record,int[] data){ int num=line.length(); int id=0; if(header[22].charAt(record)=='1'){ for(int i=9;i<num;i=i+5){ data[id++]=Integer.parseInt(line.substring(i,i+5)); } }else{ for(int i=0;i<200;i++){ data[i]=-9999; } } } private int[] getHeaderFormat(){ int[] ret=new int[]{ 6,5,4,4,4,3,3,7,7,7,7,1,10,1,10,1,10,1,10,1,3, 40,200,319,1,10,8,8,1,8,8,1,8,8,1,8,8,1,10,8,8, 1,8,8,1,8,8,1,8,8,1,10,8,8,1,8,8,1,8,8,1,8,8,1 }; return ret; } private int getSexagesimal(String arg){ double deg=Double.parseDouble(arg.substring(0,3))*3600; double min=Double.parseDouble(arg.substring(3,5))*60; double sec=0; if(arg.length()==7){ sec=Double.parseDouble(arg.substring(5,7)); }else{ sec=Double.parseDouble(arg.substring(5,8))/10; } return (int)(deg+min+sec); } public int[] getIDOfNan(){ if(parser!=null){ return parser.getIDOfNan(); }else{ return null; } } private class Parser { private int[] left; private int[] right; private int[][] altitudes; private Parser dem; private List<Integer> nan; Parser(int[] left,int[] right,int[][] alt,Parser dem){ this.left=left; this.right=right; altitudes=alt; this.dem=dem; nan=new ArrayList<Integer>(); } double[][] getCoordinate(){ double[] dd; if(dem!=null){ dd=getDxDz(dem); }else{ dd=new double[]{0,0}; } int num=altitudes.length*altitudes[0].length; double[][] ret=new double[num][3]; int id=0; for(int z=altitudes.length-1;0<=z;z--){ for(int x=0;x<altitudes[0].length;x++){ ret[id][0]=x*50+dd[1]; if(altitudes[z][x]!=-9999){ ret[id][1]=((double)altitudes[z][x])/10.0d; }else{ ret[id][1]=0; nan.add(new Integer(id)); } ret[id][2]=z*50+dd[0]; id++; } } return ret; } int numOfWestToEase(){ return altitudes[0].length; } int numOfNorthToSouth(){ return altitudes.length; } private double[] getDxDz(Parser dem){ double z=left[0]-dem.left[0]; double x=left[1]-dem.left[1]; z /=1.5; x /=2.25; if(x==0){ return new double[]{-z*50+50,x*50}; }else if(z==0){ return new double[]{-z*50,x*50-50}; }else{ return new double[]{-z*50+50,x*50-50}; } } public double[][][] getNext(Parser d){ if(right[0]==d.left[0]&&right[1]==d.right[1]){ double[][] a=this.getEdge(0); double[][] b=d.getEdge(3); return new double[][][]{a,b}; } if(left[0]==d.left[0]&&right[1]==d.left[1]){ double[][] a=this.getEdge(1); double[][] b=d.getEdge(2); return new double[][][]{a,b}; } if(right[0]==d.right[0]&&left[1]==d.right[1]){ double[][] a=this.getEdge(2); double[][] b=d.getEdge(1); return new double[][][]{a,b}; } if(left[0]==d.right[0]&&left[1]==d.left[1]){ double[][]a=this.getEdge(3); double[][] b=d.getEdge(0); return new double[][][]{a,b}; } return null; } public double[][] getEdge(int num){ switch(num){ case 0: return getCoordinateNorth(); case 1: return getCoordinateEast(); case 2: return getCoordinateWest(); case 3: return getCoordinateSouth(); default: return null; } } public int[] getIDOfNan(){ int[] ret=new int[nan.size()]; for(int i=0;i<ret.length;i++){ ret[i]=nan.get(i).intValue(); } return ret; } private double[][] getCoordinateNorth(){ int we=numOfWestToEase(); int ns=numOfNorthToSouth(); double[] dd; if(dem!=null){ dd=getDxDz(dem); }else{ dd=new double[]{0,0}; } double[][] ret=new double[we][]; double val=0; for(int i=0;i<we;i++){ if(altitudes[ns-1][i]!=-9999){ val=((double)altitudes[ns-1][i])/10.0d; }else{ val=-10; } ret[i]=new double[]{i*50+dd[1],val,(ns-1)*50+dd[0]}; } return ret; } private double[][] getCoordinateSouth(){ int we=numOfWestToEase(); double[] dd; if(dem!=null){ dd=getDxDz(dem); }else{ dd=new double[]{0,0}; } double[][] ret=new double[we][]; double val=0; for(int i=0;i<we;i++){ if(altitudes[0][i]!=-9999){ val=((double)altitudes[0][i])/10.0d; }else{ val=-10; } ret[i]=new double[]{i*50+dd[1],val,dd[0]}; } return ret; } private double[][] getCoordinateEast(){ int we=numOfWestToEase(); int ns=numOfNorthToSouth(); double[] dd; if(dem!=null){ dd=getDxDz(dem); }else{ dd=new double[]{0,0}; } double[][] ret=new double[we][]; double val=0; for(int i=0;i<ns;i++){ if(altitudes[i][we-1]!=-9999){ val=((double)altitudes[i][we-1])/10.0d; }else{ val=-10; } ret[i]=new double[]{(we-1)*50+dd[1],val,i*50+dd[0]}; } return ret; } private double[][] getCoordinateWest(){ int ns=numOfNorthToSouth(); double[] dd; if(dem!=null){ dd=getDxDz(dem); }else{ dd=new double[]{0,0}; } double[][] ret=new double[ns][]; double val=0; for(int i=0;i<ns;i++){ if(altitudes[i][0]!=-9999){ val=((double)altitudes[i][0])/10.0d; }else{ val=-10; } ret[i]=new double[]{dd[1],val,i*50+dd[0]}; } return ret; } } }
・DemLoader.java(BranchGroupを構成するクラス)
package test.dem50m; import java.awt.geom.Rectangle2D; import java.io.File; import java.util.Stack; import javax.media.j3d.Appearance; import javax.media.j3d.BranchGroup; import javax.media.j3d.GeometryArray; import javax.media.j3d.Material; import javax.media.j3d.PolygonAttributes; import javax.media.j3d.RenderingAttributes; import javax.media.j3d.Shape3D; import javax.media.j3d.TexCoordGeneration; import javax.media.j3d.TextureAttributes; import javax.media.j3d.TextureUnitState; import javax.media.j3d.TransparencyAttributes; import javax.swing.JPanel; import javax.vecmath.Color3f; import javax.vecmath.Point3d; import test.Gradient; import test.GradientFactory; import com.sun.j3d.utils.geometry.GeometryInfo; import com.sun.j3d.utils.geometry.NormalGenerator; import com.sun.j3d.utils.geometry.Stripifier; import com.sun.j3d.utils.image.TextureLoader; public class DemLoader{ private DemParser parser; private double[][] vertex; private int[][] face; private int[] nan; private Rectangle2D rec; private double minH; private double maxH; private double minX; private double minY; private double maxX; private double maxY; public DemLoader(){ parser=new DemParser(); } private BranchGroup createBranchGroup() { BranchGroup group=new BranchGroup(); float[][]color=computeColor(); GeometryInfo ginfo = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY); Point3d[] ver=new Point3d[vertex.length]; Color3f[] vc=new Color3f[vertex.length]; for(int i=0;i<ver.length;i++){ ver[i]=new Point3d(vertex[i]); vc[i]=new Color3f(color[i]); } Point3d[] tmp=new Point3d[face.length*3]; Color3f[] co=new Color3f[face.length*3]; int id=0; for(int i=0;i<face.length;i++){ tmp[id]=ver[face[i][0]]; co[id++]=vc[face[i][0]]; tmp[id]=ver[face[i][1]]; co[id++]=vc[face[i][1]]; tmp[id]=ver[face[i][2]]; co[id++]=vc[face[i][2]]; } ginfo.setCoordinates(tmp); ginfo.setColors(co); NormalGenerator ng = new NormalGenerator(); ng.generateNormals(ginfo); Stripifier st = new Stripifier(); st.stripify(ginfo); GeometryArray geometry = ginfo.getGeometryArray(); Appearance ap=new Appearance(); PolygonAttributes pattr=createPolyAtt(); ap.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ); ap.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE); ap.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ); ap.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_WRITE); ap.setPolygonAttributes(pattr); ap.getPolygonAttributes().setBackFaceNormalFlip(true); ap.getPolygonAttributes().setCullFace(PolygonAttributes.CULL_NONE); ap.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_READ); ap.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_WRITE); ap.setCapability(Appearance.ALLOW_MATERIAL_READ); ap.setCapability(Appearance.ALLOW_MATERIAL_WRITE); ap.setTransparencyAttributes(createTransparency()); ap.setRenderingAttributes(createRenderingAttributes()); Material mat=createMaterial(); Color3f ac=new Color3f(0.2f,1.0f,0.2f); mat.setAmbientColor(ac.x*0.5f,ac.y*0.5f,ac.z*0.5f); mat.setDiffuseColor(ac); mat.setSpecularColor(0.0f, 0.0f, 0.0f); mat.setShininess(1.0f); ap.setMaterial(mat); TexCoordGeneration ge=new TexCoordGeneration(TexCoordGeneration.NORMAL_MAP, TexCoordGeneration.TEXTURE_COORDINATE_2); TextureUnitState ts0=new TextureUnitState(); ts0.setTexCoordGeneration(ge); TextureAttributes ta0=new TextureAttributes(); ta0.setTextureMode(TextureAttributes.MODULATE); TextureLoader tl0=new TextureLoader(getClass().getResource("soil.png"),new JPanel()); ts0.setTexture(tl0.getTexture()); ts0.setTextureAttributes(ta0); ts0.getTextureAttributes().setCapability(TextureAttributes.ALLOW_MODE_READ); ts0.getTextureAttributes().setCapability(TextureAttributes.ALLOW_MODE_WRITE); ap.setTextureUnitState(new TextureUnitState[]{ts0}); Shape3D shape=new Shape3D(geometry,ap); shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ); shape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); group.addChild(shape); return group; } public static PolygonAttributes createPolyAtt(){ PolygonAttributes pattr = new PolygonAttributes(); pattr.setCapability(PolygonAttributes.ALLOW_CULL_FACE_WRITE); pattr.setCapability(PolygonAttributes.ALLOW_MODE_WRITE); pattr.setCapability(PolygonAttributes.ALLOW_NORMAL_FLIP_WRITE); pattr.setCapability(PolygonAttributes.ALLOW_OFFSET_WRITE); pattr.setCapability(PolygonAttributes.ALLOW_MODE_READ); pattr.setCapability(PolygonAttributes.ALLOW_MODE_WRITE); return pattr; } public static TransparencyAttributes createTransparency(){ TransparencyAttributes t=new TransparencyAttributes(); t.setCapability(TransparencyAttributes.ALLOW_MODE_WRITE); t.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE); t.setCapability(TransparencyAttributes.ALLOW_MODE_READ); t.setCapability(TransparencyAttributes.ALLOW_VALUE_READ); return t; } public static RenderingAttributes createRenderingAttributes(){ RenderingAttributes r=new RenderingAttributes(); r.setCapability(RenderingAttributes.ALLOW_ALPHA_TEST_VALUE_READ); r.setCapability(RenderingAttributes.ALLOW_ALPHA_TEST_FUNCTION_READ); r.setCapability(RenderingAttributes.ALLOW_ALPHA_TEST_VALUE_WRITE); r.setCapability(RenderingAttributes.ALLOW_ALPHA_TEST_FUNCTION_WRITE); r.setCapability(RenderingAttributes.ALLOW_DEPTH_ENABLE_READ); r.setCapability(RenderingAttributes.ALLOW_DEPTH_ENABLE_WRITE); return r; } public static Material createMaterial(){ Material mat=new Material(); mat.setCapability(Material.ALLOW_COMPONENT_READ); mat.setCapability(Material.ALLOW_COMPONENT_WRITE); return mat; } public float[] getCenter(){ return new float[]{(float)rec.getCenterX(),(float)rec.getCenterY()}; } public float getScale(){ return 1.0f/((float)maxX-(float)minX); } public BranchGroup load(File f) { try{ minX=Double.MAX_VALUE; minH=Double.MAX_VALUE; minY=Double.MAX_VALUE; maxX=-Double.MAX_VALUE; maxH=-Double.MAX_VALUE; maxY=-Double.MAX_VALUE; vertex=parser.readFile(f); nan=parser.getIDOfNan(); for(int i=0;i<vertex.length;i++){ if(vertex[i][0]<minX)minX=vertex[i][0]; if(vertex[i][1]<minH)minH=vertex[i][1]; if(vertex[i][2]<minY)minY=vertex[i][2]; if(vertex[i][0]>maxX)maxX=vertex[i][0]; if(vertex[i][1]>maxH)maxH=vertex[i][1]; if(vertex[i][2]>maxY)maxY=vertex[i][2]; } rec=new Rectangle2D.Double(minX,minY,maxX-minX,maxY-minY); face=new int[79202][]; int index=0; for(int col=0;col*200<vertex.length-200;col++){ for(int row=0;row<199;row++){ face[index++]=new int[]{ col*200+row, col*200+row+1, col*200+200+row+1 }; face[index++]=new int[]{ col*200+row, col*200+200+row+1, col*200+200+row }; } } return createBranchGroup(); }catch(Exception e){ e.printStackTrace(); return null; } } private float[][] computeColor(){ Stack<Integer> stack=new Stack<Integer>(); float[][] color=new float[vertex.length][]; Gradient gg=GradientFactory.createGradient(GradientFactory.GRADIENT_NATURAL_COLOR); for(int i=nan.length-1;i>=0;i--){ stack.push(new Integer(nan[i])); } for(int i=0;i<vertex.length;i++){ if(stack.isEmpty()){ double vv=getVal(vertex[i][1]); color[i]=gg.getColorByFloat(vv); }else{ Integer p=stack.pop(); if(i==p.intValue()){ color[i]=new float[]{0.0f,0.0f,1.0f,1.0f}; }else{ stack.push(p); double vv=getVal(vertex[i][1]); color[i]=gg.getColorByFloat(vv); } } } return color; } private double getVal(double h){ return (h-minH)/(maxH-minH); } }