- 投稿日:2019-05-26T20:46:24+09:00
Desktop : OpenCV Text
Goal
Test OpenCV text.
OpenCV_Text.javaimport 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
- 投稿日:2019-05-26T20:33:38+09:00
Desktop : OpenCV Polylines
Goal
Test OpenCV Polyline.
OpenCV_Polylines.javaimport 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
- 投稿日:2019-05-26T18:57:53+09:00
【Java初学者】抽象化とインターフェースについて
概要
抽象化とインターフェースの復習として投稿させて頂きます。
抽象化
上位モジュールにおいて、下位モジュールに共通実装するメソッドや変数を宣言し、実装は下位モジュールに任せる。
また、抽象化したクラスはインスタンス生成を行えない。・抽象化を行うことのメリット
- オーバーライドのし忘れ防止
- 何もしない処理との区別をつける
- 意図しないインスタンス生成の防止Human.javapublic 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.javapublic class Japanese extends Human{ public Japanese(String name){ super(name); } @Override public void talk(){ System.out.println("こんにちは"); } }American.javapublic class American extends Human{ public American(String name){ super(name); } @Override public void talk(){ System.out.println("Hello"); } }Main.javapublic 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.javapublic interface Creature{ //public static final double PI = 3.14; double PI = 3.14; // 自動的にpublic static finalな変数となる。 //public abstract void breath(); void breath(); //自動的にpublic abstractなメソッドとなる。 }まとめ
未だ、継承を用いた開発経験が少ないため、メリットの恩恵を受けづらいというのが正直な感想。
- 投稿日:2019-05-26T16:47:06+09:00
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; };よくできました。
- 投稿日:2019-05-26T16:36:09+09:00
Desktop : OpenCV Fill Poly With Mouse Control
Goal
Test OpenCV fill poly with mouse control.
OpenCV_FillPolyWithMouseControl.javaimport 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
- 投稿日:2019-05-26T15:55:37+09:00
Desktop : OpenCV Fill Poly
Goal
Test OpenCV Fill Poly.
OpenCV_FillPoly.javaimport 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
- 投稿日:2019-05-26T15:29:49+09:00
Desktop : OpenCV Fill ConvexPoly
Goal
Test OpenCV fill convex poly.
OpenCV_FillConvexPoly.javaimport 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
- 投稿日:2019-05-26T15:13:19+09:00
Desktop : OpenCV Ellipse2Poly
Goal
Test OpenCV Ellipse2Poly.
OpenCV_Ellipse2Poly.javaimport 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
- 投稿日:2019-05-26T14:57:37+09:00
Desktop : OpenCV Draw Circle
Goal
OpenCV Draw Circle.
OpenCV_Draw_Circle.javaimport 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
- 投稿日:2019-05-26T14:32:50+09:00
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アノテーションを追加する。
- @Componentアノテーションは様々なアノテーションの定義に含まれる。(->@Controller, @Service, @Repository, @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つポイントがある。
- @SpringBootApplicationアノテーションを付与する
- @SpringApplicationクラスのrunメソッドを実行する
@SpringBootAnnotationは以下のアノテーションを含む。
scanBasePackages属性を用いて、スキャンするパッケージの指定もできる。
@SpringBootApplication(scanBasePackages = "com.example")結論
@SpringBootApplicationアノテーションを付与することで、デフォルトのJavaConfigの定義、DIコンテナへの登録を省略できる。
自分で登録する際(デフォルトのJavaConfigとの差分を追加する際)、アノテーションによる定義しか行わないのであれば、@ComponentアノテーションをつけるだけでDIコンテナへの登録ができ、@Autowiredアノテーションで注入ができる。
補足
- 差分があまりに巨大な場合は自分でJavaConfigを定義することになるのだろうが、現状ではアノテーションによる定義しかしていない。
- アノテーションによる定義の際、で@SpringBootApplicationアノテーションでコンポーネントスキャンを自動化することになるので、@Componentアノテーションを付与するクラスは実行クラスのカレントディレクトリ以下になければならない。
(まあ、普通に考えて実行クラスは一番上の階層に置くからあまり意識する必要はないと思う)
- 投稿日:2019-05-26T13:31:30+09:00
既存の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.hTopのCMakeLists.txtcmake_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.txtcmake_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から、LLDB
とCMake
とNDK
を選びOKをクリックしてインストールします。(これは、後でプロジェクトを作ってからやっても良いです)
Androidプロジェクトの生成
Android Studioを開き、新規プロジェクトを作ります。
テンプレートを選ぶ際に、Native C++
を選びます。プロジェクトの保存場所
僕は、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のひな形などは作られないようです。C/C++コードの編集
生成されたプロジェクトを見ると、上記のように
cpp
というフォルダが作られ、その下にCMakeLists.txt
とnative-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.javapackage 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.CvCameraViewListener
のonCameraFrame
コールバック内でネイティブ関数を呼び、その中でOpenMPでfor文を回してみましたが、クラッシュなく動作していました。高速化もされていました。おわりに
かなり的を絞ったユースケースを想定した方法です。
もっといい方法があるかもしれません。
改善点があれば、コメントなどで教えていただけると嬉しいです。
- 投稿日:2019-05-26T12:25:55+09:00
最近(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など学習コストの高いものを覚える必要があり、開発を始める敷居が高くなっているように思います
- 「これはみんな使ってるっしょ!」みたいなライブラリがあれば是非教えてください
- 投稿日:2019-05-26T12:25:55+09:00
最近の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など学習コストの高いものを覚える必要があり、開発を始める敷居が高くなっているように思います
- 「これはみんな使ってるっしょ!」みたいなライブラリがあれば是非教えてください
- 投稿日:2019-05-26T12:04:47+09:00
【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を使用
それぞれの長所短所を理解した上での使い分けが非常に大事ですね✨
補足
沢山の値を保持したいけどどこにでも持ち運びたい!!
という事が多々あると思います。
その場合は、JavaBeansをSessionに入れましょう❗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();//保持された値が表示される
- 投稿日:2019-05-26T10:46:26+09:00
Desktop : OpenCV Draw Point
Goal
OpenCV Draw point.
OpenCV_Draw_Point.javaimport 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
- 投稿日:2019-05-26T09:46:26+09:00
ポリモーフィズムについて
説明する時にいつも読むのでまとめ
ポリモーフィズムとは
メッセージの送り方を共通にするもの
- 親クラス生成時に子クラスに渡すメッセージを共通にする仕組み
- インターフェースを使って(Javaの場合)メッセージを共通にする仕組み
- コードの重複を排除して汎用性の高い部品をつくる仕組みの1つ
- オブジェクト指向三大要素の1つ
- 意訳すると多態性
特徴
概念的側面(現実世界)
上位と下位の概念それぞれの動詞として成立する
共通の動詞が成立する
- 例えば「動物」と「人間」と「犬」
- 「動物」(上位)が「食べる」
- 「人間」(下位)が「食べる」
- 「犬」が(下位)「食べる」
切り口を1つにしている
- 1つのオプション(追加情報)で固有の挙動が成立する
- 例えば「動物」と「人間」と「犬」
- 「動物」が「食べる」-> どんな感じで食べるか定まっていない
- 「動物」の「人間」(オプション)が「食べる」-> 食べ物を手で口に近づけて食べる
- 「動物」の「犬」(オプション)が「食べる」-> 食べ物に頭を近づけて口で食べる
技術的側面(プログラミング)
引数を受け取ったインスタンスがクラスによって違う振る舞いをする
共通のメソッド名で成立する
- スーパークラスとサブクラスそれぞれのメソッドとして成立する
- 例えば「Animal」と「human」と「dog」と「chef」(eater)
- 「シェフ」が「動物」(食べるくん)に提供して「動物」(食べるくん)がそれを「食べる」
- 「動物」(食べるくん)を変えるだけでぱくぱく食べてたりがぶがぶ食べてたりする
プログラミング技術的における「継承」を利用した場合
Animal.javapublic abstract class Animal { public abstract void eat(); }Human.javapublic class Human extends Animal { public void eat() { System.out.println("ぱくぱくたべる"); } }Dog.javapublic class Dog extends Animal { public void eat() { System.out.println("がぶがぶたべる"); } }Chef.javapublic class Chef { public void serve(Animal animal) { System.out.println("どうぞー"); System.out.println(animal.eat()); } }プログラミング技術的における「インターフェース」を利用した場合
Eater.javapublic interface Eater { public void eat(); }Human.javapublic class Human implements Eater { public void eat() { System.out.println("ぱくぱくたべる"); } }Dog.javapublic class Dog implements Eater { public void eat() { System.out.println("がぶがぶたべる"); } }Chef.javapublic class Chef { public void serve(Eater eater) { System.out.println("どうぞー"); System.out.println(eater.eat()); } }ポイント
- 継承の機能を使ったものもインターフェースの機能を使ったものも共通していえるのは呼び出し方が種類関係なく同じ(ここでいうとシェフが提供する相手)という部分
- interfaceはポリモーフィズムを実現するためのようなもの
- interfaceの機能は言語的にサポートしていないものもある(Rubyなどはそう)
書籍情報
平澤 章 (著) オブジェクト指向でなぜつくるのか 第2版
https://amzn.to/2VSrzwe雑感
デザインパターンやリファクタリングでもこの視点をもてていると上手く適用できる
- 投稿日:2019-05-26T06:23:09+09:00
Desktop :OpenCV Drawing Line
Goal
OpenCV Draw line.
OpenCV_DrawingLine.javaimport 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
- 投稿日:2019-05-26T03:21:36+09:00
Deck.GLのTripLayerでGTFS-JPのバス運行状況を可視化してみた
概要
地域交通の未来と公共交通オープンデータ - HackMDの発表資料「地域交通の未来と公共交通オープンデータ」を見て、GTSF-JPに興味を持ちました。
GTFS-JPの仕様を確認したところ面白そうだったので、GTFS-JPデータを公開している両備バスの運行状況をDeck.GLのTriplayerで可視化してみました。
両備バス(https://t.co/VclrIjVPxZ)のGTFS-JPをhttps://t.co/3DasovoRX8のTripLayerで可視化してみました。 pic.twitter.com/2WJPtzZthN
— t_mat (@t_mat) May 25, 2019なお、今回作成したコードはGithubにおいています。
Github:gtfsjp_tmat_testGTFS-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-bindingsGTFS-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の本格的な普及はこれからだと思いますが、少子高齢化や過疎化により地方の公共交通機関は色々な課題を抱えているので、かじっておくと何かしら地域の役に立つかも?と思いました。
- 投稿日:2019-05-26T03:21:36+09:00
Deck.GLのTripLayerでGTFS-JPのバス運行状況を可視化してみる
概要
地域交通の未来と公共交通オープンデータ - HackMDの発表資料「地域交通の未来と公共交通オープンデータ」を見て、GTSF-JPに興味を持ちました。
GTFS-JPの仕様を確認したところ面白そうだったので、GTFS-JPデータを公開している両備バスの運行状況をDeck.GLのTriplayerで可視化してみました。
両備バス(https://t.co/VclrIjVPxZ)のGTFS-JPをhttps://t.co/3DasovoRX8のTripLayerで可視化してみました。 pic.twitter.com/2WJPtzZthN
— t_mat (@t_mat) May 25, 2019なお、今回作成したコードはGithubにおいています。
Github:gtfsjp_tmat_testGTFS-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-bindingsGTFS-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の本格的な普及はこれからだと思いますが、少子高齢化や過疎化により地方の公共交通機関は色々な課題を抱えているので、かじっておくと何かしら地域の役に立つかも?と思いました。
- 投稿日:2019-05-26T00:58:01+09:00
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では、 httpclientのDefaultHttpRequestRetryHandler#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.javapublic 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で質問してみてます。
ただ、上にも書いた通り、Connection reset関係は回答をなかなか貰えない、そもそも英語が通じているかわからないので、何か意見貰えるかは怪しいですが・・・。
参考
- https://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html
- https://bugs.openjdk.java.net/browse/JDK-8214339
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の方にも回答・意見貰えると助かります。
多分・・・同じように困っている人がちょっとはいるかな?と思いますので。
- 投稿日:2019-05-26T00:57:27+09:00
[初心者向け]リスト操作関数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
の簡単な例になります。
数値のリストを作る
ちなみに、業務では固定でリストを初期化することはあまりないと思われます。
普通はリスト系のデータはファクトリーメソットにて生成されて参照すると思います。
ただし、私はテストなどでテストデータとして以下のような初期化をすることはあります。JavaList<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())Rubyxs = [1, 2, 3, 4, 5] # 実際には上はあまりよくない例であるらしく、 # @scivolaさんより教えていただいた以下の方が初期化としてはよいです。 xs = [*1..5]Pythonxs = [1, 2, 3, 4, 5]Haskellxs = [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関数は省略します。Javaxs.stream().map(i -> i * 2).collect(toList());Rubyxs.map {|i| i * 2}Pythonlist(map(lambda i: i * 2, xs)) # 多分この答えを出す場合、Pythonではリスト内包表記を使うのが普通と思われる。 [i * 2 for i in xs]Haskellmap (*2) xsClojure(map (fn [n] (* n 2)) xs) ; または@lagenorhynqueさんにおしえていただいたもの。こっちのが簡潔でいいですね! (map #(* % 2) xs)filter関数
偶数だけ選んで返す。
結果は
[2, 4]
になります。Javaxs.stream().filter(n -> n % 2 == 0).collect(toList());Rubyxs.select(&:even?)Pythonlist(filter(lambda n: n % 2 == 0, xs)) #多分普通上のような書き方はせずリスト内包表記になると思います。 [n for n in xs if n % 2 == 0]Haskellfilter even xsClojure(filter even? xs)reduce関数
リストの数字を加算して集約して返す。
結果は
15
になります。Javaxs.stream().reduce(Integer::sum).orElse(0);Rubyxs.reduce(:+) #または l.inject(:+) #reduce使わない場合 xs.sumPythonは@shiracamusさんよりimportがないとのことで不親切でしたので記載しました。
Pythonfrom functools import reduce from operator import add reduce(lambda a, b: a + b, xs) # reduce使わない場合 sum(xs) # または@shiracamusさんより教えていただきました、 reduce(add, xs)Haskellfoldl (+) 0 xs -- fold使わない場合 sum xsClojure(reduce + xs) ;@lagenorhynqueさんより教えていただいた (apply + xs) ;という書き方でも同じ結果です。内部で行われるのは (+ 1 2 3 4 5) ; reduceは(+ (+ (+ (+ 1 2) 3) 4) 5)以上です。
いままで勉強したことのある言語に対して書きましたが、個人的に書いていて気持ちがいいと思ったのはRuby,Clojureです。
なんとなく感じが似ている気がします。(作者がどちらもlispが好きだから?)
Haskellはすごすぎて気持ち悪!(いい意味で)と思うところがあるのですが、楽しい!と思えるのはRuby,Clojureでしょうか?なぜなのかわかりませんが。またHaskellは考え方が他の言語とまるで違うのでなかなか勉強が進まないです。