japanese_address_parser 3.0.5 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -2
  3. data/Gemfile.lock +6 -6
  4. data/js/node_modules/.package-lock.json +86 -1
  5. data/js/node_modules/@geolonia/normalize-japanese-addresses/dist/lib/cacheRegexes.d.ts +9 -2
  6. data/js/node_modules/@geolonia/normalize-japanese-addresses/dist/main-browser.js +141 -25
  7. data/js/node_modules/@geolonia/normalize-japanese-addresses/dist/main-es.d.ts +3 -0
  8. data/js/node_modules/@geolonia/normalize-japanese-addresses/dist/main-es.js +1922 -0
  9. data/js/node_modules/@geolonia/normalize-japanese-addresses/dist/main-node.d.ts +6 -0
  10. data/js/node_modules/@geolonia/normalize-japanese-addresses/dist/main-node.js +203 -54
  11. data/js/node_modules/@geolonia/normalize-japanese-addresses/dist/normalize.d.ts +47 -3
  12. data/js/node_modules/@geolonia/normalize-japanese-addresses/package.json +9 -5
  13. data/js/node_modules/@geolonia/normalize-japanese-addresses/rollup.config.ts +9 -0
  14. data/js/node_modules/@geolonia/normalize-japanese-addresses/src/config.ts +1 -0
  15. data/js/node_modules/@geolonia/normalize-japanese-addresses/src/lib/cacheRegexes.ts +71 -11
  16. data/js/node_modules/@geolonia/normalize-japanese-addresses/src/lib/dict.ts +3 -2
  17. data/js/node_modules/@geolonia/normalize-japanese-addresses/src/lib/kan2num.ts +6 -2
  18. data/js/node_modules/@geolonia/normalize-japanese-addresses/src/main-es.ts +4 -0
  19. data/js/node_modules/@geolonia/normalize-japanese-addresses/src/main-node.ts +41 -12
  20. data/js/node_modules/@geolonia/normalize-japanese-addresses/src/normalize.ts +165 -37
  21. data/js/package-lock.json +7 -7
  22. data/js/package.json +1 -1
  23. data/lib/japanese_address_parser/data/03-03206.csv +1 -0
  24. data/lib/japanese_address_parser/data/04-04421.csv +8 -8
  25. data/lib/japanese_address_parser/data/05-05203.csv +5 -0
  26. data/lib/japanese_address_parser/data/08-08222.csv +4 -0
  27. data/lib/japanese_address_parser/data/11-11442.csv +2 -0
  28. data/lib/japanese_address_parser/data/13-13225.csv +3 -0
  29. data/lib/japanese_address_parser/data/14-14134.csv +3 -0
  30. data/lib/japanese_address_parser/data/15-15104.csv +1 -1
  31. data/lib/japanese_address_parser/data/15-15222.csv +1 -1
  32. data/lib/japanese_address_parser/data/15-15225.csv +1 -1
  33. data/lib/japanese_address_parser/data/18-18202.csv +1 -1
  34. data/lib/japanese_address_parser/data/22-22102.csv +6 -0
  35. data/lib/japanese_address_parser/data/23-23202.csv +1 -0
  36. data/lib/japanese_address_parser/data/23-23207.csv +5 -0
  37. data/lib/japanese_address_parser/data/23-23213.csv +1 -0
  38. data/lib/japanese_address_parser/data/23-23236.csv +1 -0
  39. data/lib/japanese_address_parser/data/25-25206.csv +4 -0
  40. data/lib/japanese_address_parser/data/27-27212.csv +4 -0
  41. data/lib/japanese_address_parser/data/28-28108.csv +1 -0
  42. data/lib/japanese_address_parser/data/28-28204.csv +1 -1
  43. data/lib/japanese_address_parser/data/28-28226.csv +1 -0
  44. data/lib/japanese_address_parser/data/29-29201.csv +1 -1
  45. data/lib/japanese_address_parser/data/34-34207.csv +3 -0
  46. data/lib/japanese_address_parser/data/35-35215.csv +3 -0
  47. data/lib/japanese_address_parser/data/38-38204.csv +17 -0
  48. data/lib/japanese_address_parser/data/40-40131.csv +1 -0
  49. data/lib/japanese_address_parser/data/40-40342.csv +4 -0
  50. data/lib/japanese_address_parser/data/44-44201.csv +27 -0
  51. data/lib/japanese_address_parser/data/46-46201.csv +4 -0
  52. data/lib/japanese_address_parser/data/47-47205.csv +4 -0
  53. data/lib/japanese_address_parser/version.rb +1 -1
  54. metadata +6 -3
@@ -5,7 +5,7 @@ import { currentConfig } from '../config'
5
5
  import { __internals } from '../normalize'
6
6
  import { findKanjiNumbers } from '@geolonia/japanese-numeral'
7
7
 
