20190526のJavaに関する記事は21件です。

Desktop : OpenCV Text

Goal

Test OpenCV text.

OpenCV_Text.java
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

import org.opencv.core.Point;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;

public class OpenCV_Text {
    static{ System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }
    private JFrame frmjavaSwing;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    OpenCV_Text window = new OpenCV_Text();
                    window.frmjavaSwing.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public OpenCV_Text() {
        init();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void init() {
        Mat source =  paint();
        BufferedImage image=matToBufferedImage(source);

        frmjavaSwing = new JFrame();
        frmjavaSwing.setTitle("opencv ¿é¤J¤å¦r½m²ß");
        frmjavaSwing.setBounds(100, 100, 300, 300);
        frmjavaSwing.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmjavaSwing.getContentPane().setLayout(null);

        final JLabel lblNewLabel = new JLabel("");
        lblNewLabel.setBounds(5, 5, image.getHeight()+10, image.getWidth()+10);
        lblNewLabel.setIcon(new ImageIcon(image));
        frmjavaSwing.getContentPane().add(lblNewLabel);
    }

    public Mat paint(){

        Mat source = new Mat(250,250, CvType.CV_8UC3, new Scalar(255,255,255));

        Imgproc.putText(source,new String("I love Opencv"), new Point(125,25), 0, 0.5, new Scalar(255, 0, 0));
        Imgproc.putText(source,new String("I love Opencv"), new Point(125,50), 1, 0.5, new Scalar(255, 0, 0));
        Imgproc.putText(source,new String("I love Opencv"), new Point(125,75), 2, 0.5, new Scalar(255, 0, 0));
        Imgproc.putText(source,new String("I love Opencv"), new Point(125,100), 3, 0.5, new Scalar(255, 0, 0));
        Imgproc.putText(source,new String("I love Opencv"), new Point(125,125), 4, 0.5, new Scalar(255, 0, 0));
        Imgproc.putText(source,new String("I love Opencv"), new Point(125,150), 5, 0.5, new Scalar(255, 0, 0));
        Imgproc.putText(source,new String("I love Opencv"), new Point(125,175), 6, 0.5, new Scalar(255, 0, 0));
        Imgproc.putText(source,new String("I love Opencv"), new Point(125,200), 7, 0.5, new Scalar(255, 0, 0));
        return source;
    }

    public BufferedImage matToBufferedImage(Mat matrix) {
        int cols = matrix.cols();
        int rows = matrix.rows();
        int elemSize = (int)matrix.elemSize();
        byte[] data = new byte[cols * rows * elemSize];
        int type;
        matrix.get(0, 0, data);
        switch (matrix.channels()) {
            case 1:
                type = BufferedImage.TYPE_BYTE_GRAY;
                break;
            case 3:
                type = BufferedImage.TYPE_3BYTE_BGR;
                // bgr to rgb
                byte b;
                for(int i=0; i<data.length; i=i+3) {
                    b = data[i];
                    data[i] = data[i+2];
                    data[i+2] = b;
                }
                break;
            default:
                return null;
        }
        BufferedImage image2 = new BufferedImage(cols, rows, type);
        image2.getRaster().setDataElements(0, 0, cols, rows, data);
        return image2;
    }
}
Result

opencv_text.JPG

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

Desktop : OpenCV Polylines

Goal

Test OpenCV Polyline.

OpenCV_Polylines.java
import org.opencv.core.*;
import org.opencv.core.Point;
import org.opencv.imgproc.Imgproc;

import javax.swing.*;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

public class OpenCV_Polylines {
    static{System.loadLibrary(Core.NATIVE_LIBRARY_NAME);}
    private JFrame frmjavaSwing;

    /**
        *  Launch the application.
        */
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try{
                    OpenCV_Polylines window = new OpenCV_Polylines();
                    window.frmjavaSwing.setVisible(true);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public OpenCV_Polylines() {
        initialize();
    }
    /**
        *  Init the contents of the frame.
        */
    private void initialize() {
        Mat source =  paint();
        BufferedImage image=matToBufferedImage(source);

        frmjavaSwing = new JFrame();
        frmjavaSwing.setTitle("opencv 畫多邊形練習(中空)");
        frmjavaSwing.setBounds(100, 100, 300, 300);
        frmjavaSwing.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmjavaSwing.getContentPane().setLayout(null);

        final JLabel lblNewLabel = new JLabel("");
        lblNewLabel.setBounds(5, 5, image.getHeight()+10, image.getWidth()+10);
        lblNewLabel.setIcon(new ImageIcon(image));
        frmjavaSwing.getContentPane().add(lblNewLabel);
    }

    public Mat paint(){

        Mat source = new Mat(250,250, CvType.CV_8UC3, new Scalar(255,255,255));
        List<MatOfPoint> allMatOfPoint=new ArrayList<MatOfPoint>();
        MatOfPoint mop1=new MatOfPoint();
        List<Point> allPoint1=new ArrayList<Point>();
        allPoint1.add(new Point(38,21));
        allPoint1.add(new Point(13,128));
        allPoint1.add(new Point(13,139));
        allPoint1.add(new Point(155,240));
        allPoint1.add(new Point(148,167));
        allPoint1.add(new Point(203,209));
        allPoint1.add(new Point(233,133));
        allPoint1.add(new Point(154,26));
        mop1.fromList(allPoint1);

        MatOfPoint mop2=new MatOfPoint();
        List<Point> allPoint2=new ArrayList<Point>();
        allPoint2.add(new Point(125,0));
        allPoint2.add(new Point(0,128));
        allPoint2.add(new Point(130,139));

        mop2.fromList(allPoint2);
        allMatOfPoint.add(mop1);
        allMatOfPoint.add(mop2);
        Imgproc.polylines(source, allMatOfPoint,true, new Scalar(0,255,0),3);
        return source;
    }

    public BufferedImage matToBufferedImage(Mat matrix) {
        int cols = matrix.cols();
        int rows = matrix.rows();
        int elemSize = (int)matrix.elemSize();
        byte[] data = new byte[cols * rows * elemSize];
        int type;
        matrix.get(0, 0, data);
        switch (matrix.channels()) {
            case 1:
                type = BufferedImage.TYPE_BYTE_GRAY;
                break;
            case 3:
                type = BufferedImage.TYPE_3BYTE_BGR;
                // bgr to rgb
                byte b;
                for(int i=0; i<data.length; i=i+3) {
                    b = data[i];
                    data[i] = data[i+2];
                    data[i+2] = b;
                }
                break;
            default:
                return null;
        }
        BufferedImage image2 = new BufferedImage(cols, rows, type);
        image2.getRaster().setDataElements(0, 0, cols, rows, data);
        return image2;
    }
}
Result

opencv_poly_lines.JPG

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

【Java初学者】抽象化とインターフェースについて

概要

抽象化インターフェースの復習として投稿させて頂きます。

抽象化

上位モジュールにおいて、下位モジュールに共通実装するメソッドや変数を宣言し、実装は下位モジュールに任せる。
また、抽象化したクラスはインスタンス生成を行えない。

・抽象化を行うことのメリット
- オーバーライドのし忘れ防止
- 何もしない処理との区別をつける
- 意図しないインスタンス生成の防止

Human.java
public abstract class Human{
  private String name;
  public Human(String name){
    this.name = name;
  }
  public String getName(){
    return name;
  }
  public void setName(String name){
    this.name = name;  
  }
  //共通したメソッドを宣言し実装は下位モジュールに任せる
  public abstract void talk();
  //何もしないメソッド
  public void tmp(){}
}
Japanese.java
public class Japanese extends Human{
  public Japanese(String name){
    super(name);
  }
  @Override
  public void talk(){
    System.out.println("こんにちは");
  }
}
American.java
public class American extends Human{
  public American(String name){
    super(name);
  }
  @Override
  public void talk(){
    System.out.println("Hello");
  }
}

Main.java
public class Main{
  public static void main(String[] args){
    //Human human = new Human("山田太郎");
    //インスタンス生成出来ない為、コンパイルエラー

    Japanese jp = new Japanese("山田太郎");
    jp.talk();    //こんにちは
    American en = new American("TaroYamada");
    en.talk();    //Hello

  }
}

インターフェース

インターフェースでは、抽象化と同様共通実装するメソッドを宣言し、インターフェースを継承したクラスに実装を任せることが出来る。しかし、フィールドに関しては抽象クラスと扱いが異なる。

インターフェースの特徴
- 基本的にフィールドは一つも持たない
- 全てのメソッドは抽象メソッドとなる。

インターフェースにおいて基本的にフィールドを実装することは許されていないが、public static finalがついたフィールドだけは宣言することが出来、インターフェースに型名から変数宣言を始めると自動的に付加される。再代入不可のfinal修飾子が付いているため、変数宣言と同時に初期化を行う必要がある。

また、メソッドに関して、インターフェースに宣言できるメソッドはpublicかつabstractなものに限定され、戻り値の型から宣言した場合は自動的に付加される。

Creature.java
public interface Creature{
  //public static final double PI = 3.14;
  double PI = 3.14; // 自動的にpublic static finalな変数となる。

  //public abstract void breath();
  void breath();  //自動的にpublic abstractなメソッドとなる。
}

まとめ

未だ、継承を用いた開発経験が少ないため、メリットの恩恵を受けづらいというのが正直な感想。

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

ArgumentMatcherとは

ArgumentMatcherとは

テストにおいて、メソッドの動作設定とメソッド呼び出し検証でモックに渡す引数と、実際にテスト対象クラスからモックのメソッドに渡される引数は異なる可能性がある。
この時、ArgumentMatcherを使うことで、引数の中身をふわっと検証できる。

  • 渡される型のみ検証する
  • 他のテストケースで検証済みの引数の検証を省略する
  • 細かい条件を指定する

などの使い道がある。

例えば、渡される型のみ検証したい場合はArgumentMatchers.anyメソッドが利用可能。
また、すでに他のテストケースで検証済みの引数の検証をスキップするのにも使われる。

Mockito.doNothing().when(display).display(ArgumentMatchers.any());
Mockito.verify(display, Mockito.times(1)).display(ArgumentMatchers.any());

基本型やコレクション型の一部はあらかじめ対応するanyメソッドがあるが、自作クラスの型を検証するときは作成することもできる。

Mockito.doNothing().when(display).display(ArgumentMatchers.anyString()); 
Mockito.verify(display, Mockito.times(1)).display(ArgumentMatchers.any(String.class));

細かい条件を指定したいとき

  • ArgumentMatcherインターフェースの実装クラスを作成する。このとき、matchesメソッドをオーバーライドする。
  • メソッドの動作設定とメソッド呼び出し検証で、MockitoクラスのargThatメソッドの引数に上記のクラスのインスタンスを渡す。
ArgumentMatcher<String> matcher = argument -> { 
  assertThat(argument.substring(1), is(msg));
  return true; 
};
Mockito.doNothing().when(display).display(Mockito.argThat(matcher)); 
Mockito.verify(display, Mockito.times(1)).display(Mockito.argThat(matcher));

注意

上記の式にはラムダ式が使われている。
ラムダ式は匿名クラスによる記述をさらに省略したもの。

ArgumentMatcherのmatchesメソッドのオーバーライドを匿名クラスを使って書くと以下の通り。

ArgumentMatcher<String> matcher = new ArgumentMatcher(
  @Override 
  public boolean matches(Object argument) {
    assertThat(argument.substring(1), is(msg));
    return true;
  }
)

ラムダ式を用いることで以下のことができる

  • new を省略 (ラムダ式では必ずインスタンスを作成するため)
  • ArgumentMatcher を省略 (ここで、matcherに渡したいのはArgumentMatcherの実装クラスだから,明らか。多分変数の型宣言がここで効いてる)
  • @Override, runを省略 (ArgumentMatcherインターフェースはメソッドが1つしかない。これの実装クラスを作るので、どちらも明らか。)

練習のために、今一度ArgumentMatcherの定義を書いてみる。

ArgumentMatcher<String> matcher = argument -> {
  assertThat(argument.substring(1), is(msg));
  return true;
};

よくできました。

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

Desktop : OpenCV Fill Poly With Mouse Control

Goal

Test OpenCV fill poly with mouse control.

OpenCV_FillPolyWithMouseControl.java
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import org.opencv.core.MatOfPoint;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class OpenCV_FillPolyWithMouseControl {

    static{System.loadLibrary(Core.NATIVE_LIBRARY_NAME);}
    private JFrame frmjavaSwing;

    public List<Point> allPoint = new ArrayList<>();

    /**
     *  Launch the application.
     */
    public static void main(String[] args){

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try{
                    OpenCV_FillPolyWithMouseControl window = new OpenCV_FillPolyWithMouseControl();
                    window.frmjavaSwing.setVisible(true);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     *  Create the application.
     */
    public OpenCV_FillPolyWithMouseControl() {
        initialize();
    }

    /**
     *  Init the contents of the frame.
     */
    private void initialize() {
        final Mat source = Imgcodecs.imread(
                "D:\\projects\\Java\\OpenCV_Samples\\resource\\imgs\\DSC_0625.jpg");

        BufferedImage image=matToBufferedImage(source);

        frmjavaSwing = new JFrame();
        frmjavaSwing.setTitle("多邊形填充使用滑鼠練習");
        frmjavaSwing.setBounds(100, 100, 491, 425);
        frmjavaSwing.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmjavaSwing.getContentPane().setLayout(null);

        final JLabel lblLocation = new JLabel("");
        lblLocation.setBounds(10, 10, 112, 25);
        frmjavaSwing.getContentPane().add(lblLocation);

        final JLabel lblLocation2 = new JLabel("");
        lblLocation2.setBounds(158, 10, 112, 25);
        frmjavaSwing.getContentPane().add(lblLocation2);

        JPanel panel = new JPanel();
        panel.setBounds(10, 45, 429, 348);
        frmjavaSwing.getContentPane().add(panel);

        final JLabel lblNewLabel = new JLabel("");
        panel.add(lblNewLabel);


        lblNewLabel.setIcon(new ImageIcon(image));



        lblNewLabel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent arg0) {
                lblLocation2.setText("");
                lblLocation.setText("加入點:X:"+arg0.getX()+",Y:"+arg0.getY());

                BufferedImage newImage=matToBufferedImage(paintPointAndFillPoly(arg0.getX(),arg0.getY()));
                lblNewLabel.setIcon(new ImageIcon(newImage));
            }
        });
    }

