20200116のJavaに関する記事は12件です。

兵庫県全域数値地形図DSMから三次元モデル(PLY形式)を作成してみる

1.概要

先週末、兵庫県が1mメッシュの全県域3次元データセットを公開をしました。
公開データはDSM、DEM、CS立体図で、ライセンスはC.C.4.0、「目的を問わず二次利用可能なデータですので、様々な用途でご利用ください。」とのことです。

これまで公開されていた三次元データセットとしては、国土地理院:基盤地図情報サイトの数値標高モデル(5mメッシュDEM)がありましたが、兵庫県全域数値地形図はその25倍のデータ密度であり、DEMに加え、建物や森林を含めたDSMが公開されています。
さっそくこれを利用して、都市や山林部の三次元モデルを作りたいと思いました。
snapshot00.png

2.今回作るもの

兵庫県公開のDSMは、平面直角座標第5系のx,y座標と標高値からなるデータセットです。三次元モデルを作るには色情報が欲しいところです。
このため、まず、地理院タイル全国最新写真の画像タイルから各メッシュの色情報を取得し、DSMをXZYRGB形式のデータに変換するツールを作成します。
次いで、XZYRGBの頂点データから面を生成し、PLY形式の三次元モデルを生成するツールを作成します。
なお、PLY形式の仕様については、下記のサイトを参考にしました。

3.DSM ->XYZRGB変換ツールの実装

(1)処理の流れ

処理の流れは、以下のとおりです。
 1.DSMのx,y座標(平面直角座標第5系)を検査し、DSM点群の領域を取得する。
 2.DSM点群領域を緯度経度の矩形領域に座標変換する。
 3.緯度経度の矩形領域をピクセル座標に変換し、取得するタイル座標を把握する。
 4.DSMに対応した座標の画像タイルを取得し、連結する。
 5.DSM点群座標をピクセル座標に変換し、取得した航空写真画像から対応するピクセルの色情報を取得する。
 6.「x y z r g b」の様式でテキストファイルに出力する。

(2)座標変換クラス(平面直角座標系<->緯度経度)の実装

まず、平面直角座標系のxy座標を緯度経度に変換するクラスを実装します。
国土地理院時報の「Gauss-Krüger 投影における経緯度座標及び平面直角座標相互間の座標換算についてのより簡明な計算方法」を参考に下記のクラスを作成しました。

LonlatXYT.java
import java.awt.geom.Point2D;

public class LonLatXY {
    private static final double a=6378137;
    private static final double rf=298.257222101;
    private static final double m0=0.9999;
    private static final double s2r=Math.PI/648000;
    private static final double n=0.5/(rf-0.5);
    private static final double n15=1.5*n;
    private static final double anh=0.5*a/(1+n);
    private static final double nsq=n*n;
    private static final double e2n=2*Math.sqrt(n)/(1+n);
    private static final double ra=2*anh*m0*(1+nsq/4+nsq*nsq/64);
    private static int jt=5;
    private static int jt2=2*jt;
    private static double ep=1.0;
    private static double[] e=getE();
    private static final double[] phi0=new double[]{0,33,33,36,33,36,36,36,36,36,40,44,44,44,26,26,26,26,20,26};
    private static final double[] lmbd0=new double[]{0,7770,7860,7930,8010,8060,8160,8230,8310,8390,8450,8415,8535,8655,8520,7650,7440,7860,8160,9240};
    private static double[] alp=getAlp();
    private static double[] beta=getBeta();
    private static double[] dlt=getDlt();


    private static double[] getAlp(){
        double[] alp=new double[6];
        alp[1]=(1.0/2.0+(-2.0/3.0+(5.0/16.0+(41.0/180.0-127.0/288.0*n)*n)*n)*n)*n;
        alp[2]=(13.0/48.0+(-3.0/5.0+(557.0/1440.0+281.0/630.0*n)*n)*n)*nsq;
        alp[3]=(61.0/240.0+(-103.0/140.0+15061.0/26880.0*n)*n)*n*nsq;
        alp[4]=(49561.0/161280.0-179.0/168.0*n)*nsq*nsq;
        alp[5]=34729.0/80640.0*n*nsq*nsq;
        return alp;
    }

    private static double[] getBeta(){
        double[] beta=new double[6];
        beta[1]=(1.0/2.0+(-2.0/3.0+(37.0/96.0+(-1.0/360.0-81.0/512.0*n)*n)*n)*n)*n;
        beta[2]=(1.0/48.0+(1.0/15.0+(-437.0/1440.0+46.0/105.0*n)*n)*n)*nsq;
        beta[3]=(17.0/480.0+(-37.0/840.0-209.0/4480.0*n)*n)*n*nsq;
        beta[4]=(4397.0/161280.0-11.0/504.0*n)*nsq*nsq;
        beta[5]=4583.0/161280.0*n*nsq*nsq;
        return beta;
    }

    private static double[] getDlt(){
        double[] dlt=new double[7];
        dlt[1]=(2.0+(-2.0/3.0+(-2.0+(116.0/45.0+(26.0/45.0-2854.0/675.0*n)*n)*n)*n)*n)*n;
        dlt[2]=(7.0/3.0+(-8.0/5.0+(-227.0/45.0+(2704.0/315.0+2323.0/945.0*n)*n)*n)*n)*nsq;
        dlt[3]=(56.0/15.0+(-136.0/35.0+(-1262.0/105.0+73814.0/2835.0*n)*n)*n)*n*nsq;
        dlt[4]=(4279.0/630.0+(-332.0/35.0-399572.0/14175.0*n)*n)*nsq*nsq;
        dlt[5]=(4174.0/315.0-144838.0/6237.0*n)*n*nsq*nsq;
        dlt[6]=601676.0/22275.0*nsq*nsq*nsq;
        return dlt;
    }

    private static double[] getE(){
        double[] e=new double[jt2+2];
        for(int k=1;k<=jt;k++){
            ep*=e[k]=n15/k-n;
            e[k+jt]=n15/(k+jt)-n;
        }
        return e;
    }

    public static Point2D xyToLonLat(int num,double xx,double yy){
        double x=yy;
        double y=xx;
        double xi=(x+m0*Merid(2*phi0[num]*3600*s2r))/ra;
        double xip=xi;
        double eta=y/ra;
        double etap=eta;
        double sgmp=1;
        double taup=0;
        for(int j=beta.length-1;j>0;j--){
            double besin=beta[j]*Math.sin(2*j*xi);
            double becos=beta[j]*Math.cos(2*j*xi);
            xip -=besin*Math.cosh(2*j*eta);
            etap -=becos*Math.sinh(2*j*eta);
            sgmp -=2*j*becos*Math.cosh(2*j*eta);
            taup +=2*j*besin*Math.sinh(2*j*eta);
        }
        double sxip=Math.sin(xip);
        double cxip=Math.cos(xip);
        double shetap=Math.sinh(etap);
        double chetap=Math.cosh(etap);
        double chi=Math.asin(sxip/chetap);
        double phi=chi;
        for(int j=dlt.length-1;j>=0;j--){
            phi +=dlt[j]*Math.sin(2*j*chi);
        }
        double nphi=(1-n)/(1+n)*Math.tan(phi);

        double lmbd=lmbd0[num]*60+Math.atan2(shetap, cxip)/s2r;
        double lat=phi/s2r/3600;
        double lon=lmbd/3600;
        return new Point2D.Double(lon,lat);
    }

    public static Point2D lonlatToXY(int num,double lon,double lat){
        double phirad=Math.toRadians(lat);
        double lmbddeg=Math.floor(lon);
        double lmbdmin=Math.floor(60.0*(lon-lmbddeg));
        double lmbdsec=lmbddeg*3600.0+lmbdmin*60.0+(lon-lmbddeg-lmbdmin/60)*3600.0;

        double sphi=Math.sin(phirad);
        double nphi=(1-n)/(1+n)*Math.tan(phirad);
        double dlmbd=(lmbdsec-lmbd0[num]*60.0)*s2r;
        double sdlmbd=Math.sin(dlmbd);
        double cdlmbd=Math.cos(dlmbd);
        double tchi=Math.sinh(atanh(sphi)-e2n*atanh(e2n*sphi));
        double cchi=Math.sqrt(1+tchi*tchi);
        double xip=Math.atan2(tchi, cdlmbd);
        double xi=xip;
        double etap=atanh(sdlmbd/cchi);
        double eta=etap;
        double sgm=1;
        double tau=0;
        for(int j=alp.length-1;j>0;j--){
            double alsin=alp[j]*Math.sin(2*j*xip);
            double alcos=alp[j]*Math.cos(2*j*xip);
            xi +=alsin*Math.cosh(2*j*etap);
            eta +=alcos*Math.sinh(2*j*etap);
            sgm +=2*j*alcos*Math.cosh(2*j*etap);
            tau +=2*j*alsin*Math.sinh(2*j*etap);
        }
        double x=ra*xi-m0*Merid(2*phi0[num]*3600*s2r);
        double y=ra*eta;
        return new Point2D.Double(x,y);
    }

    private static double Merid(double phi2) {
            double dc=2.0*Math.cos(phi2);
            double[] s=new double[jt2+2];
            double[] t=new double[jt2+2];
            s[1]=Math.sin(phi2);
            for(int i=1;i<=jt2;i++){
                s[i+1]=dc*s[i]-s[i-1];
                t[i]=(1.0/i-4.0*i)*s[i];
            }
            double sum=0.0;
            double c1=ep;
            int j=jt;
            while(j>0){
                double c2=phi2;
                double c3=2.0;
                int l=j;
                int m=0;
                while(l>0){
                    c2 +=(c3/=e[l--])*t[++m]+(c3*=e[2*j-l])*t[++m];
                }
                sum +=c1*c1*c2 ; c1/=e[j--];
            }
            return anh*(sum+phi2);
    }