8
- type PrefectureList = { [key: string]: string[] }
8
+ export type PrefectureList = { [key: string]: string[] }
9
9
  interface SingleTown {
10
10
  town: string
11
11
  originalTown?: string
@@ -13,8 +13,13 @@ interface SingleTown {
13
13
  lat: string
14
14
  lng: string
15
15
  }
16
- type TownList = SingleTown[]
17
-
16
+ export type TownList = SingleTown[]
17
+ interface SingleAddr {
18
+ addr: string
19
+ lat: string | null
20
+ lng: string | null
21
+ }
22
+ export type AddrList = SingleAddr[]
18
23
  interface GaikuListItem {
19
24
  gaiku: string
20
25
  lat: string
@@ -40,16 +45,16 @@ let cachedPrefectures: PrefectureList | undefined = undefined
40
45
  const cachedTowns: { [key: string]: TownList } = {}
41
46
  const cachedGaikuListItem: { [key: string]: GaikuListItem[] } = {}
42
47
  const cachedResidentials: { [key: string]: ResidentialList } = {}
43
- let cachedSameNamedPrefectureCityRegexPatterns:
44
- | [string, string][]
45
- | undefined = undefined
48
+ const cachedAddrs: { [key: string]: AddrList } = {} // TODO: use LRU
49
+ let cachedSameNamedPrefectureCityRegexPatterns: [string, string][] | undefined =
50
+ undefined
46
51
 
47
52
  export const getPrefectures = async () => {
48
53
  if (typeof cachedPrefectures !== 'undefined') {
49
54
  return cachedPrefectures
50
55
  }
51
56
 
52
- const prefsResp = await __internals.fetch('.json') // ja.json
57
+ const prefsResp = await __internals.fetch('.json', { level: 1 }) // ja.json
53
58
  const data = (await prefsResp.json()) as PrefectureList
54
59
  return cachePrefectures(data)
55
60
  }
@@ -104,6 +109,7 @@ export const getTowns = async (pref: string, city: string) => {
104
109
 
105
110
  const townsResp = await __internals.fetch(
106
111
  ['', encodeURI(pref), encodeURI(city) + '.json'].join('/'),
112
+ { level: 3, pref, city },
107
113
  )
108
114
  const towns = (await townsResp.json()) as TownList
109
115
  return (cachedTowns[cacheKey] = towns)
@@ -114,7 +120,13 @@ export const getGaikuList = async (
114
120
  city: string,
115
121
  town: string,
116
122
  ) => {
117
- const cacheKey = `${pref}-${city}-${town}`
123
+ if (currentConfig.interfaceVersion > 1) {
124
+ throw new Error(
125
+ `Invalid config.interfaceVersion: ${currentConfig.interfaceVersion}'}. Please set config.interfaceVersion to 1.`,
126
+ )
127
+ }
128
+
129
+ const cacheKey = `${pref}-${city}-${town}-v${currentConfig.interfaceVersion}`
118
130
  const cache = cachedGaikuListItem[cacheKey]
119
131
  if (typeof cache !== 'undefined') {
120
132
  return cache
@@ -136,7 +148,13 @@ export const getResidentials = async (
136
148
  city: string,
137
149
  town: string,
138
150
  ) => {
139
- const cacheKey = `${pref}-${city}-${town}`
151
+ if (currentConfig.interfaceVersion > 1) {
152
+ throw new Error(
153
+ `Invalid config.interfaceVersion: ${currentConfig.interfaceVersion}'}. Please set config.interfaceVersion to 1.`,
154
+ )
155
+ }
156
+
157
+ const cacheKey = `${pref}-${city}-${town}-v${currentConfig.interfaceVersion}`
140
158
  const cache = cachedResidentials[cacheKey]
141
159
  if (typeof cache !== 'undefined') {
142
160
  return cache
@@ -166,6 +184,34 @@ export const getResidentials = async (
166
184
  return (cachedResidentials[cacheKey] = residentials)
167
185
  }
168
186
 
187
+ export const getAddrs = async (pref: string, city: string, town: string) => {
188
+ if (currentConfig.interfaceVersion < 2) {
189
+ throw new Error(
190
+ `Invalid config.interfaceVersion: ${currentConfig.interfaceVersion}'}. Please set config.interfaceVersion to 2 or higher`,
191
+ )
192
+ }
193
+
194
+ const cacheKey = `${pref}-${city}-${town}-v${currentConfig.interfaceVersion}`
195
+ const cache = cachedAddrs[cacheKey]
196
+ if (typeof cache !== 'undefined') {
197
+ return cache
198
+ }
199
+
200
+ const addrsResp = await __internals.fetch(
201
+ ['', encodeURI(pref), encodeURI(city), encodeURI(town) + '.json'].join('/'),
202
+ { level: 8, pref, city, town },
203
+ )
204
+ let addrs: AddrList
205
+ try {
206
+ addrs = (await addrsResp.json()) as AddrList
207
+ } catch {
208
+ addrs = []
209
+ }
210
+
211
+ addrs.sort((res1, res2) => res1.addr.length - res2.addr.length)
212
+ return (cachedAddrs[cacheKey] = addrs)
213
+ }
214
+
169
215
  // 十六町 のように漢数字と町が連結しているか
170
216
  const isKanjiNumberFollewedByCho = (targetTownName: string) => {
171
217
  const xCho = targetTownName.match(/.町/g)
@@ -260,15 +306,29 @@ export const getTownRegexPatterns = async (pref: string, city: string) => {
260
306
  const _pattern = `(${patterns.join(
261
307
  '|',
262
308
  )})((丁|町)目?|番(町|丁)|条|軒|線|の町?|地割|号|[--﹣−‐⁃‑‒–—﹘―⎯⏤ーー─━])`
263
-
264
309
  return _pattern // デバッグのときにめんどくさいので変数に入れる。
265
310
  },
266
311
  ),
267
312
  )
268
-
269
313
  return [town, pattern]
270
314
  }) as [SingleTown, string][]
271
315
 
316
+ // X丁目の丁目なしの数字だけ許容するため、最後に数字だけ追加していく
317
+ for (const town of towns) {
318
+ const chomeMatch = town.town.match(
319
+ /([^一二三四五六七八九十]+)([一二三四五六七八九十]+)(丁目?)/,
320
+ )
321
+ if (!chomeMatch) {
322
+ continue
323
+ }
324
+ const chomeNamePart = chomeMatch[1]
325
+ const chomeNum = chomeMatch[2]
326
+ const pattern = toRegexPattern(
327
+ `^${chomeNamePart}(${chomeNum}|${kan2num(chomeNum)})`,
328
+ )
329
+ patterns.push([town, pattern])
330
+ }
331
+
272
332
  cachedTownRegexes.set(`${pref}-${city}`, patterns)
273
333
  return patterns
274
334
  }
@@ -1,8 +1,8 @@
1
1
  // JIS 第2水準 => 第1水準 及び 旧字体 => 新字体
2
- const JIS_OLD_KANJI = '亞,圍,壹,榮,驛,應,櫻,假,會,懷,覺,樂,陷,歡,氣,戲,據,挾,區,徑,溪,輕,藝,儉,圈,權,嚴,恆,國,齋,雜,蠶,殘,兒,實,釋,從,縱,敍,燒,條,剩,壤,釀,眞,盡,醉,髓,聲,竊,淺,錢,禪,爭,插,騷,屬,對,滯,擇,單,斷,癡,鑄,敕,鐵,傳,黨,鬪,屆,腦,廢,發,蠻,拂,邊,瓣,寶,沒,滿,藥,餘,樣,亂,兩,禮,靈,爐,灣,惡,醫,飮,營,圓,歐,奧,價,繪,擴,學,罐,勸,觀,歸,犧,擧,狹,驅,莖,經,繼,缺,劍,檢,顯,廣,鑛,碎,劑,參,慘,絲,辭,舍,壽,澁,肅,將,證,乘,疊,孃,觸,寢,圖,穗,樞,齊,攝,戰,潛,雙,莊,裝,藏,續,體,臺,澤,膽,彈,蟲,廳,鎭,點,燈,盜,獨,貳,霸,賣,髮,祕,佛,變,辯,豐,飜,默,與,譽,謠,覽,獵,勵,齡,勞,壓,爲,隱,衞,鹽,毆,穩,畫,壞,殼,嶽,卷,關,顏,僞,舊,峽,曉,勳,惠,螢,鷄,縣,險,獻,驗,效,號,濟,册,棧,贊,齒,濕,寫,收,獸,處,稱,奬,淨,繩,讓,囑,愼,粹,隨,數,靜,專,踐,纖,壯,搜,總,臟,墮,帶,瀧,擔,團,遲,晝,聽,遞,轉,當,稻,讀,惱,拜,麥,拔,濱,竝,辨,舖,襃,萬,譯,豫,搖,來,龍,壘,隸,戀,樓,鰺,鶯,蠣,攪,竈,灌,諫,頸,礦,蘂,靱,賤,壺,礪,檮,濤,邇,蠅,檜,儘,藪,籠,彌'.split(
2
+ const JIS_OLD_KANJI = '亞,圍,壹,榮,驛,應,櫻,假,會,懷,覺,樂,陷,歡,氣,戲,據,挾,區,徑,溪,輕,藝,儉,圈,權,嚴,恆,國,齋,雜,蠶,殘,兒,實,釋,從,縱,敍,燒,條,剩,壤,釀,眞,盡,醉,髓,聲,竊,淺,錢,禪,爭,插,騷,屬,對,滯,擇,單,斷,癡,鑄,敕,鐵,傳,黨,鬪,屆,腦,廢,發,蠻,拂,邊,瓣,寶,沒,滿,藥,餘,樣,亂,兩,禮,靈,爐,灣,惡,醫,飮,營,圓,歐,奧,價,繪,擴,學,罐,勸,觀,歸,犧,擧,狹,驅,莖,經,繼,缺,劍,檢,顯,廣,鑛,碎,劑,參,慘,絲,辭,舍,壽,澁,肅,將,證,乘,疊,孃,觸,寢,圖,穗,樞,齊,攝,戰,潛,雙,莊,裝,藏,續,體,臺,澤,膽,彈,蟲,廳,鎭,點,燈,盜,獨,貳,霸,賣,髮,祕,佛,變,辯,豐,飜,默,與,譽,謠,覽,獵,勵,齡,勞,壓,爲,隱,衞,鹽,毆,穩,畫,壞,殼,嶽,卷,關,顏,僞,舊,峽,曉,勳,惠,螢,鷄,縣,險,獻,驗,效,號,濟,册,棧,贊,齒,濕,寫,收,獸,處,稱,奬,淨,繩,讓,囑,愼,粹,隨,數,靜,專,踐,纖,壯,搜,總,臟,墮,帶,瀧,擔,團,遲,晝,聽,遞,轉,當,稻,讀,惱,拜,麥,拔,濱,竝,辨,舖,襃,萬,譯,豫,搖,來,龍,壘,隸,戀,樓,鰺,鶯,蠣,攪,竈,灌,諫,頸,礦,蘂,靱,賤,壺,礪,檮,濤,邇,蠅,檜,儘,藪,籠,彌,麩'.split(
3
3
  /,/,
4
4
  )
5
- const JIS_NEW_KANJI = '亜,囲,壱,栄,駅,応,桜,仮,会,懐,覚,楽,陥,歓,気,戯,拠,挟,区,径,渓,軽,芸,倹,圏,権,厳,恒,国,斎,雑,蚕,残,児,実,釈,従,縦,叙,焼,条,剰,壌,醸,真,尽,酔,髄,声,窃,浅,銭,禅,争,挿,騒,属,対,滞,択,単,断,痴,鋳,勅,鉄,伝,党,闘,届,脳,廃,発,蛮,払,辺,弁,宝,没,満,薬,余,様,乱,両,礼,霊,炉,湾,悪,医,飲,営,円,欧,奥,価,絵,拡,学,缶,勧,観,帰,犠,挙,狭,駆,茎,経,継,欠,剣,検,顕,広,鉱,砕,剤,参,惨,糸,辞,舎,寿,渋,粛,将,証,乗,畳,嬢,触,寝,図,穂,枢,斉,摂,戦,潜,双,荘,装,蔵,続,体,台,沢,胆,弾,虫,庁,鎮,点,灯,盗,独,弐,覇,売,髪,秘,仏,変,弁,豊,翻,黙,与,誉,謡,覧,猟,励,齢,労,圧,為,隠,衛,塩,殴,穏,画,壊,殻,岳,巻,関,顔,偽,旧,峡,暁,勲,恵,蛍,鶏,県,険,献,験,効,号,済,冊,桟,賛,歯,湿,写,収,獣,処,称,奨,浄,縄,譲,嘱,慎,粋,随,数,静,専,践,繊,壮,捜,総,臓,堕,帯,滝,担,団,遅,昼,聴,逓,転,当,稲,読,悩,拝,麦,抜,浜,並,弁,舗,褒,万,訳,予,揺,来,竜,塁,隷,恋,楼,鯵,鴬,蛎,撹,竃,潅,諌,頚,砿,蕊,靭,賎,壷,砺,梼,涛,迩,蝿,桧,侭,薮,篭,弥'.split(
5
+ const JIS_NEW_KANJI = '亜,囲,壱,栄,駅,応,桜,仮,会,懐,覚,楽,陥,歓,気,戯,拠,挟,区,径,渓,軽,芸,倹,圏,権,厳,恒,国,斎,雑,蚕,残,児,実,釈,従,縦,叙,焼,条,剰,壌,醸,真,尽,酔,髄,声,窃,浅,銭,禅,争,挿,騒,属,対,滞,択,単,断,痴,鋳,勅,鉄,伝,党,闘,届,脳,廃,発,蛮,払,辺,弁,宝,没,満,薬,余,様,乱,両,礼,霊,炉,湾,悪,医,飲,営,円,欧,奥,価,絵,拡,学,缶,勧,観,帰,犠,挙,狭,駆,茎,経,継,欠,剣,検,顕,広,鉱,砕,剤,参,惨,糸,辞,舎,寿,渋,粛,将,証,乗,畳,嬢,触,寝,図,穂,枢,斉,摂,戦,潜,双,荘,装,蔵,続,体,台,沢,胆,弾,虫,庁,鎮,点,灯,盗,独,弐,覇,売,髪,秘,仏,変,弁,豊,翻,黙,与,誉,謡,覧,猟,励,齢,労,圧,為,隠,衛,塩,殴,穏,画,壊,殻,岳,巻,関,顔,偽,旧,峡,暁,勲,恵,蛍,鶏,県,険,献,験,効,号,済,冊,桟,賛,歯,湿,写,収,獣,処,称,奨,浄,縄,譲,嘱,慎,粋,随,数,静,専,践,繊,壮,捜,総,臓,堕,帯,滝,担,団,遅,昼,聴,逓,転,当,稲,読,悩,拝,麦,抜,浜,並,弁,舗,褒,万,訳,予,揺,来,竜,塁,隷,恋,楼,鯵,鴬,蛎,撹,竃,潅,諌,頚,砿,蕊,靭,賎,壷,砺,梼,涛,迩,蝿,桧,侭,薮,篭,弥,麸'.split(
6
6
  /,/,
7
7
  )
8
8
 
@@ -29,6 +29,7 @@ export const toRegexPattern = (string: string) => {
29
29
  _str = _str
30
30
  .replace(/三栄町|四谷三栄町/g, '(三栄町|四谷三栄町)')
31
31
  .replace(/鬮野川|くじ野川|くじの川/g, '(鬮野川|くじ野川|くじの川)')
32
+ .replace(/柿碕町|柿さき町/g, '(柿碕町|柿さき町)')
32
33
  .replace(/通り|とおり/g, '(通り|とおり)')
33
34
  .replace(/埠頭|ふ頭/g, '(埠頭|ふ頭)')
34
35
  .replace(/番町|番丁/g, '(番町|番丁)')
@@ -3,8 +3,12 @@ import { kanji2number, findKanjiNumbers } from '@geolonia/japanese-numeral'
3
3
  export const kan2num = (string: string) => {
4
4
  const kanjiNumbers = findKanjiNumbers(string)
5
5
  for (let i = 0; i < kanjiNumbers.length; i++) {
6
- // @ts-ignore
7
- string = string.replace(kanjiNumbers[i], kanji2number(kanjiNumbers[i]))
6
+ try {
7
+ // @ts-ignore
8
+ string = string.replace(kanjiNumbers[i], kanji2number(kanjiNumbers[i]));
9
+ } catch (error) {
10
+ // ignore
11
+ }
8
12
  }
9
13
 
10
14
  return string
@@ -0,0 +1,4 @@
1
+ import * as Normalize from './normalize'
2
+
3
+ export const config = Normalize.config
4
+ export const normalize = Normalize.normalize
@@ -1,26 +1,55 @@
1
1
  import * as Normalize from './normalize'
2
2
  import { promises as fs } from 'fs'
3
3
  import unfetch from 'isomorphic-unfetch'
4
+ import { TransformRequestQuery } from './normalize'
4
5
 
5
- const fetchOrReadFile = async (
6
- input: string,
7
- ): Promise<Response | { json: () => Promise<unknown> }> => {
8
- const fileURL = new URL(`${Normalize.config.japaneseAddressesApi}${input}`)
9
- if (fileURL.protocol === 'http:' || fileURL.protocol === 'https:') {
10
- if (Normalize.config.geoloniaApiKey) {
11
- fileURL.search = `?geolonia-api-key=${Normalize.config.geoloniaApiKey}`
12
- }
13
- return unfetch(fileURL.toString())
14
- } else if (fileURL.protocol === 'file:') {
15
- const filePath = decodeURI(fileURL.pathname)
6
+ export const requestHandlers = {
7
+ file: (fileURL: URL) => {
8
+ const filePath =
9
+ process.platform === 'win32'
10
+ ? decodeURI(fileURL.pathname).substr(1)
11
+ : decodeURI(fileURL.pathname)
16
12
  return {
17
13
  json: async () => {
18
14
  const contents = await fs.readFile(filePath)
19
15
  return JSON.parse(contents.toString('utf-8'))
20
16
  },
21
17
  }
18
+ },
19
+ http: (fileURL: URL) => {
20
+ if (Normalize.config.geoloniaApiKey) {
21
+ fileURL.search = `?geolonia-api-key=${Normalize.config.geoloniaApiKey}`
22
+ }
23
+ return unfetch(fileURL.toString())
24
+ },
25
+ }
26
+
27
+ /**
28
+ * 正規化のためのデータを取得する
29
+ * @param input - Path part like '東京都/文京区.json'
30
+ * @param requestOptions - input を構造化したデータ
31
+ */
32
+ const fetchOrReadFile = async (
33
+ input: string,
34
+ requestOptions: TransformRequestQuery = { level: -1 },
35
+ ): Promise<Response | { json: () => Promise<unknown> }> => {
36
+ const fileURL = new URL(`${Normalize.config.japaneseAddressesApi}${input}`)
37
+ if (Normalize.config.transformRequest && requestOptions.level !== -1) {
38
+ const result = await Normalize.config.transformRequest(
39
+ fileURL,
40
+ requestOptions,
41
+ )
42
+ return {
43
+ json: async () => Promise.resolve(result),
44
+ }
22
45
  } else {
23
- throw new Error(`Unknown URL schema: ${fileURL.protocol}`)
46
+ if (fileURL.protocol === 'http:' || fileURL.protocol === 'https:') {
47
+ return requestHandlers.http(fileURL)
48
+ } else if (fileURL.protocol === 'file:') {
49
+ return requestHandlers.file(fileURL)
50
+ } else {
51
+ throw new Error(`Unknown URL schema: ${fileURL.protocol}`)
52
+ }
24
53
  }
25
54
  }
26
55
 
@@ -9,21 +9,51 @@ import {
9
9
  getCityRegexPatterns,
10
10
  getTownRegexPatterns,
11
11
  getSameNamedPrefectureCityRegexPatterns,
12
+ // interface version 1
12
13
  getResidentials,
13
14
  getGaikuList,
15
+ // interface version 2
16
+ getAddrs,
17
+ PrefectureList,
18
+ TownList,
19
+ AddrList,
14
20
  } from './lib/cacheRegexes'
15
21
  import unfetch from 'isomorphic-unfetch'
16
22
 
23
+ type TransformRequestResponse = null | PrefectureList | TownList | AddrList
24
+
25
+ export type TransformRequestQuery = {
26
+ level: number // level = -1 は旧 API。 transformRequestFunction を設定しても無視する
27
+ pref?: string
28
+ city?: string
29
+ town?: string
30
+ }
31
+
32
+ export type TransformRequestFunction = (
33
+ url: URL,
34
+ query: TransformRequestQuery,
35
+ ) => TransformRequestResponse | Promise<TransformRequestResponse>
36
+
17
37
  /**
18
38
  * normalize {@link Normalizer} の動作オプション。
19
39
  */
20
40
  export interface Config {
41
+ /**
42
+ * レスポンス型のバージョン。デフォルト 1
43
+ * 1 の場合は jyukyo: string, gaiku: string
44
+ * 2 の場合は addr: string, other: string
45
+ */
46
+ interfaceVersion: number
47
+
21
48
  /** 住所データを URL 形式で指定。 file:// 形式で指定するとローカルファイルを参照できます。 */
22
49
  japaneseAddressesApi: string
23
50
 
24
51
  /** 町丁目のデータを何件までキャッシュするか。デフォルト 1,000 */
25
52
  townCacheSize: number
26
53
 
54
+ /** 住所データへのリクエストを変形するオプション。 interfaceVersion === 2 で有効 */
55
+ transformRequest?: TransformRequestFunction
56
+
27
57
  geoloniaApiKey?: string
28
58
  }
29
59
  export const config: Config = currentConfig
@@ -31,7 +61,7 @@ export const config: Config = currentConfig
31
61
  /**
32
62
  * 住所の正規化結果として戻されるオブジェクト
33
63
  */
34
- export interface NormalizeResult {
64
+ interface NormalizeResult_v1 {
35
65
  /** 都道府県 */
36
66
  pref: string
37
67
  /** 市区町村 */
@@ -46,7 +76,7 @@ export interface NormalizeResult {
46
76
  addr: string
47
77
  /** 緯度。データが存在しない場合は null */
48
78
  lat: number | null
49
- /** 軽度。データが存在しない場合は null */
79
+ /** 経度。データが存在しない場合は null */
50
80
  lng: number | null
51
81
  /**
52
82
  * 住所文字列をどこまで判別できたかを表す正規化レベル
@@ -60,6 +90,34 @@ export interface NormalizeResult {
60
90
  level: number
61
91
  }
62
92
 
93
+ type NormalizeResult_v2 = {
94
+ /** 都道府県 */
95
+ pref: string
96
+ /** 市区町村 */
97
+ city: string
98
+ /** 町丁目 */
99
+ town: string
100
+ /** 住居表示または地番 */
101
+ addr: string
102
+ /** 正規化後の住所文字列 */
103
+ other?: string
104
+ /** 緯度。データが存在しない場合は null */
105
+ lat: number | null
106
+ /** 経度。データが存在しない場合は null */
107
+ lng: number | null
108
+ /**
109
+ * 住所文字列をどこまで判別できたかを表す正規化レベル
110
+ * - 0 - 都道府県も判別できなかった。
111
+ * - 1 - 都道府県まで判別できた。
112
+ * - 2 - 市区町村まで判別できた。
113
+ * - 3 - 町丁目まで判別できた。
114
+ * - 8 - 住居表示住所の街区符号・住居番号までの判別または地番住所の判別ができた。
115
+ */
116
+ level: number
117
+ }
118
+
119
+ export type NormalizeResult = NormalizeResult_v1 | NormalizeResult_v2
120
+
63
121
  /**
64
122
  * 正規化関数の {@link normalize} のオプション
65
123
  */
@@ -92,6 +150,7 @@ export type Normalizer = (
92
150
 
93
151
  export type FetchLike = (
94
152
  input: string,
153
+ requestQuery?: TransformRequestQuery,
95
154
  ) => Promise<Response | { json: () => Promise<unknown> }>
96
155
 
97
156
  const defaultOption = {
@@ -143,7 +202,13 @@ const normalizeResidentialPart = async (
143
202
  pref: string,
144
203
  city: string,
145
204
  town: string,
146
- ) => {
205
+ ): Promise<{
206
+ gaiku: string
207
+ jyukyo?: string
208
+ addr: string
209
+ lat: string
210
+ lng: string
211
+ } | null> => {
147
212
  const [gaikuListItem, residentials] = await Promise.all([
148
213
  getGaikuList(pref, city, town),
149
214
  getResidentials(pref, city, town),
@@ -183,6 +248,27 @@ const normalizeResidentialPart = async (
183
248
  return null
184
249
  }
185
250
 
251
+ const normalizeAddrPart = async (
252
+ addr: string,
253
+ pref: string,
254
+ city: string,
255
+ town: string,
256
+ ) => {
257
+ const addrListItem = await getAddrs(pref, city, town)
258
+
259
+ // 住居表示住所、および地番住所が見つからなかった
260
+ if (addrListItem.length === 0) {
261
+ return null
262
+ }
263
+
264
+ const addrItem = addrListItem.find((item) => addr.startsWith(item.addr))
265
+ if (addrItem) {
266
+ const other = addr.replace(addrItem.addr, '').trim()
267
+ return { addr: addrItem.addr, other, lat: addrItem.lat, lng: addrItem.lng }
268
+ }
269
+ return null
270
+ }
271
+
186
272
  export const normalize: Normalizer = async (
187
273
  address,
188
274
  _option = defaultOption,
@@ -353,7 +439,7 @@ export const normalize: Normalizer = async (
353
439
  '$1 $5',
354
440
  )
355
441
  .replace(
356
- /([0-9〇一二三四五六七八九十百千]+)(番地?)([0-9〇一二三四五六七八九十百千]+)号?/,
442
+ /([0-9〇一二三四五六七八九十百千]+)\s*(番地?)\s*([0-9〇一二三四五六七八九十百千]+)\s*号?/,
357
443
  '$1-$3',
358
444
  )
359
445
  .replace(/([0-9〇一二三四五六七八九十百千]+)番地?/, '$1')
@@ -378,7 +464,7 @@ export const normalize: Normalizer = async (
378
464
  // `-1` のようなケース
379
465
  return kan2num(s)
380
466
  })
381
- .replace(/-[^0-9]+([0-9〇一二三四五六七八九十百千]+)/, (s) => {
467
+ .replace(/-[^0-9]([0-9〇一二三四五六七八九十百千]+)/, (s) => {
382
468
  // `-あ1` のようなケース
383
469
  return kan2num(zen2han(s))
384
470
  })
@@ -392,43 +478,85 @@ export const normalize: Normalizer = async (
392
478
 
393
479
  addr = patchAddr(pref, city, town, addr)
394
480
 
395
- // 住居表示住所リストを使い番地号までの正規化を行う
396
- if (option.level > 3 && normalized && town) {
397
- normalized = await normalizeResidentialPart(addr, pref, city, town)
398
- }
399
- if (normalized) {
400
- lat = parseFloat(normalized.lat)
401
- lng = parseFloat(normalized.lng)
402
- }
403
-
404
- if (Number.isNaN(lat) || Number.isNaN(lng)) {
405
- lat = null
406
- lng = null
407
- }
408
-
409
481
  if (pref) level = level + 1
410
482
  if (city) level = level + 1
411
483
  if (town) level = level + 1
412
484
 
413
- const result: NormalizeResult = {
414
- pref,
415
- city,
416
- town,
417
- addr,
418
- lat,
419
- lng,
420
- level,
485
+ if (option.level <= 3 || level < 3) {
486
+ return { pref, city, town, addr, level, lat, lng }
421
487
  }
422
488
 
423
- if (normalized && 'gaiku' in normalized) {
424
- result.addr = normalized.addr
425
- result.gaiku = normalized.gaiku
426
- result.level = 7
427
- }
428
- if (normalized && 'jyukyo' in normalized) {
429
- result.jyukyo = normalized.jyukyo
430
- result.level = 8
431
- }
489
+ // ======================== Advanced section ========================
490
+ // これ以下は地番住所または住居表示住所までの正規化・ジオコーディングを行う処理
491
+ // 現状、インターフェース v1 と v2 が存在する
492
+ // japanese-addresses のフォーマット、および normalize 関数の戻り値が異なる
493
+ // 将来的に v2 に統一することを検討中
494
+ // ==================================================================
495
+
496
+ // v2 のインターフェース
497
+ if (currentConfig.interfaceVersion === 2) {
498
+ const normalizedAddrPart = await normalizeAddrPart(addr, pref, city, town)
499
+ let other = undefined
500
+ if (normalizedAddrPart) {
501
+ addr = normalizedAddrPart.addr
502
+ if (normalizedAddrPart.other) {
503
+ other = normalizedAddrPart.other
504
+ }
505
+ if (normalizedAddrPart.lat !== null)
506
+ lat = parseFloat(normalizedAddrPart.lat)
507
+ if (normalizedAddrPart.lng !== null)
508
+ lng = parseFloat(normalizedAddrPart.lng)
509
+ level = 8
510
+ }
511
+ const result: NormalizeResult_v2 = {
512
+ pref,
513
+ city,
514
+ town,
515
+ addr,
516
+ level,
517
+ lat,
518
+ lng,
519
+ }
520
+ if (other) {
521
+ result.other = other
522
+ }
523
+ return result
524
+ } else if (currentConfig.interfaceVersion === 1) {
525
+ // 住居表示住所リストを使い番地号までの正規化を行う
526
+ if (option.level > 3 && normalized && town) {
527
+ normalized = await normalizeResidentialPart(addr, pref, city, town)
528
+ }
529
+ if (normalized) {
530
+ lat = parseFloat(normalized.lat)
531
+ lng = parseFloat(normalized.lng)
532
+ }
533
+
534
+ if (Number.isNaN(lat) || Number.isNaN(lng)) {
535
+ lat = null
536
+ lng = null
537
+ }
538
+
539
+ const result: NormalizeResult_v1 = {
540
+ pref,
541
+ city,
542
+ town,
543
+ addr,
544
+ lat,
545
+ lng,
546
+ level,
547
+ }
548
+ if (normalized && 'gaiku' in normalized) {
549
+ result.addr = normalized.addr
550
+ result.gaiku = normalized.gaiku
551
+ result.level = 7
552
+ }
553
+ if (normalized && 'jyukyo' in normalized) {
554
+ result.jyukyo = normalized.jyukyo
555
+ result.level = 8
556
+ }
432
557
 
433
- return result
558
+ return result
559
+ } else {
560
+ throw new Error('invalid interfaceVersion')
561
+ }
434
562
  }
data/js/package-lock.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "": {
7
7
  "name": "japanese_address_parser",
8
8
  "dependencies": {
9
- "@geolonia/normalize-japanese-addresses": "^2.7.3"
9
+ "@geolonia/normalize-japanese-addresses": "^2.9.2"
10
10
  },
11
11
  "devDependencies": {}
12
12
  },
@@ -16,9 +16,9 @@
16
16
  "integrity": "sha512-xzSsIHhyyjqNpW8qSh5bFMu3FJvyJGBbNZ/t8clPHkigjUdZ7Ck8wkzCkcwlVd00RkoHwGDLnvXx5W0WiGK0TQ=="
17
17
  },
18
18
  "node_modules/@geolonia/normalize-japanese-addresses": {
19
- "version": "2.7.3",
20
- "resolved": "https://registry.npmjs.org/@geolonia/normalize-japanese-addresses/-/normalize-japanese-addresses-2.7.3.tgz",
21
- "integrity": "sha512-Gc1FI2oS8SjpL6l5Avkj+qcdBMbOIhUpZoETXgydxXMtvM2b9bVVQ7+YtlnBo4NDVIwFPSxaqeRqsXasig/mtg==",
19
+ "version": "2.9.2",
20
+ "resolved": "https://registry.npmjs.org/@geolonia/normalize-japanese-addresses/-/normalize-japanese-addresses-2.9.2.tgz",
21
+ "integrity": "sha512-sbeV/seJ/ue6pn2sLo+Ccunl6eB3yNhrUIzNpT2IiR9Fw6fW5f1lLdApODqhTmz++xr1dyPwZOzTXCGEaXejDA==",
22
22
  "dependencies": {
23
23
  "@geolonia/japanese-numeral": "^0.1.16",
24
24
  "isomorphic-unfetch": "^3.1.0",
@@ -101,9 +101,9 @@
101
101
  "integrity": "sha512-xzSsIHhyyjqNpW8qSh5bFMu3FJvyJGBbNZ/t8clPHkigjUdZ7Ck8wkzCkcwlVd00RkoHwGDLnvXx5W0WiGK0TQ=="
102
102
  },
103
103
  "@geolonia/normalize-japanese-addresses": {
104
- "version": "2.7.3",
105
- "resolved": "https://registry.npmjs.org/@geolonia/normalize-japanese-addresses/-/normalize-japanese-addresses-2.7.3.tgz",
106
- "integrity": "sha512-Gc1FI2oS8SjpL6l5Avkj+qcdBMbOIhUpZoETXgydxXMtvM2b9bVVQ7+YtlnBo4NDVIwFPSxaqeRqsXasig/mtg==",
104
+ "version": "2.9.2",
105
+ "resolved": "https://registry.npmjs.org/@geolonia/normalize-japanese-addresses/-/normalize-japanese-addresses-2.9.2.tgz",
106
+ "integrity": "sha512-sbeV/seJ/ue6pn2sLo+Ccunl6eB3yNhrUIzNpT2IiR9Fw6fW5f1lLdApODqhTmz++xr1dyPwZOzTXCGEaXejDA==",
107
107
  "requires": {
108
108
  "@geolonia/japanese-numeral": "^0.1.16",
109
109
  "isomorphic-unfetch": "^3.1.0",
data/js/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": true,
4
4
  "description": "JapaneseAddressParserは日本の住所をパースすることができるRuby gemです。",
5
5
  "dependencies": {
6
- "@geolonia/normalize-japanese-addresses": "^2.7.3"
6
+ "@geolonia/normalize-japanese-addresses": "^2.9.2"
7
7
  },
8
8
  "devDependencies": {}
9
9
  }
@@ -353,6 +353,7 @@ name,name_kana,name_romaji,nickname,latitude,longitude
353
353
  下鬼柳,シモオニヤナギ,SHIMONIYANAGI,十六地割,39.266345,141.096501
354
354
  下鬼柳,シモオニヤナギ,SHIMONIYANAGI,十四地割,39.266478,141.102455
355
355
  下鬼柳,シモオニヤナギ,SHIMONIYANAGI,四地割,39.282827,141.091036
356
+ しらゆり,シラユリ,SHIRAYURI,,,
356
357
  新穀町一丁目,シンコクチョウ 1,SHINKOKUCHO 1,,39.289124,141.112788
357
358
  新穀町二丁目,シンコクチョウ 2,SHINKOKUCHO 2,,39.289984,141.110576
358
359
  諏訪町一丁目,スワチョウ 1,SUWACHO 1,,39.286243,141.11941
@@ -1,12 +1,4 @@
1
1
  name,name_kana,name_romaji,nickname,latitude,longitude
2
- 落合桧和田,,,,38.430514,140.947568
3
- 落合桧和田,,,中道,38.430873,140.92928
4
- 落合桧和田,,,八幡堂,38.427406,140.938836
5
- 落合桧和田,,,千刈田,38.430733,140.932626
6
- 落合桧和田,,,新高原,38.432263,140.930077
7
- 落合桧和田,,,糯田,38.429822,140.932751
8
- 落合桧和田,,,遠浦,38.431684,140.925735
9
- 落合桧和田,,,高原,38.430335,140.93046
10
2
  落合相川,オチアイアイカワ,OCHIAI AIKAWA,,38.442109,140.921086
11
3
  落合相川,オチアイアイカワ,OCHIAI AIKAWA,塚越,38.438884,140.930493
12
4
  落合相川,オチアイアイカワ,OCHIAI AIKAWA,塚越三番,38.439941,140.935737
@@ -31,6 +23,14 @@ name,name_kana,name_romaji,nickname,latitude,longitude
31
23
  落合蒜袋,オチアイヒルブクロ,OCHIAI HIRUBUKURO,菱柄,38.447649,140.911786
32
24
  落合蒜袋,オチアイヒルブクロ,OCHIAI HIRUBUKURO,行屋下,38.449988,140.909907
33
25
  落合蒜袋,オチアイヒルブクロ,OCHIAI HIRUBUKURO,長町,38.449781,140.908786
26
+ 落合桧和田,オチアイヒワダ,,,38.430514,140.947568
27
+ 落合桧和田,オチアイヒワダ,,中道,38.430873,140.92928
28
+ 落合桧和田,オチアイヒワダ,,八幡堂,38.427406,140.938836
29
+ 落合桧和田,オチアイヒワダ,,千刈田,38.430733,140.932626
30
+ 落合桧和田,オチアイヒワダ,,新高原,38.432263,140.930077
31
+ 落合桧和田,オチアイヒワダ,,糯田,38.429822,140.932751
32
+ 落合桧和田,オチアイヒワダ,,遠浦,38.431684,140.925735
33
+ 落合桧和田,オチアイヒワダ,,高原,38.430335,140.93046
34
34
  落合報恩寺,オチアイホウオンジ,OCHIAI HOONJI,,38.443497,140.953906
35
35
  落合報恩寺,オチアイホウオンジ,OCHIAI HOONJI,大久保,38.44271,140.944656
36
36
  落合報恩寺,オチアイホウオンジ,OCHIAI HOONJI,竹沢下,38.438117,140.944617
@@ -43,9 +43,13 @@ name,name_kana,name_romaji,nickname,latitude,longitude
43
43
  猪岡,イノオカ,INOKA,水上,39.31756,140.521665
44
44
  猪岡,イノオカ,INOKA,長瀞,39.315335,140.519002
45
45
  梅の木町,ウメノキマチ,UMENOKIMACHI,,39.315702,140.557751
46
+ 駅西一丁目,エキニシ 1,EKINISHI 1,,,
47
+ 駅西二丁目,エキニシ 2,EKINISHI 2,,,
48
+ 駅西三丁目,エキニシ 3,EKINISHI 3,,,
46
49
  駅前町,エキマエチョウ,EKIMAECHO,,39.308784,140.561408
47
50
  駅南一丁目,エキミナミ 1,EKIMINAMI 1,,39.302415,140.56071
48
51
  駅南二丁目,エキミナミ 2,EKIMINAMI 2,,39.302577,140.558314
52
+ 駅南三丁目,エキミナミ 3,EKIMINAMI 3,,,
49
53
  追廻一丁目,オイマワシ 1,OIMAWASHI 1,,39.332247,140.568572
50
54
  追廻二丁目,オイマワシ 2,OIMAWASHI 2,,39.331959,140.570628
51
55
  追廻三丁目,オイマワシ 3,OIMAWASHI 3,,39.334844,140.572056
@@ -408,6 +412,7 @@ name,name_kana,name_romaji,nickname,latitude,longitude
408
412
  三本柳,サンボンヤナギ,SAMBONYANAGI,街道下,39.321213,140.538111
409
413
  三本柳,サンボンヤナギ,SAMBONYANAGI,馬場尻,39.324461,140.53328
410
414
  三枚橋一丁目,サンマイバシ 1,SAMMAIBASHI 1,,39.313272,140.558151
415
+ 三枚橋二丁目,サンマイバシ 2,SANMAIBASHI 2,,,
411
416
  静町,シズカマチ,SHIZUKAMACHI,,39.332264,140.545279
412
417
  静町,シズカマチ,SHIZUKAMACHI,北小屋,39.336115,140.543985
413
418
  静町,シズカマチ,SHIZUKAMACHI,払小屋,39.334205,140.543283
@@ -76,6 +76,10 @@ name,name_kana,name_romaji,nickname,latitude,longitude
76
76
  鉢形台一丁目,ハチガタダイ 1,HACHIGATADAI 1,,35.96364,140.647669
77
77
  鉢形台二丁目,ハチガタダイ 2,HACHIGATADAI 2,,35.961016,140.648239
78
78
  鉢形台三丁目,ハチガタダイ 3,HACHIGATADAI 3,,35.95818,140.649236
79
+ 平井東一丁目,ヒライヒガシ 1,HIRAIHIGASHI 1,,,
80
+ 平井東二丁目,ヒライヒガシ 2,HIRAIHIGASHI 2,,,
81
+ 平井東三丁目,ヒライヒガシ 3,HIRAIHIGASHI 3,,,
82
+ 平井東四丁目,ヒライヒガシ 4,HIRAIHIGASHI 4,,,
79
83
  緑ヶ丘一丁目,ミドリガオカ 1,MIDORIGAOKA 1,,35.973731,140.623658
80
84
  緑ヶ丘二丁目,ミドリガオカ 2,MIDORIGAOKA 2,,35.972016,140.622091
81
85
  緑ヶ丘三丁目,ミドリガオカ 3,MIDORIGAOKA 3,,35.971853,140.618483