base
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,56 @@
|
||||
"""Generate a tiny unlocked classic Garmin IMG with one RGN vector feature.
|
||||
Only for exercising the converter's control flow, NOT a real-world compatibility test.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import sys
|
||||
|
||||
def u24(n): return (n & 0xffffff).to_bytes(3,'little')
|
||||
def coord(deg):return round(deg*(1<<24)/360)
|
||||
def bitstream(values):
|
||||
bits = [1,0,1,0] # x same-sign+, y same-sign+
|
||||
for x,y in values:
|
||||
for value in (x,y):
|
||||
bits += [(value >> i)&1 for i in range(11)]
|
||||
return bytes(sum(bits[i+j]<<j for j in range(min(8,len(bits)-i))) for i in range(0,len(bits),8))
|
||||
|
||||
def gen(dest):
|
||||
blocksize=512
|
||||
blob=bytearray(512*20)
|
||||
blob[0x10:0x17]=b'DSKIMG\0'
|
||||
blob[0x41:0x48]=b'GARMIN\0'
|
||||
blob[0x61]=9;blob[0x62]=0
|
||||
blob[0x40c:0x410]=(512*8).to_bytes(4,'little')
|
||||
for j,(ext,block,length) in enumerate([('TRE',8,0xe0),('RGN',9,0xd0)]):
|
||||
at=0x600+j*512
|
||||
blob[at]=1;blob[at+1:at+9]=b'TESTMAP '
|
||||
blob[at+9:at+12]=ext.encode()
|
||||
struct.pack_into('<IHH',blob,at+12,length,0,0)
|
||||
struct.pack_into('<H',blob,at+32,block)
|
||||
struct.pack_into('<H',blob,at+34,0xffff)
|
||||
tre=bytearray(0xe0)
|
||||
struct.pack_into('<H',tre,0,0xc0)
|
||||
tre[2:12]=b'GARMIN TRE'
|
||||
for at,value in [(0x15,42.35),(0x18,25.05),(0x1b,41.85),(0x1e,24.45)]:
|
||||
tre[at:at+3]=u24(coord(value))
|
||||
struct.pack_into('<IIII',tre,0x21,0xc0,4,0xc4,14)
|
||||
# level with bits=20 and 1 subdivision
|
||||
tre[0xc0:0xc4]=bytes([0,20,1,0])
|
||||
sub=bytearray(14)
|
||||
sub[0:3]=u24(0);sub[3]=0x40
|
||||
sub[4:7]=u24(coord(24.75))
|
||||
sub[7:10]=u24(coord(42.1))
|
||||
struct.pack_into('<HH',sub,10,1200,1200)
|
||||
tre[0xc4:0xd2]=sub
|
||||
blob[8*512:8*512+len(tre)]=tre
|
||||
rgn=bytearray(0xd0)
|
||||
struct.pack_into('<H',rgn,0,0x7d)
|
||||
rgn[2:12]=b'GARMIN RGN'
|
||||
path=bitstream([(2000,2000)]*4)
|
||||
geom=bytes([0x03,0,0,0])+struct.pack('<hh',0,0)+bytes([len(path),0x99])+path
|
||||
struct.pack_into('<II',rgn,0x15,0x7d,len(geom))
|
||||
rgn[0x7d:0x7d+len(geom)]=geom
|
||||
blob[9*512:9*512+len(rgn)]=rgn
|
||||
Path(dest).write_bytes(blob)
|
||||
|
||||
if __name__=='__main__':gen(sys.argv[1])
|
||||
@@ -0,0 +1,88 @@
|
||||
"""Synthetic native IMG regression tests; does not assert real Garmin compatibility."""
|
||||
import sqlite3
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from make_fixture import gen,coord,u24
|
||||
|
||||
binary=Path(sys.argv[1]); work=Path(tempfile.mkdtemp(prefix='garmin2osmand-'))
|
||||
try:
|
||||
classic=work/'classic.img';gen(classic)
|
||||
gmp=work/'gmp.img'
|
||||
original=classic.read_bytes()
|
||||
b=bytearray(len(original))
|
||||
b[0:0x600]=original[:0x600]
|
||||
# Garmin FAT record for one GMP file, backed by four 512-byte blocks.
|
||||
b[0x600]=1;b[0x601:0x609]=b'TESTMAP '
|
||||
b[0x609:0x60c]=b'GMP'
|
||||
struct.pack_into('<IH',b,0x60c,2048,0)
|
||||
for k in range(4):struct.pack_into('<H',b,0x620+2*k,8+k)
|
||||
struct.pack_into('<H',b,0x628,0xffff)
|
||||
g=bytearray(2048)
|
||||
struct.pack_into('<H',g,0,0x35);g[2:12]=b'GARMIN GMP'
|
||||
struct.pack_into('<II',g,0x19,0x100,0x400)
|
||||
tre=bytearray(original[8*512:9*512]);rgn=bytearray(original[9*512:10*512])
|
||||
struct.pack_into('<II',tre,0x21,0x1c0,4)
|
||||
struct.pack_into('<II',tre,0x29,0x1c4,14)
|
||||
struct.pack_into('<II',rgn,0x15,0x47d,struct.unpack_from('<I',rgn,0x19)[0])
|
||||
g[0x100:0x100+0xd2]=tre[:0xd2]
|
||||
g[0x400:0x400+0x8b]=rgn[:0x8b]
|
||||
# RGN1 data has to live at its new absolute GMP offset.
|
||||
g[0x47d:0x47d+20]=rgn[0x7d:0x7d+20]
|
||||
b[8*512:12*512]=g
|
||||
gmp.write_bytes(b)
|
||||
for kind,img in [('classic',classic),('gmp',gmp)]:
|
||||
info=subprocess.run([binary,'--input',img,'--info'],capture_output=True,text=True)
|
||||
assert info.returncode==0,(kind,info.stderr)
|
||||
out=work/f'{kind}.sqlitedb'
|
||||
cmd=[binary,'--input',img,'--output',out,'--bbox','24.5,41.85,25.05,42.35','--min-zoom','12','--max-zoom','12']
|
||||
p=subprocess.run(cmd,capture_output=True,text=True)
|
||||
assert p.returncode==0,(kind,p.stdout,p.stderr)
|
||||
db=sqlite3.connect(out)
|
||||
count=db.execute('select count(*) from tiles').fetchone()[0]
|
||||
assert count>0,kind
|
||||
assert db.execute('pragma integrity_check').fetchone()[0]=='ok'
|
||||
assert db.execute('select hex(substr(image,1,8)) from tiles limit 1').fetchone()[0]=='89504E470D0A1A0A'
|
||||
assert db.execute('select tilenumbering from info').fetchone()[0]=='simple'
|
||||
db.close()
|
||||
resumed=subprocess.run(cmd+['--resume'],capture_output=True,text=True)
|
||||
assert resumed.returncode==0,(kind,resumed.stderr)
|
||||
capped=subprocess.run(cmd[:-0]+['--max-tiles','1'] if False else
|
||||
[binary,'--input',img,'--output',work/f'{kind}-cap.sqlitedb','--max-tiles','1'],capture_output=True,text=True)
|
||||
assert capped.returncode!=0 and not (work/f'{kind}-cap.sqlitedb').exists()
|
||||
# Plan mode must not create a DB, and --whole-map bypasses 100k budget automatically.
|
||||
plan=subprocess.run([binary,'--input',img,'--whole-map','--min-zoom','12','--max-zoom','12','--plan'],capture_output=True,text=True)
|
||||
assert plan.returncode==0,(kind,plan.stdout,plan.stderr)
|
||||
assert 'Tile candidates:' in plan.stdout
|
||||
whole=work/f'{kind}-whole.sqlitedb'
|
||||
full_cmd=[binary,'--input',img,'--output',whole,'--whole-map','--min-zoom','12','--max-zoom','12']
|
||||
p=subprocess.run(full_cmd,capture_output=True,text=True)
|
||||
assert p.returncode==0,(kind,p.stdout,p.stderr)
|
||||
with sqlite3.connect(whole) as db:
|
||||
assert db.execute('pragma integrity_check').fetchone()[0]=='ok'
|
||||
assert db.execute('select count(*) from tiles').fetchone()[0]>0
|
||||
assert db.execute('select count(*) from progress').fetchone()[0]>0
|
||||
assert db.execute('select count(*) from converter_settings').fetchone()[0]==1
|
||||
original=db.execute('select count(*) from tiles').fetchone()[0]
|
||||
resumed=subprocess.run(full_cmd+['--resume'],capture_output=True,text=True)
|
||||
assert resumed.returncode==0 and 'candidate tiles visited=0' in resumed.stdout,(kind,resumed.stdout,resumed.stderr)
|
||||
with sqlite3.connect(whole) as db:
|
||||
assert db.execute('select count(*) from tiles').fetchone()[0]==original
|
||||
changed=subprocess.run(full_cmd+['--resume','--aa','1'],capture_output=True,text=True)
|
||||
assert changed.returncode!=0 and 'Resume settings do not match' in changed.stderr
|
||||
# Force a small byte budget to exercise persistent scanline checkpoints.
|
||||
limited=work/f'{kind}-limited.sqlitedb'
|
||||
lim_cmd=[binary,'--input',img,'--output',limited,'--whole-map','--min-zoom','12','--max-zoom','12']
|
||||
failrun=subprocess.run(lim_cmd+['--max-bytes','1'],capture_output=True,text=True)
|
||||
assert failrun.returncode!=0 and limited.is_file()
|
||||
recovery=subprocess.run(lim_cmd+['--max-bytes','100000000','--resume'],capture_output=True,text=True)
|
||||
assert recovery.returncode==0,(kind,recovery.stdout,recovery.stderr)
|
||||
with sqlite3.connect(limited) as db:
|
||||
assert db.execute('pragma integrity_check').fetchone()[0]=='ok'
|
||||
assert db.execute('select count(*) from tiles').fetchone()[0]==original
|
||||
print(kind, 'PASSED tiles',count,'plus whole-map, checkpoint, restart and settings validation')
|
||||
finally:
|
||||
import shutil
|
||||
shutil.rmtree(work)
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Independent, streaming validation of the standalone C++ IMG->OSM pipeline."""
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
|
||||
binary = Path(sys.argv[1]).resolve()
|
||||
fixture = Path(__file__).with_name("demo.img")
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
destination = Path(tmp) / "smoke.osm"
|
||||
result = subprocess.run([str(binary), '--input', str(fixture), '--output', str(destination)], capture_output=True, text=True)
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
ids, ways, referenced = set(), 0, []
|
||||
for _event, e in ET.iterparse(destination, events=('end',)):
|
||||
if e.tag == 'node':
|
||||
assert e.attrib['id'] not in ids
|
||||
ids.add(e.attrib['id'])
|
||||
elif e.tag == 'way':
|
||||
ways += 1
|
||||
refs = [n.attrib['ref'] for n in e.findall('nd')]
|
||||
assert len(refs) >= 2
|
||||
referenced.extend(refs)
|
||||
assert any(t.attrib.get('k') == 'highway' for t in e.findall('tag'))
|
||||
if e.tag in ('node', 'way'):
|
||||
e.clear()
|
||||
assert ways == 1, ways
|
||||
assert all(n in ids for n in referenced)
|
||||
assert len(ids) >= 2
|
||||
print('Standalone Garmin IMG -> OSM XML fixture test passed')
|
||||
@@ -0,0 +1,18 @@
|
||||
"""Verify an OsmAnd simple XYZ SQLite raster map with Python stdlib only."""
|
||||
import sqlite3
|
||||
import struct
|
||||
import sys
|
||||
|
||||
path = sys.argv[1]
|
||||
db = sqlite3.connect(f'file:{path}?mode=ro', uri=True)
|
||||
assert db.execute('PRAGMA integrity_check').fetchone()[0] == 'ok'
|
||||
info = db.execute('SELECT tilenumbering,minzoom,maxzoom,tilesize FROM info').fetchone()
|
||||
assert info[0] == 'simple' and info[3] == 256, info
|
||||
zlo, zhi = int(info[1]), int(info[2])
|
||||
counts = db.execute('SELECT z, COUNT(*) FROM tiles GROUP BY z ORDER BY z').fetchall()
|
||||
assert counts and min(z for z,n in counts)>=zlo and max(z for z,n in counts)<=zhi
|
||||
for x,y,z,img in db.execute('SELECT x,y,z,image FROM tiles'):
|
||||
assert 0<=x<1<<z and 0<=y<1<<z and zlo<=z<=zhi, (x,y,z)
|
||||
assert img[:8]==b'\x89PNG\r\n\x1a\n'
|
||||
assert struct.unpack('>II',img[16:24])==(256,256)
|
||||
print('OK:',path, 'metadata=',info, 'tiles=',counts)
|
||||
Reference in New Issue
Block a user