SpatiaLite のネタと動機は同じ。
「GIS の色んなこと」。要するに個人的に 一連の cartopy ネタからちょっと火が着いちゃって、「そういえば」という類のネタを、思いつく限り思い出してみてる真っ最中、なの。
ワタシが仕事としていわゆる「NoSQL、XMLDB」の検証をしたのが 2011年、なので、もう10年前のこと。その際にピックアップしたのは、Redis、Memcached、MongoDB、BaseX、とかだったと思うんだけれど、今の今までずっと存在を忘れなかったのは Redis だけ。Memcached はほんとにさっき名前をみるまですっかり失念してた。
そして、ずっと頭の片隅にあって、だけれども後回しにしてたのが、そうした NoSQL の「spatial 拡張は、ありや、なしや」の件。たとえば「個人的なナレッジベース」を自 PC に構築するためのインフラとして NoSQL を考えるとして、それが spatial を扱えたら、果たして嬉しいであろうか、ということをね、まぁたまには考えてたわけだ。目的が「non-relational な kv-store の spatial 拡張」であっても、実際 Redis しか覚えてなかったわけだから、検索はまさしく「Redis+spatial」。結局 memcached には spatial 拡張はないようなので、「ひとまず Redis の spatial 拡張を目的のものと考えてみる」ことに。
まぁ迷走したよ。
一般論としてだけど、C/S アーキテクチャを採っているシステムについて、「サーバサイド」のほうを Windows に構築するのは非常に困難であることが多いわけね。Redis も現在進行形ではまさにその状態になっていて、最新の 6.x は、もうね、これ、Windows でのビルドは絶望的とみた。少なくとも Redis 本家は「Windows でのビルドを考えてる形跡はない」。ので、さすがに vcpkg にも Redis は含まれておらず、msys2 のパッケージもない。そして、「Redis for Windos」で見つかるものが「非常に古い」ということに愕然とするわけだ。
なので、「これはもう、virtual box 使ってでも本物の Unix に頼るしか正解はなさそうだなぁ」と結論付けようとして、やっと気付いた:
- この説明が指している「このとても古い Redis」は、実はギリギリセーフのバージョン 3.2.100。
- GEOADD は Available since 3.2.0。
このキャプチャで右側に見えている「Related commands」に関して、3.2 と 6.x との差異は、ざっくり言えば、「GEORADIUS、GEORADIUSBYMEMBER は(3.2 にはない)GEOSEARCH、GEOSEARCHSTORE によって deprecated となった」てこと。どちらも目的は位置による検索。
というわけでめでたく「2016年版、というかなり古いものだが一応目的の評価は部分的に出来るもの」として 3.2.100 をインストール出来た。
あまりに久しぶりで、動かし方から何からすっかり忘れてたが、サーバはコマンドラインからなら例えば:
1 [me@host: ~]$ "/c/Program Files/Redis/redis-server.exe" "/c/Program Files/Redis/redis.windows-service.conf"
クライアントは単純なテキストをサーバにぶん投げて結果をテキストで受け取るだけなので、あらゆるバインディングを簡単に作れる、てことなわけで、すなわち、「Redis クライアントを用立てるのに Windows では苦労する」なんてことはないはずだ。そしてワタシの本日のゴールは無論「python がクライアント」のパターンのお試し。けれども、ひとまず出来合いの、Redis をインストールするだけで同梱されるクライアントが今手に入ったのだから:
1 [me@host: ~]$ "/c/Program Files/Redis/redis-client.exe"
2 127.0.0.1:6379> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
3 (integer) 2
4 127.0.0.1:6379> GEOADD Sicily 12.758489 38.788135 "edge1" 17.241510 38.788135 "edge2"
5 (integer) 2
6 127.0.0.1:6379> GEOSEARCH Sicily FROMLONLAT 15 37 BYRADIUS 200 km ASC
7 (error) ERR unknown command 'GEOSEARCH'
8 127.0.0.1:6379> GEODIST Sicily Palermo Catania
9 "166274.1516"
10 127.0.0.1:6379> GEORADIUS Sicily 15 37 200 km WITHDIST
11 1) 1) "Palermo"
12 2) "190.4424"
13 2) 1) "Catania"
14 2) "56.4413"
15 127.0.0.1:6379>
うん、良いね。
ぶっちゃけ、redis の C/S のプロトコルは HTTP の数億倍かは単純で、なんなら剥き身の socket 通信で自力でクライアントを短時間で書けてしまうほどなんだけれど、もちろん「ちゃんとしたのが既に書かれてる」。ドキュメントがやや荒れているようだが、pip install redis でインストール出来る redis に、geoadd メソッドなどが明示的に追加されているみたいだ。「荒れている」ちうかさ、ワタシが一番嫌いなパターンになってる。「API リファレンスだけ保守」してる模様。トップレベルから読めるドキュメントからは、geoadd 等は見えない。やめてほしいよなぁこういうの…。ともあれ:
1 >>> import redis
2 >>> r = redis.Redis(host='127.0.0.1', port=6379, db=0) # 追記参照
3 >>> r.delete("ToFuTbl")
4 1
5 >>> # 「究極的には key-value だけが世界である」ということを忘れてはいけない。
6 >>> # geometry を扱える、といっても結局は「ある key に対応付く value 内が
7 >>> # spatial を解する」というだけのことなのだ。key をまたいだ串刺しは
8 >>> # 出来ない。ほんとはそれこそがやりたいことなんだけどね、Redis の
9 >>> # 基本原則には馴染まない、てことだわな。
10 >>> r.geoadd(
11 ... "ToFuTbl",
12 ... 141.35437400, 43.06197200, "Tokyo",
13 ... 135.50204600, 34.69389100, "Osaka",
14 ... 135.76818100, 35.01157400, "Kyoto")
15 3
16 >>> r.geodist("ToFuTbl", "Osaka", "Kyoto", "km")
17 42.8791
18 >>> r.geodist("ToFuTbl", "Tokyo", "Kyoto", "km")
19 1016.5984
20 >>> r.georadius("ToFuTbl", 135.7, 34.5, 80, "km", "WITHCOORD")
21 [[b'Osaka', 28.1702], [b'Kyoto', 57.2407]]
22 >>> # ↑この結果はオカシイよなぁ。WITHCOORD 言うておるに、これ、
23 >>> # WITHDIST の結果じゃない?
24 >>>
25 >>> r.georadius("ToFuTbl", 135.7, 34.5, 80, "km")
26 [b'Osaka', b'Kyoto']
27 >>> r.georadius("ToFuTbl", 135.7, 34.5, 80, "km", "WITHDIST")
28 [[b'Osaka', 28.1702], [b'Kyoto', 57.2407]]
29 >>> r.georadius("ToFuTbl", 135.7, 34.5, 30, "km", "WITHDIST", "WITHCOORD")
30 [[b'Osaka', 28.1702, (135.50204783678055, 34.693890677523775)]]
31 >>> # ↑あぁ…WITHCOORD のみ、はダメなのか…。
32 >>>
コメントに書いた通りなのだろうなぁと思う。たとえば PostGIS や SpatiaLite に求めるようなものを期待すると、これは全然違う、まるで似て非なるもの、てこと。そうなのかぁ…。まぁ理解できてしまうとそりゃそーだ、とは思うんだけどね。(あと思ったんだけど、もう少し SQL/MM に寄せるとか考えなかったんだろうか? なんでこんなに発想からして違うんだか…。)
一応上でやってる「ToFuTbl」に相当するものをデータベースで言うところの「テーブル」とみなし、そのレコードをそれなりの量ブチ込むつもりならば、georadius や geosearch による検索、というものに意味があるわけだが、感覚的にはその「レコード」として大量レコードを放り込んで使う、という使い方は想像しにくくて、であるならば、つまり例にしたような「少量」相手なのならば、いったいなんの役に立つんだ、という気分になる、と。なにかいいあんばいの用途はありそうな気はするけど、これを上手に活用するには結構頭使いそうだね。
あと、ドキュメントを読めばちゃんと書いてあるけれど、PROJ や GeographicLib を用いて求めることが出来るような正確さを求めてはいけない。完全なインチキをしているわけではないけれど、回転楕円体としてではなく真球として計算するので、正確な距離を求めたいなら結構誤差が出る。
遅ればせながら WSL2 の存在に気付いたので、ubuntu 20.04 on WSL2 に apt 管理の redis をインストールして試してみた。この場合、「127.0.0.1」では接続出来なくて、「localhost」にする。
振る舞いが全く同じでちょっと面食らったけれど、apt で取れるのが 5.0.7 とあまり最新じゃない。Windows で使えたのよりは遥かに新しいけれど、それでも本日時点での latest は Release-candidate が 7.0、Stable が 6.2。Geo* のインターフェイス変更が入ったのが 6.2 なので、結局 Windows クローズドで試したのと実質同じみたいね。
2021-06-10追記:
「大量レコードを放り込んで使う、という使い方は想像しにくく」と思ったけれど、実際やってみないとなんとも言えんなぁと思って、geoshape-city のデータを Redis にブチ込み、そこから引っ掛けた位置を中心として overpass API で検索というストーリーを考えてみた。
やってみると悪くないかなって気がする:
1 # -*- coding: utf-8 -*-
2 # redis を R-Tree インデクスみたいな気分で使ってみるとするならば。
3 import io
4 import csv
5 from datetime import date
6 import json
7 import shapely.geometry as sgeom
8 import redis
9 # ↓redis georadius でヒットしたものを overpass で OSM にお問い合わせ、的なことを。
10 import overpy
11
12
13 def _load_from_geoshape_city_csv():
14 # 『歴史的行政区域データセットβ版』(CODH作成)
15 # https://geonlp.ex.nii.ac.jp/dictionary/geoshape-city/
16 def _map(rec, fields):
17 import re
18 temp = []
19 for v in rec:
20 if re.match(r"\d+\.\d+", v):
21 temp.append(float(v))
22 elif re.match(r"\d+\-\d+\-\d+", v):
23 temp.append(date(*map(int, v.split("-"))))
24 else:
25 temp.append(v)
26 return {k: v for k, v in zip(fields, temp)}
27 reader = csv.reader(
28 io.open("geoshape-city.csv", encoding="utf-8"))
29 fields = next(reader)
30 return (_map(rec, fields) for rec in reader)
31
32
33 def _build_geoshape_city_index(redis_cli):
34 # マインドとしては R-Tree の insert をしているような気分で。
35 # 実用で考えるなら geoadd に与える「lon lat name」の name としては
36 # 何かキー項目、geoshape-city なら entry_id を使い、これに紐づく
37 # 情報も引けるようにするのがベストだが、今回の例では単にわかりやすさの
38 # ために「address」を使う。(geoshape-city のミッションの性質上、
39 # これにより「すでに存在していない都市」がそうであるとわからないまま
40 # 平気でヒットしまくるが、今回の例ではそこは問わない。
41 allcol = [] # lon, lat, dat, lon, lat, dat, ...
42 for rec in _load_from_geoshape_city_csv():
43 # R-Tree とは違って MBR を考える必要はない。(というか geoadd
44 # 内部がまさに R-Tree を使ってたりするんじゃないかしらね?)
45 allcol.extend(
46 [rec["longitude"], rec["latitude"], rec["address"]])
47 #
48 redis_cli.geoadd(*(["geoshape-city"] + allcol))
49
50
51 def _overpass_query(
52 redis_cli, center_pt, radius_km_redis, buffer_deg_overpass):
53 # たとえば「仙台市の lon, lat はざっくりとしか知らない」状態
54 # の際に、そのおよそ知ってる lon, lat から「本当の仙台市代表地点」
55 # を探り当て、そしてその中心から改めて overpass API で「何かを
56 # 検索」ということ。あんまり複雑な例から試そうとするとハマるので、
57 # ここでの overpass 検索は「"religion"="shinto"」でのみ。
58 # (神社検索てこと。)
59
60 # GEORADIUS 検索結果は先頭のものだけ使うことにする。
61 founds = redis_cli.georadius(
62 "geoshape-city", center_pt[0], center_pt[1], radius_km_redis, "km",
63 "WITHDIST", "WITHCOORD")
64 if not founds:
65 return
66 # たとえば「北海道函館市, (140.72910800, 41.76871200)」
67 address, _, (rptlon, rptlat) = founds[0]
68
69 ex = buffer_deg_overpass / 2
70 # bb: lat-min,lon-min,lat-max,lon-max
71 bb = [rptlat - ex, rptlon - ex, rptlat + ex, rptlon + ex]
72 #
73 api = overpy.Overpass()
74 opresult = api.query("""
75 [timeout:60][bbox:{}, {}, {}, {}];
76 way["religion"="shinto"];
77 out;
78 >; /* recurse down */
79 out;""".format(*bb))
80 result = []
81 for way in opresult.ways:
82 pts = []
83 for node in way.nodes:
84 pts.append((node.lon, node.lat))
85 if len(pts) == 1:
86 poly = sgeom.Point(pts)
87 elif len(pts) == 2:
88 poly = sgeom.LineString(pts)
89 else:
90 poly = sgeom.Polygon(pts)
91 result.append({
92 "tags": way.tags,
93 "centroid": poly.centroid.wkt,
94 "areapolygon": poly.wkt})
95 # ほんとはシンプルにコンソールにダンプしたいんだけどね、どうせ
96 # 「日本語なんかきらいだ」問題が起こるでしょ、windows では。
97 json.dump(
98 result,
99 io.open("exam_redis_result.json", "w", encoding="utf-8"),
100 ensure_ascii=False, indent=4)
101
102
103 def main(args):
104 redis_cli = redis.Redis(host='127.0.0.1', port=6379, db=0)
105 if not redis_cli.exists("geoshape-city"):
106 _build_geoshape_city_index(redis_cli)
107 _overpass_query(
108 redis_cli,
109 (args.rough_lon, args.rough_lat),
110 args.radius_km_redis,
111 args.buffer_deg_overpass)
112
113
114 if __name__ == '__main__':
115 import argparse
116 ap = argparse.ArgumentParser()
117 ap.add_argument("rough_lon", type=float)
118 ap.add_argument("rough_lat", type=float)
119 ap.add_argument("radius_km_redis", type=float)
120 ap.add_argument("buffer_deg_overpass", type=float)
121 args = ap.parse_args()
122 main(args)
1 [me@host: ~]$ ls -l geoshape-city.csv
2 -rw-r--r-- 3 hhsprings Administrators 4485002 Dec 3 2020 geoshape-city.csv
3 [me@host: ~]$ py -3 exam_redis.py 141.3 43 50 0.5
4 [me@host: ~]$ ls -l exam_redis_result.json
5 -rw-r--r-- 1 hhsprings Administrators 2333 Jun 10 04:14 exam_redis_result.json
1 [
2 {
3 "tags": {
4 "amenity": "place_of_worship",
5 "name": "俱知安神社",
6 "religion": "shinto"
7 },
8 "centroid": "POINT (140.7560756022154 42.90038809006553)",
9 "areapolygon": "POLYGON ((140.7557014 42.9005077, 140.7564933 42.9004441, 140.7564825 42.9002851, 140.7556797 42.9003129, 140.7557014 42.9005077))"
10 },
11 {
12 "tags": {
13 "man_made": "torii",
14 "name": "鳥居",
15 "religion": "shinto"
16 },
17 "centroid": "POINT (140.78534555 42.6328931)",
18 "areapolygon": "LINESTRING (140.7853232 42.6328839, 140.7853679 42.6329023)"
19 },
20 {
21 "tags": {
22 "amenity": "place_of_worship",
23 "building": "yes",
24 "religion": "shinto"
25 },
26 "centroid": "POINT (140.7870706206552 42.51482001004054)",
27 "areapolygon": "POLYGON ((140.7869773 42.5147707, 140.7871639 42.5147696, 140.7871703 42.5148671, 140.7869725 42.5148707, 140.7869773 42.5147707))"
28 },
29 {
30 "tags": {
31 "amenity": "place_of_worship",
32 "building": "yes",
33 "name": "大原神社",
34 "religion": "shinto"
35 },
36 "centroid": "POINT (140.8491003921191 42.67959363085933)",
37 "areapolygon": "POLYGON ((140.8490419 42.679623, 140.8490453 42.6795687, 140.8490458 42.6795605, 140.8491588 42.6795642, 140.849155 42.6796268, 140.8490419 42.679623))"
38 },
39 {
40 "tags": {
41 "amenity": "place_of_worship",
42 "building": "yes",
43 "name": "伏見神社",
44 "religion": "shinto"
45 },
46 "centroid": "POINT (140.9577684391768 42.79930241473576)",
47 "areapolygon": "POLYGON ((140.9577061 42.7993299, 140.9577034 42.7992709, 140.9578294 42.7992729, 140.9578321 42.7993359, 140.9577061 42.7993299))"
48 },
49 {
50 "tags": {
51 "amenity": "place_of_worship",
52 "building": "yes",
53 "name": "尻別神社",
54 "religion": "shinto"
55 },
56 "centroid": "POINT (140.9433636332977 42.77955644969908)",
57 "areapolygon": "POLYGON ((140.9433672 42.7795868, 140.9434051 42.7795557, 140.94336 42.7795261, 140.9433222 42.7795572, 140.9433672 42.7795868))"
58 }
59 ]
これは言うほど「少量」の例ではないので、少なくとも「価値を見いだせる使い方はないではない」てことだけは言えたと思う。ゆえに、問題は「Redis でなくてもええやんけ」という葛藤とどう闘うか、だけよね。
2021-06-10 18:00追記:
cartopy ネタ(5)に追記したのだが、「__geo_interface__」をホームポジションとしておくといいのでは、と思い始めてて、なので、上のスクリプト、「どうせ json にしてるんだから」、いっそ geojson 互換の方がいいよねと:
1 # ... (省略) ...
2
3 # ... (省略) ...
4 for way in opresult.ways:
5 pts = []
6 for node in way.nodes:
7 pts.append((node.lon, node.lat))
8 if len(pts) == 1:
9 poly = sgeom.Point(pts)
10 elif len(pts) == 2:
11 poly = sgeom.LineString(pts)
12 else:
13 poly = sgeom.Polygon(pts)
14
15 # geojson 互換の構造にしておく。
16 r = sgeom.mapping(poly)
17 r["properties"] = way.tags.copy()
18 result.append(r)
「FeatureCollection→Feature」という外堀構造部分が geojson としての MUST なのかどうかがワタシは今わかってなくて、上のスクリプトはそこをやらないけど、一番内側の構造としては geojson 互換であり、実際 geojson パッケージをインストールし、そちらで dump した場合に、geojson パッケージはこれに不平は言わない。のでこれを geojson と呼んでもいい…のかな? そこはよくわからんけど、とにかくこれで json dump した結果はこう:
1 [
2 {
3 "type": "Polygon",
4 "coordinates": [
5 [
6 [
7 140.7557014,
8 42.9005077
9 ],
10 [
11 140.7564933,
12 42.9004441
13 ],
14 [
15 140.7564825,
16 42.9002851
17 ],
18 [
19 140.7556797,
20 42.9003129
21 ],
22 [
23 140.7557014,
24 42.9005077
25 ]
26 ]
27 ],
28 "properties": {
29 "amenity": "place_of_worship",
30 "name": "俱知安神社",
31 "religion": "shinto"
32 }
33 },
34 {
35 "type": "LineString",
36 "coordinates": [
37 [
38 140.7853232,
39 42.6328839
40 ],
41 [
42 140.7853679,
43 42.6329023
44 ]
45 ],
46 "properties": {
47 "man_made": "torii",
48 "name": "鳥居",
49 "religion": "shinto"
50 }
51 },
52 {
53 "type": "Polygon",
54 "coordinates": [
55 [
56 [
57 140.7869773,
58 42.5147707
59 ],
60 [
61 140.7871639,
62 42.5147696
63 ],
64 [
65 140.7871703,
66 42.5148671
67 ],
68 [
69 140.7869725,
70 42.5148707
71 ],
72 [
73 140.7869773,
74 42.5147707
75 ]
76 ]
77 ],
78 "properties": {
79 "amenity": "place_of_worship",
80 "building": "yes",
81 "religion": "shinto"
82 }
83 },
84 {
85 "type": "Polygon",
86 "coordinates": [
87 [
88 [
89 140.8490419,
90 42.679623
91 ],
92 [
93 140.8490453,
94 42.6795687
95 ],
96 [
97 140.8490458,
98 42.6795605
99 ],
100 [
101 140.8491588,
102 42.6795642
103 ],
104 [
105 140.849155,
106 42.6796268
107 ],
108 [
109 140.8490419,
110 42.679623
111 ]
112 ]
113 ],
114 "properties": {
115 "amenity": "place_of_worship",
116 "building": "yes",
117 "name": "大原神社",
118 "religion": "shinto"
119 }
120 },
121 {
122 "type": "Polygon",
123 "coordinates": [
124 [
125 [
126 140.9577061,
127 42.7993299
128 ],
129 [
130 140.9577034,
131 42.7992709
132 ],
133 [
134 140.9578294,
135 42.7992729
136 ],
137 [
138 140.9578321,
139 42.7993359
140 ],
141 [
142 140.9577061,
143 42.7993299
144 ]
145 ]
146 ],
147 "properties": {
148 "amenity": "place_of_worship",
149 "building": "yes",
150 "name": "伏見神社",
151 "religion": "shinto"
152 }
153 },
154 {
155 "type": "Polygon",
156 "coordinates": [
157 [
158 [
159 140.9433672,
160 42.7795868
161 ],
162 [
163 140.9434051,
164 42.7795557
165 ],
166 [
167 140.94336,
168 42.7795261
169 ],
170 [
171 140.9433222,
172 42.7795572
173 ],
174 [
175 140.9433672,
176 42.7795868
177 ]
178 ]
179 ],
180 "properties": {
181 "amenity": "place_of_worship",
182 "building": "yes",
183 "name": "尻別神社",
184 "religion": "shinto"
185 }
186 }
187 ]
うん、まぁ本題の Redis とはあんまし関係ない話をしちゃったね、すまん。
2022-05-22追記:
あまりにも基本的な話を今さら、なのだけれど、「Docker 版の Redis をお試したい」がトリガーとなって、これまでスルーしてた問題をちゃんとせざるを得なくなった。
つまり「Docker 版の Redis をお試す」際に、「Windows ネイティブ版」(しかもおっそろしく古いもの)が動いててもらってたらちょっと困っちゃうんだよね、「Docker 版のテストがうまくいってないのにうまくいってるように見えたり、単にポートの衝突で失敗したり」するから。なのだが、…、まぁずっと気付いてはいたんだけどね、誰のせいか知らないが、「Windows ネイティブ版の Redis」って、知らないうちに自動スタートアップのサービスとして起動しっぱなしになっちゃってたんだよね。「清く正しい Windows サービス」として作られてて、行儀よくちゃんと Windows 再起動のたんびに常駐しちゃってたんだよ。
これがインストーラの仕業なのか、はたまた redis-server を一回でも起動したことが原因なのか、どれが原因なのかはわからんけれど、とにかく Windows としては正規のサービスとして常駐登録されてたというのが今の状態で、これを解除しときたい、てわけな。アンインストールでも別にいいかなとも思わないでもないが、この際なので「生きたまま殺」そうと。
最近やってなかったのもあるし、そもそもワタシは Windows サービスに関してほんとかなり無知でな、慣れとらんの。いやぁ助かる、このサイトが至れてる:
「コマンドラインからやる場合は」だけじゃないのがとても良いと思う、が、まぁワタシ向けには無論コマンドラインから:
1 [me@host: ~]$ # 止まれ
2 [me@host: ~]$ net stop redis
3 [me@host: ~]$ # 自動開始すんな
4 [me@host: ~]$ sc config redis start=disabled
Docker 版の方の話は後日。ここに追記、がいいかな。
独立したネタとして書くようなものではないがどこかに書き留めたくて、追記出来る場所を探すも、似たネタがここにしかなかったもんで…。全然関係ないネタだよ。「Find all Wi-Fi passwords with only 1 command | Windows 10 / 11 | NETVN」をメモしておきたかった。
sc や net コマンドとともに、この netsh コマンドも頭の片隅に入れときたいと思ったの。というかね、「ずっと接続しっぱなしのワタシの Wi-Fi」相手に「パスワードを毎度入力」してないから忘れてるわけさ、これを「接続出来てるなら」設定画面でパスワード可視にすればいいだけではあるものの、そもそも「Windows は各種設定に辿り着くのが年々ややこしくなって」てこれも覚えられん。こうやって一撃で確認出来る手段はありがたいなと思った。(あとこの際なので、パスワードをメモとっこうと思う。これとか駆使して。)
2022-05-24追記:
うん、結論から言うと、Docker 版が手に入ったことで「微」ではなく、完全な検証が可能になった。
まずは。「Docker の導入は貴様には簡単ろ?」とはいえ、時間だけはたんまりかかると思われるので、導入可能に該当し、それでも導入がまだの方はお急げ。
Docker Desktop 本体の導入が済んでいるならば、まずは「DOCKER OFFICIAL な redis コンテナお取り寄せ」:
1 [me@host: ~]$ docker pull redis
こういうイメージのレポジトリは dockerhub。探し物はここにある。
docker について「ざっくりとおおまかにイメージをつかみたい」際に、ワタシはこのビデオが役に立ったけど、貴様はどうかな。まぁ探せば何か良いものはあるかと思う。ともあれ。
ひとまず入手できた redis のバージョンを確認してみる:
1 [me@host: ~]$ docker run redis --version
2 Redis server v=7.0.0 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=1fad57151235ad0c
おお、最新、かな? なお、慣れないとかなりわかりにくいのだが、run の後ろに指定してる「redis」はコマンドとは違って、たぶん厳密にいうと「指定してるのはイメージだが、これによって起動するのはイメージに指定されたエントリポイントであり、そのエントリポイントはこの場合結局 redis なので、redis コマンドを起動してるのとほぼ等価」ということだと思う。
していよいよ「サービス開始」なのだが、それの「お行儀が良いやり方」:
1 [me@host: ~]$ docker run --name redis01 -p=6379:6379 -d redis
d は「デーモン」モード、多くの説明では「バックグランドモード」と説明してる。pで指定しているポートのマッピングは省略出来ないんだよね。理解のためのコツはまずは Docker コンテナを「小さな別マシン」と考えて、その機器にリモートアクセスしてるつもりになるといい。で、リモートのポートを「アタシんPCのポート」とマッピングする。「オレ:貴様」でマッピングするんだけど、そもそも「貴様(リモート)側は何番でリッスンしとるん」は、いったんマッピングせずに run してみて、docker ps で確認する手はある。ただ redis のデフォルトは 6379 で、変えられてはいないけどね。
「お行儀が」と言ってるのが「—name」の指定で、ここには任意の名前を指定出来るんだけど、「出来る」というか「しろ」。しないとはっきり言って管理が不快になる。docker stop などこの名前で行うので。指定しないと、コンテナが予め持ってる名前になっちゃうみたい、たとえば「admiring_kilby」みたいなとても記憶出来ないものに。それだけでなくて、そもそも多重起動を自在に出来るようになってるので、まさにそのために、名前管理が不可欠てことみたい。
さて、これでサービスが起動した状態になったのだが。最初にこのネタを書いた時点で使えてたのがこの redis-3.5.3 というバージョンだったんだけど、今 pip でアップグレードを試みたら、4.3.1 がインストールされた。中身を確認してみたら、GEOSEARCH などの対応がされてた。よしよし。
GEOSEARCH の追加はいいのだが、そもそもインターフェイスが結構変わってる:
1 Python 3.9.10 (tags/v3.9.10:f2f3f53, Jan 17 2022, 15:14:21) [MSC v.1929 64 bit (AMD64)] on win32
2 Type "help", "copyright", "credits" or "license" for more information.
3 >>> # 今回のは redis-4.3.1。
4 >>> import redis
5 >>> r = redis.Redis(host='127.0.0.1', port=6379, db=0)
6 >>> r.delete("ToFuTbl")
7 1
8 >>>
9 >>> # geoadd のインターフェイスが変わってる。*args で受けてたのが
10 >>> # Sequence になったんで、lon, lat, name 部分はタプルなりに。
11 >>> r.geoadd("ToFuTbl", (
12 ... 141.35437400, 43.06197200, "Tokyo",
13 ... 135.50204600, 34.69389100, "Osaka",
14 ... 135.76818100, 35.01157400, "Kyoto"))
15 3
16 >>>
17 >>> r.geodist("ToFuTbl", "Osaka", "Kyoto", "km")
18 42.8791
19 >>>
20 >>> r.geodist("ToFuTbl", "Tokyo", "Kyoto", "km")
21 1016.5984
22 >>>
23 >>> r.georadius("ToFuTbl", 135.7, 34.5, 80, "km")
24 [b'Osaka', b'Kyoto']
25 >>>
26 >>> r.georadius("ToFuTbl", 135.7, 34.5, 80, "km", "WITHDIST")
27 [[b'Osaka', 28.1702], [b'Kyoto', 57.2407]]
28 >>>
29 >>> r.georadius("ToFuTbl", 135.7, 34.5, 30, "km", "WITHDIST", "WITHCOORD")
30 [[b'Osaka', 28.1702, (135.50204783678055, 34.693890677523775)]]
31 >>>
32 >>> # georadius とか geodist がインターフェイス変更になってないのに、
33 >>> # 新規追加の geosearch はそれらとノリが違うんだよな。どうせなら
34 >>> # 全部こちらのノリにすりゃぁいいのに。
35 >>> r.geosearch("ToFuTbl", None, 135.7, 34.5, "km", 80, withcoord=True, withdist=True)
36 [[b'Osaka', 28.1702, (135.50204783678055, 34.693890677523775)], [b'Kyoto', 57.2407, (135.76818197965622, 35.01157488458721)]]
37 >>>
うん、たぶん良いと思う。georadius は deprecated としてマークされてるからてことだろうね、昔のまま使えた、けど、このあと geosearch だけになっていくんだろう。
ふむふむ。ここしばらく、ちょっとずつちょっとずつ Docker 遊びを進めてきてたんだけど、初めて「Docker は救世主」のちゃんとしたもので遊べた。満足だわい。よかったよかった。
あとね、慣れたらきっとこれ、サービスの活殺管理もすごく楽なんじゃないかしら。思ったより考え方が単純で理解しやすい上に docker コマンドも結構スッキリしてる…ように感じてる、今のところ。慣れたら、なので、まだワタシには少し難しく感じることもあるけどね。