    public Mat paintPointAndFillPoly(int x,int y){
        Mat src = Imgcodecs.imread("D:\\projects\\Java\\OpenCV_Samples\\resource\\imgs\\DSC_0625.jpg");

        List<Point> allPoint=getAllPoint();
        allPoint.add(new Point(x,y));
        //畫滑鼠點擊的點
        Imgproc.circle(src, new Point(x,y), 2, new Scalar(0,55,255),2);

        //畫出FillPoly
        if(allPoint.size()>=2){
            MatOfPoint mop1=new MatOfPoint();
            List<MatOfPoint> allMatOfPoint=new ArrayList<MatOfPoint>();
            mop1.fromList(allPoint);
            allMatOfPoint.add(mop1);
            Imgproc.fillPoly(src, allMatOfPoint,  new Scalar(255,255,255));

        }
        return src;
    }

    public BufferedImage matToBufferedImage(Mat matrix) {
        int cols = matrix.cols();
        int rows = matrix.rows();
        int elemSize = (int)matrix.elemSize();
        byte[] data = new byte[cols * rows * elemSize];
        int type;
        matrix.get(0, 0, data);
        switch (matrix.channels()) {
            case 1:
                type = BufferedImage.TYPE_BYTE_GRAY;
                break;
            case 3:
                type = BufferedImage.TYPE_3BYTE_BGR;
                // bgr to rgb
                byte b;
                for(int i=0; i<data.length; i=i+3) {
                    b = data[i];
                    data[i] = data[i+2];
                    data[i+2] = b;
                }
                break;
            default:
                return null;
        }
        BufferedImage image2 = new BufferedImage(cols, rows, type);
        image2.getRaster().setDataElements(0, 0, cols, rows, data);
        return image2;
    }

    public List<Point> getAllPoint() {
        return allPoint;
    }

    public void setAllPoint(List<Point> allPoint) {
        this.allPoint = allPoint;
    }

}
Result

opencv_fill_poly_with_mouse_control.JPG

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

Desktop : OpenCV Fill Poly

Goal

Test OpenCV Fill Poly.

OpenCV_FillPoly.java
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import org.opencv.core.Point;

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

public class OpenCV_FillPoly {

    static {System.loadLibrary(Core.NATIVE_LIBRARY_NAME);}
    private JFrame frmjavaSwing;

    /**
        * Launch the application.
        */
    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    OpenCV_FillPoly window = new OpenCV_FillPoly();
                    window.frmjavaSwing.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public OpenCV_FillPoly() {
        initialize();
    }

    /**
     *  init the contents of the frame.
     */
    private void initialize() {
        Mat source =  paint();
        BufferedImage image=matToBufferedImage(source);

        frmjavaSwing = new JFrame();
        frmjavaSwing.setTitle("opencv 畫多邊形練習(填滿)");
        frmjavaSwing.setBounds(100, 100, 300, 300);
        frmjavaSwing.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmjavaSwing.getContentPane().setLayout(null);

        final JLabel lblNewLabel = new JLabel("");
        lblNewLabel.setBounds(5, 5, image.getHeight()+10, image.getWidth()+10);
        lblNewLabel.setIcon(new ImageIcon(image));
        frmjavaSwing.getContentPane().add(lblNewLabel);
    }
    public Mat paint(){

        Mat source = new Mat(250,250, CvType.CV_8UC3, new Scalar(255,255,255));
        java.util.List<MatOfPoint> allMatOfPoint=new ArrayList<>();
        MatOfPoint mop1=new MatOfPoint();
        java.util.List<Point> allPoint1=new ArrayList<>();
        allPoint1.add(new Point(38,21));
        allPoint1.add(new Point(13,128));
        allPoint1.add(new Point(13,139));
        allPoint1.add(new Point(150,240));
        allPoint1.add(new Point(220,209));
        allPoint1.add(new Point(243,143));
        allPoint1.add(new Point(154,26));
        mop1.fromList(allPoint1);

        MatOfPoint mop2=new MatOfPoint();
        List<Point> allPoint2=new ArrayList<>();
        allPoint2.add(new Point(125,0));
        allPoint2.add(new Point(0,128));
        allPoint2.add(new Point(130,139));
        mop2.fromList(allPoint2);
        allMatOfPoint.add(mop1);
        Imgproc.fillPoly(source, allMatOfPoint, new Scalar(255,0,0));
        allMatOfPoint.add(mop2);
        Imgproc.fillPoly(source, allMatOfPoint, new Scalar(0,255,0));
        return source;
    }

    public BufferedImage matToBufferedImage(Mat matrix) {
        int cols = matrix.cols();
        int rows = matrix.rows();
        int elemSize = (int)matrix.elemSize();
        byte[] data = new byte[cols * rows * elemSize];
        int type;
        matrix.get(0, 0, data);
        switch (matrix.channels()) {
            case 1:
                type = BufferedImage.TYPE_BYTE_GRAY;
                break;
            case 3:
                type = BufferedImage.TYPE_3BYTE_BGR;
                // bgr to rgb
                byte b;
                for(int i=0; i<data.length; i=i+3) {
                    b = data[i];
                    data[i] = data[i+2];
                    data[i+2] = b;
                }
                break;
            default:
                return null;
        }
        BufferedImage image2 = new BufferedImage(cols, rows, type);
        image2.getRaster().setDataElements(0, 0, cols, rows, data);
        return image2;
    }
}
Result

opencv_fill_poly.JPG

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

Desktop : OpenCV Fill ConvexPoly

Goal

Test OpenCV fill convex poly.

OpenCV_FillConvexPoly.java
import java.awt.EventQueue;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

public class OpenCV_FillConvexPoly {
    static {System.loadLibrary(Core.NATIVE_LIBRARY_NAME);}

    private JFrame frmjavaSwing;

    /**
        * Launch the application.
        */
    public static void main(String[] argv){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try{
                    OpenCV_FillConvexPoly window = new OpenCV_FillConvexPoly();
                    window.frmjavaSwing.setVisible(true);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
    }

    /**
        * Create the application.
        */
    public OpenCV_FillConvexPoly(){
        init();
    }

    /**
        * Init the contens of the frame.
        */
    private void init(){
        Mat source =  paint();
        BufferedImage image=matToBufferedImage(source);

        frmjavaSwing = new JFrame();
        frmjavaSwing.setTitle("opencv ¶ñ¸É¥Y¦hÃä§Î½m²ß");
        frmjavaSwing.setBounds(100, 100, 300, 300);
        frmjavaSwing.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmjavaSwing.getContentPane().setLayout(null);

        final JLabel lblNewLabel = new JLabel("");
        lblNewLabel.setBounds(5, 5, image.getHeight()+10, image.getWidth()+10);
        lblNewLabel.setIcon(new ImageIcon(image));
        frmjavaSwing.getContentPane().add(lblNewLabel);
    }
    public Mat paint(){

        Mat source = new Mat(250,250, CvType.CV_8UC3, new Scalar(255,255,255));
        MatOfPoint mop=new MatOfPoint();
        List<Point> allPoint=new ArrayList<Point>();
        allPoint.add(new Point(38,21));
        allPoint.add(new Point(13,128));
        allPoint.add(new Point(13,139));
        allPoint.add(new Point(155,240));
        allPoint.add(new Point(158,167));
        allPoint.add(new Point(200,219));
        allPoint.add(new Point(243,143));
        allPoint.add(new Point(154,26));
        mop.fromList(allPoint);
        Imgproc.fillConvexPoly(source, mop, new Scalar(255,0,0));
        return source;
    }
    public BufferedImage matToBufferedImage(Mat matrix) {
        int cols = matrix.cols();
        int rows = matrix.rows();
        int elemSize = (int)matrix.elemSize();
        byte[] data = new byte[cols * rows * elemSize];
        int type;
        matrix.get(0, 0, data);
        switch (matrix.channels()) {
            case 1:
                type = BufferedImage.TYPE_BYTE_GRAY;
                break;
            case 3:
                type = BufferedImage.TYPE_3BYTE_BGR;
                // bgr to rgb
                byte b;
                for(int i=0; i<data.length; i=i+3) {
                    b = data[i];
                    data[i] = data[i+2];
                    data[i+2] = b;
                }
                break;
            default:
                return null;
        }
        BufferedImage image2 = new BufferedImage(cols, rows, type);
        image2.getRaster().setDataElements(0, 0, cols, rows, data);
        return image2;
    }
}
Result

opencv_fill_convex_poly.JPG

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

Desktop : OpenCV Ellipse2Poly

Goal

Test OpenCV Ellipse2Poly.

OpenCV_Ellipse2Poly.java
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import org.opencv.core.Point;

public class OpenCV_Ellipse2Poly {

    static{System.loadLibrary(Core.NATIVE_LIBRARY_NAME);}
    private JFrame frmjavaSwing;

    /**
     *  Launch the application.
     */
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try{
                    OpenCV_Ellipse2Poly window = new OpenCV_Ellipse2Poly();
                    window.frmjavaSwing.setVisible(true);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     *  Create the application.
     */
    public OpenCV_Ellipse2Poly(){
        init();
    }

    /**
     *  Init the contents of the frame.
     */
    private void init(){
        Mat source =  paint();
        BufferedImage image=matToBufferedImage(source);

        frmjavaSwing = new JFrame();
        frmjavaSwing.setTitle("opencv 橢圓內近似多邊形練習");
        frmjavaSwing.setBounds(100, 100, 300, 300);
        frmjavaSwing.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmjavaSwing.getContentPane().setLayout(null);

        final JLabel lblNewLabel = new JLabel("");
        lblNewLabel.setBounds(5, 5, image.getHeight()+10, image.getWidth()+10);
        lblNewLabel.setIcon(new ImageIcon(image));
        frmjavaSwing.getContentPane().add(lblNewLabel);
    }

    public Mat paint(){

        Mat source = new Mat(250,250, CvType.CV_8UC3, new Scalar(255,255,255));
        Point pt1=new Point(125,125);
        //原橢圓形
        Imgproc.ellipse(source,pt1,new Size(120,50),50,0,360,new Scalar(255,0,0),4);
        MatOfPoint pts=new MatOfPoint();

        //原橢圓形的近似多邊形
        Imgproc.ellipse2Poly(pt1,new Size(120,50),50,0,360,60, pts);
        Point[] allPoint=pts.toArray();

        for(int i=1;i<allPoint.length;i++){

            Imgproc.line(source, allPoint[i-1], allPoint[i], new Scalar(0,0,0),2);

        }
        return source;
    }

    public BufferedImage matToBufferedImage(Mat matrix) {
        int cols = matrix.cols();
        int rows = matrix.rows();
        int elemSize = (int)matrix.elemSize();
        byte[] data = new byte[cols * rows * elemSize];
        int type;
        matrix.get(0, 0, data);
        switch (matrix.channels()) {
            case 1:
                type = BufferedImage.TYPE_BYTE_GRAY;
                break;
            case 3:
                type = BufferedImage.TYPE_3BYTE_BGR;
                // bgr to rgb
                byte b;
                for(int i=0; i<data.length; i=i+3) {
                    b = data[i];
                    data[i] = data[i+2];
                    data[i+2] = b;
                }
                break;
            default:
                return null;
        }
        BufferedImage image2 = new BufferedImage(cols, rows, type);
        image2.getRaster().setDataElements(0, 0, cols, rows, data);
        return image2;
    }

}
Result

opencv_ellipse2poly.JPG

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

Desktop : OpenCV Draw Circle

Goal

OpenCV Draw Circle.

OpenCV_Draw_Circle.java
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import org.opencv.core.Point;


public class OpenCV_Draw_Circle {

    static {System.loadLibrary(Core.NATIVE_LIBRARY_NAME);}
    private JFrame frmjavaSwing;

    /**
        *  Launch tha application.
        */
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try{
                    OpenCV_Draw_Circle window = new OpenCV_Draw_Circle();
                    window.frmjavaSwing.setVisible(true);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
    }

    /**
        * Create the application.
        */
    public OpenCV_Draw_Circle(){
        init();
    }

    /**
        * Init the contents of the frame.
        */
    private void init(){
        Mat source =  paint();
        BufferedImage image=matToBufferedImage(source);

        frmjavaSwing = new JFrame();
        frmjavaSwing.setTitle("opencv µe¶ê½m²ß");
        frmjavaSwing.setBounds(100, 100, 300, 300);
        frmjavaSwing.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmjavaSwing.getContentPane().setLayout(null);

        final JLabel lblNewLabel = new JLabel("");
        lblNewLabel.setBounds(5, 5, image.getHeight()+10, image.getWidth()+10);
        lblNewLabel.setIcon(new ImageIcon(image));
        frmjavaSwing.getContentPane().add(lblNewLabel);
    }

    public Mat paint(){

        Mat source = new Mat(250,250, CvType.CV_8UC3, new Scalar(255,255,255));
        Point pt1=new Point(125,125);
        Imgproc.circle(source,pt1,58,new Scalar(255,55,220),6);
        return source;
    }

