japanese_address_parser 3.0.5 → 3.1.1

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.
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