File size: 4,033 Bytes
a1c0952
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import fs from 'node:fs';
import path from 'node:path';

import { parse as csvParse } from 'csv-parse';
import cliProgress from 'cli-progress';

import { cityName, MachiAzaApi, machiAzaName, PrefectureApi, prefectureName, SingleCity, SinglePrefecture } from '../data.js';
import { HeaderRow } from '../address_data.js';

function readUntilHeaderEnd(path: string): Promise<Buffer> {
  const HEADER_END = Buffer.from('=END=\n', 'utf8');
  return new Promise((resolve, reject) => {
    const readStream = fs.createReadStream(path);
    const chunks: Buffer[] = [];
    let foundEnd = false;
    readStream.on('data', (chunk: Buffer) => {
      if (foundEnd) {
        return;
      }
      const headerEndPos = chunk.indexOf(HEADER_END);
      if (headerEndPos !== -1) {
        foundEnd = true;
        readStream.close();
        chunks.push(chunk.subarray(0, headerEndPos));
      } else {
        chunks.push(chunk);
      }
    });
    readStream.on('close', () => {
      resolve(Buffer.concat(chunks));
    });
    readStream.on('error', (err) => {
      reject(err);
    });
  });
}

export async function getRangesFromCSV(path: string): Promise<undefined | HeaderRow[]> {
  try {
    const headerData = await readUntilHeaderEnd(path);
    const headerStream = csvParse(headerData);
    const rows: HeaderRow[] = [];
    for await (const line_ of headerStream) {
      const line = line_ as [string, string, string];
      if (line[0] === '=END=') {
        break;
      }
      const [name, start, length] = line;
      rows.push({
        name,
        offset: parseInt(start),
        length: parseInt(length),
      });
    }
    return rows;
  } catch (e) {
    if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
      return undefined;
    }
    throw e;
  }
}

async function main(argv: string[]) {
  const apiDir = argv[2] || path.join(import.meta.dirname, '..', '..', 'out', 'api');
  const jaFile = path.join(apiDir, 'ja.json');
  const api = JSON.parse(fs.readFileSync(jaFile, 'utf-8')) as PrefectureApi;

  const progress = new cliProgress.SingleBar({
    format: ' {bar} {percentage}% | ETA: {eta_formatted} | {value}/{total}',
    barCompleteChar: '\u2588',
    barIncompleteChar: '\u2591',
    etaBuffer: 30,
    fps: 2,
    // No-TTY output is required for CI/CD environments
    noTTYOutput: true,
  });

  const flatCities: [SinglePrefecture, SingleCity][] = [];
  for (const pref of api.data) {
    for (const city of pref.cities) {
      flatCities.push([pref, city]);
    }
  }

  progress.start(flatCities.length, 0);
  try {
    for (const [pref, city] of flatCities) {
      const cityPrefix = path.join(apiDir, 'ja', prefectureName(pref), cityName(city));
      const [
        chibanHeader,
        rsdtHeader,
      ] = await Promise.all([
        getRangesFromCSV(`${cityPrefix}-地番.txt`),
        getRangesFromCSV(`${cityPrefix}-住居表示.txt`),
      ]);

      if (!chibanHeader && !rsdtHeader) {
        progress.increment();
        continue;
      }

      const maData = JSON.parse(await fs.promises.readFile(`${cityPrefix}.json`, 'utf8')) as MachiAzaApi;
      for (const headerRow of chibanHeader || []) {
        const ma = maData.data.find((ma) => machiAzaName(ma) === headerRow.name);
        if (ma) {
          ma.csv_ranges = ma.csv_ranges || {};
          ma.csv_ranges['地番'] = { start: headerRow.offset, length: headerRow.length };
        }
      }
      for (const headerRow of rsdtHeader || []) {
        const ma = maData.data.find((ma) => machiAzaName(ma) === headerRow.name);
        if (ma) {
          ma.csv_ranges = ma.csv_ranges || {};
          ma.csv_ranges['住居表示'] = { start: headerRow.offset, length: headerRow.length };
        }
      }

      await fs.promises.writeFile(`${cityPrefix}.json`, JSON.stringify(maData));

      progress.increment();
    }
  } finally {
    progress.stop();
  }
}

export default main;