    public BufferedImage matToBufferedImage(Mat matrix) {
        int cols = matrix.cols();
        int rows = matrix.rows();
        int elemSize = (int)matrix.elemSize();
        byte[] data = new byte[cols * rows * elemSize];
        int type;
        matrix.get(0, 0, data);
        switch (matrix.channels()) {
            case 1:
                type = BufferedImage.TYPE_BYTE_GRAY;
                break;
            case 3:
                type = BufferedImage.TYPE_3BYTE_BGR;
                // bgr to rgb
                byte b;
                for(int i=0; i<data.length; i=i+3) {
                    b = data[i];
                    data[i] = data[i+2];
                    data[i+2] = b;
                }
                break;
            default:
                return null;
        }
        BufferedImage image2 = new BufferedImage(cols, rows, type);
        image2.getRaster().setDataElements(0, 0, cols, rows, data);
        return image2;
    }
}
Result

opencv_draw_circle.JPG

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

DIについて理解する

DIとは

依存性の注入。
依存しているクラスの中で被依存クラスのインスタンスを作成するのではなく、
依存しているクラスのフィールド変数として被依存クラスを持たせるようにする。そのために、コンストラクタなどで外部から被依存クラスを渡すようにする。
習うより慣れよ。

DIのプロセス

DIに関わるアノテーションはたくさんあるが、基本的に以下の3段階のどこかに分類されると考えるとわかりやすい。
DIの3段階とは

  • DIコンテナに登録するクラスの定義
  • DIコンテナへの登録
  • DIコンテナからの注入

である。

DIコンテナに登録するクラスの定義

  • Javaによる定義 (=JavaConfig)
  • アノテーションによる定義
  • (xmlによる定義) <- 最近はあまりやらない

Javaによる定義

JavaConfigと呼ばれる。
- @Configurationアノテーションを付与したクラスを作成する
- @Beanアノテーションを付与したメソッドを作成する

@Configuration
public class AppConfiguration {

  // ServiceImplクラスをDIコンテナに登録する
  @Bean 
  public Service service() {
    return new ServiceImpl();
  }

  // RepositoryImplクラスをDIコンテナに登録する
  @Bean 
  public Repository repository()  {
    return new RepositoryImpl();
  }
  // 以下同様
  .
  .
  .
}

インターフェースを定義し、実装クラスを@Configurationに入れるのが普通?

アノテーションによる定義

//@Componentアノテーションは@Serviceアノテーションに含まれる
@Service 
public class ServiceImpl implements Service {
}

DIコンテナへの登録

DIコンテナにインスタンスを実際に登録する。
コンポーネントスキャンを行うことで、DIへのインスタンスの登録を実現する。

JavaConfigによるコンポーネントスキャン

  • @Configurationアノテーションを付与したクラスに@ComponentScanアノテーションを追加する
  • 基本はカレントディレクトリ以下をスキャンするが、指定したいときはbasePackages属性を指定する
  • スコープを指定したいときは、@Scopeアノテーションに属性を持たせる ->(singleton(デフォルト), prototype, request, session)
@Configuration
@ComponentScan(basePackages = "jp.co.yahoo")
// @ComponentScan <- カレントディレクトリ以下でいい場合
public class AppConfiguration {
}

DIコンテナからの注入

@Autowiredアノテーションを付与する。
以下の3つの方法がある。

  • コンストラクタインジェクション
  • フィールドインジェクション
  • セッタインジェクション

コンストラクタインジェクション

コンストラクタに@Autowiredを付与する。

  • 対象フィールドをfinal化できる (=実行中に依存するインスタンスの差し替えを禁止する)
  • @Autowiredの記述を省略できる

というメリットがある。推奨される方法。

private final Service service; 
public Controller(Service service) {
  this.service = service; 
}

フィールドインジェクション

@Autowired
private Service service;

セッタインジェクション

private Service service;
@Autowired
public void setService(Service service) {
  this.service = service; 
}

唯一神@SpringBootApplication

アプリケーションの起動の際に、@SpringBootApplicationアノテーションを付与することがある。
これさえあれば上記のアノテーションの多くを勝手にやってくれる。そのプロセスを以下で見ていく。

参考

Many Spring Boot developers always have their main class annotated with @Configuration, @EnableAutoConfiguration and @ComponentScan. Since these annotations are so frequently used together (especially if you follow the best practices above), Spring Boot provides a convenient @SpringBootApplication alternative.

The @SpringBootApplication annotation is equivalent to using @Configuration, @EnableAutoConfiguration and @ComponentScan with their default attributes:

package com.example.myproject;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

準備1: AutoConfigurationについて

SpringBootにはあらかじめ定義されたJavaConfigクラスがある。
考えてみればこれは当たり前だ。外部ライブラリを追加するごとにDI対象クラスを定義し、DIコンテナに登録する作業を繰り返す必要があるようではフレームワークとは呼べない。

このクラスで定義されたインスタンスを自動的にDIコンテナに登録する機能をAutoConfigurationという。

AutoConfigurationによってDIコンテナに登録される条件

  • Auto-configurationが有効
  • 依存ライブラリがpom.xmlに指定されている
  • 開発者独自のJavaConfigであらかじめ存在したJavaConfigの中で定義されたインスタンスを定義し直していない

Auto-configurationの有効化は@EnableAutoConfigurationアノテーションが付与されたクラスを作成することで行える。
一般的には、@Configurationアノテーションが付与されたクラスに@EnableAutoConfigurationアノテーションを付与する

準備2: SpringBootの起動

アプリケーション起動クラスの作成には2つポイントがある。

@SpringBootAnnotationは以下のアノテーションを含む。

scanBasePackages属性を用いて、スキャンするパッケージの指定もできる。

@SpringBootApplication(scanBasePackages = "com.example")

結論

@SpringBootApplicationアノテーションを付与することで、デフォルトのJavaConfigの定義、DIコンテナへの登録を省略できる。

自分で登録する際(デフォルトのJavaConfigとの差分を追加する際)、アノテーションによる定義しか行わないのであれば、@ComponentアノテーションをつけるだけでDIコンテナへの登録ができ、@Autowiredアノテーションで注入ができる。

補足

  • 差分があまりに巨大な場合は自分でJavaConfigを定義することになるのだろうが、現状ではアノテーションによる定義しかしていない。
  • アノテーションによる定義の際、で@SpringBootApplicationアノテーションでコンポーネントスキャンを自動化することになるので、@Componentアノテーションを付与するクラスは実行クラスのカレントディレクトリ以下になければならない。

(まあ、普通に考えて実行クラスは一番上の階層に置くからあまり意識する必要はないと思う)

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

既存のC/C++コードをAndroidアプリケーションに組み込む

この記事について

既存のC/C++コードを、Android NDKを用いて、ネイティブ関数としてAndroidアプリケーションに組み込みます。
基本的には、公式のUSER GUIDE(https://developer.android.com/studio/projects/add-native-code )のまんまです。

想定するシチュエーション

  • 元々、CMakeを使って、C/C++アプリケーション開発をしていた
    • 普段の開発環境はVisual Studioとか、GCC
  • アプリケーションをAndroidに移植する必要が出てきた
    • 移植するのは、コアとなるライブラリ部のみ(関数)
    • アプリケーション部(main関数や描画処理)は移植しない。アプリケーション部に該当するのはJava側で作られるはず
  • 「移植」とはいえ、ライブラリ部は従来のC/C++アプリケーションでも使うし、新規に開発するAndroidアプリケーションでも使う。二重メンテはしたくないので、共用したい

既存のプロジェクト

以下のような非常にシンプルなプロジェクトを考えます。
Main.cpp にはmain関数のみがあります。普段のC/C++開発ではこのmain関数が「アプリケーション」として主に動いているとします。今回、Android側では使用しません。
Submodule には関数群があります。今回は、単に入力値を2倍するだけのSubModule_funcDouble という関数を用意しました。これを「ライブラリ」としてJavaから呼ぶことを今回の目標にします。

プロジェクト構造
MyProject
├─CMakeLists.txt
├─Main.cpp
└─SubModule
   ├─CMakeLists.txt
   ├─SubModule.cpp
   └─SubModule.h
TopのCMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project("C_application") 

add_executable(Main "Main.cpp")

add_subdirectory(SubModule)
target_include_directories(Main PUBLIC ./SubModule)
target_link_libraries(Main SubModule)
Main.cpp
#include <stdio.h>
#include "SubModule.h"

int main()
{
    SubModule_funcDouble(10);
    return 0;
}
SubModuleのCMakeLists.txt
cmake_minimum_required(VERSION 3.0)
add_library(SubModule STATIC SubModule.cpp SubModule.h)
SubModule.cpp
#include <stdio.h>
#include "SubModule.h"

int SubModule_funcDouble(int a)
{
    printf("SubModule_funcDouble: %d x 2 = %d\n", a, a * 2);
    return a * 2;
}
SubModule.h
#ifndef _SUB_MODULE_H_
#define _SUB_MODULE_H_

#ifdef __cplusplus
extern "C" {
#endif
#if 0
}
#endif

int SubModule_funcDouble(int a);

#ifdef __cplusplus
}
#endif

#endif  /* _SUB_MODULE_H_ */

Androidアプリケーションを作る

NDKのインストール

まず、Android Studioの適当なプロジェクトを開き、メニューバー -> Tools -> SDK Manager を開きます。
SDK Toolsから、LLDBCMakeNDK を選びOKをクリックしてインストールします。

01.png

(これは、後でプロジェクトを作ってからやっても良いです)

Androidプロジェクトの生成

Android Studioを開き、新規プロジェクトを作ります。
テンプレートを選ぶ際に、Native C++ を選びます。

03.png

プロジェクトの保存場所

僕は、C/C++と一緒にソースコード管理したかったので、以下のように同じ場所にAndroidApp というフォルダを作り、その中に保存しました。

プロジェクト構造
MyProject
├─AndroidApp/     <-- ここにAndroidプロジェクトを保存
├─CMakeLists.txt
├─Main.cpp
└─SubModule/
   ├─CMakeLists.txt
   ├─SubModule.cpp
   └─SubModule.h

メモ: 他の方法

C/C++コードとAndroid用コードを別々に管理する場合には、全然別の場所に保存しても大丈夫です。
また、C/C++コードも新規に作る場合には、Androidプロジェクトの中にCPPフォルダがあるのでそこに保存していけばいいと思います。おそらくこれが想定された使い方だと思います。
ただ、今回は「既存の」C/C++コードを使いたいということと、C/C++コードをAndroidとは関係なく既存のC/C++プロジェクトとして使用している人もいることを想定して、上記のような構成にしました。(共通のライブラリ関数を、Androidアプリでも使うし、C/C++プロジェクトでも使う場合を想定)

どういう構成で各種ファイルを保存するかは、プロジェクト体制やチームメンバー、運用方法によって決めてください。

メモ: 既存のAndroidプロジェクトにC/C++を組込む場合

取り込みたいプロジェクト(app) を右クリックして、Link C++ Project with Gradle を選べば大丈夫です。ただ、後で紹介するようなCMakeLists.txtのひな形などは作られないようです。

02.png

C/C++コードの編集

05.png

生成されたプロジェクトを見ると、上記のようにcpp というフォルダが作られ、その下にCMakeLists.txtnative-lib.cpp が作られています。デフォルトで、Hello Worldという文字列を返すだけの関数を用意したライブラリのひな形が作られています。

以後、CMakeLists.txtを変更した場合は、まずはメニューバー -> Build -> Reflesh Linked C++ Projects をするといいと思います。

CMakeListsの編集

C/C++コードも新規で作る場合にはこのCMakeListsもがりがり編集して良いと思うのですが、今回は既存のC/C++コードを取り込むだけなので、出来るだけ変更の手間が少なくなるようにします。

以下のように、既存のC/C++コードのSubModuleのadd_subdirectory と、インクルードパスの追加、リンク設定だけを最後に追加しています。パスの指定は、このCMakeLists.txt からの相対パスになります。
今後SubModuleの方でファイルを増やしたり、さらに別のモジュールを呼んだりしても、ここを変更する必要はなくなります。(依存性を駄々洩れにしたヘッダファイルを作ったりしなければ、ですが)

native-lib/CMakeLists.txt
~略~

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp)

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

~略~

target_link_libraries( # Specifies the target library.
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

# ↓追加
add_subdirectory(../../../../../SubModule SubModule)
target_include_directories(native-lib PUBLIC ../../../../../SubModule)
target_link_libraries(native-lib SubModule)
# ↑追加

native-lib.cppの編集

元々用意されている、Hello worldという文字列を返すだけの関数があります。
別にこれは名前を変えてもいいのですが、そのまま使います。

今回呼びたい関数はSubModule の中にあるので、基本的にはここにはWrapperとしての役割を担ってもらいます。
JNIに則って、JavaとC/C++のインターフェイス変換をしてもらいます。また、今回はintだけなので簡単ですが、配列やオブジェクトが引数や戻り値にある場合には、その変換もしてもらいます。

native-lib.cpp
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

// ↓追加
#include "SubModule.h"
extern "C" JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_funcDouble(
        JNIEnv *env,
        jobject /* this */,
        jint a
) {
    int ret = SubModule_funcDouble(a);
    return ret;
}
// ↑追加

JavaコードからC/C++関数を呼ぶ

いよいよ、JavaコードからC/C++関数を呼んでみます。
ライブラリ(so)ファイルのロード処理(loadLibrary )は既に自動生成されています。

C/C++ネイティブ関数の宣言をします。どうも一番下に書くのがお作法みたいです。
その後、関数コールは普通のJavaコードとして実装できます。

MainActivity.java
package com.example.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());

        // ↓追加
        int ret = funcDouble(10);
        Log.i("MyApp", "funcDouble: " + ret);
        // ↑追加
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();

