Files
ncmr-songs/wip/274-romaji.py
T
mikkeli ee4c177233 274: shrines, power stations, readings; romaji generator
Field sweep additions, all absent from OpenStreetMap and found by the
author on Google Maps:
  40.16  滝下八幡神社          栗山町字滝下
  43.07  滝下発電所           栗山町字滝下 (北海道企業局)
  57.44  登川神社            夕張市楓
  66.78  豊進神社            むかわ町穂別稲里
  71.86  穂別ダム神社          むかわ町穂別稲里
  79.67  福山神社            むかわ町穂別福山
  93.58  名石駐車場           占冠村字中央
 102.36  日高神社            日高町宮下町
 104.52  日高発電所           日高町字千栄 (北海道電力)
 106.22  右左府発電所          日高町字日高
Plus 滝ノ上信号場 and 楓信号場, which OSM tags railway=signal /
railway=junction with no place tag, so both fell through every filter.

Corrections: モトツ橋 moved 86.16 → 87.12. It does not cross パンケモトツ沢川;
it is the sheltered span between モトツトンネル and 福山トンネル, roofed by
モトツシェルター — the identical 62 m lengths are the same deck. Author
also fixed 下福山橋 → 77.05 and オロロップ橋 → 77.55, matching route01's order.

Readings: kana is the source of truth. Romaji regenerated from it in
wāpuro style, no macrons. Adds 274-romaji.py, which does that
deterministically and is idempotent against the current table.

Artifact now shows Romaji in the table and kana under each strip mark.
CSV and GeoJSON re-propagated. 168 entries, 118 with readings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6MpcBisZYhX45oDGRRJoz
2026-09-12 17:54:45 +09:00

