ここのことはなかったことにするかもしれない

仕事がらみの記事を主として扱いますが、あくまで個人ブログです。2013年以前の記事は https://yellow-73.hatenablog.com/ にあります。

Cordova/AndroidアプリをカスタムURLスキームから立ち上がるようにする

使用するプラグイン

cordova-plugin-customurlscheme を使用します。https://www.npmjs.com/package/cordova-plugin-customurlscheme あたりを参照して下さい。

# 実際に作ってみる

プロジェクトを作る

まずプロジェクトを作ります。customurlschemeをプロジェクトにインストールする際に、スキームを決めておきます。

cordova create (フォルダ名) (ネームスペース) (タイトル)
cd (フォルダ名)
cordova platform add android
cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME=(スキーム名)

関数を入れる

function handleOpenURL(url) {
  setTimeout(
    function() {
      alert("received url: " + url);
    },
    0
  );
}

これで、カスタムURLスキームによってアプリが起動した場合には、URLがアラートとして表示されます。

確かめてみる

内部ストレージのHTMLファイルを読み込む

まず、次のようなアンカーを入れたHTMLファイルを作ります。

<a href="(スキーム名):">CLICK</a>

"CLICK" となっている箇所をクリックすると、(スキーム名)に関連付けられているアプリが起動します。

が、デスクトップでは起動しないので、Androidに転送します。

デスクトップのエクスプローラで、「内部ストレージ」にHTMLファイルを転送します。

Androidのブラウザから開いてみます。ローカルパスが (内部ストレージ)/abc.html なら、ブラウザでURLを次のように指定します。

file:///sdcard/abc.html

となります。

Cordova/Android でファイル操作

cordova-plugin-file

https://github.com/apache/cordova-plugin-file

プロジェクトを作る

cordova create (ディレクトリ名) (ネームスペース) (タイトル)
cd (ディレクトリ名)
cordova platform add android
cordova plugin add cordova-plugin-file

クラス作成例

たとえば、次のようなクラスを作りました。

function TextFileSystem(dirPath) {
  this.dirPath = dirPath
};

TextFileSystem.prototype.readFile = function readFile(fileName, success, fail) {
    window.resolveLocalFileSystemURL(
        this.dirPath+fileName,
        function(fileEntry) {
            fileEntry.file(
                function(file) {
                    var reader = new FileReader();
                    reader.onloadend = function() {
                        success(this.result);
                    };
                    reader.readAsText(file);
                },
                fail
            );
        },
        fail
    );
};

TextFileSystem.prototype.createFile = function createFile(fileName, data, success, fail) {
    window.resolveLocalFileSystemURL(
        this.dirPath,
        function(fileSystem) {
            // ファイルを生成する、または上書きオープンする
            fileSystem.getFile(
                fileName,
                {
                    exclusive: false,
                    create: true,
                },
                function(fileEntry) {
                    // 作成できたので書き込む
                    fileEntry.createWriter(
                        function(writer) {
                            writer.write(data);
                            success();
                        },
                        fail
                    );
                },
                fail
            );
        },
        fail
    );
};

TextFileSystem.prototype.removeFile = function writeFile(fileName, success, fail) {
    // fetches filesystem.
    window.resolveLocalFileSystemURL(
        this.dirPath,
        function(fileSystem) {
            fileSystem.getFile(
                fileName,
                {
                    exclusive: false,
                    create: false,
                },
                function(fileEntry) {
                    fileEntry.remove();
                    success();
                },
                fail
            );
        },
        fail
    );
};

resolveLocalFileSystemURLは都度実行しています

resolveLocalFileSystemURL() を都度実行していますが、正直いいのか悪いのかわかりません。Dalvik側の onPause(), onResume() とかとの関係で、勝手にインスタンスが潰されると困るな、と。

failコールバック

fail のコールバックは一つのパラメータを取ります。chrome では error パラメータのオブジェクトは code のみからなります。

function (error) {
    console.log(error.code); // 数字だけ出る
}

エラーコードは https://developer.mozilla.org/en-US/docs/Web/API/FileError あたり参照。

cordova.file.* の中身 on Android