    // ↓追加
    public native int funcDouble(int a);
    // ↑追加
}

メモ

printf

C/C++側でのprintfはどこにも出力されず、無視されるみたいでした。ただ、おそらく環境依存なので、#ifdef __ANDRDOID__ などでprintfにするか__android_log_print にするかを切り替えられるようにした方がよさそうです

fopen

万が一、C/C++側でファイル読み書きするコードがある場合、何の考慮もしないとSIGSEGVでクラッシュします。
ファイル読み書きに対応するには、

  • Manifestに、<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> を追加
    • その後、Androidのアプリ設定から権限付与。または、本記事のようにアプリから自発的に権限付与を催促する
  • fopenで指定するパスをAndroidシステム上で存在するディレクトリにする
    • カレントディレクトリに出力したいからと、fopen("test.txt", "w"); とするのはダメ
    • 例えばシステムメモリのトップに出力するためには、fopen("/sdcard/test.txt", "w"); とする

OpenMP

native-lib下のCMakeLists.txt に以下を追加することで、普通に使えました。
SubModule下のCMakeLists.txtにつけるのだと、上手くいきませんでした。(ビルドエラー発生)

native-lib下のCMakeLists.txtに以下を追加
# ↓追加
find_package(OpenMP REQUIRED)
if(OpenMP_FOUND)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
endif()
# ↑追加

add_subdirectory(../../../../../SubModule SubModule)
target_include_directories(native-lib PUBLIC ../../../../../SubModule)
target_link_libraries(native-lib SubModule)

また、UIスレッドからの呼び出しじゃないとCrashするという情報がネットにありましたが、UIスレッド以外からの呼び出しでも問題なく動いているようです。(NDKバージョンは19.2)
具体的には、OpenCVのCameraBridgeViewBase.CvCameraViewListeneronCameraFrame コールバック内でネイティブ関数を呼び、その中でOpenMPでfor文を回してみましたが、クラッシュなく動作していました。高速化もされていました。

おわりに

かなり的を絞ったユースケースを想定した方法です。
もっといい方法があるかもしれません。
改善点があれば、コメントなどで教えていただけると嬉しいです。

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

最近(2019/05)のAndroid開発でよく使われているっぽいライブラリまとめ

私は去年から真面目にAndroid開発を始めたのですが、プロダクトの開発で息するように使われているライブラリなどは誰も教えてくれず知るまでなかなか時間がかかりました。この記事ではこれからAndroid開発を始める人向けに今よく使われているライブラリをまとめます。

「よく使われている」は完全に主観です。

AAC

Android Architecture Componentsの略で、Android Jetpackのコンポーネントの一つです

https://developer.android.com/jetpack?hl=JA

AACに含まれるライブラリの中でも特によく使うものについて紹介します。

Data Binding Library

https://developer.android.com/topic/libraries/data-binding/?hl=JA

文字通りデータバインディングに使うライブラリです。私はMVVMでの開発でよく使ってます。XMLにオブジェクトをバインドしたり、後述するLiveDataなどで双方向のデータバインディングもできるので大変便利です。

Lifecycle

https://developer.android.com/topic/libraries/architecture/lifecycle?hl=JA

ActivityやFragmentのライフサイクルを管理します。LifecycleObserverを使うとActivityやFragmentのライフサイクルイベントを他のクラスで監視することができるのでとても便利です。

LiveData

https://developer.android.com/topic/libraries/architecture/livedata?hl=JA

LiveDataはライフサイクルと連動して、値の変更を通知することができます。私はRxのSubjectを使ってレイヤー間の通知を行っていた部分をLiveDataに置き換えました。双方向のデータバインディングにも利用できて便利です。

Room

https://developer.android.com/topic/libraries/architecture/room?hl=JA

RoomではSQLiteをスムーズに使えるようなインターフェースを提供しています。私はそこまでがっつり使ってないのでなんとも言えません。

ViewModel

https://developer.android.com/topic/libraries/architecture/viewmodel?hl=JA

ViewModelはActivityやFragmentのライフサイクルと関連づけてUI関連のデータを管理することができます。いわゆるMVVMのViewModelとは文脈が少々異なる物だと思いますが、MVVMでのアプリケーション開発にはとても役立ちます。私はめちゃくちゃ使っています。

その他

  • WorkManager
    • ぶっちゃけ使ったことないので知りません
  • Navigation
    • 遷移周りを管理します。safeArgs(Fragment間でのデータ受け渡しに使えるモノ)が便利だった
  • Paging
    • ぶっちゃけ使ったことないので知りません

通信周り

okhttp + retrofit + RxJava(RxKotlin) + RxAndroid + moshi(gson) みたいな構成をよく見ます。 最近はcoroutineを使った非同期処理もよく見かけます。

HTTPクライアント

okhttp

https://github.com/square/okhttp

デファクトでしょう。私はこれが無いと生きていけないです。

retrofit2

https://github.com/square/retrofit

RESTをいい感じに扱えるライブラリ。okhttpと一緒に使うことが多し。

JSONパーサ

一昔前はJacksonが使われていたらしい?

gson

https://github.com/google/gson

google製です。よく見ます。私はDeserializerを自分で書かなきゃいけない時など複雑な要件の時に使ってます。

moshi

https://github.com/square/moshi

square製です。最近は専らこっちを使ってます。

kotshi

https://github.com/ansman/kotshi

kotlin実装です。kotlin向けのMoshiのadapter。

画像

glide

https://github.com/bumptech/glide

画像表示には欠かせない存在です。よく見ます。

picasso

https://github.com/square/picasso

こっちもよく見ます。体感的にはglideの方が使われてそう。

Rx

RxJava

https://github.com/ReactiveX/RxJava

リアクティブプログラミングというパラダイムのコード記述ができます。学習コストは高いですが覚えると大変便利です。非同期処理用のライブラリだと思われがち。最近は他の技術に置き換える流れを感じますがまだまだ色んなところで使われているし個人的にはこれが無いとかなり辛い。

RxKotlin

https://github.com/ReactiveX/RxKotlin

RxJavaのKotlin拡張です。KotlinでRxを使うならぜひ導入しましょう。

RxAndroid

https://github.com/ReactiveX/RxAndroid

Android開発におけるRxJavaでの非同期通信には欠かせない存在です。

RecyclerView

groupie

https://github.com/lisawray/groupie

最近人気すぎて怖い。でも確かに便利です。

epoxy

https://github.com/airbnb/epoxy

airbnb製。ぶっちゃけ使ったこと無いのでよくわかりませんが便利そう。MvRxや他のairbnb製のライブラリと相性が良さげです。

デバッグ関連

Log代替

timber

https://github.com/JakeWharton/timber

android.util.Logのラッパーです。誰も教えてくれないけどみんな使ってます(主観)。ビルドタイプによる機能の切り替えなどができます。

メモリ管理

leakcanary

https://github.com/square/leakcanary

信頼と実績のsquare製。アプリのメモリリークを検出し、通知します。当たり前のように使われている感じがします。

通信ログ周り

stetho

https://github.com/facebook/stetho

okhttpに噛ませるとChromeで通信ログなどが見れます。めちゃ便利。

flipper

https://github.com/facebook/flipper

Facebook製。Android以外のプラットフォームでも使えます。使ってる人見たことないです。stethoで良くね?

その他

Hyperion-Android

https://github.com/willowtreeapps/Hyperion-Android

色々できます。詳しくはこちらの記事を見てください。

DI

Dependency Injectionの略です。最近は知ってて当たり前感があります。参考

dagger2

https://github.com/google/dagger

square発google製。めちゃくちゃ使われてます。学習コストはかなり高いと思います。

koin

https://github.com/InsertKoinIO/koin

kotlin実装のDIライブラリ。知名度も上がってきて勢いを感じます。daggerよりわかりやすいので私は好きです。

テスト周り

mockito

https://github.com/mockito/mockito

モッキングフレームワークです。デファクト感があります。

mockk

https://github.com/mockk/mockk

kotlin実装のモッキングライブラリです。最近見かけるようになりました。

その他

ThreeTenABP

https://github.com/JakeWharton/ThreeTenABP

Androidで日付を簡単に扱えるライブラリ。誰も教えてくれないけど大変便利で使ってる人も多いはず。

所感

  • Java -> Kotlinの流れを強く感じます(koinとか)
  • 最近のAndroid開発ではdaggerやRxなど学習コストの高いものを覚える必要があり、開発を始める敷居が高くなっているように思います
  • 「これはみんな使ってるっしょ!」みたいなライブラリがあれば是非教えてください
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最近のAndroid開発でよく使われているっぽいライブラリまとめ(2019/05)

私は去年から真面目にAndroid開発を始めたのですが、プロダクトの開発で息するように使われているライブラリなどは誰も教えてくれず知るまでなかなか時間がかかりました。この記事ではこれからAndroid開発を始める人向けに今よく使われているライブラリをまとめます。

「よく使われている」は完全に主観です。

AAC

Android Architecture Componentsの略で、Android Jetpackのコンポーネントの一つです

https://developer.android.com/jetpack?hl=JA

AACに含まれるライブラリの中でも特によく使うものについて紹介します。

Data Binding Library

https://developer.android.com/topic/libraries/data-binding/?hl=JA

文字通りデータバインディングに使うライブラリです。私はMVVMでの開発でよく使ってます。XMLにオブジェクトをバインドしたり、後述するLiveDataなどで双方向のデータバインディングもできるので大変便利です。

Lifecycle

https://developer.android.com/topic/libraries/architecture/lifecycle?hl=JA

ActivityやFragmentのライフサイクルを管理します。LifecycleObserverを使うとActivityやFragmentのライフサイクルイベントを他のクラスで監視することができるのでとても便利です。

LiveData

https://developer.android.com/topic/libraries/architecture/livedata?hl=JA

LiveDataはライフサイクルと連動して、値の変更を通知することができます。私はRxのSubjectを使ってレイヤー間の通知を行っていた部分をLiveDataに置き換えました。双方向のデータバインディングにも利用できて便利です。

Room

https://developer.android.com/topic/libraries/architecture/room?hl=JA

RoomではSQLiteをスムーズに使えるようなインターフェースを提供しています。私はそこまでがっつり使ってないのでなんとも言えません。

ViewModel

https://developer.android.com/topic/libraries/architecture/viewmodel?hl=JA

ViewModelはActivityやFragmentのライフサイクルと関連づけてUI関連のデータを管理することができます。いわゆるMVVMのViewModelとは文脈が少々異なる物だと思いますが、MVVMでのアプリケーション開発にはとても役立ちます。私はめちゃくちゃ使っています。

その他

  • WorkManager
    • ぶっちゃけ使ったことないので知りません
  • Navigation
    • 遷移周りを管理します。safeArgs(Fragment間でのデータ受け渡しに使えるモノ)が便利だった
  • Paging
    • ぶっちゃけ使ったことないので知りません

通信周り

okhttp + retrofit + RxJava(RxKotlin) + RxAndroid + moshi(gson) みたいな構成をよく見ます。 最近はcoroutineを使った非同期処理もよく見かけます。

HTTPクライアント

okhttp

https://github.com/square/okhttp

デファクトでしょう。私はこれが無いと生きていけないです。

retrofit2

https://github.com/square/retrofit

RESTをいい感じに扱えるライブラリ。okhttpと一緒に使うことが多し。

JSONパーサ

一昔前はJacksonが使われていたらしい?

gson

https://github.com/google/gson

google製です。よく見ます。私はDeserializerを自分で書かなきゃいけない時など複雑な要件の時に使ってます。

moshi

https://github.com/square/moshi

square製です。最近は専らこっちを使ってます。

kotshi

https://github.com/ansman/kotshi

kotlin実装です。kotlin向けのMoshiのadapter。

画像

glide

https://github.com/bumptech/glide

画像表示には欠かせない存在です。よく見ます。

picasso

https://github.com/square/picasso

こっちもよく見ます。体感的にはglideの方が使われてそう。

Rx

RxJava

https://github.com/ReactiveX/RxJava

リアクティブプログラミングというパラダイムのコード記述ができます。学習コストは高いですが覚えると大変便利です。非同期処理用のライブラリだと思われがち。最近は他の技術に置き換える流れを感じますがまだまだ色んなところで使われているし個人的にはこれが無いとかなり辛い。

RxKotlin

https://github.com/ReactiveX/RxKotlin

RxJavaのKotlin拡張です。KotlinでRxを使うならぜひ導入しましょう。

RxAndroid

https://github.com/ReactiveX/RxAndroid

Android開発におけるRxJavaでの非同期通信には欠かせない存在です。

RecyclerView

groupie

https://github.com/lisawray/groupie

最近人気すぎて怖い。でも確かに便利です。

epoxy

https://github.com/airbnb/epoxy

airbnb製。ぶっちゃけ使ったこと無いのでよくわかりませんが便利そう。MvRxや他のairbnb製のライブラリと相性が良さげです。

デバッグ関連

Log代替

timber

https://github.com/JakeWharton/timber

android.util.Logのラッパーです。誰も教えてくれないけどみんな使ってます(主観)。ビルドタイプによる機能の切り替えなどができます。

メモリ管理

leakcanary

https://github.com/square/leakcanary

信頼と実績のsquare製。アプリのメモリリークを検出し、通知します。当たり前のように使われている感じがします。

通信ログ周り

stetho

https://github.com/facebook/stetho

okhttpに噛ませるとChromeで通信ログなどが見れます。めちゃ便利。

flipper

https://github.com/facebook/flipper

Facebook製。Android以外のプラットフォームでも使えます。使ってる人見たことないです。stethoで良くね?

その他

Hyperion-Android

https://github.com/willowtreeapps/Hyperion-Android

色々できます。詳しくはこちらの記事を見てください。

DI

Dependency Injectionの略です。最近は知ってて当たり前感があります。参考

dagger2

https://github.com/google/dagger

square発google製。めちゃくちゃ使われてます。学習コストはかなり高いと思います。

koin

https://github.com/InsertKoinIO/koin

kotlin実装のDIライブラリ。知名度も上がってきて勢いを感じます。daggerよりわかりやすいので私は好きです。

テスト周り

mockito

https://github.com/mockito/mockito

モッキングフレームワークです。デファクト感があります。

mockk

https://github.com/mockk/mockk

kotlin実装のモッキングライブラリです。最近見かけるようになりました。

その他

ThreeTenABP

https://github.com/JakeWharton/ThreeTenABP

Androidで日付を簡単に扱えるライブラリ。誰も教えてくれないけど大変便利で使ってる人も多いはず。

所感

