Fix: 탭 추가 시 활성화 안됨 + 지오코딩 속도 개선
- add_tab: 새 탭을 서버에서 즉시 활성화하도록 수정 - addTab(): loadTabs() 대신 직접 render() 호출 (탭 전환 오버헤드 제거) - 지오코딩: concurrent.futures.ThreadPoolExecutor로 병렬 처리 (5 worker) - 지오코딩: 단일 시도 + 캐시로 중복 요청 제거, 30초→4초 단축 - upload: 먼저 모든 주소 수집 후 병렬 지오코딩, 이후 데이터 구성
This commit is contained in:
96
app.py
96
app.py
@@ -1,4 +1,4 @@
|
|||||||
import os, io, random, requests, openpyxl
|
import os, io, random, requests, openpyxl, concurrent.futures
|
||||||
from flask import Flask, render_template, request, jsonify, send_file
|
from flask import Flask, render_template, request, jsonify, send_file
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@@ -23,32 +23,28 @@ def tab_data(tab_id):
|
|||||||
return t, None
|
return t, None
|
||||||
|
|
||||||
|
|
||||||
def geocode(address):
|
GEO_CACHE = {}
|
||||||
try:
|
|
||||||
url = 'https://nominatim.openstreetmap.org/search'
|
|
||||||
params = {'q': address.strip(), 'format': 'json', 'limit': 1}
|
|
||||||
headers = {'User-Agent': 'MapinDrag/1.0'}
|
|
||||||
resp = requests.get(url, params=params, headers=headers, timeout=10)
|
|
||||||
if resp.ok and resp.json():
|
|
||||||
r = resp.json()[0]
|
|
||||||
return float(r['lat']), float(r['lon'])
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
|
|
||||||
def geocode_with_fallback(address):
|
def geocode_with_fallback(address):
|
||||||
lat, lng = geocode(f'{address}, 대한민국')
|
if address in GEO_CACHE:
|
||||||
if lat is not None:
|
return GEO_CACHE[address]
|
||||||
return lat, lng
|
try:
|
||||||
lat, lng = geocode(f'서울특별시 용산구 {address}')
|
url = 'https://nominatim.openstreetmap.org/search'
|
||||||
if lat is not None:
|
params = {'q': address.strip(), 'format': 'json', 'limit': 1, 'countrycodes': 'kr'}
|
||||||
return lat, lng
|
headers = {'User-Agent': 'MapinDrag/1.0'}
|
||||||
lat, lng = geocode(f'용산구 {address}')
|
resp = requests.get(url, params=params, headers=headers, timeout=5)
|
||||||
if lat is not None:
|
if resp.ok:
|
||||||
|
data = resp.json()
|
||||||
|
if data:
|
||||||
|
lat, lng = float(data[0]['lat']), float(data[0]['lon'])
|
||||||
|
GEO_CACHE[address] = (lat, lng)
|
||||||
return lat, lng
|
return lat, lng
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
jitter = random.uniform(-0.005, 0.005)
|
jitter = random.uniform(-0.005, 0.005)
|
||||||
return YONGSAN_CENTER[0] + jitter, YONGSAN_CENTER[1] + jitter
|
result = (YONGSAN_CENTER[0] + jitter, YONGSAN_CENTER[1] + jitter)
|
||||||
|
GEO_CACHE[address] = result
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def empty_tab(name):
|
def empty_tab(name):
|
||||||
@@ -73,9 +69,8 @@ def add_tab():
|
|||||||
tid = f'tab_{store["next_id"]}'
|
tid = f'tab_{store["next_id"]}'
|
||||||
store['next_id'] += 1
|
store['next_id'] += 1
|
||||||
store['tabs'][tid] = empty_tab(name)
|
store['tabs'][tid] = empty_tab(name)
|
||||||
if store['active_tab'] is None:
|
|
||||||
store['active_tab'] = tid
|
store['active_tab'] = tid
|
||||||
return jsonify({'id': tid, 'name': name})
|
return jsonify({'id': tid, 'name': name, 'activeTab': tid})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/tabs/rename', methods=['POST'])
|
@app.route('/api/tabs/rename', methods=['POST'])
|
||||||
@@ -149,7 +144,27 @@ def upload():
|
|||||||
file.save(path)
|
file.save(path)
|
||||||
wb = openpyxl.load_workbook(path, data_only=True)
|
wb = openpyxl.load_workbook(path, data_only=True)
|
||||||
|
|
||||||
workers, workplaces = [], []
|
workers, workplaces, all_addrs = [], [], set()
|
||||||
|
for sheet_name in wb.sheetnames:
|
||||||
|
ws = wb[sheet_name]
|
||||||
|
headers = [str(c.value or '').strip() for c in ws[1]]
|
||||||
|
if '희망사항' in headers:
|
||||||
|
idx = {h: i for i, h in enumerate(headers)}
|
||||||
|
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||||
|
if not any(row): continue
|
||||||
|
name = str(row[idx.get('이름', 0)] or '').strip()
|
||||||
|
if name: all_addrs.add(str(row[idx.get('주소', 2)] or '').strip())
|
||||||
|
if '희망사항' not in headers:
|
||||||
|
idx = {h: i for i, h in enumerate(headers)}
|
||||||
|
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||||
|
if not any(row): continue
|
||||||
|
name = str(row[idx.get('이름', 0)] or '').strip()
|
||||||
|
if name: all_addrs.add(str(row[idx.get('주소', 1)] or '').strip())
|
||||||
|
|
||||||
|
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as exc:
|
||||||
|
geo_results = {a: exc.submit(geocode_with_fallback, a) for a in all_addrs}
|
||||||
|
geo_results = {a: f.result() for a, f in geo_results.items()}
|
||||||
|
|
||||||
for sheet_name in wb.sheetnames:
|
for sheet_name in wb.sheetnames:
|
||||||
ws = wb[sheet_name]
|
ws = wb[sheet_name]
|
||||||
headers = [str(c.value or '').strip() for c in ws[1]]
|
headers = [str(c.value or '').strip() for c in ws[1]]
|
||||||
@@ -157,17 +172,15 @@ def upload():
|
|||||||
if '희망사항' in headers:
|
if '희망사항' in headers:
|
||||||
idx = {h: i for i, h in enumerate(headers)}
|
idx = {h: i for i, h in enumerate(headers)}
|
||||||
for row in ws.iter_rows(min_row=2, values_only=True):
|
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||||
if not any(row):
|
if not any(row): continue
|
||||||
continue
|
|
||||||
name = str(row[idx.get('이름', 0)] or '').strip()
|
name = str(row[idx.get('이름', 0)] or '').strip()
|
||||||
if not name:
|
if not name: continue
|
||||||
continue
|
addr = str(row[idx.get('주소', 2)] or '').strip()
|
||||||
lat, lng = geocode_with_fallback(str(row[idx.get('주소', 2)] or '').strip())
|
lat, lng = geo_results[addr]
|
||||||
workers.append({
|
workers.append({
|
||||||
'id': f'w{len(workers)}',
|
'id': f'w{len(workers)}', 'name': name,
|
||||||
'name': name,
|
|
||||||
'hope': str(row[idx.get('희망사항', 1)] or '').strip(),
|
'hope': str(row[idx.get('희망사항', 1)] or '').strip(),
|
||||||
'address': str(row[idx.get('주소', 2)] or '').strip(),
|
'address': addr,
|
||||||
'dob': str(row[idx.get('생년월일', 3)] or '').strip(),
|
'dob': str(row[idx.get('생년월일', 3)] or '').strip(),
|
||||||
'phone': str(row[idx.get('연락처', 4)] or '').strip(),
|
'phone': str(row[idx.get('연락처', 4)] or '').strip(),
|
||||||
'lat': lat, 'lng': lng,
|
'lat': lat, 'lng': lng,
|
||||||
@@ -176,17 +189,14 @@ def upload():
|
|||||||
if '희망사항' not in headers:
|
if '희망사항' not in headers:
|
||||||
idx = {h: i for i, h in enumerate(headers)}
|
idx = {h: i for i, h in enumerate(headers)}
|
||||||
for row in ws.iter_rows(min_row=2, values_only=True):
|
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||||
if not any(row):
|
if not any(row): continue
|
||||||
continue
|
|
||||||
name = str(row[idx.get('이름', 0)] or '').strip()
|
name = str(row[idx.get('이름', 0)] or '').strip()
|
||||||
if not name:
|
if not name: continue
|
||||||
continue
|
addr = str(row[idx.get('주소', 1)] or '').strip()
|
||||||
lat, lng = geocode_with_fallback(str(row[idx.get('주소', 1)] or '').strip())
|
lat, lng = geo_results[addr]
|
||||||
workplaces.append({
|
workplaces.append({
|
||||||
'id': f'p{len(workplaces)}',
|
'id': f'p{len(workplaces)}', 'name': name,
|
||||||
'name': name,
|
'address': addr, 'lat': lat, 'lng': lng,
|
||||||
'address': str(row[idx.get('주소', 1)] or '').strip(),
|
|
||||||
'lat': lat, 'lng': lng,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t['workers'] = workers
|
t['workers'] = workers
|
||||||
|
|||||||
@@ -115,8 +115,9 @@ async function addTab() {
|
|||||||
body: JSON.stringify({ name: name.trim() || undefined }),
|
body: JSON.stringify({ name: name.trim() || undefined }),
|
||||||
});
|
});
|
||||||
tabDataCache[r.id] = { workers: [], workplaces: [], assignments: {} };
|
tabDataCache[r.id] = { workers: [], workplaces: [], assignments: {} };
|
||||||
activeTabId = r.id;
|
activeTabId = r.activeTab;
|
||||||
await loadTabs();
|
render();
|
||||||
|
renderTabBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renameTab(e, tid) {
|
async function renameTab(e, tid) {
|
||||||
|
|||||||
Reference in New Issue
Block a user