applicationDirectory: "file:///android_asset/"
applicationStorageDirectory: "file:///data/user/0/(ネームスペース)/"
cacheDirectory: "file:///data/user/0/j(ネームスペース)/cache/"
dataDirectory: "file:///data/user/0/(ネームスペース)/files/"
documentsDirectory: null
externalApplicationStorageDirectory: "file:///storage/emulated/0/Android/data/(ネームスペース)/"
externalCacheDirectory: "file:///storage/emulated/0/Android/data/j(ネームスペース)/cache/"
externalDataDirectory: "file:///storage/emulated/0/Android/data/(ネームスペース)/files/"
externalRootDirectory: "file:///storage/emulated/0/"
sharedDirectory: null
syncedDataDirectory: null
tempDirectory: null

nullが出ているので注意が必要です。

また、iOSとは値が違ってくるのだろうと思います。

パソコンとつなげるなら for Windows

externalDataDirectory(file:///storage/emulated/0/Android/data/(ネームスペース)/files/)は、Windowsでは

(デバイス)\内部共有ストレージ\Android\data\(ネームスペース)\files

になります。

GeoDjangoに挑戦中 #5 GeoJSONを読むLeaflet

参照サイト

https://homata.gitbook.io/geodjango/geodjango/tutorial#geojson-serializer

jqueryとleafletのURL

jqeury (js)

https://code.jquery.com/ から適切なバージョンと圧縮タイプを選ぶと、URLとコピーボタンが現れるので、コピーしてペーストします。

leaflet (js, css)

https://leafletjs.com/examples/quick-start/ はたぶん最新でないかと考えて、ここから、<script>タグと<link>タグをコピペしました。

前提

http://(ホスト名):(ポート)/(アプリ名)/geojson で GeoJSONを吐くものとします。

ファイル

index.html

ローカルのパスは(アプリ名)/templates/(アプリ名)/index.htmlです。 URLは/(アプリ名)/ になっています((アプリ名)/urls.pyによる)。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Map</title>
{% load static %}
<meta viewport="width=device-width,initial-scale=1">
<script src="https://code.jquery.com/jquery-3.5.1.js" integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
  integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
  crossorigin="" />
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
  integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
  crossorigin=""></script>

<style>
HTML, body, #MAP {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  position: relative;
}
</style>
<script src="{% static "(アプリ名)/js/index.js" %}"></script>
<body>
<div id="MAP"></div>
</body>
</html>

scriptsrc属性値は{% static "(アプリ名)/js/index.js" %}となっていますが、適切なURLに置換されます。

index.js

ローカルのパスは(アプリ名)/static/(アプリ名)/js/index.js です。 URLは/static/(アプリ名)/js/index.js です。

window.onload = function() {
  var lyr_stdmap = L.tileLayer(
    "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png",
    {
      "id": "stdmap",
      "attribution": "<a href=\"http://portal.cyberjapan.jp/help/termsofuse.html\" target=\"_blank\">国土地理院</a>"
    }
  );

  var map = L.map(
    "MAP",
    {
      "layers": [lyr_stdmap]
    }
  );
  map.setView([34.5, 133.39], 16);
  // GeoJSONレイヤ
  // HTMLのURLが http://.../(アプリ名)/ になっているので
  // http://.../(アプリ名)/geojson/ にアクセス
  $.getJSON("geojson/", function(data) {
    L.geoJson(data).addTo(map);
  });
}

GeoDjangoに挑戦中 #4 GeoJSONを吐く

参考URL

https://homata.gitbook.io/geodjango/geodjango/tutorial#geojson-serializer

ビューを登録する

(アプリ名)/urls.py

urlpatterns = [
    ....
    path('geojson/', GeojsonAPIView.as_view(), name='geojson_view'),
    ....
]

(アプリ名)/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import traceback
import json
from django.core.serializers import serialize

class GeojsonAPIView(APIView):
    def get(self, request, *args, **keywords):
        try:
            encjson  = serialize(
                'geojson',
                FieldPolygon.objects.all(),
                srid=4326,
                geometry_field='geom',
                fields=('gid', 'name')  
            )
            result   = json.loads(encjson)
            response = Response(result, status=status.HTTP_200_OK)
        except Exception as e:
            traceback.print_exc()
            response = Response({}, status=status.HTTP_404_NOT_FOUND)
        except:
            response = Response({}, status=status.HTTP_404_NOT_FOUND)
        return response
  • fieldsに指定したカラムがプロパティになります。
  • FieldPolygon.object.all()FieldPolygon.object.filter()等をかけられるようです。