  • Java -> Kotlinの流れを強く感じます(koinとか)
  • 最近のAndroid開発ではdaggerやRxなど学習コストの高いものを覚える必要があり、開発を始める敷居が高くなっているように思います
  • 「これはみんな使ってるっしょ!」みたいなライブラリがあれば是非教えてください
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java初心者】JavaBeansとSessionの違い

JavaBeansとSessionってどっちも値を保持できるけど違いってなんですか?

これを聞かれてうまく答えることができなかったのと、調べても比較しているサイトがあまり出てこなかったのでここにまとめたいと思います?

対象読者

  • Java初心者(私もここです)
  • なんとなくJavaBeansとSessionを使っていた

JavaBeansの長所、短所

長所

  • getter,setterで入ってきた値を操作することができる
JavabeansData.java
/* --------------------------------
今後の説明のために一通りコードを記載しています。
---------------------------------*/

public class JavaBeans implements Serializable { //永続化

  //カプセル
  private String name = "";
  private int age = "";
  private String tel = "";
  private String gender = "";
  private Date birthday = null;

  JavaBeans(){}//コンストラクタ

  //標準
  public String getName(){
    return name;
  }
  public void setName(String name){
    this.name = name;
  }

  /*省略*/

  //変更
  public void setBirthday(Date birthday){
    //空文字ならnullを代入
    if(birthday == ""){
      this.birthday = null;
    }
    this.birthday = birthday;
  }

}
  • インスタンスに値を保持することで値の出し入れが簡単
javabeans.java
//インスタンス生成
JavaBeansData jbd = new JavaBeansData();

//インスタンスに登録
jb.setName(name);
jb.setAge(age);
jb.setTel(tel);
jb.setGender(gender);
jb.setBirthday(birthday);

//呼び出し
jb.getName();
jb.getAge();
jb.getTel();
jb.getGender();
jb.getBirthday();

短所

  • 別のクラスに値を運ぶことができない

JavaBeansのみだと呼び出す際に毎回値の初期化を行ってしまうので永続化を行っていても値の持ち運びができない

javabeans2.java
///初期化されてしまう
JavaBeansData jbd = new JavaBeans();

jdb.getName();//値が初期値

Sessionの長所、短所

長所

別のクラスに値を持ち運べる

session2.java
//セッションの呼び出しでリクエストスコープから値を毎回取得するので初期化されない
HttpSession session = request.getSession();

短所

  • たくさんの値を出し入れするのは不向き
session.java
//セッションスタート
HttpSession session = request.getSession();

//登録
session.setAttribute("name", name);
session.setAttribute("age", age);
session.setAttribute("tel", tel);
session.setAttribute("gender", gender);
session.setAttribute("birthday", birthday);

//呼び出し
session.getAttribute("name");
session.getAttribute("age");
session.getAttribute("tel");
session.getAttribute("gender");
session.getAttribute("birthday");
//セッションだと冗長的
  • 値の操作ができない

まとめ

  • 保持する値を操作したい
  • 沢山の値を保持したい

上記のような場合はJavaBeansを使用

  • 値をどこにでも持ち運びたい
  • 持ち運ぶ数が少ない

上記のような場合はSessionを使用

それぞれの長所短所を理解した上での使い分けが非常に大事ですね✨

補足

沢山の値を保持したいけどどこにでも持ち運びたい!!

という事が多々あると思います。
その場合は、JavaBeansSessionに入れましょう❗

hoge.java
//インスタンス生成
JavaBeansData jbd = new JavaBeansData();

//インスタンスに登録
jb.setName(name);
jb.setAge(age);
jb.setTel(tel);
jb.setGender(gender);
jb.setBirthday(birthday);

//セッションスタート
HttpSession session = request.getSession();

//JavaBeansのインスタンスをセッションに登録
session.setAttribute("jbd",jbd);
hoge2.java
//セッションスタート
HttpSession session = request.getSession();

//JavaBeansメソッドを扱えるようにセッションをキャスト
JavaBeansData jbd = (JavaBeansData)session.getAttribute("jbd")

jbd.getName();//保持された値が表示される
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Desktop : OpenCV Draw Point

Goal

OpenCV Draw point.

OpenCV_Draw_Point.java
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

import org.opencv.core.Point;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;

public class OpenCV_Draw_Point {
    static{System.loadLibrary(Core.NATIVE_LIBRARY_NAME);}

    private JFrame frmjavaSwing;

    /**
        *   Launch the application.
        */
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try{
                    OpenCV_Draw_Point window = new OpenCV_Draw_Point();
                    window.frmjavaSwing.setVisible(true);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     *  Create the application.
     */
    public OpenCV_Draw_Point(){
        init();
    }
    /**
     * Initialize the contents of the frame.
     */
    private void init() {
        Mat source =  paint();
        BufferedImage image=matToBufferedImage(source);

        frmjavaSwing = new JFrame();
        frmjavaSwing.setTitle("opencv µeÂI½m²ß");
        frmjavaSwing.setBounds(100, 100, 300, 300);
        frmjavaSwing.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmjavaSwing.getContentPane().setLayout(null);

        final JLabel lblNewLabel = new JLabel("");
        lblNewLabel.setBounds(5, 5, image.getHeight()+10, image.getWidth()+10);
        lblNewLabel.setIcon(new ImageIcon(image));
        frmjavaSwing.getContentPane().add(lblNewLabel);
    }

    public Mat paint(){
        Mat source = new Mat(250,250, CvType.CV_8UC3, new Scalar(255,255,255));
        Point pt1=new Point(125,125);

        Imgproc.line(source,pt1,pt1,new Scalar(255,55,220),6);
        return source;
    }

    public BufferedImage matToBufferedImage(Mat matrix) {
        int cols = matrix.cols();
        int rows = matrix.rows();
        int elemSize = (int)matrix.elemSize();
        byte[] data = new byte[cols * rows * elemSize];
        int type;
        matrix.get(0, 0, data);
        switch (matrix.channels()) {
            case 1:
                type = BufferedImage.TYPE_BYTE_GRAY;
                break;
            case 3:
                type = BufferedImage.TYPE_3BYTE_BGR;
                // bgr to rgb
                byte b;
                for(int i=0; i<data.length; i=i+3) {
                    b = data[i];
                    data[i] = data[i+2];
                    data[i+2] = b;
                }
                break;
            default:
                return null;
        }
        BufferedImage image2 = new BufferedImage(cols, rows, type);
        image2.getRaster().setDataElements(0, 0, cols, rows, data);
        return image2;
    }
}
Result

opencv_draw_point.JPG

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

ポリモーフィズムについて

image.png

説明する時にいつも読むのでまとめ

ポリモーフィズムとは

メッセージの送り方を共通にするもの

  • 親クラス生成時に子クラスに渡すメッセージを共通にする仕組み
  • インターフェースを使って(Javaの場合)メッセージを共通にする仕組み
  • コードの重複を排除して汎用性の高い部品をつくる仕組みの1つ
  • オブジェクト指向三大要素の1つ
  • 意訳すると多態性

特徴

概念的側面(現実世界)

上位と下位の概念それぞれの動詞として成立する

共通の動詞が成立する

  • 例えば「動物」と「人間」と「犬」
    • 「動物」(上位)が「食べる」
    • 「人間」(下位)が「食べる」
    • 「犬」が(下位)「食べる」

切り口を1つにしている

  • 1つのオプション(追加情報)で固有の挙動が成立する
  • 例えば「動物」と「人間」と「犬」
    • 「動物」が「食べる」-> どんな感じで食べるか定まっていない
    • 「動物」の「人間」(オプション)が「食べる」-> 食べ物を手で口に近づけて食べる
    • 「動物」の「犬」(オプション)が「食べる」-> 食べ物に頭を近づけて口で食べる

技術的側面(プログラミング)

引数を受け取ったインスタンスがクラスによって違う振る舞いをする

共通のメソッド名で成立する

  • スーパークラスとサブクラスそれぞれのメソッドとして成立する
  • 例えば「Animal」と「human」と「dog」と「chef」(eater)
    • 「シェフ」が「動物」(食べるくん)に提供して「動物」(食べるくん)がそれを「食べる」
    • 「動物」(食べるくん)を変えるだけでぱくぱく食べてたりがぶがぶ食べてたりする

プログラミング技術的における「継承」を利用した場合

Animal.java
public abstract class Animal {
    public abstract void eat();
}
Human.java
public class Human extends Animal {
    public void eat() {
        System.out.println("ぱくぱくたべる");
    }
}
Dog.java
public class Dog extends Animal {
    public void eat() {
        System.out.println("がぶがぶたべる");
    }
}
Chef.java
public class Chef {
    public void serve(Animal animal) {
        System.out.println("どうぞー");
        System.out.println(animal.eat());
    }
}

プログラミング技術的における「インターフェース」を利用した場合

Eater.java
public interface Eater {
    public void eat();
}
Human.java
public class Human implements Eater {
    public void eat() {
        System.out.println("ぱくぱくたべる");
    }
}
Dog.java
public class Dog implements Eater {
    public void eat() {
        System.out.println("がぶがぶたべる");
    }
}
Chef.java
public class Chef {
    public void serve(Eater eater) {
        System.out.println("どうぞー");
        System.out.println(eater.eat());
    }
}

ポイント

  • 継承の機能を使ったものもインターフェースの機能を使ったものも共通していえるのは呼び出し方が種類関係なく同じ(ここでいうとシェフが提供する相手)という部分
  • interfaceはポリモーフィズムを実現するためのようなもの
  • interfaceの機能は言語的にサポートしていないものもある(Rubyなどはそう)

書籍情報

平澤 章 (著) オブジェクト指向でなぜつくるのか 第2版
https://amzn.to/2VSrzwe

雑感

デザインパターンやリファクタリングでもこの視点をもてていると上手く適用できる

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

Desktop :OpenCV Drawing Line

Goal

OpenCV Draw line.

OpenCV_DrawingLine.java
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

import org.opencv.core.Point;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;

public class OpenCV_DrawingLine {

    static{System.loadLibrary(Core.NATIVE_LIBRARY_NAME);}

    private JFrame frmjavaSwing;

