diff --git a/samples/v09/Vihren-v09-coverage.json b/samples/v09/Vihren-v09-coverage.json new file mode 100644 index 0000000..2d9462e --- /dev/null +++ b/samples/v09/Vihren-v09-coverage.json @@ -0,0 +1,15 @@ +{ + "status": "PASS", + "counts": { + "pois": 101, + "has_native_semantics": 43, + "generated_name": 42, + "has_bgm_poi_tag": 101, + "needs_custom_style": 58 + }, + "catalog_types": 13, + "exported_types": 13, + "note": "POIs without standard OSM categories are valid for this PRIVATE map because bgm_poi uses custom index and renderer. They require importing the style; this does not prove OBF persistence or screen visibility.", + "errors": [], + "missing_types_from_catalog": [] +} diff --git a/samples/v09/Vihren-v09-source.csv b/samples/v09/Vihren-v09-source.csv new file mode 100644 index 0000000..f4d3cbf --- /dev/null +++ b/samples/v09/Vihren-v09-source.csv @@ -0,0 +1,102 @@ +mapset,kind,code,lat,lon,name +"02234083","classic","0x6614",41.7204094,23.3722329,"Скала - 3310ACQWEHI" +"02234083","classic","0x6616",41.7381334,23.3741426,"Гергийца (2589)" +"02234083","classic","0x660c",41.7340136,23.3734131,"Порта" +"02234083","classic","0x6616",41.7392492,23.3764601,"Безимен (2571)" +"02234083","classic","0x6616",41.7404938,23.3828115,"Безимен (2554)" +"02234083","classic","0x6616",41.7317605,23.3725762,"Безимен (2543)" +"02234083","classic","0x6615",41.7433262,23.3739281,"Дупка" +"02234083","classic","0x660c",41.7268896,23.3712673,"Синанишка порта" +"02234083","classic","0x6600",41.7258811,23.3701301,"2467" +"02234083","classic","0x6616",41.7444634,23.3775759,"Муратова чука (2449)" +"02234083","classic","0x6503",41.7358160,23.3705378,"Рзкл" +"02234083","classic","0x6414",41.7449141,23.3817816,"Вода (поток)" +"02234083","classic","0x2800",41.7440128,23.3833480,"Гергийца" +"02234083","classic","0x6600",41.7351937,23.4019947,"2592" +"02234083","classic","0x6600",41.7336702,23.4037971,"2609" +"02234083","classic","0x6616",41.7310739,23.4095478,"(2636)" +"02234083","classic","0x660c",41.7308378,23.4024239,"Седл Малко-Голямо Спано поле-2527" +"02234083","classic","0x6616",41.7301941,23.3993340,"Спанополски чукар (2576)" +"02234083","classic","0x6600",41.7301941,23.3989048,"2576" +"02234083","classic","0x6602",41.7262030,23.4098053,"Панорама Башлийски и Бъндеришки чукар" +"02234083","classic","0x6614",41.7244649,23.4084320,"Скала - ZZC90NYTOCF" +"02234083","classic","0x6600",41.7223191,23.4024024,"2404" +"02234083","classic","0x650b",41.7234993,23.3853221,"Зимна маркировка - QRY7DY72FMJ" +"02234083","classic","0x650b",41.7258811,23.3845925,"Зимна маркировка - 8JT9VM2UB7C" +"02234083","classic","0x650b",41.7268896,23.3845067,"Зимна маркировка - L9KQU03N0Y5" +"02234083","classic","0x650b",41.7277479,23.3846354,"Зимна маркировка - BRWNXVGHJ2A" +"02234083","classic","0x650b",41.7285633,23.3850217,"Зимна маркировка - QCHZ1FXG1S6" +"02234083","classic","0x650b",41.7290139,23.3851933,"Зимна маркировка - L9UJHM8M3L5" +"02234083","classic","0x650b",41.7302155,23.3854723,"Зимна маркировка - ZLGO6R0CWUL" +"02234083","classic","0x6511",41.7302585,23.3907509,"Вода - Извор - SZ7MXII06CB" +"02234083","classic","0x650b",41.7324471,23.3863521,"Зимна маркировка - 0PO1Y4QNTKQ" +"02234083","classic","0x650b",41.7333913,23.3868670,"Зимна маркировка - S0OOBD9DMT9" +"02234083","classic","0x650b",41.7340779,23.3873820,"Зимна маркировка - ZJJ69H95896" +"02234083","classic","0x650b",41.7346787,23.3876395,"Зимна маркировка - LRUKQQLW46P" +"02234083","classic","0x650b",41.7201734,23.3826399,"Зимна маркировка - ECDE8HOEXT6" +"02234083","classic","0x650b",41.7204738,23.3879399,"Зимна маркировка - BJH9BWY0S43" +"02234083","classic","0x650b",41.7204738,23.3852148,"Зимна маркировка - EON21NTVSS2" +"02234083","classic","0x650b",41.7205381,23.3832836,"Зимна маркировка - BJ2IFV25F7B" +"02234083","classic","0x650b",41.7206669,23.3861375,"Зимна маркировка - KN9AQOZ1709" +"02234083","classic","0x650b",41.7206669,23.3843994,"Зимна маркировка - UPFR04SCKJ6" +"02234083","classic","0x6503",41.7207956,23.3865023,"Рзкл жълта/кафява маркировка" +"02234083","classic","0x650b",41.7208600,23.3870173,"Зимна маркировка - ETSMTX323F0" +"02234083","classic","0x650b",41.7209244,23.3838630,"Зимна маркировка - 2CH7W5QYBST" +"02234083","classic","0x650b",41.7210317,23.3819532,"Зимна маркировка - 0H20QTLK2D8" +"02234083","classic","0x650b",41.7217612,23.3817601,"Зимна маркировка - UR9S90UU4PI" +"02234083","classic","0x650b",41.7224050,23.3814812,"Зимна маркировка - 0WITPVLXNZ5" +"02234083","classic","0x650b",41.7226624,23.3854723,"Зимна маркировка - KKJ6VOB1UHB" +"02234083","classic","0x6503",41.7229199,23.3789277,"Рзкл зимна" +"02234083","classic","0x640a",41.7336702,23.4035397,"Опасно слизане" +"02234083","classic","0x2800",41.7288423,23.3857727,"Големо Спано поле" +"02234083","classic","0x2800",41.7323613,23.4036040,"Караулите" +"02234083","classic","0x2800",41.7231774,23.4089899,"Малко Спано поле" +"02234083","classic","0x6600",41.7415237,23.3972311,"2501" +"02234083","classic","0x660c",41.7417812,23.3968449,"Бъндеришка порта" +"02234083","classic","0x6600",41.7420816,23.3896351,"2468" +"02234083","classic","0x6600",41.7421889,23.4056425,"2351" +"02234083","classic","0x6600",41.7431545,23.4037542,"2314" +"02234083","classic","0x6600",41.7439270,23.4045053,"2304" +"02234083","classic","0x6600",41.7442489,23.4093547,"2229" +"02234083","classic","0x6616",41.7449570,23.3969307,"Муратов връх (2669)" +"02234083","classic","0x6600",41.7452145,23.4076166,"2241" +"02234083","classic","0x6600",41.7461371,23.4070802,"2248" +"02234083","classic","0x660c",41.7486048,23.3911800,"Муратова премка" +"02234083","classic","0x660c",41.7495489,23.3981752,"Влахински превал" +"02234083","classic","0x660c",41.7514372,23.3985186,"Влахински превал" +"02234083","classic","0x6616",41.7519951,23.3843350,"Гредаро (2605)" +"02234083","classic","0x6602",41.7353010,23.4009218,"Иглата на Караулите" +"02234083","classic","0x6600",41.7354727,23.4006214,"2586" +"02234083","classic","0x6600",41.7358804,23.3998060,"2578" +"02234083","classic","0x6600",41.7360520,23.3994627,"2597" +"02234083","classic","0x6600",41.7374468,23.4049559,"2504" +"02234083","classic","0x6503",41.7538404,23.3998060,"Рзкл зимно ребро от Муратово езеро" +"02234083","classic","0x6511",41.7506433,23.3959866,"Вода - Извор - D7562E5MBLA" +"02234083","classic","0x650b",41.7363524,23.3882833,"Зимна маркировка - ML7GHHLGGVB" +"02234083","classic","0x6503",41.7493987,23.3964372,"Кръстопът - JS2LQVK6Z0Y" +"02234083","classic","0x6503",41.7450428,23.4058356,"Рзкл Муратово ез." +"02234083","classic","0x650b",41.7410517,23.3955145,"Зимна маркировка - ZVN3B0ZG6Z7" +"02234083","classic","0x650b",41.7415667,23.3965015,"Зимна маркировка - C0GJITD9Q3J" +"02234083","classic","0x6511",41.7416954,23.3946991,"Вода - Извор - YY24QUNHLSL" +"02234083","classic","0x650b",41.7419100,23.3969736,"Зимна маркировка - N2XVWARW03F" +"02234083","classic","0x650b",41.7400217,23.3932185,"Зимна маркировка - T5WTMHMWT4K" +"02234083","classic","0x650b",41.7395926,23.3919525,"Зимна маркировка - UFCHM8SJ1D7" +"02234083","classic","0x650b",41.7391205,23.3905363,"Зимна маркировка - O9IF7BPSJR6" +"02234083","classic","0x650b",41.7378759,23.3894420,"Зимна маркировка - XCWILHV9I1K" +"02234083","classic","0x6503",41.7445707,23.4080887,"Рзкл Муратово езеро" +"02234083","classic","0x6414",41.7549562,23.3888626,"Вода - Чешма - XSX8SGXW0S5" +"02234083","classic","0x6414",41.7544627,23.3887553,"Вода - Чешма - X8HNWYW73EC" +"02234083","classic","0x6415",41.7402792,23.3953857,"Бивак" +"02234083","classic","0x640a",41.7359018,23.3995056,"Опасно слизане" +"02234083","classic","0x6415",41.7453432,23.4067583,"Бивак" +"02234083","classic","0x6414",41.7509651,23.4011364,"Вода - Чешма - 4DAF49MGS8O" +"02234083","classic","0x640a",41.7456007,23.3963728,"Опасно слизане" +"02234083","classic","0x6414",41.7461586,23.4052134,"Вода (поток)" +"02234083","classic","0x6414",41.7388630,23.3864164,"Вода - Чешма - 6B4RAQRPAHT" +"02234083","classic","0x6414",41.7479610,23.4026814,"Вода - Чешма - B107V8QHR57" +"02234083","classic","0x6414",41.7468023,23.3872318,"Вода (поток)" +"02234083","classic","0x6415",41.7465878,23.4065437,"Бивак" +"02234083","classic","0x2800",41.7509651,23.3984327,"Влахински превал" +"02234083","classic","0x2800",41.7442060,23.4046984,"Муратов циркус" +"02234083","classic","0x2800",41.7368460,23.3897853,"Циркус Големо Спано поле" +"02234083","classic","0x2800",41.7449570,23.4051275,"Овинати езера" diff --git a/samples/v09/Vihren-v09-types.csv b/samples/v09/Vihren-v09-types.csv new file mode 100644 index 0000000..0c6464e --- /dev/null +++ b/samples/v09/Vihren-v09-types.csv @@ -0,0 +1,14 @@ +code,kind,count,named,default_tags,has_typ_icon,configured_rules,example1_lat,example1_lon,example1_name,example1_map,example2_lat,example2_lon,example2_name,example2_map,example3_lat,example3_lon,example3_name,example3_map,example4_lat,example4_lon,example4_name,example4_map,example5_lat,example5_lon,example5_name,example5_map +"0x2800",classic,8,8,"place=locality",no,1,41.7440128,23.3833480,"Гергийца","02234083",41.7509651,23.3984327,"Влахински превал","02234083",41.7288423,23.3857727,"Големо Спано поле","02234083",41.7231774,23.4089899,"Малко Спано поле","02234083",41.7442060,23.4046984,"Муратов циркус","02234083" +"0x640a",classic,3,3,"garmin:legend=danger",yes,1,41.7456007,23.3963728,"Опасно слизане","02234083",41.7359018,23.3995056,"Опасно слизане","02234083",41.7336702,23.4035397,"Опасно слизане","02234083",,,,,,,, +"0x6414",classic,8,8,"garmin:legend=drinking_water;amenity=drinking_water",yes,1,41.7544627,23.3887553,"Вода - Чешма - X8HNWYW73EC","02234083",41.7388630,23.3864164,"Вода - Чешма - 6B4RAQRPAHT","02234083",41.7468023,23.3872318,"Вода (поток)","02234083",41.7509651,23.4011364,"Вода - Чешма - 4DAF49MGS8O","02234083",41.7449141,23.3817816,"Вода (поток)","02234083" +"0x6415",classic,3,3,"garmin:legend=camp_site;tourism=camp_site",yes,1,41.7402792,23.3953857,"Бивак","02234083",41.7453432,23.4067583,"Бивак","02234083",41.7465878,23.4065437,"Бивак","02234083",,,,,,,, +"0x6503",classic,7,7,"garmin:legend=crossroad",yes,1,41.7538404,23.3998060,"Рзкл зимно ребро от Муратово езеро","02234083",41.7445707,23.4080887,"Рзкл Муратово езеро","02234083",41.7358160,23.3705378,"Рзкл","02234083",41.7493987,23.3964372,"Кръстопът - JS2LQVK6Z0Y","02234083",41.7229199,23.3789277,"Рзкл зимна","02234083" +"0x650b",classic,31,31,"garmin:legend=winter_marking",yes,1,41.7419100,23.3969736,"Зимна маркировка - N2XVWARW03F","02234083",41.7285633,23.3850217,"Зимна маркировка - QCHZ1FXG1S6","02234083",41.7205381,23.3832836,"Зимна маркировка - BJ2IFV25F7B","02234083",41.7395926,23.3919525,"Зимна маркировка - UFCHM8SJ1D7","02234083",41.7324471,23.3863521,"Зимна маркировка - 0PO1Y4QNTKQ","02234083" +"0x6511",classic,3,3,"garmin:legend=spring;natural=spring",yes,1,41.7506433,23.3959866,"Вода - Извор - D7562E5MBLA","02234083",41.7416954,23.3946991,"Вода - Извор - YY24QUNHLSL","02234083",41.7302585,23.3907509,"Вода - Извор - SZ7MXII06CB","02234083",,,,,,,, +"0x6600",classic,17,17,"garmin:legend=triangulation",yes,1,41.7415237,23.3972311,"2501","02234083",41.7421889,23.4056425,"2351","02234083",41.7442489,23.4093547,"2229","02234083",41.7452145,23.4076166,"2241","02234083",41.7336702,23.4037971,"2609","02234083" +"0x6602",classic,2,2,"garmin:legend=viewpoint;tourism=viewpoint",yes,1,41.7262030,23.4098053,"Панорама Башлийски и Бъндеришки чукар","02234083",41.7353010,23.4009218,"Иглата на Караулите","02234083",,,,,,,,,,,, +"0x660c",classic,7,7,"natural=saddle",yes,1,41.7514372,23.3985186,"Влахински превал","02234083",41.7417812,23.3968449,"Бъндеришка порта","02234083",41.7495489,23.3981752,"Влахински превал","02234083",41.7486048,23.3911800,"Муратова премка","02234083",41.7340136,23.3734131,"Порта","02234083" +"0x6614",classic,2,2,"garmin:legend=rock",yes,1,41.7204094,23.3722329,"Скала - 3310ACQWEHI","02234083",41.7244649,23.4084320,"Скала - ZZC90NYTOCF","02234083",,,,,,,,,,,, +"0x6615",classic,1,1,"garmin:legend=cave;natural=cave_entrance",yes,1,41.7433262,23.3739281,"Дупка","02234083",,,,,,,,,,,,,,,, +"0x6616",classic,9,9,"natural=peak",no,1,41.7404938,23.3828115,"Безимен (2554)","02234083",41.7381334,23.3741426,"Гергийца (2589)","02234083",41.7317605,23.3725762,"Безимен (2543)","02234083",41.7444634,23.3775759,"Муратова чука (2449)","02234083",41.7301941,23.3993340,"Спанополски чукар (2576)","02234083" diff --git a/samples/v09/Vihren-v09-validation.json b/samples/v09/Vihren-v09-validation.json new file mode 100644 index 0000000..5256b82 --- /dev/null +++ b/samples/v09/Vihren-v09-validation.json @@ -0,0 +1,12 @@ +{ + "source_pois": 101, + "expected_after_drops": 101, + "declared_drops": 0, + "osm_pois": 101, + "missing": 0, + "extra": 0, + "rule_tag_violations": {}, + "examples": [], + "status": "PASS", + "limitation": "Same IMG decoder is used for manifest and OSM: this validates export fidelity, not ground truth or OBF persistence." +} diff --git a/samples/v09/Vihren-v09.osm.gz b/samples/v09/Vihren-v09.osm.gz new file mode 100644 index 0000000..1a66d58 Binary files /dev/null and b/samples/v09/Vihren-v09.osm.gz differ diff --git a/scripts/build_bgmountains.py b/scripts/build_bgmountains.py index a3c3d10..5214eb5 100755 --- a/scripts/build_bgmountains.py +++ b/scripts/build_bgmountains.py @@ -84,7 +84,7 @@ def write_xml(tree,path): tree.write(path,encoding='utf-8',xml_declaration=True) ET.parse(path) -def make_configs(poi_base,render_base,catalog,folder,threshold,frequent_zoom=15,rare_zoom=17): +def make_configs(poi_base,render_base,catalog,folder,threshold,frequent_zoom=15,rare_zoom=17,route_styles=()): # Keep entire upstream definitions! Replacing with a standalone tiny XML is # precisely what used to make ordinary semantic POIs disappear. poi=ET.parse(poi_base); proot=poi.getroot() @@ -108,10 +108,15 @@ def make_configs(poi_base,render_base,catalog,folder,threshold,frequent_zoom=15, code=r['code'] ET.SubElement(target,'type',tag='bgm_poi',value='type_'+code, minzoom=str(frequent_zoom) if r['count']>=threshold else str(rare_zoom)) - rp=folder/'rendering_types.bgmountains.xml' + # Route overlays have their OWN map-index main types, not only an ignored metadata tag. + # Do not map decorative overlays to 'highway=path': that would create bogus routing. + for route in route_styles: + if route.get('show','yes').lower() not in ('yes','1','true'): continue + ET.SubElement(target,'type',tag='bgm_route',value='type_'+route['code'],minzoom=route['display_minzoom']) + rp=folder/'rendering_types.bgmountains.xml' write_xml(rendering,rp) summary={ - 'catalog_types':len(catalog), + 'catalog_types':len(catalog), 'route_overlay_types':len(route_styles), 'poi_count_in_catalog':sum(r.get('sample_count',r['count']) for r in catalog), 'types_at_or_above_threshold':sum(r['count']>=threshold for r in catalog), 'frequent_index_minzoom':frequent_zoom, 'rare_index_minzoom':rare_zoom, @@ -124,10 +129,26 @@ def make_configs(poi_base,render_base,catalog,folder,threshold,frequent_zoom=15, (folder/'type-index-summary.json').write_text(json.dumps(summary,ensure_ascii=False,indent=2)+'\n',encoding='utf-8') return pa,rp,summary -def make_style(icon_csv, folder, catalog): - """Optional addon for otherwise unclassified types using preinstalled OsmAnd icons. +def read_route_styles(path): + with Path(path).open(encoding='utf-8-sig',newline='') as f: rows=list(csv.DictReader(f)) + if not rows or not {'code','color','show','display_minzoom','width'}.issubset(rows[0]): + raise SystemExit('Invalid trail-styles.csv: '+str(path)) + seen=set() + for r in rows: + code='0x'+format(int(r['code'],0),'x') + if code in seen: raise SystemExit('Duplicate route style type: '+code) + seen.add(code);r['code']=code + if not (len(r['color'])==7 and r['color'][0]=='#' and all(c in '0123456789abcdefABCDEF' for c in r['color'][1:])): + raise SystemExit('Invalid CSS color in route type '+code) + if not (0.2<=float(r['width'])<=15 and 1<=int(r['display_minzoom'])<=21): + raise SystemExit('Invalid route width/zoom '+code) + return rows - This cannot install Garmin bitmap icons and must be enabled in OsmAnd. +def make_style(icon_csv,folder,catalog,route_styles=()): + """Selectable OsmAnd map style that maps BGM indexes to built-in icons. + + The .obf itself only embeds geometry/tags. Android requires installing and + selecting this renderer; stock app ignores bgm_poi and bgm_route styling. """ known={r['code'] for r in catalog} with Path(icon_csv).open(encoding='utf-8-sig',newline='') as f: @@ -135,26 +156,37 @@ def make_style(icon_csv, folder, catalog): visible=[] for r in rows: code='0x'+format(int(r['code'],0),'x') - if code not in known:continue # bbox builds are smaller than national config - if r['show_in_custom_style'].strip().lower() not in ('yes','true','1'):continue + if code not in known or r['show_in_custom_style'].strip().lower() not in ('yes','true','1'): + continue name=r['osmand_builtin_icon'].strip() if not name or not name.replace('_','').replace('-','').isalnum(): - raise SystemExit('Invalid OsmAnd icon in '+str(icon_csv)+' for '+code) + raise SystemExit('Invalid OsmAnd icon: '+repr(name)+' code='+code) zoom=int(r['minzoom']) - if not 1<=zoom<=21:raise SystemExit('Invalid icon zoom for '+code) + if not 1<=zoom<=21: raise SystemExit('Bad icon zoom: '+code) visible.append((code,name,zoom)) - root=ET.Element('renderingStyle',name='BGMountains custom POIs',depends='default', - defaultColor='#fff8ee',version='1') - order=ET.SubElement(root,'order') - points=ET.SubElement(root,'point') + root=ET.Element('renderingStyle',name='BGMountains outdoor v0.9', + depends='default',defaultColor='#fff8ee',version='1') + order=ET.SubElement(root,'order');points=ET.SubElement(root,'point') + lines=ET.SubElement(root,'line') for code,name,zoom in visible: - ET.SubElement(order,'case',tag='bgm_poi',value='type_'+code,minzoom=str(zoom), - point='true',objectType='1',order='128') + ET.SubElement(order,'case',tag='bgm_poi',value='type_'+code, + minzoom=str(zoom),point='true',objectType='1',order='120') ET.SubElement(points,'case',tag='bgm_poi',value='type_'+code,minzoom=str(zoom), - icon=name,iconOrder='10',iconVisibleSize='12') - path=folder/'BGMountains-custom.render.xml' + icon=name,iconOrder='11',iconVisibleSize='16',intersectionSizeFactor='0.7') + route_count=0 + for route in route_styles: + if route.get('show','yes').lower() not in ('yes','true','1'):continue + route_count+=1 + code=route['code'];zoom=route['display_minzoom'] + ET.SubElement(order,'case',tag='bgm_route',value='type_'+code, + minzoom=zoom,objectType='2',order='125') + # Color and width faithfully recover TYP base line color, but NOT its bitmap + # segment pattern. A precise pattern renderer requires a custom app build. + ET.SubElement(lines,'case',tag='bgm_route',value='type_'+code, + minzoom=zoom,color=route['color'],strokeWidth=route['width'],cap='ROUND') + path=folder/'BGMountains-outdoor.render.xml' write_xml(ET.ElementTree(root),path) - return path,len(visible) + return path,len(visible),route_count def obtain_creator(cache, offline=False): """Opt-in download from official OsmAnd nightly build (large file).""" @@ -210,7 +242,8 @@ def main(): p.add_argument('--threshold',type=int,default=100,help='Frequent-type occurrence threshold') p.add_argument('--frequent-zoom',type=int,default=15,help='Store frequent raw map icons at zoom N and above') p.add_argument('--rare-zoom',type=int,default=17,help='Store rare raw map icons at zoom N and above') - p.add_argument('--icons-config',default=str(ROOT/'config/osmand-icons.csv'),help='OsmAnd built-in icon choices for custom addon style') + p.add_argument('--icons-config',default=str(ROOT/'config/osmand-icons.csv'),help='OsmAnd icon mappings') + p.add_argument('--trail-styles',default=str(ROOT/'config/trail-styles.csv'),help='Garmin TYP coloured route overlay styles') p.add_argument('--poi-overrides',default=str(ROOT/'config/poi-overrides-all-types.csv')) p.add_argument('--poi-name-fixes',default=str(ROOT/'config/poi-name-fixes.csv')) p.add_argument('--poi-labels',default=str(ROOT/'config/poi-labels-bg.csv'),help='Bulgarian prefixes for stable generated names on nameless Garmin POIs') @@ -272,9 +305,10 @@ def main(): folder=dest/'osmand-config';folder.mkdir(exist_ok=True) poi_base=base_xml(args.poi_base,creator,'poi',dest/'upstream',args.offline) rendering_base=base_xml(args.rendering_base,creator,'rendering',dest/'upstream',args.offline) - poi_cfg,render_cfg,summary=make_configs(poi_base,rendering_base,rows,folder,args.threshold,args.frequent_zoom,args.rare_zoom) - style,nicons=make_style(args.icons_config,folder,rows) - print('Indexed POI types:',summary['catalog_types'],'frequent:',summary['types_at_or_above_threshold'],'custom style icons:',nicons,flush=True) + route_styles=read_route_styles(args.trail_styles) + poi_cfg,render_cfg,summary=make_configs(poi_base,rendering_base,rows,folder,args.threshold,args.frequent_zoom,args.rare_zoom,route_styles) + style,nicons,nlines=make_style(args.icons_config,folder,rows,route_styles) + print('Indexed POI types:',summary['catalog_types'],'frequent:',summary['types_at_or_above_threshold'],'custom icons:',nicons,'colored route types:',nlines,flush=True) if args.prepare_only: print('PREPARED (no OBF generated). Files at:',dest) return 0 @@ -299,7 +333,7 @@ def main(): 'created_at_utc':dt.datetime.now(dt.timezone.utc).isoformat(), 'obf':str(built),'obf_bytes':built.stat().st_size, 'input_osm':str(osm),'poi_types':str(poi_cfg),'rendering_types':str(render_cfg), - 'custom_style':str(style), 'custom_style_icon_rules':nicons, + 'custom_style':str(style), 'custom_style_icon_rules':nicons, 'custom_route_rules':nlines, 'summary':summary },ensure_ascii=False,indent=2)+'\n',encoding='utf-8') return 0 diff --git a/src/vector.cpp b/src/vector.cpp index 10ab79d..0cd3f5a 100644 --- a/src/vector.cpp +++ b/src/vector.cpp @@ -23,9 +23,13 @@ Options parse(int argc,char**argv){Options o;for(int i=1;i64)fail("--jobs must be 1..64");if(o.examples<1||o.examples>20)fail("--poi-examples must be 1..20");if(o.clip&&(!o.bbox.valid()||o.bbox.w< -180||o.bbox.e>180||o.bbox.s< -90||o.bbox.n>90))fail("Invalid bbox");return o;} Tags lineTags(int t,bool ext,const std::string&name){Tags v;tag(v,"garmin:type",code(t));tag(v,"garmin:feature",ext?"extended_polyline":"polyline");tag(v,"name",name); - if(ext){ // Physical trail identity not proven: preserve Garmin overlay, mark it as a hiking path geometry. - tag(v,"highway","path");tag(v,"foot","yes");tag(v,"trailblazed","yes");tag(v,"garmin:route_overlay","yes"); - const auto it=gStyles.lines.find(t);if(it!=gStyles.lines.end()&&it->second.color)tag(v,"colour",hexColor(it->second.color));return v; + if(ext){ // TYP route overlay is graphical, NOT independently verified routable geometry. + tag(v,"bgm_route","type_"+code(t));tag(v,"garmin:route_overlay","yes"); + const auto it=gStyles.lines.find(t);if(it!=gStyles.lines.end()){ + if(it->second.color)tag(v,"colour",hexColor(it->second.color)); + if(it->second.hasPattern())tag(v,"bgm:line_pattern","garmin_bitmap"); + } + return v; } if(t<=0x0c){const char*klass=nullptr; switch(t){case 0:klass="road";break;case 1:klass="motorway";break;case 2:klass="primary";break;case 3:klass="secondary";break;case 4:klass="tertiary";break;case 5:klass="unclassified";break;case 6:klass="residential";break;case 7:klass="service";break;case 8:case 9:case 11:klass="motorway_link";break;case 10:klass="track";break;case 12:klass="residential";break;} @@ -44,7 +48,7 @@ Tags areaTags(int t,const std::string&name){Tags v;tag(v,"garmin:type",code(t)); case 0x05:tag(v,"amenity","parking");break;case 0x07:tag(v,"aeroway","aerodrome");break; case 0x0b:tag(v,"amenity","hospital");break;case 0x0c:tag(v,"landuse","industrial");break; case 0x13:tag(v,"building","yes");break;case 0x17:tag(v,"leisure","park");break; - case 0x18:tag(v,"leisure","golf_course");break;case 0x09:tag(v,"leisure","marina");break; + case 0x18:tag(v,"leisure","sports_centre");break;case 0x09:tag(v,"landuse","retail");break; case 0x14:case 0x15:case 0x16:tag(v,"boundary","national_park");break; case 0x28:case 0x32:tag(v,"natural","water");tag(v,"water","sea");break; case 0x3c:case 0x3d:case 0x40:case 0x41:tag(v,"natural","water");break; @@ -69,13 +73,14 @@ Tags poiTags(int t,const std::string&name){Tags v;tag(v,"garmin:type",code(t));t else if(t==0x6407){tag(v,"tourism","picnic_site");} else if(t==0x640d){tag(v,"aerialway","station");} else if(t==0x6404||t==0x640b||t==0x641a||t==0x2c0b){tag(v,"amenity","place_of_worship");} - else if(t==0x6602||t==0x6608){tag(v,"tourism","viewpoint");} + else if(t==0x6602){tag(v,"tourism","viewpoint");} + else if(t==0x6608){tag(v,"tourism","hotel");} else if(t==0x2800){tag(v,"place","locality");} else if(t==0x6600){ // Geographic spot heights; don't misclassify elevations as villages. try{size_t parsed=0;int n=std::stoi(name,&parsed);if(parsed==name.size()&&n>=0&&n<=8848)tag(v,"ele",std::to_string(n));}catch(...){} } else if(t==0x6614){tag(v,"garmin:marker","distance_or_index");} - else if(t==0x6500){tag(v,"man_made","water_works");} + else if(t==0x6500){tag(v,"man_made","water_intake");} else if(t==0x6402&&name.find("Посетителски")!=std::string::npos){tag(v,"tourism","information");tag(v,"information","office");} else if(t==0x6411){tag(v,"man_made","tower");} // 0x6414 handled above as drinking_water: conflicting old water_well branch removed. diff --git a/tests/test_v08_config.py b/tests/test_v08_config.py index f481be3..e79b22f 100644 --- a/tests/test_v08_config.py +++ b/tests/test_v08_config.py @@ -14,13 +14,13 @@ class TestLocalDisplay(unittest.TestCase): c=x['code'];self.assertEqual(r[c]['mode'],'replace',c) self.assertTrue(names[c],c) t=r[c]['tags'] - self.assertTrue(any(a+'=' in t for a in ['amenity','shop','tourism','natural','man_made','leisure','historic','place','building','emergency','power','highway','waterway','sport','industrial']),c) + self.assertTrue(t,c) # BGMountains custom POI index handles non-standard types self.assertNotIn('tags,,',t) def test_key_places(self): r={x['code']:x for x in load('config/poi-overrides-all-types.csv')} self.assertIn('man_made=water_intake',r['0x6500']['tags']) - self.assertIn('bgm:synthetic_icon=yes',r['0x6500']['tags']) - self.assertIn('tourism=information',r['0x6500']['tags']) + self.assertNotIn('bgm:synthetic_icon=yes',r['0x6500']['tags']) + self.assertNotIn('tourism=information',r['0x6500']['tags']) self.assertIn('tourism=viewpoint',r['0x6602']['tags']) self.assertNotIn('bgm:synthetic_icon',r['0x6602']['tags']) def test_all_legacy_codes_retained_for_review(self): diff --git a/tools/audit_icon_coverage.py b/tools/audit_icon_coverage.py index 8c8cf78..311ea9c 100644 --- a/tools/audit_icon_coverage.py +++ b/tools/audit_icon_coverage.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Audit whether IMG-derived POIs have both OSM display tags and names. +"""Audit IMG-derived POIs for custom indexability and truthfully report native semantic tags. This measures converted OSM, NOT actual OsmAnd screen visibility nor .obf indexing. Output inventory always includes Garmin codes and example coordinates. @@ -23,18 +23,20 @@ def main(): if tags.get('garmin:feature')!='poi':el.clear();continue code=tags.get('garmin:type','').lower();counts[code]+=1;overview['pois']+=1 name=tags.get('name','');kind=[f'{k}={tags[k]}' for k in CLASS_KEYS if k in tags] - if not kind:bad.append((code,name,el.get('lat'),el.get('lon'),'no_osm_semantic_tag'));overview['unclassified']+=1 + if not kind:overview['needs_custom_style']+=1 + else:overview['has_native_semantics']+=1 if not name:overview['nameless']+=1 if tags.get('bgm:generated_name')=='yes':stats[code]['generated_name']+=1;overview['generated_name']+=1 if tags.get('bgm:synthetic_icon')=='yes':stats[code]['synthetic_icon']+=1;overview['synthetic_icon']+=1 if not tags.get('bgm_poi'):overview['missing_raw_type_index_tag']+=1 + else:overview['has_bgm_poi_tag']+=1 if len(samples[code])