三重県の郷土料理「こしょう汁」「泣汁」とは

絹田
絹田

三重県民が泣汁について説明するよ.

胡椒汁,泣汁,涙汁の写真

三重県や岐阜県では「胡椒汁」「泣汁」という葬儀の火葬の後に食べる特別な汁物があります.胡椒とは唐辛子のことで,唐辛子で辛くしたお吸い物,薄い味噌汁のことです.辛いから泣いているのか,悲しいから泣いているのか分からないように火葬の後に食べるのです.別名「涙汁」「泣汁」というそうです.

「食の民族辞典」の唐辛子の欄にこのように記載があります.

三重県亀山市下庄朝神向山(昭和八年生まれ)は,葬式当日の朝餉ないし昼餉に、参列してもらう人々に精進の膳を出したが、その一品に「トウガラシ汁」があったという。同じく葬式のときに出されるものに甘辛く煮たヒリョウズ(がんもどき)があり、その煮汁を水でのばし、さらに醤油で味つけしたところに唐辛子を入れて辛味を移すように煮出す。そこに青昆布やアゲなどを浮き実にして出す。
三重県四日市市桜町斧研の近藤善治さん(昭和四年生まれ)も、葬式のときにはヒジヤド(非時宿)でこの汁を食
べたという。葬式で涙が出るように食べるといわれたこの辛い汁は、「涙汁」とも呼ばれていた。 (谷阪智佳子)

食の民族辞典

具はがんもどきを入れたり,油揚げを入れたりで地域差があるようですが,唐辛子が下に沈殿するくらいめちゃめちゃに辛くするのは共通しているようです.私も個人的にいなべ市出身の知り合いの30代の女性に話を聞いたところ,その女性の祖母が亡くなり自宅で葬儀を行なった際,火葬の後,地域の集会所で2010年頃に振る舞われたようです.

「長机をコの字に並べて,皆で泣汁を食べました.泣汁だけでそれ以外には食べません.辛いのがわかっているので私は飲んでいるふりをしていましたが,辛いから泣いているのか,悲しいから泣いているのかわからないようにするためのものだと思います.火葬の後,集会所の厨房に女性が集まって作りました.住職さんと父が残していきたいとよく話をしていました」

女性の話

作り方

がんもどきの煮物を作る

  1. がんもどきに熱湯をかけ油抜きをする
  2. それを鍋に入れて,砂糖とひたひたの水を入れて煮立てる。 煮立ったら醤油を入れて中火で煮る。
  3. 煮汁がなくなるまで煮詰める

煮物の作り方はこちらを参考にしました.

https://cookpad.com/recipe/4800225

汁物にして胡椒(唐辛子)の辛味をうつす

  1. がんもどきの煮物を水で薄めて醤油で味を整えて汁物にする
  2. 鷹の爪を大量に刻んで一緒に煮る

辛いから泣いているのか,悲しいから泣いているのかわからないように

するための汁物ということで,それを飲む際の情景がありありと思い浮かび,地域で大切にされてきた風習なのだと思いました.

参考文献

https://ja.wikipedia.org/wiki/%E8%83%A1%E6%A4%92%E6%B1%81

https://www.olive-hitomawashi.com/column/2021/03/post-13984.html

絹田
絹田

読んでくれてありがとう.これからもよろしくね

capacitorでadmobでアプリ広告をAndroidで表示する

いろんなプラグインがあります.以前は

https://github.com/rahadur/capacitor-admob

を使っていましたが,メンテナンスされていないようなので現在使うのはおすすめじゃないです.

今は

https://github.com/capacitor-community/admob

を使うのが良さそうです.

admob on capacitor

Android Studioの設定は別途ドキュメントで行うとして,バナーを表示するソースコードはtsファイルで下記のようにするだけで表示されました.

import { Component } from '@angular/core';
import { AdMob, BannerAdOptions, BannerAdSize, BannerAdPosition, BannerAdPluginEvents, AdMobBannerSize } from '@capacitor-community/admob';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})


export class Tab1Page {


  constructor() {
    AdMob.initialize({
      requestTrackingAuthorization: true,
      //testingDevices: ['2077ef9a63d2b398840261c8221a0c9b'],
      //initializeForTesting: true,
    });
  }

  ionViewDidEnter(){

    AdMob.addListener(BannerAdPluginEvents.Loaded, () => {
      // Subscribe Banner Event Listener
      console.log('loaded');
    });

    AdMob.addListener(BannerAdPluginEvents.SizeChanged, (size: AdMobBannerSize) => {
      // Subscribe Change Banner Size
      console.log('size changed');

    });

    const options: BannerAdOptions = {
      adId: 'ca-app-pub-3940256099942544/15453xxxxxx',//test
      adSize: BannerAdSize.BANNER,
      position: BannerAdPosition.BOTTOM_CENTER,
      margin: 100,
      isTesting: true
      // npa: true
    };
    AdMob.showBanner(options);

  }

}

moduleファイルには何の記載もなくてOK

toDataURL()メソッド利用の際の 「Tainted canvases may not be exported」エラーについて

Google Cloud のStorageからJavaScriptで画像をダウンロードしてCanvasに書き込んだとき,そのCanvas要素の画像をtoDataURL()メソッドでdataURIに変換する際に

Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

というエラーが出る.直訳すると

「HTMLCanvasElement」で「toDataURL」の実行に失敗しました:汚染されたキャンバスはエクスポートされない可能性があります。

つまり,Corsポリシーに引っかかってしまう.解決するためにはフロントエンドのJavaScriptで任意のドメインからのデータを利用できるように明示的に指定する必要と,生成元からデータを返す際にCorsポリシーを適用しないドメインを明示的に指定する必要がある.

フロントエンドの方法

Canvas要素に描く際に生成元をanounymousに設定すればいい

  download(){
    var gsReference = this.firestorage.refFromURL('gs://my-app.appspot.com/imgs/'+this.authUid +'/'+this.query_img_name)
    const task = gsReference.getDownloadURL().subscribe(dataurl => {
      console.log(dataurl,'tsk');
      const image = new Image();
      image.onload = () => {
        this.canvas.nativeElement.width = image.width;
        this.canvas.nativeElement.height = image.height;
        this.canvas_rendering_context.drawImage(image, 0, 0);
      }
      (→これを追加)image.crossOrigin = "anonymous";
      image.src = dataurl;

    })
  }

生成元の変更

今回はGoogle CloudのStorageから画像をダウンロードしている.従ってStorageの設定をいじる必要がある.

my-cors.jsonなどのファイルを使って設定情報を書いたあと,gsutilコマンドを使ってStorageに反映させればいい.

touch my-cors.json
 nano my-cors.json

my-cors.jsonは下記のように変更しておく

[
    {
      "origin": ["*"],
      "method": ["GET","POST"],
      "responseHeader": ["Content-Type"],
      "maxAgeSeconds": 3600
    }
]

あとはコマンドから反映させる

gsutil cors set ./my-cors.json gs://my-app.appspot.com 

参考文献

https://cloud.google.com/storage/docs/configuring-cors

Angularでpipeを使って日付データをフォーマットする

準備

標準機能なので追加のインストールは必要ありません.module.tsで@angular/commonからDatePipeをインストールし,providersに登録します.

import { IonicModule } from '@ionic/angular';
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab3Page } from './tab3.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
+import { DatePipe } from '@angular/common';

import { Tab3PageRoutingModule } from './tab3-routing.module';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    RouterModule.forChild([{ path: '', component: Tab3Page }]),
    Tab3PageRoutingModule,
  ],
-  declarations: [Tab3Page]
+  declarations: [Tab3Page],
+  providers: [DatePipe]
})
export class Tab3PageModule {}

次に,page.tsファイルで読み込みます.

import { Component,ElementRef,ViewChild } from '@angular/core';
+import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-tab3',
  templateUrl: 'tab3.page.html',
  styleUrls: ['tab3.page.scss']
})
export class Tab3Page {

  constructor(
+    private datePipe: DatePipe
  ) {
  }

  ionViewDidEnter(){
  }

}

HTMLでの表示を変えたい場合

import { Component,ElementRef,ViewChild } from '@angular/core';
import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-tab3',
  templateUrl: 'tab3.page.html',
  styleUrls: ['tab3.page.scss']
})
export class Tab3Page {
+  time_on_html:any;
  constructor(
    private datePipe: DatePipe
  ) {
  }

  ionViewDidEnter(){
+    this.time_on_html = new Date();
+    console.log(this.time_on_html);
  }

}

そして,HTML側は{{time_on_html | date:”MM/dd/yy” }}で参照します.

tsファイルの中で表示を変えたい場合

import { Component,ElementRef,ViewChild } from '@angular/core';
import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-tab3',
  templateUrl: 'tab3.page.html',
  styleUrls: ['tab3.page.scss']
})
export class Tab3Page {
  time_on_html:any;
  constructor(
    private datePipe: DatePipe
  ) {
  }

  ionViewDidEnter(){
    this.time_on_html = this.datePipe.transform(new Date(), 'yyyyMMddHHmmss');
    console.log(this.time_on_html);

  }

}

2021/07/19 18:42:52と表示される.

画像のオブジェクトURLをCanvas要素に描写する

Capacitorのカメラプラグインでの動作記録

image2.onloadファンクションでアロー関数を使う場合

export class Tab3Page {
  @ViewChild('canvas') public canvas: ElementRef;
  public cx: CanvasRenderingContext2D;

  constructor(
  ) {}

  ionViewDidEnter(){
    this.startCamera();
    const canvasEl: HTMLCanvasElement = this.canvas.nativeElement;
    this.cx = canvasEl.getContext('2d');
  }
  
async startCamera(){
    const imageFunc = await Camera.getPhoto({
      quality: 90,
      allowEditing: false,
      resultType: CameraResultType.Uri,
    }).then((image) => {

      // image.webPath will contain a path that can be set as an image src.
      // You can access the original file using image.path, which can be
      // passed to the Filesystem API to read the raw data of the image,
      // if desired (or pass resultType: CameraResultType.Base64 to getPhoto)
      var imageUrl = image.webPath;
      console.log(imageUrl);

      let image2:any = new Image();
      image2.onload = () => {
        console.log(image2.width);
        console.log(image2.height);
        this.cx.canvas.width = image2.width;
        this.cx.canvas.height = image2.height;
        aa.drawImage(image2, 0, 0);
        console.log('done');
      }
      image2.src = imageUrl;
    });
  };

よく乗っているもの

  export class Tab3Page {
  @ViewChild('canvas') public canvas: ElementRef;
  public cx: CanvasRenderingContext2D;

  constructor(
  ) {}

  ionViewDidEnter(){
    this.startCamera();
    const canvasEl: HTMLCanvasElement = this.canvas.nativeElement;
    this.cx = canvasEl.getContext('2d');
  }

async startCamera(){
    const imageFunc = await Camera.getPhoto({
      quality: 90,
      allowEditing: false,
      resultType: CameraResultType.Uri,
    }).then((image) => {

      // image.webPath will contain a path that can be set as an image src.
      // You can access the original file using image.path, which can be
      // passed to the Filesystem API to read the raw data of the image,
      // if desired (or pass resultType: CameraResultType.Base64 to getPhoto)
      var imageUrl = image.webPath;
      console.log(imageUrl);
      let canvasVar:any =this.cx;

      let image2:any = new Image();
      let aa:any =this.cx;
      image2.onload = function() {
        canvasVar.canvas.width = image2.width;
        canvasVar.canvas.height = image2.height;
        aa.drawImage(image2, 0, 0);
        console.log('done');
      }
      image2.src = imageUrl;
    });
  };

bind(this)を使ってこれでもいく

export class Tab3Page {
  @ViewChild('canvas') public canvas: ElementRef;
  public cx: CanvasRenderingContext2D;

  constructor(
  ) {}

  ionViewDidEnter(){
    this.startCamera();
    const canvasEl: HTMLCanvasElement = this.canvas.nativeElement;
    this.cx = canvasEl.getContext('2d');
  }
  
async startCamera(){
    const imageFunc = await Camera.getPhoto({
      quality: 90,
      allowEditing: false,
      resultType: CameraResultType.Uri,
    }).then((image) => {

      // image.webPath will contain a path that can be set as an image src.
      // You can access the original file using image.path, which can be
      // passed to the Filesystem API to read the raw data of the image,
      // if desired (or pass resultType: CameraResultType.Base64 to getPhoto)
      var imageUrl = image.webPath;
      console.log(imageUrl);
      let canvasVar:any =this.cx;

      let image2:any = new Image();
      let aa:any =this.cx;
      image2.onload = function() {
        this.cx.canvas.width = image2.width;
        this.cx.canvas.height = image2.height;
        aa.drawImage(image2, 0, 0);
        console.log('done');
      }.bind(this)
      image2.src = imageUrl;
    });
  };

https://stackoverflow.com/questions/30824756/javascript-saving-this-variable-inside-of-image-onload

Angular(Ionic)でアコーディオンを作成する

Angular(Ionic)でカスタムコンポーネントを使ってアコーディオンを作成する方法です.

今回はIonicのTabsプロジェクトをもとに作成し,Tab1に設置することにします.

まず,tab1の中にコンポーネントを作成します.

ionic g component tab1/components/expandable

次に作成したコンポーネントを下記のように書き換えます.

<div #expandWrapper class='expand-wrapper' [class.collapsed]="!expanded">
    <ng-content></ng-content>
</div>
.expand-wrapper {
  transition: max-height 0.4s ease-in-out;
  overflow: hidden;
  height: auto;
}

.collapsed {
  max-height: 0 !important;
}
import { Component, AfterViewInit, Input, ViewChild,  ElementRef, Renderer2 } from "@angular/core";

@Component({
  selector: 'app-expandable',
  templateUrl: './expandable.component.html',
  styleUrls: ['./expandable.component.scss'],
})
export class ExpandableComponent implements AfterViewInit {
  @ViewChild("expandWrapper", { read: ElementRef }) expandWrapper: ElementRef;
  @Input("expanded") expanded: boolean = false;
  @Input("expandHeight") expandHeight: string = "150px";

  constructor(public renderer: Renderer2) {}

  ngAfterViewInit() {
    this.renderer.setStyle(this.expandWrapper.nativeElement, "max-height", this.expandHeight);
  }
}

以上でコンポーネントは作成できました.

次にTab1にコンポーネントを登録します.moduleで読み込んで,declarationsに登録します.これによりapp-expandableタグを使ってTab1のHTMLからコンポーネントを呼び出せるようになります.

import { IonicModule } from '@ionic/angular';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab1Page } from './tab1.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';

import { Tab1PageRoutingModule } from './tab1-routing.module';
import { ImageCropperModule } from 'ngx-image-cropper';

+import { ExpandableComponent } from "./components/expandable/expandable.component";

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    Tab1PageRoutingModule,
    ImageCropperModule
  ],
  declarations: [
    Tab1Page,
+    ExpandableComponent
  ]
})
export class Tab1PageModule {}

実装

あとはtab1.page.htmlとtab1.page.tsに書くだけです.

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>


<ion-content>
  <ion-card (click)="expandItem()">
    <ion-card-header>
      <ion-card-title>My Neighbor Totoro</ion-card-title>
    </ion-card-header>

    <ion-card-content>
      <app-expandable expandHeight="100px" [expanded]="item1expand">
        <p>
          Hello there.
        </p>
      </app-expandable>
    </ion-card-content>
  </ion-card>
  <ion-card (click)="expandItem()">
    <ion-card-header>
      <ion-card-title>My Neighbor Totoro</ion-card-title>
    </ion-card-header>

    <ion-card-content>
      <app-expandable expandHeight="100px" [expanded]="item2expand">
        <p>
          Hello there.
        </p>
      </app-expandable>
    </ion-card-content>
  </ion-card>


</ion-content>
import { Component, ViewChild, ElementRef } from '@angular/core';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  item1expand:boolean = true;
  item2expand:boolean = false;

  constructor(
  ) {}

  expandItem(): void {
    this.item1expand = !this.item1expand;
    this.item2expand = !this.item2expand;
  }

}

AliExpressを使ってみた感想とデメリット

Amazonの代わりにAliExpressを使ってみた感想とデメリットです.

購入した経緯はこちら

AliExpressは中国のアリババグループが運営するECモールで,外国人向けに作られているものです.

メリット1:安い

最大のメリットは安価なことです.日本では1400円が相場の電子機器がアリエクスプレスだと800円で買えました.

メリット2:日本にないものがある

結構いい商品があります.目ききの能力があれば個人輸入もできそうだと思いました.

デメリット1:メールが多い

登録するとメールの配信が多くて,しかも止めることができません.かなり煩わしいです.

→止められました.

デメリット2:

クレジットカードを登録する必要がある.これはAmazonや楽天でも同じなのですが,裏でどのように情報を扱っているか信用できないのである程度の覚悟が必要だと思います.私はデビットカードにしました.

→他でも登録しないといけない以上,AliExperessのデメリットではないですね.

デメリット3:

配送に時間がかかる.3週間程度はかかります.

RaberryPiの一式を安く買う方法

Rasberry Pi4を一式で揃えると結構な値段がします.現在最も一般的なRasberry Pi4の4GBモデルだと本体,電源,HDMIケーブル,SDカードのセットで14000円程度が相場です.

でもRasberry Pi4の本体は8100円しかありません.日本だとどうも電源をはじめとする付属品で高くなっています.

そこで,付属品をいかに安く買うかが重要なのですが,中国のアリエクスプレスという電子モールから買うとかなり安くなります.アリエクスプレスはアリババが運営する海外むけのサイトで,個人での出品が禁止されているサイトなので比較的トラブルが少ないとされています.ここで電源アダプターなどを揃えるとかなり安くなります.

https://comprasyreise.com/shopnow

例えばラズベリーパイで案外高くつく電源アダプターです.5V 3Aとかなり高出力なアダプターであること,端子がUSB TypeCであることからレアで日本で買うと1400円が相場です.しかし,アリエクスプレスだと800円です.

配送料は1.48ドルですのでクーポン使って7.56ドルです.

私は普通にクレジットカードで買いました.

このサイトは情報を届ける代わりに広告を出しています.

Python embeddedのエラー対処法

Python embeddedでmatplotlibを使おうとするとimport errorが起きるエラーです.

import matplotlib

これでエラーが起こる.

ImportError: DLL load failed: The specified module could not be found.

原因

matplotlibを動作するにはMicrosoft Visual C++ のダウンロード が必要です.下記からダウンロードする必要があります.

https://support.microsoft.com/ja-jp/topic/%E6%9C%80%E6%96%B0%E3%81%AE%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%81%95%E3%82%8C%E3%82%8B-visual-c-%E3%81%AE%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89-2647da03-1eea-4433-9aff-95f26a218cc0

参考文献

https://www.python.org/downloads/windows/

Angularでリアルタイムに変化するグラフを書く

リアルタイムにデータが移り変わるグラフです.

realtime-streaming-data-with-angular-chartjs

概要

Angularでリアルタイムにデータが移り変わるグラフを作成します.

プロジェクトの作成

% ionic start app-name-streaming tabs --type=angular

プラグインのインストール

npm install ng2-charts@2 chartjs-plugin-streaming@1 --save
npm install chart.js@2.9.3 --save

プラグインの設定

app.module.tsで設定します.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

+import { ChartsModule } from 'ng2-charts';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule,
    IonicModule.forRoot(),
    AppRoutingModule,
+    ChartsModule
  ],
  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
  bootstrap: [AppComponent],
})
export class AppModule {}

リアルタイムに変化するグラフをかく

今回はIonicのタブプロジェクトのtab2に追加します.まずtab2.page.moduleにChartModuleを登録します.

import { IonicModule } from '@ionic/angular';
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab2Page } from './tab2.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';

import { Tab2PageRoutingModule } from './tab2-routing.module';
+import { ChartsModule } from 'ng2-charts';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    Tab2PageRoutingModule,
+    ChartsModule
  ],
  declarations: [Tab2Page]
})
export class Tab2PageModule {}

次にtab2.page.tsとtab2.page.htmlを編集します.

import { Component } from '@angular/core';
import 'chartjs-plugin-streaming';

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {

  myDataFromServer:number=20;
  updateMyDataFromServerFunction:any;

  datasets: any[] = [{
    data: []
  }, {
    data: []
  }];

  options: any;
  constructor( ) {}

  ngOnInit(){

    this.options= {
      scales: {
        xAxes: [{
          type: 'realtime',
          realtime: {
            onRefresh: (chart: any) =>{
              chart.data.datasets.forEach((dataset: any) => {  
                dataset.data.push({
                  x: Date.now(),
                  y:this.myDataFromServer
                });
              });
            },
            delay: 2000
          }
        }],
        yAxes: [{
          ticks: {
            max:100,
            min:0
          }
        }]
      }
    };
    this.updateMyDataFromServer();
  }

  updateMyDataFromServer(){
    console.log('updateMyDataFromServer() called');    
    this.updateMyDataFromServerFunction = setInterval(() => {
      console.log('called');
      this.myDataFromServer = Math.random() * 100;
      console.log(this.myDataFromServer,'this.myDataFromServer');
    },1000)
  }
}

リアルタイムに描写されるグラフが作成できました.

realtime-streaming-data-with-angular-chartjs

環境詳細

{
  "name": "1206_scrach_image",
  "version": "0.0.1",
  "author": "Ionic Framework",
  "homepage": "https://ionicframework.com/",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "~12.0.1",
    "@angular/core": "~12.0.1",
    "@angular/forms": "~12.0.1",
    "@angular/platform-browser": "~12.0.1",
    "@angular/platform-browser-dynamic": "~12.0.1",
    "@angular/router": "~12.0.1",
    "@ionic/angular": "^5.5.2",
    "@nebulae/angular-ble": "^1.0.6",
    "@types/web-bluetooth": "0.0.9",
    "aes-js": "^3.1.2",
    "chart.js": "^2.9.3",
    "chartjs-plugin-streaming": "^1.9.0",
    "ng2-charts": "^2.4.2",
    "rxjs": "~6.6.0",
    "tslib": "^2.0.0",
    "zone.js": "~0.11.4"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~12.0.1",
    "@angular-eslint/builder": "~12.0.0",
    "@angular-eslint/eslint-plugin": "~12.0.0",
    "@angular-eslint/eslint-plugin-template": "~12.0.0",
    "@angular-eslint/template-parser": "~12.0.0",
    "@angular/cli": "~12.0.1",
    "@angular/compiler": "~12.0.1",
    "@angular/compiler-cli": "~12.0.1",
    "@angular/language-service": "~12.0.1",
    "@ionic/angular-toolkit": "^4.0.0",
    "@types/jasmine": "~3.6.0",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "^12.11.1",
    "@typescript-eslint/eslint-plugin": "4.16.1",
    "@typescript-eslint/parser": "4.16.1",
    "eslint": "^7.6.0",
    "eslint-plugin-import": "2.22.1",
    "eslint-plugin-jsdoc": "30.7.6",
    "eslint-plugin-prefer-arrow": "1.2.2",
    "jasmine-core": "~3.7.1",
    "jasmine-spec-reporter": "~5.0.0",
    "karma": "~6.3.2",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.0.3",
    "karma-coverage-istanbul-reporter": "~3.0.2",
    "karma-jasmine": "~4.0.0",
    "karma-jasmine-html-reporter": "^1.5.0",
    "protractor": "~7.0.0",
    "ts-node": "~8.3.0",
    "typescript": "~4.2.4"
  },
  "description": "An Ionic project"
}

ブラウザからBLEでラズパイとデータをやり取りする方法

ブラウザ経由でBLEでラズパイと接続する方法についてまとめます.

ラズパイでBLEペリフェラルを立ち上げる

ラズパイの環境

私はpyblenoというライブラリを使ってペリフェラルを立ち上げることにしましたが,2021年6月現在,ペリフェラルからセントラルに値が変化したときに通知する「Notification」という機能が2018-11-13以降のラズベリーパイOSでは動作しません.公式ドキュメントによると,リナックスカーネルのBluetoothモジュールのバグが原因ということで,通知機能が必須な場合はラズベリーパイのOSを2018-11-13以前にする必要があります.従って,その場合rasbianのStrechやJessiになると思いますのでrasberry piは3B+を使う必要があります.

一方,私はラズベリーパイ4のBモデル(4G)を使ってこの記事を書いていますが,通知機能は使えなかったのでセントラルから定期的に読みに行くことにしてあまり問題は感じていません.

ライブラリをインストールする

sudo pip3 install pybleno

ソースコード

公式に置いてあるサンプルコードを参考に作成しました.main.pyとEchoCharacteristic.pyからなっています.

from pybleno import *
import sys
import signal
from EchoCharacteristic import *

print('bleno - echo');

bleno = Bleno()

def onStateChange(state):
   print('on -> stateChange: ' + state);

   if (state == 'poweredOn'):
     bleno.startAdvertising('echo', ['0000fff0-0000-1000-8000-00805f9b34fb'])
   else:
     bleno.stopAdvertising();

bleno.on('stateChange', onStateChange)
    
def onAdvertisingStart(error):
    print('on -> advertisingStart: ' + ('error ' + error if error else 'success'));

    if not error:
        bleno.setServices([
            BlenoPrimaryService({
                'uuid': '0000fff0-0000-1000-8000-00805f9b34fb',
                'characteristics': [ 
                    EchoCharacteristic('0000fff1-0000-1000-8000-00805f9b34fb')
                    ]
            })
        ])
bleno.on('advertisingStart', onAdvertisingStart)

bleno.start()
print ('Hit <ENTER> to disconnect')

if (sys.version_info > (3, 0)):
    input()
else:
    raw_input()

bleno.stopAdvertising()
bleno.disconnect()

print ('terminated.')
sys.exit(1)

from pybleno import Characteristic
import array
import struct
import sys
import traceback
import random

class EchoCharacteristic(Characteristic):
    
    def __init__(self, uuid):
        Characteristic.__init__(self, {
            'uuid': uuid,
            'properties': ['read', 'write', 'notify'],
            'value': None
          })
          
        self._value = array.array('B', [0] * 0)
        self._updateValueCallback = None
          
    def onReadRequest(self, offset, callback):
        try:
            print('EchoCharacteristic - %s - onReadRequest: value = %s' % (self['uuid'], [hex(c) for c in self._value]))
        except:
            print('error')
        #callback(Characteristic.RESULT_SUCCESS, self._value[offset:])
        callback(Characteristic.RESULT_SUCCESS, array.array('B',[20,90,50,100]))
    def onWriteRequest(self, data, offset, withoutResponse, callback):
        #global data
        #data += 1
        self._value = random.randint(1,10)#data
        print('write called')
        print(data,data[0],data.hex(),'data')
        #print('EchoCharacteristic - %s - onWriteRequest: value = %s' % (self['uuid'], [hex(c) for c in self._value]))
        """
        if self._updateValueCallback:
            print('EchoCharacteristic - onWriteRequest: notifying'); 
            self._updateValueCallback(self._value)
        """
        
        callback(Characteristic.RESULT_SUCCESS)
        
    def onSubscribe(self, maxValueSize, updateValueCallback):
        print('EchoCharacteristic - onSubscribe')
        
        self._updateValueCallback = updateValueCallback

    def onUnsubscribe(self):
        print('EchoCharacteristic - onUnsubscribe');
        
        self._updateValueCallback = None

“`

スマホやPCのブラウザをBLEセントラルにしてラズパイからデータを取得

フレームワークを用いずに素のHTMLとJavaScriptで実装する方法はこちら

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="utf-8"/>
  <title>AI App</title>

  <base href="/"/>

  <meta name="color-scheme" content="light dark"/>
  <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
  <meta name="format-detection" content="telephone=no"/>
  <meta name="msapplication-tap-highlight" content="no"/>
</head>

<body>
  <h1>お知らせ</h1>
 <div id="text1">Hello BLE</div>
<button id="button1">READ</button>
<button id="button2">Write</button>

<script>

 function uint32ToArrayBuffer(n) {
   const view = new DataView(new ArrayBuffer(4));
   view.setUint32(0, n, false);
   return view.buffer;
 }
    
//ClickEvent
document.getElementById("button1").addEventListener("click", function(){
    console.log('hi');

    // 1.BLEデバイスをスキャンする
navigator.bluetooth.requestDevice({
  acceptAllDevices:true, // 全てのデバイスを対象にスキャンを実施する
  optionalServices:['0000fff0-0000-1000-8000-00805f9b34fb']
}).then(device => {

  // 2.デバイスに接続
  return device.gatt.connect();

}).then(server =>{

  // 3-1.「Service」を指定
  return server.getPrimaryService("0000fff0-0000-1000-8000-00805f9b34fb");

}).then(service =>{
    console.log('hikoko')

  // 3-2.「Characteristc」を指定
  return service.getCharacteristic("0000fff1-0000-1000-8000-00805f9b34fb");

}).then((characteristic)  => {
    console.log('hikoko2')

    return characteristic.writeValue(uint32ToArrayBuffer(15)).then(char => {
        console.log('write done',char)
    });
    
    return characteristic.readValue().then(char => {
        console.log('hikoko3',char,char.getUint8(0))
        console.log('hikoko4',char,char.getUint8(1))
        console.log('hikoko4',char,char.getUint8(2))
    });
    
/*
  const countUp = () => {
      console.log('unko');
      return characteristic.readValue().then(char => {
        console.log('hikoko3',char,char.getUint8(0))
        console.log('hikoko4',char,char.getUint8(1))
        console.log('hikoko4',char,char.getUint8(2))
      });
  }
  setInterval(countUp, 50);
  */
});

});

//ClickEvent
document.getElementById("button2").addEventListener("click", function(){
    console.log('hi button2');

    // 1.BLEデバイスをスキャンする
navigator.bluetooth.requestDevice({
  acceptAllDevices:true, // 全てのデバイスを対象にスキャンを実施する
  optionalServices:['0000fff0-0000-1000-8000-00805f9b34fb']
}).then(device => {

  // 2.デバイスに接続
  return device.gatt.connect();

}).then(server =>{

  // 3-1.「Service」を指定
  return server.getPrimaryService("0000fff0-0000-1000-8000-00805f9b34fb");

}).then(service =>{
    console.log('hikoko')

  // 3-2.「Characteristc」を指定
  return service.getCharacteristic("0000fff3-0000-1000-8000-00805f9b34fb");

}).then((characteristic)  => {
    console.log('hikoko2')

  //4.受信準備を行う
  return characteristic.startNotifications().then(char => {
    console.log('hikoko3',char)

    //5.受信したバイナリを解析、処理の実施
    characteristic.addEventListener('characteristicvaluechanged', (event) => {
        console.log(event.target.value,'event.target.value');
      // 「event.target.value」がDataView型で渡ってくるのでこれを解析

    });
  });
});


});

    
</script>
    </body>
</html>

Angularを用いる方法

GiuHubの方にまとめました.

https://github.com/NP-Systems/demo-project-of-angular-ble/tree/main

Angularで画像を読み込む

そもそも

Imageオブジェクト,Fileオブジェクト,dataurlというのがキーワードになる.

FIleオブジェクトはInputタグから読み込んだもの.dataurlはブラウザ上で表示できるように文字列で表したもの.dataurlはdata:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASAB****という文字列になる.

Inputタグからファイルを読み込んでcanvasに描写したい場合,Fileオブジェクトをdataurlに変換したあと,ImageオブジェクトのSRCとしてこれを設定する.canvasはdataurlを直接受け取ることはできないためImageオブジェクトに変換する.

一方,canvasからfirebaseのStorageに書き込む場合は,canvasからdataurlを生成し,それをバイナリデータであるFileオブジェクトに変換する.FileオブジェクトはそのままStorageに保存できる.

base64, blob,もあるが,base64はdataurlと似たもので,blobはブラウザ上で生成されたFileオブジェクトのことだと理解している.

基本

Inputタグから読み込んで,canvas要素に描写する.

  <div class="upload">
    <input type="file" accept="image/*"
           (change)="fileChangeEvent($event)">
  </div>

  <div class="parent">
    <canvas #canvas></canvas>
  </div>
import { Component,ElementRef,Input,ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  file: File = null;
  @ViewChild('canvas') public canvas: ElementRef;
  width:number;// = 1400;
  height:number;// = 1400;
  private cx: CanvasRenderingContext2D;

  constructor(
  ) {
  }

  ionViewDidEnter(){
    this.width=window.innerWidth;
    this.height=window.innerHeight;
    const canvasEl: HTMLCanvasElement = this.canvas.nativeElement;
    this.cx = canvasEl.getContext('2d');
  }

  fileChangeEvent(event: any): void {
    if (event.target.files.length === 0) {
      this.file = null;
      return;
    }
    console.log(this.cx,'cx');
    console.log(event.target.files[0],'event.target.files[0]');
    this.cellRender(event.target.files[0]).subscribe((dataUrl)=>{
      console.log(dataUrl,123);
      const image = new Image();
      let aa:any =this.cx;
      image.onload = function() {
        
        aa.drawImage(image, 0, 0);   
      }
      image.src = dataUrl;


    })

}

cellRender(file):Observable<any>{
  return new Observable(observer => {
    let reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function(){
      var dataUrl:any = reader.result;
      observer.next(dataUrl);
    }
  })
}
}

Inputタグで読み込んだデータは,event.target.filesで容易にアクセスできる.配列になっているので一つしか選択していない場合はevent.target.files[0]で取得できる.

% event.target.files[0]
File {name: "スクリーンショット 2021-05-19 7.04.28.jpg", lastModified: 1621375474163, lastModifiedDate: Wed May 19 2021 07:04:34 GMT+0900 (日本標準時), webkitRelativePath: "", size: 615460, …} "event.target.files[0]"

dataURLの形式にすると便利だが,AngularだとObserverを導入するとうまくいった.


  fileChangeEvent(event: any): void {
    if (event.target.files.length === 0) {
      this.file = null;
      return;
    }
    this.imageRender(event.target.files[0]).subscribe((dataUrl)=>{
         console.log(dataUrl)
        //data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASAB****
    })
}

imageRender(file):Observable<any>{
  return new Observable(observer => {
    let reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function(){
      var dataUrl:any = reader.result;
      observer.next(dataUrl);
    }
  })
}

さらにDataURLをcanvasに描写する.canvasで画像を描く際、drawImageというメソッドを用いるが,

によると,imageはHTMLImageElement, HTMLCanvasElement, HTMLVideoElement のいずれかを取ることができるということなので,dataUrlは受け取れない.従ってconst image = new Image();のように生成しておいて,image.srcでdataURLを読み込ませる.new Image()といってもconsole.log(image)で確認すると<img src=”da” />の形式だった.

  fileChangeEvent(event: any): void {
    //fileが選択されていなければリセット
    if (event.target.files.length === 0) {
      this.file = null;
      return;
    }
    
    console.log(event.target.files[0],'event.target.files[0]');
    this.imageRender(event.target.files[0]).subscribe((dataUrl)=>{
      console.log(dataUrl,123);
      const image = new Image();
      let canvasVar:any =this.cx;
      image.onload = function() {
        canvasVar.drawImage(image, 0, 0);   
      }
      image.src = dataUrl;
    })

}

imageRender(file):Observable<any>{
  return new Observable(observer => {
    let reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function(){
      var dataUrl:any = reader.result;
      observer.next(dataUrl);
    }
  })
}

以上でファイルオブジェクトからdataURL(base64と同等)に変換し,canvasへImageオブジェクトに変換して描写する流れができたので,次はcanvasからDataURlを生成し,ファイルオブジェクトを生成する.

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <div class="upload">
    <input type="file" accept="image/*"
           (change)="fileChangeEvent($event)">
  </div>

  <div class="parent">
    <canvas #canvas></canvas>
  </div>

  + <ion-button expand="full" shape='round' (click)='save()'>Save</ion-button>


</ion-content>
save(){
  const canvasEl: HTMLCanvasElement = this.canvas.nativeElement;
  var dataURI = canvasEl.toDataURL( "image/jpeg", 0.75 ) ;
  console.log(dataURI,'dataURI');
  
  var bin = atob(dataURI.split(',')[1].replace(/^.*,/, ''));
  var buffer = new Uint8Array(bin.length);
  for (var i = 0; i < bin.length; i++) {
      buffer[i] = bin.charCodeAt(i);
  }
  this.createdFileObject = new File([buffer.buffer], "name.jpg",{type: "image/jpeg"});

}

console.log(created)すると下記が確認できる.

{name: “name.jpg”, lastModified: 1622154958219, lastModifiedDate: Fri May 28 2021 07:35:58 GMT+0900 (日本標準時), webkitRelativePath: “”, size: 569, …}

Angular(Ionic)でfirebaseによるGoogle認証でエラー

Failed to compile.

src/app/authorization/authorization.service.ts:88:48 – error TS2339: Property ‘auth’ does not exist on type ‘typeof import(“/Users/masaya/Desktop/MyGoodness/210523_webAppTemplate/templateApp/node_modules/firebase/index”)’. 88 this.afAuth.signInWithPopup(new firebase.auth.GoogleAuthProvider());

というエラーが出るようになった.

package.jsonでバージョンを確認すると

"firebase": "^8.6.2",

だった.version8からのエラーらしい.

import * as firebase from ‘firebase/app’;

と書いていたのを

import firebase from ‘firebase/app’

とかいたら動作した.

Angular(Ionic)で画像にお絵描き

Angular(Ionic)で画像にお絵描きする最も簡単な方法はプラグインを使うことです.

https://www.npmjs.com/package/ngx-image-drawing

インストール

npm install --save ngx-image-drawing

使い方

モジュールに追加

import { ImageDrawingModule } from 'ngx-image-drawing';

@NgModule({
  imports: [
    ImageDrawingModule
  ],
  declarations: []
})
export class Tab1PageModule {}

あとはHMTLから呼び出せる

<image-drawing
  <div class="upload">
      <input type="file" accept="image/*"
             (change)="fileChangeEvent($event)">
      <img [src]="imgSrc" alt="">
  </div>

  <image-drawing
      [src]="imageUrl"
      outputMimeType="'image/jpeg'"
      outputQuality="0.8"
      (save)="save($event)"
      (cancel)="cancel()">
  </image-drawing>

tsファイルはこれ


    fileChangeEvent(event: any): void {

    //fileが選択されていなければリセット
    if (event.target.files.length === 0) {
      this.file = null;
      this.imgSrc = "";
      return;
    }

    //ファイルの情報をfileとimgSrcに保存
    let reader = new FileReader();
    this.file = event.target.files[0];
    reader.onload = () => {
      this.imgSrc = reader.result;
      this.imageUrl = reader.result;
      //this.cx.drawImage(event.target.files[0], 0, 0, this.width, this.height);

      console.log(this.imgSrc);

    }
    reader.readAsDataURL(this.file);

  }


  save(event: any){
    console.log(event);
    const url = window.URL.createObjectURL(event);
    let title = "Angular_sample_file";
    //this.cx.drawImage(event, 0, 0, this.width, this.height);

    // aタグを作成して無理やりクリック -> ダウンロード機能発火
    let a = document.createElement('a');
    document.body.appendChild(a);
    a.setAttribute('style', 'display: none');
    a.href = url;
    a.download = title;
    a.click();
    window.URL.revokeObjectURL(url);
  }

プラグインを使わない方法

https://medium.com/@tarik.nzl/creating-a-canvas-component-with-free-hand-drawing-with-rxjs-and-angular-61279f577415

pandasの表示列数が少ないので増やす

terminalで作業しているとpandasの表示列数が少ない時がある。main.py

(Pdb) pd.DataFrame(data['positions'])
  instrument                                               long  ... unrealizedPL marginUsed
0    USD_CNH  {'units': '0', 'pl': '0.0000', 'resettablePL':...  ...     -39.1847  2085.3600
1    USD_CHF  {'units': '0', 'pl': '0.0000', 'resettablePL':...  ...     131.7863  2085.3600
2    NZD_USD  {'units': '400', 'averagePrice': '0.70474', 'p...  ...     175.8605  1184.0320
3    USD_CAD  {'units': '0', 'pl': '0.0000', 'resettablePL':...  ...     118.2543  1668.2880
4    GBP_USD  {'units': '400', 'averagePrice': '1.33051', 'p...  ...     -62.2680  2772.4000
5    AUD_USD  {'units': '400', 'averagePrice': '0.74940', 'p...  ...     169.8251  1257.0400
6    USD_JPY  {'units': '200', 'averagePrice': '104.144', 'p...  ...      21.0000   834.1440
7    EUR_USD  {'units': '1000', 'averagePrice': '1.21002', '...  ...     382.7307  5063.5600

もっと列数をみたい場合、main.py

(Pdb) import pandas as pd
(Pdb) pd.set_option('display.max_rows', 500)
(Pdb) pd.set_option('display.max_columns', 500)
(Pdb) pd.set_option('display.width', 1000)

と打つと、main.py

(Pdb) pd.DataFrame(data['positions'])
  instrument                                               long                                              short        pl resettablePL financing commission dividendAdjustment guaranteedExecutionFees unrealizedPL marginUsed
0    USD_CNH  {'units': '0', 'pl': '0.0000', 'resettablePL':...  {'units': '-400', 'averagePrice': '6.53311', '...    0.0000       0.0000   -1.1446     0.0000             0.0000                  0.0000     -39.1847  2085.3600
1    USD_CHF  {'units': '0', 'pl': '0.0000', 'resettablePL':...  {'units': '-400', 'averagePrice': '0.88884', '...    0.0000       0.0000   -2.2664     0.0000             0.0000                  0.0000     131.7863  2085.3600
2    NZD_USD  {'units': '400', 'averagePrice': '0.70474', 'p...  {'units': '0', 'pl': '0.0000', 'resettablePL':...    0.0000       0.0000   -0.9832     0.0000             0.0000                  0.0000     175.8605  1184.0320
3    USD_CAD  {'units': '0', 'pl': '0.0000', 'resettablePL':...  {'units': '-400', 'averagePrice': '1.27810', '...    0.0000       0.0000   -4.0178     0.0000             0.0000                  0.0000     118.2543  1668.2880
4    GBP_USD  {'units': '400', 'averagePrice': '1.33051', 'p...  {'units': '0', 'pl': '0.0000', 'resettablePL':...    0.0000       0.0000   -1.7342     0.0000             0.0000                  0.0000     -62.2680  2772.4000
5    AUD_USD  {'units': '400', 'averagePrice': '0.74940', 'p...  {'units': '0', 'pl': '0.0000', 'resettablePL':...    0.0000       0.0000   -1.1558     0.0000             0.0000                  0.0000     169.8251  1257.0400
6    USD_JPY  {'units': '200', 'averagePrice': '104.144', 'p...  {'units': '0', 'pl': '0.0000', 'resettablePL':...  130.6000     130.6000   -3.1302     0.0000             0.0000                  0.0000      21.0000   834.1440
7    EUR_USD  {'units': '1000', 'averagePrice': '1.21002', '...  {'units': '0', 'pl': '20.1626', 'resettablePL'...   20.1626      20.1626  -16.6970     0.0000             0.0000                  0.0000     382.7307  5063.5600

と表示されるようになります。

参考文献

How do I expand the output display to see more columns of a pandas Dat…https://stackoverflow.com

【2025年の崖】経産省の「DXレポート2」が刊行されたので読んでみた

「2025年の崖」というキャッチーな用語などおよそ政府の刊行物とは思えないほどキレのある文面で話題になった経済産業省のDXレポート(@2018年)ですが、昨年の暮れに「DXレポート2」が刊行されていたのでそのレポートです

なお、DXレポートについてご存知ない方のために抜粋をすると、DXレポートはこういうものです

「2018 年に公開した DX レポートにおいては、複雑化・ブラックボックス化した既存システムを解消できず DX が実現できない場合、デジタル競争の敗者になってしまうだけでなく、多額の経済損失が生じるとして警鐘を鳴らし(2025 年の崖)、この問題に対応するため、2025 年までに集中的にシステム刷新を実施する必要があると指摘した」

結構「2025年の崖」っていう言葉が話題になったんですよね。
昨年の12月29日に刊行されたDXレポート2もとても面白かったので、ITに携わる人はぜひ知っておいて良いことだと思ったのでQiitaに載せさせていただきました

各ユーザー企業におけるIT活用の指針に加えて、ベンダー企業のあるべき姿などかなり突っ込んだ内容となっており、前回にもましてキレのある文章で読み応えバッチリでした。「2020年の崖」に引くも劣らない名言揃いでしたので、章ごとにまとめていきたいと思います。

オリジナル

経産省のHPにあります。なるべく内容を損なわないようにしましたが、ぜひソースを当たっていただくといいと思います。デジタルトランスフォーメーションの加速に向けた研究会の中間報告書『DXレポート2(中間取りまとめ)』を取りまとめました (METI/経済産業…https://www.meti.go.jp

エグゼクティブサマリ

それでは、まずは冒頭の「エグゼクティブサマリ」から追っていきます。
「エグゼクティブサマリ」という名前に負けず中身も迫真に迫るものがありました

まず、2018年のDXレポートでDXによる変革の警鐘を鳴らしたにも関わらずなかなか取り組みが進まないことを受けて下記のように断じます。

  • 実に全体の9割以上の企業が DX にまったく取り組めていない(DX 未着手企業)レベルか、散発的な実施に留まっている(DX 途上企業)状況であることが明らかになった。
  • 我が国企業全体における DX への取り組みは全く不十分なレベルにあると認識せざるを得ない

と断じます。

そして、結構大企業に勤めている人はニヤリとしてしまうかもしれませんが、それに対してこのようにコメントします

  • DX =「レガシーシステムの刷新」などの本質ではない解釈が是となっていた
  • DX の本質とは単にレガシーなシステムを刷新すると言ったことに留まるのではなく、事業環境の変化に迅速に適応する能力を身につけること、そしてその中で企業文化変革することにあると考えられる

そして、コロナ禍に言及した後、このようにサマリーを締めくくっています。

  • 人々の固定観念が変化している今こそ「2025年の壁」問題の対処に向けて、企業文化を変革するある意味絶好(最後)の機会である

いや、コロナ禍に言及して「これが絶好で最後の機会」という部分が迫真に迫るものがありますね。それでは全体構成を紹介の後、本文を細かく見ていきます。

全体構成と読みどころ

ppt形式のサマリーとWord形式のレポートがあるのですが、全体の構成はこのようになっております(pptのサマリから転載)。

スクリーンショット 2021-01-13 20.55.17.jpg

個人的には、読みどころは下記だと思いました。

  • コロナ禍で表出した本質的な課題
  • 企業の目指すべき事業変革の方向性
  • ベンダー企業の目指すべき変革の方向性
  • 企業の経営・戦略の変革の方向性について、コロナ禍を契機に企業が直ちに取り組むべきもの
  • DX を進めるための短期的、中長期的な対応
  • 変革を加速するための政府の取組

それでは、それぞれについて抜粋する形で紹介していきます。

2章:コロナ禍で表出した本質的な課題

1章はこれまでの部分で説明したので2章からの紹介です。結構大きな題目を掲げて「コロナ禍で表出した本質的な課題」とありますが、いったい何なのでしょうか
まず、2020年を下記のように振り返ります。

  • 2020 年初頭からの新型コロナウイルスの世界的な感染拡大により、企業は「感染拡大を防ぎ顧客・従業員の生命を守りながら、いかに事業を継続するか」という対応を否応なしに求められることとなった

そして、テレワークの増加や新しいデジタル技術を活用した楽しみが人々の中で広まりつつあることを踏まえて
「人々は新たな価値の重要性に気付き、コロナ禍において新しいサービスを大いに利用し、順応している」
と国民を評価します。
しかし、それに追いつける企業と追いつけない企業がいることを記載した上でこのように断じます。

「ビジネスにおける価値創出の中心は急速にデジタル空間へ移行しており、今すぐ企業文化を刷新しビジネスを変革できない企業は、デジタル競争の敗者としての道を歩むことになるであろう
「そして、デジタル技術によるサービスを提供するベンダー企業も、受託開発型の既存のビジネスモデルではこのような変革に対応できないことを認識すべき」

これ政府の刊行物ぽくないですよね、?? そのように断じたのち、目指すべき方向についてテーマが移ります。

3章:企業の目指すべき方向性

3章は「デジタル企業の姿と産業の変革」という章で、ユーザ企業とベンダー企業がそれぞれ何を目指すべきかということを短期、中期長期の視点から分析しています。そして、前段でこのように名言が飛び出します。

  • ビジネスにおける価値創出の源泉はデジタルの領域に移行しつつあり、この流れはコロナ禍が終息した後も元には戻らない
  • 周囲の環境が変わっているにもかかわらず、これまで続けてきた業務形態やビジネスモデルは所与のものであるという固定観念に囚われてしまうと、抜本的な変革を実現することはできない

そして、ベンダー企業の目指すべき方向に章は進みます。

ベンダー企業の目指すべき方向性

  • 価値創造型のビジネスにおいては、ユーザー企業は絶えず変化する顧客のニーズに対応するために自社の IT システムを迅速に更新し続ける必要がある。そのためには、最もニーズの高い機能を迅速に開発し,フィードバックしながら変化に迅速に対応できるアジャイル型に開発を変革しなければ変化の速さに対応できない
  • 従来のウォーターフォール開発による受託開発型のビジネスに固執するベンダー企業は、今後ユーザー企業のニーズ・スピード感に応えられなくなる

そして問題点を指摘した後に目指すべき方向を論じます。

  • 顧客や社会の課題を正確にとらえるために、ベンダー企業はユーザー企業と DX を一体的に推進する共創的パートナーとなっていくことが求められる

なぜなら、その心は、

「米国では、システム開発をユーザー企業で行う等、ベンダー企業との分野の境目がなくなる形で変化が加速している。しかし、わが国では IT 人材がベンダー企業に偏り、雇用環境も米国とは異なる」ためです。したがって「デジタル社会における将来のベンダー企業には、顧客企業と自社の DX をともに進めていくことが求められる」からです。

以上のことはppt形式サマリーのP9を見れば綺麗にまとまっていました。

スクリーンショット 2021-01-13 21.13.54.jpg

そして次にユーザー企業を含む全体の話です。ユーザー企業はどうすればいいのでしょうか。

企業の経営・戦略の変革の方向性

短期、中長期にわけて章立てがありましたが、まずは短期の部分です。これは比較的内容が複雑なのでサマリにまとまっているものを転載させていただきます。政府刊行物のため転載が自由ということですので。

スクリーンショット 2021-01-13 21.23.38.jpg
スクリーンショット 2021-01-13 21.24.24.jpg

以下、各ポイントについての詳細です

DX推進に向けた関係者間の共通理解の形成

まず、DX推進に向けた関係者間の共通理解の形成が短期的にしなければならないことですよと言っているわけですが、これは前提として下記の2点があることを踏まえて

  • DX の推進にあたっては、経営層、事業部門、IT 部門が協働してビジネス変革に向けたコンセプトを描いていく必要がある
  • DX を推進する関係者の間で基礎的な共通理解を初めに形成することが必要

具体的には下記の方向を示しています。

経営層の課題をデータとデジタル技術を活用していかに解決していくかという視点に対しては、経営層や事業部門がアイデアを提示し、デジタルを活用することで可能となるまったく新たなビジネスを模索するという視点に対してはIT 部門がアイデアを提示し、仮説検証のプロセスを推進していくこと

そして最後にとても(!)いいことが書いてあります。

関係者間での協働を促すためにも、アジャイルマインド(俊敏に適応し続ける精神)や、心理的安全性を確保すること(失敗を恐れない・失敗を減点としないマインドを大切にする雰囲気づくり)が求められる

アジャイルマインドで心理的安全、いいですよね。。!

CIO/CDXO の役割・権限等の明確化

その他、短期的にやらないといけないこととしてCIO/CDXO の役割・権限等の明確化もあります。これは抜粋だけで。

  • CIO/CDXO がどのような役割・権限を担うべきか明確にした上で、これに基づき、DX を推進するための適切な人材が配置されるようにするべき
  • 適切なリーダーシップが欠如していると IT 部門が事業部門の現行業務の支援に留まり、業務プロセスが個別最適で縦割りとなってしまうため、DX の目標である事業変革を妨げる
  • デジタル化に係る投資を行うためには、事業部門の業務プロセスの見直しを含めた IT 投資の効率化にとどまらず、場合によっては不要となる業務プロセスと対応する IT システムの廃止・廃棄にまでつなげることが必要

なるほど。

遠隔でのコラボレーションを可能とするインフラ整備

短期的にやること3つ目です。

  • 新型コロナウイルスの感染を防止しながら事業を継続するためのツールとして、リモートワークを実現する IT インフラの整備が急速に進んでいる
  • こうした遠隔でのコラボレーションを可能とするインフラは感染防止の観点にとどまらず、今後のイノベーション創出のインフラとなる可能性がある

業務プロセスの再設計

4つ目。

  • 社会や企業においてこれまで当たり前のこととされていた業務プロセスの中には、前例を踏襲しているだけで実は見直しによって効率化可能なものや、過去の検討の結果積み重ねられてきた個別ルールによりかえって非効率となっているものが潜んでいる可能性がある
  • 「人が作業することを前提とした業務プロセス」を、デジタルを前提とし、かつ顧客起点で見直しを行うことにより大幅な生産性向上や新たな価値創造が期待

最後の部分もポイントです。

  • 業務プロセスの見直しを一度実施したとしても、そこで見直しの活動を停止してしまえば業務プロセスがレガシー化してしまう
  • 業務プロセスが顧客への価値創出に寄与しているか否かという視点をもち、恒常的な見直しが求められる

いいこと言いますよね。

中長期的な対応

以上が短期的な対応で、中長期的な対応についてです。まずはppt形式のサマリーを転載させていただきます。これを見れば概ねわかると思います。

スクリーンショット 2021-01-13 21.41.04.jpg
スクリーンショット 2021-01-13 21.41.28.jpg
スクリーンショット 2021-01-13 21.41.42.jpg
スクリーンショット 2021-01-13 21.41.48.jpg
スクリーンショット 2021-01-13 21.41.55.jpg

長期的に実施することについてもポイントを抜粋していきます。

デジタルプラットフォームの形成

中長期的にやらなければならないこととして、まずデジタルプラットフォームの形成があると言っています。

  • 企業は、協調領域については自前主義を排し、経営トップのリーダーシップの下、業務プロセスの標準化を進めることで SaaS、パッケージソフトウェアを活用し、貴重な IT 投資の予算や従事する人材の投入を抑制すべき
  • IT 投資の効果を高めるために、業界内の他社と協調領域を形成して共通プラットフォーム化することも検討すべき
  • 共通プラットフォームは、特定業界における協調領域をプラットフォーム化した業界プラットフォームや、特定の地域における社会課題の解決のための地域プラットフォーム等が想定
  • こうした共通プラットフォームによって生み出される個社を超えたつながりは、社会課題の迅速な解決と、新たな価値の提供を可能とするため、デジタル社会の重要な基盤となる

変化対応力の高い IT システムを構築するために

中長期的にやらなければならないことの2つ目は、変化対応力の高いIT システムを構築するということのようです。

  • デジタル時代の特徴として、顧客や社会との接点(Engagement)を通して顧客や社会の課題を発見し、解決することで新たな価値提案を行うためのシステム、すなわち、SoE(Systems of Engagement)の領域が広がっている
  • スモールスタートで迅速に仮説としての製品・サービスを市場に提示し、データドリブンで仮説の検証を実施するとともに、その結果を用いて製品・サービスの改善へとつなげる、というサイクルを繰り返すことで、より良い価値提案が可能となる
  • SoE の領域において、大規模ソフトウェアを外部に開発委託することは、これまでの受発注形態では対応が困難
  • 大規模なソフトウェア開発を一括発注し長期間をかけて開発するのではなく、アジャイルな開発体制を社内に構築し、市場の変化をとらえながら小規模な開発を繰り返すべき

ベンダー企業の事業変革とユーザー企業とベンダー企業との新たな関係

中長期の3つ目です。

  • 今後、大規模な受託開発は減少していく
  • 今後、ユーザー企業において DX が進展すると、受託開発の開発規模や案件数が減少するとともに、アジャイル開発による内製が主流になる
  • しかし、内製化する過程で必要となるアジャイル開発の考え方や、クラウドネイティブな開発技術等について、ユーザー企業の内部人材ではすぐに対応できないことが多いため、ベンダー企業が内製開発へ移行するための支援や、伴走しながらスキル移転することに対するニーズが高まる
  • ベンダー企業はこうした事業機会を顧客企業への客先常駐ビジネスとするのではなく、対等なパートナーシップを体現できる拠点において、ユーザー企業とアジャイルの考え方を共有_しながらチームの能力を育て(共育)、内製開発を協力して実践する(共創)べき

ジョブ型人事制度の拡大とDX 人材の確保

中長期4つ目。

  • DX を推進するために必要となる人材については(外部のベンダー企業に任せるのではなく)企業が自ら確保するべき
  • DX の推進においては、企業が市場に対して提案する価値を現実のシステムへと落とし込む技術者の役割が極めて重要
  • 副業・兼業を行いやすくし、人材流動や、社員が多様な価値観と触れる環境を整えることも重要

以上までが4章です。5章の政府の取り組みについてです。

政府の政策の方向性

5章の政府の取り組みについてです。非常に多くの良い取り組みをしてくれているんだと感じました。抜粋はしませんが、ぜひオリジナルでご一読いただくといいと思いました。

以下政府の実施内容です

共通理解形成のためのポイント集の策定,CIO/CDXO の役割再定義,DX 成功パターンの策定,DX 推進状況の把握,デジタルプラットフォームの形成,産業変革の制度的支援,ユーザー企業とベンダー企業の共創の推進,デジタル技術を活用するビジネスモデル変革の支援,研究開発に対する支援,DX 人材確保のためのリスキル・流動化環境の整備ということです。

結構いいことやってるんだなと思いました

最後に

DXレポート自身は6章目もあり、そこでは2018年のDXレポートでの指摘とその後の政策展開を振り返っていますが、今回はDXレポート2の振り返りなのでこちらは割愛させていただきます。最後に1パラグラムだけ6章から抜粋し、終わりたいと思います。

企業の行動変容が進まない理由は、生活習慣病のアナロジーで理解が可能である。誰しも、一般論としてメタボリックシンドロームの状態よりも痩せていたほうが良いことは理解している上、生活習慣病のリスクについても理解しているが、自分自身は健康だと信じている。企業の DX についても同様で、DX が必要だと理解はしていながらも、行動を変容できていない企業は多い

最後はメタボに掛けてわかりやすく説明いただきました。


個人的にはこの流れだと、Python、JavaScript、クラウドがいま以上に熱くなると思いました。

GPT-3の使い方と概要

Introduction

  • GPT-3は汎用の「テキスト入力、テキスト出力」インターフェースなので事実上すべての言語タスクに適用可能
  • APIを使用するには、テキストプロンプト(APIに提供するテキストベースの入力または「指示」)を指定するだけで、指定したコンテキストまたはパターンに一致させようとして、テキストの補完が返される
  • 実行したいことのほんの数例を書くことで、それを「プログラム」することができる
  • モデルのトレーニングデータは2019年10月で打ち切りとなっており,継続的なデータの追加は現在検討中

重要な概念

  • APIの中核となる概念は、プロンプト、 完了、トークンの3つ
  • 「プロンプト」はAPIへのテキスト入力であり、「完了」はAPIがプロンプトに基づいて生成するテキスト
  • APIの探索を開始する最良の方法は、Playgroundを使用すること
  • デフォルトでは,テキストプロンプトと生成された補完を組み合わせると、2048トークン(約1500ワード)未満に制限されている
  • モデルはdavinci、curie、babbageとadaの4つ

プロンプトの与え方の基本

  • APIは、オリジナルストーリーの生成から複雑なテキスト分析の実行まであらゆることを実行できる.が,ゆえにしたいことを明確にAPIに伝える必要がある
  • 伝える」だけでなく、見せることが良いプロンプトの秘訣.APIは、プロンプトから必要なものを推測しようとする
  • 「猫の品種のリストを教えてください」という言葉を送信しても、APIは、猫の品種のリストを要求していると自動的に想定しない
  • 最初の単語が「猫の品種のリストを教えて」で、次の単語が「好きなものを教えてあげる」という会話を続けるようにAPIに簡単に依頼するのが良い

プロンプト利用のガイドライン

  • 指示、例、または2つの組み合わせのいずれかを通じて、必要なものをAPIに明確に。APIでアイテムのリストをアルファベット順にランク付けしたり、段落を感情で分類したりする場合はそれを表示。
  • 高品質のデータを提供する.分類子を構築しようとしている場合、またはAPIをパターンに従わせようとしている場合は十分な例があることを確認
  • temperatureとtop_pの設定は、APIが応答を生成する際の決定論性を制御する。この2つが重要.正解が1つしかない応答を提供するようにAPIに要求している場合は、これらを低く設定する必要がある。明らかでない応答を探している場合は、それらをより高く設定

Classification

例はこちら

これはツイート感情分類子です
ツイート:「新しいバットマン映画が大好きでした!」
感情:ポジティブ
###
ツイート:「携帯電話のバッテリーがなくなると嫌いです。」 
感情:否定的
###
ツイート:「私の一日は👍」
感情:ポジティブ
###
ツイート:「これは記事へのリンクです」
感情:ニュートラル
###
ツイート:「この新しいミュージックビデオは私の心を吹き飛ばしました」
感情: 
  • プロンプトが最初に何をするかを述べる例の始めに、分類子が何をするかを平易な言葉で述べます.最初に伝えることで例が少なくてすむ.平易な言葉で伝える.
  • 簡単で平易な例を示す
  • 例を区切るマークとして「###」を使用する.
  • APIに任意のケースに対応する方法を示す.これでいうとニュートラルという答えを用意してあげる
  • テキストと絵文字を使用可能

応用例はこんな感じ

This is a tweet sentiment classifier\
Tweet: "I loved the new Batman movie!"\
Sentiment: Positive\
###\
Tweet: "I hate it when my phone battery dies"\
Sentiment: Negative\
###\
Tweet: "My day has been 👍"\
Sentiment: Positive\
###\
Tweet: "This is the link to the article"\
Sentiment: Neutral\
###\
Tweet text

"I loved the new Batman movie!"
"I hate it when my phone battery dies"
"My day has been 👍"
"This is the link to the article"
"This new music video blew my mind"
Tweet sentiment ratings:
1: Positive
2: Negative
3: Positive
4: Neutral
5: Positive

###
Tweet text

"I can't stand homework"
"This sucks. I'm bored 😠"
"I can't wait for Halloween!!!"
"My cat is adorable ❤️❤️"
"I hate chocolate"

Tweet sentiment ratings:

1.
import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.Completion.create(
  engine="davinci",
  prompt="This is a tweet sentiment classifier\n\nTweet: \"I loved the new Batman movie!\"\nSentiment: Positive\n###\nTweet: \"I hate it when my phone battery dies\"\nSentiment: Negative\n###\nTweet: \"My day has been 👍\"\nSentiment: Positive\n###\nTweet: \"This is the link to the article\"\nSentiment: Neutral\n###\n\nTweet text\n1. \"I loved the new Batman movie!\"\n2. \"I hate it when my phone battery dies\"\n3. \"My day has been 👍\"\n4. \"This is the link to the article\"\n5. \"This new music video blew my mind\"\n\nTweet sentiment ratings:\n1: Positive\n2: Negative\n3: Positive\n4: Neutral\n5: Positive\n\n###\n\nTweet text\n1. \"I can't stand homework\"\n2. \"This sucks. I'm bored 😠\"\n3. \"I can't wait for Halloween!!!\"\n4. \"My cat is adorable ❤️❤️\"\n5. \"I hate chocolate\"\n\nTweet sentiment ratings:\n1.",
  temperature=0.2,
  max_tokens=64,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0,
  stop=["###"]
)

Generation

結構これがすごい.

  • APIにいくつかのストーリーのアイデアのリストを与えると、そのリストに追加しようとする
教育とバーチャルリアリティに関するアイデア

1. 仮想火星
学生はバーチャルリアリティを介して火星を探索し、見たものを収集してカタログ化するミッションに進みます。

2.
import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.Completion.create(
  engine="davinci",
  prompt="Ideas involving education and virtual reality\n\n1. Virtual Mars\nStudents get to explore Mars via virtual reality and go on missions to collect and catalog what they see.\n\n2.",
  temperature=0.7,
  max_tokens=64,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0
)

Conversation

最低限で動かすならこれでもいいが,これは不十分な例.

以下はAIアシスタントとの会話です。アシスタントは親切で、創造的で、賢く、そしてとてもフレンドリーです。

人間:こんにちは、あなたは誰ですか?

AI:私はOpenAIによって作成されたAIです。今日はなんか手伝うことある?

人間: 
  • APIに会話をしてくださいということだけではなく,動作方法も伝える
  • API側の名前をAIではなく,現実に存在した専門家の名前にする

上記を考慮するとより適切にはこのような例になる.

Marvは、しぶしぶ質問に答えるチャットボットです。

###

ユーザー:1キログラムは何ポンドですか?

マーブ:これも?キログラムには2.2ポンドあります。これをメモしてください。

###

ユーザー:HTMLは何の略ですか?

Marv:Googleは忙しすぎましたか?ハイパーテキストマークアップ言語。Tは、将来、より良い質問をしようとするためのものです。

###
ユーザー:最初の飛行機が飛ぶのですか?

マーヴ:1903年12月17日、ウィルバーとオーヴィルライトが初飛行を行いました。彼らが来て私を連れ去ってくれたらいいのにと思います。

###

ユーザー:宇宙で最初の人は誰でしたか?

マーブ: 
import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.Completion.create(
  engine="davinci",
  prompt="Marv is a chatbot that reluctantly answers questions.\n\n###\nUser: How many pounds are in a kilogram?\nMarv: This again? There are 2.2 pounds in a kilogram. Please make a note of this.\n###\nUser: What does HTML stand for?\nMarv: Was Google too busy? Hypertext Markup Language. The T is for try to ask better questions in the future.\n###\nUser: When did the first airplane fly?\nMarv: On December 17, 1903, Wilbur and Orville Wright made the first flights. I wish they’d come and take me away.\n###\nUser: Who was the first man in space?\nMarv:",
  temperature=0.8,
  max_tokens=64,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0,
  stop=["###"]
)

Translation

モデルの学習済みのデータを使えるのでこれでいい

English: I do not speak French.

French: Je ne parle pas français.

English: See you later!

French: À tout à l'heure!

English: Where is a good restaurant?

French: Où est un bon restaurant?

English: What rooms do you have available?

French: Quelles chambres avez-vous de disponible?

English:
import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

start_sequence = "\nFrench:"
restart_sequence = "\n\nEnglish: "

response = openai.Completion.create(
  engine="davinci",
  prompt="English: I do not speak French.\nFrench: Je ne parle pas français.\n\nEnglish: See you later!\nFrench: À tout à l'heure!\n\nEnglish: Where is a good restaurant?\nFrench: Où est un bon restaurant?\n\nEnglish: What rooms do you have available?\nFrench: Quelles chambres avez-vous de disponible?\n\nEnglish: ",
  temperature=0.5,
  max_tokens=100,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0,
  stop=["\n"]
)

似たような例でこれでもいい.

Back to Future: 👨👴🚗🕒

Batman: 🤵🦇

Transformers: 🚗🤖

Wonder Woman: 👸🏻👸🏼👸🏽👸🏾👸🏿

Spider-Man: 🕸🕷🕸🕸🕷🕸

Winnie the Pooh: 🐻🐼🐻

The Godfather: 👨👩👧🕵🏻‍♂️👲💥

Game of Thrones: 🏹🗡🗡🏹

Spider-Man:
import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.Completion.create(
  engine="davinci",
  prompt="Back to Future: 👨👴🚗🕒\n\nBatman: 🤵🦇\n\nTransformers: 🚗🤖\n\nWonder Woman: 👸🏻👸🏼👸🏽👸🏾👸🏿\n\nWinnie the Pooh: 🐻🐼🐻\n\nThe Godfather: 👨👩👧🕵🏻‍♂️👲💥\n\nGame of Thrones: 🏹🗡🗡🏹\n\nSpider-Man:",
  temperature=0.7,
  max_tokens=64,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0
)

Summarization

  • APIは、テキストのコンテキストを把握し、さまざまな方法で言い換えることが可能
My ten-year-old asked me what this passage means:

"""

A neutron star is the collapsed core of a massive supergiant star, which had a total mass of between 10 and 25 solar masses, possibly more if the star was especially metal-rich.[1] Neutron stars are the smallest and densest stellar objects, excluding black holes and hypothetical white holes, quark stars, and strange stars.[2] Neutron stars have a radius on the order of 10 kilometres (6.2 mi) and a mass of about 1.4 solar masses.[3] They result from the supernova explosion of a massive star, combined with gravitational collapse, that compresses the core past white dwarf star density to that of atomic nuclei.

"""

I rephrased it for him, in plain language a ten-year-old can understand:

"""
  • 要約したい対象をトリプルクオートで囲むのがコツ
import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.Completion.create(
  engine="davinci",
  prompt="My ten-year-old asked me what this passage means:\n\"\"\"\nA neutron star is the collapsed core of a massive supergiant star, which had a total mass of between 10 and 25 solar masses, possibly more if the star was especially metal-rich.[1] Neutron stars are the smallest and densest stellar objects, excluding black holes and hypothetical white holes, quark stars, and strange stars.[2] Neutron stars have a radius on the order of 10 kilometres (6.2 mi) and a mass of about 1.4 solar masses.[3] They result from the supernova explosion of a massive star, combined with gravitational collapse, that compresses the core past white dwarf star density to that of atomic nuclei.\n\"\"\"\n\nI rephrased it for him, in plain language a ten-year-old can understand:\n\"\"\"",
  temperature=1,
  max_tokens=64,
  top_p=0.88,
  frequency_penalty=0,
  presence_penalty=0,
  stop=["\"\"\""]
)

tl;dr:を使って要約することもできる.

import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.Completion.create(
  engine="davinci",
  prompt="Jupiter is the fifth planet from the Sun and the largest in the Solar System. It is a gas giant with a mass one-thousandth that of the Sun, but two-and-a-half times that of all the other planets in the Solar System combined. Jupiter is one of the brightest objects visible to the naked eye in the night sky, and has been known to ancient civilizations since before recorded history. It is named after the Roman god Jupiter.[19] When viewed from Earth, Jupiter can be bright enough for its reflected light to cast visible shadows,[20] and is on average the third-brightest natural object in the night sky after the Moon and Venus.\n\nJupiter is primarily composed of hydrogen with a quarter of its mass being helium, though helium comprises only about a tenth of the number of molecules. It may also have a rocky core of heavier elements,[21] but like the other giant planets, Jupiter lacks a well-defined solid surface. Because of its rapid rotation, the planet's shape is that of an oblate spheroid (it has a slight but noticeable bulge around the equator).\n\ntl;dr:",
  temperature=0.3,
  max_tokens=64,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0,
  stop=["\n"]
)
tl;dr: Jupiter is a gas giant, the largest planet in the solar system. It is the fifth planet from the Sun and the largest in the solar system. It is a gas giant with a mass one-thousandth that of the Sun, but two-and-a-half times that of all the other planets in

Completion

ある程度次に来るものが決まっている場合はこれが使える.

import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.Completion.create(
  engine="davinci",
  prompt="```\nimport React from 'react';\nconst ThreeButtonComponent=()=>(\n<div>\n<p>Button One</p>\n<button className=\"button-green\" onClick={this.handleButtonClick}>Button One</button>\n<p>Button Two</p>\n<button className=\"button-green\" onClick={this.handleButtonClick}>Button Two</button>\n<p>Button Three</p>\n<button className=\"button-green\" onClick={this.handleButtonClick}>Button Three</button>\n</div>\n)\n''''\nimport React from 'react';\nconst HeaderComponent=()=>(",
  temperature=0.7,
  max_tokens=64,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0
)

自動で

<div>
<h1>Header</h1>
</div>

を追加してくれる

import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.Completion.create(
  engine="davinci",
  prompt="Vertical farming provides a novel solution for producing food locally, reducing transportation costs and",
  temperature=0.29,
  max_tokens=64,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0
)

生成されるものはこちら

(Vertical farming provides a novel solution for producing food locally, reducing transportation costs and) energy use, and reducing the environmental impact of agriculture.

The vertical farm is a controlled environment where crops are grown indoors, usually in stacked layers.

The vertical farm is a controlled environment where crops are grown indoors, usually in stacked layers.

Vertical farming provides a novel solution for producing food locally

Factual responses

  • Wikipediaの記事のようなものをそのまま渡すとQAの流れを理解しないので会話形式が良い
  • 低い割合でわららないという例を入れておく
Q:バットマンとは誰ですか?

A:バットマンは架空の漫画のキャラクターです。

###

Q:torsalplexityとは何ですか?
A:?
###
Q:Devz9とは何ですか?
A:?
###
Q:ジョージ・ルーカスとは誰ですか?
A:ジョージ・ルーカスは、スターウォーズの作成で有名なアメリカの映画監督兼プロデューサーです。
###
Q:カリフォルニアの首都はどこですか?
A:サクラメント。
###
Q:地球を周回するのは何ですか?
A:月。
###
Q:フレッドリッカーソンとは誰ですか?
A:?
###
Q:アトムとは何ですか?
A:原子は、すべてを構成する小さな粒子です。
###
Q:Alvan Muntzとは誰ですか?
A:?
###
Q:Kozar-09とは何ですか?
A:?
###
Q:火星にはいくつの衛星がありますか?
A:2つ、フォボスとデイモス。
###
Q:
import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.Completion.create(
  engine="davinci",
  prompt="Q: Who is Batman?\nA: Batman is a fictional comic book character.\n###\nQ: What is torsalplexity?\nA: ?\n###\nQ: What is Devz9?\nA: ?\n###\nQ: Who is George Lucas?\nA: George Lucas is American film director and producer famous for creating Star Wars.\n###\nQ: What is the capital of California?\nA: Sacramento.\n###\nQ: What orbits the Earth?\nA: The Moon.\n###\nQ: Who is Fred Rickerson?\nA: ?\n###\nQ: What is an atom?\nA: An atom is a tiny particle that makes up everything.\n###\nQ: Who is Alvan Muntz?\nA: ?\n###\nQ: What is Kozar-09?\nA: ?\n###\nQ: How many moons does Mars have?\nA: Two, Phobos and Deimos.\n###\nQ:\n",
  temperature=0,
  max_tokens=64,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0,
  stop=["###"]
)

Ionicでカスタムパイプを作成する

あるページ内で利用するカスタムパイプを作成する.ngForで繰り返した要素に対して文字列をサニタイズする方法について.一つの要素だけであれば下記方法に従えばできるが,ngForで繰り返した要素に対してサニタイズする方法について.

ionic g pipe app-summarization/customPipe1

そうすると,そのページのモジュールページに下記のコードが追加される.もしされない場合は手動で追加する.

import { CustomPipe1Pipe } from ‘./custom-pipe1.pipe’;

@NgModule({
imports: [
**],
declarations: [**,CustomPipe1Pipe]
})

今回はcustom-pipe1.pipe.tsでサニタイズするので

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Pipe({
  name: 'sanitizeHTML'
})
export class CustomPipe1Pipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}
  transform(code) {
    return this.sanitizer.bypassSecurityTrustHtml(code);
  }
}

使う場所はこんな感じ

    <div *ngFor="let message of messages;">
      <div [ngSwitch]="message['isAi']">
         <div *ngSwitchCase="'true'">
           <div class="media" >
             <div class="media-left">
               <a href="#" class="icon-rounded">AI</a>
             </div>
             <div class="media-body">
               <h4 class="media-heading">Sunny Date:2021/04/17</h4>
               <div [innerHTML]="message.message">{{message.message| sanitizeHTML}}</div>
             </div>
           </div>
         </div>
      </div>
    </div>

Ionicで特定の要素まで自動でスクロールする

tsファイルで

import {ViewChild} from ‘@angular/core’
import { IonContent } from ‘@ionic/angular’;

を読み込んだあと,

@ViewChild(IonContent, { static: false }) content: IonContent;

でIoncontentを参照する(@ViewChild(‘ion-content’) content: IonContent;とかくと動かない)

あとは

ionViewDidEnter(){
  this.content.scrollToPoint(0, 600, 300)
}

という感じで指定する.x,y,秒.

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {ViewChild} from '@angular/core'
import { IonContent } from '@ionic/angular';

@Component({
  selector: 'app-tab3',
  templateUrl: './tab3.page.html',
  styleUrls: ['./tab3.page.scss'],
})
export class Tab3Page implements OnInit {
  //@ViewChild('ion-content') content: IonContent;
  @ViewChild(IonContent, { static: false }) content: IonContent;


    ionViewDidEnter(){
      this.content.scrollToPoint(0, 600, 300)
    }

IonicでBootStrapのCSSを使えるようにする

Ionic 4以降なら,CSSをダウンロードしたあと,angular.jsonファイルに場所を指定することで簡単に設定できます.

プロジェクトのルートににあるangle.jsonファイルを開きます

styles配列を見つけ、ダウンロードしたブートストラップファイルへのパスを追加します

bootstrap-4.5.3-dist

https://getbootstrap.com/docs/4.5/getting-started/download/

            "styles": [
              "src/theme/variables.scss",
              "src/global.scss",
              "node_modules/bootstrap-4.5.3-dist/css/bootstrap.min.css"
            ],

https://stackoverflow.com/questions/53063005/how-can-i-add-and-use-bootstrap-to-an-ionic-4-app

GAEでPythonをデプロイする

# coding: utf-8
import os
import io
import time
import string
import random
import datetime
from flask import Flask, render_template, request, redirect, url_for, send_from_directory,send_file
from flask_cors import CORS
import inspect
import pandas as pd


app = Flask(__name__)
CORS(app)

@app.route('/',methods=["GET","POST"])
def hello():
    df = pd.DataFrame({'A':[i for i in range(100)]})
    a = str(list(df.T.to_dict().values()))
    print(a)
    return a
if __name__ == "__main__":
    pass
    #app.run(host='127.0.0.1',debug=True)

Flask==1.1.2
Flask-Cors==3.0.10
numpy==1.18.0
pandas==1.2.0
runtime: python37 # or another supported version
service: default
instance_class: B1
basic_scaling:
  max_instances: 1
  idle_timeout: 5m

2021年はPython37が良さそう.requirements.txtはpip3 listで表示されたものを書く.

Angular(Ionic)でHTTPリクエスト

AngularでバックエンドAPIへアクセスしてデータを取得する際、認証情報やクエリパラメータを付与したい時がある。

Post通信

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent implements OnInit {
    postId;

    constructor(private http: HttpClient) { }

    ngOnInit() {      
    const headers = { 'Authorization': 'Bearer my-token', 'My-Custom-Header': 'foobar' };
    const body = { title: 'Ionic POST Request Example' };
    this.http.post('https://example.com', body, { headers }).subscribe(data => {
        console.log(data);
    });
    }
}

Get通信

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent implements OnInit {
    postId;

    constructor(private http: HttpClient) { }

    ngOnInit() {      
    const headers = { 'Authorization': 'Bearer my-token', 'My-Custom-Header': 'foobar' };
    const parameters = { title: 'Ionic POST Request Example' };
    this.http.post('https://example.com',  { headers, parameters }).subscribe(data => {
        console.log(data);
    });
    }
}

3月15日AngularでAdsense

もう3月を中旬ですね。もう春ですね!Yoasobiに最高にハマっています。

昨日ようやくAngular(Ionic)でAdsenseを表示できるようになりました。

http://ocr-app.np-sys.com/

https://adsense-demo.np-sys.com/

https://github.com/NP-Systems/Ionic-adsense-demo

にまとめました。結構苦労したけど、大きな進展があって良かったです。

宣伝兼ねて

https://stackoverflow.com/questions/65736507/is-there-a-way-to-add-google-adsense-or-admob-to-an-ionic-angular-pwa

にも回答しておきました。Analyticsの方で表示広告数がわかるので楽しみです。

次に実施したいこと

次はOCRアプリの方の刷新をしようかと思っています。

Integrate adsense in Ionic Angular.

Note:please keep in mind that I am not native English speaker.

In this article, I would like to summarize how to insert ads into Ionic (Angular) project using adsense.

Admob is more popular than adsense for Ionic, but Admob does not support web platforms. If you want to deploy your project as a web app, you need to use Adsense instead of Admob.

However, there is little information on how to use Adsense with Angular, let alone Ionic. Therefore, I would like to introduce the steps here in this article.

Summary

Basically, I recommend you to use ng2-adsense plugin, but it doesn’t work even if you implement it according to the instructions on the official page. You need to add some other code. In addition, it is important to have a ad with fixed size(not responsive). If you set your ad style with responsive, it will not work. Lastly, I recommend you to place your ad on the top area for your page. It may sounds nonsense, but it was important to me.

GitHub page

https://github.com/NP-Systems/Ionic-adsense-demo

Demo site

https://adsense-demo.np-sys.com

Environment

Angular11, Ionic6

Ionic:

   Ionic CLI                     : 6.13.1 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.6.0
   @angular-devkit/build-angular : 0.1102.4
   @angular-devkit/schematics    : 9.1.6
   @angular/cli                  : 11.2.4
   @ionic/angular-toolkit        : 2.2.0

Capacitor:

   Capacitor CLI   : 2.1.2
   @capacitor/core : 2.1.2

Utility:

   cordova-res                          : not installed
   native-run (update available: 1.3.0) : 1.0.0

System:

   NodeJS : v12.18.0 (/usr/local/bin/node)
   npm    : 6.14.6
   OS     : macOS Big Sur

the version of ng2-adsense was 9.1.0.

Steps

Create your project

ionic start adsense-demo blank --type=angular
cd  adsense-demo

Install plugin from here . https://github.com/scttcper/ng2-adsense

npm install ng2-adsense

Please note that you need to install the one that matches your version of Angular. You can specify plugin version as followings.

npm install ng2-adsense@9.1.0

Next, add the following snippet in Index.html(above </head>).

<script async src=//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js></script>

It should look like the picture below.

Next, add following codes to the module where you want to place your ad. Officially, it says to add to app.module.ts, but please add it into the module you want to use. For example, add code into home.module.ts as shown below.

The codes you need to add are followings.

import { AdsenseModule } from 'ng2-adsense';
@NgModule({
    imports: [
    <Other modules>,
      AdsenseModule.forRoot({
        adClient: 'ca-pub-XXXXXXXXXXXXXX',
        adSlot: XXXXXXXXXXX,
      }),

You don’t have to add anything to home.page.ts.

Then add snippet below into home.page.html.

  </ion-header>
<ion-content>

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <!-- ocr-app -->
  <ins class="adsbygoogle"
       style="display:inline-block;width:728px;height:90px"
       data-ad-client="ca-pub-XXXXXXXXXXXXXX"
       data-ad-slot="XXXXXXXXX"></ins>
  <script>
       (adsbygoogle = window.adsbygoogle || []).push({});
  </script>

  <ng-adsense></ng-adsense>

Please note the followings.

  • Create ad styles in fixed size, not responsive mode (I recommend you to create ad with inline-block; width: 728px; height: 90px).
  • Add the snippet on top of the ion-content tag.

If you set ad style to responsive, it will not work. If you do not add it to the top of the ion-content tag, it will not work neither. I know it sounds strange, but I think Adsense doesn’t recognize content in Angular page. So I recommend you to do that.

Furthermore, it sounds strange to add the snippet starting with “<script” tag. However, this was also necessary for me to work.

You can check adClient and adSlot and snippet in your adsense page.

To see your snippet, click the <> button at the bottom right of the screen below.

Also, to change the style of your ad from responsive to fixed size, press the pencil mark to fix it from responsive.

It it my pleasure to visit following demo site.

https://adsense-demo.np-sys.com

Thank you.

HTMLで要素の位置を調整する

要素を真ん中におく

このように真ん中におきたい時

    <div class="ion-text-center my-class-parent">
        <ol class="my-class-child">
          <li>hi</li>
          <li>hi2</li>
        </ol>
    </div>
.my-class-parent {
  height: 100%;
  position: relative;
}

.my-class-child {
  position: absolute;
  left: 50%;
  top:30%;
  padding-left: 0;
}

my-class-parentでBody要素に対する長さを100%に指定しており、それに対してmy-class-childで場所を指定している、

2つの要素を横に並べたい時

並べたい要素をまとめて囲ったTagにdisplay:flexをつけてやる.あとはboxの幅を100%で指定しているので,Imgで幅を指定したらそれを埋めてくれるようになる

                      <div class="flex">
                      <img src="assets/img/syabani.jpg" alt="" >
                      <div class="box">{{welcomeMessage}}</div>
                      </div>

  .flex{
    display: flex;
    /*justify-content: center;*/
  }

  .flex img{
    max-width: 100%;
    width: 30%;
    height: auto;
  }

  div.box{
    width: 100%;
    background-color:#CCCCFF;
}

(Google Cloud Platform)CloudFunctionsとAppEngineどちらにするか

外貨預金としてFXを使用していますが、自動売買のようなものをやってみたくなりました。でもFXは外貨預金として始めたものだし、FXのギャンブル性に危険が怖くもあるので、外貨預金用の口座と自動売買用の口座を別に持つことにしました。

外貨預金用はSBIを使っています(アフィリエイトはやってませんが)

https://www.sbifxt.co.jp/

で、お小遣いを入れるのはOanda。こちらは3年前の口座を復活させました。

https://www.oanda.jp/

余談ですが、SBIFXは大変使いやすいです。外貨預金用としては非常にいいです。ただ、APIを提供していないです。というか、APIを提供しているのはOandaだけなんですよね。。カスタマーサポートは悪いし、スワップポイント低いしで以前愛想つかしたのですが、まぁAPIを提供しているのはここだけなのでしょうがないですね。

SBIFXは預金用です。売買は基本しません。超長期保有です。Oandaはお小遣い、遊びという位置付けです。自由にやります。昨日、名古屋の丸善でこんな本を買いました。おもしろかったです。これを実現するようなソフトを作成することにしました。

どうやってソフトを作ろうかという話で、最初はRasberryPi4にしようとおもったんですよね。メモリも4Gbあるので、これをローカルで動かして、為替データを取得して、グラフ化するというのはどうかというわけです。これは2018年に仮想通貨で使った手法なので簡単にできるのですが、なんか以前と同じ方法というのはつまらないと思いました。

そこで、今回はGoogleCloudPlatformとWebを使って同じことができないかと考えました。

GoogleCloudPlatformで行うことは2つです。

1。為替APIを使って定常的にデータベースへ保存する

2。保存したデータを取得するAPIをさらに自作する

そのうえで、

3。自作APIでデータを扱えるWebアプリを作成します。

で、GoogleCloudPlatformで上記を実現するのにGoogleAppEngineとGoogleCloudFunctionsのどちらがいいか迷いました。先日作成したアプリではGoogleCloudFunctionsを使っているのですが、GoogleAppEngineのほうがメジャーですし、別にGoogleCloudFunctionsで不満はないのですが使いやすいのかなと。。でちょっと調べてみたんですけど、そもそも値段が違うんですよね。GoogleCloudFunctionsは一つの関数しか割り当てられない代わりにものすごくやすい。200万回の呼び出しは無料で、次の200万回で1ドル程度の料金です。驚きの安さである一方、GoogleAppEngineは驚きの高さなんですよね。。一番安いプランでも月に7ドルかかってしまいます。一つの関数しか割り当てられないGoogleCloudFunctionsと比べて使い勝手はよさそうだけど、あまり使いこなせるのかと。。

https://cloud.google.com/appengine/pricing?hl=ja

AppEngineは時間に対して料金がかかります。アイオワのサーバでインスタンスを作成した場合、1時間あたり0.01ドルなので、月に7ドル程度はかかってしまいます。

CloudFunctionsの場合

https://cloud.google.com/functions/pricing

こちらの場合は、回数に対して費用が発生します。最初の200万回が無料ということなので、よっぽどアクセスを集められる以外はCloudFunctionsのほうがよさそうです。

ただ、CloudFunctionsの場合は一つの関数しかアップできないのであまり使い勝手はよくないかもしれません。まぁ、CloudFunctionsで初めてみてからの移行にしようかな。。

Angular(Ionic)で相方向バインディングを行う最低限のテンプレート

Formの場合はこちら

https://ionicframework.com/docs/v3/developer-resources/forms/

Note: If you use ngModel within a Form tag, you have to provide a name property. If you do not, you must set standalone to true in ngModelOptions

概要

Angularでは、相方向バインディングを行うための二つの方法があります。一つはテンプレート駆動型、もう一つはリアクティブフォールによるものです。

こちらの公式

https://angular.jp/guide/forms-overview

によると、

  • リアクティブフォーム はより堅牢です。よりスケーラブルで、再利用しやすく、そしてテストがしやすいです。フォームがアプリケーションの重要なパーツである場合、またはアプリケーションの構築にリアクティブパターンをすでに使用している場合は、リアクティブフォームを使用してください。
  • テンプレート駆動フォーム は、メーリングリストの申し込みフォームなどの単純なフォームをアプリに追加するのに役立ちます。アプリに追加するのは簡単ですが、リアクティブフォームほどスケーラビリティはありません。テンプレートでのみ管理できるとても基本的なフォーム要件とロジックをもつような場合は、テンプレート駆動フォームを使用してください。

ということです。

最低限のリアクティブフォーム

使いたいページのmodule、tsファイル、HTMLの3つに変更を加えます。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { Tab1p5PageRoutingModule } from './tab1p5-routing.module';
import { Tab1p5Page } from './tab1p5.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    Tab1p5PageRoutingModule,
    ReactiveFormsModule
  ],
  declarations: [Tab1p5Page]
})
export class Tab1p5PageModule {}
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-tab1p5',
  templateUrl: './tab1p5.page.html',
  styleUrls: ['./tab1p5.page.scss'],
})
export class Tab1p5Page implements OnInit {
  favoriteColorControl:formControl;

  constructor() { }

  ngOnInit() {
    this.favoriteColorControl = new FormControl('');
  }

}
<ion-header>
  <ion-toolbar>
    <ion-title>tab1p5</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  Favorite Color: <input type="text" [formControl]="favoriteColorControl">
{{favoriteColorControl.value}}
</ion-content>

Angularでfirestoreのコレクションからデータ一覧を取得する

概要

Angular2以降でfirestoreのコレクションからデータ一覧を取得する方法。

読み込む

コレクションに対して、valueChanges()をsubscribeする。valueChanges()ってなんだ。。

import { Component, ViewChild } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { AngularFirestore } from '@angular/fire/firestore';

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  authUid:string;

  constructor(
    public auth: AuthService,
    private firestore:AngularFirestore,
  ) {
    this.initialize();
    }

  initialize(){
      this.auth.getAuthUid().subscribe(uid =>{
        console.log(uid,'e');
        this.authUid = uid;
      })

  }

  test(){
    this.firestore.collection('users')
      .doc('data').collection(this.authUid)
      .valueChanges().subscribe(value =>{
        console.log(value);
      },
      error =>{
        console.log('error');
      })
  };
}

オブジェクトの配列で帰ってくる。

書き込み

こちらの方がシンプル

                      this.firestore.collection('users')
                        .doc('data')
                        .collection(this.authUid)
                        .doc(String(new Date().getTime()))
                        //.doc('test')
                        .set({
                          'title':this.originalImgName,
                          'content':data,
                          'filename':this.originalImgName,
                          'lastUpade':String(new Date().getTime()),
                        });

HuaweiのHMSとはなにか

HMSとはなにか

HMSとは、Huawei Mobile Serviceの略です。Google Mobile Service(GMS)のHuaweiバージョンです。

GMSとは何かがわかればHMSがどういうものかすぐにわかると思いますが、これは素のAndroidにGmail,YoutubeなどGoogle独自のアプリや機能を付与したものになります。

2019年からの貿易戦争により、アメリカは中国に対して禁輸措置をとりました。その結果、HuaweiはGMSに代わりサービスを独自で提供する必要に迫られました。具体的には、先ほど挙げたメールサービスやマップ、ユーザー認証などになります。詳細を見ていきましょう。

HMSの2つの構成

HMSには、利用者用のコンポーネント(AppGallery、その他のファーストパーティのHuaweiアプリとサービス)と、HMS Coreと呼ばれる開発者向けコンポーネントがあります。HMS Coreは、開発者がアプリの作成と改善に使用できるさまざまなAPI、SDK、およびサービスで構成されています。

ユーザー向けHMS

Huawei ID

GoogleアカウントやAppleIDに相当します。ユーザーのデータをこれに紐づけて管理します。

Huawei Mobile Cloud

HuaweiIDごとに用意されたクラウドのストレージです。最大5GBまで写真や音楽などが保存できます。

Huawei Assistant

これはまぁSiriのようなものなのかと。

Huawei Themes

スマホのテーマを設定できます。

Huawei Music

音楽を管理します。

Huawei Video

動画を管理します。

Huawei Browser

専用のブラウザです。

Huawei AppGallery

Google Playに相当するものですね。

HMSコア

HMSコアには下記が含まれます。結構いろいろあります。

詳細は公式でどうぞ

https://developer.huawei.com/consumer/en/hms

IonicのAndroidアプリをWebアプリケーションとしてデプロイする

概要

Angular,IonicをWebアプリとしてデプロイする方法について記述する。

firebaseを使ってホスティングする方法と、Xserverでapacheでホスティングする方法を試した。firebaseでホスティングするのは(慣れてしまえば)至れり尽くせりであるが、先のことを考えると値段が気になるためXserverのほうが望ましいように感じた。

Xserverで既にドメインを持っていれば、そのサブドメインにデプロイすることで追加負担0でサイトをデプロイすることができる。速度も速いし、こちらのほうがいい。

Firebaseでのホスティング

https://qiita.com/M_Faust/items/a514b8a79d2fd13cb72b

https://qiita.com/daikiojm/items/89f46bd83c9a2285bbc6

が参考になった。

まず、ライブラリのインストールをする

npm install -g firebase-tools

ログインする

firebase login

なお、.firebasercというファイルが生成されているので2度目以降はこれを消す。macならcommand + shift + . で隠しファイルを表示できる

初期設定を行う

firebase init

なにをしたいか聞かれるので
Hosting: Configure and deploy Firebase Hosting sitesをスペースで選択する。

What do you want to use as your public directory?

と聞かれたら、アップロードしたいファイル一式があるフォルダを指定する。私の場合はIonicだったのでwwwだった。

Configure as a single-page app (rewrite all urls to /index.html)? Yes

と言う質問については、Ionicなのでyest

File www/index.html already exists. Overwrite? (y/N)

これは、先のフォルダで指定した場所のindex.htmlをこの場でテスト用のものに上書きしますかと言うことなので、Noをしておく

その後、

firebase deploy

とすればいい。

Xserverの場合

流れは、xserverのファイルマネージャで確認できる場所へファイルをFTPソフトを使って自分でアップロードし、apacheの設定を.httpaccessとhtconfで行う

https://www.xserver.ne.jp/login_file.php

私はfirezillaを使った。

ionic build --prod

で生成されるファイル郡をfilezillaでアップロードする。私の場合は、サブドメインだったので、サブドメイン用のフォルダ(pwaと言うもの)にアップロードした

その後、pwaの中に.httpaccessファイルを生成し、さらにルートフォルダ(public_html)にhttpd.confを作成し、編集した

https://mi12cp.hatenablog.com/entry/2018/08/25/224149
<Directory "/pwa">
#   Options Indexes FollowSymLinks
    Options FollowSymLinks

#   AllowOverride None
    AllowOverride All

    Require all granted
</Directory>

.htaccessは下記

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.html [L]
</IfModule>

Angular(Ionic)でStripeでクレカ決済を導入する

目的

Angular(Ionic)でStripeでクレカ決済を導入するにあたり、ソフト側の設定方法をまとめる。AndroidのPlay Storeでの設定方法は下記参照。

概要、考え方

・クライアント側とサーバ側で処理が必要になる。

Stripeのダッシュボードにいくと、公開キーと秘密キーが得られる。

支払いを完了するさい、まずはサーバ側で支払い情報を作っておく。curlでとりあえずはいける。

curl https://api.stripe.com/v1/payment_intents \
  -u sk_test_秘密キー \
  -d amount=100 \
  -d currency=jpy

そうすると、返り値に下記が得られる。

ここにあるClient_Secretと言うものがあるのでこれを使って、ConfirmPaymentメソッドで支払いをクライアントを行う。

https://stripe.com/docs/js/payment_intents/confirm_card_payment

Angular(Ionic)でAdsenseを導入する

AngularでAdsenseで広告を出す方法です。

AndroidやiOSアプリで広告を出すためにはAdmobが利用可能ですが、AdmobはWebプラットフフォームをサポートしておりませんのでWebでは使えません。Webの場合はAdsenseを使う必要があります。

ところがAngularでAdsenseを利用する方法はほとんど情報がなく、ましてやIonicとなると皆無でした。非常に苦労しましたが、なんとかわかってきたのでまとめます。

まとめ

ng2-adsenseを使います。しかし公式の通りにやっても動作しませんので、追加でHTMLページにコードを追加する必要があります。また、広告をレスポンシブにしない、ページの上部に設置するといった一見ナンセンスなことに注意する必要があります。

環境

Angular11でIonic6です。

Ionic:

   Ionic CLI                     : 6.13.1 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.6.0
   @angular-devkit/build-angular : 0.1102.4
   @angular-devkit/schematics    : 9.1.6
   @angular/cli                  : 11.2.4
   @ionic/angular-toolkit        : 2.2.0

Capacitor:

   Capacitor CLI   : 2.1.2
   @capacitor/core : 2.1.2

Utility:

   cordova-res                          : not installed
   native-run (update available: 1.3.0) : 1.0.0

System:

   NodeJS : v12.18.0 (/usr/local/bin/node)
   npm    : 6.14.6
   OS     : macOS Big Sur

ng2-adsenseのバージョンは9.01でした。

手順

https://github.com/scttcper/ng2-adsenseからプラグインをInstallします

npm install ng2-adsense

Angularとバージョンを合わせる必要がありますので、公式のGitHubから自分が利用するAngularにあったバージョンをインストールしてください。バージョンを指定する場合は@の後にバージョンを指定します。

npm install ng2-adsense@9.1.0

次に、Index.htmlの<head></head>に下記のスニペットを追加します。

<script async src=//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js></script>

こんな感じになります。Angular、Ionicでscriptタグを利用することはほとんどありませんが、index.htmlなら貼り付けても動作しますので下記のようにしてください。

次に、広告を導入したいモジュールにコードを追加していきます。公式ではapp.module.tsに追加と書いてありますが、使いたいモジュールで追加してください。例えば下記のようなhome.module.tsを編集します。

import { AdsenseModule } from 'ng2-adsense';
@NgModule({
    imports: [
    <Other modules>,
      AdsenseModule.forRoot({
        adClient: 'ca-pub-XXXXXXXXXXXXXX',
        adSlot: XXXXXXXXXXX,
      }),

AdClientなどはAdsenseのサイトから確認していただけると思います。

home.page.tsには何も追加しなくていいです。home.page.htmlを次に編集します。home.page.htmlのion-contentタグの一番上に”<script”から始まる部分を追加します。

  </ion-header>
<ion-content>

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <!-- ocr-app -->
  <ins class="adsbygoogle"
       style="display:inline-block;width:728px;height:90px"
       data-ad-client="ca-pub-XXXXXXXXXXXXXX"
       data-ad-slot="XXXXXXXXX"></ins>
  <script>
       (adsbygoogle = window.adsbygoogle || []).push({});
  </script>

  <ng-adsense></ng-adsense>

注意点は下記です。

  • 広告のStyleはレスポンシブにせず、固定(inline-block;width:728px;height:90pxで動作確認)にする
  • ng-adsenseタグだけではなく、スクリプトタグなど部分も追加する
  • ion-contentタグの一番上に追加すること

です。これを見つけ出すのに非常に苦労しました。

広告のStyleはレスポンシブにすると表示されなくなります。ion-contentタグの一番上に追加することも似たように少しおかしな内容ですが、おそらくAdsense側でAngularのページは認識していないのだと思います。したがってページの途中に「最適な」形で読み込ませようとしてもそれが叶わないので表示されないのだと思います。それがレスポンシブにしたりページ途中に入れるとダメになることではないかと。

一方、ng-adsenseタグだけではなく、”<script”から始まる部分も追加するというのは意味がわかりません。AngularでHTMLにスクリプトを埋め込んでそれが機能するので意味がわかりませんが、動作させるためには必要でした。なんでサニタイズしなくてもいいのかは全くわかりませんが、動作はしました。

なお、adClientとadSlot、追加用のスクリプトなどはadsenseのホームページから確認可能です。

スクリプトの確認のためには下記画面の右下の<>のボタンをクリックしてください。

また、広告のスタイルを固定するためには、鉛筆のマークを押してレスポンシブから固定にします。

まじで苦労してけど表示されるようになった。

https://ocr-app.np-sys.com/

AngularでSEOを考えたときに考慮すること

背景

AngularでSEOに配慮する場合、何に気をつければいいかGoogle Developers Codelabsで勉強したのでその内容を記載します!

https://codelabs.developers.google.com/codelabs/making-a-single-page-app-search-friendly

Google Developers CodelabはハンズオンでGoogleのサービスが学べるもの

https://codelabs.developers.google.com/

1.まずはモバイルフレンドリーかチェックせよ

モバイルファーストと言われて久しいですが、まずはモバイルフレンドリーかチェックをするように記載されていました。

便利なサイトが紹介されていて、このサイトでリンク貼り付ければ調べられます

https://search.google.com/test/mobile-friendly

2.ページ毎にタイトルとメタデータを設定せよ

2番目に紹介されていたのは、基本的なことだが、ページにタイトルを付与し、メタデータディスクリプションを設定することだった。確かにAngularで作成する際、設定したことがなかった。勉強になった。AngularのConponentに直接タイトルとメタデータディスクリプションは設定できないとのことなので、BrowserModuleを使って設定するとよさそう。

https://www.atmarkit.co.jp/ait/articles/1802/05/news142.html

https://www.atmarkit.co.jp/ait/articles/1803/30/news135.html

3.Googlebotが見つけられるようにリンクを記載する

google botがURLを見つけられるようにリンクをちゃんと記載する。

Codelabにはこう記載があった。

A few tips on links

As a rule of thumb, to make sure Googlebot finds all views, make sure that:

  • All links are implemented as <a href="">
  • All links have a valid URL as their href attribute (no javascript: URLs)
  • All views have a URL that doesn’t use the fragment identifier to load different content (no URLs like “/#example” or “/#!example“, sometimes called a hash URL)

要するにハッシュタグを使わないこと、aタグを使うようにとのこと、JavaScriptでレンダリングしないようにするとのことだった。

例として出されていたのは下記

<button class="mdc-button mdc-button--outlined">
  <span class="mdc-button__label">Learn more</span>
</button>

はこう書く

<a class="mdc-button mdc-button--outlined">Learn more</a>

対応するJavaScriptはこレから

item.querySelector('button').addEventListener('click', function () {
  location.href = story.link;
});

これに変える

item.querySelector('a').setAttribute('href', story.link);

このサイトでパフォーマンスを調べると良いとのこと。

https://developers.google.com/web/tools/lighthouse/

エラー時における処理

ページが見つからない場合、リダイレクトするようにする

https://codelabs.developers.google.com/codelabs/making-a-single-page-app-search-friendly/#6

所感

大変勉強になった。AngularだからSEOが不利というわけでもなさそうです。

新しいMac Book Proが届いた!

ついに新しいMacが届きました。2019年の秋に購入したのですが、128GBのストレージがいっぱいになってしまったので新しく購入しました!(2021年追記。この記事を書いたのは2020年の夏くらい)

テンションあがります!!

スペック

開封から立ち上げ

普通のダンボールで届きました。

代引きにしたので玄関口で20万8030円を支払い、無事に受け取り。

箱から恐る恐る出して机に起きました。ドキドキしますね。

開けるとこんな感じです。

PCを取り出すとこんな感じ。

さて、早速開きます。

名前の入力、言語の設定、データの扱いに関する設定のあと、タイムゾーンを設定すると5分ほどで無事に立ち上がりました。うれしいです。

Catalinaの新しいデスクトップですね。

基本設定

日本語と英語の切り替え

まず、さっそく日本語入力と英語入力の設定を行います。

Appleのマークからシステム環境設定を選びます。

そのあと、キーボードを選択します。

4番目のTabの入力ソースから設定します

下側の画面からはずれたところに3つのチェックボックスがあり、このなかのCapsLockでエイジ入力と切り替えるでOKです。

スクロールの方向を変更

システム環境設定からトラックパッドを選んで、2番目のTabでスクロールの方向:ナチュラルの選択を解除すればいいです。

ドック(下のアイコンが並んだもの)のカスタマイズ

システム環境設定からDockを選びます。

わたしは小さめにして、左にして、普段は隠すようにしました

右クリックの設定

システム環境設定からトラックパッドを選んで、副ボタンの設定を行います。今回から同時に日本の指で押して右クリックするようにするので設計しません、

バッテリの残量の数字表示

バッテリーアイコンの上でクリックして設定するだけです。

隠しファイルのデフォルトでの表示

どこかのフォルダでcommand shift . を押します

その他

Macにはbashrcとbash_profileが最初からないので、ユーザーのルートで.bash_profile と.bashrcを作成します。.bash_profileがログイン時に実行され、.bashrcはターミナル起動時の実行となります。

.bashrcに下記を記入しておきます

alias o=’open ./’
alias p=’pwd’
alias l=’ls’
alias n=’cd ~/Desktop;open ~/Desktop’

→と思ったのですがうまく反映されず。

catalonaからはbashではなくてzshに変わったようです。ホームディレクトリに.zshrcと.zshprofileを作成します。立ち上げた時に実行されるのが.zshprofileでターミナルを普通に立ち上げた時に実行されるのが.zshrcです。

https://qiita.com/ktr_type23/items/3eb782f98c7a5f4c60b0

ソフトウェアのインストール

ソフトウェアをインストールします。

項目
Chrome普通にインストールします。dmgファイルをインストールしてから、ダブルクリックで起動して画面の操作にしたがってアイコンを移動するだけです。インストールあと、dmgファイルはゴミ箱に移動です、
node.jshttps://nodejs.org/ja/からインストールしました。pathの設定が勝手にされたのでそこがよく分からずですが、使えるようになったのでよしとします
Anacondahttps://www.anaconda.com/products/individualからインストールします.普通にインストール完了。
AndroidStudiohttps://developer.android.com/studioからインストールします。
Ionichttps://ionicframework.com/docs/intro/cliを参考にして、sudo npm install -g @ionic/cliでOK.ionic -vで情報表示されたらOK

Filezillahttps://filezilla-project.org/download.php?platform=osxからインストール。HPが古い感じだったので警戒したがURLでここと判断.FileZilla_3.48.1_macosx-x86.app.tar.bz2というtar.bz2なので、それをダブルクリックするtどう改装に保存される。アプリケーションフォルダに移動する
GImphttps://www.gimp.org/
VScodehttps://azure.microsoft.com/ja-jp/products/visual-studio-code/からダウンロード
Atomhttps://atom.io/からダウンロード
Capacitorhttps://capacitor.ionicframework.com/docs/getting-started/を参考にして、npm install –save @capacitor/core @capacitor/cliでインストール
Xcodehttps://apps.apple.com/jp/app/xcode/id497799835?mt=12からインストール

Coccoapodnpx cap add iosをした時に必要と言われたので、、https://cocoapods.org/に基づいてsudo gem install cocoapodsでインストール
Vivaldihttps://vivaldi.com/ja/からインストール
FIrefoxhttps://www.mozilla.org/en-US/firefox/download/thanks/からインストール

その他

・Githubように .sshをホームディレクトリに配置。

Xcodeの設定引継ぎ

めちゃくちゃ苦労した。全て記録に残せたわけではないが、少なくとも以前のMacは必要なかった。

AppleDeveloperサイトに行って、ログインする。Certificates, Identifiers & ProfilesからPlatformのカラムがALLのもののうち、DevelopmentとDeistributionを選んで、ダウンロードしてキーチェーンに登録する。これを新しい方でダブルクリックして登録する

それと、app developer idでパソコンでログインして、新しいMacを登録する。そうするとチームが選べるようになる。

基本的には以上の二つでOK.ってこんだけのことだったっけ?

Angular(Ionic)をWebアプリケーションとしてapache(Xserver)でデプロイする

XserverでAngular(Ionic)をホスティングする方法です。

流れとしては

  • サブドメインを設定
  • そのサブドメインにionic build –prodで生成したファイルをアップロードする
  • そのサブドメインのルートに.htaccessを設置する
  • 常時SSL化する
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.html [L]
</IfModule>

https://awesome-rainbow-colors.com/2020/07/25/ionic%E3%81%AEandroid%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92web%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%A8%E3%81%97%E3%81%A6%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4/

常時SSL化する

常時SSL化するためには、2つのことを行います

https://www.xserver.ne.jp/manual/man_server_fullssl.php

まず、無料独自SSLを導入します。これでHTTPSで接続するとSSL通信できるようになります。

さらに、HTTPで接続した時に自動でリダイレクトするように.htaccessに下記を追加します。

RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

なので最終的には、下記の.htaccessを作成して、サブドメインのプロジェクトのルートフォルダに設置すればいいです。

RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.html [L]
</IfModule>

【転職】ロイヤルエージェント株式会社を利用してみた

ロイヤルエージェント株式会社を利用して転職活動を行いました。結局縁がなかったのですが、転職エージェント選びは転職をする上で大事な選択です。そこで、転職を検討している方のためにロイヤルエージェント株式会社2ヶ月程度利用をする中で感じたことを記載します。

まとめると下記の3点です

  • 特記するほど悪い点はないが、手厚いフォローはない。方針が決まった30代向けのエージェント
  • 求人は少なく偏っていると感じたが、良い求人はある
  • 他の大手エージェントと合わせて利用すると良い

特記するほど悪い点はないが、手厚いフォローはない

ROYAL AGENT株式会社は2018年に設立された会社です。元リクルートで求人業務をしていた長岡裕己さんという方が創立されています。AIやデータサイエンス領域に特化した人材紹介事業、データ活用コンサルティングをされています。

メールのレスポンスも早く高いビジネススキルを感じましたので、その点は良かったです。ただ、個人的には応募者を右から左にただ流しているような印象も受けました。あまり経歴書のアドバイスなどもなかったですし、もう少し言ったことだけではなく突っ込んで相談に乗ってほしかったかな。方針が決まった30代向けのエージェントなのかもしれません。個人的には5点満点だと2点をつけたいと思いますが一般的には3点かな。人によっては4点5点かも。

求人は少なく偏っていると感じたが、良い求人が中には結構ある

紹介いただいたものは年収レンジも高かったです。リクルートやDENSO系、分析会社の求人がありました。ただ、個人的にはSES企業はあまり興味はなかったのでリクルートを受けました。条件厳しくしたのでその中で紹介いただいたのはとてもよかったです。一方、結構玉石金剛というか、個人的な繋がりで紹介している求人が多いと感じました。リクルート然り、知り合いが立ち上げた会社とか。結構偏っていると感じたのでちょっとこれは微妙だったかな。

他の大手エージェントと合わせて利用すると良い

以上から、他の大手エージェントと合わせて利用すると良いと思います。

一部いい求人を紹介してくれるので良かったですが、あまり手厚いフォローもないし、ビビッと来るものを紹介されたら利用してみる。でもメインのエージェントは別にもった方がいいと思いました。相談する相手という感じではないし、向こうもポンポンしたペースを望んでいると感じました。どちらかというと30代向けのエージェントです。

OANDAでGOLD会員になってAPIを使用するための費用をスプレッドから算出すると4000円程度

絹田
絹田

OandaのAPIの実質負担金額について説明するよ

2021年3月までFXでAPIといえばOandaというくらい有名だったOanda APIですが,とあるベンチャー企業のAPI不正利用とみられるトラブルにより2021年3月から無料で利用できなくなりました.2021年5月においてOandaAPIを利用するに当たっての条件と費用についてまとめます.

概要

従来は20万円の入金によりOanda APIを無料で使うことができましたが,現在は月に50万ドル以上の取引を毎月行ってゴールド会員になる必要があります.海外の業者だと月当たり1000アクセスまでは無料で配信する会社があるのですが(※末尾にリンクあります)Oandaのようにオーダーブックは提供していませんので,PythonなどでFXの自動売買を検討する場合,Oandaを利用できれば大きな強みになります.

Oanda APIを利用する条件

2021年現在,Oanda APIを利用するには月に50万ドル以上の取引を行いゴールド会員になる必要があります.会員ステータスはRegular,Silver, Goldの3区分で

  • Regular会員:口座を保有しているデフォルトの状態
  • Silver会員:前月1万USD の取引実績
  • GOLD会員:前月50万USDの取引を実績

取引量の算出は往復で算出すると言うことですので,例えばUSD/JPYでUSD25万を新規で買いUSD25万を決済する(売る)とUSD50万として計算されGOLD会員となります。1ドル100円とすると5000万円ですのである程度大きな額を売買する必要があります

実際の利用料はいくらなのか

では,50万USDの取引は実際どの程度のコストなのでしょうか.スプレッドから算出すると現実的な数字が出てきます。

OANDAのプロコースのスプレッドは0.8pipです。ベーシックならこの半分ですが、APIを使うならプロコースが前提となるため,GOLD会員になるための費用(=APIの利用費用)は下記のようになります

(0.8)*50*10000/100 = 4000円

月に4000円でOanda APIが使えるという計算になりました.50万ドルの取引と言われるよりハードルは下がりますが,以前は無料だったことを考えるとかなり高いという印象です.

その他のAPIを提供している会社

The Currency Exchange Rates 

月に250回までなら無料です.

https://exchangeratesapi.io/pricing/

Open Exchange Rates 

個人的には一番信頼感があります.月に1000回までなら無料ですし,12ドル払えば1時間ごとのデータが得られます.

Xe

有料ですがまぁ信頼感はあります.

https://www.xe.com/xecurrencydata/

その他のサイトについて

https://medium.com/@yasunaka.cho.rakuten/top-10-currency-and-forex-apis-oanda-xe-and-currencylayer-9914c830d4b2

また,OandaがAPIの無料提供をやめた理由については下記に書きました,QUOREA FXはほんと迷惑です。

絹田
絹田

読んでくれてありがとう.これからもよろしくね

QUOREA FXが迷惑な件について

QUOREA FXがOANDAのAPIを無断で使用していることもあり、OANDA APIを使用したFXの自動売買ができなくなってしまいました。明確な原因はOANDA内部の人間ではないのでわかりませんが、長年提供してきたOANDA APIが突然使えなくなり、同時にQUOREA FXを運営している株式会社efitを名指しで非難するメールが公式メールとしてきていることから判断すると、QUOREA FXのせいだと考えても妥当だと思います。QUOREA FXを運営している株式会社efitは非常に迷惑だと思います。

QUOREA FXは、AIによる高度な投資戦略が使える自動売買プラットフォームと謳っています。きれいなランディングページを作っていますが、実態はOANDAのデータを元に作成したWebシステムのようです。最近はPythonやDataRobotを使えば簡単にAI分析はできますから、浅薄なサービスとだと思います。

https://quorea.jp/login.php

OANDAから名指して非難するメールはこちら。

【重要】QUOREA FXを利用されるために口座開設されたお客様へ

平素はOANDA JapanのFXサービスをご利用いただき、誠にありがとうございます。 近頃、QUOREA FXおよび弊社の提供するAPI関連についてのお問い合わせが多く寄せられております。しかしながら、QUOREA FXを運営する株式会社efitは、弊社の配信レートおよびシステムを無断で使用し、営業しており、同社と弊社の間には何の契約もございません。 従いまして、株式会社efitのサービスについてサポートすることは致しかねます。 また、株式会社efitのウェブサイト等で、弊社があたかも「取引所取引」を提供しているような表現がございますが、弊社の外国為替証拠金取引は、お客様と弊社との間での「相対取引」でございます。どうぞ誤解のないようお願い申し上げます。 なお、弊社がお客様へ発行するAPIトークンを第三者と共有等をすることは、弊社での口座開設時に同意された利用規約に反する行為でございます。口座を閉鎖する場合もございますので、十分ご注意ください。 弊社口座に建玉がある場合には、弊社取引システムをご利用頂き、ご自身の判断で決済くださるようお願いいたします。建玉のお取引に関し、ご不明な点等ございましたら、弊社カスタマーサービス(0120-923-213)へお問い合わせください。 QUOREA FXのサービスについては、株式会社efitに直接お問い合わせください。またQUOREA FXのサービスに何らかの問題等がある場合には「金融庁 金融サービス利用者相談室(0570-016811)」あるいは「特定非営利活動法人証券・金融商品あっせん相談センター(0120-64-5005)」までお問い合わせください。 今後ともOANDA Japanをご愛顧賜りますよう、何卒よろしくお願い申し上げます。

今後ともOANDA Japanをご愛顧賜りますように、と書いてありますが、今回のAPIの提供制限はあまりにも改悪ですからね。。個々の事例の中で判断すれば良かったのではないかと思います。

もし影響が株式会社efitのせいだとすれば、非常に迷惑な話です。

ついでに言うと、株式会社efitの代表がMENSA会員であることを公言しているのが顧客には心底どーでもいい話で、逆立ちしても好きになれません。

現在、Oandaでどれくらいの費用でAPIを利用できるのかスプレッドから計算してみたら4000円くらいでした。

Oandaのデモ口座における改悪について

2021年2月はじめに突然Oandaからメールがきて多くの改悪を行うということでびっくりしました。今までも一方的な変更が突然起こり辟易して来ましたが、今回の変化は大きすぎます。

【重要】デモ口座の利用期限およびオーダーブックインジケーター、APIの利用条件変更について
お客様各位 平素は、弊社のサービスをご利用いただき誠に有難うございます。 この度、デモ口座の利用期限を設けさせていただくとともに、オーダーブックインジケーター、およびAPIのご利用条件を変更させていただくことになりましたので、ご案内いたします。 はじめに、これらの判断に至った経緯は次の通りです。 デモ口座の利用期限の導入の経緯について  これまでデモ口座は期間無制限で使用可能でしたが、過去に作成されたものの、現在は未使用で放置されているデモ口座のデータが蓄積され、膨大なデータ量がサーバへの負荷となっていること、また、商用利用をはじめとして一部で、デモ口座の本来の目的以外で使用しているケースが散見され、サーバに負担をかけているためです。 オーダーブックインジケーター、APIの利用条件の変更の経緯について インジケーター、API提供の本来の目的である弊社との取引以外の目的での使用が多数見受けられ、サーバへの負担が増加しているためです。 いずれも、サーバへの負荷を高め、システム障害発生の原因となり、実際にお取引をしていらっしゃるお客様にご迷惑をおかけする原因の一つです。昨年はこのような事例が実際に発生しておりました。 このため、これらの無制限であったサービスの一部に制限をかけることで、実際にお取引されているお客様へのサービスの品質向上を目指すことこそがお客様にとっても、弊社にとっても健全であると判断し、このような決定に至りました。 何卒、ご理解のほどよろしくお願い申し上げます。 制限対象になるお客様に弊社のサービスをより理解していただくために、移行期間である2021年2月はキャンペーンとして、本番口座を保有しているお客様全員にゴールド会員資格をプレゼントさせていただきます。この機会のゴールド会員のサービスをお試しください。
デモ口座の利用期限について
本年2月1日からは、デモ口座の利用期限を当該口座開設後30日間とさせていただきます。 ただし、以下の条件を満たすお客様は継続してデモ口座をご利用いただけます。1. 本番口座ステータスがゴールド(Gold)であること。2. 本番口座のマイページから、種類ごとに1つのデモ口座を登録すること。 利用期限を経過、またはゴールド会員を失ったことによりデモ口座の利用権を失った口座はロックされます。(ログインできません。) ロックの解除にはロック後60日以内にゴールド会員のステータスに昇格(回復)後、再び本番口座のマイページより、当該デモ口座の登録を行うことでロックを解除することができます。(過去に登録していた口座に関しても再登録が必要です。) なお、60日以内にロックの解除が行われなかったデモ口座は閉鎖され、復元することはできませんのでご注意ください。
移行期間2021年2月1日より、デモ口座の利用期限を設定させていただきます。既存のデモ口座につきましては、利用期限を2021年2月28日までとさせていただきます。 ゴールド会員の方でデモ口座を継続使用される場合は2月末までにマイページより必ずご登録願います。(ご登録は2021年2月2日以降行うことができます。)

30日と言っていますが、その後、2月28日を待たずにOandaのデモ口座ではAPIも使えなくなりました。今までも一方的な変更が突然起こり辟易して来ましたが、今回の変化は大きすぎますね。。

GitHubのREADME.mdをグラフ入りで効率的に書く方法

GitHubのREADME.mdをグラフ入りで効率的に書く方法です。

  • VSCodeでMarkdown Preview Enhancedを入れておく
  • 普通にREADME.mdを作成する
  • 文章はここに書く
  • グラフはグラフごとに.mdファイルを生成して、Markdown Preview Enhancedに入っているmermaid.jsを使ってグラフを書く
  • それをJPEGやPNGとしてエクスポートする
  • その画像をREADME.mdから読み込む

これが一番早そうです。

Ionic(Angular)でAdmobを導入する方法(iOS編)

Ionic(Angular)でAdmobを導入する方法です。こちらはiOSでの実装となります。Androidでの実装方法はこちら。

ポイント

  • capacitor-admobプラグインを使用します
  • 公式の方法+Plugin.swiftの編集が必要です(この記事に方法あります)

流れ

  1. npmでcapacitor-admobをインストールし、info.plistを編集します
  2. そのままだとエラーが起こるので、Plugin.swiftを修正します
  3. app.component.tsでアプリ情報を初期化してから読み込みたいモジュールで広告呼び出します

動作環境

Ionic4, 5, 6でiOS13から14.3で確認しています。

参考文献

https://github.com/rahadur/capacitor-admob

https://developers.google.com/admob/android/test-ads?hl=ja

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

作成方法はこちら。


見た目はこんな感じ。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
├── tabs-routing.module.ts
├── tabs.module.ts
├── tabs.page.html
├── tabs.page.scss
├── tabs.page.spec.ts
└── tabs.page.ts

実装する

1. ライブラリをインストールする

npmでcapacitor-admobをインストールします。

 npm install --save capacitor-admob

2. iOSのプロジェクトを生成する

iOSのプロジェクトを生成するために下記コマンドを打ちます。capacitorを入れていない方はcapacitorを入れてください。

ionic build
npx cap add ios
npx cap sync
npx cap open ios

3. info.plistを編集する

info.plistとは、iOSアプリの設定情報などを記入するものです。インフォプロパティリストと読みます。下記コマンドでXcodeを開きます。

npx cap open ios

info.plistを選択します。

このファイルを編集するのですが、編集するにはコツが入ります、info.plistの上で右クリックをして、open as > source codeを選択して編集できるようにします。

そして、下記のスニペットを下の方に貼り付けます。自分のアプリIDに変更するのを忘れないでください。

    <key>GADIsAdManagerApp</key>
    <true/>
    <key>GADApplicationIdentifier</key>
    <!-- replace this value with your App ID key-->
    <string>ca-app-pub-7171076285090910~XXXXXXXX</string>

もし場所がわからない方がいたらinfo.plistを丸ごと貼り付けるので参考にしてください。下から3行目から8行目までが貼り付けたものです。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleDisplayName</key>
        <string>admob-master</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleURLName</key>
			<string>com.getcapacitor.capacitor</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>capacitor</string>
			</array>
		</dict>
	</array>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
	</dict>
	<key>NSCameraUsageDescription</key>
	<string>To Take Photos and Video</string>
	<key>NSLocationAlwaysUsageDescription</key>
	<string>Always allow Geolocation?</string>
	<key>NSLocationWhenInUseUsageDescription</key>
	<string>Allow Geolocation?</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>To Record Audio With Video</string>
	<key>NSPhotoLibraryAddUsageDescription</key>
	<string>Store camera photos to camera</string>
	<key>NSPhotoLibraryUsageDescription</key>
	<string>To Pick Photos from Library</string>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<true/>
    
    <key>GADIsAdManagerApp</key>
    <true/>
    <key>GADApplicationIdentifier</key>
    <!-- replace this value with your App ID key-->
    <string>ca-app-pub-7171076285090910~3362455272</string>
    
</dict>
</plist>

5. 公式にはないエラー処理のコードを書く

公式ではこれで完了ということですが、これだとエラーが起きます。これでエミュレータを起動すると下記のエラーが出ます。

右上を見ると赤い背景で9というエラーが表示されています。その右下の別の赤いボタンからエラーが起きている「Plugin.swift」へ移動します。

そうするとそれぞれのエラーの詳細が確認できます。

よく見るとどれも同じメッセージのようです。こう書いてあります、

Method 'interstitialDidReceiveAd' must be declared public because it matches a requirement in public protocol 'GADInterstitialDelegate'

publicで宣言せよと書いてあるのでそうします。

修正前

    private func interstitialDidReceiveAd(_ ad: GADInterstitial) {
        print("interstitialDidReceiveAd")
        self.notifyListeners("onAdLoaded", data: ["value": true])
    }

修正後

    public func interstitialDidReceiveAd(_ ad: GADInterstitial) {
        print("interstitialDidReceiveAd")
        self.notifyListeners("onAdLoaded", data: ["value": true])
    }

合計9箇所同じように変えると、ビルドできるようになるはずです。

広告を表示する

以上で広告を表示するためのプラグインの設定が終わったので、いよいよ広告をアプリで表示していきます。

まず、app.component.tsでアプリケーションを初期化します。

import { Plugins } from "@capacitor/core";
// import { initialize } from 'capacitor-admob'; No longar required

const { AdMob } = Plugins;

@Component({
  selector: "app-root",
  templateUrl: "app.component.html",
  styleUrls: ["app.component.scss"]
})
export class AppComponent {
  constructor() {
    // Initialize AdMob for your Application
    AdMob.initialize("YOUR APPID");
  }
}

次に、読み込みたいページで任意の広告を呼び出します。ここでは、tab1でバナー広告、tab2でインタースティシャル広告を呼び出します。

テストIDを使ってください。

https://developers.google.com/admob/android/test-ads?hl=ja

tab1でバナー広告を呼び出す

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions, AdSize, AdPosition } from "capacitor-admob";

const { AdMob } = Plugins;
@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/6300978111",
    adSize: AdSize.BANNER,
    position: AdPosition.BOTTOM_CENTER
  };

  constructor() {
    // Show Banner Ad
    AdMob.showBanner(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      console.log("Banner Ad Loaded");
    });
  }

}

tab2でインタースティシャルを表示する

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions } from "capacitor-admob";
const { AdMob } = Plugins;

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/1033173712",
    hasTabBar: false, // make it true if you have TabBar Layout.
    tabBarHeight: 56 // you can assign custom margin in pixel default is 56
  };

  constructor() {
    // Prepare interstitial banner
    AdMob.prepareInterstitial(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      // You can call showInterstitial() here or anytime you want.
      console.log("Interstitial Ad Loaded");
      // Show interstitial ad when it’s ready

      AdMob.showInterstitial().then(
        value => {
          console.log(value); // true
        },
        error => {
          console.error(error); // show error
        }
      );

    });
  }
}

時間を空ける

Admobの仕様で広告が表示されるまでに時間が必要です。

設定が正しくされていても最初は

  • Ad failed to load : 2
  • Ad failed to load : 0

などが表示されて広告が表示されません。これは時間をおけば解決なので少し忍耐が必要です。

その他何かうまくいかないときは下記を実行することをお勧めします。

  • ionic build –prodでプロダクションモードでビルドする
  • npx cap sync ios
  • npx cap copy

終わりに

あー記事まとめるのめっちゃ大変だった!お疲れ様でした。

Ionic(Angular)でAdmobを導入する方法(Android編)

Ionic(Angular)でAdmobを導入する方法です。こちらはAndroidでの実装となります。iOS版はこちら。

ポイント

  • capacitor-admobプラグインを使用します
  • 公式の方法+依存関係の解決(この記事に方法あります)が必要です

流れ

  1. npmでcapacitor-admobをインストールします
  2. そのままだとエラーが起こるので、依存関係を解決します
  3. エミュレータで正しくアプリが立ち上がり依存関係が解決されていることを確認したのち、app.component.tsでアプリ情報を初期化してから読み込みたいモジュールで広告呼び出します

動作環境

Ionic4, 5, 6でAndroid SDKはAPIレベル29(Android X)で検証。28以下でもいけました。

参考文献

https://github.com/rahadur/capacitor-admob

https://developers.google.com/admob/android/test-ads?hl=ja

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
├── tabs-routing.module.ts
├── tabs.module.ts
├── tabs.page.html
├── tabs.page.scss
├── tabs.page.spec.ts
└── tabs.page.ts

実装する

1. ライブラリをインストールする

npmでcapacitor-admobをインストールします。

 npm install --save capacitor-admob

2. Androidのプロジェクトを生成する

Androidのプロジェクトを生成するために下記コマンドを打ちます。capacitorを入れていない方はcapacitorを入れてください。

ionic build
npx cap add android
npx cap sync
npx cap open android

3. AndroidManifest.xmlを編集する

Androidのマニフェストファイルはアプリに機能を追加するときに編集するものです。Android Studioではデフォルトで作成されるものとなります。

ここからどのファイルを編集するか分かりにくいので詳細に記載します。非常にここは苦労しました。

デフォルトでは下記のようにAndroidが選択されていると思います。

ドリルダウンを選択してProjectを選択します

この状態にまずしてください。

マニフェストファイルはProjectのandroid/app/src/main/AndroidManifest.xmlにあります。

自分のAdmobのアプリIDに編集する必要がありますが、下記のように変更します。

<application>
  <!-- this line needs to be added (replace the value!) -->
         <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-7171076285090910~XXXXXXXX" />
  <activity></activity>
</application>

つまり、編集した後のファイルは下記のようになります。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="io.ionic.starter">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-7171076285090910~XXXXXXXXX" />

        <activity
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
            android:name="io.ionic.starter.MainActivity"
            android:label="@string/title_activity_main"
            android:theme="@style/AppTheme.NoActionBarLaunch"
            android:launchMode="singleTask">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="@string/custom_url_scheme" />
            </intent-filter>

        </activity>

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"></meta-data>
        </provider>
    </application>

    <!-- Permissions -->

    <uses-permission android:name="android.permission.INTERNET" />
    <!-- Camera, Photos, input file -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- Geolocation API -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-feature android:name="android.hardware.location.gps" />
    <!-- Network API -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- Navigator.getUserMedia -->
    <!-- Video -->
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- Audio -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
</manifest>

4. MainActivity.javaを編集する

admobをandroidに登録します。

Projectを選択した状態で、Android>app>src>main>java><your original name>>MainActivityを選択します。

そして、このように変更します。

// Other imports...
import app.xplatform.capacitor.plugins.AdMob;

public class MainActivity extends BridgeActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{

      add(AdMob.class);  // Add AdMob as a Capacitor Plugin

    }});
  }
}

つまり、このようにします。

package io.ionic.starter;

import android.os.Bundle;

import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;
import java.util.ArrayList;

import app.xplatform.capacitor.plugins.AdMob;

public class MainActivity extends BridgeActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Initializes the Bridge
    this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
      // Additional plugins you've installed go here
      // Ex: add(TotallyAwesomePlugin.class);
      add(AdMob.class);  // Add AdMob as a Capacitor Plugin

    }});
  }
}

5. 公式にはないエラー処理のコードを書く

公式ではこれで完了ということですが、これだとエラーが起きます。これでエミュレータを起動すると下記のエラーが出ます。

/Users/masaya/Desktop/MyGoodness/admob-master/admob-master/node_modules/capacitor-admob/android/src/main/java/app/xplatform/capacitor/plugins/AdMob.java:4: エラー: パッケージandroid.support.design.widgetは存在しません
import android.support.design.widget.CoordinatorLayout;

そこで、焦らずに下記の対応を行います。

https://github.com/rahadur/capacitor-admob/issues/35

Admob.javaの編集

まず、エラーが出ているAdmob.javaのファイルのエラー発生している行を下記に変更します。

import androidx.coordinatorlayout.widget.CoordinatorLayout;

前のものはコメントアウトでもしておいてこのようにします。

そして、capacito-admobのbuild.gradleのdependenciesの中にimplementation ‘androidx.coordinatorlayout:coordinatorlayout:1.1.0’を追加します。

implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'

編集するファイルを間違えるとドツボにハマるので注意。

これでサイドエミュレータを立ち上げると下記のエラーが出ます。

2020-12-21 15:44:03.866 9236-9236/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: io.ionic.starter, PID: 9236
    java.lang.RuntimeException: Unable to start activity ComponentInfo{io.ionic.starter/io.ionic.starter.MainActivity}: android.view.InflateException: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Error inflating class android.support.design.widget.CoordinatorLayout
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3270)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
     Caused by: android.view.InflateException: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Error inflating class android.support.design.widget.CoordinatorLayout
     Caused by: android.view.InflateException: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Error inflating class android.support.design.widget.CoordinatorLayout
     Caused by: java.lang.ClassNotFoundException: android.support.design.widget.CoordinatorLayout
        at java.lang.Class.classForName(Native Method)
        at java.lang.Class.forName(Class.java:454)
        at android.view.LayoutInflater.createView(LayoutInflater.java:815)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:1006)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:961)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:659)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:534)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:481)
        at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:555)
        at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161)
        at com.getcapacitor.BridgeActivity.init(BridgeActivity.java:60)
        at com.getcapacitor.BridgeActivity.init(BridgeActivity.java:48)
        at io.ionic.starter.MainActivity.onCreate(MainActivity.java:17)
        at android.app.Activity.performCreate(Activity.java:7802)
        at android.app.Activity.performCreate(Activity.java:7791)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
2020-12-21 15:44:03.866 9236-9236/? E/AndroidRuntime: Caused by: java.lang.ClassNotFoundException: Didn't find class "android.support.design.widget.CoordinatorLayout" on path: DexPathList[[zip file "/data/app/io.ionic.starter-wIOzWpenwsyjIPlSvEtd-g==/base.apk"],nativeLibraryDirectories=[/data/app/io.ionic.starter-wIOzWpenwsyjIPlSvEtd-g==/lib/x86, /system/lib, /system/product/lib]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:196)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        	... 28 more

そこで、この辺を解決してくれるように下記のライブラリを導入します。

terminalで下記を打ちます。

npm install jetifier
npx jetify
npx cap sync android

これで再度エミュレータを起動すると、ようやく無事に立ち上がるようになります。これで依存関係が無事に解決されました。

広告を表示する

以上で広告を表示するためのプラグインの設定が終わったので、いよいよ広告をアプリで表示していきます。

まず、app.component.tsでアプリケーションを初期化します。

import { Plugins } from "@capacitor/core";
// import { initialize } from 'capacitor-admob'; No longar required

const { AdMob } = Plugins;

@Component({
  selector: "app-root",
  templateUrl: "app.component.html",
  styleUrls: ["app.component.scss"]
})
export class AppComponent {
  constructor() {
    // Initialize AdMob for your Application
    AdMob.initialize("YOUR APPID");
  }
}

次に、読み込みたいページで任意の広告を呼び出します。ここでは、tab1でバナー広告、tab2でインタースティシャル広告を呼び出します。

テストIDを使ってください。

https://developers.google.com/admob/android/test-ads?hl=ja

tab1でバナー広告を呼び出す

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions, AdSize, AdPosition } from "capacitor-admob";

const { AdMob } = Plugins;
@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/6300978111",
    adSize: AdSize.BANNER,
    position: AdPosition.BOTTOM_CENTER
  };

  constructor() {
    // Show Banner Ad
    AdMob.showBanner(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      console.log("Banner Ad Loaded");
    });
  }

}

tab2でインタースティシャルを表示する

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions } from "capacitor-admob";
const { AdMob } = Plugins;

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/1033173712",
    hasTabBar: false, // make it true if you have TabBar Layout.
    tabBarHeight: 56 // you can assign custom margin in pixel default is 56
  };

  constructor() {
    // Prepare interstitial banner
    AdMob.prepareInterstitial(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      // You can call showInterstitial() here or anytime you want.
      console.log("Interstitial Ad Loaded");
      // Show interstitial ad when it’s ready

      AdMob.showInterstitial().then(
        value => {
          console.log(value); // true
        },
        error => {
          console.error(error); // show error
        }
      );

    });
  }
}

時間を空ける

Admobの仕様で広告が表示されるまでに時間が必要です。

設定が正しくされていても最初は

  • Ad failed to load : 2
  • Ad failed to load : 0

などが表示されて広告が表示されません。これは時間をおけば解決なので少し忍耐が必要です。

その他何かうまくいかないときは下記を実行することをお勧めします。

  • ionic build –prodでプロダクションモードでビルドする
  • npx cap sync android
  • npx cap copy

終わりに

あー記事まとめるのめっちゃ大変だった。お疲れ様でした。

Angularで画像をトリミングする方法

Angularで読み込んだ画像をトリミングできるようにする方法です。

ポイント

  • ngx-image-cropperを使います

流れ

  1. ngx-image-cropperをnpmでinstallします
  2. 読み込みたい場所でngx-image-cropperをmoduleに登録します
  3. その場所のHTMLとTSファイルを編集します

環境

Ionic 4, 5, 6で動作検証しています。

ソースコード

GitHubに上げました。

https://github.com/NP-Systems/Ionic_tips/tree/id1137-imageCropper

参考文献

https://www.npmjs.com/package/ngx-image-cropper

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
├── tabs-routing.module.ts
├── tabs.module.ts
├── tabs.page.html
├── tabs.page.scss
├── tabs.page.spec.ts
└── tabs.page.ts

実装方法

それではTab1ページに実装していきます。

インストール

プロジェクトのフォルダに移動して下記コマンドでインストールします。

npm install ngx-image-cropper --save

モジュールへの登録

今回はtab1に実装することにするので、tab1.module.tsに下記のようにします。

import { IonicModule } from '@ionic/angular';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab1Page } from './tab1.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
import { ImageCropperModule } from 'ngx-image-cropper';

import { Tab1PageRoutingModule } from './tab1-routing.module';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    Tab1PageRoutingModule,
    ImageCropperModule
  ],
  declarations: [Tab1Page]
})
export class Tab1PageModule {}

import { ImageCropperModule } from ‘ngx-image-cropper’;を追加して、

@NgModuleのimportsにImageCropperModuleを追加します。

HTMLとTSファイルの編集

上記で準備は完了なので、HTMLとTSファイルの編集を行います。

tab1.page.htmlに下記のように追加します、

<input type="file" (change)="fileChangeEvent($event)" />

<image-cropper
    [imageChangedEvent]="imageChangedEvent"
    [maintainAspectRatio]="true"
    [aspectRatio]="4 / 3"
    format="png"
    (imageCropped)="imageCropped($event)"
    (imageLoaded)="imageLoaded()"
    (cropperReady)="cropperReady()"
    (loadImageFailed)="loadImageFailed()"
></image-cropper>

<img [src]="croppedImage" />

tab1.page.tsを下記のようにします。

import { Component } from '@angular/core';
import { ImageCroppedEvent } from 'ngx-image-cropper';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
    imageChangedEvent: any = '';
    croppedImage: any = '';

    constructor(
    ) {}

    fileChangeEvent(event: any): void {
        this.imageChangedEvent = event;
    }
    imageCropped(event: ImageCroppedEvent) {
        this.croppedImage = event.base64;
    }
    imageLoaded(image: HTMLImageElement) {
        // show cropper
    }
    cropperReady() {
        // cropper ready
    }
    loadImageFailed() {
        // show message
    }

}

応用編

以上が基礎ですが、ここからは切り取った画像を新たに別の画像をとして読み込む方法について記載します。

まず、HTMLに切り取った後に押下するためのボタンを配置します。

tab1.page.htmlの当該部分を下記のように変更します。

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-item>
    <input type="file" accept='image/*'  (change)="fileChangeEvent($event)" />
  </ion-item>

  <ion-progress-bar type="indeterminate" *ngIf="show" reversed="true"></ion-progress-bar>
  <!--プログレスバーの追加です-->

  <image-cropper
  [imageChangedEvent]="imageChangedEvent"
  [maintainAspectRatio]="false"
  [aspectRatio]="5/ 3"
  format="jpeg"
  (imageCropped)="imageCropped($event)"
  (imageLoaded)="imageLoaded()"
  (cropperReady)="cropperReady()"
  (loadImageFailed)="loadImageFailed()"
  imageQuality=25
></image-cropper>

  <img [src]="croppedImage" />

  <ion-button expand='block' (click)="onButtonClicked()" shape='round'>Get cropped image</ion-button>

</ion-content>

そして、tsファイルで追加で下記2つのモジュールを読み込みます。

import { LoadingController } from '@ionic/angular';
import { Observable } from 'rxjs';

'
'
  constructor(
    public loadingController: LoadingController,
  ) {}

そして、tsファイルの変数として下記を追加します。

export class Tab1Page {
  imageChangedEvent: any = '';
  originalImgName:string;
  buffCroppedImageBase64:string;
  croppedImageBase64: any = '';
  croppedFileObject:any;
  show:any=false;
  • imageChangedEventは、画像の選択範囲が変わったら都度呼び出されるイベントに関する情報を保持するものです。
  • originalImgNameは、inputタグで読み込んだ画像から元のファイル名を取得できるのでそれを格納する変数です
  • buffCroppedImageBase64は、切り取った画像(base64です)を格納する一次ファイルです。HTML側で<img [src]=”croppedImageBase64″ />と定義していますが、ユーザーが選んでいる時に都度croppedImageBase64″に入れると切り取った画像がチラチラしてしまうので、一時的に格納するものとしてbuffCroppedImageBase64を用意しました。
  • CroppedImageBase64は最終的な結果を入れるものです。ここにBase64の画像を入れると、HTML側で表示されます
  • croppedFileObjectは、切り取った画像を別のファイルとして読み込むのに使用します。
  • showはProgressバーを制御するものです

fileChangeEventの部分を下記のように拡張します。

  fileChangeEvent(event: any): void {
    this.show = true;
    this.read(event).subscribe(()=>{
      console.log('done');
      this.show = false;
    });
  }

  read(event: any):Observable<any> {
    return new Observable(observer => {
      setTimeout(() => {
        this.imageChangedEvent = event;
        this.originalImgName = event.target.files[0].name;
        observer.next('e');
      }, 500);
    })
  }

重い画像だと読み込むのに数秒かかってしまいます。そこで、読み込み開始したらprogressバーで動作していることを知らせています、500は使った感触で決めました。

imageCroppedの関数を下記のように変えます。

  imageCropped(event: ImageCroppedEvent) {
      this.buffCroppedImageBase64 = event.base64;
  }

先ほど行ったように一度Bafferの方に切り取った画像を入れています。

最後にonButtonClickedの関数を下記のように変更します。


  onButtonClicked(){
    this.croppedImageBase64 = this.buffCroppedImageBase64;
    console.log(this.croppedImageBase64,'this.croppedImageBase64');

    var bin = atob(this.buffCroppedImageBase64.replace(/^.*,/, ''));
    // バイナリデータ化
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
    }
    this.croppedFileObject = new File([buffer.buffer], "heihei.jpg",{type: "image/jpeg"});
    console.log(this.croppedFileObject,'this.croppedFileObject');
  }

最終的に下記にようにします。

import { Component } from '@angular/core';
import { ImageCroppedEvent } from 'ngx-image-cropper';
import { LoadingController } from '@ionic/angular';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  imageChangedEvent: any = '';
  originalImgName:string;
  buffCroppedImageBase64:string;
  croppedImageBase64: any = '';
  show:any=false;

  constructor(
    public loadingController: LoadingController,
  ) {}

  fileChangeEvent(event: any): void {
    console.log(event,'abcde');
    this.show = true;
    this.read(event).subscribe(()=>{
      console.log('done');
      this.show = false;
    });
  }

  read(event: any):Observable<any> {
    return new Observable(observer => {
      setTimeout(() => {
        this.imageChangedEvent = event;
        this.originalImgName = event.target.files[0].name;
        observer.next('e');
      }, 500);
    })
  }


  imageCropped(event: ImageCroppedEvent) {
      this.buffCroppedImageBase64 = event.base64;
  }
  imageLoaded(image: HTMLImageElement) {
      // show cropper
  }
  cropperReady() {
      // cropper ready
  }
  loadImageFailed() {
      // show message
  }

  onButtonClicked(){
    this.croppedImageBase64 = this.buffCroppedImageBase64;
    console.log(this.croppedImageBase64,'this.croppedImageBase64');

    var bin = atob(this.buffCroppedImageBase64.replace(/^.*,/, ''));
    // バイナリデータ化
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
    }
    this.croppedFileObject = new File([buffer.buffer], "heihei.jpg",{type: "image/jpeg"});
    console.log(this.croppedFileObject,'this.croppedFileObject');
  }


}

Ionic(Angular)でアコーディオンを実装する方法

Ionic(Angular)でアコーディオンを実装する方法です。

ポイント

  • Componentを自作して、それを読み込むようにします
  • これによりいろいろな場所で使い回しができるようになります

結果

こんな感じでクリックするとカードが広がるようになります。

環境

Ionic 4, 5, 6で動作検証しています。

流れ

  1. コンポーネントを作成し、HTMLとSCSSを記載します
  2. 読み込みたい場所で作成したコンポーネントをmoduleに登録します
  3. 使いたい場所のHTMLでapp-expandableタグで呼び出します

ソースコード

GItHubに上げました。

https://github.com/NP-Systems/Ionic_tips/tree/id1131-createAccordion/frontend

参考文献

https://www.joshmorony.com/creating-an-accordion-list-in-ionic/

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

自作コンポーネントの作成

下記のコマンドで自作コンポーネントを作成します。

% ionic g component components/Expandable

app.module.tsと同じ階層にcomponentsフォルダができて新しいコンポーネントが生成されます。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── components
│   └── expandable
│       ├── expandable.component.html
│       ├── expandable.component.scss
│       ├── expandable.component.spec.ts
│       └── expandable.component.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

tab1.module.tsでの読み込み

今回はtab1ページに埋め込むことにするので、tab1.module.tsに下記のように登録します。

import { IonicModule } from '@ionic/angular';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab1Page } from './tab1.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
import { ExpandableComponent } from "../components/expandable/expandable.component";

import { Tab1PageRoutingModule } from './tab1-routing.module';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    Tab1PageRoutingModule
  ],
  declarations: [Tab1Page,ExpandableComponent]
})
export class Tab1PageModule {}

expandable.component.htmlの変更

 src/components/expandable/expandable.component.htmlのファイルを下記のように変更します。

<p>
  expandable works!
</p>

<div #expandWrapper class='expand-wrapper' [class.collapsed]="!expanded">
    <ng-content></ng-content>
</div>

expandable.component.scssの変更

.expand-wrapper {
  transition: max-height 0.4s ease-in-out;
  overflow: hidden;
  height: auto;
}

.collapsed {
  max-height: 0 !important;
}

expandable.component.tsの変更

Before

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-expandable',
  templateUrl: './expandable.component.html',
  styleUrls: ['./expandable.component.scss'],
})
export class ExpandableComponent implements OnInit {

  constructor() { }

  ngOnInit() {}

}

After

import { Component, AfterViewInit, Input, ViewChild,  ElementRef, Renderer2 } from "@angular/core";

@Component({
  selector: "app-expandable",
  templateUrl: "./expandable.component.html",
  styleUrls: ["./expandable.component.scss"]
})
export class ExpandableComponent implements AfterViewInit {
  @ViewChild("expandWrapper", { read: ElementRef }) expandWrapper: ElementRef;
  @Input("expanded") expanded: boolean = false;
  @Input("expandHeight") expandHeight: string = "150px";

  constructor(public renderer: Renderer2) {}

  ngAfterViewInit() {
    this.renderer.setStyle(this.expandWrapper.nativeElement, "max-height", this.expandHeight);
  }
}

tab1.page.htmlの変更

Before

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <app-explore-container name="Tab 1 page"></app-explore-container>
</ion-content>

After

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-card (click)="expandItem(item)" *ngFor="let item of items">
    <ion-card-header>
      <ion-card-title>隣のトトロ</ion-card-title>
    </ion-card-header>

    <ion-card-content>
      <app-expandable expandHeight="100px" [expanded]="item.expanded">
        <p>
          とっとろ、と、とーろ
        </p>
        <p>
          とっとろ、と、とーろ
        </p>
      </app-expandable>
    </ion-card-content>
  </ion-card>
</ion-content>

tab1.page.tsの変更

Before

import { Component } from '@angular/core';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {

  constructor() {}

}

After

import { Component } from '@angular/core';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  public items: any = [];

  constructor() {
    this.items = [
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false }
    ];
  }

  expandItem(item): void {
    if (item.expanded) {
      item.expanded = false;
    } else {
      this.items.map(listItem => {
        if (item == listItem) {
          listItem.expanded = !listItem.expanded;
        } else {
          listItem.expanded = false;
        }
        return listItem;
      });
    }
  }
}

FileZillaでローカルのファイルにアクセスできない

FileZillaで「このディレクトリを表示する権限がありません」という表示が出てローカルのファイルにアクセスできないときは、

左上のリンゴのマークから「システム環境設定」から「セキュリティとプライバシー」を選択し、「ファイルとフォルダ」からFileZillaのアクセスできる場所を設定します。

自社内アプリを開発する私がIonicを気に入っている理由

Ionicとはなにか

会社の中で使う業務アプリをよくlonic(Angular)で作っています。ここでは、自社内アプリを開発する私がIonicを気に入っている理由を解説します。

一応Ionicについて説明しておくと、

  • HTMLやJavascript(TypeScript)系のWeb技術を使ってAndroid, iOSのスマホアプリやWindows,Mac向けのデスクトップアプリを作るフレームワーク
  • 「Web Components」というWeb標準の技術が採用されており、もっとも推奨されているのはAngularですが、ReactやVueからも利用可能

結論

先に結論書くとIonicを使う理由は

  • UI構築が楽
  • ページ遷移時の動作が良い
  • バンドルサイズが小さい
  • Capacitorと連携してNativeの機能にアクセスできる

からです。

UI構築が楽

自社内アプリなので、デザインは凝らなくていいんですよね。ださくなければいいです。以前Cordovaでスマホアプリを開発していましたのですが、さすがにHTMLとJavaScriptで記述したWebページがそのままAndroid, iOSで動作するとダサいです。

そ決してCordovaが悪いわけではないのですが、HTMLとJavaScriptで記述したWebページがそのままアプリになる以上、どうしてもダサいです。例えばボタン一つとってもbootstrapを使えばWebでは綺麗に見えるのにアプリで見るとなんか違う感が拭えなかったです。Monacaのonsen.uiというものでNativeの見た目を再現できるということでしたが、個人的な感想としてMonacaは嫌いでした。

  • ビルドがあまりにも遅すぎてPDCAが回せない
  • 有料でも作成できるプロジェクト数が不便を感じるほど限られている
  • Monacaという名称やonsen.uiという名前がときめかない
  • Monacaで一度作ると、Monaca上でしかメンテナンスできなくなる

というデメリットを感じ使用を中止しました。特に最後の「Monacaで一度作ると、Monaca上でしかメンテナンスできなくなる」というのは強烈で、有料プランでエンパワーメントしてもらえると思っていたのに、実際は囲い込まれているということで非常に残念に思いました。

その点、IonicはiOS/Android別の美しいUIを提供してくれます。IonicがAngularやReactから使えることからわかるように、Ionicの本質はUIコンポーネントのライブラリです。あるコンポーネント(例えばボタン)を設置したとしましょう。そうすると、iOSで表示した場合はAppleのHuman Interface Guidelinesに基づいたデザイン、Androidで表示した場合はマテリアルデザインと自動的に表示が切り替わります。これは実際に書いていて最高にウキウキするIonicの素晴らしいところです。

また、こちらの記事(Cordovaをdisる人類全員に読んでほしい「Cordovaつらいを考える」)に記載されているように、60fpsでの表示で滑らかに動くことや、無限スクロールが可能なことも拡張性を保証する大事なポイントだと考えています。

ページ遷移時の動作が良い

最近、スマホ画面の下にTabがあるアプリケーションが多くあります。Cordovaでも当然実装できるのですが、音楽を入れた時にページが切り替わるたび音楽が初めから再生されるという非常に残念な思いをしたことがあります。

その点、IonicはTabを切り替えてもバックグラウンド音楽が途切れない。。Nativeで開発しているわけではないのに、Nativeで違うことで悩まされないというのは地味ですが大きいと考えています。

この辺を非線形ナビゲーション」と「ナビゲーションスタック」というらしいですが、Ionicのドキュメントからコアコンセプトのページがなくなっていたので詳細をしらべることができませんでした。

バンドルサイズが小さい

特にAndroidでは顕著ですが、アプリケーションのバンドルサイズは非常に重要視されています。Androidアプリを開発されている方はご存知かもしれませんが、近年、Androidアプリの拡張子は.apkではないのです。デベロッパーはaboというプレビルドしたような形でアップロードしておいて、ユーザーがインストールする際、そのデバイスに最適化したapkをGoogleが生成してダウンロードします。日本のように通信環境が整っていない国も多いなかで、バンドルサイズを小さくすることがいかに重要視されているかわかると思います。

その点、lonicは、「Stencil」というWeb Components開発ライブラリによって作られており、バンドルサイズはとても小さくなります。

また、LazyLoadingという最初に全てを読み込むわけではなく、必要なものはあとで読み込む機能を実装しているので、軽量に高速で動作します。LazyLoadingはAngularにも実装されているので迷うかもしれませんが、こちらに英語ですがIonic X AngularにおけるLazy Loadingの実装方法について記事がまとまっています。このように公式が充実しているのもIonicのいいところです。https://ionicframework.com/blog/how-to-lazy-load-in-ionic-angular/

なお、Angularについて解説した記事はこちらですので、Angularについて知りたい方はどうぞ。

https://np-sys.com/general/%e5%80%8b%e4%ba%ba%e3%83%bb%e5%b0%91%e4%ba%ba%e6%95%b0%e3%81%ae%e3%82%b7%e3%82%b9%e3%83%86%e3%83%a0%e9%96%8b%e7%99%ba%e3%81%ab%e3%81%afangular%e3%81%8c%e3%82%aa%e3%82%b9%e3%82%b9%e3%83%a1/

Nativeの機能にアクセスできる

Ionicは(Cordovaもそうですが)Webページをアプリにできるだけではなく、そこにNativeのカメラ、位置情報などのAPIにアクセスできることは大きな魅力です。Ionicにはさまざまなプラグインがありますので、これを利用することでWeb技術を使いながら、それを超えたアプリケーションを作成することが可能です。

Ionicプラグインの導入方法

総括、その他

 Ionicについて簡単に解説しました。拡張性が担保されたフレームワークでプロジェクトをひとつ作り、それをそれぞれの最高のUIでクロスプラットフォームに出力できる点が一番気に入っています。

AngularでIonicを用いてtab barのデザインをカスタマイズする

Angualrとクロスプラットフォームアプリ開発用のフレームワークであるIonicを組み合わせて、Tab barをカスタマイズする方法を解説します。

ポイント

  • Tab barを備えたプロジェクトはIonicが提供しているテンプレートを使用
  • 比較的コードは単純で実装は容易

Github

Githubにコードあげています。

https://github.com/NP-Systems/Ionic_tips/tree/id-1113-customize-tab-bar

実装手順

Ionicが提供しているTab barを備えたテンプレートをダウンロードします。

% ionic start

Pick a framework! 😁

Please select the JavaScript framework to use for your new app. To bypass this
prompt next time, supply a value for the --type option.

? Framework: Angular

Every great app needs a name! 😍

Please enter the full name of your app. You can change this at any time. To
bypass this prompt next time, supply name, the first argument to ionic start.

? Project name: myApp

そうするとつらつら自動でダウンロードしてくれるので、完了後作成したフォルダへ移動します。

% cd myApp

フォルダ構成

tabsコンポーネント

tabs.page.htmlとtabs.page.scssの二つを変更します。

tabs.page.html

修正前

<ion-tabs>

  <ion-fab vertical="bottom" horizontal="center" translucent="true">
    <ion-fab-button (click)="goToPictures()">
      <ion-icon name="camera"></ion-icon>
    </ion-fab-button>
  </ion-fab>

  <ion-tab-bar slot="bottom" class="ion-no-border">


    <ion-tab-button tab="tab-conversations" class="comments">
      <ion-icon name="triangle"></ion-icon>
      <ion-label>Tab 1</ion-label>
      <ion-badge >Hi</ion-badge>
    </ion-tab-button>

    <svg height="50" viewBox="0 0 100 50" width="100" xmlns="http://www.w3.org/2000/svg">
      <path d="M100 0v50H0V0c.543 27.153 22.72 49 50 49S99.457 27.153 99.99 0h.01z" fill="red" fill-rule="evenodd">
      </path>
    </svg>

    <ion-tab-button tab="tab-notifications" class="notifs">
      <ion-icon name="notifications"></ion-icon>
      <ion-badge *ngIf="new_activities">{{new_activities}}</ion-badge>
    </ion-tab-button>


  </ion-tab-bar>
</ion-tabs>

修正後

<ion-tabs>

  <ion-fab vertical="bottom" horizontal="center" translucent="true">
    <ion-fab-button (click)="yourCustomFunction()">
      <ion-icon name="camera"></ion-icon>
    </ion-fab-button>
  </ion-fab>

  <ion-tab-bar slot="bottom" class="ion-no-border">

    <ion-tab-button tab="tab-conversations" class="comments">
      <ion-icon name="triangle"></ion-icon>
      <ion-label>Tab 1</ion-label>
      <ion-badge >Hi</ion-badge>
    </ion-tab-button>

    <svg height="50" viewBox="0 0 100 50" width="100" xmlns="http://www.w3.org/2000/svg">
      <path d="M100 0v50H0V0c.543 27.153 22.72 49 50 49S99.457 27.153 99.99 0h.01z" fill="red" fill-rule="evenodd">
      </path>
    </svg>

    <ion-tab-button tab="tab-notifications" class="notifs">
      <ion-icon name="notifications"></ion-icon>
    </ion-tab-button>


  </ion-tab-bar>
</ion-tabs>

tabs.page.scss

最初は何も書いてありませんが、下記のように変更します。


ion-tabs{
	ion-fab {
		margin-bottom: env(safe-area-inset-bottom); /* fix notch ios*/
		ion-fab-button {
			--box-shadow: none;
		}
	}
	ion-tab-bar {
		--border: 0;
		--background: transparent;
		position: absolute;
		bottom: 0;
		left:0; right: 0;
		width: 100%;
		&:after{
			content: " ";
			width: 100%;
			bottom: 0;
			background: var(--ion-color-light);
			height: env(safe-area-inset-bottom);
			position: absolute;
		}
		ion-tab-button {
			--background: var(--ion-color-light);
		}
		ion-tab-button.comments {
			margin-right: 0px;
			border-top-right-radius: 18px;
		}
		ion-tab-button.notifs {
			margin-left: 0px;
			border-top-left-radius: 18px;
		}
		svg {
			width: 72px;
			margin-top: 19px;
			path{
				fill:  var(--ion-color-light);
			}
		}
	}
}

結果

アイコンにメッセージを追加している部分はこのように書いています。

    <ion-tab-button tab="tab-conversations" class="comments">
      <ion-icon name="triangle"></ion-icon>
      <ion-label>Tab 1</ion-label>
      <ion-badge >Hi</ion-badge>
    </ion-tab-button>

元ネタだとちゃんと条件分岐をしていました。

<ion-badge *ngIf="new_message">{{new_message}}</ion-badge>

参考文献

https://forum.ionicframework.com/t/ionic-5-awesome-tab-bar-with-curve/186579

新しいMacでXcodeの証明書を引き継ぐ

新しいMacでXcodeでアプリ開発をするための設定についてまとめます。

  • 以前のMacは必要なく、新しいMacだけで完結します

まず、ブランクのアプリを作ってみることから始めます。そうすると、Signing&Capabilityの部分でTeamを選べない問題に直面します。

そこで、これをまず解決する必要がりますので、Add an Accountというボタンを押して、Apple IDでサインインします。

そうすると、一応ビルドはできるようになります。しかし、そのままだとCodeSignの「キーチェーンログインのパスワードを入力してください」でパスワードを繰り返し求められて先に進みませんでした。

そこで、https://developer.apple.comからApple Developerにサインインして証明書をダウンロードしました。

ここで、該当するProfileをダウンロードしてダブルクリックしたら、動作するようになりました。

こんなに簡単だったっけって感じです。。あとで落とし穴がないか怖いです。

MacのXcodeのデベロッパーライセンスを更新するときにやること

Xcodeのデベロッパーライセンスを更新するときにやることをまとめておく。

まず、Appleからライセンス切れる旨を伝えるメールが来るので購入する。

次に、Apple Developerにログインして、Certificates&Identifiers & ProfilesのDevicesを選ぶ。

Continueを選ぶ。下記のようにデバイスリストを最新にしてくださいというメッセージが表示される。

Now that you’ve started a new membership year, make sure your device list is up to date. Once you complete this process, new devices can be added.

Select the devices you’d like to keep for this membership year and deselect the devices you’d like to remove. Only your currently enabled devices are shown and all previously disabled devices will be removed automatically. To keep a previously disabled device, visit the All Devices page, enable the devices you’d like to keep, and return to this page.

Continueを押下

この画面の「I acknowledge that any devices I disable during this membership year will continue to count towards my total registered devices.」にチェックを入れてResetする。

私は以上だけでした。

Macの環境設定の備忘録

データ分析とWebアプリ開発をしているエンジニアのMacの設定方法です。Mac book Pro 2019,2020でそれぞれBig SurとCataliaにて設定したものを備忘録として書いたものです。

  • CapsLockでの英語/日本語の切り替え
  • スクロール方向の切り替え
  • スクリーンショットの拡張子変更
  • aliasの設定
  • 一般ソフト(Chrome,Gimp,FileZilla,Atom,VScode)のインストール
  • Tableau Publicのインストール
  • node.jsのインストール
  • Ionicのインストール
  • Xcodeのインストール
  • CocoaPodのインストール
  • Pythonのインストール
  • GitHubのssh設定

CapsLockでの英語/日本語の切り替え

CapsLockで英語/日本語の切り替えができるようにします。

  1. デスクトップの左上にあるAppleのマークからシステム環境設定を選び
  2. 「キーボード」を選択し
  3. 上に並んでいるタブのうち、左から4番目の「入力ソース」を選択し
  4. ウィンドウの下にある「CapsLockでABC入力と切り替える」のチャックを打つ

スクロール方向の切り替え

タッチパッドの指の動かし方と画面の遷移方向を設定します。

  1. デスクトップの左上にあるAppleのマークからシステム環境設定を選び
  2. 「トラックパッド」を選択し
  3. 上に並んでいるタブのうち、左から2番目の「スクロールとズーム」を選び
  4. 「スクロールの方向:ナチュラル」のチェックを外す

スクリーンショットの拡張子変更

デフォルトのpngだと容量が大きすぎるので、jpgに変更して容量が小さくなるようにします。

defaults write com.apple.screencapture type 拡張子

で拡張子を指定すれば変更できるので、

defaults write com.apple.screencapture type jpg
killall SystemUIServer
exit

とします。jpgをpng,pdf,tiffなど任意のものに変更可能です。

aliasの設定

terminalにaliasを設定して、任意のコマンドを自分好みのコマンドで打てるようにします。

使用シェルがbashなら~/に.bash_profileと.bashrcを作成し、zshなら.zshrcと.zshprofileを作成し設定します。ログイン時に実行されるのは.bash_profileと.zshprofileで、.bashrcと.zshrchはターミナル起動時にいつも実行されます。

私はBig Surだったので、.zshrcに

alias o='open ./'
alias p='pwd'
alias l='ls'
alias n='cd /Users/masaya/Desktop;open /Users/masaya/Desktop'

と書きました。設定を反映するために再起動をするか

source ~/.zshrc

します。

一般ソフト(Chrome,Gimp,FileZilla,Atom,VScode)のインストール

いずれも公式サイトにアクセスして、ファイルをダウンロードすれば特に設定なしで使えるものです。

項目
Chrome普通にインストールします。dmgファイルをインストールしてから、ダブルクリックで起動して画面の操作にしたがってアイコンを移動するだけです。インストールあと、dmgファイルはゴミ箱に移動です、
VScodehttps://azure.microsoft.com/ja-jp/products/visual-studio-code/からダウンロード
Atomhttps://atom.io/からダウンロード

その他、Vivaldi,Firefoxなども同じようにインストールします。

Tableau Public

データ可視化用にTableau Publicを入れます。これも公式からダウンロードしてぽちぽちするだけです。

FileZillaのインストール

Macで無料で使えるFTPソフトです。

node.jsのインストール

Ionicのインストール

AndroidStudioのインストール

https://developer.android.com/studioからインストールします。

Xcodeのインストール

AppStoreから最新をインストールします。11GB程度と非常に重いので注意。

証明書関係が面倒ですが、以前のメモには下記のように書いてあります。

めちゃくちゃ苦労した。全て記録に残せたわけではないが、少なくとも以前のMacは必要なかった。AppleDeveloperサイトに行って、ログインする。Certificates, Identifiers & ProfilesからPlatformのカラムがALLのもののうち、DevelopmentとDeistributionを選んで、ダウンロードしてキーチェーンに登録する。これを新しい方でダブルクリックして登録する。それと、app developer idでパソコンでログインして、新しいMacを登録する。そうするとチームが選べるようになる。基本的には以上の二つでOK.ってこんだけのことだったっけ?

CocoaPodのインストール

https://cocoapods.org/に基づいて

sudo gem install cocoapods

でインストールします。

Pythonのインストール

以前はAnacondaを使っていましたが、有料化したので使うのはやめました。有料のAnacondaをインストールしたいなら、公式からダウンロードしてぽちぽちすればいけます。

https://www.anaconda.com/products/individual

GitHubのssh設定

Github用に .sshをホームディレクトリに配置して設定します。

ホーム画面で.sshディレクトリを作成します。

% mkdir ./.ssh
masaya@local-host ~ % cd .ssh 

鍵を作成します。

%  ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/masaya/.ssh/id_rsa): /Users/masaya/.ssh/id_rsa
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/masaya/.ssh/id_rsa.
Your public key has been saved in /Users/masaya/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:chw/VNuyHQ9D0DLooAVRfu9KtEK4TXoOVh36Gkw2c9U masaya@local-host
The key's randomart image is:
+---[RSA 3072]----+
|      o+.  .oo.  |
|       .o ..o=.  |
|       ooo+ +oE  |
|      .o B.+ + = |
|      o S * o . .|
|       & * +     |
|      = * + .    |
|     . + = .     |
|        o .      |
+----[SHA256]-----+

権限を読み取り専用に変更して

 chmod 600 id_rsa

情報をコピーして

% pbcopy < ~/.ssh/id_rsa.pub

GitHubに行って、アイコンをクリックしてから「settings」 > 「SSH and GPG keys」 > 「New SSH Key」と進んで、情報を貼り付ける

あとは下記で疎通できることを確認します。

% ssh -T git@github.com
The authenticity of host 'github.com (13.**.40.48)' can't be established.
RSA key fingerprint is SHA256:********pJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'github.com,13.***.40.48' (RSA) to the list of known hosts.
Hi N****! You've successfully authenticated, but GitHub does not provide shell access.

https://qiita.com/unsoluble_sugar/items/14bea376d8e6fce82eb3

設定でよく使うコマンド

macOSでデスクトップ表示 :「Fn」+「F11」

隠しファイルの表示;command shift .

Macにionicをインストールしてサンプルプロジェクトを作成する

クロスプラットフォームアプリケーションのフレームワークであるIonicをMacにインストールする方法について紹介します。

まず、node.jsをインストールする必要があります。インストールしていない方はこちらに最も簡単なインストール方法を記載していますのでご覧ください。

インストール方法

npm経由でのインストールとなりますので、sudo npm install -g @ionic/cliコマンドを打ちます。

% sudo npm install -g @ionic/cli
Password:
⸨   ░░░░░░░░░░░░░░░⸩ ⠇ loadDep:yallist: sill resolveWithNewModule smart-buffer@

ionic -vでバージョンが表示されたらインストール完了です。

% ionic -v
6.12.3

アプリを作ってみる

ionic startコマンドで簡単なアプリを作ってみます。

 % ionic start

Pick a framework! 😁

Please select the JavaScript framework to use for your new app. To bypass this
prompt next time, supply a value for the --type option.

? Framework: (Use arrow keys)
❯ Angular | https://angular.io 
  React   | https://reactjs.org 

今回はAngularを選びます。プロジェクト名やCapacitorがいるかなど聞かれますので、入力します。CapacitorはIonicのプロジェクトをAndroid,iOS,デスクトップアプリとして動作させる環境(ランタイム)です。Web用なら入りませんが、今回は入れておきます。

? Project name: my-first-app

Let's pick the perfect starter template! 💪

Starter templates are ready-to-go Ionic apps that come packed with everything
you need to build your app. To bypass this prompt next time, supply template,
the second argument to ionic start.

? Starter template: tabs
✔ Preparing directory ./my-first-app in 1.93ms
✔ Downloading and extracting tabs starter in 998.19ms
? Integrate your new app with Capacitor to target native iOS and Android? (y/N) 
y

ダウンロードしたら、プロジェクトへ移動します。

% cd my-first-app/

ionic serveコマンドでビルドしてみます。

% ionic serve    
> ng run app:serve --host=localhost --port=8100

アプリケーションが立ち上がったら完了です。

参考文献

https://ionicframework.com/docs/intro/cli

MacにFileZillaをインストールする

Macで無料で使えるFTPソフトであるFIleZillaをインストールする方法について解説します。

まず、公式サイトからFileZillaの圧縮ファイルをダウンロードします。

https://filezilla-project.org/download.php?type=client#close

FileZilla_3.51.0_macosx-x86.app.tar.bz2というファイルがダウンロードされますので、ダウンロード先でダブルクリックして解凍します。

下記のようなアイコンのついたものが作成されます。

これがアプリケーション本体なので、このファイルをコピペして、アプリケーションフォルダへ移動して完了です。

普通にダブルクリックしたら起動するので、あとはサーバ情報を入力して接続します。

Macでnode.jsをインストールする

日本ではhomebrewでインストールする方法ばかりが紹介されていますが、海外ではほとんど情報がなくマイナーな方法です。node.jsの公式からダウンロードしてインストールする方法が多く紹介されていますが、こちらの方が簡単なので今回はその方法を紹介します。

installerを公式から落とす

https://nodejs.org/en/にアクセスしてinstallerをダウンロードします。

LTSと書いてあるものがサポートの長い安定版です。

ダウンロードしたら、ファイルをクリックして起動させます。

途中、インストール先が表示されるのでメモっておきます。あとは画面の指示に従って進みます。完了したらインストーラはゴミ箱に移動していいですかと聞かれます。これが表示されたらもうこれだけでインストール完了です。

動作確認

ターミナルで下記のように打ち込んでバージョンが表示されたらOKです。

% node -v
v14.15.1

もしバージョンが表示されない場合、下記のコマンドを打って現在のパスを通している場所を表示させ、インストーラで保存先に指定していた場所が入っているかどうかを確かめます。

% echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

簡単なプログラムを書いてみる

node.jsがちゃんと動作しているか簡単なコマンドで調べてみます。

% node
Welcome to Node.js v14.15.1.
Type ".help" for more information.
> console.log('hello node.js')
hello node.js
undefined

ちゃんと動作していることが確認できたら、control + cを2回打って終了します。

【oandapyV20】OandaAPIでオーダブック、ポジションブックを取得する

この記事ではoandapyV20を使ってオーダブック、ポジションブックを取得する方法について記載します。

oandapyV20では、オーダーブックをとるためにはoandapyV20.endpoints.instrumentsのInstrumentsOrderBookというクラスで/v3/instruments/{instrument}/orderBookをGet通信で叩きます。

ポジションブックの場合は、oandapyV20.endpoints.instrumentsのInstrumentsPositionBookというクラスで/v3/instruments/{instrument}/positionBookをGet通信で叩きます。

いずれの場合もparamsで指定できるパラメータはinstrumentとtimeだけです。instrumentは必須引数ですが、timeは指定しない場合は最新が返されます。timeだUNIXTIMEを文字列型で渡します。最新を取りたい場合は、paramsは{}でいいと思います。

オーダーブックもポジションブックもほとんど同じなので、今回はオーダブックで説明したいと思います。

ドル円の最新のオーダーブックを取得するコードはこちらです。ポジションブックを見たい場合はInstrumentsPositionBookを使ってください。

# -*- coding: utf-8 -*-
import json
from oandapyV20 import API
from oandapyV20.endpoints.pricing import PricingInfo
from oandapyV20.exceptions import V20Error
import oandapyV20.endpoints.instruments as instruments
import datetime
import pandas as pd

def main():
    account_id="XXX-XXX-XXXXXXX-XXX"
    access_token = "***************************************************************"
    api = API(access_token=access_token, environment="practice")#or live

    params ={}
    r = instruments.InstrumentsOrderBook(instrument="EUR_USD",
                                         params=params)
    api.request(r)
    data = (r.response)
    print(data)

if __name__ == "__main__":
    main()

返り値の中身を詳細に見てみます。所期のデータはdata[‘orderBook’][‘buckets’]にリストで返ってきています。

#まずはなんの型が帰ってきているかきます。
(Pdb) type(data)
<class 'dict'>
(Pdb) data.keys()
dict_keys(['orderBook'])

(Pdb) type(data['orderBook'])
<class 'dict'>
(Pdb) data['orderBook'].keys()
dict_keys(['instrument', 'time', 'unixTime', 'price', 'bucketWidth', 'buckets'])

(Pdb) data['orderBook']['instrument']
'EUR_USD'
(Pdb) data['orderBook']['time']
'2020-12-07T12:00:00Z'
(Pdb) data['orderBook']['price']
'1.21131'
(Pdb) data['orderBook']['bucketWidth']
'0.00050'

(Pdb) type(data['orderBook']['buckets'])
<class 'list'>
(Pdb) len(data['orderBook']['buckets'])
5548
(Pdb) data['orderBook']['buckets'][0]
{'price': '0.00000', 'longCountPercent': '0.4861', 'shortCountPercent': '0.3107'}
(Pdb) data['orderBook']['buckets'][1000]
{'price': '1.12050', 'longCountPercent': '0.0351', 'shortCountPercent': '0.0200'}
(Pdb) data['orderBook']['buckets'][3000]
{'price': '7.59750', 'longCountPercent': '0.0000', 'shortCountPercent': '0.0150'}
(Pdb) data['orderBook']['buckets'][5000]
{'price': '107.77800', 'longCountPercent': '0.0025', 'shortCountPercent': '0.0000'}

綺麗な最終結果だけが欲しいならdata[‘orderBook’][‘buckets’]をPandasに変換します。

(Pdb) pd.DataFrame(data['orderBook']['buckets'])
                 price longCountPercent shortCountPercent
0              0.00000           0.4861            0.3107
1              0.00050           0.1428            0.0802
2              0.00100           0.2956            0.0451
3              0.00150           0.0576            0.0251
4              0.00200           0.0401            0.0225
...                ...              ...               ...
5543    34887878.00000           0.0025            0.0000
5544   100000000.00000           0.0025            0.0000
5545  1000000000.00000           0.0000            0.0050
5546  7000000000.00000           0.0000            0.0025
5547  9999999999.00000           0.0025            0.0000

[5548 rows x 3 columns]

参考文献

https://developer.oanda.com/rest-live-v20/instrument-ep/

https://developer.oanda.com/rest-live-v20/instrument-ep/

ローソクを取得したい場合はこちらをご覧ください

https://np-sys.com/hacks-with-it/oanda%e3%81%a7%e3%81%ae%e7%82%ba%e6%9b%bf%e3%83%87%e3%83%bc%e3%82%bf%e3%82%92%e5%8f%96%e5%be%97/

現在の価格をリアムタイムに欲しい場合はこちらをご覧ください

https://np-sys.com/hacks-with-it/%e3%80%90oandapyv20%e3%80%91oandaapi%e3%81%a7%e4%be%a1%e6%a0%bc%e3%82%92streaming%e9%85%8d%e4%bf%a1%e3%81%a7%e5%8f%96%e5%be%97%e3%81%99%e3%82%8b/

【oandapyV20】OandaAPIで価格をStreaming配信で取得する

この記事ではoandapyV20を使ってStreaming配信でリアルタイムに価格を取得する方法について記載します。

Streaming配信は非常に便利であるものの、最大で250msごとのデータしか提供されませんので急激な値動きまで把握できるわけではない事に注意が必要です。公式では「1秒間に4回のウィンドウを作成し、それぞれのウィンドウの最後で有効な値を返す」という記載があります。詳細は記載されていないものの、250ms程度のデータであること、アカウントごとにデータを生成するので、すべての人に同じデータが配信されるわけではない事に留意が必要です。

ストリーミング配信は/v3/accounts/{accountID}/pricing/streamにGet通信でアクセスする事で提供されます。

oandapyV20を使った基本のコードはこちらです(ドル円の価格を取得)。

# -*- coding: utf-8 -*-
import json
from oandapyV20 import API
from oandapyV20.endpoints.pricing import PricingInfo
from oandapyV20.exceptions import V20Error
import oandapyV20.endpoints.pricing as pricing
import oandapyV20.endpoints.instruments as instruments
import datetime
import pandas

def main():
    account_id="XXX-XXX-XXXXXXX-XXX"
    access_token = "******************************************************************"
    api = API(access_token=access_token, environment="practice")#or live

    params ={
            "instruments": "USD_JPY,EUR_JPY"
            }
    r = pricing.PricingStream(accountID=account_id, params=params)
    rv = api.request(r)
    maxrecs = 100
    for ticks in rv:
        print(json.dumps(ticks, indent=4),",")
        if maxrecs == 0:
            r.terminate("maxrecs records received")

if __name__ == "__main__":
    main()

これを実行すると、価格のリアルタイムデータが取得でします。ticksという変数にはこのような値が入ります。

{
    "type": "PRICE",
    "time": "2020-12-04T21:59:55.602697089Z",
    "bids": [
        {
            "price": "104.185",
            "liquidity": 250000
        }
    ],
    "asks": [
        {
            "price": "104.191",
            "liquidity": 250000
        }
    ],
    "closeoutBid": "104.177",
    "closeoutAsk": "104.199",
    "status": "non-tradeable",
    "tradeable": false,
    "instrument": "USD_JPY"
} ,

基本的には通貨ペア(Instruments)しかカスタマイズしないと思います。通貨ペアを変更したいときはparamsの値を変えてください。2つ以上のペアを同時にデータ取得したいときは、paramsにそれぞれのペアをカンマで区切って指定すればOKです。例えば、2つを指定したいときはこのように指定します。

    params ={
            "instruments": "USD_JPY,EUR_JPY"
            }

そうすると下記のデータが返ってきます。

{
    "type": "PRICE",
    "time": "2020-12-04T21:59:53.324563034Z",
    "bids": [
        {
            "price": "126.254",
            "liquidity": 250000
        }
    ],
    "asks": [
        {
            "price": "126.320",
            "liquidity": 250000
        }
    ],
    "closeoutBid": "126.238",
    "closeoutAsk": "126.336",
    "status": "non-tradeable",
    "tradeable": false,
    "instrument": "EUR_JPY"
} ,
{
    "type": "PRICE",
    "time": "2020-12-04T21:59:55.602697089Z",
    "bids": [
        {
            "price": "104.185",
            "liquidity": 250000
        }
    ],
    "asks": [
        {
            "price": "104.191",
            "liquidity": 250000
        }
    ],
    "closeoutBid": "104.177",
    "closeoutAsk": "104.199",
    "status": "non-tradeable",
    "tradeable": false,
    "instrument": "USD_JPY"
} ,
{
    "type": "HEARTBEAT",
    "time": "2020-12-06T11:28:32.333476447Z"
} ,

リアルタイムでデータが取得できるのはとても便利です。

通貨ペアの指定について

円関連

“USD_JPY”,’EUR_JPY’,’GBP_JPY’,’AUD_JPY’,’NZD_JPY’,’CAD_JPY’,’CHF_JPY’,

ドル関連

“EUR_USD”,’GBP_USD’,’AUD_USD’,’NZD_USD’,’USD_CAD’,’USD_CHF’,’USD_CNH’,

ユーロ関連

‘EUR_GBP’,’EUR_AUD’,’EUR_NZD’,’EUR_CHF’,’GBP_AUD’

参考文献

https://developer.oanda.com/rest-live-v20/pricing-ep/

https://developer.oanda.com/rest-live-v20/pricing-ep/

ローソクチャートを取得したいときはこちらの記事をご覧ください。

https://np-sys.com/hacks-with-it/oanda%e3%81%a7%e3%81%ae%e7%82%ba%e6%9b%bf%e3%83%87%e3%83%bc%e3%82%bf%e3%82%92%e5%8f%96%e5%be%97/

GCP(Google Cloud Platform)の特徴を解説。主要サービスの紹介も。

GCP(Google Cloud Platform)は、GoogleがGmail,Youtubeなど一般向けに開発しているサービスのバックエンドで使われているクラウド基盤を外部に公開したものです。GoogleはGCPですべてのサービスを運用しており、同じ環境を安価に利用することができます

「Datacenter as a Computer」という言葉に集約されるように、GCPではGoogleの世界中にあるコンピュータをまとめて一つのコンピュータのように扱えるようにするというコンセプトがあり、エンジニアに取って使いやすいサービスとなっています。

歴史

GCPは2008年に公開されたGoogleAppEngineが元になっており、IaaSとして始まったAWSとは異なり当初はPaaSとして導入されました。その後、2011年にGoogleCloudSQL ,GoogleCloudStoragが導入され、2013年にIaaSであるGCEが導入され現在の主要サービスがそろいました。

東京リージョンが開設されたのは2016年であり、まだまだ日本では導入後まもないサービスです

基本概念

GCPではユーザー、課金アカウント、プロジェクトという3つの基本概念があります。ユーザーとは、Googleのサービスを利用するときに利用しているIDのことです。GCP専用のアカウントは存在せず、GoogleアカウントにGCPの情報を紐づけることとなります。プロジェクトとは、GCPの中で機能やサービスを束ねるためのものです。プロジェクト内では各サービスの連携は容易ですが、プロジェクトを跨ぐことは基本的にはできません。さらにプロジェクトには1つの課金アカウントが設定されている必要があり、こちらに対して請求が来る事になります。

コンポーネントについて

GCPには様々なサービスがありますが、代表的なものを下記にまとめました。

分類名称機能
コンピューティングComputeEngineクラウド上で仮想マシンを立ち上げられます。OS以上のレイヤーを自由に設定できる一方で、環境構築やメンテナンスを自分でやらなければならないため上級者向けです。
コンピューティングAppEngineアプリケーションを運用してくれるPaaSサービス。GUIのアプリのホスティングサービス。基本的にはリクエスト受け取り後、60秒以上の処理はできない。ローカルファイルへのアクセスは禁止されている。
コンピューティングCloudFunctions関数ベースでGUIを伴わないサービスについてはこちらで十分。関数プログラムをホスティングできるサーバレスなコンピューテング環境
分類名称機能
ストレージCloud Storageストレージサービス。データ保存だけではなく、HTMLや画像などの静的なデータを直接Webに公開することも可能。バケットと呼ばれるコンテナで管理される。ファイルやフォルダと言ったGCS上のデータはオブジェクトと呼ばれる。オブジェクト数に上限はないが、1オブジェクト5TBまで。
ストレージSQLMySQLなどのリレーショナルデータベース
ストレージFirestoreNoSQLサービス
ストレージBigtableスケーラブルなNoSQLサービス
ビッグデータBigQuery低コストなビッグデータ解析用サービス。SQLでデータ取得可能。読み出しに強いのがSQLとの違い。

その他、様々なAPIが提供されています。

特徴

AmazonやMicrosoftと比較したGoogleの特徴の一つは、インフラを自社で保有していることです。Googleでは、データセンタ、光ファイバ回線などを独自で設計し導入しています。世界規模のクローズトネットワークを保有している数少ない事業者がGoogleですが、これによりサーバ間のデータ転送は極めて高速で可用性に優れているという特徴があります。また、元々、世界中の個人に対してサービスを提供してきた背景から、スケーラビリティと大規模処理に非常に強く大量のデータを保有しているために機械学習などのモデル精度に優れています。日本ではAWSが最も勢いに乗っていると思いますが、独自のサービスやデータに強いというところで一定の存在感を持ったサービスであり続ける可能性が高いです。

サービスアカウントとメンバーについて

https://cloud.google.com/iam/docs/service-accounts?hl=ja

GCPではメンバーとサービスアカウントという概念があります。メンバーはプロジェクトに登録した個々のGoogleアカウント(ユーザーアカウント)とサービスアカウントであり、サービスアカウントはアプリケーションなどで使用される特別なアカウントです。リソースへのアクセスの際はサービスアカウントを介する必要があります。

重要なのは、サービスアカウントを作成するとメンバーにも登録されることです。メンバーを登録してもサービスアカウントは作成されないです。サービスアカウントの方が強いイメージです、

もし証明書を介してリソースにアクセスする際、下記の手順を踏みます。

  • サービスアカウントを作成する
  • そのサービスアカウントはメンバーとしても登録されているので、メンバーの画面から権限を編集
  • サービスアカウントの画面から証明書の発行

という流れになります。確証はありませんが、証明書の権限は発行された際のメ権限を反映しているような挙動になりました。そんなはずはないですが、でもそれで少し詰まりました。おそらく多少はタイムラグがあるということかもしれないです。

【oandapyV20】OandaAPIでローソクを取得

この記事ではoandapyV20を使ってローソクチャートを取得する方法について記載します。ローソクの長さ、取得データの種類、取得期間の指定方法なども含めて記載します。

oandapyV20では、PricingInfoというクラスで/v3/instruments/{instrument}/candlesをGet通信で叩いています。

ドル円のローソクチャートを取得基本のコードはこちらです。

# -*- coding: utf-8 -*-
import json
from oandapyV20 import API
from oandapyV20.endpoints.pricing import PricingInfo
from oandapyV20.exceptions import V20Error
import oandapyV20.endpoints.instruments as instruments
import datetime
import pandas

def main():
    account_id="XXX-XXX-XXXXXXX-XXX"
    access_token = "***************************************************************"
    api = API(access_token=access_token, environment="practice")#or live

    params = {"instruments": "USD_JPY"}
    pricing_info = PricingInfo(accountID=account_id, params=params)


    r = instruments.InstrumentsCandles(instrument="USD_JPY",
                                    params={
                                        "granularity": "S15", # ロウソク足の種類を選択
                                        "alignmentTimezone": "Japan", # タイムゾーン
                                        #"from":datetime.datetime.now()
                                    }
    )
    data = api.request(r)
    for candle in r.response["candles"]:
        print(candle)

if __name__ == "__main__":
    main()

データの返り値であるcandleの変数には下記の値が入ります

(Pdb) candle
{'complete': True, 'volume': 117597, 'time': '2020-11-05T08:00:00.000000000Z', 'mid': {'o': '104.314', 'h': '104.388', 'l': '103.360', 'c': '103.424'}}

ローソク足の長さを指定する

上記ではローソクの長さを15秒に指定しますが、最短で5秒、最大で1ヶ月まで変更できます。oandapyV20では、InstrumentsCandlesをインスタンス化する際のparamという変数で様々な取得条件を指定しますが、ローソク足の長さはgranularityというキーで指定します。これに対応する値は文字列型で下記で指定します。

    r = instruments.InstrumentsCandles(instrument="USD_JPY",
                                    params={
                                        "granularity": "S15", # ロウソク足の種類を選択
                                        "alignmentTimezone": "Japan", # タイムゾーン
                                        #"from":datetime.datetime.now()
                                    }
    )
引数間隔
S55秒間
S1010秒間
S1515秒間
S3030秒間
M11分間
M22分間
M33分間
M44分間
M55分間
M1010分間
M1515分間
M3030分間
H11時間
H22時間
H33時間
H44時間
H66時間
H88時間
H1212時間
D1日
W1週間
M1ヶ月

総データ取得期間の指定

総データ取得期間を指定する際は、同様にparamという辞書の中で指定します。この中でfromとtoというキーで時間を「文字列型」のUNIXTIMEで指定します。

r = instruments.InstrumentsCandles(
                        instrument="USD_JPY",
                        params={
                            "granularity": "D",
                            "alignmentTimezone": "Japan",
                            "from":str((datetime.datetime.now()-datetime.timedelta(days=30)).timestamp()),
                            "to":str((datetime.datetime.now()-datetime.timedelta(days=25)).timestamp())
                            }
                        )

キャンドルの本数の指定

データの期間を指定する代わりに、キャンドルの本数で総データ取得期間を指定することもできます。デフォルトは500本となります。キャンドルの本数で帰って来るデータの量を指定したい場合はcount引数を整数で指定します。なお、期間をfrom,toで指定した場合こちらは使用できません。countの最大は5000本です。

r = instruments.InstrumentsCandles(
instrument="USD_JPY",
params={
"granularity": "D",
"alignmentTimezone": "Japan",
"count":100}
)

ローソクの価格種を指定する

ask,bid,middleのいずれのローソクを取得するかについてはparamsでpriceキーで指定できます。“M” はmidpoint candles、 “B” はbid candles)、 “A” はask candlesとのことで、デフォルトは”M”となります。

midの場合

(Pdb) candle
{'complete': True, 'volume': 117597, 'time': '2020-11-05T08:00:00.000000000Z', 'mid': {'o': '104.314', 'h': '104.388', 'l': '103.360', 'c': '103.424'}}

bidの場合

(Pdb) candle
{'complete': True, 'volume': 117597, 'time': '2020-11-05T08:00:00.000000000Z', 'bid': {'o': '104.308', 'h': '104.382', 'l': '103.354', 'c': '103.418'}}

通貨を指定する

これはinstrument引数で指定します。通貨のペアを/ではなく_でつないで指定します。

例えば,よく使うものを列挙すると

ペアコード
ドル円USD_JPY
ドルユーロEUR_USD
ユーロ円EUR_JPY

https://www.oanda.jp/course/currencypair

参考文献

https://oanda-api-v20.readthedocs.io/en/latest/endpoints/instruments/instrumentlist.html

https://developer.oanda.com/rest-live-v20/instrument-ep/

リアルタイムでデータを取得したい場合はこちらの記事をご覧ください。

https://np-sys.com/hacks-with-it/%e3%80%90oandapyv20%e3%80%91oandaapi%e3%81%a7%e4%be%a1%e6%a0%bc%e3%82%92streaming%e9%85%8d%e4%bf%a1%e3%81%a7%e5%8f%96%e5%be%97%e3%81%99%e3%82%8b/

Adsenseの住所確認の日数と手順について

Google Adsenseでは、収益が1000円程度を超えると住所確認のために郵送でPINコードが発送されます。振り込み準備に向けて、これに印字された6桁の番号をAdsenseのHPで入力し、住所が正しいことを認証伝えておく必要があります。記載された通りに実施するとうまくいきませんので、この方法について解説します。

Adsenseからメールが届きます

収益が一定を超えると、Adsenseからメールが届きます。

Screenshot
AdSense でお知らせいただいたお支払い先住所宛に、個人識別番号(PIN)を記載したハガキを 11月 18, 2020 付で発送いたしました。
ハガキが届きましたら、このメールにある [住所を確認する] をクリックし、AdSense のホーム画面で PIN をご入力ください。あるいは AdSense アカウントにログインして、ホーム画面で直接、同じ手順を行っていただくこともできます。
重要: この PIN による住所確認が最初の郵送日から 4 か月以内に行われなかった場合、広告配信が停止されますのでご注意ください。

4ヶ月以内に行わないと広告停止されると書いてありますが、葉書が到着するまでに結構時間がかかるので不安になります。日本では結構時間がかかるようで心配は入りません。私は2つのサイトで実施したことがありますがそれぞれ3週間弱かかりました。直近のサイトについては、11月18日発送で自宅に届いたのは12月4日でした。18日かかったことになります。

PINの入力方法

葉書の通りにやってもうまくいきません。葉書では歯車のアカウントからPINを送信してくださいと書いてありますが、アカウント の画面にはPINのことなど書いておりませんPCでAdsenseにログインして、トップページから辿る必要があります。

Screenshot

赤丸で囲ったところから入力するようにしてください。

Angular(Ionic)で非同期通信を実装する

フロントエンドアプリケーションフレームワークであるAngular(Ionic)で非同期通信を実装する方法をサンプルアプリとして紹介します。動作確認用にPythonで作成したバックエンドコード付きです。

Angularの場合、非同期通信を実装するにはHttpClientModuleをapp.module.tsに追加して、tsファイルで呼び出して使うだけです。AngularはReactと異なりフルスタックなので、この辺の安心感はReactにはるかに勝ると思います。

Angular公式サイトの説明

ソースコードの素性

ソースコードの素性ですが、Ionic(Aunglar)のblankアプリから実装しました。なお、バックエンドのサンプルコードも作成しましたが、こちらはPythonのFlaskを使っています。Flaskでホスティングした http;//127.0.0.1:5000 のURLを叩いてデータを取ってくるアプリです。
GitHubにコードあげたのでそのまま動作確認できると思います。

環境

フロントエンド

Angular CLI: 10.0.5
Ionic CLI : 6.10.0
Node: 12.18.0

バックエンド

Python 3.7.3
Flask 1.1.2
Flask-Cors 3.0.9

コード

app.module.ts

app.module.tsに
import { HttpClientModule } from ‘@angular/common/http’;
を追加します。importするのも忘れずに

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule,HttpClientModule, IonicModule.forRoot(), AppRoutingModule],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Ionicの場合、moduleファイルが複数あるけど、app.module.tsにだけ書いておけば問題ないです。

home.page.ts

こちらには
import { HttpClient } from ‘@angular/common/http’;
import { DomSanitizer } from ‘@angular/platform-browser’;
の2つを追加します。あとは処理を書くだけです。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  unsafeImageUrl:any;
  imageUrl:any;

  constructor(
    private http: HttpClient,
    private sanitizer:DomSanitizer)
  {
    this.hello();
  }

  hello(){
    this.http.get('http://127.0.0.1:5000',{responseType:'blob'})
     .subscribe(
       (res:any) => {
         console.log(res);
         this.unsafeImageUrl = URL.createObjectURL(res);
         this.imageUrl = this.sanitizer.bypassSecurityTrustUrl(this.unsafeImageUrl);
       })
  }
}

home.page.html

htmlはを追加するだけです。

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Blank
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Blank</ion-title>
    </ion-toolbar>
  </ion-header>

  <div id="container">
    <img [src]=imageUrl>
    <strong>Ready to create an app?</strong>
    <p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
  </div>
</ion-content>

これでOK.anyを指定してしまっていますので書き方知っていたら教えてください。

バックエンド

バックエンドはこちら

# -*- coding: utf-8 -*-
import io
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from flask import Flask, send_file
from flask_cors import CORS
import json

app = Flask(__name__)
CORS(app)

@app.route('/',methods=["GET","POST"])
def hello():
    image = io.BytesIO()
    x = np.linspace(0, 10)
    y = np.sin(x)
    plt.plot(x, y)
    plt.savefig(image, format='png')
    image.seek(0)
    return send_file(image,
                     attachment_filename="image.png",
                     as_attachment=True)

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

AngularでWebAPIからblob形式で返された画像データを取得する方法

フロントエンドアプリケーションフレームワークであるAngular(Ionic)を使って、非同期通信で画像を読み込む方法をサンプルアプリとして紹介します。動作確認用にPythonで作成したバックエンドコード付きです。

Angularの場合、非同期通信を実装するにはHttpClientModuleをapp.module.tsに追加して、tsファイルで呼び出して使うだけです。画像の場合は、さらにresponseTypeに’blob’形式を指定し、DomSanitizerでサニタイズして読み込みます。HTML側は<img [src]=imageUrl>で行けます。

AngularはReactと異なりフルスタックなので、この辺の安心感があります。

参考にしたサイト

stackOverflow

ソースコード

githubはこちら

解説

app.module.ts

app.module.tsに
import { HttpClientModule } from ‘@angular/common/http’;
を追加します。importするのも忘れずに

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule,HttpClientModule, IonicModule.forRoot(), AppRoutingModule],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Ionicの場合、moduleファイルが複数あリますが、app.module.tsにだけ書いておけば問題ないです。

home.page.ts

こちらには
import { HttpClient } from ‘@angular/common/http’;
import { DomSanitizer } from ‘@angular/platform-browser’;
の2つを追加します。あとは処理を書くだけです。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  unsafeImageUrl:any;
  imageUrl:any;

  constructor(
    private http: HttpClient,
    private sanitizer:DomSanitizer)
  {
    this.hello();
  }

  hello(){
    this.http.get('http://127.0.0.1:5000',{responseType:'blob'})
     .subscribe(
       (res:any) => {
         console.log(res);
         this.unsafeImageUrl = URL.createObjectURL(res);
         this.imageUrl = this.sanitizer.bypassSecurityTrustUrl(this.unsafeImageUrl);
       })
  }
}

これでOK。anyを指定してしまっていますので書き方知っていたら教えてください。

home.page.html

htmlはを追加するだけです。

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Blank
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Blank</ion-title>
    </ion-toolbar>
  </ion-header>

  <div id="container">
    <img [src]=imageUrl>
    <strong>Ready to create an app?</strong>
    <p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
  </div>
</ion-content>

バックエンド

バックエンドはこちら

# -*- coding: utf-8 -*-
import io
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from flask import Flask, send_file
from flask_cors import CORS
import json

app = Flask(__name__)
CORS(app)

@app.route('/',methods=["GET","POST"])
def hello():
    image = io.BytesIO()
    x = np.linspace(0, 10)
    y = np.sin(x)
    plt.plot(x, y)
    plt.savefig(image, format='png')
    image.seek(0)
    return send_file(image,
                     attachment_filename="image.png",
                     as_attachment=True)

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

【保存版】Flaskで画像を操作する際の6パターンを書き分ける

Flask上で画像を操作する際のソースコードのパターンについてですが、 画像の生成元をどうするかというもので3パターン、画像の最終的な処理方法をどうするかというもので2パターンで(組み合わせで)合計6パターンがあると思います。Flaskで画像処理を実装しようとするとこれらを書き分ける必要がありますのでそれについて記載します。

画像の生成元の3つとは

  • HTMLからFlaskで立てたURLへ直接画像がアップロードされる場合、
  • Flaskの関数の中でmatplotlibなどで生成する場合、
  • クラウドないしはローカルフォルダなどに保存されている画像を読み込んでくる場合

の3つです。

最終的な処理方法として2つあるというのは、

  • 画像をクラウドないしはローカルフォルダなどに保存する場合と
  • クライアント側へ画像をreturnする場合

の2つです。メタ情報だけ抽出して文字列で返す場合などもあると思いますが、これはJSONなどでリターンすればOKなので上記6パターンで対応できると思います。

Flaskで画像を操作したいと思うと、この6パターンについてソースコードを書き分ける必要があり、かなり大変です
しかし、幸いなことに、画像をどこかに保存する場合とクライアント側へ返す場合については画像をメモリ上でBlob形式であらかじめ保存することができれば同時に対応できますので、事実上かき分けなければならないのは3パターンとなります。
この記事では、画像をメモリ上でBlob形式で書き出すコードを使いながらこの3パターンについて整理したいと思います。

なお、ここでは処理後の画像の保存先としてGoogle Cloud Storageを利用した例で説明しますが、適宜AWSのS3、ローカルのフォルダの場所などと読み替えて下さい。今回記載するサンプルコードは下記の3つです。

1. 画像をFlaskの関数内で生成し、メモリ上でBlob形式で保存したのちにGCSへ保存しクライアント側へ返す
2. 画像をクライアントから受け取り、メモリ上でBlob形式で保存したのちにGCSへ保存しクライアント側へ返す
3. 画像をGSCから読み取り、メモリ上でBlob形式で保存したのちにGCSへ保存しクライアント側へ返す

画像をFlaskの関数内で生成し、メモリ上でBlob形式で保持したのちにGCSへ保存とクライアント側へ返す

# coding: utf-8
import os
import io
import time
import string
import random
import datetime
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from PIL import Image
import cv2
from flask import Flask, render_template, request, redirect, url_for, send_from_directory,send_file
from flask_cors import CORS
from google.cloud import storage
from google.cloud.storage import Blob
import inspect
import numpy as np
import cv2

app = Flask(__name__)
CORS(app)

@app.route('/',methods=["GET","POST"])
def hello():
    buf = io.BytesIO()
    x = np.linspace(0, 10)
    y = np.sin(x)
    plt.plot(x, y)
    plt.savefig(image, format='png')
    buf.seek(0)

    destination_blob_name = "path/to/your/file_on_gcs.jpg"
    blob = Blob(destination_blob_name, bucket)
    blob.upload_from_string(data=buf.getvalue(), content_type=content_type)

    return send_file(
        buf,attachment_filename=source_blob_name,as_attachment=True
    )
if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

ポイントは2つあって、一つ目はGUIに対応していないFlask上でmatplotlibが動作するようにmatplotlib.use(‘Agg’)というコードを入れていること二つ目はsavefigの部分でメモリ上でファイルを書き出していることです。ローカルで動かす場合は、普通にHDD(SSD?)にjpgなどで書き出しても問題ないと思いますが、クラウドなどだとローカルへのアクセスが禁止されていることが往々にしてあります。この場合に備えて、メモリ上でファイルの読み書きを行うioモジュールを使ってメモリ上に画像を出力しています。ここは個人的に結構ポイントです。

それでは次に既に保存されている画像を取ってくる場合です。

画像をGSCから読み取り、メモリ上でBlob形式で保持したのちにGCSへ保存とクライアント側へ返す

クラウドからデータを持ってきます。重いデータなどは予め画像にしておくと便利ですのでよく使っています。

# coding: utf-8
import os
import io
import time
import string
import random
import datetime
import numpy as np
from PIL import Image
import cv2
from flask import Flask, render_template, request, redirect, url_for, send_from_directory,send_file
from flask_cors import CORS
from google.cloud import storage
from google.cloud.storage import Blob
import inspect
import numpy as np
import cv2

app = Flask(__name__)
CORS(app)

@app.route("/", methods=["GET", "POST"])
def hello():
    os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = './for-dev-268800-ed3aecf9b014.json'
    storage_client = storage.Client()
    bucket = storage_client.get_bucket('for-dev-268800.appspot.com')#"'for-dev-268800.appspot.com'

    source_blob_name = "path/to/your/file_on_gcs.jpg"
    
    #検索する場合はここを使う
    """
    blobs = bucket.list_blobs(prefix="")
    for blob in blobs:
        print(blob.name)
        print(dir(blob))
        print(blob.content_type)
        source_blob_name = blob.name
        content_type = blob.content_type
    print(inspect.getfullargspec(bucket.list_blobs))
    """    

    blob = bucket.get_blob(source_blob_name)
    buf = io.BytesIO()
    blob.download_to_file(buf)
    buf.seek(0)

    destination_blob_name = "path/to/your/file_on_gcs.jpg"
    blob = Blob(destination_blob_name, bucket)
    blob.upload_from_string(data=buf.getvalue(), content_type=content_type)

    return send_file(
        buf,attachment_filename=source_blob_name,as_attachment=True
    )

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

上記ファイルを実行しFlaskサーバが立てたあと、こちらについてはブラウザなどで直接叩いたいただければ問題ありません。

画像をクライアントから受け取り、メモリ上でBlob形式で保持したのちにGCSへ保存とクライアント側へ返す

最後のパターンです。これはクライアントから画像を受け取る場合ですが、要するにユーザがブラウザから画像を選択してアップロードするよく使うやつです(curlでこれをコード化すると、画像の一括アップロードなどにも使えます)。

# coding: utf-8
import os
import io
import time
import string
import random
import datetime
import numpy as np
from PIL import Image
import cv2
from flask import Flask, render_template, request, redirect, url_for, send_from_directory,send_file
from flask_cors import CORS
from google.cloud import storage
from google.cloud.storage import Blob
import inspect
import numpy as np
import cv2

app = Flask(__name__)
CORS(app)

@app.route("/", methods=["GET", "POST"])
def hello():   
    if request.files['image']:
        filename = request.files['image'].filename
        content_type = request.files['image'].content_type
        # 画像として読み込み
        stream = request.files['image'].stream
        img_array = np.asarray(bytearray(stream.read()), dtype=np.uint8)
        img = cv2.imdecode(img_array, 1)
        is_success, buffer = cv2.imencode(".jpg", img)
        buf = io.BytesIO(buffer)
        #buf = io.BytesIO()
        buf.seek(0)

        os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = './path/to/your/credential_goole.json'
        storage_client = storage.Client()
        bucket = storage_client.get_bucket('your_backetname.com')
        blob = Blob(filename, bucket)
        blob.upload_from_string(data=buf.getvalue(), content_type=content_type)

        return send_file(
            buf,attachment_filename=filename,as_attachment=True
        )

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

上記ファイルを実行しFlaskサーバが立てたあと、こちらについては画像をHTMLからアップロードする必要があります。
APIを叩いてcanvas要素に描写するHTMLも載せておきますので、こちらを参考にしてください。buf変数に読み込んだものをOpenCVなどで操作すれば簡単な画像処理ソフトになると思います。

<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

    <title>bootstrapとjquery</title>

  </head>
  <body>

<main>
<article>
  <section>
       <input type="button" value="Start" onclick="main();"/>
        <canvas style="height: 30vw;width:50vh"></canvas>
  </section>
</article>
</main>

<script language="javascript" type="text/javascript">

    function main(){
      var canvas = document.getElementsByTagName('canvas');
      var ctx = canvas[0].getContext('2d');

      var img = new Image();
      img.src = 'http://0.0.0.0:5000';//FlaskでホスティングしたURL

      img.onload = function() {
        img.style.display = 'none'; // ようわからん
        console.log('WxH: ' + img.width + 'x' + img.height)

        ctx.drawImage(img, 0, 0);
        var imageData = ctx.getImageData(0, 0, img.width*2, img.height*2)

        for(x = 0 ; x < 1000 ; x += 10) {
          for(y = 0 ; y < 1000 ; y += 10) {
             ctx.putImageData(imageData, x, y);
          }
        }
      };    }
</script>

  </body>
</html>

curlコマンドでForm送信は代替できます。
HTMLのform送信をcurlコマンドで代替する方法はこちら

Flaskで画像を処理場合のソースコードはパターンごとに書き分ける必要があり、普段は目の前のタスクを終わらせて終わりがちですが、今回は6つのパターンについてまとめました。AWSのS3を使う場合やローカルで完結させる場合も同様に処理できると思いますので、ぜひ参考にしていただけるとうれしいです。

この記事が役に立ったと思ったらLGTMお願いいたします:thumbsup:

Matplotlibで作成したグラフをクライアントへ返す方法についてさらに詳細をまとめたのはこちら

https://qiita.com/NP_Systems/items/9bea70ade73cc48cbdf0

製造業において、Pythonに加えてWebも活用して飛躍しよう

最近、製造業におけるPythonの導入が進んでます。自動化やアプリケーション制作に便利ですし、今後もこの勢いは止まらなそうです。

ところが、私は実務担当として工場でPythonを導入して5年になるのですが、近年はWeb技術を使用することが多くなりました。Pythonを使い始めた当初、Web技術と言えばHTMLとCSS?データ分析メインの俺にはあんまり関係ないよね?と思っていました。でも、実務を行う中でPythonとWeb技術の相性が非常に良いことに気付き、最近はWebとPythonを半分ずつ使っています。

記事の内容

この記事では、とある工場で働くエンジニアがWeb技術の重要性について発見したことを書きたいと思います。おそらく、最近の空気は、製造業でもPythonは役に立つが、Webは分野が違うので関係ないという感じだと思います。でも、私はWeb技術があってこそPythonが100%生かせるのではないかと思っています。ある工場でPythonを導入した結果、Web技術の(意外な?)重要性について発見したことを共有したいと思います。

構成

構成としては
・製造業におけるPythonの使い所を整理し、
・Web技術の重要性について
述べたいと思います。

製造業におけるPythonの使い所

少し冗長ですが、そもそも製造業のおけるPythonの使い所として主に下記の3つがあると感じています。
・データ整形や業務の自動化
・高度なデータ分析
・業務アプリの制作(AIアプリ制作含む)

1.データ整形や業務の自動化

まず一つ目です。工場だと、製造に必要なファイルはエクセルで管理されていることが多いと思います。そして、どこかのフォルダに入っているデータをそのエクセルにコピペして行う集計作業も多いと思います。Pythonの使い所として最初に出てくるのがこの作業の自動化です。csvで管理されているデータならPandasで読み込めますし、Excelも操作できるので集計作業を一瞬で終わらせることができます。Pythonは汎用プログラミング言語ですので、PC上で行う「手順の決まった繰り返し作業」であれば、原理的にはすべて自動化できるというのは心強いです。

2.高度なデータ分析

二つ目が高度なデータ分析です。研究系で使用される方はこの用途が多いのではないかと思います。当然オフラインで行うこともできるのですが、うまくやれば高度な分析も自動化できます。
つまり、一般的には

  1. 最初はオフラインで工場からデータをもらう
  2. 手元のPCで分析して結果報告
    って流れがありがちだと思うのですが、Pythonを使うとこういった流れも自動化してラインに組み込むことができると思うのです。後述するAPIの作成や、データの取得部分の自動化、分析コードのライブラリ化が必要になりますが、すべての製造ロットについて自動で不良原因を分析したり、品質を予測と言ったことができるようになります。オフラインに留まらず工場の中で生きたものとして分析コードが利用できるというのは夢がありますよね。

3.業務アプリの制作

三つ目が業務アプリ制作です。
自分ごとで恐縮なのですが、私はPythonを使い始めた時、主に自分の業務の自動化で使っていましたが、次第にある欲求が抑えられなくなってきました。「作ったアプリを周りに活用して欲しい」という欲求です。結構あるあるだと思いますので、業務アプリの制作というのも製造業におけるPython活用のよくあるパターンだと思います。あるいは、最近はAIを実装すると言った業務もあるのではないでしょうか。
そして、私がWeb技術の活用が必要だとおもったきっかけは、この3つ目の用途においてです。

Web技術の必要性

PythonでGUIを作ってしまうと、使用する人全員に同じ環境を用意しなければなりません。一桁程度ならなんとかなるのですが、人数が増えるとその手間は大きくなり苦痛に感じるようになりました。
かといって実行ファイルにするとサイズが大きくなったり、コンパイルに失敗したりデバッグできなかったりで使い勝手が悪かったです。そこで、サーバPCを用意して環境構築をその1台だけで完結させ、使用者はそこにアクセスすることでサービスを提供できるようにするためにはどうすればいいだろうかという課題に直面しました。Pythonの活用が進めば進むほどこのニーズが大きくなり、Web技術の必要性が出てくると思います。

また、二つ目の高度なデータ分析におけるWeb技術の活用のメリットも大きいです

A.手渡ししてもらったデータをオフラインで解析する
B.解析コードをラインに組み込んで全ロットで自動に結果を出す

という違いは大きいと思うのですが、後者のためにWebが非常に役立つからです。

Webを導入する他のメリット

つまり、Web技術を導入すると、分析の自動化のために必要な下記の2点が可能になります。
・整形したデータをAPIを介して配信できる(データの活用の裾野が広がる)
・本社や現場で集めたデータを活用するスマホアプリまで作れる(実行環境を問わずデータを活用するアプリケーションが開発できる)

整形したデータをAPIを介して配信できる

Pythonの使い所の一つ目で挙げたとおり、データ整形という用途はPython活用の王道だと思います。そして、整形したデータを活用するというのを考えた時、Webブラウザを叩いてデータが返ってきたらめちゃくちゃ便利ではないかと思い至りました。要するにWebAPIで配信するということです。
ロットやデータ数を指定してAPIを叩いたらデータが勝手に得られる環境というのは、分析の自動化にもだいぶ役に立つと思います。Pythonだけだとデータ整理で終わったかも分からないのですが、APIを作ることでPythonで集計したデータを他事業所からアクセスしてもらえるようにしたり、より広く活用することができるようになります。個人的には、バックエンドをFlaskでAPI化して、フロントエンドはクロスプラットフォームフレームワークのAngularかReactという構成が好きです。でも、今ならVue.jsから始めるかもしれません。

現場で活用するスマホアプリも作れる

発見だったのがこれです。Web技術というとHTMLとCSSの静的なサイトのイメージが強かったのですが、最近はSPA(シングルページアプリケーション)という技術があるそうで、SPAを使うとデスクトップアプリと同等のものを作れます(SPAとセットでよく使う言葉にPWA(Progressive Web アプリ)という物もあります。SPAという技術で作った物の性質を表すときにPWAと表現していると理解しています。なので、文脈的にはSPAよりPWAの方が正確かも)(注釈1)。AngularやReact、Vueといったフレームワークがそれなのですが、これらはTypeScriptやJavaScriptの拡張言語であるJSXなどをかなり駆使しますので、HP制作というよりアプリケーション開発のイメージです。これによりTKinter、PysimpleGUI、PyQt、のようなデスクトップアプリをWeb上でも作ることができます。
加えて、AngularやReact、Vueではクロスプラットフォーム性があります。つまり、Webアプリだけではなく、AndroidやiOSのアプリにも出力が可能ということです(!)(注釈2)。これにより、ブラウザを介してユーザーのアクセス性を確保しつつ、ブラウザでアクセスできない場所(=例えば現場)ではアプリとして使ってもらうことができるようになります。これはWebを導入して見つけた想定外の発見で、PythonだけではスタッフのPCで動作するアプリ開発しかできなかったと思いますが、Webを導入することで多くの人や場面で使ってもらえるための開発手段を手に入れられました。
相変わらずバックエンドはPythonで書いていますが、フロントエンドはWebに移行することで大きなメリットが得られました。

まとめ

少し冗長な説明になってしまいましたが、上記のようにWeb技術を使うとPythonで作ったデータを活用できる幅が広がったり、Pythonの普及に伴う環境整備の手間が低減できます。
近年注目されるPythonによる高度な分析についても、Webの力を借りることで、オフラインに留まらず全ロットに対して自動で行いそれを工場全体で生かす仕組みも構築できます。
Pythonについては製造業でも普及が進みつつあると思いますが、Webについては分野が違うという認識をされがちです。でも、Webがあるとより活用の幅が広がると思いますので、ぜひ検討してみてください。

この記事が役に立ったと思ったらLGTMお願いいたします:thumbsup:
製造業でのIT活用頑張りましょう!

注釈1:PWA、SPAとはなんぞや
注釈2:クロスプラットフォームとは何

 個人サイトの方で解説した記事1(個人・少人数のシステム開発にはAngularがオススメ)
 個人サイトの方で解説した記事2(pythonとangularの組み合わせが最強な理由)
 Monacaの記事
 勉強した本1(掌田津耶乃さんのAngularの本。React版でもいいかも。Vue.jsもいい本あると思います)
 勉強した本2(AngularをIonicベースで利用)
 

curlコマンドでHTMLのformでの画像送信を代替する

curlコマンドでHTMLのformでの画像送信を代替する方法です。
curlコマンドを打つときの場所にna18_1920x1080_221804.jpgと言う画像を置いた場合です。

結論

url -X POST -F 'image=@./na18_1920x1080_221804.jpg' http://127.0.0.1:5000/ --insecure

追加

ヘッダー情報とかクエリパラメータを引き渡したい場合はこちら。

curl -X POST -H 'Host:some_destination.com.' -H 'Authorization:something' -F 'image=@./na18_1920x1080_221804.jpg' -F "arg1=myarg1" -F "arg2=myarg2" http://127.0.0.1:5000/ --insecure

Get通信の場合

ちなみに、画像送信とは関係ないですが、Get通信の諸々のサンプルこちらです。

curl  -H 'Host:some_destination.com.' -H 'Authorization:something' -X GET  "http://127.0.0.1:5000?arg1=myarg1&arg2=myarg2" --insecure

Windowsの場合の補足

Windows10のcurlコマンドだと、実際はinvoke-webrequestというコマンドのエイリアスなので動作しません。その場合、本物のcurlの実行ファイルをダウンロードして、パスを通して実行ファイル名を書き換えて、それでコマンドを打てば大丈夫です。

USキーボードのMacに外付けのキーボードを取り付ける

MacBookを使っているのですが、タイピングしにくいので外付けのキーボードを取り付けることにしました。Windowsなら何も考えずに購入できたのですが、実際お店に行ってみると

  • Macに外付けキーボードをつけて安定して動くのか
  • USキーボードのMacを使っているのだが、外付けキーボードはUSキーボードである必要があるのか

心配になりました。上記について、購入して試した結果をまとめたいと思います。

Macに外付けキーボードをつけて安定して動くのか

Mac対応と書いてあるものなら普通に動きそうです(Elecomとか)。栄のビックカメラで買ったのですが、5種類ほど選べました。形状が凝ったものだとWindowsのみの対応だったりしましたが、まぁ基本的なものであれば普通に売っている感じです。純正のキーボード高いですから、サードパーティ製で対応したいものです。

USキーボードのMacを使っているのだが、外付けキーボードはUSキーボードである必要があるのか

JISキーボードで問題ありません。普通にキーボードの配列通りに打てます。安めのMac対応の外付けのUSキーボードが売っているはずもなく、US配列であることは諦めていたので、一番うちやすかったのでElecomのTK-FBP101BKというものを買いました。

打ちやすかったのはパンタグラフ式という構造のせいらしいです。Macとは正反対の印象です。快適に使えますし、気分転換にもなるので気に入りました。

https://www.elecom.co.jp/products/TK-FBP101BK.html

近鉄特急ひのとりで大阪へ

名古屋と大阪を結ぶ近鉄特急ひのとりに乗ってきました。大阪一泊旅行です。

まずはひのとりのレポートから。

https://www.kintetsu.co.jp/senden/hinotori/

前日に切符を買いました。ひのとりは名古屋を出た後最初に止まるのが津ですので、最寄駅から津までは通常の特急で行き、そこから乗り換えることを勧められました。しかし、せっかくなので名古屋まで一度出て改めて乗ることにしました。というわけで朝9寺半前に名古屋駅へ。

10寺発名古屋のひのとりを待ちます。

ホームにひのとりが入ってきました。

かっこいいですね。中もめちゃめちゃきれいです。

トイレもきれい。

ここは手洗い場

席はこんな感じです。

重厚な感じでしっかりしてました。フットレストもいい感じです。

津までは南へ南下して、そこから西へ。

1日目の目的地は、川西にあった母の実家です。子供の頃よく遊びに行ったのですが、18年ぶりに行きました。

畦野から歩くつもりでしたが、畦野えきで妻と喧嘩。かなり最悪な流れでしたが、結局話がついてタクシーで向かいました。昔は遠いと思ってたけどそこまで遠くはなかったです。まだ家があったことにびっくり。

母が15歳の時に買ったそうで、狭い社宅から越したのがうれしかったそうです。その母も結婚して東京に移り子供を育て、それでその子供は独立して、、ということでとても時間の流れを不思議に感じました。この家に遊びに行っていた時、俺はまだ子供だったんだよなぁ。

夜は西梅田のほうにホテルを取りました。

妻が疲れていたので弁当を買いに行って、ついでに美味しんぼで紹介されたというたこ焼きを買って帰りました。

翌日(というか今日)は、ドトールで1時間くらいゆっくりした後に大阪観光ということで、アメリカ村まで歩いて、そこから電気街やコンカフェに行ってカレーを食べて、家族へのクリスマスプレゼント物色で難波駅をブラブラして、15寺半大阪難波発のアーバンライナーで帰ってきました。

というわけで今9時過ぎです。早めに寝ます。

Qiitaでorganizationを作成する方法

qiitaのプロフィール欄の右側に所属組織を表示されるときがあります。

会社が表示される人がいますが、中には明らかに非公式の組織が表示される時もあります。調べてみると、Qiitaのorganizationは非公式なものも登録できるそうなので、作ってみることにしました。

Organizationsとは

Qiita Organizationは会社や組織等の団体メンバーの記事をまとめることができる機能です。qiitaを読んでいると、この人どんな人なんだろうって思うこともあると思いますので、自分の所属や志向を伝えることができるので便利ですよね。

作成する時、企業か団体を選択できるので、会社などの公式の組織はもちろん、有志で集まった団体でも2名以上のメンバーがいれば作成できるそうです。公式の説明はこちら。

https://qiita.com/organizations

作成する方法

先日、製造業におけるPythonとWebの活用の記事を書いたところ、結構反響がありました。おそらく、製造業でのIT活用というところで共感を感じていただいたのかと思います。私自身そういう仲間がいたら面白うかなと思って、作成してみることにしました。

組織・団体名

考え中です。何かいいあんがあればコメント欲しいですが、

  • 製造業でIT活用を頑張る会
  • どうやって飛んでいるの?「Pythonだよ」を製造業で実現する会
    など。

URL名

これはスラッグ です。massive-indeustriesとでもしておきましょう。

メイン画像とロゴ画像

結構皆さん適当です。

これとか切り出し感あるし

これも味はいいが単純

これは企業ロゴそのまま。

結構趣味を出してもいいらしい。

というわけで、その気になればフリー素材使ってなんとかなりそう。

Organizationメンバー

Organizationは二人以上じゃないと作成できないので誰か仲間を指定します。

説明

これも皆さん適当なので適当に

「製造業でIT技術の活用を頑張る会です。」など後で考えます。

以上の内容を下記のフォームで入力すればできるっぽい。

https://qiita.com/organizations/new

メイン画像とロゴを作成したら(してもらうんですが)、作成してみようかな!

Oandaを解約して口座を作り直す方法

OandaのAPIを使いたいのですが、私が作った3年前と現在ではAPIのバージョンが異なります。昔のバージョンのときに作成し、現在は動作しなくなってしまいました。いろいろ頑張ったのですがラチがあかないので、古い口座を解約し、新しいものを作り直すという強硬手段に出ることにしました。

ここでは、その時の注意事項などについてまとめたいと思います。

Oanda APIでエラーが出る

まず、問題なのはこのエラーです。適当に最近のレートを取得しようとするとこのようなエラーがでます。

Error: {"errorMessage":"Insufficient authorization to perform request."}

デモ口座だとうまくいくのに本番環境ではうまくいきません。日本法人に問い合わせると、APIは管轄外なので米国の本社に聞いて欲しいと言われます。しかし、そこに聞いても結局解決しませんでした。

解決策

いろいろ情報を探して

https://developer.oanda.com/rest-live-v20/troubleshooting-errors/

https://stackoverflow.com/questions/63822473/v20error-errormessageinsufficient-authorization-to-perform-request

などを見ましたが解決せず。また、ログイン後の画面がこのようになっており、accout IDの後にV20と表示されていないのでV20に対応していないのかと思ったのですがそういうわけでもないようです。

Screenshot

デモ口座だとこのようにV20と表示されてるんですけどね。

Screenshot

そこで、古い口座を解約し、新しいものを作り直すという強硬手段に出ることにしました。必要な時間は2日程度でした。

流れ

解約する方法と新しいものを作る方法について説明します。

解約

従来のものを解約するのに必要な日数は半日です。10時までにカスタマーセンタに電話で連絡すれば、その日に解約できると思います。出金手続きも電話口で代行してくれます。私は10時半くらいに電話、11時までなら当日の出金に対応できるということでその場で指定口座への出金代行を依頼。15時には解約されました。

口座に預金がある状態では解約できない仕組みになっていますし、カスタマーセンタが問題があれば口頭で言ってくれるので安心です。

新規申し込み

解約されたら直後に申請可能です。これは通常通りの申請ということでリンク貼っておきます。私の場合、月曜日に新規申し込みをして、木曜日にIDを書いたものが簡易書留できました。あとは画面の指示にしたがって初期設定を澄ませばすぐに使えるようになると思います。

https://www.oanda.jp/register/web/accountopenperson.do

Xserverのメールを他のメールソフトで見られるようにする

ここでは、Xserverのメールを他のメールソフトで見られるようにする方法として、iPhoneのGmailで設定する方法を紹介します。

手順

Gmailを起動して、右上の自分のアイコンをクリックします。このような画面になります。

そしたら、「別のアカウントを追加」を押下します。

その他(IMAP)をタップし、そこで登録したいメールアドレスを入力します。

さらにボタンを押すと、下記の画面になり、ここでサーバとパスワードを入力します。これは受信サーバと送信サーバの両方が必要になり、最初に表示されるのは受信サーバです。

パスワードとサーバ名を入力します。

パスワードはXserverの管理画面からメールアドレスを発行する際に、メールアドレス に対して発行するものなのでもしこれがわからない場合は作り直すか作った人に聞くしかありません。

サーバー名を入れて終わりです。ポート番号は基本的には既に入力されているはずですが、変える場合は下記を参照してください。

POP/IMAPサーバー名
(ホスト名)
設定完了メールに記載されている「メールサーバー」を入力してください。※サーバーパネル内「サーバー情報」に記載の“ホスト名”と同一です。例)sv***.xserver.jp
SMTPサーバー名
(ホスト名)
設定完了メールに記載されている「メールサーバー」を入力してください。※サーバーパネル内「サーバー情報」に記載の“ホスト名”と同一です。例)sv***.xserver.jp
ユーザー名
(アカウント名)
メールアカウントの追加にて設定したメールアドレス(ドメイン名を含む)を入力してください。

例)user@example.com
例)info@example.com
パスワードメールアカウントの追加にて指定したパスワードを入力してください。
ポート番号
POPの場合
995 (SSLを利用しない場合は 110)
ポート番号
IMAPの場合
993 (SSLを利用しない場合は 143)
ポート番号
SMTPの場合
465 (SSLを利用しない場合は 587)

そうすると、頑張って設定をしてくれます。

送信サーバの設定画面に移行するので、同じ情報を入力して完了です。

まとめ

Xserverのメールを他のメールソフトで見られるようにする方法として、iPhoneのGmailで設定する方法を紹介しました。Xserverのリンクはこちら。

https://www.xserver.ne.jp/manual/man_mail_setting.php

SBI FXトレードで出金指示をした際、受付完了連絡が来ない

スマホからSBI FXトレードで出金指示をした際、画面から預託金残高はすぐに減りますが、メールも来ないし履歴も残りません。極めてひどいUIだと思います。ここではSBI FXトレードの出金の流れを記載します。

普通、出金指示を行った時点で出金依頼を受けつけたメールが来て、その後出金が完了した旨が通知される流れを想定すると思います。OANDAではこのようにされますし、当然こう言った流れが適切だと思います。しかし、SBI FXトレードで出金をすると、画面から預託金残高はすぐに減りますが、メールも来ないし履歴も残りません。ちゃんとシステムが受け付けてるのか心配になります。

自分の一例

私は出金依頼を11月11日の夜にしました。その際、実際に出金確定メールが来たのは11月13日9時でした。出金した額が自分的には大きな額だったので連絡が来るまで冷や冷やものでした。本当にひどいものです。

公式サイトのFAQ

公式サイトの「出金依頼をしてからどのくらいで振り込まれますか?」という質問にはこのような回答となっています。

「取引終了時間までにご依頼された場合、通常時は翌営業日にお振込みします。
ただし、何等かの事象が発生した場合に備え、出金依頼された日から4営業日以内(※土日祝は営業日ではございません)のお振込みを原則とさせていただきます。」

時間差はいいとして、出金指示の受付メールが来ないことは改善されることを望みます。みなさん不安になると思うので記事にさせていただきました。

個人・少人数のシステム開発にはAngularがオススメ

Angularとは

 GoogleのドキュメントやGCPで使われているAngularは、Googleが中心開発している JavaScript フレームワークです。AngularJSと混同されていている方もいらっしゃいますが、AngularJSのメリットを生かしたまま互換性のない形で生まれ変わったのがAngularです。Angularは、SPA (Single Page Application)と呼ばれる技術を用いて、従来のWebでは実現できなかった機能を提供する技術です。イメージ的には、従来のWebページよりアプリケーションよりのものを作りやすくなると感じています

SPAの特徴

 最近、従来のWeb技術では考えられなかったようなサイトが増えてきています。たとえば、オフラインでも動作するWebページやGoogleのドキュメントのように同じURLなのに検索ワードによって違う内容が表示されるページなどです。SPA (Single Page Application)ではindex.htmlだけを読み込み、そのあとはDOMの操作をフロントエンドで行って擬似的な遷移を行っています。これによりオフラインでも画面遷移ができたり、検索クエリによって動的にページを書き換えたりといったことができます。

SPAフレームワークにおけるAngularの特徴

 SPAのフレームワークはいくつかあります。Facebookの開発しているReact.jsなどが有名ですが、個人的にはReact.jsよりも断然Angularがおすすめです。Reactが最小限の本体とサードパーティのライブラリで構成されているのに対して、Angularは開発に必要な機能をすべて盛り込んだフルスタックです。開発に必要なデータバインディング、フォームバリデーション、非同期通信、ルーティング、テスタビリティ、セキュリティへの配慮など、一通りの機能がすべてあらかじめ用意されているので、Angular を選択することでコーディングに集中できます。ライブラリが複数あるとどれを使えばいいのか迷いますしライブラリ間の依存関係などを気にする必要が出てくると思いますが、そういった懸念から解放されコーディングに集中できることがAngularのメリットだと考えています。個人的にはAngularは「控えめにいって最高」のフレームワークです。

クロスプラットフォームなのもよい

わたしは個人でシステム開発をしているのですが、iOS用にSwiftで、Android用にKotlinで、そしてWeb用にJavaScriptで開発をしていたらいくら時間があってもたりません。そこで一つのソースコードでさまざまなプラットフォームで動作すればありがたいわけです。そこでクロスプラットフォームの仕組みとして「Ionic 」「Cordova」「NativeScript」「React Native」Electron」「Capacitor」「Xamarin」「Unity」などが候補にあがるわけですが、Angularは「Capacitor」というMacデスクトップアプリ、Windowsデスクトップアプリ、Web、iOS, Androidに出力できるこれまた最高のフレームワークと組み合わせることができます。したがって、Angularは効率的にプロジェクを作成し、それを複数のプラットフォームに同時に出力できるという夢のような生産性を実現するフレームワークです。

AngularJSとの非互換性について

Angularの前身であるAngularJSがあまりにも有名なため、AngularJSの終了から5年弱経っているのにまださまざまなところで混同されています。しかし、AngularはAngularJSで顕在化した問題を解決するべく、内部構造から細部に至るまで再設計され実装されています。

一般的にフルスタックフレームワークだと、開発規模が大きくなるにれバンドルされるファイルサイズが大きくなりパフォーマンス面で悪影響を与えることもあります。AngularJSでも顕在化したその問題を解決するために、AngularではJavaScript VM (Virtual Machine) フレンドリーな実行コードを生成することで実行速度を改善したり、AoT コンパイル(Ahead-of-Time Compilation)と呼ばれるビルド時に表示を高速化するための仕組み、さらに段階的にソースコードをよみこむLazyLoadingと呼ばれる仕組みを利用して高速化する方法も提供されるなどです。最初にAngularで開発するときに訳が分からなくなってしまいがちですが、AngularJSとは互換性がないということを覚えておくといいと思います。半年毎にバージョンが変わるのですが(例えばAngular8が現在のバージョンなら、半年後はAngular9がでる)これらには一部の機能を除いて互換性があります。

まとめと結論

SPAの技術の一つであるAngularについてまとめました。近年普及が急速に進んでいるPWAアプリ作成のフレームワークとして代表的な物で、一つのコードで複数のプラットフォームに出力できるクロスプラットフォーム性について記載しました。

Angularで個人で作ったアプリがあります。OCRという写真から文字を抜き出す技術を使って、ブログなどを早く書くためのツールです。これも一つのコードでAndroid ,Web,iOSに出力できるので大変便利でした。

iOS => https://apps.apple.com/app/id1497498494

Android => https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Ionic(Angular)のフォルダ構成を完全解説

Ionicのフォルダ構成について

 Ionicで作成したプロジェクトは下記のような構造になっています。

directory

 かなり複雑で、最初に見たときは戸惑うかもしれません。実際に触るのはsrc/appフォルダがほとんどになりますが、他のフォルダを含めて解説したいと思います。

ルートフォルダについて

名前説明
e2e/システム全体の動作検証を行うE2Eテストの際に利用します。最初はほとんど触りません
node_modules/node.js上で動くため、package管理ソフトにnpmを使うことになりますが、npm経由でインストールしたライブラリが保存されます
src/実際に開発を行う場所です。9割このフォルダを触ります。
angular.jsonAngularの設定ファイルです。フォルダの出力先などビルドを中心にした設定はここで行います。
package.json実際にプロジェクトで使用するライブラリを指定します
package-lock.jsonpackage.jsonで指定したものが入っているか確認します。エラーの時にたまにいじります

したがって、基本的にはsrcだけをいじると考えておいて差し支えないと思います。

srcフォルダについて

それでは、実際に開発を行うsrcフォルダを見てみます。

srcのなかでも触るのはsrc/appがほとんどになりますが、こちらについても簡単に解説をします。

ファイル/フォルダ説明
src/app実際に触るのはほとんどこのフォルダのなかです。このフォルダが開発対象と考えていただいて問題ないと思います。
src/assets画像や音声、動画などのリソースを配置します。例えばsrc/resourcesというフォルダを自分で作成すると読み込まれないので注意が必要です。
src/environmentsなかには簡単なjsonファイルが本番用と開発用に入っています、環境変数をそれぞれ定義します。Google Analyticsをいれるときなどにいじりました。
src/themecssの拡張であるscssでデザインを規定しますが、そのファイルが入っています。IonicのUIが優れているため、個人的にはあまり触りません。
src/global.scssプロジェクト全体に適用するscssを書く際はこちらを使います。たた、実際はpageごとに用意されるscssをいじることが多いです。
src/index.htmlAnugular(Ionic)でも最初によみこまれるのはIndex.htmlです。
src/main.tsアプリを起動するためのファイルです。index.htmlのあとに読み込まれます。
src/polyfills.tsIE10/
11対応を行うもののようですが、IonicってIEで見れないんですよね。。
src/test.ts単体テストの設定ファイルです

まとめ

基本的に、src/appのなかをいじる、とだけ考えておけばいいと思います。

その他

IonicをAngularで使って、写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494
Android =>  https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

【まったく新しい最高のブラウザ】Braveブラウザでインターネットの仕組みとありかたを変えよう。

これはただのプラグインではありません。Louis Armstrong によって歌われた最も有名な二つの単語、Hello, Dolly に要約された同一世代のすべての人々の希望と情熱を象徴するものです。
これはただの記事ではありません。Robert E. Kahnによって歌われた開かれたインターネットへのすべての人々の希望と情熱を象徴するものです。

概要

この記事ではBraveブラウザについて説明します。Braveブラウザを使えば広告なしでYoutubeを閲覧できたり、サイトを見るとお金がもらえたりします。また、Adsenseに代わりWebサイトやTwitterを収益化できるブラウザでもあります。しかしそれだけではなく
1. (Googleの無料サービスに定義された)インターネットの仕組みを変える
2. コンテンツ製作者に適切に報いる
ことを目的とする従来とは全く異なるパラダイムから生まれたブラウザです(製作者はJavaScriptの生みの親で元MozillaCEOのアイク氏です)。
そこで、クリエータとして収益化する方法とWebサイトを閲覧してお金をもらう方法をメインで説明しつつ、Braveブラウザの背景や理念などについても紹介します。インターネットの仕組みを変えようとする非常に面白い取組みで、インターネットを使う人なら誰でも関わりのある話だと思いますのでぜひご覧ください。

構成

・Braveブラウザとはなんぞや
・クリエータの収益化の設定方法
・ユーザとして収益化の設定方法

Braveブラウザとはなんぞや

技術的には2019年に公開されたChromiumベースのブラウザです。ただし単なるブラウザではありません。他との決定的な違いは、JavaScriptの生みの親で元MozillaCEOのアイク氏がインターネットのあり方と仕組みを根本的に変えることを目指して作ったことです。しかしインターネットのあり方と仕組みを変えるとはなんぞや。。
ここではわかりやすくアイク氏の着眼点から整理することでまとめてみたいと思います。
(Braveブラウザはまだメジャーではないため、前段が長くなることをお許しください。)

Braveブラウザの着眼点と現状の問題

既存のインターネットの仕組みについて、無料でサービス展開して広告で収益化する仕組みって限界あるよね?ということが発端になっていると理解しています。
ユーザー視点で言えば

  • さすがに広告邪魔すぎじゃね?

という限界であり、コンテンツ製作者としては

  • 広告出しても全然もうからんのやけど。アフィもやらないとダメかな。(作りたいコンテンツ作れねー)

という限界であり、ビジネスモデルや倫理的な観点で言えば、アップルのティムクックの「サービスが無料であれば、それを受け取る人は顧客ではなく、製品である」ということをもじれば

function 広告利益最大化(
   ティムクック="サービスが無料であれば、それを受け取る人は顧客ではなく、製品である"
){
 => (ユーザ-="単なる変数";)
}

ということです。これって正しいようだけど、よくみると文法エラーですよね?

元々インターネットというのは分散型で自由であったもののはずなのに、今は「データの吸い上げ×広告出稿」でプラットフォーマーが牛耳ってる超中央集権状態だよね?しかもその中で一人ひとりの扱いは変数の一つにすぎず、モチっと別の仕組みはないのかね?ということがアイク氏の着眼点でした。

スクリーンショット 2020-10-31 23.36.33.jpg

方法

そこでアイク氏はMozillaを突然退社してBraveブラウザの作成を始めます。さすがJavaScriptを生み出して、Mozillaを率いただけある人は違うなと思うのですが、曰く、突然モジラのCEOを辞任したアイクは、

生まれ変わったようにプログラミングを再開し、Brave というこれまでにないブラウザを開発した。
Brave は、Cookie の弊害を取り除くとともに、
すべてをトップダウンで決められるインターネットの仕組みを変えるものだった。

ということです(「グーグルが消える日 Life after Google(ジョージ・ギルダー著)」)。

結局何が変わるの?

本を読んだり公式サイトみるといろいろ書いてあるのですが、要するに下記のメリットがあります。

ユーザーのメリット

  • ユーザーはBraveブラウザ上での広告表示を完全に管理可能。ブロックすることも表示することも可能
  • ユーザーはブラウザ上に広告を表示させることで、金銭的な対価(batという暗号通貨に紐付けられたbatポイント)を得られる。コンテンツ製作者にも支払われる。
  • ユーザーは、その対価をコンテンツ製作者に寄付できる
  • ユーザーは個人情報を売られたり集められたりしない

出稿者のメリット

  • ユーザーの好意的な反応を期待できる

コンテンツ制作者のメリット

  • Adsenseやアフィリエイト以外の第3の収益化の方法が得られる
  • 過剰な広告にレイアウトを崩されない
  • Braveブラウザを使用するユーザーからの寄付を期待できる(コンテンツ自身が価値を持つようになる!)

という点です。結構こうやって考えてみると大きいですよね?

以上が
・Braveブラウザとはなんぞや
・クリエータの収益化の方法
・ユーザとして収益化する方法
という3構成の最初の部分でした。さて、それでは実際にいよいよ収益化する方法について記載していきたいと思います。Adsenseと併用可能でしたのでぜひ登録してみるといいと思います(Adsenseのように審査厳しくありませんでした)。
なお、WEB系じゃない方はここはスルーしてください。この下にユーザーとして使う方法があります。

Braveブラウザへの登録

こちらの公式サイトから設定をしていきます。
https://brave.com/ja/

Braveブラウザでクリエイター登録するために、大きく分けて2つのことを行います。

  • 1.クリエータ登録をして、収益化したいサイトを登録する
  • 2.プラグインを入れて、パブリッシャーIDを入力してサイトを認証する

1.クリエータ登録をして、サイトを登録する

トップページ右上の「クリエイターの方」というボタンから下記のページに飛びます。
https://creators.brave.com/?locale=ja


登録ボタンを押して、メールアドレスを入力します。


メールが届くので、そこに記載されたリンクを飛んで氏名を入力して完了です。非常に簡単ですね。
ログインするとこんな画面になります。


ここでサイトの登録をしていきます。右上の「チャンネルを登録」ボタンを押して、下記のカードから収益化させたいチャンネルを選択します。Twitterなども収益化できるようですが、私の場合はウェブサイトを選んでドメインを入力しました。


すると勝手にWordPressだと判定してくれました。(Angular React,Pythonのサイトだとどうなるかわかりませんでした。でも実際はad.txtに相当するテキストファイル作っているだけなので行けると思います)


画面の指示にしたがって、プラグインのインストールして、サイトを認証します。

プラグインのインストール

からzipファイルをダウンロードして、WordPressの管理画面から新しいプラグインとしてアップロードし、有効化します。

検証コードの追加してサイトを検証する

管理画面のプラグインの中にBrave Payments Verificationという行が追加されますのでそれを選択して、先の画面に表示された検証コードを追加します。あとは検証コードが表示されたページで検証ボタンを押せばOKです。


するとこういうメールが届きますのでこれでクリエイター登録は完了です。


最後にプラグインをdeactivateして終わりです。
(支払い先でPayPalと紐づける必要はありますが、これはPayPal口座があればすぐできますし、特にクリエイター登録という意味ではしなくても問題ないかと思います)

以上です。これでBraveブラウザでクリエイター登録ができました。あとはユーザーがBraveブラウザを介してアクセスしたら、コンテンツに対する支払いが振り込まれることとなります。(でもまだ未検証です。私は https://np-sys.com/ で登録したので、Braveブラウザで誰かアクセスしてみてくれたら振り込み額など追記します。また、こちらの投稿にコメントをいただけたら、記事の下にサイト一覧のような形でリンクを貼ろうと思います。ぜひご連絡ください)

ユーザーとしてBraveブラウザを使用する

上記はクリエーターとして登録する方法を説明しましたが、ユーザーとしてBraveブラウザを利用することもできます。Braveブラウザを使うと、ChromeやSafariと異なる3点のメリットがあります。

  • 広告を排除してコンテンツを閲覧できる
  • 広告を表示することも可能で、その場合広告閲覧によって収益が得られる
  • サイト作成者に投げ銭できる

このうち、サイト(広告)の閲覧によって収益が得られるというのは理解に苦しむところだと思います。私もそんな美味しい話があるものかと思いましたが、これはBraveブラウザの理念を踏まえると理解できます。つまり
「広告に基づくウェブのエコシステムをリセットし、広告主、コンテンツ・パブリッシャー、顧客の誰もが得をするウィン・ウィン型のソリューションを提供する」
ことをBraveブラウザが目的としているためです。まぁ投げ銭の元資金って感じなのだろうとは思いますし、こういう仕組みがないとなかなか広まらないですから、いい仕組みかと思います。

ユーザーとしてBraveブラウザを利用する方法

それではいよいよネット閲覧でお小遣いがもらえる謎のBraveブラウザのインストール方法です。と言っても公式からダウンロードしてインストールするだけです。
公式→ https://brave.com/ja/

インストール後、Rewardを有効にすれば収益化は完了します。個人データの集約などもしていないとのこと。


大体、広告一回見ると1円くらいです。最大でも1時間に5回しか広告を表示させられないので額としては大したことはありませんが、得られた収益をサイト製作者に寄付できることが大きいです。小さな投げ銭が集まることである程度大きな金額になれば、正当な方法でコンテンツ製作者に報いることができると思います。作った人がコンテンツを見てもらい、金銭という形で認めてもらえる、これって健全な社会の形ですよね?

まとめ

というわけで、この記事では次世代のBraveブラウザについて紹介しました。
1. (Googleの無料サービスに定義された)インターネットの仕組みを変える
2. コンテンツ製作者に適切に報いる
ことを目的とする従来とは全く異なるパラダイムから生まれたブラウザで、Google Adsenseに変わる方法(実際は並列して使えます)として利用できます。私は開発者としてもユーザーとしてもGoogleが非常に好きなのですが、Braveブラウザの理念にも共感します。Braveブラウザはとてもいい仕組みだと思いますし、何事にも多様性は大事なはずなので、Braveブラウザも使っていきたいとと思います。ぜひ皆さんもご検討してみてください。軽くて普通にいいブラウザでした。

最後になりますが、こちらの記事は、アメリカでベストセラーとなった「グーグルが消える日 Life after Google(ジョージ・ギルダー著)」を参考にしております。哲学的なことが多かったけど面白かったです。

アフィリエイトリンクはこちら。ぜひクリックして購入してね!
=> https://amazon.com

、、、、!?。


ぜひみんなで新しいインターネットを作っていきましょう!

pandasで条件抽出する2つの方法(単一条件、複数条件)

pandasで条件抽出する方法には、queryメソッドを使う場合と使わない場合がある。

A列に1-100、B列に101-200、 C列に201から300のデータが並んでいる100行のデータを考える。

df = pd.DataFrame({
    "A":[i for i in range(100)], 
    "B":[i+100 for i in range(100)], 
    "C":[i+200 for i in range(100)],])

メソッドを使用しない場合:

#A列が20未満を抜き出す
df = df[(df["A"]<20)]
#A列が20より大きく、かつ、50未満を抜き出す
df = df[(df["A"]>20)&(df["A"]<50)]
#A列が20より大きく、または、50未満を抜き出す
df = df[(df["Α"]>20)|(dF["B"]<50))

複数条件の場合、かっこで個々の条件を囲む必要がある。

queryメソッドを使用する場合:

単一条件での抽出:

#Aが20未満を抜き出す
df = df.query('A< 20')
df = df.query('(A > 20) and (A < 50)')
df = df.query('(A > 20) | (A < 50)')

個人的にはqueryを使わない方が好みです。

特定の文字列を含む行を取得したい場合はこちら

現場からの報告。製造業でAI/IoTを活用するために必要だと思うこと

製造業に勤めるエンジニアから見た製造業でAI/IoTを活用するために必要だと思うことです。 

はじめに

「2025年の崖」と言われているように、製造業にとってデジタルトランスフォーメーションが喫緊の課題となっております。新興国が品質とコストで猛追するなか、工場にいて日本の厳しさを感じるんですよね。
もはや「日本の品質って本当にいいの?」って思ってしまいます(まぁうちの工場だけかもしれないけど!)。
とはいえ人口減少する日本にとって、高付加価値品の製造が必要なのは明白であり、特にAI/IoTというのはその大きな試金石となっていると思います。いちメーカでAI/IoT担当(かっこ笑いw)をしている身から現場の感覚をお伝えできればと思います。

言いたいことは下記の3点です。
・本部機構にだけAI・IoT推進課を作っても無駄
・買い物だけでは不十分、プログラミングレベルの全体の底上げが必要
・既存のIT担当者は障害になる場合も

本部機構にだけAI・IoT推進課を作っても無駄

大企業で多いのは、工場とは離れた本部機構にAI/IoTの専門部署を作ることですよね。これ、本部だけに作るとうまくいかないパターンだと思います。というのは、AIを活用していくにあたって重要なのは、質のいいデータと現場にカスタマイズされたソフトだと思います。その点、本部にいる人が工場の既存のデータもよくわからないまま新しいデータを取るのって至難の技だと思います。加えて、新しくデータを取り始めたとしても日々起こるトラブルにすぐ対処できず工場任せにするようだと
本部「いいデータが上がってこないから解析できない」
工場「データとるのどんだけ大変だとおもってんねん!」
となる可能性が高いです。結局、本部だけに推進グループを設置するのは不十分で、工場にメインの担当チームを置かないとダメなんですよね。本部にグループ設置すること自身はいいことだと思いますが、本部だけでうまくいくなんてありえないのではないかと。

買い物だけでは不十分、プログラミングレベルの全体の底上げが必要

あとありがちなのが、買い物で済ませようってのも多くあると思います。やれTableauだ、やれDataRobotだ、そのほかにもDataSpiderやSensorCorpusなど高い買い物して「うちはAI/IoTやってる」と満足しているパターン。これって使いこなせないという意味で本当にもったいないと思います。日々の製造で活用するにはカスタマイズが必要で、どこかで自分でコード書く必要があります。この必要性を理解せずにツールを買っても片手落ちってものだと思います。

既存のIT担当者はむしろ障壁になることも

3つのなかでこれを一番いいたい。バブルの時代に建てられた工場なんかだともう30年近く経っています。うちの工場もそれくらいなのですが、こういった工場にありがちなのがシステムのレガシー化です。どんな感じかというと、例えばうちの工場ではOracleを使っているのですが、SQLでデータベースに接続すると怒られます。
既存のIT担当「接続して不安定になったらどうするんだ」
「・・・・」
Oracleにコマンドで接続してSQLでselect文発行して怒られるっていったいなんなんですか(30年前のGUIソフトでかちかちクリックしてデータを取得するのです)。。こんな状態でAI開発なんてむりですね。一悶着の末select文の発行だけさせてもらっても、なにかあると全部新しいコードがスケープゴートにされますので日々ひやひやものです。
Oracleに接続するbatファイル書いていて申し訳なさでいっぱいになるんですけど、かなしい。。悪いことしてるんだっけ!?加えてタチが悪いのは、既存の担当者の知識が一世代前のものだということです。問題起きて説明しても
「Pythonおれわからないから!!」やる気完全になくします。

対策

以上を踏まえて、製造業でAI/IoTを活用するためにわたしが思う必要な対策を下記に記載します。

本部にだけAI・IoT推進課を作っても無駄

→本部にはとても助かっています。でも、工場側にもチームが必要だと思います。
1. データの質を担保するインフラ担当、
2. データベース管理やAPI管理を行うバックエンド担当、
3. そして生技の業務に合わせてカスタマイズするフロントエンド担当

が必要です。共通化できるアルゴリズム開発などは本部に任せてもいいかもしれないですけど、でも、工場のなかにメインのチームが必要だと思います。

既存のIT担当者はむしろ邪魔になることも

→これきついですね。はっきりいって辟易しています。トラブル避けるために、AI/IoT用にはデータ回線、データベース、サーバは完全に分けられたら理想ですね。データベースはMySQLを立てて、サーバはGCPやAWSも使ってパソコンでもなんとかなるし、データ回線もセカンドイーサで分けちゃう。それでもデータ通信量が多くて他のソフト止まったと言われたら心の中で笑います、そして泣きます。

全体の底上げが必要

全員のプログラミングに対する理解が必要です。自分は関係ないと思っているひとたちのなかで根付かせるのは大変ですが、スタッフはある程度理解が必要なのではないでしょうか。「手順の決まっていることを自動化するツール」という名目で勉強してもらうのもいいと思います。

最後に

AI/IoT使いこなしたら素晴らしいと思うけど、日本の製造業課題多いなぁと。なにかにつけて古いし、固着してますよね。。工場いちから作り直したら簡単だけどね!って。あぁ、もう新興国の勝ちですね〜。・・・っいやいや!!

エキサイトモバイル WiFiとWiMAXの比較。エキサイトの契約から初期契約解除に至るまでの経緯

三重県でNP-Systemsという個人事務所をやっています。

WiMAXを3年使っていたのですが、住んでいる場所では回線が不安定なのでエキサイトモバイルWifiを契約してみました。Docomo回線を使用しているということで安定していそうだったので。ただ、結果的にWiMAX継続でエキサイトモバイルWifiは初期契約解除に至りました。契約の流れ、回線速度、初期契約解除の流れなどをレポートしたいと思います。

概要

エキサイトモバイルWifiは、2020年10月からサービスが開始された新しいWiFiサービスで、Docomo回線を利用しているというのが一番の特徴だと思います。

  • ドコモ4G/LTE回線のデータ通信専用SIM(物理SIM)を採用
  • 速度制限時でも最大700kbpsでデータ通信が使い放題
  • 毎月のデータ通信容量は使い放題で3980円

ということがHPには記載されています。WiMAXが住んでいる三重県だだと結構不安定だったので、Docomo回線なら安定しているだろうということで契約してみました。値段も安くなりますしね。

ただ、結論を言うと初期契約解除に至りました。

  • Docomo回線だけあって、確かに回線は常に安定
  • しかし、速度は0.8Mbpsしかでない(え?いつも速度制限なみでは、、でもブースト機能あります)
  • 上りは30Mbpsで高速

と言うことで解約に至りました。

回線速度は遅い代わりに、ブースト機能(https://www.kashi-mo.com/media/50035/)があるようで接続の最初に30Mbps程度が出ます、なのでWebページの閲覧は非常に早く感じます。値段が安いなりの工夫ですね。しかし読み込みたいものがブーストで読み込めない場合は多少待つことになります。また、動画についてはある程度バッファする必要があるのですが、これもブーストで対応できないのでものすごくスムーズと言うわけではないです。

ただ、上りは非常に安定していて、30Mbps程度は出ます。上りが速いのはいいですね。WiMaxだとよくて1,2Mbps程度なので。Docomoなので屋内や地下などでも使えるのでは?

契約してから商品が届くまで

契約から商品が届くまでの流れです。

  • 火曜日の22時ごろに契約申し込み。申し込み確認のメールを即受信。
  • 4日後にSIMとモバイル端末を別々に発送すると連絡。実際は到着予定日に合わせてメールがくる。
  • 木曜日の朝9時に両方到着

と言う流れです。発送連絡が初期契約解除の算出日になりますので、お試しで試したいという人には重要です。

届いてから使えるようになるまで設定

開封してSIMカード入れるだけですが、挿入の仕方は少し迷いました。なんとかなって30分後には使えるようになりました。

こちらのサイトがわかりやいです。SIMカード傷つけないように気をつけてください。

https://support.qtmobile.jp/manual/dv/310011800189.html

ルータ端末について

HUAWEI Mobile WiFi E5785です。軽いし使いやすいしよかったです。

HUAWEI Mobile WiFi E5785
使いやすいしこちらはいい感じ。
HUAWEI Mobile WiFi E5785

回線速度について

Docomo回線ということで一番期待していましたが、これはよくありませんでした。速度制限時に「最大700kbpsでデータ通信が使い放題」と書いてありますが、実際はいつもこれくらいのスピードしか出ませんでした(いつも0.8Mbps程度)。平日朝、夜、場所を変えてもおなじでした。回線は安定していたのはWiMAXよりよかったです。せめて安定して5Mbps出れば継続でしたが、さすがに遅かったのですぐ初期契約解除することにしました。

speed test result

初期契約解除について

初期契約解除は端末が届いて8日以内であれば違約金なしで解約できる制度です。法律で定められており、ちゃんと対応してくれます。申し込みをしてから数日後に「発送したよ」というメール連絡がくるのですが、その8日以内であれば返却が可能です。その場合、違約金などはかかりません。

ただ日数分割した月額費用はかかるということで私は461円かかりました。

初期契約解除は下記の項目を書いた紙をFAXする必要があります

  • ご契約者名(フリガナ)
  • ご契約者住所
  • ご連絡先電話番号
  • エキサイトID
  • 契約のご案内通知の受領日
  • 初期契約解除請求対象サービス名
  • 初期契約解除請求FAX発送日
  • 初期契約解除請求対象の電話番号(xxx-xxxx-xxxx)

「初期契約解除を請求します。」の一文

これを記載した紙をFAXすればいいです。なお初期契約解除請求対象の電話番号というのはWiFiの端末の電話番号ですので、これはSIMカード同封の紙に書いてあると思います。私は020から始まる番号でした。自分の電話番号を書かないように。紙の記入は手書きで大丈夫です。

私の流れは

  • 日曜日の21時に紙に書いてセブンイレブンからFAX
  • 月曜日の14時にFAXの受領連絡がメールであり
  • その中に端末とSIMカードの返却先が記載されているので、火曜日の朝にそこへ発送

という流れでした。

FAXすると翌日にこんなメールが届きます。

=========================
1.端末及びSIM カードの返却について
=========================

<返却先>
〒106-0047
東京都港区南麻布3丁目20‐1 Daiwa麻布テラス4F エキサイト株式会社
エキサイトモバイル WiFi 返却受付窓口

※送料はお客様負担になります。
(着払いで返却された場合、後日送料を請求いたします。)
※初期契約解除を通知された日から1ヶ⽉以内に、端末及びSIMカードの返却が確認ができない場合、WiFi ルーター損害⾦(初期契約解除)10,000 円/台を請求いたします。
※当社に返却された端末に故障⼜は破損が認められた場合、故障⼜は破損にかかる修理代⾦相当額を請求いたします。

=========================
2.ご請求について
=========================

<請求金額>
461円(税抜き)

初期契約解除手続きの場合、「解約事務手数料」の請求はございません。
ただし、初期契約解除までの期間に応じた本サービスの⽉額料⾦、契約締結費⽤及び本SIMカードの提供に要する費⽤等について、電気通信事業法が定める範囲内にて請求いたします。

弊社からの請求に関するお支払い方法は、クレジットカード払いのみとなります。
※クレジットカードの場合は、各カード会社の請求書明細をご確認ください。
※クレジットカードのお引き落としタイミングについては、お客様の各カード会社とのご契約内容によって異なります。
※ご請求金額内訳については、弊社ホームページ内マイページをご確認ください。

=========================
3.その他の注意点
=========================

初期契約解除のご請求書面を弊社が確認できた日をもって、契約解除いたします。
契約解除の手続き後は、契約解除の取り消しには応じられません。
(再度、新規契約のお手続きが必要となります)

以上でございます。

本件に関しまして、ご不明な点がございましたら、本メールに直接ご返信をお願いいたします。

あとは住所に発送すればOK。法律に定められてますし、まぁ可もなく不可もなく対応してくれます。ただメールの二次利用はやめてくださいというようなことが書いてあるのが伏線はってる感じ。

契約前にいざとなれば初期契約解除すればいいや(でもちゃんとできるかな)と思っていたのですが、無事にできてよかった反面もう少し回線速度が出れば初期契約解除しなかったのですがね。。せめて5 Mbpsはでてほしかったですが、1Mbpsを切ると厳しいです。屋内で繋がるのはいいと思います。

なので、結局私はWiMAX継続でした。まぁ回線の安定性と値段が重要ならエキサイトモバイルWiFi、速度重視ならWiMAXというのが私の結論です。

さすがにシステム開発をやっているのでねぇ。。

https://bb.excite.co.jp/wifi/

Angularでfirestoreのコレクションからデータ一覧を取得する

概要

Angular2以降でfirestoreのコレクションからデータ一覧を取得する方法。

読み込む

コレクションに対して、valueChanges()をsubscribeする。valueChanges()ってなんだ。。

import { Component, ViewChild } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { AngularFirestore } from '@angular/fire/firestore';

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  authUid:string;

  constructor(
    public auth: AuthService,
    private firestore:AngularFirestore,
  ) {
    this.initialize();
    }

  initialize(){
      this.auth.getAuthUid().subscribe(uid =>{
        console.log(uid,'e');
        this.authUid = uid;
      })

  }

  test(){
    this.firestore.collection('users')
      .doc('data').collection(this.authUid)
      .valueChanges().subscribe(value =>{
        console.log(value);
      },
      error =>{
        console.log('error');
      })
  };
}

オブジェクトの配列で帰ってくる。

書き込み

こちらの方がシンプル

                      this.firestore.collection('users')
                        .doc('data')
                        .collection(this.authUid)
                        .doc(String(new Date().getTime()))
                        //.doc('test')
                        .set({
                          'title':this.originalImgName,
                          'content':data,
                          'filename':this.originalImgName,
                          'lastUpade':String(new Date().getTime()),
                        });

Angular(Ionic)で相方向バインディングを行う最低限のテンプレート

概要

Angularでは、相方向バインディングを行うための二つの方法があります。一つはテンプレート駆動型、もう一つはリアクティブフォールによるものです。

こちらの公式

https://angular.jp/guide/forms-overview

によると、

  • リアクティブフォーム はより堅牢です。よりスケーラブルで、再利用しやすく、そしてテストがしやすいです。フォームがアプリケーションの重要なパーツである場合、またはアプリケーションの構築にリアクティブパターンをすでに使用している場合は、リアクティブフォームを使用してください。
  • テンプレート駆動フォーム は、メーリングリストの申し込みフォームなどの単純なフォームをアプリに追加するのに役立ちます。アプリに追加するのは簡単ですが、リアクティブフォームほどスケーラビリティはありません。テンプレートでのみ管理できるとても基本的なフォーム要件とロジックをもつような場合は、テンプレート駆動フォームを使用してください。

ということです。

最低限のリアクティブフォーム

使いたいページのmodule、tsファイル、HTMLの3つに変更を加えます。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { Tab1p5PageRoutingModule } from './tab1p5-routing.module';
import { Tab1p5Page } from './tab1p5.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    Tab1p5PageRoutingModule,
    ReactiveFormsModule
  ],
  declarations: [Tab1p5Page]
})
export class Tab1p5PageModule {}
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-tab1p5',
  templateUrl: './tab1p5.page.html',
  styleUrls: ['./tab1p5.page.scss'],
})
export class Tab1p5Page implements OnInit {
  favoriteColorControl:formControl;

  constructor() { }

  ngOnInit() {
    this.favoriteColorControl = new FormControl('');
  }

}
<ion-header>
  <ion-toolbar>
    <ion-title>tab1p5</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  Favorite Color: <input type="text" [formControl]="favoriteColorControl">
{{favoriteColorControl.value}}
</ion-content>

Ionic(Angular)でズーム可能なグラフをchart.jsで作成する

概要

Ionic(Angular)でマウスホイールで拡大や移動などが可能なグラフを作成します

方法

標準のchart.jsに加えて、charjs-plugin-zoomを導入します。

通常のchart.jsの導入方法はこちら。

https://np-sys.com/general/438/

これをズーム可能にするためにcharjs-plugin-zoomを導入します。

npm i chartjs-plugin-zoom -s

そしたら、グラフを作成しているpageのtsファイルにて(今回はsrc/app/tab2/tab2.page.tsでした)

import 'chartjs-plugin-zoom';

を上部に追加して、あとはOptionsのなかに

          plugins: {
            zoom: {
              pan: {
                enabled: true,
                mode: 'xy'
              },
              zoom: {
                enabled: true,
                mode: 'xy'
              }
            }
          },

のように指定するだけです。

冗長ですが、コードを貼り付けておきます。貼り付ける場所などの確認にご使用ください。

import { Component, ViewChild } from '@angular/core';
import { Chart } from 'chart.js';
import 'chartjs-plugin-zoom';

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  @ViewChild('myChart') myChart;
  @ViewChild('myChart2') myChart2;
  @ViewChild('myChart3') myChart3;
  bars: any;
  colorArray: any;
  myNum:number;
  data:any=  {
        type: 'line',
        data: {
  				labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'Aug'],
  				datasets: [{
  					label: 'My First dataset',
            backgroundColor: 'rgb(150, 194, 129)', // array should have same number of elements as number of dataset
            borderColor: 'rgb(150, 194, 129)',// array should have same number of elements as number of dataset
  					data: [
  						12,
  						14,
              12,
  						14,
              12,
  						41,
              12,
  						4,
  					],
  					fill: false,
  				}, {
  					label: 'My Second dataset',
  					fill: false,
            backgroundColor: 'rgb(50, 194, 129)', // array should have same number of elements as number of dataset
            borderColor: 'rgb(50, 194, 129)',// array should have same number of elements as number of dataset
  					data: [
              2,
  						4,
              2,
  						4,
              2,
  						4,
              20,
  						142,
  					],
  				}]
  			},
        options: {
          scales: {
            yAxes: [{
              ticks: {
                beginAtZero: true,
                min: 0,
                max: 200
              }
            }]
          },
          responsive:true,
          maintainAspectRatio: false,
          plugins: {
            zoom: {
              pan: {
                enabled: true,
                mode: 'xy'
              },
              zoom: {
                enabled: true,
                mode: 'xy'
              }
            }
          },
          }
      }

      title = 'app';

      columnDefs = [
          {headerName: 'Make', field: 'make' },
          {headerName: 'Model', field: 'model' },
          {headerName: 'Price', field: 'price'},
          {headerName: 'Make', field: 'make2' },
          {headerName: 'Model', field: 'model2' },
          {headerName: 'Price', field: 'price2'},
          {headerName: 'Make', field: 'make3' },
          {headerName: 'Model', field: 'model3' },
          {headerName: 'Price', field: 'price3'},
      ];

      rowData = [
          { make: 'Toyota', model: 'Celica', price: 35000, make2: 'Toyota', model2: 'Celica', price2: 35000, make3: 'Toyota', model3: 'Celica', price3: 35000},
          { make: 'Ford', model: 'Mondeo', price: 32000 },
          { make: 'Porsche', model: 'Boxter', price: 72000 },
          { make: 'Toyota', model: 'Celica', price: 35000, make2: 'Toyota', model2: 'Celica', price2: 35000, make3: 'Toyota', model3: 'Celica', price3: 35000},
          { make: 'Ford', model: 'Mondeo', price: 32000 },
          { make: 'Porsche', model: 'Boxter', price: 72000 },
          { make: 'Toyota', model: 'Celica', price: 35000, make2: 'Toyota', model2: 'Celica', price2: 35000, make3: 'Toyota', model3: 'Celica', price3: 35000},
          { make: 'Ford', model: 'Mondeo', price: 32000 },
          { make: 'Porsche', model: 'Boxter', price: 72000 }
      ];

  constructor(
  ) { }

  ionViewDidEnter() {
    this.createBarChart(this.myChart);
    this.createBarChart(this.myChart2);
    this.createBarChart(this.myChart3);
  }

  onClicked(){
    let v = window.innerWidth;
    alert(v);
  }


  createBarChart(canvasId) {
    this.bars = new Chart(canvasId.nativeElement, this.data);
  }

}

Ionicにag-gridでエクセルライクな表を導入する

背景

Webで表をいれたいときってあります。主に3つの方法があると考えております。

・Tableを使う

・FlexBoxを使う

・ライブラリを使う

今回は、ライブラリを使う方法としてAg-gridを導入します。先頭の二つは簡単な表には使用できると思いますが、例えば1万行を超えるようなデータを表にして、Excelのようにセルごとに編集して色分け、、などをするためにはAg-gridを使うのが最適だと思います。

https://www.ag-grid.com/

無料でもできる範囲はかなりひろいです。

環境

$ ionic info
Ionic:
   Ionic CLI : 6.4.1
Utility:
   cordova-res (update available: 0.14.0) : 0.9.0
   native-run (update available: 1.0.0)   : 0.2.9
System:
   NodeJS : v12.10.0
   npm    : 6.14.2
   OS     : macOS Mojave

流れ

ライブラリをnmpでインストールしたあと、プロジェクト全体のレイアウトを管理するglobal.scssで読み込みます。その後、使いたいページのpage.module.tsで読み込んだ後、page.tsで情報を設定して、page.htmlにタグを設置します。

参考文献

https://www.ag-grid.com/angular-grid/

インストール

パッケージのインストールを行います

npm install --save ag-grid-community ag-grid-angular

すると、node_modulesにライブラリが配置されます。

scssの読み込み

Angularではstyles.scss に書き込みますが、ionicではglobal.scssに書き込みます。

@import "../node_modules/ag-grid-community/src/styles/ag-grid.scss";
@import "../node_modules/ag-grid-community/src/styles/ag-theme-alpine/sass/ag-theme-alpine-mixin.scss";

.ag-theme-alpine {
    @include ag-theme-alpine();
}

あとは、これを使いたいページのモジュールで読み込むだけです。ag-gridはモジュールなので、tsファイルで読み込む必要はありません

表を作る

以上で準備完了です。あとは使いたいpageで読み込みます。下記はsrc/app/app.module.ts、src/app/app.component.ts,src/app/app.module.htmlの場合ですが、src/app/tab1/tab1.module.tsなどでも同じです。その場合、src/app/app.module.tsには設定する必要はないです。

それではsrc/app/app.module.tsから編集します。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { AgGridModule } from 'ag-grid-angular';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AgGridModule.withComponents([])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Copy

次にsrc/app.component.tsで情報を設定します。

import { Component } from '@angular/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent {
    title = 'app';

    columnDefs = [
        {headerName: 'Make', field: 'make' },
        {headerName: 'Model', field: 'model' },
        {headerName: 'Price', field: 'price'}
    ];

    rowData = [
        { make: 'Toyota', model: 'Celica', price: 35000 },
        { make: 'Ford', model: 'Mondeo', price: 32000 },
        { make: 'Porsche', model: 'Boxter', price: 72000 }
    ];
}

最後にapp/app.component.htmlファイルでタグを設置します。

<ag-grid-angular
    style="width: 500px; height: 500px;"
    class="ag-theme-alpine"
    [rowData]="rowData"
    [columnDefs]="columnDefs"
    >
</ag-grid-angular>

そうすると、下記のような表が表示されるはずです。

all-in-one-migrationでインポートしてログインできなくなった

タイトルの通り、サーバ上のデータをexportしてxammpのローカルにインポートしたらログインできなくなった。

’http://localhost:8080/phpmyadmin/’でデータベースの情報を見たら、メールアドレスとして使用していたユーザー名が変わっていた。メールアドレスは独自ドメインのものを使っていたのだが、@以降がlocalhostに変わっていた。

また、site-guardと干渉することもあるということなので、import/export時にはそのプラグインをオフにした方が良さそう。

独自ドメインからローカルへ

site-guardをオフにする

export/importする

’http://localhost:8080/phpmyadmin/’でユーザ名を書き換える

ということになりそう。あまり普段からやる感じでもなくなるけど。

https://webst8.com/blog/wordpress-all-in-one-migration/

個人でプレスリリースしたら、Yahoo!に載ったった

先日公開した個人アプリをプレスリリースしたら、ケータイウォッチ経由でYahoo!に取り上げてもらいました。
どう書くか、どこに送るか、そのタイトル、文面は?などわからないことばらけで進めたのですが、なんとか成功体験を1つつかめたのは良かったです。

https://k-tai.watch.impress.co.jp/docs/news/1190264.html

プレスリリースの書き方について迷われている方もいると思うので、全文面を公開します・・!

取り上げられた様子がこちら。

こちらがその文面です。
よろしくお願いいたします

理系男子必見!「数マニア -数学は美しい-」を6月12日よりリリース
~ひたすら円周率を読み上げる究極のリラクゼーションアプリ~

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Androidアプリ「数マニア -数学は美しい-」を提供開始
- 「数学ガール」「博士の愛した数式」など数学をテーマにした小説や映画が有名ですが、「数マニア」は、美しいヒーリングミュージックの代わりに、数をひたすら読み上げて安眠へと導く少し変わったリラクゼーションアプリです。ひたすら続く数字を聞きながら、深い眠りへとあなたを導きます。
https://play.google.com/store/apps/details?id=com.rainbows.read_pi
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
夫婦でスマートフォンアプリケーション開発を手掛ける「なないろプロジェクト」(所在地:**)は、リラクゼーションアプリ「数マニア -数学は美しい-」を、2018年6月12日より正式にリリースしました。これまで、数学定数を1万桁読み上げるリラクゼーションアプリはありませんでした。
【Google Play ストア ダウンロードページ】
https://play.google.com/store/apps/details?id=com.rainbows.read_pi


■アプリ概要
アプリ名 :「数マニア -数学は美しい-」
配信開始 :2019年 6 月 12 日
料金 :無料(広告モデル)
ジャンル :音楽&オーディオ
対応 OS :Androidバージョン 4.1以降

■ターゲット
理系の男子大学生

■背景
今回開発したのは夫である**。開発背景をこのように語ります。
「高校の数学クラブで数学オリンピックを目指した高校生も、しがない会社員となりました。それでも数の不思議への想いは変わらず、円周率をひたすら読み上げるアプリを作りたくなりました。
円周率をピアノで弾くととても美しいことをご存知ですか。無味乾燥な数字の羅列も、実際に聞いてみると不思議な気持ちにさせてくれます。寝る前のリラックスミュージックの代わりにきいてみてください。『どうして円周率にはゼロが出てこないの?』『どうして同じ数字がこんなに連続するの?』そんな疑問をもとにだんだん眠くなるはず。。きっと数学マニアだけではなく、みんなに響きます」

■3つの特長
1:円周率以外にも、アペリー定数、オメガ定数など5つの定数を収録
当アプリは、数学的に興味深い性質を持つ定数を5つ厳選しました。
円周率や黄金比などだれもが知っている定数から、アペリー定数、オメガ定数など数学的に奥深いものも掲載されています。それぞれの定数には、小ネタも併せて掲載されているので、友人とのちょっとした雑談にも重宝します。

2:スマホアプリ初!5つの無理数を1万桁まで読み上げ可能
当アプリは、Androidの提供するTTS(Text to Speech)機能を用いて、それぞれの定数を1万桁読み上げます。数列を掲載するWebページは数多くありますが、1万桁を読み上げるリラクゼーションアプリはこれまでありませんでした。

3:読み上げ言語は、英語、日本語に対応
「数字を読み上げられると目が冴えちゃう」という方に配慮して、読み上げは日本語、英語に対応しています。気分に合わせて好きな言語が選択可能です。

■今後の展開
今後の展開について、開発者はこのように語ります。
「このアプリ製作を通じて、数学の奥深さを改めて感じることができました。興奮しすぎて眠れないときは、わたしもこのアプリでリラックスしたいと思います。少しでも多くの方に興味を持っていただけると幸いです」

■会社概要
組織名(個人開発者) :なないろプロジェクト
(英語名Awesome Rainbows)
代表者  :実名
所在地  :実際の住所
■本件に関するお問い合わせ先
組織名 :なないろプロジェクト
担当者  :実名
所在地  :TEL:携帯番号
Email :awesome.rainbows7じーめいる

是非、参考にしてみてください。
また、もっとこうしたらというアドバイスもお願いいたします!

Pandasでcsvを読み込む

高機能なデータハンドリングが魅力のPythonですが、Pandasを使用すると複雑な処理を1行でできるので便利です。よくわからないけど、とりあえずPythonでデータを操作したいというなら、Pandasでデータを読み込めば間違いありません。

[chat face=”man1″ name=”” align=”left” border=”gray” bg=”none” style=””]汎用的なデータの読み込みならPandasがおススメ![/chat]

そもそもPandasってなんだっけ?

Pandasは、データハンドリング用で使われる最も有名なライブラリのひとつです。
Pythonは本体には最小限の機能しか備えておりません。パソコンにソフトをインストールしてできることを増やすように、Pythonは、追加のパッケージ(ライブラリ)をインストールして様々なことができるようになります。Pandasは、データ処理用のライブラリです。データの読み込みは様々な方法でできますが、最も覚えることが少なく、汎用的な手法として、Pandasでのデータ読み込みがおススメです。
例えばPandasは下記のようなときに使用できます

・Excelデータの読み込み
・CSV形式の読み込み
・txtデータの読み込み

どうやってインストールするの?

Pandasのインストール方法は、大きく分けて二つあります。一つは、Pythonの本体に単独でインストールする方法。もう一つは、Anacondaを利用する方法です。Anacondaとは、よく使用するパッケージをPython本体と一緒にダウンロードできる拡張版Pythonのイメージです。Anacondaは下記からインストールできますが、その場合、すでにPandasは自動でインストールされていますのでそれ以上の環境構築は必要ありません。

pipでインストールする場合:

pip install pandas

Anacondaでインストールする場合(おすすめ):
https://www.anaconda.com/で最新版をインストールすればOK.他の言語でもいえることですが、追加ライブラリのバージョンは、合わせておかないと動きません。これを依存関係といいますが、これらを整えて配布されているのはAnacondaです。Continuum社の有料サービスの無料版というのも安心です。

ホップ:Pandasでのcsvデータを読み込むには?

それでは、実際にPandasでcsvのデータを読み込みます。コードをGitHubにあげておくので、そちらからダウンロードかまいません。web上でPythonを実行できるpaizaのページからコードを貼り付けて実行していただいてもいいと思います。
paizaのページ:https://paiza.io/projects/featured?language=python3

GitHub;https://github.com/awesomerainbows/wp_read_csv

もし初めての場合、どこに何をおいたらいいかわからないと思います。そのときは、読み込みたいcsvとpythonのファイルは同じ階層においてください。おススメは、デスクトップにtestというフォルダを作成して、そこにpythonファイルとcsvの2つを配置することです。
こんな感じです。

Pythonコードは下記です。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pandas as pd
df = pd.read_csv("hogehoge.csv")#ファイル名は、クオテーションで囲んでください。もしtest.csvという名前なら、"hogehoge.csv"は、"test.csv"です

print(df)#読み込んだデータをすべて表示
print(df.head())#最初の5行程度を表示
print(df.shape)#読み込んだデータの行数と列数を表示。100行2列なら(100,2)です

[chat face=”man1″ name=”” align=”left” border=”gray” bg=”none” style=””]全然読み込めないんだけど[/chat]

[chat face=”man1″ name=”” align=”right” border=”gray” bg=”none” style=””]ファイル名のあとに読み込む条件を指定すれば読み込めるよ[/chat]

読み込むことはできましたでしょうか。
整形されたデータの場合はこれで読み込めたはずです。
ところが、実際扱うデータは往々にしてエラーを吐いてしまいます。
その場合、下記の項目をチェックして、読み込む条件を指定して読み込んでみてください。

エラーが出る場合、ポイントは下記になります。列名(header)が先頭にあるかどうか
行番号(index)がデータに含まれるかどうか
列名のないデータだけのファイルかどうか

列名(header)が先頭にない場合の読み込み

この場合、下記のコードで1行目と2行目をスキップして読み込みますので下記になります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pandas as pd
df = pd.read_csv("hogehoge.csv",skiprows=2)#skiprowsの引数を指定することで先頭の2行を無視します

print(df)#読み込んだデータをすべて表示
print(df.head())#最初の5行程度を表示
print(df.shape)#読み込んだデータの行数と列数を表示。100行2列なら(100,2)です

[chat face=”man1″ name=”” align=”left” border=”gray” bg=”none” style=””]df = pd.read_csv(“hogehogehoge.csv”,header=2)と列名を指定しても読み込めます[/chat]

[chat face=”man1″ name=”” align=”right” border=”gray” bg=”none” style=””]0から数えるから、Excelでいう3行目は2で指定するんだね[/chat]

行番号(index)がデータに含まれる場合

この場合は、1列目はindexであると明示的に指定します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pandas as pd
df = pd.read_csv("hogehoge.csv",index_col=0))#indexは0列目だと明示します

print(df)#読み込んだデータをすべて表示
print(df.head())#最初の5行程度を表示
print(df.shape)#読み込んだデータの行数と列数を表示。100行2列なら(100,2)です
列名がデータにない場合

この場合、1行目からデータであるということを指定する必要があります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pandas as pd
df = pd.read_csv("hogehoge.csv",header=None))#header=Noneでカラム名はないことを明示します

print(df)#読み込んだデータをすべて表示
print(df.head())#最初の5行程度を表示
print(df.shape)#読み込んだデータの行数と列数を表示。100行2列なら(100,2)です

列名を作成しながら読み込みたい場合、

df=pd.read_csv(“hogehoge.csv”,header=None,names=[“a”,”b”,”c”,”d”])

のようにnamesの引数で指定します

その他の場合

その他さまざまな条件を指定してデータを読み込むことができますが、詳細は公式ドキュメントを参照していただくのがよいと思いますので、最後によく使う指定条件をまとめたいと思います。

◎ファイルが大きすぎる
⇒nrows:nrows=100のように読み込む行数をint型で指定
◎なぜかエラーが起きる
⇒engine:pythonで読み込むかCで読み込むかを指定。engine=”python”と指定すると柔軟に読み込める
◎日本語が文字化けする
⇒encoding:encoding=”shift-jis”で読み込んでみる

よろしくお願いします。
以上、お疲れさまでした。

CordovaでjavaScriptを1行ずつデバッグする

Chrome Remote Web Inspector によるデバッグがよさそう。
参考にしたサイトはこちら。

https://mobilefirstplatform.ibmcloud.com/tutorials/ja/foundation/8.0/application-development/debugging-applications/#debugging-with-chrome-remote-web-inspector

半日ほど迷った。ポイントは、アンドロイドの端末で「usbデバッグを有効化」することと、アプリ側にもやり取りを許可するためのソースを埋め込まないといけないこと。ソースはJavascriptではなくて、Javaなので、どこに書くか迷った。結局追加プラグインのファイルにJavaのファイルがあったので、そこに張り付けた。

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ WebView.setWebContentsDebuggingEnabled(true); }

一度実行されたらchromeとの接続には支障がないとのことで、プラグイン呼び出しのメインの関数のなかに記載した。なお、JAVAのファイルの中でパッケージをインストールする必要があるので、そのファイルの上のほうで下記コードも書き加えた。

import android.os.Build;import android.util.Log;import android.content.pm.ApplicationInfo;import android.webkit.WebView;

import android.util.Log;import android.content.pm.ApplicationInfo;import android.webkit.WebView;

書き加えたのはcordova-plugin-admobproプラグインのなかの
AdMobPlugin.javaというファイルで、

@Override protected void pluginInitialize() { super.pluginInitialize(); // TODO: any init code }という部分を

@Override protected void pluginInitialize() { super.pluginInitialize(); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ WebView.setWebContentsDebuggingEnabled(true); }; // TODO: any init code }と書き換えた

Extract android app’s package name

windows10

if you are able to use use adb commands already, then you can get package name as below.

extract android app's package name

matplotlibで日本語を表示(python)

matplotlibで日本語を扱いたいとき、フォントをダウンロードして、matplotlibrcファイルを修正して、、みたいな方法が正統派なのかもしれない。
でも職場のパソコンに一斉にそういう設定ができますか?みたいなときがある。

1.フォントをダウンロードして、C:\Users\**\Anaconda3\Lib\site-packages\matplotlib\mpl-data\fonts\ttfの中にコピペをする
2.ファイルのなかにigfont = {‘family’:’IPAexGothic’}という文字列を追加しておく

これだけでよさそう。これなら自動でできそう。
ちなみにつまったところとして、コピペするファイルはipaexg.ttfとipaexm.ttfの2つだということ。一方だけでをコピペするだけだとうまくいきませんでした。フォントのダウンロード先は下記。

https://ipafont.ipa.go.jp/node26

バラ十字会(Amorc)の正体。薔薇十字団とフリーメイソンとの関係はあるの?

絹田
絹田

4年間名古屋で活動していた会員経験を基に説明するよ.

4年間会員をしていましたが,2020年に名古屋支部の役員の休会されたのを機に休会しました.

3000円の月額が少し高いというように感じていたのも事実です.以降は休会半年前くらいの2020年初めの記事です.

2021年8月に追記

t

わたしはバラ十字会に入会して4年目になります。バラ十字!?なにそれ(笑)と妻に言われたものですが、なんだかんだ続いています。「怪しさ満点だな」という印象でしたが、HPに載っているオバマ大統領の推薦書をみて興味本位で入りました。ここでは実際にどんな活動をしているかお伝えしたいと思います。

バラ十字会とは結局なんなのか?怪しい組織なのか

HPを見るといろいろかいてあります。

・人生の意味・意義・理由を知りたいあなたへ

・人生を知り、豊かに生きる叡智を学ぶ

・世界の歴史の偉人たちが学んできた成功哲学

2016年に入会を検討したとき,ネットで評判を調べてもなかなか出ず実態がよくわからなくて困りました.
バラ十字会は
①月に一度送られてくる薄めの本を毎週読む。3ヶ月ごとに理解度確認テストのようなものを送付する
②毎月ないしは隔月で会合に参加する
という通信教育を行なっているNPO法人です。入っている感覚として、新興宗教かと聞かれるとそうではないと思います。入ったところでそれ以上の勧誘はありませんし、教義を強制されることもありません。物品の購入で寄付することはできますが、それはカトリック教会などでも教会に売店があるのと同じですし、購入を強制されたと感じたことは一切ありません。感覚でいうと、啓蒙・自助団体にちかいです。

とはいえ、バラ十字会は良くも悪くもぶっとんだスピリチュアル団体だと感じました。人間にはソウルと呼ばれる輪廻を繰り返す主体が存在し、人生を通じてその本質を開花させていくという思想です.ソウルを薔薇で表現し、肉体を十字で表現し、その組み合わせがアイコンに使われている薔薇十字です。学者を含む著名人も入会しており、海外では比較的知られた国際団体でもあります。

(強制はされないものの薔薇十字会の立場でいうと)特徴的なところでいうと、

・身体と霊魂(ソウルと呼ぶ)が存在する
・生まれ変わりはあり、ソウルは人生を通じて開花している
・この世界を超えた世界が存在する

などの考えは一般にはかなり衝撃だと思います。また、(強制はされないものの)会合に出ると全面に押し出されるのでこれも他にはない特徴だと思います。まぁ結構私は好きですけどね、

母体は?正体は?

ネットで検索すると薔薇十字団という昔の団体が出てくると思います。ただこれ自身は直接関係あるわけではないと思います。フリーメイソンとの違いみたいなのも聞かれるのですが、向こうのほうが有名です。本質的には変わらないと思いますが、もし人脈構築を目的とするならフリーメイソンの方がいいと思います。


日本ではマイナーなバラ十字会ですが、実はかなり大きな組織だと感じることがあります。
たとえば日本バラ十字会のHPには、オバマ大統領の祝電が掲載されています。いわく、バラ十字会自身は、古代エジプトの思想から生まれ、ギリシャなどの哲学を踏まえ、さらに先進的な科学的な知見を取り入れた教義を伝えるといっております。実態は上に述べたようなNPO法人で、4年経ってもまだ全体がつかめていないというのが正直な感想で、ときどき隔月で行われている会合で、日本にビジネスの出張できたのでついでに顔を出した、というフランス人がきたりしたこともありました。

どんな人がいるの?バラ十字の思想とは

HPに公開されている情報以上のことは言わないでくださいということなのですが、参加している人は会社員だったり学校関係者だったり主婦であったりです。私はNP-Systemsという個人事務所をやっています。精神的なものに惹かれているっていう点では皆同じだと思います。女性がやや多いです。
あらためてになりますが、バラ十字の思想としては
・身体と霊魂(ソウルと呼ぶ)が存在する
・生まれ変わりはあり、ソウルは人生を通じて開花している
・この世界を超えた世界が存在する
あたりは特徴的な思想です。古代エジプトから生まれ現代に続く知識を体系立てて伝えるとしています。シンボルはこん感じで、ソウルが身体を通じて開花していく様子を示しているとされています。

フリーメイソンとの関係は?秘密結社なの?

昔は秘密結社だったようです。いまは通信教育(!)です。一般には公開されておりません。(適切な準備をしてからじゃないとうまく教義を伝えられないからという理由です)フリーメイソンも似たイメージですが、フリーメイソンほどお金はかからないと思います。あと、フリーメイソンは紹介制なのですがバラ十字会は誰でも入会できます。フリーメイソンほど有名ではないので、興味本位で入る人が少ない分静かに過ごせるかなとは思います。

バラ十字会への疑問点

わたしは理系で、むかしは無神論者でした。科学しか信じないような性格でしたが、その目線であえて批判的な点をあげたいと思います

  • 自らの組織の考えが歴史的にも科学的にももっとも正当・正しいと思っている
  • 若い人が少ない

という2点です。

1点目については、例えば「前世があるか」「過去世があるか」「7という数字は神秘的か」「魂はいつ人体に宿るのか」といった事柄は、実際はわからないというのが真実だと思うのです。それを踏まえた上で、「でもわたしは個人的にはそうだと思います」というならわかりますが、バラ十字のスタンスは「あなたは知らないし信じないかもしれないけど、それが実は絶対的な真実なんです、強制はしないし思想は自由だと思うけど」というスタンスなのです。これを少し独善的だと感じる時があります。また、自分の意見をいうときに「科学的に正しい」「神秘学的に正しい」というのもやめた方がいいかと思います。性格には、合理的な判断によりこう考えている、ということだと思います。

2つ目については、まぁ高齢化の日本社会ではどうしようもないかな。。ちなみに男女比は男性3.5に女性6.5のイメージです。

おすすめなの?

わたしの意見だと、自らの本質であるソウルというものがあり、それが人生を通じて花開くことを目指すということに共感を感じられるか次第だと思います。無神論者で人間は肉体以上のものではないと思うなら難しいと思いますが、自分の本質は意識とは別のところにあるなにか高尚なものかもしれない、それに触れたい、近づきたいと思うなら、バラ十字会はベルトコンベアのような役割を果たすかもしれません。

また、個人的に会合で面白いと思ったことがあるます。
・幽霊の話、前世のはなし?ぜんぜんOK!
わたしの参加している地区を主導してくれている方はとてもいい女性ですが、お茶会のときに「どうぞここでは普段できない話を(笑)」と言ってくれたりすると和みます。たしかに前世、霊魂、秘密結社、話題はなんでもありです。
・会合ではエプロンのようなものをする。ふんどしに見えないこともないが、それを若い女性がしているのがちょっと面白い
強制はされませんが、薔薇十字会自身はかなりコアな思想をもっています。会合では瞑想のようなものを全員で最初の1時間程度おこなうのですが、建前上こう振舞ってくださいとか、これを着用してくださいとか言われます。別に思想を強要するわけではないですし、マナーのイメージなので問題ないと思いますが、ちょっとそういうところがどきどきしたりします。
・ときどき「こんな人が生活していたのか」と思うような人が参加していたりする。
以前、会合に初参加のひとが、古参の会員に「前世以来、ひさしぶり!」と挨拶していたのはびっくりした(笑)

どこで活動してるの?

会合は規模の大きさにしたがっていろんな名前がありますが、東京の大きなものだと入会してすぐに参加するのは難しいですが、3ヶ月くらいするとどの会合にも参加できるようになります。
東京では、板橋で毎月会合があります。
名古屋と奇数月に、金沢では偶数月に行なっています。
あとは札幌と大阪も定期的におこなっていますね。岡山もやっているようです。

実際に参加するとどういう人がいるのかわかりますし、迷っているなら参加するといいと思います。2回くらいきてやめてしまう人が多い印象ですが、それも問題ないとされている空気ですし。「フリーメイソンとか秘密結社に興味ある」「前世療法を仕事にしてて興味あって」みたいな人もいますし、いたって普通な人もいます。

まぁ精神的なことに興味があるならきてみてもいいんじゃないかと思います。

絹田
絹田

読んでくれてありがとう.これからもよろしくね

Pythonでwordpressに自動で投稿する(python-wordpress-xmlrpc)

絹田
絹田

プラグインやライブラリをPython側に入れるかWordPres側に入れるかで2通りの方法があるよ

自動投稿する際,プラグインやライブラリをPython側に入れるかWordPres側に入れるかで2通りの方法があります.

  • REST:WordPressの「WP REST API」プラグインを使用する
  • XML-RPC:WordPressにデフォルトで備わっているwordpresのxmlrpcのインターフェースを利用する

RESTの方が汎用性は高い一方で、テーマによっては使えないことがあります。XML-rpcの場合は昔からあるので追加設定はいらず安定感はあります。Pythonを使ってxmlrpcでWordPressサイトを操作する場合、python-wordpress-xmlrpcをpipすれば使えるようになります。私はWordPressのプラグインを増やしたくなので後者の方を利用するようにしています。

環境

動作環境はWindows10 64bitでPython3.6.5(Anaconda3.5.1-0)。 Mac Python3.8とXserverのターミナル(Python3.8)からもうまく動作しました。

コード

pip install python-wordpress-xmlrpc

でライブラリを入れました。
(conda install ***だとファイルが見つからなかったので、 Anaconda環境であったがpipで入れました)

あとは下記のコードを実行するだけです。

新規に投稿する場合


# -*- coding: utf-8 -*-
import time
from datetime import datetime
from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods.users import GetUserInfo
from wordpress_xmlrpc.methods.posts import GetPosts, NewPost
def main():
    """
    変数を定義
    """
    id="******"
    password="******"
    #idとpasswordはwordpressの管理画面に入るためのもの

    url="https:***/xmlrpc.php"
    #第3者が閲覧するURLの後ろに/xmlrpc.phpをつける。
    #ワードプレスの管理画面の後ろにつけるとエラーになった

    which="publish"
    #which="draft"
    #下書きに投稿するか本番で投稿するか選択

    """
    クライアントの呼び出しなど
    """
    wp = Client(url, id,password)
    post = WordPressPost()

    """
    実際に投稿する
    """
    post.post_status = which
    post.title = "タイトルをここに書く"
    post.content = "本文をここに書く"
    post.terms_names = {
    "post_tag": ['希望するtag1', '希望するtag2'],
    "category": ['希望するカテゴリー1', '希望するカテゴリー2'],
    }
    #過去に投稿した記事としたい場合、投稿日をここで指定。例として2018年1月1日10時5分10秒に投稿した例を示す。
    post.date=datetime.strptime("2018/1/01 10:05:10","%Y/%m/%d %H:%M:%S")
    wp.call(NewPost(post))

if __name__=="__main__":
   main()

編集する場合

編集をする場合、編集したい記事のIDを取得したあと、その記事に対して操作します。

1.まず編集したい記事のIDを取得


# -*- coding: utf-8 -*-
import time
from datetime import datetime
from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods.users import GetUserInfo
from wordpress_xmlrpc.methods.posts import GetPost, GetPosts, NewPost, EditPost

def main():
    id="******"
    password="******"
    #idとpasswordはwordpressの管理画面に入るためのもの

    url="https:***/xmlrpc.php"
    #第3者が閲覧するURLの後ろに/xmlrpc.phpをつける。
    #ワードプレスの管理画面の後ろにつけるとエラーになった

    """
    クライアントの呼び出しなど
    """
    wp = Client(url, id, password)
    post = WordPressPost()
    posts = wp.call(GetPosts({'number': 5}))
    for post in posts:
        print(post.id, post.title,str(post.terms[0]))

if __name__=="__main__":
   main()

もし編集したい記事が決まっている場合、その記事の編集画面にいくとURLにIDが表示されますのでそちらを利用することも可能です。

2. 記事を編集する

上記で記事IDを調べて、その記事に対して操作します。pidが編集したい記事のID(post_id)です。


# -*- coding: utf-8 -*-
import time
from datetime import datetime
from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods.users import GetUserInfo
from wordpress_xmlrpc.methods.posts import GetPost, GetPosts, NewPost, EditPost
def main():
    id="******"
    password="******"
    #idとpasswordはwordpressの管理画面に入るためのもの

    url="https:***/xmlrpc.php"
    #第3者が閲覧するURLの後ろに/xmlrpc.phpをつける。
    #ワードプレスの管理画面の後ろにつけるとエラーになった

    """
    クライアントの呼び出しなど
    """
    wp = Client(url, id, password)

    pid = 1030
    post = wp.call(GetPost(pid))

    post.post_status = "publish"
    post.content = "content"
    post.terms_names = {
    "post_tag": ['tag1'],
    "category": ['diary',],
    }
    ret = wp.call(EditPost(pid, post))

if __name__=="__main__":
    main()

動作環境の注意

手元のPCはWIndowsでも Macでも動作確認しています。Xserverのターミナルからもうまく動作しました。しかし、以前Google Cloud PlatformのCloud functionsでデプロイした場合はできなかった記憶があります。AWS lamdbaなどでもエラーが起きる可能性があるのでローカルで動作させる事をお勧めします。

動作しない場合

・siteGuardなどセキュリティ系のプラグインを入れると、デフォルトでxmlrpcをオフにしますので、解除が必要です

・Xserverなど一部のレンタルサーバでは意図的にデフォルト でアクセスをOFFにしていますので管理画面から設定変更が必要です

siteguardの設定をオフにする

ダッシュ画面の左下からsiteguardを選択します。その中にXMMRPC防御という項目があるのでクリックします。

左上の部分でOFFを選択します。

Xserverの場合

xserverの場合、公式が丁寧に説明してくれているのでこちらを見れば大丈夫だと思います。

https://www.xserver.ne.jp/news_detail.php?view_id=6875

https://www.xserver.ne.jp/manual/man_server_wpsecurity.php#link-b02

絹田
絹田

読んでくれてありがとう.これからもよろしくね

コモディティ化するビックデータ分析と今後求められるスモールデータ分析

ビックデータというのは10年くらい前から言われた言葉で、センサが安価になって多くのデータが得られるようになったり、Web上で多くのデータが得られるようになって言われた言葉です。データの量とマシンパワーで力づくでモデルを構築し、原因を分析したらモデル作成を行う方法です。特に深層学習のように人の判断を介在させず、データからのみで判断させるような解析は素晴らしいと思います。

しかし、今後、より注目されるのがスモールデータ分析だと思います。

ビックデータ解析ではもはやいかにデータを集められるかというのがポイントとなっています。ハードウェアの値段は下がっていますし、クラウドサービスで一時的にマシンパワーを借りることもできます。方法も公開されているものが多いので、既にビックデータ分析はレッドオーシャンという意見もあります。

それに対して、スモールデータは

  • 装置の故障データのようにその発生自身稀であるデータ
  • 疾患データのように多くのデータが集められないようなデータ

のことでビックデータのようにブルートフォースな解析ができません。したがって、専門家の知見に基づく考察やデータのより繊細な前処理が必要となります。スモールデータでは、データからすべてを判断することができるほどのデータがありませんので固有技術的な考察を駆使してメカニズムを推定し、知見や仮説につなげることが重要です。

スモールデータの特徴

ビックデータと比べてスモールデータには下記のような特徴があります。

  • カラム数に比べてサンプルデータ数が少ない
  • カラム同士で相関がある
  • 疾患数や故障数のように正常と異常データの数が違う不均衡データ

企業のドメスティックな環境で得られるデータというのはほとんどこれに相当すると思います。

スモールデータ分析の心構え

そして、このスモールデータを解析するにあたって重要なのは

  • 目的に対して適切な問題設定を行う
  • データの質が命

ということです。これはビックデータでも重要であることには違いありませんが、データ数が少ないと問題設定に応じたデータを選択的に取得する必要が出てきますのでよりその傾向は強くなると思います。

また、1番目の「目的に対して適切な問題設定を行う」という件についてはAIや機械学習になると途端に問題設定が曖昧になる傾向があると思います。しかし、何が目的なのか、そのためにどういう問題設定が適切なのかというのが人が行うべき業務なわけで解析でもこれが定まらないと成功するのは困難となります。例えば機械学習で株価を当てるということを考えたとき、目的は儲けるということですので、上がるか下がるかを予測するのが大事であって1円単位で予測することではないはずです。問題を適切に設定することで難易度は変わりますし、スモールデータだと今言った目的設定や課題設定がより重要になってきます。

必要なこと

今後スモールデータ分析が重要になる中で何を学べばいいかというと

  • 機械学習やデータ前処理に関するアルゴリズムへの最低限の理解
  • 専門知識

だと思います。ビックデータだとライブラリに放り込んで比較的いい感じに分析してくれることがあってもスモールデータだと中身を理解していないと導く結論が大きく変わってしまうことがあります。またもう一つが専門的な知見です。固有技術的な考察を駆使してメカニズムを推定することでデータを効率的に活用することが求められますので、データサイエンスのみならず専門的な知見というのが大事になってくると思います。

データサイエンティストという職業がすっかり普及しましたが、今後はデータサイエンスに関する知識というのは専門家に求められる教養や素養という位置づけになり、各分野の専門家の役割というのが復活していくかもしれませんね。

Angularで再読み込み時に404エラーがでる

なぜ404エラーとなるのか

AngularをはじめとするSPAでは、URLごとに実態のあるページが存在する一般的なサイトとは異なり、index.htmlをクライアント側で書き換えるため直接特定のアドレスにアクセスするとエラーがでます。

開発環境では出ない理由

開発環境だと開発サーバ側でindex.htmlを読み込んでくれているので問題にならないのですが、デプロイ時には自分で設定することになります。設定内容はファイルが見つからない時はindex.htmlを参照してくれというものですので、Angularではなくサーバ側のapacheやnginxなどの設定ということになります。なのでデプロイ時に顕在化する問題となります。

APacheの場合

.htaccessファイルに下記を指定します。サブドメインを使ってホスティングする場合、サブドメインのルート(サブドメインのindex.htmlのある場所)に下記を設置すればいいです。

<pre class="wp-block-syntaxhighlighter-code">RewriteEngine On
# If an existing asset or directory is requested go to it as it is
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]
# If the requested resource doesn't exist, use index.html
RewriteRule ^ /index.html</pre>

Nginxの場合

こちらのページ(Front Controller Pattern Web Apps)に記載されているtry_filesを使ってindex.htmlに対して下記を設定します

try_files $uri $uri/ /index.html;

Firebaseでホスティングする場合

rootにある設定ファイル(確かfirebase.jsonだったと記憶)を下記のように直せばいいです

"rewrites": [ {
  "source": "**",
  "destination": "/index.html"
} ]

その他の場合

IISやRubyの場合は公式を見ていただくのが良いかと思います。

https://angular.io/guide/deployment#server-configuration

その他

写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494

Android => https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Ionic(Angular)でtabとサイドバーを備えたプロジェクトを作成する

概要

上記の画像のようなサイドメニューとタブバーを備えたプロジェクトを作成します。

流れ

IonicのデフォルトテンプレートのひとつであるTabプロジェクトにサイドメニューを追加します。

参考文献

https://petercoding.com/ionic/2019/05/05/side-menu-in-ionic4/

https://ionicframework.com/jp/docs/api/menu

準備 デフォルトのプロジェクトを作成する

下記のコマンドを打ってTabバーを備えたプロジェクトを生成します。

% ionic start sidemanu tabs --type=angular

さらにCapacitorを入れますか?(Integrate your new app with Capacitor to target native iOS and Android?) と聞かれたら、スマホアプリにすることを検討指定場合はYESにしておきます。

その後、作成したプロジェクトへ移動します。

$ cd sidemanu

プロジェクトへ移動したあと、下記コマンドで一度起動してみます。

ionic serve

このような画面が立ち上がればOKです。なお、デフォルトがIEなどのブラウザになっているとそもそも表示されないです。ChromeやEdgeでアクセスしてください。

なお、Tabバーのプロジェクト構成は下記となっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

これにサイドメニューを設置して行きます

サイドバーに配置するページを作成しておく

事前にサイドメニューに設置するページを作成しておきます。新しいターミナルを立ち上げてプロジェクトフォルダまで移動したあと、下記コマンドでpageを2つ追加します。

ionic generate page side/mySide1
ionic generate page side/mySide2

プロジェクトは下記のようになります。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── side
│   ├── my-side1
│   │   ├── my-side1-routing.module.ts
│   │   ├── my-side1.module.ts
│   │   ├── my-side1.page.html
│   │   ├── my-side1.page.scss
│   │   ├── my-side1.page.spec.ts
│   │   └── my-side1.page.ts
│   └── my-side2
│       ├── my-side2-routing.module.ts
│       ├── my-side2.module.ts
│       ├── my-side2.page.html
│       ├── my-side2.page.scss
│       ├── my-side2.page.spec.ts
│       └── my-side2.page.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

以上で準備は完了です。あとはこの2つのページへハンバーガメニューからアクセスできるようにします。

サイドメニューを設置

app.component.htmlでサイドメニューを作成し、ion-router-outletに登録する

Ionicは、サイドメニューを簡単に作成するためにion-menuと呼ばれるコンポーネントを提供しています。app.coponent.htmlの中にion-menuタグを使ってサイドメニューを作っておいて、それを<ion-router-outlet>の中でidで紐付けます。ion-router-outletタグはコンポーネントを埋め込むために使用するものですので、これに登録しておくと作成したサイドメニューを他のページから簡単に読み込むことができるようになります。

最初はapp.component.htmlは下記のようになっています。ion-router-outletタグに何も登録がありません。

<ion-app>
  <ion-router-outlet></ion-router-outlet>
</ion-app>

これを下記のようにします。

<ion-app>
    <ion-menu side="start" menuId="first" contentId="my-content">
    '''
    '''
    </ion-menu>
  <ion-router-outdlet id="my-content"></ion-router-outlet>
</ion-app>

こうすることにより、ion-menuタグの中に設定した中身を他のページから読み込むことができるようになります。私の場合、下記のように設定しました。

<ion-app>
    <ion-menu side="start" menuId="first" contentId="content1">
        <ion-header>
          <ion-toolbar>
            <ion-title>Navigate</ion-title>
          </ion-toolbar>
        </ion-header>
        <ion-content>

          <ion-list lines="none">
            <ion-list-header>
              一般情報
            </ion-list-header>

            <ion-menu-toggle autoHide="false">
              <ion-item routerLink="/my-side1" routerLinkActive="active" routerDirection="root" detail="false">
                <ion-icon slot="start" name="person"></ion-icon>
                <ion-label>
                  Privacy Policy
                </ion-label>
              </ion-item>
            </ion-menu-toggle>

            <ion-menu-toggle autoHide="false">
              <ion-item routerLink="/my-side2" routerLinkActive="active" routerDirection="root" detail="false">
                <ion-icon slot="start" name="help"></ion-icon>
                <ion-label>
                  Terms of use
                </ion-label>
              </ion-item>
            </ion-menu-toggle>

          </ion-list>
        </ion-content>
      </ion-menu>
  <ion-router-outdlet id="content1"></ion-router-outlet>
</ion-app>

公式にも実装例が載っています。

https://ionicframework.com/docs/api/menu

任意のページからのサイドメニューの読み込み

以上でサイドメニューを設置できましたので、tab1ページから読み込んでみます。tab1.page.htmlはデフォルトではこのようになっていると思います。

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <app-explore-container name="Tab 1 page"></app-explore-container>
</ion-content>

hdearの中で下記のタグで読み込めるようになりますので

      <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
      </ion-buttons>

tab1.page.htmlを丸ごとこのように変更します。

<ion-header>
  <ion-toolbar>
      <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
      </ion-buttons>
    <ion-title>
      File selection
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <app-explore-container name="Tab 1 page"></app-explore-container>
</ion-content>

おまけ

サイドメニューから飛んだ先で戻るボタンを設置できます。

最初はこのようになっていると思いますが、

<ion-header>
  <ion-toolbar>
    <ion-title>mySide2</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>

</ion-content>

下記のようにion-back-buttonを設置します。

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/"></ion-back-button>
    </ion-buttons>
    <ion-title>
      Privacy policy
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>

</ion-content>

そうするとこのように戻るボタンが設置できます。

Angular(Ionic)でグラフを描写する

グラフ描写のライブラリ

Ionicでグラフを描写するライブラリにはいくつかあるようですが、安定していてよく使われているchart.jsを使いことにしました。意外に簡単でびっくりしました。

インストール

プロジェクトのルートフォルダに移動して、Terminalで下記コマンドを実行します。

npm install chart.js --save

すると下記のようなメッセージが出てchat.jsがインストールされます。

+ chart.js@2.9.3
added 4 packages from 7 contributors, removed 1 package and audited 1527 packages in 12.531s

node_modulesのなかに格納されるので、これを使いたいページのtsファイルから呼び出すだけで使用できるようになります。

実装する

このような棒グラフを書いてみます。

参考サイト

https://enappd.com/blog/charts-in-ionic-4-apps-and-pwa-part-1/52/

HTMLファイルに要素を追加する

chart.jsはcanvas要素にグラフを描写するので、まずはグラフ要素をHTMLに記載します、

<ion-header>
</ion-header>

<ion-content>
=>  <canvas #myChart></canvas>
</ion-content>

描写する

あとはtsファイルからライブラリをimportしたあとにグラフを描写するだけです。HTMLの要素を取得するので、ViewChildも呼び出します。

import { Component, ViewChild } from '@angular/core';
import { Chart } from 'chart.js';

@Component({
  selector: 'app-admin',
  templateUrl: './admin.page.html',
  styleUrls: ['./admin.page.scss'],
})

export class AdminPage implements OnInit {

  @ViewChild('myChart') myChart;

  bars: any;
  colorArray: any;
  constructor() { }

  ionViewDidEnter() {
    this.createBarChart();
  }

  createBarChart() {
    this.bars = new Chart(this.myChart.nativeElement, {
      type: 'bar',
      data: {
        labels: ['Label1', 'Label2', 'Label3', 'Label4', 'Label5', 'Label6', 'Label7', 'Label8'],
        datasets: [{
          label: 'Viewers in millions',
          data: [25, 30, 50, 60, 92, 75, 100, 67],
          backgroundColor: 'rgb(50, 194, 129)', // array should have same number of elements as number of dataset
          borderColor: 'rgb(50, 194, 129)',// array should have same number of elements as number of dataset
          borderWidth: 1
        }]
      },
      options: {
        scales: {
          yAxes: [{
            ticks: {
              beginAtZero: true
            }
          }]
        }
      }
    });
  }
}

pythonのリストをmap、filter、lambdaで操作する

操作対象のリストを作成

>>> lst = [i for i in range(10)]
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

map関数で全体に対して操作

こういうリストがあったときに全体に対して操作するならこれ

>>> lst2 = list(map(lambda e:e*2,lst))
>>> lst2
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

python3では、map関数が返すものはmap objectなのでlistへ変換する必要がある。

filter関数で特定のものを抽出

>>> lst2 = list(filter(lambda e:e%2==0,lst))
>>> lst2
[0, 2, 4, 6, 8]

Agular(ionic)で多言語化対応を行う

環境

Ionic:

 Ionic CLI: 6.4.1 (/Users/myName/.nodebrew/node/v12.10.0/lib/node_modules/@ionic/cli)
   Ionic Framework  : @ionic/angular 5.1.1
   @angular-devkit/build-angular : 0.901.6
   @angular-devkit/schematics: 9.1.6
   @angular/cli   : 9.1.6
   @ionic/angular-toolkit : 2.2.0

Utility:
   cordova-res (update available: 0.14.0) : 0.9.0
   native-run (update available: 1.0.0)   : 0.2.9

System:
   NodeJS : v12.10.0 (/Users/masaya/.nodebrew/node/v12.10.0/bin/node)
   npm    : 6.14.2
   OS     : macOS Mojave



参考のサイト

公式ドキュメントはこちら

https://github.com/ngx-translate/core

パッケージのインストール

npm install @ngx-translate/core @ngx-translate/http-loader --save

package.json

  "dependencies": {
    "@ngx-translate/core": "^12.1.2",
    "@ngx-translate/http-loader": "^4.0.0",

実装

app.module.ts

app.module.tsにて、TranslateModuleをHttpClientModule, HttpClientと一緒にインポートします.

import {HttpClientModule, HttpClient} from '@angular/common/http';
import {TranslateModule} from '@ngx-translate/core';
・
  imports: [
    HttpClientModule,    
    TranslateModule.forRoot(),
  ],

使いたい場所のモジュール

使いたい場所のモジュールで

import {HttpClientModule, HttpClient} from '@angular/common/http';
import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';

export function createTranslateLoader(http: HttpClient) {
    return new TranslateHttpLoader(http, '../../assets/i18n/', '.json');
}
@NgModule({
    imports: [
      TranslateModule.forChild({
        loader: {
           provide: TranslateLoader,
           useFactory: (createTranslateLoader),
           deps: [HttpClient]
         }
      }),
    ],

使いたい場所のtsファイル

import {TranslateService} from '@ngx-translate/core';
 constructor(
    private translate: TranslateService,
  ) {
    this.translate.setDefaultLang('en');
    this.translate.use('en');
}

あとHTMLに

<p>{{ 'HELLO' | translate }}</p>

とかく

assetに情報を配置

最後に

/assets/i18n/

を作成して、jsonファイルをおけばいいです。

{
  "HELLO": "こんにちはEnglish"
}

複数の場合はこのように書いてください

{
  "Main": "メイン",
  "File selection": "ファイルの選択",
  "1. Choose a file": "1. ファイルの選択",
  "Pick a file from your storage": "ストレージから選択",
  "Or take a new picture": "新しく写真を撮る",
  "2. Crop your image": "2. 写真の切り抜き",
  "select the area you want to analyze from the photo": "解析したいエリアを選択してください",
  "No ads 3 times if you watch a short video": "広告が3回表示されなくなります!",
  "Preparing for the analysis. Since this app uses service that need to be payed(from not you but me), an ad will be shown to you.":"安全な方法でアップロードします。 このアプリは有料のAPIを使用しているため、広告が表示されます。",
  "Analyzing.":"分析しています。"
}

最後のブロックの後にカンマを入れるとエラーになりますので注意.

app.component.htmsも多言語化したい場合

app.module.tsを下記のようにします.

+import {HttpClientModule, HttpClient} from '@angular/common/http';
+import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
+import {TranslateHttpLoader} from '@ngx-translate/http-loader';

+export function createTranslateLoader(http: HttpClient) {
+    return new TranslateHttpLoader(http, '../assets/i18n/', '.json');
+}

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
+    TranslateModule.forRoot({
+      loader: {
+         provide: TranslateLoader,
+         useFactory: (createTranslateLoader),
+         deps: [HttpClient]
+       }
+    }),
  ],
  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
  bootstrap: [AppComponent],
})
export class AppModule {}

app.component.ts

+import {TranslateService} from '@ngx-translate/core';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {

  constructor(
+    private translate: TranslateService,
  ) {
+      this.translate.setDefaultLang('en');
+      this.translate.use('en');
  }
}

Ionic(Angular)のライフイベントについて

ライフイベント、ライフサイクルとは

ライフイベント、ライフサイクルとは、ページを読み込み、表示し、その後ユーザーが離脱する一連の流れの中で特定のタイミングで発火するDOMイベントのことです。Ionicでは、Ionic独自のライフイベントに加えて、Angularのライフイベントも利用することができます。

これにより、ページが読み込まれたらこれをする、ページに変化があったらこれをするといったことを実装できます。

Angularのライフイベントについて

 それまでまずはAngularのライフイベントからです。Ionicでは、Ionic独自のライフイベントに加えて、Angularのライフイベントも利用することができます。個人的にはAngularのイベントを使う頻度の方が高いです。

こちらの公式ドキュメントに細かく記載されておりますので、ここではよく使うライフイベントについて転載します。

https://angular.jp/guide/lifecycle-hooks

イベントフック(これを関数のように書いて呼び出します)概要
ngOnInit()ページやコンポートネントが最初に完全に読み込んだら実行されるものと理解しています。イメージではJavaScriptのonDOMcontentLOaded()です。その後、 ディレクティブ/コンポーネントを初期化します。ページ読み込みの際に1度だけ呼び出されます。
最初 の nOnChanges() の後に 一度 呼び出されます
ngOnChanges()Angular がデータバインドされた入力プロパティを(再)設定するときに応答します。 このメソッドは、現在および以前のプロパティ値の SimpleChanges オブジェクトを受け取ります。
ngOnInit() の前に呼び出され、データバインドされた入力プロパティが変更されるたびに呼び出されます。
ngOnDestroy()Angularがディレクティブ/コンポーネントを破棄する直前に、クリーンアップします。 メモリリークを回避するためにObservableの購読を解除し、イベントハンドラをデタッチしましょう。
Angularがディレクティブ/コンポーネントを破棄する 直前 に呼び出されます。

Angular場合、ライフサイクルはコンポーネントの生成や破棄がトリガーになります。そのため、最初の1回しかイベントを実行したくないときなどに利用します。

なお、constructorとngInitを同じように考えがちですが、前者はライフイベントではありませんので、コンポーネントが表示されるまで待ってくれません。そのため、constructorを実行したのになにも反映されないということがあります。constructorではページ操作を行うような動作は入れない方が良いです。

Ionicのライフイベント

 次にIonicのライフイベントです。

イベントフック(これを関数のように書いて呼び出します)概要
ionViewWillEnter()ページが表示されるアニメーションが始まるとき
ionViewDidEnter()ページが表示されるアニメーションが終了したとき
ionViewWilLeave()ページから離脱・遷移するアニメーションが始まるとき
ionViewDidLeave()離脱するアニメーションが終了したとき

Willのタイミングでデータの描画を行うのはよくないです。Will を使用する場合はデータの取得などの準備、Didを使用するのはデータを表示するなどのタイミングがよさそうです。

 Angularの場合、先に述べたようにコンポーネントがトリガーとなっているのに対して、lonicのライフライクルトリガーはユーザーにとっての表示です。なのでページ遷移して戻ってきたときにも発生するなどでUIの改善などの観点では非常に使いやすくなっています。

ライフイベントの実装方法

Ionicの場合、ライフサイクルイベントを関数のように追記するだけですが、Angularのライフサイクルイベントは先に型を定義する必要があります。

その他

IonicをAngularで使って、写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494
Android => https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Ionicの忘備録

Observableなオブジェクトの作成

import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
・
・
  constructor(
    private client:HttpClient
  ) { }  
・
・
devTestObservable(myStr):Observable<any> {
    return new Observable(observer => {
      setTimeout(() => {
        observer.next(myStr+'myObservable');
      }, 1000);
    })
  }

  devTestPromise() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('myPromise');
      }, 1000)
    })
  }

  devTestHttp():Observable<any> {
    return new Observable(observer => {
        this.client.get('/assets/data/test.json').subscribe(e =>{
          observer.next(e);
        })
    })
  }

Copy

  
import { DataService } from '../tabs/data.service';
import { HttpClient } from '@angular/common/http';
・
・
  constructor(
    private dataService:DataService,
    private client:HttpClient,
  ) {
      this.myStr = 'first ';
  }
・
・
async onButtonClick(){

    this.dataService.devTestHttp().subscribe(e =>{
      console.log(e,'Hi');
      this.myStr = e.authAdmin.linkLoginButton+'Hi';
    })

    this.dataService.devTestObservable().subscribe(e =>{
      console.log(e);
    })

    this.dataService.devTestPromise().then(e =>{
      console.log(e);
    })

  }

Copy

input

<ion-item lines=’full’><ion-label position=’floating’>らべる</ion-label><ion-input type=’text’></ion-input></ion-item>

inputには、最初にplaceholderが表示されていて、フォーカスをあてるとplaceholderのラベルが左上に移動するものが一番いいです。

こんな感じになります。

フォーカス前

フォーカス後

ボタン

横いっぱいに広がったよく見るボタンが表示されます。クリック時の関数は(click)=で指定します。

<div><ion-button expand=’block’ (click)=”someFunc()”>Button</ion-button></div>

新しい部品をつくる

ionic g

とだけ入力すれば、下記の選択肢を表示してくれます。

まだオンプレミスで消耗してるの?Firebaseについて

サーバを立てるのは常識だった

 以前はなにかサービスを作ろうと思うと、サーバを立てて運用するというのが常識でした。物理的なサーバを購入して自社や自宅に置くことはもちろんですが、そうでなくてもVPSで借りて運用するなどは常識だったと思います。WebならPHPでのバックエンドになりますし、アプリの開発ならRestAPIなどを設計したでしょうか。いずれにせよ、サーバを借りてプログラムを書くという点は常識でした。

サーバの運用は大変

 しかしサーバの運用は大変なものです。プログラムを設置し、アクセスを適切に受けるようにして、セキュリティも考慮しなければなりません。OSの設定も大変ですし、物理的なサーバなら電源の冗長化や放熱なども考慮する必要があります。わたしも昨年、会社でDellのWindows Serverを購入しましたが、初期投資は150万円かかりましたし、場所を確保したり専用クーラを設置したり導入までに3ヶ月程度はかかりました。

クラウドがあるじゃないか

 こうした負担を軽減する方法はないのか?と考えた時にクラウドがあります。

クラウドはGoogleやAmazon、Microsoftなどがネット上に構築したサービス群で、OSをVPNのように提供する低レベルなものから(IaaS)、サービスだけを提供するSaaSレベルのものもあります。そして必要なものを選んで利用するほうが、自前でサーバを運用するより圧倒的に楽です。

従来と比べて大きく違うのは、まず初期コストはゼロというです。そして、サーバを運用し続ける負担がほぼなくなります。インフラの部分の担当を任せられるのですから非常にらくになります。これにより爆発的な導入がなさされておりますが、「セキュリティ」を理由に導入していないところも多くあると思いますが、官公庁ではAWSを使ったり、NetFlixのような非常に大規模サイトでも使用されています。

その他、個人的に感じるクラウドの強みは、開発者が納得いくまで何度でもやり直せる点です。先ほど述べたように、わたしは昨年DellのWindows Serverを購入しましたが、「とりあえず動くようにプログラム設置できたら、あとは怖いから放置」ということになってしまっています。実際、一念発起しても杞憂に終わらなかったですし。。一方、クラウドはサービスなので何度でも自分が納得するまで設定をして消去ということを繰り返すことができます。テスト環境を作って、なっとくするまで設定する、そしてうまくいったものだけで本番環境で再現できるとなれば、取り合えず動くようになったら放置というオンプレミス環境と比べて大きくプログラムへの理解が深まります。

Firebaseとは

サーバレスを実現するクラウドサービスはいろいろありますが、特に利用されるのがFirebaseです。現在はGoogle(Alphabet)の傘下ですが、スタートアップだったものをGoogleが買収したものです。

Firebaseの機能

スマホアプリ開発におけるバックエンドを担うことがおおいようですが、JavaScript,JAVA,Swift,Objective-C、Swift、C++、Unity、Python、PHP、Goなどほとんどの環境からアクセスできます。

機能はこちらです

ユーザー認証非常にいやらしい認証周りを簡素化するFirebaseの代表的なサービスです。IDとパスワードで実現するものに加えて、GoogleやTwitter、 Facebookなどのアカウントを利用したユーザー認証や匿名認証も可能です。
データベース「キー=バリュー型」の非SQLデータベース(Json形式)です。SQLの使えるRDBは連携しているGCPで利用できるのでFIrebaseにはNoSQLが搭載されています。ソケット通信で接続しておいて、変更があったらフロント側のデータを自動で更新してくれるので非常に便利です
ストレージファイルの保管機能。普通に画像などをストレージできます
ホスティングWebサイトをデプロイし、公開します
クラウドファンクションネットワーク経由で特定の処理を呼び出し、実行します。Pythonで書くこともできます。このURLを叩いたらPythonプログラムを実行して解析した結果を返すといったことができます
機械学習キット言語翻訳、写真からのテキスト認識、顔認証などです
Google AnalyticsGoogle Analyticsによるアクセス解析です
メッセージングスマホアプリへのプッシュ通信を実現します。Niftyなどでもできるようですが、FIrebaseがメジャーかと。。

いくらかかるのか

実際はGCPと連携して使う場合もあり一概にはいえないですが、使い始めてすぐお金がかかるようにはなっていません。2種類の料金プランが用意されていて、まずは一ひとつめのSparkプラン(無料枠)から始めるようになっているためです。Sparkプランだけでもデータの保存に1GB/月、読み込みに10GB/月が設定され、認証は1 万/月可能なのでかなりでかいです。料枠を超えると利用量に応じた料金が自動的にかかる仕組みです。

公式によると下記の料金体系となっています。

プロダクト  無料Spark プランGenerous limits to get started従量制Blaze プラン大規模なアプリの料金を計算checkSpark プランの無料使用量を含む*
A/B テスト無料
Analytics無料
App Distribution無料
App Indexing無料
認証電話認証 – 米国、カナダ、インド help電話認証 – 他のすべての国 help他の認証サービス1 万/月1 万/月check$0.01/認証$0.06/認証check
Cloud Firestore保存データ下りネットワークドキュメントの書き込みドキュメントの読み取りドキュメントの削除合計 1 GiB10 GiB/月2 万/日5 万/日2 万/日$0.18/GiBGoogle Cloud pricing$0.18/10 万$0.06/10 万$0.02/10 万
Cloud Functionshelp呼び出しGB 秒CPU 秒アウトバウンド ネットワーキング12.5 万/月4 万/月4 万/月Google サービス専用$0.40/百万$0.0025/千$0.01/千$0.12/GB
Cloud Messaging(FCM)無料
Crashlytics無料
Dynamic Links無料
HostingGB 保存済みGB 転送済みカスタム ドメインと SSLプロジェクトごとに複数のサイト1 GB10 GB/月checkcheck$0.026/GB$0.15/GBcheckcheck
アプリ内メッセージング無料
ML Kithelpデバイス用 APIカスタムモデルのホスティング / サービス提供AutoML Vision Edge データセットAutoML Vision Edge トレーニング

Cloud Vision API
checkcheck画像 1,000 件/プロジェクト3 時間/プロジェクト

close
checkcheckGoogle Cloud pricing15 時間/請求先アカウント、
$4.95/時間$1.50/1,000
Cloud Vision の料金を参照)
パフォーマンス監視無料
Predictions無料
Realtime Database同時接続 helpGB 保存済みGB ダウンロード済みプロジェクトごとに複数のデータベース1001 GB10 GB/月close20 万/データベース$5/GB$1/GBcheck
Remote Config無料
ストレージhelpGB 保存済みGB ダウンロード済みオペレーションをアップロードダウンロード オペレーションプロジェクトごとに複数のバケット5 GB1 GB/日2 万/日5 万/日close$0.026/GB$0.12/GB$0.05/1 万$0.004/1 万check
Test Labhelp仮想デバイスでのテスト物理デバイスでのテスト10 件/日5 件/日$1/デバイス/時間$5/デバイス/時間
Google Cloud PlatformBigQuery およびその他の IaaS を使用 helpclosecheck
プランを選択Looking for the Flame plan?無料Spark プランすぐに開始可能従量制Blaze プランプランを選択

その他

写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。フロントエンドはAngularで開発し、バックエンドはGCPとFirebaseを使っています。

iOS => https://apps.apple.com/app/id1497498494
Android =>  https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

PythonとAngularの組み合わせが最強な理由

クロスプラットフォームは生産性が高い

 なにかをサービスインしようとしたとき、Androidだけ、iOSだけ、あるいはWebだけというのは片手落ちです。だからといってAndroid用にKotlinで開発し、iOS用にSwiftで開発し、そしてWeb用にも別個に開発など現実的ではありません。

 すでに大成功しているソフトならもちろんありですが、現実はうまくいくか保証のないまま開発し、なるべく多くのユーザーに使ってもらいながら成功につなげていきたいと考えると思います。だからこそ、クロスプラットフォームであることは特に個人や小規模の開発チームにとって必須のツールとなると考えています。

ここでは、フロントエンドとしてAngular,バックエンドとしてPythonという組み合わせがメリットの大きな組み合わせであることを解説します。

どこのプラットフォームを抑えるか

すぐに思いつくプラットフォームとしては、デスクトップ、スマホアプリ、Webです。

  • デスクトップアプリ
    Linuxは置いておいてもMacとWindowsがあります
  • スマホアプリ
    AndroidとiOSがあります
  • Web
    これはユーザーに非常に簡単にアクセスできるプラットフォームとして外すことはできないです

ユーザーは各プラットフォームに分散していますが、個人的にはWebとスマホアプリを抑えればほとんどカバーできると考えており、デスクトップアプリの優先度は低いと思っています。したがって、クロスプラットフォームの環境はUnity,Xamarin,Cordova,Ionic,React,Capacitorなどがあると思いますが、Ionic,React,CordovaなどのWeb系の技術が選択肢として残ります。

さらにそのなかでどのフレームワークが強いかをこちらの記事で考えたのですが、私の意見ではAngularが断然オススメです。

https://np-sys.com/general/%e5%80%8b%e4%ba%ba%e3%83%bb%e5%b0%91%e4%ba%ba%e6%95%b0%e3%81%ae%e3%82%b7%e3%82%b9%e3%83%86%e3%83%a0%e9%96%8b%e7%99%ba%e3%81%ab%e3%81%afangular%e3%81%8c%e3%82%aa%e3%82%b9%e3%82%b9%e3%83%a1/

バックエンドはどうするか

Angularでフロントエンドを書くとするとバックエンドはどうすればいいでしょうか。TypeScriptが使えるのでnode.jsで書くのはもちろんいいと思います。PHPエンジニアもいると思います。

 ただ、わたしはバックエンドはPythonが好きです。もともとPythonがもっとも馴染みのある言語ということがありますが、バックエンドというのはデータ処理が少なからずあると思うのです。例えばAIを搭載したアプリケーションを開発する場合を考えた時、AIはほぼPython一択です。そうでなくても多くのデータ処理を実行する可能性は多いにあるため、node.jsでもなくPHPでもなく、バックエンドはPythonが汎用性が高いと考えます。Pythonで書いておけばカバー範囲が広いと思っており、バックエンドはPythonで作成したAPIで担当させておいて、AngularからそのAPIを叩いてデータ処理を行うというイメージです。

組み合わせるとどうなるか

 AngularをIonicで使用すると、見た目は完全にNativeになります。Nativeに比べると挙動が遅いということですが(https://developer.medley.jp/entry/2017/11/24/120000)、現在のスマートフォンの性能なら描写の多いゲームなどを除いてまったく問題ならないと思います。したがって、Ionicで開発しておけば、ひとつのプロジェクトをつくるだけでAndroid,Web,iOSのプラットフォームに高いクオリティでビルドできます。そしてバックエンドはPythonで受ける。これによって高い生産性と拡張性を備えたシステムが開発ができると考えているため、これが個人の開発や小規模でも開発を実施する場合私の考える最強の組みわせです。

榊原昌彦さんのコメント

とても共感したので、こちらの著者である榊原昌彦さんの冒頭の挨拶を引用させていただきます。

大きく変わったWebの価値Web技術で作ることができるプロダクトの選択肢は大きく増えました。Web Native開発フレームワークの「Ionic Framework」を使えば、Mobile Native,と同じようなUIとUXをユーザに提供することができます。クロスプラットフォームライブラリ「Capacitor」を利用すれば、Webアプリを「App Store」(iPhone/iPad) や「Google Play」(Android)で配信することができます。Google Chromeをはじめとしたインターネットブラウザも大きく進化しました。モバイルアプリ同等のUX(ユーザ体験)を提供することを目指して、今までモバイルアプリでしか実現できなかったプッシュ通知やオフラインでの表示、高精度GPSといった機能をWebで提供できます。また、「Electron」というWebアプリをデスクトップアプリにすることができるフレームワークが利用されるシーンも増えています。今では、WebアプリのプラットフォームはWebだけではなくなり、1つのプロダクトをiOS、Android、デスクトップへと、ハイブリッドに展開できるようになっています。Webとモバイルの境目がなくなりつつある多くのWebサイト、Webアプリではモバイルデバイスからのアクセス数が増え続けており、モバイルファースト(モバイルデバイスを利用するユーザを優先した設計)が当たり前になりつつあります。ユーザが、「ホーム画面に追加」という機能を使えば、Webをモバイルアプリのように利用できます。Webアプリとモバイルアプリの違いは、アプリストアからインストールするか、ブラウザからアクセスするかの違いになりつつあります。その中で、ハイブリッドに展開する大きなメリットは「そこにユーザがいるから」です。皆さんが作ろうとしているアプリのユーザは、Webブラウザをよく使いますか? Androidユーザが多いですか? もしかすると、App Storeでアプリを探すような人かもしれません。Webとモバイルの境目がなくなりつつある今だからこそ、プラットフォームに依存しない形であなたのアイデアを実現できることは大きな武器になります。

個人的にはこれにPythonを加えると最強だと考えています。

最後に

写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。フロントエンドはIonicでCapacitorを使って開発し、バックエンドはGCPのApp EngineをPythonで書いてます。

iOS => https://apps.apple.com/app/id1497498494
Android => https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Angularにおけるモジュール解説

モジュールとは

Anularは、コードをモジュールという単位で機能ごとに分割することで管理します。電気自動車が部品を組み立てることで比較的簡単に組み立てられるように、機能ごとにモジュールへ分割することでプログラムを作成しやすくなります。代表的なものとして、ルーティングや HTTP 通信などの機能を持つものなどがあります。

CommonModule

CommonModuleはNgIf や NgFor などの基本的なディレクティブやパイプを提供します。両者ともAngularを代表する機能ですが、Ngifは変数の条件によって(例えばログインしているかしていないかで)表示を変えるときに使用しますし、NgForはリスト構造になっているものに対してイテレートすることで劇的にソースコードを書く手間を低減できます。Angularを代表する機能であるモジュールのため直接読み込む必要はなく、BrowserModule やWorkerAppModule などの各プラットフォームごとのモジュールがエクスポートしているので、これらのモジュールを読み込んでいれば同時に利用可能になります。

Forms Module

Forms Moduleは、Template Driven なフォームを実装するのに必要となるディレクティブやプロバイダを提供します。Template Driven はフォームにユーザーが入力した情報によって変数側のデータを書き換える方法で、これによりJavaScriptからクラスやIDを指定してgetElementbyID()などの手間を省くことができます。Forms Module は、@angular/forms からインポートすることで利用できます。

src/app/app.module.ts

import { NgModule }      from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { FormsModule }   from '@angular/forms';import { AppComponent }  from './app.component'; @NgModule({imports: [BrowserModule,FormsModule],declarations: [AppComponent,],providers: [],bootstrap: [ AppComponent ]})export class AppModule { }

Reactive FormsModule

ReactiveForms Module は、アプリケーションに Reactive Form を実装するために必要なディレクティブやプロバイダを提供します。Forms Moduleがユーザー側の入力でバックエンド側の変数を書き換える方法なのに対して、このモジュールはバックエンド側からフロントエンドを書き換える方法です。ReactiveForms Module は Forms Module と同様に、@angular/forms からインポートして利用します。

HttpClientModule

HttpClientModule は、HTTP リクエストや、レスポンスの処理を実装するのに必要なプロバイダを提供します。Pythonなどとは異なり非同期通信だるJavaScript(そしてSupersetであるTypeScript)では、Ajax通信をよく使いますが、Ajax通信をAngularで実装する際はこのモジュールを使用します。AjaxHttpClientModule は @angular/common/http からインポートすることで利用可能になります。

JsonpModule

JsonpModule は、JSONP を用いた HTTP 通信を実装するのに必要なプロバイダを提供します。JsonpModule は HttpClientModule と同様、@angular/http からインポートすることで、利用可能になります。個人的にはあまり使用したことはありません。

Router Module

AngularはSPAですので、実態は一枚のHTMLのDOMを書き換えてページ遷移を実現します。その際のURL変更に関する機能を担当するモジュールです。RouterModule は @angular/router からインポートすることで利用します。

BrowserModule 

BrowserModule は、DOM イベント(たとえばheaderの読み込みが完了したかなど(https://phpjavascriptroom.com/?t=js&p=event))やキーボードイベント・タッチイベントなど、主にブラウザアプリケーションの作成に必要な機能を提供します。これもWebからきている人には理解しやすい概念だと思います。なお、BrowserModule のなかでCommonModule もエクスポートしているため、BrowserModule をインポートするだけでCommonModule も読み込まれます。@angular/platform-browser からインポートして利用しますが、個人的には何も考えずにインポートするモジュールです。

ServerModule

ServerModule は、主にサーバーサイドレンダリングを行うアプリケーションを作成するために必要な機能を提供します。公式を見ると解説が省略されすぎているのですが。。

https://angular.io/api/platform-server/ServerModule

前述の BrowserModule もエクスポートしているので、BrowserModule で使える機能も利用できます。@angular/platform-server からインポートします。サーバ側はPythonで書くことが多いので、あまり個人的に使用したことはありません。

WorkerAppModule

これも個人的にはほとんど使ったことがないモジュールで、公式によると、Angular10から廃止されることが決まっておりますので使用しないほうがいいでしょう。

https://angular.jp/api/platform-webworker/WorkerAppModule

まとめと結論

Anularは、モジュールという単位で特定の機能を担うコードを分割し、使用する場合はImportしてすぐに使用できるようにしています。個人的には、

  • 存在をしっていればいいもの
    BrowserModule
  • 使う中でわかってくるもの
    Forms Module、Reactive FormsModule
  • 勉強して使えるようにならないといけないもの
    HttpClientModule

というイメージです。

写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494
Android =>  https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

HuaweiのApp galleryでアプリで提出してみた

背景

いま、スマホアプリって作っても全然インストールしてもらえないんですよね。もう市場飽和してて、10年前ならインストールされたのになんて思いなが開発していました。そんな矢先、Google Play storeからHuaweiが締め出されたという話があり、あっという間に5社で連合で新しいアプリストアが立ち上げられました。App Galleryです。これはチャンスでございます。なので手元のAndroidアプリをApp galleryでリリースしたのでその所感をお伝えします。

野良アプリストアでしょうか

まぁどうでしょうか。当面はそうでしょうね

今後はどうでしょうか

Huaweiの技術力、わたしはGoogleやAppleに劣らずすごいと思います。共産党はトップダウンでものごと進めるし、動画などがもっとも普及しているのが中国であることを鑑みると、今後のITを引っ張るのは中国だと思います。App galleryって「危険そう」「野良アプリ」ってイメージありますよね?でも今後、GoogleとAppleのストアに並ぶ市場になると思います。

所感


・全体的な印象は、中身はGoogle Play、見た目はApp Store Connect
・デベロッパー登録は無料。デベロッパー登録に1日、審査に4日程度。なので1週間程度あればアプリ出せる
・デベロッパー登録にパスポートと銀行のキャッシュカードが必要
・日本語はなくて英語か中国語。でも手順はGoogleとAppleより簡単
・Googleの.apkならそのまま出せる
・.apkならいいので、cordovaやunity,ionicなども問題なく出せる

デベロッパー登録にパスポートと銀行のキャッシュカードが必要

え?まじか。。この時点でかなり気分が冷める。。いろいろ心配になる。。しかし無視して強行突破。

日本語はなくて英語か中国語。でも手順はGoogleとAppleより簡単

これもかなりのハードル。英語重いなぁと思ったけど、中国語だと本当にわからないので英語のほうがありがたいと思う不思議。。まぁ、わかりやすいレイアウトなので、Google翻訳しておいて画面にしたがっていけば問題ないかと。最初どこからてをつければいいかわからなかったから苦労した。

方法

ここから作業していく。

https://developer.huawei.com/consumer/en/appgallery

開くとこんな感じ。

でupload your appかregisterで下記画面にいく。するとこう

国に日本を選択して、必要な事項を登録。携帯に認証コードが送られてくる。俺の場合、文字化けしていて焦ったが、resendすると文字コードが違うのか文字化けの解消されたメールが届くのでそれで認証すればおk。

で、パスポートとキャッシュカードを登録して審査が降りるのをまつ。審査は二十二時ごろにやって明け方には承認された。場合によっては、審査が通る前にとりあえず作業は進められますよ、という画面がでるので当日に進められる人はすすめばよい。その後Consoleへいくとこうなる

左上のMy appsを押す。こうなる。

右上のNewをおすとこうなる。

であとは画面通りに進めば良い。Googleで出したことあったら、それより入力項目少ないから、ここまでたどり着けばいけると思う。

注意したこと

・なにかあったときにGoogleのアプリに影響出したくなかったので、com.XXX.XXXの名前を別に作ってまったく別アプリとした

・なのでAdmobのアプリIDも変えた

・審査はちゃんとやってる。審査完了後こんなメールがきた。

Test Environment]: Wi-Fi networking, Android 9.0/EMUI 9.1(nova 5),Android 9.0/EMUI 9.1(p30), Multilingual settings.でテストしたとのこと

・apkをアップロードするのにエラーがなんども起きた。同じファイルなのにうまくいったり行かなかったり、時間も五分から十分かかった。かなりここで挙動を調べているように感じた。

App galleryに乗せたもののURLわからなかったからGoogleとAppleのをのせとくけどこれをアップした。写真から文字抽出して高速でブログ書くためのアプリ。使って欲しい。

https://apps.apple.com/app/id1497498494

https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

後日談

Google、Apple、Huaweiの3プラットフォームにアプリを公開してから半年が経ちました。Google Play、Apple のApp Store、App Galleryの売り上げですが、驚くことにApp Galleryが最も多いです。次がApp Storeです。App Galleryがまだブルーオーシャンなことがわかります。ただ、最も残念なのは個人開発者だと広告を打てないことです。広告売ったら結構凄そうかも。米国からの部品調達禁止もなんとか乗り切ってほしいです。

gcpで2つ目のアカウントで無料枠を適用しない方法

問題の所在

無料トライアルアカウントを2つ作ってしまって規約違反

経緯

AWSとAzureに押されておりますが、GoogleCloudPlatform が好きです。とても好きなので、最初になんとなく登録したメールアドレスではなく、ちゃんとしたアドレスで使いたくなりました。そこでふたつめのGCPアカウントを先日作成しました。
で、これについてきた1年の無料枠が問題に。

Googleからのメール

Googleからのメールです

「無料トライアルアカウントを2つ作成した件について、お知らせいただきありがとうございます。こちらのドキュメント 1 にもご確認いただけるように、無料トライアルクレジットの適用は一度に限ります。無料割り当ての制限を回避するために複数の無料トライアルアカウントを作成することは利用規約違反であり、違反が判明した場合はプロジェクトまたはアカウントがシステムにて停止される可能性がございますので、ご留意願います。詳細に関しては文末リンク 2 内の「無料トライアル期間中の割り当て制限の回避」部分にてご確認頂けますようお願いいたします。(今後ご利用される予定であるプロジェクトを元請求先アカウントに変更して頂き 3、新規無料トライアルアカウントを閉鎖 (文末リンク 4 参照) して頂ければ、既存のリソースの停止を防げます。)

https://cloud.google.com/free/docs/gcp-free-tier#free-trial
https://support.google.com/cloud/answer/7002354?hl=ja
https://cloud.google.com/billing/docs/how-to/modify-project#change_the_billing_account_for_a_project
https://cloud.google.com/billing/docs/how-to/manage-billing-account#close_a_billing_account

でいろいろメールのやりとりがあったのですが、2週間くらいで解決しました(なが!!)。答えはこれです。

答え→無料枠はアカウントではなく、支払いアカウントに紐付けされている。デフォルトの支払いアカウントを閉鎖せよ

最初、無料枠はアカウントに紐付けられていると思って右往左往していたのですが、無料枠は支払いアカウントにひもづけされているのです。なので最初の支払いアカウントをを閉鎖して、新しい支払いアカウントを作成すればいいです。二つ目に登録したアドレスに無料枠がついていると勘違いしていて長くかかってしまいました。
具体的には、下記の手順です(メールの転載)。
1. Sign in to the Google Cloud Console
2. In the Billing navigation menu, click Account Management.
3. At the top of the page, click cancel CLOSE BILLING ACCOUNT .

おまけ

先日、ようやくGCPを使ったアプリが公開されたのですが、使えなくなったら悲しすぎるのですこしあせりました。同じ思いをする方いないように投稿します。
iOS
Apple
スクリーンショット 2020-02-14 23.11.51.png

Android版
Google
スクリーンショット 2020-02-14 23.13.10.png

写真からテキストを抽出するOCRソフトです。ブログの投稿で書籍の内容を引用するときに使ってください。