    /**
        * Launch the application.
        */
    public static void main(String[] args){

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try{
                    OpenCV_DrawingLine window = new OpenCV_DrawingLine();
                    window.frmjavaSwing.setVisible(true);

                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create thr application.
     */
    public OpenCV_DrawingLine(){
        init();
    }

    private void  init(){
        Mat source =  paint();
        BufferedImage image=matToBufferedImage(source);

        frmjavaSwing = new JFrame();
        frmjavaSwing.setTitle("opencv µe½u½m²ß");
        frmjavaSwing.setBounds(100, 100, 260, 260);
        frmjavaSwing.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmjavaSwing.getContentPane().setLayout(null);

        final JLabel lblNewLabel = new JLabel("");
        lblNewLabel.setBounds(5, 10, image.getHeight()+10, image.getWidth()+10);
        lblNewLabel.setIcon(new ImageIcon(image));
        frmjavaSwing.getContentPane().add(lblNewLabel);

    }

    public Mat paint(){
        Mat source = new Mat(250,250, CvType.CV_8UC3, new Scalar(255,255,255));
        Point pt1=new Point(10,0);
        Point pt2=new Point(10,200);
        Imgproc.line(source,pt1,pt2,new Scalar(255,0,0));
        return source;
    }

    public BufferedImage matToBufferedImage(Mat matrix) {
        int cols = matrix.cols();
        int rows = matrix.rows();
        int elemSize = (int)matrix.elemSize();
        byte[] data = new byte[cols * rows * elemSize];
        int type;
        matrix.get(0, 0, data);
        switch (matrix.channels()) {
            case 1:
                type = BufferedImage.TYPE_BYTE_GRAY;
                break;
            case 3:
                type = BufferedImage.TYPE_3BYTE_BGR;
                // bgr to rgb
                byte b;
                for(int i=0; i<data.length; i=i+3) {
                    b = data[i];
                    data[i] = data[i+2];
                    data[i+2] = b;
                }
                break;
            default:
                return null;
        }
        BufferedImage image2 = new BufferedImage(cols, rows, type);
        image2.getRaster().setDataElements(0, 0, cols, rows, data);
        return image2;
    }

}
Result

opencv_draw_line.JPG

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

Deck.GLのTripLayerでGTFS-JPのバス運行状況を可視化してみた

概要

地域交通の未来と公共交通オープンデータ - HackMDの発表資料「地域交通の未来と公共交通オープンデータ」を見て、GTSF-JPに興味を持ちました。
GTFS-JPの仕様を確認したところ面白そうだったので、GTFS-JPデータを公開している両備バスの運行状況をDeck.GLのTriplayerで可視化してみました。

なお、今回作成したコードはGithubにおいています。
Github:gtfsjp_tmat_test

GTFS-JPの概要

GTFS(General Transit Feed Specification)は公共交通機関の時刻表と地理的情報に関するオープンフォーマットであり、GTFSを日本の「標準的なバス情報フォーマット」として拡張されたものがGTFS-JPです。
国土交通省・公共交通政策ホームページでは、「静的バス情報フォーマット(GTFS-JP)」と「動的バス情報フォーマット(GTFSリアルタイム)」についての仕様が示されています。
- GTFS-JP仕様書
- GTFS-JPサイト
- GTFS-Githubプロジェクト

静的バス情報フォーマット(GTFS-JP)

GTFS-JPは、バス路線の事業者やバス停、バス路線の情報を最大17個のCSVファイルで整理し、Zip形式で配布されます。 配布データは単なるCSVなので、テキスト処理で諸情報を入手することができます。
- agency.txt(事業者情報)
- stops.txt(停留所・標柱情報)
- routes.txt(経路情報)
- trips.txt(便情報)
- office_jp.txt(営業所情報)
- stop_times.txt(通過時刻情報)
- calendar.txt(運行区分情報)
- fareattributes.txt(運賃情報)
- farerules.txt(運賃定義情報)
- feed_info.txt(提供者情報)
- agency_jp.txt(事業者追加情報)
- routes_jp.txt(経路追加情報)
- calendar_dates.txt(運行日情報)
- shapes.txt(描写情報)
- frequencies.txt(運行間隔情報)
- transfers.txt(乗換情報)
- translations.txt(翻訳者情報)

動的バス情報フォーマット(GTFSリアルタイム)

GTFSリアルタイムでは、遅延等のルート最新情報や⾞両位置情報、運⾏情報等のリアルタイム除法をProtocol Bufferというデータ構造が規定されたバイナリデータ(*.bin)で配布されます。 なお、GTFSリアルタイムについては、バイナリデータ読み取り用クラスを生成する「gtfs-realtime-bindings」がMavenRepositiryに登録されています。
- gtfs-realtime-bindings

GTFS-JPからDB(Sqlite3)を生成する

仕様書に目を通したところで、Bus-Visionから、GTFS-JPを配布している両備バスのデータをSqliteに導入しています。
処理の概要は以下のとおりです。なお、ORマッパーはomliteを使用しています。

    private static Pattern csvDiv=Pattern.compile("\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\"|([^,]+)|,|");

    /** GTFS-JPファイル(ZIPファイル)からCSVデータを取得してテーブルを作成*/
    public void buildGtfsDB(InputStream is) throws IOException,ParseException{
        ZipInputStream zis=new ZipInputStream(is);
        ZipEntry ze;
        byte[] buf= new byte[1024];
        StringBuffer sb=new StringBuffer();
        while ((ze=zis.getNextEntry()) != null) {
            int size = 0;
            while((size=zis.read(buf))>0){
                String str=new String(buf,0,size);
                sb.append(str);
            }
            zis.closeEntry();
            BufferedReader br=new BufferedReader(new StringReader(sb.toString()));
            String[][] csv=parseCsv(br);
            String name=ze.getName().replace(".txt", "").toLowerCase();
            List<Map<String,Object>> list=json2Csv(csv);
            try{
                create(name,list);
            }catch(SQLException e){
                e.printStackTrace();
            }
            sb.delete(0, sb.length());
        }
    }

    private void create(String name,List<Map<String,Object>> list)throws SQLException{
        if(name.equals("agency")){
            create(this.agencyDao,list);
        }else if(name.equals("agency_jp")){
            create(this.agencyJpDao,list);
        }else if(name.equals("calendar")){
            create(this.calDao,list);
        }else if(name.equals("calendar_dates")){
            create(this.calDateDao,list);
        }else if(name.equals("fare_attributes")){
            create(this.fareAttrDao,list);
        }else if(name.equals("fare_rules")){
            create(this.fareRuleDao,list);
        }else if(name.equals("feed_info")){
            create(this.feedDao,list);
        }else if(name.equals("frequencies")){
            create(this.freqDao,list);
        }else if(name.equals("office_jp")){
            create(this.officeJpDao,list);
        }else if(name.equals("routes_jp")){
            create(this.routesJpDao,list);
        }else if(name.equals("routes")){
            create(this.routesDao,list);
        }else if(name.equals("shapes")){
            create(this.shapeDao,list);
        }else if(name.equals("stop_times")){
            create(this.stopTimeDao,list);
        }else if(name.equals("stops")){
            create(this.stopsDao,list);
        }else if(name.equals("transfers")){
            create(this.transfersDao,list);
        }else if(name.equals("translations")){
            create(this.translationslDao,list);
        }else if(name.equals("trips")){
            create(this.tripsDao,list);
        }
    }

    /** setter/getterを使うのはめんどいので、一度JSONを生成し、
        JSONからオブジェクトを生成してDBにぶっこんでます*/
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void create(Dao dao,List<Map<String,Object>> list)throws SQLException{
        final List array=new ArrayList();
        for(Map<String,Object> map : list){
            String json=gson.toJson(map);
            array.add(gson.fromJson(json,dao.getDataClass()));
        }
        TransactionManager.callInTransaction(dao.getConnectionSource(), new Callable<Void>(){
            public Void call() throws Exception {
                for(Object fp : array){
                    dao.create(fp);
                }
                return null;
            }
        });
    }

    /** CSVファイルをパース*/
    private static String[][] parseCsv(BufferedReader reader)throws IOException{
        ArrayList<String[]> list=new ArrayList<String[]>();
        String line;
        while((line=reader.readLine())!=null){
            String[] sp=split(csvDiv,line);
            list.add(sp);
        }
        if(list.size()==0)return new String[0][0];
        return list.toArray(new String[list.size()][]);
    }

    private static String[] split(Pattern pattern,String line){
        Matcher matcher=pattern.matcher(line);
        List<String> list=new ArrayList<String>();
        int index=0;
        int com=0;
        while(index<line.length()){
            if(matcher.find(index+com)){
                String s=matcher.group().replaceAll("\"", "");
                index=matcher.end();
                list.add(s);
                com=1;
            }
        }
        return list.toArray(new String[list.size()]);
    }

DBからTripLayer用データ(JSON)を生成する

次いで、Tripsからtrip_idを、Stop_timesからそのTripの停留所と発車時刻、Stopsから停留所の位置情報を取得して、TripLayer用データ(Json)を生成しています。
処理の概要は以下のとおりです。
なお、Tripデータは[経度、緯度、タイムスタンプ]の形で整理しており、タイムスタンプは32bit浮動小数点にする必要があるので、一度、Javaのシリアル値を生成した後、午前0時からの経過時間(分)に変換しています。また、今回は停留所のリストをバスの通行経路として扱いました。

    /** DBからTripLayer用データを生成 */
    public String getTripData() throws SQLException,ParseException{
        Map<String,Object> map=new HashMap<String,Object>();
        List<GTrip> trips=getTrips();
        String ymd=getSurviceDate(trips.get(0));
        List<Map<String,List<Number[]>>> waypoints=new ArrayList<Map<String,List<Number[]>>>();
        long st=dateFormat.parse(ymd+" 00:00:00").getTime();
        for(GTrip t : trips){
            List<Number[]> wl=getWaypoints(t,st);
            Map<String,List<Number[]>> mm=new HashMap<String,List<Number[]>>();
            mm.put("segments", wl);
            waypoints.add(mm);
        }
        map.put("starttime", st);
        map.put("trips",waypoints);
        map.put("stops", stop2Map(getStops()));
        return gson.toJson(map);
    }

    private List<GStop> getStops()throws SQLException{
        return this.stopsDao.queryForAll();
    }

    private List<GTrip> getTrips()throws SQLException{
        return this.tripsDao.queryForAll();
    }

    /** Trip単位で通過経路(経度、緯度、時刻)のデータを生成 */
    private List<Number[]> getWaypoints(GTrip t,long sub)throws SQLException,ParseException{
        String ymd=getSurviceDate(t);
        QueryBuilder<GStopTime,Long> query=stopTimeDao.queryBuilder();
        query.where().eq("trip_id", t.trip_id);
        List<GStopTime> list=stopTimeDao.query(query.prepare());
        list.sort(new Comparator<GStopTime>(){
            @Override
            public int compare(GStopTime arg0, GStopTime arg1) {
                int v0=Integer.parseInt(arg0.stop_sequence);
                int v1=Integer.parseInt(arg1.stop_sequence);
                return (v0-v1);
            }
        });
        int n=list.size();
        List<Number[]> data=new ArrayList<Number[]>();
        for(int i=0;i<n;i++){
            GStopTime st=list.get(i);
            data.add(createWaypoint(st,ymd,sub));
        }
        return data;
    }

    private Number[] createWaypoint(GStopTime st,String ymd,long sub)throws ParseException,SQLException{
        long timestamp=getStopTimes(st,ymd);
        GStop sa=getStops(st.stop_id);
        return new Number[]{sa.stop_lon,sa.stop_lat,((double)(timestamp-sub))/(60.0*1000.0)};
    }

    private String getSurviceDate(GTrip t)throws SQLException{
        QueryBuilder<GCalendar,Long> query=calDao.queryBuilder();
        query.where().eq("service_id", t.service_id);
        List<GCalendar> list=calDao.query(query.prepare());
        String date=list.get(0).start_date;
        return date.substring(0, 4)+"-"+date.substring(4,6)+"-"+date.substring(6,8);
    }

    private GStop getStops(String stop_id)throws SQLException{
        QueryBuilder<GStop,Long> query=stopsDao.queryBuilder();
        query.where().eq("stop_id", stop_id);
        List<GStop> list=stopsDao.query(query.prepare());
        return list.get(0);
    }

    private long getStopTimes(GStopTime s,String ymd)throws ParseException{
        return dateFormat.parse(ymd+" "+s.arrival_time).getTime();
    }

Deck.GLでTripLayer可視化

作成したJSONデータはTripLayerに合わせた形にしているので、そのまま読み込ましてTripLayerを作成しています。
今回は停留所のリストをそのまま通行経路としたため、直行便はほぼ一直線に目的地へ移動してしまいます。バス運行経路を正確に再現し、道路に沿って動くようにするにはShapes.txtのデータをトレースして[経度、緯度、タイプスタンプ]のデータのリストを生成する必要があります。

<!doctype html>
<html class="no-js" lang="ja">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>Traffic-Bus</title>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.53.1/mapbox-gl.css" />
        <script src="https://code.jquery.com/jquery-3.4.0.js" integrity="sha256-DYZMCC8HTC+QDr5QNaIcfR7VSPtcISykd+6eSmBW5qo=" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.53.1/mapbox-gl.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/deck.gl@7.0.0/dist.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-scale/1.0.7/d3-scale.js"></script>
        <style type="text/css">
            html, body {
                padding: 0;
                margin: 0;
                width: 100%;
                height: 100%;
            }
            #panel {
                position: absolute;
                background: #ffffffaa;
                top: 0;
                left: 0;
                margin: 4px;
                padding: 4px;
                line-height: 1;
                width:260px;
                height:26px;
                z-index: 2;
                text-align: center;
                vertical-align: middle;
            }
            #tooltip {
                font-family: Helvetica, Arial, sans-serif;
                font-size: 12px;
                position: absolute;
                padding: 4px;
                margin: 8px;
                background: rgba(0, 0, 0, 0.8);
                color: #fff;
                max-width: 300px;
                z-index: 9;
                pointer-events: none;
            }
        </style>
    </head>
    <body>
        <div id="panel"></div>
        <div id="app" style="width:100%;height:100%;"></div>
        <div id="tooltip"></div>
    </body>
    <script type="text/javascript">
        const LAT=34.6;
        const LNG=135.5;
        let c_lon=0;
        let c_lat=0;
        let c_time=300;
        let triplayers=[];
        const deckgl = new deck.DeckGL({
            container: 'app',
            mapboxApiAccessToken: "アクセストークン",
            mapStyle: "mapbox://styles/mapbox/dark-v9",
            longitude: LNG,
            latitude: LAT,
            zoom: 11,
            pitch: 40,
            bearing: -10,
            onViewStateChange: ({viewState}) => {
                return viewState;
            }
        });
        const timeVal = () =>{
            return c_time;
        };

        const loadData = () => {
            d3.json("trips.json", (error, response)=>{
                setCenter(response.stops);
                let v_time=response.starttime;
                let trip=renderTripLayer(response.trips);
                let stop=renderStopLayer(response.stops);
                deckgl.setProps({
                    layers: [trip,stop],
                    viewState: {
                        longitude: c_lon,
                        latitude: c_lat,
                        zoom: 11,
                        transitionInterpolator: new FlyToInterpolator(),
                        transitionDuration: 3000
                    }
                });
                const update = () =>{
                    c_time +=0.1;
                    if(c_time>1439){
                        window.cancelAnimationFrame(thread);
                        return; 
                    }else{
                        trip.setState({time: c_time});
                    let da=new Date(v_time+c_time*60*1000);
                    $("#panel").text(da);
                    window.requestAnimationFrame(update);
                    }
                };
                let thread=window.requestAnimationFrame(update);
            });
        };
        const renderStopLayer = (data) => {
            const pointlayer = new deck.ScatterplotLayer({
                id: 'stop',
                data,
                fp64: false,
                opacity: 0.8,
                stroked: true,
                filled: true,
                radiusScale: 6,
                radiusMinPixels: 1,
                radiusMaxPixels: 100,
                lineWidthMinPixels: 1,
                getPosition: d => d.coordinates,
                getRadius: 8,
                getFillColor: [255, 140, 0],
                getLineColor: [0, 0, 0],
                pickable: true,
                onHover: updateTooltipStop
            });
            return pointlayer;
        };