GeoDJangoに挑戦中 #3 JSONを吐くまで

参考URL

https://homata.gitbook.io/geodjango/geodjango/tutorial#restful-apino

モジュール追加

portsに無いのでpipから入れる。

pip install djangorestframework
pip install djangorestframework-gis
pip install django-filter
pip install markdown

これらを(プロジェクト名)/settings.py に登録します。

INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_framework_gis',
    'django_filters', 
  ...
]

アプリの登録(やってない場合)

APPSに登録

(プロジェクト名)/settings.py

INSTALLED_APPS = [
    ...
    (アプリ名),
    ...
]

(プロジェクト名)/urls.py から (アプリ名)/urls.py に引き継ぐ

(プロジェクト名)/urls.py

urlpatterns = [
    ...
    url(r'^(アプリ名)/', include('(アプリ名).urls')),
    ...
]

リアライザの作成

(アプリ名)/serializers.py

from rest_framework import serializers
from .models import FieldPolygon

class FieldPolygonSerializer(serializers.ModelSerializer):
    class Meta:
        model = FieldPolygon
        # fields = ('__all__')
        fields = ('gid', 'name', 'geom')

(アプリ名)/views.py

from rest_framework import viewsets
from rest_framework_gis.filters import DistanceToPointFilter, InBBoxFilter
from rest_framework.pagination import PageNumberPagination

from .serializers import FieldPolygonSerializer
from .models import FieldPolygon

class MyPagination(PageNumberPagination):
    page_size_query_param = 'page_size'

class FieldPolygonViewSet(viewsets.ModelViewSet):
    queryset = FieldPolygon.objects.all()
    serializer_class = FieldPolygonSerializer
    pagination_class = MyPagination
    filter_backends = (DistanceToPointFilter,)
    distance_filter_field = 'geom'
    distance_filter_convert_meters = True

http://.../(アプリ名)/json からリストを見れるようにする

(アプリ名)/urls.py

rom django.urls import path, include
from rest_framework.routers import DefaultRouter

from .views import FieldPolygonViewSet

router = DefaultRouter()
router.register('fieldpolygon', FieldPolygonViewSet)

urlpatterns = [
    path('json/', include(router.urls)),
]

ブラウザで見る ただし WKT で出る

http://(ホスト名):(ポート)/(アプリ名)/json でアクセスすると、HTMLベースでビュー一覧を表示してくれます。 http://(ホスト名):(ポート)/(アプリ名)/json/(ビュー名)で、ビューの中身を表示してくれます。

ただし、GeoJSONではなく、geomWKTで出ました。

GeoDjangoに挑戦中 #2 地物管理まで

参考URL

https://homata.gitbook.io/geodjango/geodjango/import

PostGISテーブル

プロジェクト全体の設定で、使用するデータベースを指定します。(プロジェクト名)/settings.pyを変更します。

PostgreSQLをデフォルトデータベースに指定

DATABASES = {
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'NAME': (データベース名),
        'USER': (ユーザ名),
        'HOST':'localhost',
    },
}

GISデータを使うようにする

プロジェクト全体の設定でGISデータ型を使用するようにします。(プロジェクト名)/settings.pyを変更します。

INSTALLED_APPS = [
...
    'django.contrib.gis',
...
]

psycopg2が必要

ModuleNotFoundError: No module named 'psycopg2'と怒られたので py37-psycopg2 を入れました。

データモデル

仮定するfieldテーブル

ここでは次のテーブルがデータベースにあるとします。

CREATE TABLE field (
  gid SERIAL PRIMARY KEY,
  name TEXT,
  geom GEOMETRY(POLYGON, 4612);
);

modules.pyの作成

(アプリ名)/modules.pyを作成します。

from django.db import models
from django.contrib.gis.db import models

class Border(models.Model):
    gid = models.IntegerField(primary_key=True)
    name = models.TextField()
    geom = models.PolygonField(srid=4612)

    class Meta:
        db_table = 'field'

TEXT型(models.TextField())とVARCHAR型(models.CharField())とは別になっています。

djangoはテーブル名は(アプリ名)_(モデルクラス名)と仮定します。変更するには、サブクラスMetaを作り、db_tableフィールドにテーブル名を与えます。

また、djangoは主キーはidと仮定し、モデルに存在しないなら自動追加します。shp2pgsqlからPostGISテーブルを作る場合には、デフォルトの主キーはgidです。gidprimary_key=Trueとすることで、idの自動追加は抑制されます。

