pbf_reverse_geocoder 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +47 -67
- data/example.rb +2 -0
- data/lib/pbf_reverse_geocoder/pbf_tile_reader.rb +85 -9
- data/lib/pbf_reverse_geocoder/simple_pbf_parser.rb +32 -1
- data/lib/pbf_reverse_geocoder/version.rb +1 -1
- data/lib/pbf_reverse_geocoder.rb +7 -1
- data/scripts/build_tiles.rb +105 -0
- data/scripts/reverse_geocode.rb +35 -0
- metadata +3 -2
- data/pbf_reverse_geocoder.gemspec +0 -40
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 565ce530d13a4f82c66c492fedb67fb04c4582f946a742c9f9bafbc61d5c7489
|
|
4
|
+
data.tar.gz: 7ef4a199766300d85fc56546ffcb5b8f228b02ce175498a17d5d58795e916dca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f02fc27bd784f5d50bc9f742e5a6a20da1b9fed09eba10025c8c39a28ef931bc1187a565253f2c349c9b2a61ca49d1af3f255fff05923256645c618e1d4bcb42
|
|
7
|
+
data.tar.gz: b427b846715628849378482459bf20e1462bc26afe4bda1cff82144df7ed4a034d9105c14f95bebbc2a004f7701e7cff3d17de0b306dc7226a3749ae3479c6c7
|
data/README.md
CHANGED
|
@@ -40,7 +40,12 @@ tiles_dir = './tiles' # ダウンロードしたタイルの場所
|
|
|
40
40
|
result = PbfReverseGeocoder.reverse_geocode(139.7671, 35.6812, tiles_dir)
|
|
41
41
|
|
|
42
42
|
puts result
|
|
43
|
-
# => {
|
|
43
|
+
# => {
|
|
44
|
+
# "prefecture" => "東京都",
|
|
45
|
+
# "city" => "千代田区",
|
|
46
|
+
# "municipality" => "千代田区",
|
|
47
|
+
# "code" => "13101"
|
|
48
|
+
# }
|
|
44
49
|
```
|
|
45
50
|
|
|
46
51
|
### より詳しい使用例
|
|
@@ -73,6 +78,18 @@ end
|
|
|
73
78
|
# 札幌駅: 北海道 札幌市北区 (01102)
|
|
74
79
|
```
|
|
75
80
|
|
|
81
|
+
### 取得できる情報
|
|
82
|
+
|
|
83
|
+
`reverse_geocode` は行政区域の階層を加工せず返します。返却は下記の正規化キーのみ(N03_*は返しません)。
|
|
84
|
+
|
|
85
|
+
- `prefecture`: 都道府県 (`N03_001`)
|
|
86
|
+
- `sub_prefecture`: 支庁/振興局など (`N03_002`)
|
|
87
|
+
- `county`: 郡 (`N03_003`)
|
|
88
|
+
- `municipality`: 市区町村 (`N03_004`)
|
|
89
|
+
- `ward`: 政令市の行政区 (`N03_005` がある場合)
|
|
90
|
+
- `city`: `municipality` と `ward` を連結した互換用フィールド
|
|
91
|
+
- `code`: 全国地方公共団体コード (`N03_007` を5桁でゼロ埋め)
|
|
92
|
+
|
|
76
93
|
### Railsでの使用例
|
|
77
94
|
|
|
78
95
|
```ruby
|
|
@@ -98,81 +115,37 @@ end
|
|
|
98
115
|
|
|
99
116
|
### タイルデータの準備
|
|
100
117
|
|
|
101
|
-
|
|
118
|
+
国土交通省の最新行政区域データ(例: [N03-2025](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N03-2025.html))を、そのままの属性でタイル化して利用します。リポジトリ直下に `N03-20250101_GML.zip` と展開済みの `N03-20250101_GML/` を配置済みです。
|
|
102
119
|
|
|
103
|
-
|
|
120
|
+
**必要なツール**
|
|
121
|
+
- [tippecanoe](https://github.com/felt/tippecanoe)(必須)
|
|
122
|
+
- [ogr2ogr](https://gdal.org/programs/ogr2ogr.html)(GeoJSON が同梱されていない場合のみ)
|
|
104
123
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
[@geolonia/open-reverse-geocoder](https://github.com/geolonia/open-reverse-geocoder)のリポジトリから直接タイルを取得できます。
|
|
124
|
+
**データの取得例**
|
|
108
125
|
|
|
109
126
|
```bash
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
cd open-reverse-geocoder
|
|
115
|
-
git sparse-checkout set docs/tiles
|
|
116
|
-
|
|
117
|
-
# タイルをホスティング用のディレクトリに配置
|
|
118
|
-
cp -r docs/tiles /path/to/hosting/directory
|
|
127
|
+
# 2025年版N03データをダウンロード(約数百MB)
|
|
128
|
+
curl -LO https://nlftp.mlit.go.jp/ksj/gml/data/N03/N03-2025/N03-20250101_GML.zip
|
|
129
|
+
unzip N03-20250101_GML.zip
|
|
119
130
|
```
|
|
120
131
|
|
|
121
|
-
|
|
122
|
-
> - **Webサーバ**: `public/tiles`(Rails/Nginxなど)
|
|
123
|
-
> - **共有ストレージ**: NFS/S3マウントポイントなど
|
|
124
|
-
> - **アプリケーション内蔵**: `vendor/tiles`
|
|
125
|
-
> - タイルは約560個、合計サイズは数十MB程度です
|
|
126
|
-
>
|
|
127
|
-
> **ホスティングのポイント**:
|
|
128
|
-
> - タイルデータは全サーバで共有可能(読み取り専用)
|
|
129
|
-
> - CDN経由での配信も可能(PBFファイルはバイナリ)
|
|
130
|
-
> - コンテナ環境ではボリュームマウントで共有
|
|
131
|
-
|
|
132
|
-
#### 方法2: ソースデータから自分でビルド(最新データが必要な場合)
|
|
133
|
-
|
|
134
|
-
国土数値情報から最新の行政区域データを使って、自分でタイルをビルドすることもできます。
|
|
135
|
-
|
|
136
|
-
**🔧 必要なツール:**
|
|
137
|
-
- [ogr2ogr](https://gdal.org/programs/ogr2ogr.html) - GDALツール(GeoJSON変換用)
|
|
138
|
-
- [Tippecanoe](https://github.com/felt/tippecanoe) - Mapboxのタイル生成ツール
|
|
139
|
-
- [mb-util](https://github.com/mapbox/mbutil) - MBTiles変換ツール
|
|
140
|
-
|
|
141
|
-
**macOSの場合:**
|
|
132
|
+
**タイル作成(加工なし)**
|
|
142
133
|
|
|
143
134
|
```bash
|
|
144
|
-
#
|
|
145
|
-
|
|
146
|
-
pip install mbutil
|
|
135
|
+
# 2025年版N03データをタイル化する例
|
|
136
|
+
ruby scripts/build_tiles.rb --source N03-20250101_GML --tiles-dir ./tiles --layer N03
|
|
147
137
|
```
|
|
148
138
|
|
|
149
|
-
|
|
139
|
+
- ズームレベルは `--zoom` で変更可能(デフォルト10固定の全国範囲)
|
|
140
|
+
- GeoJSONを直接使う場合は `--geojson path/to/N03.geojson` を指定
|
|
141
|
+
- GeoJSON が無い場合、Shapefile から `ogr2ogr` で自動変換します
|
|
150
142
|
|
|
151
|
-
|
|
152
|
-
# 1. 元のリポジトリをクローン(ビルドスクリプトを使用)
|
|
153
|
-
git clone https://github.com/geolonia/open-reverse-geocoder.git
|
|
154
|
-
cd open-reverse-geocoder
|
|
155
|
-
|
|
156
|
-
# 2. 依存関係をインストール
|
|
157
|
-
npm install
|
|
158
|
-
|
|
159
|
-
# 3. タイルをビルド(国土数値情報から自動ダウンロード&ビルド)
|
|
160
|
-
npm run build:tiles
|
|
143
|
+
**動作確認コマンド**
|
|
161
144
|
|
|
162
|
-
|
|
163
|
-
|
|
145
|
+
```bash
|
|
146
|
+
ruby scripts/reverse_geocode.rb --tiles-dir ./tiles --lng 139.7671 --lat 35.6812
|
|
164
147
|
```
|
|
165
|
-
|
|
166
|
-
**ビルドプロセスの詳細:**
|
|
167
|
-
|
|
168
|
-
1. 国土数値情報から最新の[行政区域データ](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N03-v2_4.html)をダウンロード
|
|
169
|
-
2. `ogr2ogr`でShapefileをGeoJSONに変換
|
|
170
|
-
3. プロパティ調整スクリプトを実行(prefecture, city, codeフィールドを整形)
|
|
171
|
-
4. `tippecanoe`でMBTilesを生成(非圧縮で出力)
|
|
172
|
-
5. `mb-util`でタイルを分解して静的ファイル化
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
> 詳細は[@geolonia/open-reverse-geocoder](https://github.com/geolonia/open-reverse-geocoder#%E3%82%BF%E3%82%A4%E3%83%AB%E3%81%AE%E3%83%93%E3%83%AB%E3%83%89%E6%96%B9%E6%B3%95)のREADMEを参照してください。
|
|
148
|
+
東京駅周辺なら東京都千代田区(コード13101)が返る想定です。
|
|
176
149
|
|
|
177
150
|
#### ディレクトリ構造
|
|
178
151
|
|
|
@@ -191,20 +164,27 @@ tiles/
|
|
|
191
164
|
└── 413.pbf
|
|
192
165
|
```
|
|
193
166
|
|
|
167
|
+
※ `tiles/` は `.gitignore` 済みです。生成物はコミット不要です。
|
|
168
|
+
|
|
194
169
|
#### タイルの範囲について
|
|
195
170
|
|
|
196
171
|
- **ズームレベル10固定**: このライブラリは @geolonia/open-reverse-geocoder と同じくズームレベル10のタイルのみを使用します(約30km四方)
|
|
197
|
-
- **日本全体**: X座標 896-926、Y座標 396-413
|
|
172
|
+
- **日本全体**: X座標 896-926、Y座標 396-413の範囲で日本全国をカバー(N03公式データに基づく)
|
|
198
173
|
- **個別地域**: 必要な地域のタイルのみをダウンロードすることも可能
|
|
199
174
|
|
|
175
|
+
> 既存の Geolonia タイル(layer: `japanese-admins`)も互換性のため読み込めますが、最新データを使う場合は上記のN03公式データを推奨します。
|
|
176
|
+
|
|
200
177
|
### 戻り値
|
|
201
178
|
|
|
202
179
|
成功時:
|
|
203
180
|
```ruby
|
|
204
181
|
{
|
|
205
|
-
"prefecture" => "
|
|
206
|
-
"
|
|
207
|
-
"
|
|
182
|
+
"prefecture" => "北海道",
|
|
183
|
+
"sub_prefecture" => "石狩振興局",
|
|
184
|
+
"city" => "札幌市中央区",
|
|
185
|
+
"municipality" => "札幌市",
|
|
186
|
+
"ward" => "中央区",
|
|
187
|
+
"code" => "01101" # 全国地方公共団体コード(5桁ゼロ埋め)
|
|
208
188
|
}
|
|
209
189
|
```
|
|
210
190
|
|
data/example.rb
CHANGED
|
@@ -11,6 +11,8 @@ result = PbfReverseGeocoder.reverse_geocode(139.7671, 35.6812, tiles_dir)
|
|
|
11
11
|
if result
|
|
12
12
|
puts "都道府県: #{result['prefecture']}"
|
|
13
13
|
puts "市区町村: #{result['city']}"
|
|
14
|
+
puts "市町村: #{result['municipality']}" if result['municipality']
|
|
15
|
+
puts "行政区: #{result['ward']}" if result['ward']
|
|
14
16
|
puts "地方公共団体コード: #{result['code']}"
|
|
15
17
|
else
|
|
16
18
|
puts '該当する行政区域が見つかりませんでした'
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'simple_pbf_parser'
|
|
4
4
|
require_relative 'geometry_decoder'
|
|
5
|
+
require 'zlib'
|
|
6
|
+
require 'stringio'
|
|
5
7
|
|
|
6
8
|
# PBFタイルを読み込んでフィーチャー一覧を返すモジュール
|
|
7
9
|
module PbfReverseGeocoder
|
|
8
10
|
|
|
9
11
|
class PbfTileReader
|
|
10
12
|
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
+
# サポートするレイヤー名
|
|
14
|
+
# - 最新のN03タイルをそのまま使う場合: N03
|
|
15
|
+
# - 互換用: @geolonia/open-reverse-geocoder の japanese-admins
|
|
16
|
+
LAYER_NAMES = ['N03', 'N03-2025', 'japanese-admins'].freeze
|
|
13
17
|
|
|
14
18
|
# PBFタイルを読み込んでフィーチャー一覧を返す
|
|
15
19
|
#
|
|
@@ -24,14 +28,22 @@ module PbfReverseGeocoder
|
|
|
24
28
|
def self.read_tile(tile_path, tile_x, tile_y, zoom)
|
|
25
29
|
return [] unless File.exist?(tile_path)
|
|
26
30
|
|
|
27
|
-
#
|
|
31
|
+
# バイナリ読み込み(gzipなら展開)
|
|
28
32
|
pbf_data = File.binread(tile_path)
|
|
33
|
+
if gzip?(pbf_data)
|
|
34
|
+
begin
|
|
35
|
+
pbf_data = Zlib::GzipReader.new(StringIO.new(pbf_data)).read
|
|
36
|
+
rescue Zlib::GzipFile::Error
|
|
37
|
+
warn "Failed to gunzip tile: #{tile_path}"
|
|
38
|
+
return []
|
|
39
|
+
end
|
|
40
|
+
end
|
|
29
41
|
|
|
30
42
|
# SimplePbfParserでパース
|
|
31
43
|
tile = SimplePbfParser.parse(pbf_data)
|
|
32
44
|
|
|
33
45
|
# japanese-admins レイヤーを抽出
|
|
34
|
-
layer = tile
|
|
46
|
+
layer = find_layer(tile)
|
|
35
47
|
return [] unless layer
|
|
36
48
|
|
|
37
49
|
# フィーチャーをGeoJSON形式に変換
|
|
@@ -74,16 +86,80 @@ module PbfReverseGeocoder
|
|
|
74
86
|
key = layer[:keys][key_idx]
|
|
75
87
|
value_obj = layer[:values][val_idx]
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
|
|
89
|
+
value = extract_value(value_obj)
|
|
90
|
+
props[key] = value unless key.nil?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
normalize_properties(props)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# 値オブジェクトからRuby値を抽出
|
|
97
|
+
#
|
|
98
|
+
# @param value_obj [Hash, nil]
|
|
99
|
+
# @return [Object, nil]
|
|
100
|
+
def self.extract_value(value_obj)
|
|
101
|
+
return '' unless value_obj
|
|
102
|
+
|
|
103
|
+
value_obj[:string_value] ||
|
|
104
|
+
value_obj[:float_value] ||
|
|
105
|
+
value_obj[:double_value] ||
|
|
106
|
+
value_obj[:int_value] ||
|
|
107
|
+
value_obj[:uint_value] ||
|
|
108
|
+
value_obj[:sint_value] ||
|
|
109
|
+
value_obj[:bool_value] ||
|
|
110
|
+
''
|
|
111
|
+
end
|
|
79
112
|
|
|
80
|
-
|
|
113
|
+
# N03形式のプロパティを標準化
|
|
114
|
+
#
|
|
115
|
+
# - N03_* を pref/municipality/ward などに正規化
|
|
116
|
+
# - city は municipality と ward を連結した互換フィールド
|
|
117
|
+
#
|
|
118
|
+
# @param props [Hash]
|
|
119
|
+
# @return [Hash]
|
|
120
|
+
def self.normalize_properties(props)
|
|
121
|
+
prefecture = props['prefecture'] || props['N03_001']
|
|
122
|
+
sub_prefecture = props['sub_prefecture'] || props['N03_002']
|
|
123
|
+
county = props['county'] || props['N03_003']
|
|
124
|
+
municipality = props['municipality'] || props['city'] || props['N03_004']
|
|
125
|
+
ward = props['ward'] || props['N03_005']
|
|
126
|
+
|
|
127
|
+
code = props['code'] || props['N03_007'] || props['id']
|
|
128
|
+
code = code.to_i.to_s if code.is_a?(Integer)
|
|
129
|
+
code = code.to_s.rjust(5, '0') if code
|
|
130
|
+
|
|
131
|
+
if ward.to_s.empty? && municipality == props['city']
|
|
132
|
+
# 分離されていない市+区の文字列を分割(例: 大阪市中央区)
|
|
133
|
+
if municipality && (m = municipality.match(/\A(.+市)(.+区)\z/))
|
|
134
|
+
municipality = m[1]
|
|
135
|
+
ward = m[2]
|
|
136
|
+
end
|
|
81
137
|
end
|
|
82
138
|
|
|
83
|
-
props
|
|
139
|
+
city = props['city'] || [municipality, ward].compact.join
|
|
140
|
+
|
|
141
|
+
result = {}
|
|
142
|
+
result['prefecture'] = prefecture if prefecture
|
|
143
|
+
result['sub_prefecture'] = sub_prefecture if sub_prefecture
|
|
144
|
+
result['county'] = county if county
|
|
145
|
+
result['municipality'] = municipality if municipality
|
|
146
|
+
result['ward'] = ward if ward
|
|
147
|
+
result['city'] = city unless city.nil? || city.empty?
|
|
148
|
+
result['code'] = code if code
|
|
149
|
+
|
|
150
|
+
result
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def self.find_layer(tile)
|
|
154
|
+
tile[:layers].find { |l| LAYER_NAMES.include?(l[:name]) } ||
|
|
155
|
+
tile[:layers].first
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def self.gzip?(data)
|
|
159
|
+
data.bytes[0, 2] == [0x1f, 0x8b]
|
|
84
160
|
end
|
|
85
161
|
|
|
86
|
-
private_class_method :decode_properties
|
|
162
|
+
private_class_method :decode_properties, :extract_value, :find_layer, :gzip?
|
|
87
163
|
|
|
88
164
|
end
|
|
89
165
|
|
|
@@ -143,6 +143,32 @@ module PbfReverseGeocoder
|
|
|
143
143
|
wire_type = field_key & 0x7
|
|
144
144
|
|
|
145
145
|
case wire_type
|
|
146
|
+
when WIRE_TYPE_VARINT
|
|
147
|
+
number, pos = read_varint(data, pos)
|
|
148
|
+
case field_number
|
|
149
|
+
when 4
|
|
150
|
+
value[:int_value] = number
|
|
151
|
+
when 5
|
|
152
|
+
value[:uint_value] = number
|
|
153
|
+
when 6
|
|
154
|
+
value[:sint_value] = zigzag_decode(number)
|
|
155
|
+
when 7
|
|
156
|
+
value[:bool_value] = number != 0
|
|
157
|
+
end
|
|
158
|
+
when WIRE_TYPE_32BIT
|
|
159
|
+
# float_value
|
|
160
|
+
if field_number == 2
|
|
161
|
+
bytes = data[pos, 4]
|
|
162
|
+
pos += 4
|
|
163
|
+
value[:float_value] = bytes.pack('C*').unpack1('e')
|
|
164
|
+
end
|
|
165
|
+
when WIRE_TYPE_64BIT
|
|
166
|
+
# double_value
|
|
167
|
+
if field_number == 3
|
|
168
|
+
bytes = data[pos, 8]
|
|
169
|
+
pos += 8
|
|
170
|
+
value[:double_value] = bytes.pack('C*').unpack1('E')
|
|
171
|
+
end
|
|
146
172
|
when WIRE_TYPE_LENGTH_DELIMITED
|
|
147
173
|
length, pos = read_varint(data, pos)
|
|
148
174
|
value_bytes = data[pos, length]
|
|
@@ -220,8 +246,13 @@ module PbfReverseGeocoder
|
|
|
220
246
|
pos
|
|
221
247
|
end
|
|
222
248
|
|
|
249
|
+
def self.zigzag_decode(value)
|
|
250
|
+
(value >> 1) ^ -(value & 1)
|
|
251
|
+
end
|
|
252
|
+
|
|
223
253
|
private_class_method :parse_layer, :parse_feature, :parse_value,
|
|
224
|
-
:read_varint, :unpack_packed_varint, :skip_field
|
|
254
|
+
:read_varint, :unpack_packed_varint, :skip_field,
|
|
255
|
+
:zigzag_decode
|
|
225
256
|
|
|
226
257
|
end
|
|
227
258
|
|
data/lib/pbf_reverse_geocoder.rb
CHANGED
|
@@ -29,7 +29,13 @@ module PbfReverseGeocoder
|
|
|
29
29
|
tile_path = TileCalculator.tile_path(tile_x, tile_y, zoom, tiles_dir)
|
|
30
30
|
|
|
31
31
|
# PBFタイルを読み込んでパース
|
|
32
|
-
features = PbfTileReader.read_tile(tile_path, tile_x, tile_y, zoom)
|
|
32
|
+
features = PbfTileReader.read_tile(tile_path, tile_x, tile_y, zoom).map do |feature|
|
|
33
|
+
normalized = PbfTileReader.normalize_properties(feature[:properties])
|
|
34
|
+
{
|
|
35
|
+
geometry: feature[:geometry],
|
|
36
|
+
properties: normalized
|
|
37
|
+
}
|
|
38
|
+
end
|
|
33
39
|
|
|
34
40
|
# 点を含むポリゴンを検索
|
|
35
41
|
point = [lng, lat]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Build vector tiles from the raw N03 dataset without altering attributes.
|
|
4
|
+
# Usage:
|
|
5
|
+
# ruby scripts/build_tiles.rb --source N03-20250101_GML --tiles-dir ./tiles
|
|
6
|
+
#
|
|
7
|
+
# Requirements:
|
|
8
|
+
# - tippecanoe
|
|
9
|
+
# - (optional) ogr2ogr when GeoJSON is not present in the source directory
|
|
10
|
+
|
|
11
|
+
require 'optparse'
|
|
12
|
+
require 'pathname'
|
|
13
|
+
require 'fileutils'
|
|
14
|
+
|
|
15
|
+
options = {
|
|
16
|
+
source: 'N03-20250101_GML',
|
|
17
|
+
tiles_dir: 'tiles',
|
|
18
|
+
zoom: 10,
|
|
19
|
+
layer: 'N03'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
OptionParser.new do |opts|
|
|
23
|
+
opts.banner = 'Usage: ruby scripts/build_tiles.rb [options]'
|
|
24
|
+
|
|
25
|
+
opts.on('--source PATH', 'Path to the extracted N03 dataset directory') do |v|
|
|
26
|
+
options[:source] = v
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
opts.on('--geojson PATH', 'Use a specific GeoJSON file (skip conversion)') do |v|
|
|
30
|
+
options[:geojson] = v
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
opts.on('--tiles-dir PATH', 'Output directory for the generated tiles') do |v|
|
|
34
|
+
options[:tiles_dir] = v
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
opts.on('--zoom LEVEL', Integer, 'Zoom level (default: 10)') do |v|
|
|
38
|
+
options[:zoom] = v
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
opts.on('--layer NAME', 'Layer name to embed in tiles (default: N03)') do |v|
|
|
42
|
+
options[:layer] = v
|
|
43
|
+
end
|
|
44
|
+
end.parse!
|
|
45
|
+
|
|
46
|
+
def command_available?(cmd)
|
|
47
|
+
system("command -v #{cmd} >/dev/null 2>&1")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def find_first(pattern)
|
|
51
|
+
Dir.glob(pattern).first
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def ensure_geojson(source_dir, geojson_override)
|
|
55
|
+
return Pathname.new(geojson_override).expand_path if geojson_override
|
|
56
|
+
|
|
57
|
+
geojson = find_first(source_dir.join('*.geojson').to_s)
|
|
58
|
+
return Pathname.new(geojson) if geojson
|
|
59
|
+
|
|
60
|
+
shp = find_first(source_dir.join('*.shp').to_s)
|
|
61
|
+
return nil unless shp
|
|
62
|
+
|
|
63
|
+
unless command_available?('ogr2ogr')
|
|
64
|
+
abort 'GeoJSON not found. Install GDAL (ogr2ogr) or provide --geojson to continue.'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
geojson_path = Pathname.new(shp.sub(/\.shp\z/, '.geojson'))
|
|
68
|
+
puts "Converting Shapefile to GeoJSON with ogr2ogr -> #{geojson_path}"
|
|
69
|
+
system('ogr2ogr', '-f', 'GeoJSON', geojson_path.to_s, shp) || abort('ogr2ogr failed')
|
|
70
|
+
|
|
71
|
+
geojson_path
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def build_tiles(geojson_path, tiles_dir, zoom, layer)
|
|
75
|
+
abort 'tippecanoe is required to build tiles' unless command_available?('tippecanoe')
|
|
76
|
+
|
|
77
|
+
FileUtils.rm_rf(tiles_dir)
|
|
78
|
+
FileUtils.mkdir_p(tiles_dir)
|
|
79
|
+
|
|
80
|
+
cmd = [
|
|
81
|
+
'tippecanoe',
|
|
82
|
+
'--output-to-directory', tiles_dir.to_s,
|
|
83
|
+
'--force',
|
|
84
|
+
'--layer', layer,
|
|
85
|
+
'-Z', zoom.to_s,
|
|
86
|
+
'-z', zoom.to_s,
|
|
87
|
+
'--no-feature-limit',
|
|
88
|
+
'--no-tile-size-limit',
|
|
89
|
+
geojson_path.to_s
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
puts "Building tiles with tippecanoe (zoom=#{zoom}, layer=#{layer})"
|
|
93
|
+
system(*cmd) || abort('tippecanoe failed')
|
|
94
|
+
|
|
95
|
+
puts "Tiles created in #{tiles_dir}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
source_dir = Pathname.new(options[:source]).expand_path
|
|
99
|
+
abort "Source directory not found: #{source_dir}" unless source_dir.directory?
|
|
100
|
+
|
|
101
|
+
geojson_path = ensure_geojson(source_dir, options[:geojson])
|
|
102
|
+
abort 'GeoJSON source could not be prepared.' unless geojson_path && geojson_path.file?
|
|
103
|
+
|
|
104
|
+
build_tiles(geojson_path, Pathname.new(options[:tiles_dir]).expand_path,
|
|
105
|
+
options[:zoom], options[:layer])
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative '../lib/pbf_reverse_geocoder'
|
|
6
|
+
|
|
7
|
+
options = {
|
|
8
|
+
tiles_dir: './tiles',
|
|
9
|
+
lng: 139.7671,
|
|
10
|
+
lat: 35.6812
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
OptionParser.new do |opts|
|
|
14
|
+
opts.banner = 'Usage: ruby scripts/reverse_geocode.rb --tiles-dir ./tiles --lng 139.7671 --lat 35.6812'
|
|
15
|
+
|
|
16
|
+
opts.on('--tiles-dir PATH', 'Directory where PBF tiles are stored') do |v|
|
|
17
|
+
options[:tiles_dir] = v
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
opts.on('--lng FLOAT', Float, 'Longitude') do |v|
|
|
21
|
+
options[:lng] = v
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
opts.on('--lat FLOAT', Float, 'Latitude') do |v|
|
|
25
|
+
options[:lat] = v
|
|
26
|
+
end
|
|
27
|
+
end.parse!
|
|
28
|
+
|
|
29
|
+
result = PbfReverseGeocoder.reverse_geocode(options[:lng], options[:lat], options[:tiles_dir])
|
|
30
|
+
|
|
31
|
+
if result
|
|
32
|
+
puts JSON.pretty_generate(result)
|
|
33
|
+
else
|
|
34
|
+
warn 'No administrative area found for the given coordinate.'
|
|
35
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pbf_reverse_geocoder
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keisuke Terada
|
|
@@ -74,7 +74,8 @@ files:
|
|
|
74
74
|
- lib/pbf_reverse_geocoder/simple_pbf_parser.rb
|
|
75
75
|
- lib/pbf_reverse_geocoder/tile_calculator.rb
|
|
76
76
|
- lib/pbf_reverse_geocoder/version.rb
|
|
77
|
-
-
|
|
77
|
+
- scripts/build_tiles.rb
|
|
78
|
+
- scripts/reverse_geocode.rb
|
|
78
79
|
homepage: https://github.com/keisuke2236/pbf_reverse_geocoder
|
|
79
80
|
licenses:
|
|
80
81
|
- MIT
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'lib/pbf_reverse_geocoder/version'
|
|
4
|
-
|
|
5
|
-
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name = 'pbf_reverse_geocoder'
|
|
7
|
-
spec.version = PbfReverseGeocoder::VERSION
|
|
8
|
-
spec.authors = ['Keisuke Terada']
|
|
9
|
-
spec.email = ['rorensu2236@gmail.com']
|
|
10
|
-
|
|
11
|
-
spec.summary = 'PBF-based reverse geocoding library for Japanese administrative areas'
|
|
12
|
-
spec.description = 'A lightweight reverse geocoding library that uses Mapbox Vector Tiles (PBF format) to find Japanese administrative areas (prefecture, city) from latitude/longitude coordinates. Ruby implementation of @geolonia/open-reverse-geocoder.'
|
|
13
|
-
spec.homepage = 'https://github.com/keisuke2236/pbf_reverse_geocoder'
|
|
14
|
-
spec.license = 'MIT'
|
|
15
|
-
spec.required_ruby_version = '>= 3.0.0'
|
|
16
|
-
|
|
17
|
-
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
|
-
spec.metadata['source_code_uri'] = 'https://github.com/keisuke2236/pbf_reverse_geocoder'
|
|
19
|
-
spec.metadata['changelog_uri'] = 'https://github.com/keisuke2236/pbf_reverse_geocoder/blob/main/CHANGELOG.md'
|
|
20
|
-
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
21
|
-
|
|
22
|
-
# Specify which files should be added to the gem when it is released.
|
|
23
|
-
spec.files = Dir.chdir(__dir__) do
|
|
24
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
|
25
|
-
(File.expand_path(f) == __FILE__) ||
|
|
26
|
-
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
spec.bindir = 'exe'
|
|
30
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
31
|
-
spec.require_paths = ['lib']
|
|
32
|
-
|
|
33
|
-
# Runtime dependencies
|
|
34
|
-
# (現時点では標準ライブラリのみ使用)
|
|
35
|
-
|
|
36
|
-
# Development dependencies
|
|
37
|
-
spec.add_development_dependency 'rake', '~> 13.0'
|
|
38
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
39
|
-
spec.add_development_dependency 'rubocop', '~> 1.21'
|
|
40
|
-
end
|