        const renderTripLayer =(trips) => {
            const tripLayer = new TripsLayer({
                id: 'trips-layer',
                data: trips,
                getPath: d => d.segments,
                getColor: [253, 128, 93],
                opacity: 0.6,
                widthMinPixels: 3,
                rounded: true,
                trailLength: 15,
                currentTime: timeVal 
            });
            return tripLayer;
        }

        const setCenter=(feature) =>{
            const n=feature.length;
             for(let i=0;i<n;i++){
                c_lon +=feature[i].coordinates[0];
                c_lat +=feature[i].coordinates[1];
            }
            c_lon=c_lon/n;
            c_lat=c_lat/n;
        };

        const updateTooltipStop=({x, y, object}) => {
            const tooltip = document.getElementById("tooltip");
            if (object) {
                tooltip.style.visibility="visible";
                tooltip.style.top = y+"px";
                tooltip.style.left = x+"px";
                tooltip.innerHTML = "<p>"+object.name+"</p>";
            } else { 
                tooltip.style.visibility="hidden";
                tooltip.innerHTML = "";
            }
        };
        loadData();
    </script>
</html>

最後に

とりあえず、GTFS-JPからDBを作成し、Tripデータを作成して可視化するとこまで処理してみました。
GTFS-JPはCSVデータなので扱いが楽です。GTFSリアルタイムも「gtfs-realtime-bindings」を使用すると簡単にデータを処理できそうな感じです。また、両備バスの場合で、DB容量は約14MB(Sqlite)だったので、Android端末等に導入することもできそうだと思いました。
GTFS-JPの本格的な普及はこれからだと思いますが、少子高齢化や過疎化により地方の公共交通機関は色々な課題を抱えているので、かじっておくと何かしら地域の役に立つかも?と思いました。

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

Deck.GLのTripLayerでGTFS-JPのバス運行状況を可視化してみる

概要

地域交通の未来と公共交通オープンデータ - HackMDの発表資料「地域交通の未来と公共交通オープンデータ」を見て、GTSF-JPに興味を持ちました。
GTFS-JPの仕様を確認したところ面白そうだったので、GTFS-JPデータを公開している両備バスの運行状況をDeck.GLのTriplayerで可視化してみました。

なお、今回作成したコードはGithubにおいています。
Github:gtfsjp_tmat_test

GTFS-JPの概要

GTFS(General Transit Feed Specification)は公共交通機関の時刻表と地理的情報に関するオープンフォーマットであり、GTFSを日本の「標準的なバス情報フォーマット」として拡張されたものがGTFS-JPです。
国土交通省・公共交通政策ホームページでは、「静的バス情報フォーマット(GTFS-JP)」と「動的バス情報フォーマット(GTFSリアルタイム)」についての仕様が示されています。
- GTFS-JP仕様書
- GTFS-JPサイト
- GTFS-Githubプロジェクト

静的バス情報フォーマット(GTFS-JP)

GTFS-JPは、バス路線の事業者やバス停、バス路線の情報を最大17個のCSVファイルで整理し、Zip形式で配布されます。 配布データは単なるCSVなので、テキスト処理で諸情報を入手することができます。
- agency.txt(事業者情報)
- stops.txt(停留所・標柱情報)
- routes.txt(経路情報)
- trips.txt(便情報)
- office_jp.txt(営業所情報)
- stop_times.txt(通過時刻情報)
- calendar.txt(運行区分情報)
- fareattributes.txt(運賃情報)
- farerules.txt(運賃定義情報)
- feed_info.txt(提供者情報)
- agency_jp.txt(事業者追加情報)
- routes_jp.txt(経路追加情報)
- calendar_dates.txt(運行日情報)
- shapes.txt(描写情報)
- frequencies.txt(運行間隔情報)
- transfers.txt(乗換情報)
- translations.txt(翻訳者情報)

動的バス情報フォーマット(GTFSリアルタイム)

GTFSリアルタイムでは、遅延等のルート最新情報や⾞両位置情報、運⾏情報等のリアルタイム除法をProtocol Bufferというデータ構造が規定されたバイナリデータ(*.bin)で配布されます。 なお、GTFSリアルタイムについては、バイナリデータ読み取り用クラスを生成する「gtfs-realtime-bindings」がMavenRepositiryに登録されています。
- gtfs-realtime-bindings

GTFS-JPからDB(Sqlite3)を生成する

仕様書に目を通したところで、Bus-Visionから、GTFS-JPを配布している両備バスのデータをSqliteに導入しています。
処理の概要は以下のとおりです。なお、ORマッパーはomliteを使用しています。

    private static Pattern csvDiv=Pattern.compile("\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\"|([^,]+)|,|");

    /** GTFS-JPファイル(ZIPファイル)からCSVデータを取得してテーブルを作成*/
    public void buildGtfsDB(InputStream is) throws IOException,ParseException{
        ZipInputStream zis=new ZipInputStream(is);
        ZipEntry ze;
        byte[] buf= new byte[1024];
        StringBuffer sb=new StringBuffer();
        while ((ze=zis.getNextEntry()) != null) {
            int size = 0;
            while((size=zis.read(buf))>0){
                String str=new String(buf,0,size);
                sb.append(str);
            }
            zis.closeEntry();
            BufferedReader br=new BufferedReader(new StringReader(sb.toString()));
            String[][] csv=parseCsv(br);
            String name=ze.getName().replace(".txt", "").toLowerCase();
            List<Map<String,Object>> list=json2Csv(csv);
            try{
                create(name,list);
            }catch(SQLException e){
                e.printStackTrace();
            }
            sb.delete(0, sb.length());
        }
    }

    private void create(String name,List<Map<String,Object>> list)throws SQLException{
        if(name.equals("agency")){
            create(this.agencyDao,list);
        }else if(name.equals("agency_jp")){
            create(this.agencyJpDao,list);
        }else if(name.equals("calendar")){
            create(this.calDao,list);
        }else if(name.equals("calendar_dates")){
            create(this.calDateDao,list);
        }else if(name.equals("fare_attributes")){
            create(this.fareAttrDao,list);
        }else if(name.equals("fare_rules")){
            create(this.fareRuleDao,list);
        }else if(name.equals("feed_info")){
            create(this.feedDao,list);
        }else if(name.equals("frequencies")){
            create(this.freqDao,list);
        }else if(name.equals("office_jp")){
            create(this.officeJpDao,list);
        }else if(name.equals("routes_jp")){
            create(this.routesJpDao,list);
        }else if(name.equals("routes")){
            create(this.routesDao,list);
        }else if(name.equals("shapes")){
            create(this.shapeDao,list);
        }else if(name.equals("stop_times")){
            create(this.stopTimeDao,list);
        }else if(name.equals("stops")){
            create(this.stopsDao,list);
        }else if(name.equals("transfers")){
            create(this.transfersDao,list);
        }else if(name.equals("translations")){
            create(this.translationslDao,list);
        }else if(name.equals("trips")){
            create(this.tripsDao,list);
        }
    }

    /** setter/getterを使うのはめんどいので、一度JSONを生成し、
        JSONからオブジェクトを生成してDBにぶっこんでます*/
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void create(Dao dao,List<Map<String,Object>> list)throws SQLException{
        final List array=new ArrayList();
        for(Map<String,Object> map : list){
            String json=gson.toJson(map);
            array.add(gson.fromJson(json,dao.getDataClass()));
        }
        TransactionManager.callInTransaction(dao.getConnectionSource(), new Callable<Void>(){
            public Void call() throws Exception {
                for(Object fp : array){
                    dao.create(fp);
                }
                return null;
            }
        });
    }

    /** CSVファイルをパース*/
    private static String[][] parseCsv(BufferedReader reader)throws IOException{
        ArrayList<String[]> list=new ArrayList<String[]>();
        String line;
        while((line=reader.readLine())!=null){
            String[] sp=split(csvDiv,line);
            list.add(sp);
        }
        if(list.size()==0)return new String[0][0];
        return list.toArray(new String[list.size()][]);
    }

    private static String[] split(Pattern pattern,String line){
        Matcher matcher=pattern.matcher(line);
        List<String> list=new ArrayList<String>();
        int index=0;
        int com=0;
        while(index<line.length()){
            if(matcher.find(index+com)){
                String s=matcher.group().replaceAll("\"", "");
                index=matcher.end();
                list.add(s);
                com=1;
            }
        }
        return list.toArray(new String[list.size()]);
    }

DBからTripLayer用データ(JSON)を生成する

次いで、Tripsからtrip_idを、Stop_timesからそのTripの停留所と発車時刻、Stopsから停留所の位置情報を取得して、TripLayer用データ(Json)を生成しています。
処理の概要は以下のとおりです。
なお、Tripデータは[経度、緯度、タイムスタンプ]の形で整理しており、タイムスタンプは32bit浮動小数点にする必要があるので、一度、Javaのシリアル値を生成した後、午前0時からの経過時間(分)に変換しています。また、今回は停留所のリストをバスの通行経路として扱いました。

    /** DBからTripLayer用データを生成 */
    public String getTripData() throws SQLException,ParseException{
        Map<String,Object> map=new HashMap<String,Object>();
        List<GTrip> trips=getTrips();
        String ymd=getSurviceDate(trips.get(0));
        List<Map<String,List<Number[]>>> waypoints=new ArrayList<Map<String,List<Number[]>>>();
        long st=dateFormat.parse(ymd+" 00:00:00").getTime();
        for(GTrip t : trips){
            List<Number[]> wl=getWaypoints(t,st);
            Map<String,List<Number[]>> mm=new HashMap<String,List<Number[]>>();
            mm.put("segments", wl);
            waypoints.add(mm);
        }
        map.put("starttime", st);
        map.put("trips",waypoints);
        map.put("stops", stop2Map(getStops()));
        return gson.toJson(map);
    }

    private List<GStop> getStops()throws SQLException{
        return this.stopsDao.queryForAll();
    }

    private List<GTrip> getTrips()throws SQLException{
        return this.tripsDao.queryForAll();
    }

    /** Trip単位で通過経路(経度、緯度、時刻)のデータを生成 */
    private List<Number[]> getWaypoints(GTrip t,long sub)throws SQLException,ParseException{
        String ymd=getSurviceDate(t);
        QueryBuilder<GStopTime,Long> query=stopTimeDao.queryBuilder();
        query.where().eq("trip_id", t.trip_id);
        List<GStopTime> list=stopTimeDao.query(query.prepare());
        list.sort(new Comparator<GStopTime>(){
            @Override
            public int compare(GStopTime arg0, GStopTime arg1) {
                int v0=Integer.parseInt(arg0.stop_sequence);
                int v1=Integer.parseInt(arg1.stop_sequence);
                return (v0-v1);
            }
        });
        int n=list.size();
        List<Number[]> data=new ArrayList<Number[]>();
        for(int i=0;i<n;i++){
            GStopTime st=list.get(i);
            data.add(createWaypoint(st,ymd,sub));
        }
        return data;
    }

    private Number[] createWaypoint(GStopTime st,String ymd,long sub)throws ParseException,SQLException{
        long timestamp=getStopTimes(st,ymd);
        GStop sa=getStops(st.stop_id);
        return new Number[]{sa.stop_lon,sa.stop_lat,((double)(timestamp-sub))/(60.0*1000.0)};
    }

    private String getSurviceDate(GTrip t)throws SQLException{
        QueryBuilder<GCalendar,Long> query=calDao.queryBuilder();
        query.where().eq("service_id", t.service_id);
        List<GCalendar> list=calDao.query(query.prepare());
        String date=list.get(0).start_date;
        return date.substring(0, 4)+"-"+date.substring(4,6)+"-"+date.substring(6,8);
    }

    private GStop getStops(String stop_id)throws SQLException{
        QueryBuilder<GStop,Long> query=stopsDao.queryBuilder();
        query.where().eq("stop_id", stop_id);
        List<GStop> list=stopsDao.query(query.prepare());
        return list.get(0);
    }

