"""Build a local reference index and GeoJSON from the downloaded OSM extract.""" import csv import hashlib import html import json from pathlib import Path import xml.etree.ElementTree as ET from PIL import Image, ImageDraw root = Path(__file__).resolve().parents[1] / 'data/references/summit' osm = ET.parse(root / 'maps/summit.osm').getroot() nodes = {n.attrib['id']: [float(n.attrib['lon']), float(n.attrib['lat'])] for n in osm.findall('node')} features = [] for element in osm: tags = {t.attrib['k']: t.attrib['v'] for t in element.findall('tag')} if not tags or element.tag not in ('node', 'way'): continue if element.tag == 'node': lon,lat = nodes[element.attrib['id']] if not (126.950 <= lon <= 126.975 and 37.430 <= lat <= 37.453): continue geometry = {'type': 'Point', 'coordinates': nodes[element.attrib['id']]} else: coordinates = [nodes[n.attrib['ref']] for n in element.findall('nd') if n.attrib['ref'] in nodes] if len(coordinates) != len(element.findall('nd')) or len(coordinates) < 2: continue area = coordinates[0] == coordinates[-1] and (tags.get('area') == 'yes' or 'building' in tags or 'landuse' in tags) geometry = {'type': 'Polygon' if area else 'LineString', 'coordinates': [coordinates] if area else coordinates} features.append({'type': 'Feature', 'id': element.tag + '/' + element.attrib['id'], 'properties': tags, 'geometry': geometry}) (root / 'maps/summit.geojson').write_text(json.dumps({'type': 'FeatureCollection', 'features': features}, ensure_ascii=False), encoding='utf-8') structures = [f for f in features if any(k in f['properties'] for k in ('building', 'man_made', 'barrier')) or f['properties'].get('tourism') == 'viewpoint'] with (root / 'maps/structure-inventory.csv').open('w', encoding='utf-8-sig', newline='') as out: writer = csv.writer(out) writer.writerow(['osm_id', 'name', 'tags', 'geometry']) for f in structures: writer.writerow([f['id'], f['properties'].get('name', ''), json.dumps(f['properties'], ensure_ascii=False), json.dumps(f['geometry'])]) def xy(c): return ((c[0]-126.950)/.025*850, (37.453-c[1])/.023*985) svg = [''] for f in features: g, p = f['geometry'], f['properties'] if g['type'] == 'Point': if f not in structures and 'name' not in p: continue x,y = xy(g['coordinates']) svg.append(f'') if 'name' in p: svg.append(f'{html.escape(p["name"])}') else: coords = g['coordinates'][0] if g['type']=='Polygon' else g['coordinates'] points = ' '.join(f'{x:.1f},{y:.1f}' for x,y in map(xy, coords)) building = 'building' in p svg.append(f'') svg.append('N ↑ | OSM reference map | © OpenStreetMap contributors, ODbL') (root/'maps/overview.svg').write_text(''.join(svg), encoding='utf-8') downloads = json.loads((root/'photos/downloads.json').read_text(encoding='utf-8')) recent_path = root/'photos/recent-downloads.json' if recent_path.exists(): downloads += json.loads(recent_path.read_text(encoding='utf-8')) cards=[] valid=[] for item in downloads: if item['status'] != 'downloaded': continue if item['file'] in {f'photo-{i:02d}.jpg' for i in range(2,9)} | {'recent-01.jpg','recent-03.jpg','recent-06.jpg'}: item['relevance'] = '주요 구조물 참고사진에서 제외: 인물, 원경, 계곡 또는 음식' continue path=root/'photos'/item['file'] with Image.open(path) as im: im.verify() item['sha256']=hashlib.sha256(path.read_bytes()).hexdigest() valid.append(item) meta=item['metadata'] details='
'.join(html.escape(k)+': '+meta.get(k,{}).get('value','미상') for k in ['Artist','DateTimeOriginal','LicenseShortName','LicenseUrl']) cards.append(f'

{html.escape(item["file"])} — {html.escape(item["title"])}

{details}

원문 / 이용조건
') (root/'photos/verified-index.json').write_text(json.dumps(downloads,ensure_ascii=False,indent=2),encoding='utf-8') (root/'index.html').write_text('관악산 정상 구조물 참고자료

관악산 정상 구조물 참고자료

수집일 2026-10-29. OSM은 국가 수치지도 대체 참고자료이며 측량도면이 아닙니다. 사진은 촬영 시점의 모습으로 현재 형태와 차이가 있을 수 있습니다. 치수 미확정.

자료 범위와 한계 · GeoJSON · 구조물 목록 · OSM 출처·ODbL

'+''.join(cards)+'',encoding='utf-8') sheet=Image.new('RGB',(1000,210*((len(valid)+3)//4)),'white') draw=ImageDraw.Draw(sheet) for i,item in enumerate(valid): with Image.open(root/'photos'/item['file']) as im: im.thumbnail((245,180)) x,y=(i%4)*250,(i//4)*210 sheet.paste(im,(x,y)) draw.text((x+5,y+183),item['file'],fill='black') sheet.save(root/'photos/contact-sheet.jpg') print(json.dumps({'features':len(features),'structure_candidates':len(structures),'verified_photos':len(valid)}))