admin.py の設定

(アプリ名)/admin.pyBorderクラスの管理方法を登録します。

from django.contrib.gis import admin
from .models import Border

admin.site.register(Border, admin.OSMGeoAdmin)

管理ツールで地物も管理できるようにする

(プロジェクト名)/urls.pyで既に管理ツールを実行できるようにしていると思いますが、これを少し変更します。

#from django.contrib import admin
from django.contrib.gis import admin

url(r'^admin/', admin.site.urls)admindjango.contrib.gis.adminに切り替えます。

アプリを登録

(プロジェクト名)/settigs.pyで次のようにアプリを登録します。

INSTALLED_APPS = [
...
    (アプリ名),
...
]

登録しないと、全く動きません。

Djangoに挑戦中 #1

URL

インストール

FreeBSDのパッケージからインストール。py37-djangoだとDjango 1系ですので、py37-django30とします。

pkg install py37-django30

プロジェクト作成からリモートのブラウザで見るまで

プロジェクトで一つのサーバみたいです。その下にアプリケーションがぶら下がります。

django-admin startproject (プロジェクト名) [(フォルダ名)]

または

./manage.py startproject (プロジェクト名) [(フォルダ名)]

フォルダなど

この時、(フォルダ名)の下にアプリが付きます。

初期状態では、(プロジェクト名)というアプリができあがります。たぶん、プロジェクト全体を統括するアプリで、 サーバのルートディレクトリに該当します。

プロジェクトの設定

(プロジェクト名)/settings.py

ALLOWED_HOSTS = ["foo.bar.example"]

LANGUAGE_CODE = 'ja-JP'
TIME_ZONE = 'Asia/Tokyo'

ALLOWED_HOSTSで指定したホスト名でアクセスしないと、サーバは応じてくれません。FQDNで書いてIPアドレス指定するとダメです。

マイグレート

まだよくわかっていません。

./manage.py migrate

とりあえず起動

./manage.py runserver (IPアドレス):(ポート)

デフォルトは、127.0.0.1:8000 です。

管理者を置く

./manage.py createsuperuser

http://(ホスト名)/admin にアクセスすると見えます。

表示だけアプリを作る

django-admin startapp (アプリケーション名)

または

./manage.py startapp (アプリケーション名)

urls設定

(プロジェクト名)/urls.py

"/(アプリ名)/.*"の設定は(アプリ名).urls = (アプリ名)/urls.pyに回す設定にします。

from django.conf.urls import url, include

urlpatterns = [
    url(r'^(アプリ名)/', include('(アプリ名).urls')),
...
]

(アプリ名)/urls.py (新規)

from django.conf.urls import url
from . import views

# urlのP1が''なら、後ろは何でもマッチする
# '^$'で本当の空っぽでないといけない
urlpatterns = [
    url('^$', views.index, name='index'),
]

(アプリ名)/views.py でHTMLを表示

from django.http import HttpResponse

def index(request):
    return HttpResponse('<h1>Hello!</h1>')

アクセス

http://(ホスト名)/(アプリ名)/ でアクセスできます。

テンプレート/静的ファイルを使ってみる

さきほどのは HttpResponse()を使ってHTMLをviews.pyから流し込んでいました。テンプレートや静的ファイルを使っていきます。

上述の参考サイトの https://python.keicode.com/django/how-to-serve-static-files.php を参考にしました。

テンプレートを使う

(アプリ名)/templates/(アプリ名)/index.html を例えば次のようにします。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>myapp2</title>
{% load static %}
<script src="{% static "myapp2/js/index.js" %}"></script>
<meta viewport="width=device-width,initial-scale=1">
<body>
<h1>myapp2</h1>
</body>
</html>

(アプリ名)/views.py を次のようにします。

from django.shortcuts import render

def index(request):
    contexts = {}
    return render(request,'(アプリ名)/index.html',contexts)

これで http://(ホスト名):(ポート)/(アプリ名)/ からindex.htmlが見れます。

静的ファイルを使う

続いて、(アプリ名)/static/(アプリ名)/js/index.js を次のようにします。

window.onload = function() {
  alert("hello");
}

これで http://(ホスト名):(ポート)/static/(アプリ名)/js/index.js からindex.jsが見れます。