    private long getStopTimes(GStopTime s,String ymd)throws ParseException{
        return dateFormat.parse(ymd+" "+s.arrival_time).getTime();
    }

Deck.GLでTripLayer可視化

作成したJSONデータはTripLayerに合わせた形にしているので、そのまま読み込ましてTripLayerを作成しています。
今回は停留所のリストをそのまま通行経路としたため、直行便はほぼ一直線に目的地へ移動してしまいます。バス運行経路を正確に再現し、道路に沿って動くようにするにはShapes.txtのデータをトレースして[経度、緯度、タイプスタンプ]のデータのリストを生成する必要があります。

<!doctype html>
<html class="no-js" lang="ja">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>Traffic-Bus</title>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.53.1/mapbox-gl.css" />
        <script src="https://code.jquery.com/jquery-3.4.0.js" integrity="sha256-DYZMCC8HTC+QDr5QNaIcfR7VSPtcISykd+6eSmBW5qo=" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.53.1/mapbox-gl.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/deck.gl@7.0.0/dist.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-scale/1.0.7/d3-scale.js"></script>
        <style type="text/css">
            html, body {
                padding: 0;
                margin: 0;
                width: 100%;
                height: 100%;
            }
            #panel {
                position: absolute;
                background: #ffffffaa;
                top: 0;
                left: 0;
                margin: 4px;
                padding: 4px;
                line-height: 1;
                width:260px;
                height:26px;
                z-index: 2;
                text-align: center;
                vertical-align: middle;
            }
            #tooltip {
                font-family: Helvetica, Arial, sans-serif;
                font-size: 12px;
                position: absolute;
                padding: 4px;
                margin: 8px;
                background: rgba(0, 0, 0, 0.8);
                color: #fff;
                max-width: 300px;
                z-index: 9;
                pointer-events: none;
            }
        </style>
    </head>
    <body>
        <div id="panel"></div>
        <div id="app" style="width:100%;height:100%;"></div>
        <div id="tooltip"></div>
    </body>
    <script type="text/javascript">
        const LAT=34.6;
        const LNG=135.5;
        let c_lon=0;
        let c_lat=0;
        let c_time=300;
        let triplayers=[];
        const deckgl = new deck.DeckGL({
            container: 'app',
            mapboxApiAccessToken: "アクセストークン",
            mapStyle: "mapbox://styles/mapbox/dark-v9",
            longitude: LNG,
            latitude: LAT,
            zoom: 11,
            pitch: 40,
            bearing: -10,
            onViewStateChange: ({viewState}) => {
                return viewState;
            }
        });
        const timeVal = () =>{
            return c_time;
        };

        const loadData = () => {
            d3.json("trips.json", (error, response)=>{
                setCenter(response.stops);
                let v_time=response.starttime;
                let trip=renderTripLayer(response.trips);
                let stop=renderStopLayer(response.stops);
                deckgl.setProps({
                    layers: [trip,stop],
                    viewState: {
                        longitude: c_lon,
                        latitude: c_lat,
                        zoom: 11,
                        transitionInterpolator: new FlyToInterpolator(),
                        transitionDuration: 3000
                    }
                });
                const update = () =>{
                    c_time +=0.1;
                    if(c_time>1439){
                        window.cancelAnimationFrame(thread);
                        return; 
                    }else{
                        trip.setState({time: c_time});
                    let da=new Date(v_time+c_time*60*1000);
                    $("#panel").text(da);
                    window.requestAnimationFrame(update);
                    }
                };
                let thread=window.requestAnimationFrame(update);
            });
        };
        const renderStopLayer = (data) => {
            const pointlayer = new deck.ScatterplotLayer({
                id: 'stop',
                data,
                fp64: false,
                opacity: 0.8,
                stroked: true,
                filled: true,
                radiusScale: 6,
                radiusMinPixels: 1,
                radiusMaxPixels: 100,
                lineWidthMinPixels: 1,
                getPosition: d => d.coordinates,
                getRadius: 8,
                getFillColor: [255, 140, 0],
                getLineColor: [0, 0, 0],
                pickable: true,
                onHover: updateTooltipStop
            });
            return pointlayer;
        };

        const renderTripLayer =(trips) => {
            const tripLayer = new TripsLayer({
                id: 'trips-layer',
                data: trips,
                getPath: d => d.segments,
                getColor: [253, 128, 93],
                opacity: 0.6,
                widthMinPixels: 3,
                rounded: true,
                trailLength: 15,
                currentTime: timeVal 
            });
            return tripLayer;
        }

        const setCenter=(feature) =>{
            const n=feature.length;
             for(let i=0;i<n;i++){
                c_lon +=feature[i].coordinates[0];
                c_lat +=feature[i].coordinates[1];
            }
            c_lon=c_lon/n;
            c_lat=c_lat/n;
        };

        const updateTooltipStop=({x, y, object}) => {
            const tooltip = document.getElementById("tooltip");
            if (object) {
                tooltip.style.visibility="visible";
                tooltip.style.top = y+"px";
                tooltip.style.left = x+"px";
                tooltip.innerHTML = "<p>"+object.name+"</p>";
            } else { 
                tooltip.style.visibility="hidden";
                tooltip.innerHTML = "";
            }
        };
        loadData();
    </script>
</html>

最後に

とりあえず、GTFS-JPからDBを作成し、Tripデータを作成して可視化するとこまで処理してみました。
GTFS-JPはCSVデータなので扱いが楽です。GTFSリアルタイムも「gtfs-realtime-bindings」を使用すると簡単にデータを処理できそうな感じです。また、両備バスの場合で、DB容量は約14MB(Sqlite)だったので、Android端末等に導入することもできそうだと思いました。
GTFS-JPの本格的な普及はこれからだと思いますが、少子高齢化や過疎化により地方の公共交通機関は色々な課題を抱えているので、かじっておくと何かしら地域の役に立つかも?と思いました。

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

Java11からSSL通信のエラーが発生しやすくなった事への考察と対処

はじめに

Java8+Spring Boot 1.5.Xの頃は、99.9999999%以上で通信が成功していたのに、Java11 + Spring Boot 2.1.Xにしてから、通信エラーの発生率が0.002%くらい上がったぞ・・・?と思い、調べてみました。

もしかしたら、間違っているかもしれないので、色々指摘を貰えると本当に嬉しいです。助かります。

発生するエラー

javax.net.ssl.SSLException: Connection reset
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:127)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:321)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:264)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:259)
    at java.base/sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1314)
    at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:839)
    at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
    at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
    at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:138)
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56)
    at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)
    at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)
    at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:157)
    at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273)
    at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:272)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
    at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:87)
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:735)
    ... 81 common frames omitted
    Suppressed: java.net.SocketException: Broken pipe (Write failed)
        at java.base/java.net.SocketOutputStream.socketWrite0(Native Method)
        at java.base/java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:110)
        at java.base/java.net.SocketOutputStream.write(SocketOutputStream.java:150)
        at java.base/sun.security.ssl.SSLSocketOutputRecord.encodeAlert(SSLSocketOutputRecord.java:81)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:352)
        ... 106 common frames omitted
Caused by: java.net.SocketException: Connection reset
    at java.base/java.net.SocketInputStream.read(SocketInputStream.java:186)
    at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
    at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:448)
    at java.base/sun.security.ssl.SSLSocketInputRecord.bytesInCompletePacket(SSLSocketInputRecord.java:68)
    at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1104)
    at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:823)
    ... 102 common frames omitted

自分を悩ませたもの。

  • 特定のLB (GCP)だけ、このエラーが発生しやすくなった。(他は安定してエラーが出ない)
    なので、
    https://stackoverflow.com/questions/49624532/java-net-socketexception-connection-reset-on-gcp
    を最初は疑いました。でも、java8だとエラーは出ない。やっぱJava11関係・・・?
  • Connection reset系は、安定してstackoverflowで回答がつかない。根本的には、ネットワークの問題だから・・・。
  • Java11からSSL通信周りが色々変わっている。
  • 発生確率的に、 javax.net.debug システムプロパティのデバッグがしんどい。GKEで動かしているので・・・ディスク量的に辛い。
  • nodeでtcpdumpをやろうにも発生確率的にやっぱり辛い。

たどりついた答え

机上でのチェックや、JDK、Spring Boot、HttpClientのバグ確認をしていて、ふと目についたものが、

https://bugs.openjdk.java.net/browse/JDK-8214339

です。
返されるエラーが変わっている・・・?
たしかに、SSLException。handshakeとかのエラーじゃないけど、SSLException。
SSLExceptionでは、 httpclientDefaultHttpRequestRetryHandler#INSTANCEでは通信が再試行されない。

なるほど。再試行されていないせいで、エラーが余計に発生しているように見えていたのかな。と現在たどり着きました。
たしかにこのエラーが発生する際、接続を開始しようとしてからすぐにエラーが発生します。

バックポートされているみたいですが、どうもSSLExceptionが返されることには変わりなさそうです。

http://hg.openjdk.java.net/jdk/jdk12/rev/9041178a0b69

を見る限り、下記のようになっている・・・というか、Alert#createSSLException を呼び出す以上、SSLException以外を返すことができないんですけどね。

+        if ((cause != null) && (cause instanceof IOException)) {
+            ssle = new SSLException(reason);
+        } 

対応

下記のように、DefaultHttpRequestRetryHandlerのretryRequestをオーバーライドして、

HttpRequestRetryHandler.java
  public class HttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {

    @Override
    public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
      IOException cause = exception;
      if (exception instanceof SSLException) {
        if (exception.getCause() != null && exception.getCause() instanceof IOException) {
          cause = (IOException) exception.getCause();
        }
      }
      return super.retryRequest(cause, executionCount, context);
    }
  }

下記のようにセットしてやればいいのかなーと思ってます。

CloseableHttpClient httpclient = HttpClients.custom()
        .setRetryHandler(new HttpRequestRetryHandler())
        .build();

ただ、もっと良い方法がないのか?というのをstackoverflowで質問してみてます。

https://stackoverflow.com/questions/56306216/how-can-i-retry-using-defaulthttprequestretryhandlerhttpclient-when-socketexce

ただ、上にも書いた通り、Connection reset関係は回答をなかなか貰えない、そもそも英語が通じているかわからないので、何か意見貰えるかは怪しいですが・・・。

参考

java8の場合のエラー

Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://xxxxxxxxx": Connection reset; nested exception is java.net.SocketException: Connection reset
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:674)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:636)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:598)
    ... 54 common frames omitted
Caused by: java.net.SocketException: Connection reset
    at java.net.SocketInputStream.read(SocketInputStream.java:210)
    at java.net.SocketInputStream.read(SocketInputStream.java:141)
    at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
    at sun.security.ssl.InputRecord.read(InputRecord.java:503)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:975)
    at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:933)
    at sun.security.ssl.AppInputStream.read(AppInputStream.java:105)
    at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
    at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
    at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:282)
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:138)
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56)
    at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)
    at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)
    at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:165)
    at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273)
    at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:272)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:111)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
    at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:89)
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:660)
    ... 58 common frames omitted

最後に

英語できるぜ!という方は、stackoverflowの方にも回答・意見貰えると助かります。
多分・・・同じように困っている人がちょっとはいるかな?と思いますので。

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

[初心者向け]リスト操作関数map,reduce,filterの各言語の簡単なサンプル

各言語のリスト操作でよく使われるmap filter reduceの簡単な操作を並べてみました。
Javaは冗長ですね。Ruby と Haskellはすごく簡潔です。
ただし、私が普段の業務で使うのがJavaなので、他の言語のことをよく知らずですので、
もっと簡潔に書ける方法があるかもしれません。

  • Java 1.8
  • Ruby 2.6
  • Python 3.7
  • Haskell GHC version 8.0.1
  • Clojure 1.9.0

の簡単な例になります。

数値のリストを作る

ちなみに、業務では固定でリストを初期化することはあまりないと思われます。
普通はリスト系のデータはファクトリーメソットにて生成されて参照すると思います。
ただし、私はテストなどでテストデータとして以下のような初期化をすることはあります。

Java
    List<Integer> xs = Arrays.asList(1, 2, 3, 4, 5);
    //または @saka1029 さんに教えていただいた、Java 10以降なら
    var xs = List.of(1, 2, 3, 4, 5);

    //こんな無限リストを使うやり方もあります。業務ではこれもしない?
    //外からseed値、function,limit値をいれて作れる。
    xs = Stream.iterate(1, i -> i + 1).limit(5).collect(toList())
Ruby
    xs = [1, 2, 3, 4, 5]
    # 実際には上はあまりよくない例であるらしく、
    # @scivolaさんより教えていただいた以下の方が初期化としてはよいです。
    xs = [*1..5]
Python
    xs = [1, 2, 3, 4, 5]
Haskell
    xs = [1, 2, 3, 4, 5]
    -- または
    xs = [1..5]
Clojure
    (def xs '(1 2 3 4 5))
    ;または @lagenorhynque さんにコメントいただいた方法
    (def xs (range 1 (inc 5)))

以下このリストをすでに作っているものとします。

map 関数

リストをとって各値を2倍にする。

各言語とも結果は[2, 4, 6, 8, 10] になります。
print関数は省略します。

Java
    xs.stream().map(i -> i * 2).collect(toList());
Ruby
    xs.map {|i| i * 2}
Python
    list(map(lambda i: i * 2, xs))
    # 多分この答えを出す場合、Pythonではリスト内包表記を使うのが普通と思われる。
    [i * 2 for i in xs]
Haskell
    map (*2) xs
Clojure
    (map (fn [n] (* n 2)) xs)
    ; または@lagenorhynqueさんにおしえていただいたもの。こっちのが簡潔でいいですね!
    (map #(* % 2) xs)

filter関数

偶数だけ選んで返す。

結果は[2, 4]になります。

Java
    xs.stream().filter(n -> n % 2 == 0).collect(toList());
Ruby
    xs.select(&:even?)
Python
    list(filter(lambda n: n % 2 == 0, xs))
    #多分普通上のような書き方はせずリスト内包表記になると思います。
    [n for n in xs if n % 2 == 0]
Haskell
    filter even xs
Clojure
    (filter even? xs)

reduce関数

リストの数字を加算して集約して返す。

結果は15になります。

Java
    xs.stream().reduce(Integer::sum).orElse(0);
Ruby
    xs.reduce(:+) 
    #または l.inject(:+)
    #reduce使わない場合
    xs.sum

Pythonは@shiracamusさんよりimportがないとのことで不親切でしたので記載しました。

Python
    from functools import reduce
    from operator import add

    reduce(lambda a, b: a + b, xs)
    # reduce使わない場合
    sum(xs)
    # または@shiracamusさんより教えていただきました、
    reduce(add, xs)
Haskell
    foldl (+) 0 xs
    -- fold使わない場合
    sum xs
Clojure
    (reduce + xs)
    ;@lagenorhynqueさんより教えていただいた
    (apply + xs)
    ;という書き方でも同じ結果です。内部で行われるのは (+ 1 2 3 4 5)
    ; reduceは(+ (+ (+ (+ 1 2) 3) 4) 5)

以上です。

いままで勉強したことのある言語に対して書きましたが、個人的に書いていて気持ちがいいと思ったのはRuby,Clojureです。
なんとなく感じが似ている気がします。(作者がどちらもlispが好きだから?)
Haskellはすごすぎて気持ち悪!(いい意味で)と思うところがあるのですが、楽しい!と思えるのはRuby,Clojureでしょうか?なぜなのかわかりませんが。またHaskellは考え方が他の言語とまるで違うのでなかなか勉強が進まないです。

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