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);
	}
	
}