Skip to content

Commit

Permalink
住居表示住所および地番住所への対応 (#217)
Browse files Browse the repository at this point in the history
* Add v2 API return type interface

* Add transform request function

* Add test for level v8

* Fix type and normalizing logic

* Windows test match fix
jestjs/jest#7914
  • Loading branch information
Kamata, Ryo committed Aug 1, 2023
1 parent 1eb382e commit 4d778a9
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 66 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
"main": "./dist/main-node.js",
"types": "./dist/normalize.d.ts",
"scripts": {
"test": "npm run test:main && npm run test:addresses && npm run test:node:platform && npm run test:residential",
"test": "npm run test:main && npm run test:addresses && npm run test:node:platform && npm run test:advanced",
"test:node:platform": "npm run test:node-win || npm run test:node",
"test:main": "jest test/main.test.ts",
"test:addresses": "jest test/addresses.test.ts",
"test:residential": "jest test/residential.test.ts --runInBand",
"test:advanced": "jest test/advanced --runInBand",
"test:util": "jest test/util.test.ts",
"test:node": "curl -sL https://github.com/geolonia/japanese-addresses/archive/refs/heads/master.tar.gz | tar xvfz - -C ./test > nul 2>&1 && jest test/fs.test.ts",
"test:node-win": "curl -sL https://github.com/geolonia/japanese-addresses/archive/refs/heads/master.tar.gz -o master.tar.gz && 7z x master.tar.gz -y -o./ && 7z x ./master.tar -y -o./test && del master.tar.gz master.tar && jest test/fs.test.ts",
"test:generate-test-data": "npx ts-node -O '{\"module\":\"commonjs\"}' test/build-test-data.ts > test/addresses.csv",
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const gh_pages_endpoint =
'https://geolonia.github.io/japanese-addresses/api/ja'

export const currentConfig: Config = {
interfaceVersion: 1,
japaneseAddressesApi: gh_pages_endpoint,
townCacheSize: 1_000,
}
64 changes: 55 additions & 9 deletions src/lib/cacheRegexes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@ import { currentConfig } from '../config'
import { __internals } from '../normalize'
import { findKanjiNumbers } from '@geolonia/japanese-numeral'

type PrefectureList = { [key: string]: string[] }
export type PrefectureList = { [key: string]: string[] }
interface SingleTown {
town: string
originalTown?: string
koaza: string
lat: string
lng: string
}
type TownList = SingleTown[]

export type TownList = SingleTown[]
interface SingleAddr {
addr: string
lat: string | null
lng: string | null
}
export type AddrList = SingleAddr[]
interface GaikuListItem {
gaiku: string
lat: string
Expand All @@ -40,16 +45,16 @@ let cachedPrefectures: PrefectureList | undefined = undefined
const cachedTowns: { [key: string]: TownList } = {}
const cachedGaikuListItem: { [key: string]: GaikuListItem[] } = {}
const cachedResidentials: { [key: string]: ResidentialList } = {}
let cachedSameNamedPrefectureCityRegexPatterns:
| [string, string][]
| undefined = undefined
const cachedAddrs: { [key: string]: AddrList } = {} // TODO: use LRU
let cachedSameNamedPrefectureCityRegexPatterns: [string, string][] | undefined =
undefined

export const getPrefectures = async () => {
if (typeof cachedPrefectures !== 'undefined') {
return cachedPrefectures
}

const prefsResp = await __internals.fetch('.json') // ja.json
const prefsResp = await __internals.fetch('.json', { level: 1 }) // ja.json
const data = (await prefsResp.json()) as PrefectureList
return cachePrefectures(data)
}
Expand Down Expand Up @@ -104,6 +109,7 @@ export const getTowns = async (pref: string, city: string) => {

const townsResp = await __internals.fetch(
['', encodeURI(pref), encodeURI(city) + '.json'].join('/'),
{ level: 3, pref, city },
)
const towns = (await townsResp.json()) as TownList
return (cachedTowns[cacheKey] = towns)
Expand All @@ -114,7 +120,13 @@ export const getGaikuList = async (
city: string,
town: string,
) => {
const cacheKey = `${pref}-${city}-${town}`
if (currentConfig.interfaceVersion > 1) {
throw new Error(
`Invalid config.interfaceVersion: ${currentConfig.interfaceVersion}'}. Please set config.interfaceVersion to 1.`,
)
}

const cacheKey = `${pref}-${city}-${town}-v${currentConfig.interfaceVersion}`
const cache = cachedGaikuListItem[cacheKey]
if (typeof cache !== 'undefined') {
return cache
Expand All @@ -136,7 +148,13 @@ export const getResidentials = async (
city: string,
town: string,
) => {
const cacheKey = `${pref}-${city}-${town}`
if (currentConfig.interfaceVersion > 1) {
throw new Error(
`Invalid config.interfaceVersion: ${currentConfig.interfaceVersion}'}. Please set config.interfaceVersion to 1.`,
)
}

const cacheKey = `${pref}-${city}-${town}-v${currentConfig.interfaceVersion}`
const cache = cachedResidentials[cacheKey]
if (typeof cache !== 'undefined') {
return cache
Expand Down Expand Up @@ -166,6 +184,34 @@ export const getResidentials = async (
return (cachedResidentials[cacheKey] = residentials)
}

export const getAddrs = async (pref: string, city: string, town: string) => {
if (currentConfig.interfaceVersion < 2) {
throw new Error(
`Invalid config.interfaceVersion: ${currentConfig.interfaceVersion}'}. Please set config.interfaceVersion to 2 or higher`,
)
}

const cacheKey = `${pref}-${city}-${town}-v${currentConfig.interfaceVersion}`
const cache = cachedAddrs[cacheKey]
if (typeof cache !== 'undefined') {
return cache
}

const addrsResp = await __internals.fetch(
['', encodeURI(pref), encodeURI(city), encodeURI(town) + 'json'].join('/'),
{ level: 8, pref, city, town },
)
let addrs: AddrList
try {
addrs = (await addrsResp.json()) as AddrList
} catch {
addrs = []
}

addrs.sort((res1, res2) => res1.addr.length - res2.addr.length)
return (cachedAddrs[cacheKey] = addrs)
}

// 十六町 のように漢数字と町が連結しているか
const isKanjiNumberFollewedByCho = (targetTownName: string) => {
const xCho = targetTownName.match(/.町/g)
Expand Down
53 changes: 41 additions & 12 deletions src/main-node.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,55 @@
import * as Normalize from './normalize'
import { promises as fs } from 'fs'
import unfetch from 'isomorphic-unfetch'
import { TransformRequestQuery } from './normalize'

const fetchOrReadFile = async (
input: string,
): Promise<Response | { json: () => Promise<unknown> }> => {
const fileURL = new URL(`${Normalize.config.japaneseAddressesApi}${input}`)
if (fileURL.protocol === 'http:' || fileURL.protocol === 'https:') {
if (Normalize.config.geoloniaApiKey) {
fileURL.search = `?geolonia-api-key=${Normalize.config.geoloniaApiKey}`
}
return unfetch(fileURL.toString())
} else if (fileURL.protocol === 'file:') {
const filePath = process.platform === 'win32' ? decodeURI(fileURL.pathname).substr(1) : decodeURI(fileURL.pathname);
export const requestHandlers = {
file: (fileURL: URL) => {
const filePath =
process.platform === 'win32'
? decodeURI(fileURL.pathname).substr(1)
: decodeURI(fileURL.pathname)
return {
json: async () => {
const contents = await fs.readFile(filePath)
return JSON.parse(contents.toString('utf-8'))
},
}
},
http: (fileURL: URL) => {
if (Normalize.config.geoloniaApiKey) {
fileURL.search = `?geolonia-api-key=${Normalize.config.geoloniaApiKey}`
}
return unfetch(fileURL.toString())
},
}

/**
* 正規化のためのデータを取得する
* @param input - Path part like '東京都/文京区.json'
* @param requestOptions - input を構造化したデータ
*/
const fetchOrReadFile = async (
input: string,
requestOptions: TransformRequestQuery = { level: -1 },
): Promise<Response | { json: () => Promise<unknown> }> => {
const fileURL = new URL(`${Normalize.config.japaneseAddressesApi}${input}`)
if (Normalize.config.transformRequest && requestOptions.level !== -1) {
const result = await Normalize.config.transformRequest(
fileURL,
requestOptions,
)
return {
json: async () => Promise.resolve(result),
}
} else {
throw new Error(`Unknown URL schema: ${fileURL.protocol}`)
if (fileURL.protocol === 'http:' || fileURL.protocol === 'https:') {
return requestHandlers.http(fileURL)
} else if (fileURL.protocol === 'file:') {
return requestHandlers.file(fileURL)
} else {
throw new Error(`Unknown URL schema: ${fileURL.protocol}`)
}
}
}

Expand Down
Loading

0 comments on commit 4d778a9

Please sign in to comment.