    private static double atanh(double v){
        return 0.5*Math.log((1.0+v)/(1.0-v));
    }
}

(3)航空写真タイル画像取得クラスの実装

地理院地図からタイル画像を取得・連結する下記クラスを作成しました。

GSITileReader.java
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.imageio.ImageIO;

public class GSITileReader {
    private static final String base_url="https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/";
    private static final double L=85.05112877980659;

    public static BufferedImage getGSIImage(Rectangle2D plane,int map_num,int zoom,double scale)throws IOException{
        Shape sp=getLonLatShapeAtPlane(map_num, plane.getX(), plane.getY(), plane.getWidth(), plane.getHeight());
        Rectangle2D rect=sp.getBounds2D();
        long[] topLeft=lonlatToPixel(zoom,rect.getX(),rect.getY());
        long[] bottomRight=lonlatToPixel(zoom,rect.getX()+rect.getWidth(),rect.getY()+rect.getHeight());
        Set<Long> x=new HashSet<Long>();
        Set<Long> y=new HashSet<Long>();
        for(long i=topLeft[1];i<=bottomRight[1];i++){
            x.add((long)Math.ceil(i/256));
        }
        for(long i=bottomRight[2];i<=topLeft[2];i++){
            y.add((long)Math.ceil(i/256));
        }
        Long[] xx=x.toArray(new Long[x.size()]);
        Long[] yy=y.toArray(new Long[y.size()]);
        Arrays.sort(xx);
        Arrays.sort(yy);
        BufferedImage im=new BufferedImage(xx.length*256,yy.length*256,BufferedImage.TYPE_INT_RGB);
        Graphics2D g=im.createGraphics();
        for(int i=0;i<xx.length;i++){
            for(int j=0;j<yy.length;j++){
                try{
                    String url=base_url+Integer.toString(zoom)+"/"+Long.toString(xx[i])+"/"+Long.toString(yy[j])+".jpg";
                    BufferedImage tmp=ImageIO.read(new URL(url));
                    g.drawImage(tmp, i*256, j*256, null);
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
        g.dispose();
        im=subImage(im,plane,(long)xx[0],(long)yy[0],map_num,zoom,scale);
        return im;
    }

    private static BufferedImage subImage(BufferedImage src,Rectangle2D rect,long minX,long minY,int num,int zoom,double scale){
        double xx=rect.getX();
        double yy=rect.getY();
        double ww=Math.abs(rect.getWidth());
        double hh=Math.abs(rect.getHeight());
        minX=minX*256;
        minY=minY*256;
        BufferedImage dst=new BufferedImage((int)(ww/scale),(int)(hh/scale),BufferedImage.TYPE_INT_RGB);
        System.out.println(dst.getWidth()+"/"+dst.getHeight());
        System.out.println(minX+"/"+minY);
        for(int i=0;i<dst.getWidth();i++){
            for(int j=0;j<dst.getHeight();j++){
                double x=xx+scale*i;
                double y=yy-scale*j;
                Point2D p=LonLatXY.xyToLonLat(num, x, y);
                long[] pc=lonlatToPixel(zoom, p.getX(), p.getY());
                int px=(int)(pc[1]-minX);
                int py=(int)(pc[2]-minY);
                int color=src.getRGB(px, py);
                dst.setRGB(i, j, color);
            }
        }
        return dst;
    }

    public static long[] lonlatToPixel(int zoom,double lon,double lat){
        long x=(long)(Math.pow(2, zoom+7)*(lon/180.0+1.0));
        long y=(long)((Math.pow(2, zoom+7)/Math.PI)*(-atanh(Math.sin(Math.toRadians(lat)))+atanh(Math.sin(Math.toRadians(L)))));
        return new long[]{(long)zoom,x,y};
    }

    private static double atanh(double v){
        return 0.5*Math.log((1.0+v)/(1.0-v));
    }

    public static AffineTransform createTfwTransform(Rectangle2D rectXY,BufferedImage img){
        double sx=rectXY.getWidth()/img.getWidth();
        double sy=rectXY.getHeight()/img.getHeight();
        double x=rectXY.getX();
        double y=rectXY.getY()+rectXY.getHeight();
        AffineTransform af=new AffineTransform(new double[]{sx,0,0,-sy,x,y});
            return af;
    }

    public static Shape getLonLatShapeAtPlane(int num,double x,double y,double w,double h){
        Point2D p1=LonLatXY.xyToLonLat(num, x, y);
        Point2D p2=LonLatXY.xyToLonLat(num, x+w, y);
        Point2D p3=LonLatXY.xyToLonLat(num, x+w, y+h);
        Point2D p4=LonLatXY.xyToLonLat(num, x, y+h);
        GeneralPath gp=new GeneralPath();
        gp.moveTo(p1.getX(),p1.getY());
        gp.lineTo(p2.getX(), p2.getY());
        gp.lineTo(p3.getX(), p3.getY());
        gp.lineTo(p4.getX(), p4.getY());
        gp.closePath();
        return gp;
    }

    public static void outTfw(AffineTransform af,File out)throws IOException{
        BufferedWriter bw=new BufferedWriter(new FileWriter(out));
        bw.write(af.getScaleX()+"\n");
        bw.write(af.getShearX()+"\n");
        bw.write(af.getShearY()+"\n");
        bw.write(af.getScaleY()+"\n");
        bw.write(af.getTranslateX()+"\n");
        bw.write(af.getTranslateY()+"\n");
        bw.close();
    }
}

(4)XYZRGB出力クラス

最後にファイル入出力など、DSMをXYZRGBに変換するツールを作成しました。
本クラスでは、取得した航空写真画像からDSM座標に対応したピクセルのRGBを出力し、XYZRGB形式で出力しています。
一応、平面直角座標第5系以外にも対応できるようにしています。

XYZRgbCreator.java
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.UIManager;

public class XYZRgbCreator {

    private JFrame frame;
    private JComboBox<String> zone;

    public XYZRgbCreator(){
        frame=new JFrame();
        frame.setTitle("XYZRGB");
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
            SwingUtilities.updateComponentTreeUI(frame);
        }catch(Exception e){
            try {
                UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
                SwingUtilities.updateComponentTreeUI(frame);
            }catch(Exception ee){
                ee.printStackTrace();
            }
        }
        WindowAdapter wa=new WindowAdapter(){
            @Override
            public void windowClosing(WindowEvent e) {
                close();
            }
        };
        frame.addWindowListener(wa);
        frame.getContentPane().setLayout(new BorderLayout());
        zone=new JComboBox<String>(new String[]{
                null,"平面直角第01系","平面直角第02系","平面直角第03系","平面直角第04系","平面直角第05系","平面直角第06系","平面直角第07系","平面直角第08系",
                "平面直角第09系","平面直角第10系","平面直角第11系","平面直角第12系","平面直角第13系","平面直角第14系","平面直角第15系","平面直角第16系",
                "平面直角第17系","平面直角第18系","平面直角第19系"
            });
        zone.setSelectedIndex(5);
        frame.getContentPane().add(zone,BorderLayout.NORTH);
        JLabel label=new JLabel("DSM > XYZRGB");
        label.setFont(new Font(Font.SANS_SERIF,Font.BOLD,24));
        label.setVerticalAlignment(JLabel.CENTER);
        label.setHorizontalAlignment(JLabel.CENTER);
        JPanel jp=new JPanel(new BorderLayout());
        jp.add(label,BorderLayout.CENTER);
        frame.getContentPane().add(jp,BorderLayout.CENTER);
        frame.setSize(480,480);
        frame.setResizable(false);
        DropFileHandler handler=new DropFileHandler();
        label.setTransferHandler(handler);
        jp.setTransferHandler(handler);
    }

    private void close(){
        int id=JOptionPane.showConfirmDialog(frame, "Exit?", "Info", JOptionPane.YES_NO_OPTION,JOptionPane.INFORMATION_MESSAGE);
        if(id==JOptionPane.YES_OPTION){
            frame.setVisible(false);
            System.exit(0);
        }
    }

    private class DropFileHandler extends TransferHandler {
        private static final long serialVersionUID = 1L;
        @Override
        public boolean canImport(TransferSupport support) {
            if (!support.isDrop()) {
                return false;
            }
            if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                return false;
            }
            return true;
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean importData(TransferSupport support) {
            if (!canImport(support)) {
                return false;
            }
            Transferable t = support.getTransferable();
            try {
                List<File> files = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
                for (File file : files){
                    if(file.isDirectory())continue;
                    try{
                        process(file);
                    }catch(IOException e){
                        e.printStackTrace();
                    }
                }
            } catch (UnsupportedFlavorException | IOException e) {
                e.printStackTrace();
            }
            return true;
        }
    }

    private void process(File f) throws IOException{
        if(!(f.getName().endsWith(".txt")||f.getName().endsWith(".xyz")))return;
        frame.repaint();
        int bwz=zone.getSelectedIndex();
        Rectangle2D rectXY=getBounds(f);
        BufferedImage img=GSITileReader.getGSIImage(rectXY,bwz,18,0.5);
        String name=f.getName().substring(0,f.getName().lastIndexOf("."));
        File dir=f.getParentFile();
        ImageIO.write(img, "jpg", new File(dir.getAbsolutePath()+"/"+name+".jpg"));
        AffineTransform af=new AffineTransform(new double[]{
                0.5,0,0,-0.5,rectXY.getX(),rectXY.getY()});
        GSITileReader.outTfw(af, new File(dir.getAbsolutePath()+"/"+name+".jgw"));
        try{
            af=af.createInverse();
            BufferedReader br=new BufferedReader(new FileReader(f));
            File out=new File(dir.getAbsolutePath()+"/"+name+"_color.txt");
            BufferedWriter bw=new BufferedWriter(new FileWriter(out));
            String line=null;
            String str=null;
            Rectangle2D imgRect=new Rectangle2D.Double(0, 0, img.getWidth(),img.getHeight());
            while((line=br.readLine())!=null){
                line=line.replaceAll(" ",",");
                String[] sp=line.split(",");
                double x=Double.parseDouble(sp[0]);
                double y=Double.parseDouble(sp[1]);
                Point2D p=af.transform(new Point2D.Double(x,y), new Point2D.Double());
                int xx=(int)Math.floor(p.getX());
                int yy=(int)Math.floor(p.getY());
                if(imgRect.contains(xx, yy)){
                    int col=img.getRGB(xx, yy);
                    Color color=new Color(col);
                    str=line+" "+Integer.toString(color.getRed())+" "+Integer.toString(color.getGreen())+" "+Integer.toString(color.getBlue())+"\n";
                }else{
                    str=line+" 0 0 0\n";
                }
                bw.write(str);
                bw.flush();
            }
            br.close();
            bw.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    private Rectangle2D getBounds(File f)throws IOException{
        double xmin=Double.MAX_VALUE;
        double xmax=-Double.MAX_VALUE;
        double ymin=Double.MAX_VALUE;
        double ymax=-Double.MAX_VALUE;
        BufferedReader br=new BufferedReader(new FileReader(f));
        String line=null;
        while((line=br.readLine())!=null){
            line=line.replaceAll(" ",",");
            String[] sp=line.split(",");
            double x=Double.parseDouble(sp[0]);
            double y=Double.parseDouble(sp[1]);
            xmin=Math.min(x, xmin);
            xmax=Math.max(x, xmax);
            ymin=Math.min(y, ymin);
            ymax=Math.max(y, ymax);
        }
        br.close();
        Rectangle2D ret=new Rectangle2D.Double(xmin,ymax,xmax-xmin+1,-(ymax-ymin)-1);
        return ret;
    }

    public static void main(String[] args){
        XYZRgbCreator gp=new XYZRgbCreator();
        gp.frame.setLocationRelativeTo(null);
        gp.frame.setVisible(true);
    }
}

(5)成果物

XYZRgbCreatorを実行すると下記のウインドウが表示されます。兵庫県全域数値地形図DSMファイルをウインドウ内にドロップすれば、処理が開始され、xyzrgbファイルが出力されます。
なお、本来は処理の進捗情報を表示すべきなのですが、今回は割愛しました。

999.jpg

4.RGBXYZ -> PLY変換ツールの実装

次いで、RGBXYZファイルをPLYファイルに変換するツールを実装します。
DSMモデルはサーフェスモデルと呼ばれる表面のみが定義された3次元構造のモデルです。このため、2次元Delaunay分割を実行し、三角形ポリゴンをFaceにすれば三次元モデルが生成できると考えました。
なお、Delaunay分割には、2D Delaunay Triangulationライブラリ「TINFOUR」を利用しました。
mvn repositiry:tinfourから必要な情報を確認し、pomファイルのdependancisに追記しました。

(1)処理の流れ

処理の流れは以下のとおりです。
 1.XYZRGBファイルのXYZ情報から頂点リストを、RGB情報から色情報リストを作成する。
 2.IncrementalTinインスタンスに頂点リストを渡し、2次元Delaunay分割を実行する。
 3.PLYの仕様に合わせ、必要事項を出力する。
 4.頂点と色情報を出力する。
 5.面(Face)情報を出力する。

(2)PlyCreatorクラスの実装

上記の処理を実行するPlyCreatorクラスを実装します。
なお、めんどくさくなったので、コマンドラインツールとして実装しています。

PlyCreator.java
import java.awt.Color;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.function.Consumer;

import org.tinfour.common.SimpleTriangle;
import org.tinfour.common.Vertex;
import org.tinfour.standard.IncrementalTin;
import org.tinfour.utils.TriangleCollector;

public class PlyCreator extends Observable{
    private List<Vertex> vertexs;
    private List<Color> colors;
    private IncrementalTin tin;

    public PlyCreator(){}

    private void outVertex(BufferedWriter bw)throws IOException{
        System.out.println("Vertex出力");
        for(Vertex v : vertexs){
            StringBuffer buf=new StringBuffer();
            buf.append(Float.toString((float)v.getX())+" ");
            buf.append(Float.toString((float)v.getY())+" ");
            buf.append(Float.toString((float)v.getZ())+" ");
            Color c=colors.get(v.getIndex());
            buf.append(Integer.toString(c.getRed())+" ");
            buf.append(Integer.toString(c.getGreen())+" ");
            buf.append(Integer.toString(c.getBlue())+"\n");
            writeBytes(bw,buf.toString());
        }
    }

    private void outFace(BufferedWriter bw)throws IOException{
        System.out.println("Face出力");
        Consumer<SimpleTriangle> cons=new Consumer<SimpleTriangle>() {
            @Override
            public void accept(SimpleTriangle arg){
                StringBuffer buf=new StringBuffer();
                buf.append("3");
                buf.append(" "+Integer.toString(arg.getVertexA().getIndex()));
                buf.append(" "+Integer.toString(arg.getVertexB().getIndex()));
                buf.append(" "+Integer.toString(arg.getVertexC().getIndex()));
                buf.append("\n");
                try{
                    writeBytes(bw,buf.toString());
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        };
        TriangleCollector.visitSimpleTriangles(tin, cons);
    }

    private void writeBytes(BufferedWriter bw,String str)throws IOException{
        bw.write(str,0,str.length());
    }

    public void writePLY(File f)throws IOException{
        System.out.println("PLY出力");
        Charset charset = Charset.forName("US-ASCII");
        BufferedWriter bw = Files.newBufferedWriter(f.toPath(),charset);
        writeBytes(bw,"ply\n");
        writeBytes(bw,"format ascii 1.0\n");
        writeBytes(bw,"element vertex "+Integer.toString(vertexs.size())+"\n");
        writeBytes(bw,"property float x\n");
        writeBytes(bw,"property float y\n");
        writeBytes(bw,"property float z\n");
        writeBytes(bw,"property uchar red\n");
        writeBytes(bw,"property uchar green\n");
        writeBytes(bw,"property uchar blue\n");
        writeBytes(bw,"element face "+Integer.toString(tin.countTriangles().getCount())+"\n");
        writeBytes(bw,"property list uchar int vertex_index\n");
        writeBytes(bw,"end_header\n");
        outVertex(bw);
        outFace(bw);;
        bw.close();
    }

    public void readXYZGRB(File f,String separator)throws IOException{
        System.out.println("ファイル読み込み");
        BufferedReader br=new BufferedReader(new FileReader(f));
        String line=null;
        vertexs=new ArrayList<Vertex>();
        colors=new ArrayList<Color>();
        int id=0;
        while((line=br.readLine())!=null){
            String[] sp=line.split(separator);
            Vertex v=new Vertex(
                Double.parseDouble(sp[0]),
                Double.parseDouble(sp[1]),
                Double.parseDouble(sp[2]),
                id++
            );
            vertexs.add(v);
            Color c=new Color(
                Integer.parseInt(sp[3]),
                Integer.parseInt(sp[4]),
                Integer.parseInt(sp[5])
            );
            colors.add(c);
        }
        br.close();
        System.out.println("TIN処理");
        tin=new IncrementalTin();
        tin.add(vertexs, null);
    }

    public static void main(String[] args){
         PlyCreator pc=new  PlyCreator();
         File in=new File(args[0]);
         File out=new File(args[1]);
         try{
             pc.readXYZGRB(in, ",");
             pc.writePLY(out);
         }catch(Exception e){
             e.printStackTrace();
         }
    }
}

(3)成果物

入力ファイル(XYZRGBファイル)と出力ファイル(PLYファイル)を引数としてPlyCreatorクラスを実行するとPLY形式の三次元モデルが生成されます。
898.jpg

MeshLab等の3次元ビュアーにPLYファイルを読み込ませると、作成したモデルが表示されます。
下の画像はJR三ノ宮駅付近のDSMから生成したモデル画像です。
snapshot02.png

5.まとめ

国土地理院航空写真から色情報を取得して兵庫県全域数値地形図DSMをXYZRGBファイルに変換し、PLY形式のモデルを生成してみました。
都市域のモデルをみると多少荒く感じられるかもしれませんが、以下の画像は佐用町の山林部のDSMをモデル化したものですが、1mメッシュなら林相による樹冠の違いが表現された地形モデルを得ることができます。
snapshot00.png

兵庫県ホームページでは「このデータや他のデータと最先端技術を組み合わせて、民・産・学・官の協働による地域課題の解決に向けた取組を推進するため、利活用のアイデア・提案を募集します。」とありますが、兵庫県全域数値地形図は、兵庫県全域のDSM、DEMが公開されているので、色々な事に使えそうです。
兵庫県以外では、静岡県が「静岡ポイントクラウドデータベース」で点群データを公開していますが、他県でもこうした公共測量データ公開の動きがあると面白いと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NLP4J [006-034c] NLP4J で言語処理100本ノック #34 「AのB」をさらにスマートに解いてみる(完結編)

課題

NLP4J [006-034b] NLP4J で言語処理100本ノック #34 「AのB」の Annotator を作ってみる では以下のようなロジックを切り出して定義することでロジックの再利用を可能にしていました。

ところがこれでもまだ足りないのです。
キーワードの抽出ルールをロジックで書いているため、柔軟性が足りないのです。
「AのB」であればこのロジックで問題ありませんが、では「AがB」のようなものを抽出したいときはまた別のロジックを用意しなければなりません。

/**
 * 「名詞の名詞」を「word_nn_no_nn」キーワードとして抽出します。
 * @author Hiroki Oya
 */
public class Nokku34Annotator extends AbstractDocumentAnnotator implements DocumentAnnotator {
    @Override
    public void annotate(Document doc) throws Exception {
        ArrayList<Keyword> newkwds = new ArrayList<>();
        Keyword meishi_a = null;
        Keyword no = null;
        for (Keyword kwd : doc.getKeywords()) {
            if (meishi_a == null && kwd.getFacet().equals("名詞")) {
                meishi_a = kwd;
            } //
            else if (meishi_a != null && no == null && kwd.getLex().equals("の")) {
                no = kwd;
            } //
            else if (meishi_a != null && no != null && kwd.getFacet().equals("名詞")) {
                Keyword kw = new DefaultKeyword();
                kwd.setLex(meishi_a.getLex() + no.getLex() + kwd.getLex());
                kwd.setFacet("word_nn_no_nn");
                kwd.setBegin(meishi_a.getBegin());
                kwd.setEnd(kwd.getEnd());
                kwd.setStr(meishi_a.getStr() + no.getStr() + kwd.getStr());
                kwd.setReading(meishi_a.getReading() + no.getReading() + kwd.getReading());
                newkwds.add(kw);
                meishi_a = null;
                no = null;
            } //
            else {
                meishi_a = null;
                no = null;
            }
        }
        doc.addKeywords(newkwds);
    }
}

どうやって解くか

言語処理100本ノック 2015 #34 を改めてみてみると、#34 は単に

「AのB」
2つの名詞が「の」で連結されている名詞句を抽出せよ

と書いてあるのみです。
この問題を解くためだけにわざわざロジックを作るということはAIっぽくありません。

そこで、独自のルール記述を開発することにします。

ルールの記述

2つの名詞が「の」で連結されている名詞句を抽出せよ

とのことなので、これを書くことができるようなルールを作ってみましょう。

みんな大好きなJSONで書いてみると、ルールとしては以下のような感じでしょうか。

[{'facet':'名詞'},{'lex':'の'},{'facet':'名詞'}]

JSON配列としてキーワードが並んでいて、「名詞,の,名詞」のようになっているのが抽出のルールとします。

抽出結果の記述

2つの名詞が「の」で連結されている名詞句を抽出せよ

とありますが、名詞句の何を抽出するのかは明記されていません。
人間的に考えてみると正規形(原形)だとしておきます。

抽出結果の記述ルールとしても文法を何にするかはいろいろ考えられますが、日本で最も大規模に使われているエンタープライズ向けテキストマイニングソフトウェアの IBM Watson Explorer と同じ形式にしておきたいと思います。(あえてJSONにはしない...)

IBM Watson Explorer のルールファイルについてのドキュメントは以下のものになります。
コンテンツ分析コレクションのカスタム・ルール・ファイル

難しいマニュアルですが、キーワード抽出部の書き方としては以下のようになります。

${0.lex}-${1.lex}-${2.lex}

数値は抽出されたキーワードのインデックス値です。
ピリオドの後に続く文字列(ここではlex)はキーワードの属性値です。lexは原形を意味します。
日本語に翻訳すると

${0番目Keywordの原形}-${1番目Keywordの原形}-${2番目Keywordの原形}

となり、抽出されたキーワードの原形をハイフンで連結するという記述になります。

${...} の部分以外は適当に記述できるので、単に連結して

${0.lex}${1.lex}${2.lex}

ともできますし、

${0.lex} ... ${1.lex} ... ${2.lex}

のようにも記述できます。

CODE

コードとしては

String rule = "[{facet:'名詞'},{lex:'の'},{facet:'名詞'}]";
String facet = "word_nn_no_nn";
String value = "${0.lex}-${1.lex}-${2.lex}";

という設定だけを用意して、これで抽出できればスマートということになります。
ちょっとAIっぽくなりましたね。(ルールを自然言語で書ければさらにスマートですが...)

指定されたルールでキーワードを抽出するAnnotatorとして
nlp4j.annotator.KeywordSequencePatternAnnotator
を用意しました。コードが長くなったのでここでの掲載は省略します。

String rule = "[{facet:'名詞'},{lex:'の'},{facet:'名詞'}]"; // #34 これだけ
String facet = "word_nn_no_nn"; // #34 これだけ
String value = "${0.lex}-${1.lex}-${2.lex}"; // #34 これだけ

// NLP4Jが提供するテキストファイルのクローラーを利用する
Crawler crawler = new TextFileLineSeparatedCrawler();
crawler.setProperty("file", "src/test/resources/nlp4j.crawler/neko_short_utf8.txt");
crawler.setProperty("encoding", "UTF-8");
crawler.setProperty("target", "text");

// ドキュメントのクロール
List<Document> docs = crawler.crawlDocuments();

// NLPパイプライン(複数の処理をパイプラインとして連結することで処理する)の定義
DocumentAnnotatorPipeline pipeline = new DefaultDocumentAnnotatorPipeline();
{
    // Yahoo! Japan の形態素解析APIを利用するアノテーター
    DocumentAnnotator annotator = new YJpMaAnnotator();
    pipeline.add(annotator);
}
{
    KeywordSequencePatternAnnotator annotator = new KeywordSequencePatternAnnotator();
    annotator.setProperty("rule[0]", rule);
    annotator.setProperty("facet[0]", facet);
    annotator.setProperty("value[0]", value);
    pipeline.add(annotator);
}
// アノテーション処理の実行
pipeline.annotate(docs);

System.err.println("<抽出されたキーワード>");
for (Document doc : docs) {
    for (Keyword kwd : doc.getKeywords(facet)) {
        System.err.println(kwd);
    }
}
System.err.println("</抽出されたキーワード>");

結果

以下のようになりました。
ルールを指定するだけでキーワードを抽出できましたね!

<抽出されたキーワード>
彼-の-掌 [sequence=-1, facet=word_nn_no_nn, lex=彼-の-掌, str=彼-の-掌, reading=null, count=-1, begin=2, end=5, correlation=0.0]
掌-の-上 [sequence=-1, facet=word_nn_no_nn, lex=掌-の-上, str=掌-の-上, reading=null, count=-1, begin=0, end=3, correlation=0.0]
書生-の-顔 [sequence=-1, facet=word_nn_no_nn, lex=書生-の-顔, str=書生-の-顔, reading=null, count=-1, begin=11, end=15, correlation=0.0]
はず-の-顔 [sequence=-1, facet=word_nn_no_nn, lex=はず-の-顔, str=はず-の-顔, reading=null, count=-1, begin=13, end=17, correlation=0.0]
顔-の-真中 [sequence=-1, facet=word_nn_no_nn, lex=顔-の-真中, str=顔-の-真中, reading=null, count=-1, begin=5, end=9, correlation=0.0]
穴-の-中 [sequence=-1, facet=word_nn_no_nn, lex=穴-の-中, str=穴-の-中, reading=null, count=-1, begin=6, end=9, correlation=0.0]
</抽出されたキーワード>

Maven

以上のコードは nlp4j-core 1.2.0.0 以上で動作します。

<dependency>
  <groupId>org.nlp4j</groupId>
  <artifactId>nlp4j-core</artifactId>
  <version>1.2.0.0</version>
</dependency>

1.2.0.0 はビルド+アップロードからMavenでデプロイされるまで、12時間以上かかったようです。
サーバーが混雑している?ようです。

所感

ルールを記述して解くというのは他の言語処理100本ノック#34と比較してもスマートだと思います。

まとめ

NLP4J を使うと、Javaで簡単に自然言語処理ができますね!

プロジェクトURL

https://www.nlp4j.org/
NLP4J_N_128.png


Indexに戻る

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringSecurityで認証機能を実装①

現在、フレームワークの勉強中でSpringBootを扱っております。
その中でログイン機能を実装する際に、SpringSecurityの認証機能を使うのが定石らしく、これを使って実装いたしました。
その際にだいぶ難儀しまして、実装までにだいぶ時間を要したので自分なりにまとめてみました。

1、SpringSecurityのイメージ

SpringBoot_Di_Security_DB.png

すごく簡素なイメージ申し訳ないのですが、イメージ的にはこんな感じになります。
SpringBootのDI(依存性の注入)という機能を使ってSpringSecurityを外部から読み込んでDBとやり取りを行います
DIに関しては詳しく説明しませんが、外部のオブジェクトをDIコンテナという箱に入れておくことで、いつでもオブジェクトの機能を使うことができるみたいなイメージだと思います。
今回の場合そのオブジェクトというのが”SpringSecurity”ということです。
またSpringSecurityにはDBに接続をするメソッドが備わっていますが、実際にはSpring Data JDBCが担いますので、上の図で言うとSpringSecurityとDBの間にはJDBCが入ることになります。

2、実装の流れ。

さっそく実装をしていきますが、簡単に実装の流れを説明していきます。

1、MySQLでListを表示させる。
2、ログイン画面の実装。
3、SpringSecurityの認証機能の実装。

このような形になります。3番が今回のメインかつ一番ボリュームが大きいので1番2番を確実に実装してから3番に移行したほうが後でエラーになった時に解決しやすいです。
自分の場合最初から3番を実装しようとしてMySQLの読み込みでエラーになったりログイン画面のリダイレクト方法を間違えていたりと3番実装以前の段階でエラーが出てしまい解決に時間をかけてしまいました。

3、MySQLでListを表示させる。

では開発に取り掛かりましょう。
前提条件として”STS”と”MySQL”は使っておけるようにしておきましょう。

3-1,プロジェクトを作成します。

 「ファイル」→「新規」→「Spring Boot」→「Spring スターター・プロジェクト」
 以下の画像を参照しアプリ名やグループ名、JAVAのバージョンなどは合わせておくといいでしょう。
 SpringBoot_Di_Security_DB_2.png

 SpringBoot_Di_Security_DB_3.png

・プロジェクトの確認。
 以下の様なファイルが作成されていればOKです。
 SpringBoot_Di_Security_DB_4.png

・プロジェクト起動します。
 「プロジェクト右クリック」→「実行」→「Spring Boot アプリケーション」の順で起動します。そうすると以下の様なエラーが吐かれると思います。

コンソール
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-01-16 15:39:27.382 ERROR 49572 --- [  restartedMain] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
    If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

「Failed to configure a DataSource・・・」のところを翻訳すると、データソースの設定に失敗した。ドライバーを特定できないと書いてあります。
更に「If you want an embedded database ・・・」のところを翻訳すると、データベースを使う場合はクラスパスに配備しろ、データベースのロードにはプロファイルをアクティブ化しろ、と書いてあります。
このことから、今回使いたいMySQLを使うためにプロファイルに設定を書く必要があることが何となく理解できるかと思います。

DBを使う場合は「application.property」を編集します。

3-2,application.propertyの編集。

 <img width=
SpringBoot_Di_Security_DB_5.png" src="" class="autolink">https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420031/bcbf58f1-9d17-16f3-472e-013050535ca7.png">

application.property
#//  jdbc:mysql://localhost:3306/login_app はMySQlのURL。それ以降はタイムゾーンの設定をしています。詳しくは[こちら](https://qiita.com/KKZ@github/items/e3f594b04c9233a86419)
spring.datasource.url=jdbc:mysql://localhost:3306/login_app?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
#// rootユーザでログインしています。
spring.datasource.username=root
spring.datasource.password=
#// MySQLのドライバー
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.jpa.database=MYSQL
spring.session.store-type=jdbc

3-3,MySQLの立ち上げ。

 コマンドプロンプトまたはPowerShellにてMySQLにログインします。
 以下実際に打つコマンドは「実行コマンド」、コマンド実行時の表示結果などを一色単にしたものは「コマンド実行例」になります。参考にしながら進めてください。

実行コマンド
mysql -uroot

create database login_app;

use login_app;

CREATE TABLE user(
    user_id INT AUTO_INCREMENT,
    user_name VARCHAR(255),
    password VARCHAR(255),
    PRIMARY KEY(user_id)
);

desc user;

INSERT INTO user (user_name, password) VALUES
(
    'user1',
    'pass1+'
),
(
    'yama',
    'kawa'
);

select * from user;

コマンド実行例
PS C:\Users\MGT-RD18> mysql -uroot
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2246
Server version: 5.7.28-log MySQL Community Server (GPL)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>
mysql>
mysql> create database login_app;
Query OK, 1 row affected (0.02 sec)

mysql> use login_app;
Database changed
mysql>
mysql>

mysql>
mysql> CREATE TABLE user(
    ->     user_id INT AUTO_INCREMENT,
    ->     user_name VARCHAR(255),
    ->     password VARCHAR(255),
    ->     PRIMARY KEY(user_id)
    -> );
Query OK, 0 rows affected (0.05 sec)

mysql>
mysql> desc user;
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| user_id   | int(11)      | NO   | PRI | NULL    | auto_increment |
| user_name | varchar(255) | YES  |     | NULL    |                |
| password  | varchar(255) | YES  |     | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

mysql>
mysql> INSERT INTO user (user_name, password) VALUES
    -> (
    ->     'user1',
    ->     'pass1+'
    -> ),
    -> (
    ->     'yama',
    ->     'kawa'
    -> );
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql>
mysql> select * from user;
+---------+-----------+----------+
| user_id | user_name | password |
+---------+-----------+----------+
|       1 | user1     | pass1+   |
|       2 | yama      | kawa     |
+---------+-----------+----------+
2 rows in set (0.00 sec)

mysql>


 最終的に「select * from user;」の結果が上記のようになっていればOKです。
ここまでくればアプリは実行されるはずです。

3-4,プロジェクト起動します。

 「プロジェクト右クリック」→「実行」→「Spring Boot アプリケーション」の順で起動します。そうすると正常に起動されるはずです。特にErrorなどが出なければOKです。

コンソール出力例
 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.2.RELEASE)

2020-01-16 16:38:52.112  INFO 50328 --- [  restartedMain] SpringLogin.app.SpringLoginApplication   : Starting SpringLoginApplication on MGT-RD18 with PID 50328 (started by MGT-RD18 in C:\project\テスト用\SpringLogin)
2020-01-16 16:38:52.127  INFO 50328 --- [  restartedMain] SpringLogin.app.SpringLoginApplication   : No active profile set, falling back to default profiles: default
2020-01-16 16:38:52.174  INFO 50328 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2020-01-16 16:38:52.174  INFO 50328 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2020-01-16 16:38:52.822  INFO 50328 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JDBC repositories in DEFAULT mode.
2020-01-16 16:38:52.837  INFO 50328 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 12ms. Found 0 JDBC repository interfaces.
2020-01-16 16:38:53.137  INFO 50328 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-01-16 16:38:53.397  INFO 50328 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-01-16 16:38:53.413  INFO 50328 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-01-16 16:38:53.413  INFO 50328 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.29]
2020-01-16 16:38:53.491  INFO 50328 --- [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-01-16 16:38:53.491  INFO 50328 --- [  restartedMain] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1317 ms
2020-01-16 16:38:53.761  INFO 50328 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-01-16 16:38:53.871  WARN 50328 --- [  restartedMain] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
2020-01-16 16:38:54.058  INFO 50328 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2020-01-16 16:38:54.109  INFO 50328 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-01-16 16:38:54.113  INFO 50328 --- [  restartedMain] SpringLogin.app.SpringLoginApplication   : Started SpringLoginApplication in 2.453 seconds (JVM running for 3.661)
2020-01-16 16:39:08.470  INFO 50328 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-01-16 16:39:08.470  INFO 50328 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-01-16 16:39:08.485  INFO 50328 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 15 ms


 この状態だとアプリ自体は起動されましたが、localhost:8080で確認することができません。理由は表示する、Viewやcontrollerを作っていないからです。

3-5,Viewとコントローラを作成しとりあえずページを表示する。

 完成イメージは以下になります。いきなり全て作ると混乱しますので、ここでも段階的に作成していきます。今回のゴールは取りあえずViewを表示するだけです。

 SpringBoot_Di_Security_DB_6.png

 ・必要なファイルを作成して、以下のコードを記述します。

SpringLogin.app.controller/HomeController.java
package SpringLogin.app.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    //userListのアドレスにアクセスした際にGetメソッドを実行。
    @GetMapping("/userList")
    public String getUserList(Model model) {

        //template配下のファイル名を指定することでViewを呼び出せる。     
        return "userList";
    }
}

templates/userList.html
<!DOCTYPE html>
<!-- thymeleafを使うため以下を記述する。以後はth:xxxとすればthymeleafのメソッドが使用可能。 -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"></meta>
</head>
<body>
    <!-- コンテンツ部分 -->
    <div>
        <h1>ユーザリスト</h1>
        <table>
            <tr>
                <th>user_id</th>
                <th>user_name</th>
                <th>password</th>
            </tr>
            <!-- ControllerからuserListを受け取りth:each文で配列の要素を回します。 -->
            <!-- 引数のuserにはUserクラスのインスタンスが格納されているので、フィールド名を指定すれば値を引き出せます。 -->
            <tr th:each="user : ${userList}">
                <td th:text="${user.userId}"></td>
                <td th:text="${user.userName}"></td>
                <td th:text="${user.password}"></td>
            </tr>

        </table>
    </div>
</body>
</html>

・記述が出来たらこれで表示は出来るはずです。

3-6,アプリをブラウザで表示させる。

・Eclipse上でSpringBootアプリケーションが立ち上がっていることを確認し、以下にアクセスします。
http://localhost:8080/userList

・以下のような画面が表示されればOKです。現在はDB接続のオブジェクトを実装していないのでリスト自体は表示出来ていません。

SpringBoot_Di_Security_DB_7.png

3-7,DB接続用のオブジェクトを実装。

・ファイルの構成イメージは以下になります。赤枠が新規作成黄色枠が編集するファイルです。

SpringBoot_Di_Security_DB_8.png

・また処理の流れもイメージにしましたので、参考にして下さい。(毎度簡素なイメージで申し訳ない・・・)

SpringBoot_Di_Security_DB_9.png

・コードは以下になります。

SpringLogin.app.controller/HomeController.java
package SpringLogin.app.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import SpringLogin.app.service.UserService;
import SpringLogin.app.model.User;

// Controllerクラスのアノテーション
@Controller
public class HomeController {

    // インスタンスを作成しDIコンテナに格納。
    @Autowired
    UserService userService;

    //userListのアドレスにアクセスした際にGetメソッドを実行。
    @GetMapping("/userList")
    public String getUserList(Model model) {

        //@Autowiredで作成したインスタンスを元に、userServiceのメソッドを呼び出す。
        List<User> userList = userService.selectMany();
        //userServiceから受け取ったデータをView側に渡す。
        model.addAttribute("userList", userList);

        //template配下のファイル名を指定することでViewを呼び出せる。
        return "userList";
    }

}

SpringLogin.app.service/UserService.java
package SpringLogin.app.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import SpringLogin.app.repository.UserDao;
import SpringLogin.app.model.User;

@Transactional //メソッド内で例外が発生した際にロールバックつまりエラーが起きる直前に戻るアノテーション。
@Service //Serviceクラスのアノテーション。ServiceクラスはDAOから受け取ったデータを変換しControllerに渡す役目がある。
public class UserService {

    @Autowired
    @Qualifier("UserDaoJdbcImpl") //DAOはインターフェースをimplimentsするのが定石らしく、implimentsするファイルを明示的にする。
    UserDao dao;

    //Daoのメソッドを実行。戻り値をList型でControllerに引き渡す。
    public List<User> selectMany() {
        return dao.selectMany();

    }

}

SpringLogin.app.repository/UserDao.java
package SpringLogin.app.repository;

import java.util.List;

import org.springframework.dao.DataAccessException;

import SpringLogin.app.model.User;

public interface UserDao {

    // Userテーブルの全データを取得.
    public List<User> selectMany() throws DataAccessException;

}


SpringLogin.app.repository/UserDaoJdbcImpl.java
package SpringLogin.app.repository;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import SpringLogin.app.model.User;
import SpringLogin.app.repository.UserDao;

@Repository("UserDaoJdbcImpl") //DB接続用クラスのアノテーション。
public class UserDaoJdbcImpl implements UserDao {

    //DB接続のため、JdbcTemplateをDIする。
    @Autowired
    JdbcTemplate jdbc;

    // Userテーブルの全データを取得.
    @Override
    public List<User> selectMany() throws DataAccessException {

        // queryForListメソッドでSQLを発行。queryForListの場合、結果はMap型をList化した形式で結果が返ってくる。
        // MapはKeyとValueがセットになった配列型オブジェクトのこと。
        List<Map<String, Object>> getList = jdbc.queryForList("SELECT * FROM user");

        // Userの型に変換するため、格納用のArrayListのインスタンスを生成。
        List<User> userList = new ArrayList<>();

        for (Map<String, Object> map : getList) {

            User user = new User();
            // map.getでKeyに基づいたValueを取得。それをUserクラスのSetterに当てはめる。
            // 今回の場合はDBのuser_idというカラムから紐づくレコードを取得している。
            user.setUserId((int) map.get("user_id"));
            user.setPassword((String) map.get("password"));
            user.setUserName((String) map.get("user_name"));

            // userList(List型の配列)にUserインスタンスを追加。
            userList.add(user);
        }
        // ListをServiceに返す。
        return userList;
    }


}


SpringLogin.app.model/User.java
package SpringLogin.app.model;

import lombok.Data;

@Data //lombokを使用しているのでアノテーション一つでGetter,Setterを作成できる。
public class User {

    private int userId;
    private String password;
    private String userName;

}


・コードが書けたら、ブラウザにて確認します。Eclipseでアプリを起動していない場合は起動してから確認しましょう。以下の様な画面になっていたらOKです。

 SpringBoot_Di_Security_DB_10.png

これでMySQL使えるようになりました!
長くなってしまったので続きは次回にします。

 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】Java クラスフィールド・メソッド/カプセル化(ゲッター・セッター)【備忘録25】

クラスフィールド・クラスメソッド

クラスフィールド

インスタンスフィールドがありましたが、クラスに属するクラスフィールド
というものもある。具体例としては、インスタンスがいくつ生成されたのか数える
countというものがある。
【例】

class Person{
 public static int count = 0;
 //...略

Person(String firstName,...);{
 Person.count++;
... //↑こうすることで、インスタンスが生成されるたびに1ずつ足されていく。

クラスメソッド

class Person{
 public static int count = 0;
 //...略
 public static void printCount(){
  System.out.println("合計" + Person.count + "人です");
 }
}

クラスメソッドは、インスタンスの生成がない場合でも呼び出すことが可能。

//<Main.java>にて
class Main{
 public static void main(String[]args){
  Person.printCount();
  Person person1 = new Person( //...略 );
  Person.printCount();
 }
}

出力:
合計0人です
合計1人です

※クラスフィールドもクラスメソッドも「static」が付くので注意!

nullを使ったfullNameの書き換え

以前の記事で作成したfullNameの中身は「firstName + lastName」としてましたが
middleNameを追加で入れたい場合の書き方を具体例で書いてみます↓
(middleNameを持っていないインスタンスもあるため、条件分岐で示す)

※何もしていない状態だと、通常middleName(String型)の箇所には「null」が入っている。
(intの場合は0 / doubleの場合は0.0 / booleanの場合はfalse)

class Person{
...
 public String fullName(){
  if (this.middleName == null){
   return this.firstName + " " + this.lastName;
  }else{
   return this.firstname + " " + this.middleName + " " + this.lastName;
  }
}
...

カプセル化

定義したものを触って壊されないようにしておく時に使用する仕組みのこと。
パソコンの内部のようなイメージ。
今までは「public」と書いていたが「private」と書くようにする。
※privateなフィールドにはMainクラスから変更などのアクセスができなくなる。

ゲッター

privateにした上で、クラス外から安全に値を取得するために
フィールドの値を返す仕組みのこと。「getフィールド名」と書く。

セッター

privateに設定後、クラス外で値を変更したい際に使う仕組み。「setフィールド名」と書く。

//<Person.java>にて...コンストラクタを書いた後↓
...
 public String getMiddleName() {
  return this.middleName;
 }
//まず、ゲッターを定義した後にセッターを定義
 public void setMiddleName(String middleName) {
  this.middleName=middleName;
 }
...
//<Main.java>にて...Person person1 = new Person("山田","花子"...);と書かれている場合。
...
person1.setMiddleName("Claire"); //変更したい場合。

System.out.println("ミドルネームを" + person1.getMiddleName() + "に変更しました");

復習課題に取り掛かってみて

・ゲッターを定義する場合

public String getName(){
 return this.name;

上記の際に()を忘れてしまうのでしっかり見直す。覚えなおす。

今後

一度ここまでのオブジェクト指向を自分で書けるように進歩するために
Progateや他サイトを使いながら書き方を復習しようと思う。
そこでなにか疑問や、わかったところなどあればまた書いてみようと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java 順列の生成

public List<String> permutation(List<String> list, String target, String ans){
  if(target.length() <= 1) {
    list.add(ans + target);
  } else {
    for (int i = 0; i < target.length(); i++) {
      permutation(
        list,
        target.substring(0, i) + target.substring(i + 1),
        ans + target.charAt(i));
    }
  }
  return list;
}

全組み合わせの列挙 Java

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ajd4jpがspring repoから完全にゴーン

2020年1月16日17時 現在
今まで使ってた、日本のカレンダーを知れる便利なライブラリajd4jpがspring repoからいなくなっている..
https://repo.spring.io/libs-release/ajd4jp/ajd4jp/1.4.6.2019/ajd4jp-1.4.6.2019.pom
当局は現在、彼の行方を追っている...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaSilver試験予約完全攻略ガイド

 この記事では、Java Silver試験を受けるのに一番の難関(ほんとか?)とされる、受験までの手順を紹介します。
 今回は「試験会場に行って受ける試験を予約する」までの説明をします。

 試験内容や勉強方法の解説は世に溢れているので自分でどうにかしてください。

必要なもの

 試験を受けるのに必要なものがあります。

予約するまでに必要なもの

  • メールアドレス

    • 試験結果を見るサービスに登録するために必要です。
    • 試験サービスに登録するために必要です。
  • 職業

    • 試験結果を見るサービスに登録するために必要です。
    • 電話番号か自宅がない場合、試験サービスに登録するために必要です。
  • クレジットカード

    • 試験の予約に必要です。仮想クレジットカードやプリペイド式のものでもおそらく可能。
    • 受験料は26600円(2020年1月現在)。

試験を受けるために必要なもの

  • 顔写真付き身分証明書
    • 職業の次に用意しづらいものです。原動機付自転車免許であれば即日、パスポートであれば1週間ほど、個人番号カードであれば1か月ほどあれば作ることができます。

 詳しくはPearson VUEのサイトにも載っています。
 https://www.pearsonvue.co.jp/test-taker/Tutorial/Identification-2.aspx
 変更される場合もあるので、こちら も参考にしてください。

アカウントを取得しよう

 この試験には2つのアカウントが必要です。

 1.コンピューター用試験管理サービスであるPearson VUEのアカウント
 2.Javaの権利元であるOracle社のアカウント(プロファイル)

 1は試験を予約するために、2は試験結果を見るために必要です。

Pearson VUEの試験用アカウントの取得

 必要なもの:メールアドレス、自宅あるいは勤務先の住所と連絡先電話番号

 試験を受けるのに必要です。

 トップページ から登録・試験の予約>受験者ホーム>試験プログラム>「Oracle | オラクル認定試験」を選択、
 あるいはオラクル認定資格 から「アカウントの作成」でアカウントの作成を始めてください。

01.png

02.png

03.png

・プライバシーポリシーへの同意
 一番下までスクロールした先のチェックボックスをオンにしないとアカウント作成ができません。

・Oracle Testing ID
 今までJavaなどの試験を受けたことがない方は「いいえ」を選んでください。
 あるけどIDを忘れた、という場合も過去の受験履歴が見えなくなる程度です。

・氏名と住所(あるいは勤務先住所)
 最初は英語で入力。認証や確認などをスムーズに済ませるための日本語登録は登録ステップの少し後のほうに出てきます。

・オラクルパートナーネットワーク
 おそらくあなたの会社は入ってません。少なくともこんなガイドを見てる時点で、公式(あるいは内製)のガイドだってないでしょう。

・ユーザー名・パスワード・秘密の質問
 被りには注意しましょう。
 とても忘れやすいのできちんと記録を取っておくことをお勧めします。
 パスワードはアルファベット大文字小文字数字全部混ぜで8文字以上。
 また、秘密の質問の回答に「null」と入れるとエラーになるのでやめておいたほうがいいです。(1敗)

Oracleプロファイルの取得

 必要なもの:氏名、メールアドレス、職業

 試験結果を見るサイトにアクセスするのに必要です。
 トップページ からView Accounts>Create an Accountでアカウントが作れます。

 04.png

・注意事項
 部署・役職名、勤務先電話番号が必須なことに気を付けて入力してください。
 また、後述する「試験結果を見るサイト」の初回認証で失敗すると非常に手間がかかるので、パスワードは普段よりさらに忘れにくいものにしてください。

アカウントの確認

 それぞれ登録確認メールが来たら、それを認証して登録は完了です。
 それぞれ一度はログインしておきましょう。

 注意点は、Pearson VUEのサイトのログインに使うのは登録時に入力したIDで、Oracleプロファイルのサインインに使うのは登録したメールアドレスであることです。特に後者を後述するCertViewの初回認証で間違えしまうと手間がかかる可能性が上がります。

05.png

10.png

試験の予約

 Pearson VUEのサイトから試験の予約をしましょう。
 先ほどのオラクル認定試験のページ からサインインをします。
 「ユーザー名」には登録時に入力したIDを入力しましょう。忘れていた場合でも認証メールに書いてあるはずです。

 ホーム画面から 試験を表示 に進んで、「808-JPN」と打ちましょう。出てくる「1Z0-808-JPN: Java SE 8 Programmer I」がJava SE 8 Silver の試験の日本語版です。Silverという名前は試験に入っていないので注意しましょう。
 「-JPN」のないほうもありますが、そちらは日本での資格として認められないそうなのでそちらにも注意しましょう。

11.png

12.png

13.png

 試験を予約する > 受験予約に進む で試験場所を選びましょう。
 登録した住所がデフォルトに入っているので、検索したり地図を見たりしながら行けそうな場所を最大3か所まで選んでください。
 次のページでカレンダーが表示されて「選んだ場所での空いている日時」が表示されるので、そこから受験日時を選びます。
 選んだ場所の空いている都合のいい日がない場合は、戻って会場を選択しなおすこともできます。
 
 そうしたら支払情報の入力をして、予約を完了させます。プロモーションコードを入力することで、一度落ちた場合もう一度だけ無料で受けられるというキャンペーンをやっているそうです。
08.png

 詳しくはPearson VUEのサイトをご覧ください。
  https://www.pearsonvue.co.jp/Clients/Oracle/Special-Offers/Retake2020.aspx

 Pearson VUEから予約確認(と支払いの受領通知)メールが来たら予約完了です。

試験結果確認サイトの認証をする

 これはOracleプロファイルを作ってから1週間以上後に行ってください。

 Oracleの資格認定システムである Certviewのサイト からOracleのプロファイルでログインをして、そこにPearson VUEの試験用アカウントについているOracle認定試験用ID(Oracle Testing ID)を結び付けて、試験結果の連携をします。
 これの初回認証に何度か失敗すると、改めてOracle社に連絡する必要があるそうなので注意してください。
 また、OracleアカウントのデータがCertViewに反映されるまでに時間がかかるため、Oracleアカウントを作ってから日数を開ける必要があるようです。

やり方

 Pearson VUEのOracle用サイトからサインインをして、Oracle用の自分のホームに飛びます。
 自分のサインイン名の下にある「Oracle Testing ID」をコピペなりメモなりしておきます。
 こちらは受験予約メールにも記載があります。

09.png

 CertViewのサイトにOracleプロファイルでサインインをして、そこから初回認証をします。ここでの注意点は、再三繰り返しますが「ユーザー名はメールアドレス」なことです。

 連携と初回認証が完了したらおしまいです。試験を待ちましょう。

試験当日

 必要な身分証を持って試験会場に向かいましょう。受付できちんと身分証明をする必要があるので、予約した時間の15分前には会場にいるようにしましょう。

 その後は試験を受けておしまいです。後日、CertViewにログインして結果を確認してください。


 今回の記事はこれで終わりです。よい試験ライフを。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LinkedListとArrayList

リストって

脳死でArrayList使っていたけれど、実はいろいろあるらしい

LinkedArrayListTest.java
package listTest;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class LinkedArrayListTest {

    public static void main(String[] args) {
        measure(1000000);
    }

    private static void linked(int num) {
        List<String> list = new LinkedList<>();
        for (int i = 0; i < num; i++) {
            list.add("asd");
        }
    }

    private static void array(int num) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            list.add("asd");
        }
    }

    public static void measure(int num) {
        Long start = System.nanoTime();
        linked(num);
        Long middle = System.nanoTime();
        array(num);
        Long end = System.nanoTime();
        System.out.println("Linked : " + (middle - start) + " ns\r\nArray : " + (end - middle) + " ns");
    }
}

結果

Linked : 19082700 ns
Array  : 14878000 ns

まぁ回数が少ないので何とも言えませんが、addだけならLinkedListの方が早いらしいです

ただ、get(0)とか、特定の要素を持ってくるのはArrayのほうが早いらしく

ただリストを作ってforループするだけならLinkedListでよく、順番とかをいっぱい使いたいならArrayって感じでしょうか

余力があればこういった細かい性能問題も考えられればうれしいです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring Boot(Spring Web MVC + Tomcat)におけるSameSite Cookie

Spring Boot(Spring Web MVC + Tomcat)でSameSite Cookieを使うにはどのようにすればいいか、調べてみました。

SameSite Cookieとはなにか

MDNのドキュメントを参照してください。

TomcatのSameSite Cookie対応

TomcatではCookieをHTTPレスポンスへ書き出すためjavax.servlet.http.Cookieから文字列へのシリアライズを行います。
シリアライズはorg.apache.tomcat.util.http.CookieProcessorインターフェースを通じて行われます。
実装クラスとしてorg.apache.tomcat.util.http.Rfc6265CookieProcessorが用意されていますが、このRfc6265CookieProcessorクラスのsetSameSiteCookiesメソッドを使用してSameSite属性を付けられます。

SameSite属性を付ける設定をしたRfc6265CookieProcessororg.apache.catalina.Contextへセットしないといけませんが、とりあえずSpring Bootで使えればそれで良いのでTomcat単体でのカスタマイズ方法はスキップします。

Spring BootでCookieProcessorをカスタマイズする

Spring Bootではorg.springframework.boot.web.embedded.tomcat.TomcatContextCustomizerインターフェースをimplementsしたコンポーネントを用意するとTomcatのContextをカスタマイズできます。

次のような実装クラスでCookieへSameSite属性を付けられます。

package com.example;

import org.apache.catalina.Context;
import org.apache.tomcat.util.http.Rfc6265CookieProcessor;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.stereotype.Component;

@Component
public class SameSiteCookieTomcatContextCustomizer implements TomcatContextCustomizer {

    @Override
    public void customize(final Context context) {
        final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
        cookieProcessor.setSameSiteCookies("Lax");
        context.setCookieProcessor(cookieProcessor);
    }
}

Spring SessionはデフォルトでSameSite属性が付く

@tokuhirom さんに教えてもらったのですがSpring Sessionを使用するとデフォルトでSameSite属性が付くようです。

まとめ

Spring Boot(Spring Web MVC + Tomcat)でSameSite Cookieを使うには次に示す2通りの方法があることがわかりました。

  • TomcatContextCustomizerをimplementsしたコンポーネントを用意してContextへカスタマイズしたRfc6265CookieProcessorをセットする
  • Spring Sessionを使用する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RやRStudioでJava (jdk)のバージョンが高すぎてh2oライブラリが使えなかったのを解決した話

動機

久しぶりに書いたと思ったらまたPATHの話です。
Mac向け。Winの人はごめんね。
学校でR環境下でh2oという深層学習ライブラリを使えという課題が出た際に私だけjdkのバージョンが引っかかって課題が進まなかったのを解決した怒りの手記。

解決方法

R環境下で使うjdkのバージョンを下げる。以下、具体的手法。

今Javaは何がいくつ入ってんの?

Rコンソールではなくターミナルを起動し、

/usr/libexec/java_home -V

と打ち込んで調べよう。入っているJavaのバージョンが全て出てきます。こういう感じです。
スクリーンショット 2020-01-16 2.32.45.png

h2oライブラリが使えるjdkを持ってる?

h2oライブラリはjdkと協働します。このとき、jdkのバージョンが7から12の間じゃないとテコでも動かないようです。
最近のjdkのバージョンは13だよ!!!!!!!!

対象のバージョンのjdkを持っていない人は手に入れよう

Javaのバージョンを確認したときに対象バージョンが入っていなかった人はダウンロード&インストールしよう。公式サイトから手に入るぞ(一番上で目立っている13ではなく中段にあるLTSの11をDLしよう)。

手に入れたら念のため、再びターミナルで

/usr/libexec/java_home -V

でちゃんと入ったか見てみてください。

jdkのバージョンを下げよう

ここめちゃくちゃ注意してほしいんですが、ターミナルでJavaプログラムをいじるときに使うjdkと、R環境が参照するjdkのpathは違います。
以下の方法を使うと、普通に使うときのjdkのバージョンは13のまま、R環境下で用いるjdkのバージョンは11になります。やったね。

FinderでRenvironファイルを検索

 RenvironファイルはR環境における.bash_programみたいなもののようです。R (とRStudio)を導入していればこのファイルがあるはず。これを適当なエディタで開いて、一番下にでも

JAVA_HOME=/Library/Java/JavaVirtualMachines/openjdk-11.0.2.jdk/Contents/Home

と書き込みましょう。

完了!!

以上です。これでRが参照するjdkがダウングレードしました。
Rコンソールで

library(h2o)
localH2O = h2o.init(ip = "localhost", port = 54321, startH2O = TRUE, nthreads = -1)

とでも打ってみてh2oを使ってみましょう。
なんかh2o自体のバージョンが古いとかなんとか言われますが、使用自体はできます。

お疲れ様でした。

追記

現在配布されているLTS11は11.0.2ではなく11.0.6のようです。手に入れたバージョンをしっかり確認して、都度PATHを変更して使ってください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アルゴリズム 体操16

Merge Sort

マージソートはソートアルゴリズムの中でもdivide&conquerを使った有名な一つですね。
再帰的に分割していき、再び併合(マージ)していくことで、並び替えを実現しようとする、ソートアルゴリズムです。
今回はそのマージソートを使って配列ではなく、Linked Listをソートしてみたいと思います。

Screen Shot 2020-01-15 at 7.18.16.png

Solution

Runtime Complexity O(n(log(n))

n 個のリストとをマージするには n に比例した時間がかかります。(merge)
また、n 個のデータを1個ずつになるまで2分割していくには log(n) 時間がかかります。(divide)
したがって、実行時間は O(n log(n)) となります。

Memory Complexity O(log n)

再帰を使うと、スタック上のメモリを消費するため、O(log n)となります。

分割ステップでは、入力されたリストを2つに半分に分割し、サイズが1または0のリストになるで続けます。
結合ステップでは、ソートされたリストをマージし、リストが完全にソートされるまでそれを続けます。
このマージソートアルゴリズムの再帰関係は次のようになります。

T(n)= 2T(n / 2)+ n

この式は再帰のアルゴリズムのRuntime Complexity を分析する時に使わて、大学の授業などで
扱われる内容なのでそこまで深入りはしないことにしますが興味がある方はこちらを参考にして下さい。

考え方

配列ではなく、リストに対してマージソートをするので、分割する際に隣り合うノードのリンクを
切っていきます。それから、一つのノード同士を比較してマージしながら並び替えていく感じです。
再帰を使っているので実装は少し分かりにくいかもしれません。アイデアはまず、三つのメソッドがあります。
一つ目のmergeSortメソッドで大まかなロジックフレームのdivide&mergeを組みます。
それから、二つ目のsplitHalfメソッドはdivide役で、Linkを分割する役割があり、
pairオブジェクトに分割した二つのリストのheadを入れておきます。
そして三つ目のmergeSortedListメソッドはmerge役で、分割されたノードをマージして並びかえていく責任を果たします。

実装

MergeSort.java
class MergeSort{

    public LinkedListNode mergeSort(LinkedListNode head) {

      // base case
      if (head == null || head.next == null) {
        return head;
      }

      Pair<LinkedListNode, LinkedListNode> pair = new Pair<LinkedListNode, LinkedListNode>(null, null);

      // Let's split the list in half, sort the sublists
      // and then merge the sorted lists.

      splitHalf(head, pair);

      pair.first = mergeSort(pair.first);
      pair.second = mergeSort(pair.second);

      return mergeSortedList(pair.first, pair.second);
  }
    // this method splits linked list in two halves by iterating over whole list
    // It returns head pointers of first and 2nd halves of linked lists in pair
    // Head of 1st half is just the head node of linked list
   public void splitHalf(LinkedListNode head, Pair<LinkedListNode, LinkedListNode> pair) {

      if (head == null) {
        return;
      }

      if (head.next == null) {
        pair.first = head;
        pair.second = null;
      } else {

        LinkedListNode slow = head;
        LinkedListNode fast = head.next;

      // Two pointers, 'fast' and 'slow'. 'fast' will move two steps in each 
      // iteration whereas 'slow' will be pointing to the middle element
      // at the end of the loop
        while (fast != null) {

          fast = fast.next;

          if(fast != null) {
            fast = fast.next;
            slow = slow.next;
          }
        }

        pair.first = head;
        pair.second = slow.next;

        // disconnecting the first linked list with the second one
        slow.next = null;
      }
    }
    public LinkedListNode mergeSortedList(LinkedListNode first, LinkedListNode second) {

      if (first == null) {
        return second;
      }

      if (second == null){
        return first;
      }

      LinkedListNode newHead;

      if (first.data <= second.data) {
        newHead = first;
        first = first.next;
      } else {
        newHead = second;
        second = second.next;
      }

      LinkedListNode current = newHead;

      while (first != null && second != null) {
        LinkedListNode temp = null;
        if (first.data <= second.data) {
          temp = first;
          first = first.next;
        } else {
          temp = second;
          second = second.next;
        }

        current.next = temp;
        current = current.next;
      }

      if (first != null) {
        current.next = first;
      } else if (second != null) {
        current.next = second;
      }

      return newHead;
    }
}

Output

こんな感じにソートされました!
Screen Shot 2020-01-15 at 8.39.23.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】統合開発環境Eclipseでjavaプログラムを実行~gitを使ってみた

【備忘録】統合開発環境Eclipseでjavaプログラムを実行~gitを使ってみた

初めて統合開発環境Eclipseをインストールし、「Hello world!」の出力までできたので、
そのコードをgithubにpushしてみた。

今後Eclipseを使用していくにあたり、備忘録として残しておく。

  1. github上にリモートリポジトリを作成

    1. githubの画面右上部の「+(プラス)」ボタンを押下し、New Repositoryを選択
    2. Repository nameにリポジトリの名前を入力し、Create repositoryを押下
  2. 「新規Javaプロジェクト」の作成

    1. メニューより[ファイル]→[新規]→[その他]を選択。ウィザードが出てきたら[Javaプロジェクト]を選択して次へをクリック。
    2. 「関連付けられたパースペクティブを開きますか?」のウィンドウがでてきたら「はい」を選択。
    3. 「新規Javaプロジェクト」ウィンドウが表示されるので、[プロジェクト名]欄に、任意のプロジェクト名をつけて「完了」ボタンをクリック。
  3. 「新規Javaクラス」の作成

    1. 新規のプロジェクトを作成したら、「Javaパースペクティブ」が表示される。(「ようこそ」の画面が表示されたままの場合は閉じる)
    2. Eclipseのメニューより、[ファイル]→[新規]→[クラス]を選択して「新規Javaクラス」ウィンドウを表示する。
    3. 名前に「クラス名」を入力。Javaではクラス名の頭文字を大文字にするのが慣例となっているので、クラス名は半角の英数字で分かりやすい名前をつける。public static void mail(String[] args)コメントの生成にチェックを入れて「完了」ボタンをクリック。
  4. Javaプログラムの記述 ~ Hello World! ~

    1. (// TODO 自動生成されたメソッド・スタブ)の下に、「System.out.println("Hello World!");」の1行を追加。
    2. ソース・コードを書いたら、Eclipseのメニューから[ファイル]→[保管]を選択してコードを保存。
  5. Javaプログラムの実行

    1. Javaプログラムを実行するには、Eclipseメニュー下の緑色の「実行」アイコンをクリック。(もしくは、メニューより[実行]→[実行]を選択するか、ショートカットキーの[Ctrl+F11]を押下)
    2. プログラムがエラーなく完了すると、下の「 コンソールビュー 」に「Hello World!」と表示される。
  6. Gitリポジトリを作成する(git init)

    1. Eclipseの「パースペクティブを開く」アイコン、もしくはEclipseメニューの[ウィンドウ(W)]→[パースペクティブを開く(R)]→[パースペクティブ(O)]→[その他(O)]をクリック。
    2. 「パースペクティブを開く」ウィンドウから「Git」を選択して「完了」ボタンを押下。
  7. 新規ローカルGitリポジトリの作成(初期化)

    1. 「Gitパースペクティブ」を開いたら、「新規ローカルGitリポジトリの作成」をクリック。
    2. 「新規Gitリポジトリの作成」-「リポジトリー・ディレクトリー」に任意のフォルダーを指定して「完了」ボタンをクリック。
  8. Gitリポジトリを追跡する(git add)

    1. 新規リポジトリを作成をしたら、管理対象のJavaプロジェクトをGitリポジトリと共有する。「Javaパースペクティブ」アイコンをクリックして、先ほど作成したJavaプロジェクトのパースペクティブに戻る。
    2. 「Javaパッケージ・エクスプローラー」ビューで右クリック→メニューから[チーム]→[プロジェクトの共用]を選択。
    3. 「Gitリポジトリーの構成」ウィンドウのリポジトリー欄に、先ほど作成したGitリポジトリを選択して「完了」ボタンをクリック。
    4. 追跡対象のファイル(pushしたいファイル)を選択して、右クリックメニューから[チーム]→[索引に追加]をクリック。Javaのファイルを索引に追加する事で、ファイルの変更履歴を追跡する。
  9. Gitリポジトリをコミットする(git commit)

    1. 追跡対象となったファイルは、「コミット」することでリポジトリの変更履歴として記録される。パッケージ・エクスプローラーで、変更履歴を管理するファイルを選択し、右クリックメニューから「 [チーム]→[コミット]」を選択。
    2. 「Git ステージングビュー」が表示され、先ほど索引に追加したファイルが「ステージされた変更」ボックスにリストされている。「ステージ」とは、Git用語で索引に追加(add)すること。
    3. ファイルの変更履歴を保存(コミット)するには、「コミット・メッセージ」にコメントを記述してから「コミット」ボタンをクリックする。
    4. コミット履歴を確認。パッケージ・エクスプローラーの右クリックメニューから[チーム]→[ヒストリーに表示]を選択。ヒストリーから「コミットID」や「メッセージ」などを確認する事ができる。
  10. pushする

    1. リポジトリと連携しているプロジェクトを右クリックし、「チーム>リモート>プッシュ」を選択。宛先リポジトリ(1.でリモートリポジトリ作成の際に自動生成されたURLのこと)、ソース参照(master)、宛先参照(master)を選択し、完了する。これでリモートリポジトリへのプッシュが完了。

参照

Eclipseのインストールから使い方まで~EclipseでJavaプログラムを実行してみよう!
EclipseでGitを使う(4)プッシュまでの流れをまとめる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む