131 lines
5.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Fill the Romaji column of 274-dev.md from its Kana column.
Kana is the source of truth. Romaji is generated wāpuro-style — no macrons,
since they are awkward to type: おう→ou, ゅう→yuu, っ doubles the following
consonant, ー repeats the preceding vowel, ん→n' before a vowel or y.
The trailing generic is split off and lowercased (Kawabata hashi, Houshin jinja,
Takinoue kosenkyou); 丁目 take a hyphen (Shinmachi 3-choume). Compound proper
stems stay as one token, because kana carries no word boundary — add them to
MANUAL below if you want a break.
python3 274-romaji.py # rewrite the Romaji column
python3 274-romaji.py --check # report differences, change nothing
Rows with no kana are left with an empty Romaji cell rather than a guess.
"""
import io, os, re, sys
HERE = os.path.dirname(os.path.abspath(__file__))
DOC = os.path.join(HERE, '274-dev.md')
# stems that must not have a trailing 川 split off — it is part of the name
NOSPLIT = {'のぼりかわ', 'にいかわ', 'しんかわ', 'みかわ', 'たきさわがわ'}
# hand-set forms that the mechanical rules cannot derive
MANUAL = {}
import re
Y={'きゃ':'kya','きゅ':'kyu','きょ':'kyo','しゃ':'sha','しゅ':'shu','しょ':'sho',
'ちゃ':'cha','ちゅ':'chu','ちょ':'cho','にゃ':'nya','にゅ':'nyu','にょ':'nyo',
'ひゃ':'hya','ひゅ':'hyu','ひょ':'hyo','みゃ':'mya','みゅ':'myu','みょ':'myo',
'りゃ':'rya','りゅ':'ryu','りょ':'ryo','ぎゃ':'gya','ぎゅ':'gyu','ぎょ':'gyo',
'じゃ':'ja','じゅ':'ju','じょ':'jo','びゃ':'bya','びゅ':'byu','びょ':'byo',
'ぴゃ':'pya','ぴゅ':'pyu','ぴょ':'pyo','ぢゃ':'ja','ぢゅ':'ju','ぢょ':'jo',
'しぇ':'she','ちぇ':'che','じぇ':'je','てぃ':'ti','でぃ':'di','ふぁ':'fa',
'ふぃ':'fi','ふぇ':'fe','ふぉ':'fo','うぃ':'wi','うぇ':'we','うぉ':'wo','とぅ':'tu'}
S={'あ':'a','い':'i','う':'u','え':'e','お':'o','か':'ka','き':'ki','く':'ku','け':'ke','こ':'ko',
'さ':'sa','し':'shi','す':'su','せ':'se','そ':'so','た':'ta','ち':'chi','つ':'tsu','て':'te','と':'to',
'な':'na','に':'ni','ぬ':'nu','ね':'ne','の':'no','は':'ha','ひ':'hi','ふ':'fu','へ':'he','ほ':'ho',
'ま':'ma','み':'mi','む':'mu','め':'me','も':'mo','や':'ya','ゆ':'yu','よ':'yo',
'ら':'ra','り':'ri','る':'ru','れ':'re','ろ':'ro','わ':'wa','を':'o','ん':'n',
'が':'ga','ぎ':'gi','ぐ':'gu','げ':'ge','ご':'go','ざ':'za','じ':'ji','ず':'zu','ぜ':'ze','ぞ':'zo',
'だ':'da','ぢ':'ji','づ':'zu','で':'de','ど':'do','ば':'ba','び':'bi','ぶ':'bu','べ':'be','ぼ':'bo',
'ぱ':'pa','ぴ':'pi','ぷ':'pu','ぺ':'pe','ぽ':'po',
'ぁ':'a','ぃ':'i','ぅ':'u','ぇ':'e','ぉ':'o','ゃ':'ya','ゅ':'yu','ょ':'yo'}
VOW='aiueo'
def kana2romaji(s):
out=[];i=0
while i<len(s):
c=s[i]
if c.isdigit(): out.append(c); i+=1; continue
if c=='ー':
if out and out[-1] and out[-1][-1] in VOW: out.append(out[-1][-1])
i+=1; continue
if c=='っ':
j=i+1
if j<len(s):
nxt=Y.get(s[j:j+2]) or S.get(s[j],'')
if nxt and nxt[0] not in VOW: out.append(nxt[0])
i+=1; continue
two=s[i:i+2]
if two in Y: out.append(Y[two]); i+=2; continue
if c in S:
r=S[c]
if c=='ん':
nx=s[i+1:i+2]
nr=Y.get(s[i+1:i+3]) or S.get(nx,'')
if nr and (nr[0] in VOW or nr[0]=='y'): r="n'"
out.append(r); i+=1; continue
out.append(c); i+=1
return ''.join(out)
GENERIC=['しんごうじょう','こせんきょう','りくきょう','ほどうきょう','ちゅうがっこう','しょうがっこう','ゆうびんきょく','はつでんしょ',
'へんでんしょ','ちゅうしゃじょう','しょうぼうしょ','としょかん','きねんかん','こうみんかん',
'きゃんぷじょう','しぇるたー','とんねる','じんじゃ','だいばし','おおはし','ばし','はし',
'がわ','かわ','だむ','やくば','こうこう','こうえん','かいかん','ほーる','えき','てん','ちょうめ']
def split_generic(kana):
if kana in NOSPLIT: return kana,''
for g in GENERIC:
if kana.endswith(g) and len(kana)>len(g):
return kana[:-len(g)], g
return kana, ''
import re as _re
def romanise(kana):
m=_re.match(r'^(.*?)(\d+)ちょうめ$',kana)
if m:
a=kana2romaji(m.group(1)); a=a[:1].upper()+a[1:]
return '%s %s-choume'%(a,m.group(2))
stem,gen=split_generic(kana)
a=kana2romaji(stem); b=kana2romaji(gen) if gen else ''
a=a[:1].upper()+a[1:]
return (a+' '+b).strip() if b else a
def process(text):
out, filled, changed = [], 0, 0
for line in text.split('\n'):
if line.startswith('| ') and line.count('|') >= 13:
c = [x.strip() for x in line.strip('|').split('|')]
if re.match(r'^\d+\.\d+$', c[0]):
kana = c[3]
new = MANUAL.get(kana, romanise(kana)) if kana else ''
if new != c[4]:
if not c[4]:
filled += 1
else:
changed += 1
c[4] = new
line = '| ' + ' | '.join(c) + ' |'
out.append(line)
return '\n'.join(out), filled, changed
def main():
text = io.open(DOC, encoding='utf-8').read()
new, filled, changed = process(text)
if '--check' in sys.argv:
print('%d blank cells would be filled, %d existing would change' % (filled, changed))
return
io.open(DOC, 'w', encoding='utf-8').write(new)
print('%d filled, %d normalised' % (filled, changed))
if __name__ == '__main__':
main()