解析o5m文件,在命令行中显示的c程序
共741行
下一步应整合、简化代码,规范接口,转化为C++程序
// o5m_demo 2012-10-14 17:10
// (c) 2012 Markus Weber, Nuernberg
//
// 这个程序从标准输入读取.o5m文件,并使用简单的文本格式将其内容写入标准输出。
// 这是如何读取.o5m文件的一个实验性例子。
//
// 编译这个文件:
// gcc o5m_demo.c -o o5m_demo
//
#define _FILE_OFFSET_BITS 64
#include //提供各种进制的转换宏
#include //申请、释放内存空间
#include
#include
#include
#include // 是POSIX标准定义的unix类系统定义符号常量的头文件
#include // 用来避免一些系统安全问题
// typedef enum {false= 0,true= 1} bool; // 一般现在无需定义
typedef uint8_t byte; // 一字节
#if !__WIN32__
#define O_BINARY 0
#endif
#define ONAME(i) \
(i==0? "node": i==1? "way": i==2? "relation": "unknown object")
static inline char *stpcpy0(char *dest, const char *src) {
// 重新定义C99的stpcpy(),因为它在MinGW中是缺少的,
// 而在Linux头文件中的声明似乎是错误的;
while(*src!=0)
*dest++= *src++;
*dest= 0;
return dest;
} // stpcpy0()
static inline int strzcmp(const char* s1,const char* s2) {
// 类似于strcmp(),这个过程比较两个字符串;
// 在这里,要比较的字符数限于第二个字符串的长度;
// 即,该过程可以用来识别长字符串s1内的短字符串s2;
// s1[]: 第一个字符串;
// s2[]: 与第一个字符串作比较的字符串;
// 返回:
// 0: 两个字符串是相同的;第一串可以比第二串长;
// -1: 第一个字符串比第二个字母小;
// 1: 第一个字符串比第二个字符串更大;
while(*s1==*s2 && *s1!=0) { s1++; s2++; }
if(*s2==0)
return 0;
return *(unsigned char*)s1 < *(unsigned char*)s2? -1: 1;
} // strzcmp()
static inline char *strmcpy(char *dest, const char *src, size_t maxlen) {
// 类似于strcpy(),这个过程 复制 一个字符串;
// 这里是关心长度的, i.e. 如果太长,目标字符串将受到限制;
// src[]: 要复制的源字符串;
// maxlen: 目标字符串的最大长度(包括终止符null)
// return:
// dest[]: 目标字符串; 这也是函数的返回值
char* d;
if(maxlen==0)
return dest;
d= dest;
while(--maxlen>0 && *src!=0)
*d++= *src++;
*d= 0;
return dest;
} // end strmcpy()
#define strMcpy(d,s) strmcpy((d),(s),sizeof(d))
//------------------------------------------------------------
// Module pbf_ protobuf转换模块
//------------------------------------------------------------
// 这个模块提供了从protobuf格式到常规数字转换的程序;
// 像往常一样,一个模块的所有标识符都有相同的前缀;
// 此时是 'pbf'; 在全局可访问的对象中将有一个下划线
// 在本模块之外不可访问的对象将有两个下划线
// 私有和公共定义的部分用水平线分隔:----
// 许多程序都有一个参数'pp';这里预期会有一个缓冲区指针
// 这个指针会增加被转换的protobuf元素消耗的字节数
//------------------------------------------------------------
static inline uint32_t pbf_uint32(byte** pp) {
// 获取无符号32位整数的值;
// pp: see module header;
byte* p;
uint32_t i;
uint32_t fac;
p= *pp;
i= *p;
if((*p & 0x80)==0) { // 只有一比特 (高位为0,代表只有一比特)
(*pp)++; // 指针后移
return i; // 返回获取的整数值
}
i&= 0x7f;
fac= 0x80;
while(*++p & 0x80) { // 更多的比特
i+= (*p & 0x7f)*fac; // 计算整数值
fac<<= 7;
}
i+= *p++ *fac;
*pp= p;
return i;
} // pbf_uint32()
static inline int32_t pbf_sint32(byte** pp) {
// 获取有符号32位整数的值;
// pp: see module header;
byte* p;
int32_t i;
int32_t fac;
int sig;
p= *pp;
i= *p;
if((*p & 0x80)==0) { // 只有一比特
(*pp)++;
if(i & 1) // 负数
return -1-(i>>1);
else
return i>>1;
}
sig= i & 1; // 符号位
i= (i & 0x7e)>>1;
fac= 0x40;
while(*++p & 0x80) { // more byte(s) will follow
i+= (*p & 0x7f)*fac;
fac<<= 7;
}
i+= *p++ *fac;
*pp= p;
if(sig) // 负数
return -1-i;
else
return i;
} // pbf_sint32()
static inline uint64_t pbf_uint64(byte** pp) {
// 获取无符号64位整数的值;
// pp: see module header;
byte* p;
uint64_t i;
uint64_t fac;
p= *pp;
i= *p;
if((*p & 0x80)==0) { // just one byte
(*pp)++;
return i;
}
i&= 0x7f;
fac= 0x80;
while(*++p & 0x80) { // more byte(s) will follow
i+= (*p & 0x7f)*fac;
fac<<= 7;
}
i+= *p++ *fac;
*pp= p;
return i;
} // pbf_uint64()
static inline int64_t pbf_sint64(byte** pp) {
// 获取有符号64位整数的值;
// pp: see module header;
byte* p;
int64_t i;
int64_t fac;
int sig;
p= *pp;
i= *p;
if((*p & 0x80)==0) { // just one byte
(*pp)++;
if(i & 1) // negative
return -1-(i>>1);
else
return i>>1;
}
sig= i & 1;
i= (i & 0x7e)>>1;
fac= 0x40;
while(*++p & 0x80) { // more byte(s) will follow
i+= (*p & 0x7f)*fac;
fac<<= 7;
}
i+= *p++ *fac;
*pp= p;
if(sig) // negative
return -1-i;
else
return i;
} // pbf_sint64()
//------------------------------------------------------------
// end Module pbf_ protobuf转换模块
//------------------------------------------------------------
//------------------------------------------------------------
// Module read_ OSM文件读取模块
//------------------------------------------------------------
// 本模块提供缓冲读取标准输入的程序;
// 像往常一样,一个模块的所有标识符都有相同的前缀;
// 此时是 'read'; 在全局可访问的对象中将有一个下划线
// 在本模块之外不可访问的对象将有两个下划线
// 私有和公共定义的部分用水平线分隔:----
#define read_PREFETCH ((32+3)*1024*1024) // (prefetch)预取 35MB
// 每次调用 read_input() 后缓冲区中可用的字节数;
#define read__bufM (read_PREFETCH*5) // 缓冲区的长度 35MBx5
// 每次调用 read_input() 后缓冲区中可用的字节数;
typedef struct { // 成员可能无法从外部访问
FILE* fi; // 文件流
off_t jumppos; // position to jump to; -1: invalid
int64_t counter;
// 字节计数器获取输入文件中的读取位置;
char filename[100];
bool eof; // 输文件结尾的标志
byte* bufp; // buf[]中的指针
byte* bufe; // 指向在buf[]中有效输入结束的指针
uint64_t bufferstart;
// 表示读取缓冲区开始的哑变量
// 与这个读信息结构的实例连接;
} read_info_t;
static bool read__jumplock= false; // do not change .jumppos anymore;
//------------------------------------------------------------
static read_info_t* read_infop= NULL;
// 目前使用的阅读信息结构, 即文件句柄
#define read__buf ((byte*)&read_infop->bufferstart)
// 文件输入缓冲区的起始地址
static byte* read_bufp= NULL;
// 可能在read_input()再次调用之前增加到 read_PREFETCH 字节
static byte* read_bufe= NULL; // 可能不会从外部改变
//------------------------------------------------------------
static int read_open(const char* filename) {
// 打开一个输入文件;
// filename[]: 输入文件的路径和名称;
// ==NULL: 标准输入;
// 返回: 0: 正常; !=0: 错误;
// read_infop: 文件句柄;
// 注意你应该在程序结束之前用read_close()关闭每个打开的文件
// 保存当前处理的输入文件的状态 (if any)
if(read_infop!=NULL) {
read_infop->bufp= read_bufp;
read_infop->bufp= read_bufe;
}
// 分配 文件句柄 及 输入缓冲区 的内存空间
read_infop= (read_info_t*)malloc(sizeof(read_info_t)+read__bufM);
if(read_infop==NULL) {
fprintf(stderr,"osmconvert Error: could not get "
"%i bytes of memory.\n",read__bufM);
return 1;
}
// 初始化 read info 结构
read_infop->fi= NULL; // (default) file not opened
strMcpy(read_infop->filename,filename);
read_infop->eof= false; // we are not at the end of input file
read_infop->bufp= read_infop->bufe= read__buf; // pointer in buf[]
// pointer to the end of valid input in buf[]
read_infop->counter= 0;
read_infop->jumppos= 0;
// 将文件的起始存储为默认跳转目的地
// 设置与此文件相关联的模块全局变量
read_bufp= read_infop->bufp;
read_bufe= read_infop->bufe;
// 打开文件
fprintf(stderr,"Read-opening: %s\n",read_infop->filename);
if(filename!=NULL) { // 一个真实的文件应该被打开
read_infop->fi= fopen(filename,"rb");
if(read_infop->fi==NULL) {
fprintf(stderr,
"osmconvert Error: could not open input file: %.80s\n",
read_infop->filename);
free(read_infop); read_infop= NULL;
read_bufp= read_bufe= NULL;
return 1;
}
} // end 一个真实的文件应该被打开
return 0;
} // end read_open()
static void read_close() {
// 关闭一个打开的文件;
// read_infop: 将要关文件句柄;
if(read_infop==NULL) // 文件句柄不可用;
return;
fprintf(stderr,"Read-closing: %s\n",read_infop->filename);
fclose(read_infop->fi);
free(read_infop); read_infop= NULL;
read_bufp= read_bufe= NULL;
} // end read_close()
static inline bool read_input() {
// 从标准输入文件读取数据,使用内部缓冲;
// 使数据在 read_bufp 可用;
// 在调用此过程之前,必须调用 read_open() ;
// read_bufp: 可用的下一个字节开始;
// 可以由调用者增加到 read_bufe ;
// read_bufe: 缓冲区中字节的结束;
// 不能被调用者更改;
// 在调用此过程之后, 在地址 read_bufp 中至少有 read_PREFETCH 字节可用
// - 有一个例外: 如果从标准输入中没有足够的可读取的字节,
// 缓冲区中文件结束后的剩余部分直到read_bufp + read_PREFETCH所有字节都将被设置为0x00
int l,r;
if(read_bufp+read_PREFETCH>=read_bufe) { // 读缓冲区过低
if(!read_infop->eof) { // still bytes to read
if(read_bufe>read_bufp) { // 缓冲区中有剩余的字节
memmove(read__buf,read_bufp,read_bufe-read_bufp);
// move remaining bytes to start of buffer
read_bufe= read__buf+(read_bufe-read_bufp);
// protect the remaining bytes at buffer start
}
else // 缓冲区中没有剩余字节
read_bufe= read__buf; // no bytes remaining to protect
// add read bytes to debug counter
read_bufp= read__buf;
do { // while buffer has not been filled
l= (read__buf+read__bufM)-read_bufe-4;
// number of bytes to read
r= read(fileno(read_infop->fi),read_bufe,l); // 读l长度的数据保存到缓冲区,返回实际读入的长度
if(r<=0) { // no more bytes in the file
read_infop->eof= true;
// memorize that there we are at end of file
l= (read__buf+read__bufM)-read_bufe;
// remaining space in buffer
if(l>read_PREFETCH) l= read_PREFETCH;
memset(read_bufe,0,l); // 2011-12-24
// set remaining space up to prefetch bytes in buffer to 0
break;
}
read_infop->counter+= r;
read_bufe+= r; // 设置新的数据结尾标志位置
read_bufe[0]= 0; read_bufe[1]= 0; // set 4 null-terminators
read_bufe[2]= 0; read_bufe[3]= 0;
} while(reof && read_bufp>=read_bufe;
} // end read__input()
//------------------------------------------------------------
// end Module read_ OSM文件读取模块
//------------------------------------------------------------
//------------------------------------------------------------
// Module str_ 字符串读取模块
//------------------------------------------------------------
// 此模块提供了从数据流对象中存储的字符串转换为c格式字符串的过程;
// 像往常一样,一个模块的所有标识符都有相同的前缀,
// 此时为 'str'; 在全局可访问的对象中将有一个下划线
// 在本模块之外不可访问的对象将有两个下划线
// 私有和公共定义的部分用水平线分隔:----
#define str__tabM (15000+4000)
// +4000是因为可能发生一个对象有很多 key/val 对或者 refroles 的情况
// 它们没有被存储
#define str__tabstrM 250 // 必须是< str__tab[]的行大小
static char str__tab[str__tabM][256];
// 字符串表 see o5m documentation ;
// 行长度必须至少是 str__tabstrM+2 ;
// 每一行包含一个双字符串;
// 两个字符串中的每一个都以零字节结尾,
// 逻辑长度总共不超过 str__tabstrM 字节;
// 这个数组的第一个 str__tabM 行被用作字符串的输入缓冲区;
static int str__tabi= 0;
// 在字符串表中最后输入元素的索引;
static int str__tabn= 0; // 字符串表中有效字符串的数量;
//------------------------------------------------------------
static inline void str_reset() {
// 清空字符串表;
// 必须在本模块的任何其他进程之前调用
// 可能在任何字符串进程需要重启时调用;
str__tabi= str__tabn= 0;
} // str_reset()
static void str_read(byte** pp,char** s1p,char** s2p) {
// 从标准输入缓冲中读取一个o5m格式的字符串(对),如. key/val;
// 如果读取到一个字符串引用,则使用内部字符串表解释它;
// 如果字符串总长度超过250个字符,则不使用引用(包括终止符为252);
// pp: 缓冲指针的地址;
// 这个指针将会增加被转换的protobuf元素消耗的字节数;
// s2p: ==NULL: 读取的不是字符串对,而是单个字符串;
// 返回:
// *s1p,*s2p: 已读取的字符串的指针;
char* p;
int len1,len2;
int ref;
bool donotstore; // 字符串“不存储标志” 2012-10-01
p= (char*)*pp;
if(*p==0) { // 直接给出的字符串(对)
p++;
donotstore= false;
#if 0 // 不使用,因为字符串不再是透明的
if(*++p==(char)0xff) { // 字符串有“不存储标志” 0xff是重置字节
donotstore= true;
p++;
} // 字符串有“不存储标志”
#endif
*s1p= p;
len1= strlen(p); // 获取当前字符串长度
p+= len1+1; // 指针后移到下一个字符串的起始地址
if(s2p==NULL) { // 单个字符串
if(!donotstore && len1<=str__tabstrM) {
// 单个字符串对于字符串表来说足够短
stpcpy0(str__tab[str__tabi], *s1p)[1]= 0;
// 添加第二个终结符,以防万一有人会尝试
// 将这个单一字符串作为字符串对来读取;
if(++str__tabi>=str__tabM) str__tabi= 0;
if(str__tabn=str__tabM) str__tabi= 0;
if(str__tabnstr__tabn) { // 无效的字符串引用(因为ref > 字符串表中有效字符串的数量)
fprintf(stderr,"Invalid .o5m string reference: %i->%i\n",
str__tabn,ref);
*s1p= "(invalid)";
if(s2p!=NULL) // 调用者需要一个字符串对
*s2p= "(invalid)";
} // 无效的字符串引用
else { // 有效的字符串引用
ref= str__tabi-ref; // 向前找引用的索引
if(ref<0) ref+= str__tabM;
*s1p= str__tab[ref];
if(s2p!=NULL) // 调用者需要一个字符串对
*s2p= strchr(str__tab[ref],0)+1;
// (strchr: 从字符串 str__tab[ref] 中寻找字符0第一次出现的位置),然后+1,即定位到了下一个字符串
} // 有效的字符串引用
} // 通过引用给出的字符串(对)
} // str_read()
//------------------------------------------------------------
// end Module str_ 字符串读取模块
//------------------------------------------------------------
static inline char* sint32tosfix7(int32_t v,char* sp) {
// 将sint32作为7位小数定点值并将其转换为字符串;
// v: 整数值(定点);
// sp[13]: 目标字符串;
// 返回: sp;
char* s1,*s2,c;
int i;
s1= sp;
if(v<0) // 负数
{ *s1++= '-'; v= -v; }
s2= s1;
i= 7;
while((v%10)==0 && i>0) // trailing zeroes
{ v/= 10; i--; }
while(--i>=0)
{ *s2++= (v%10)+'0'; v/= 10; }
*s2++= '.';
do
{ *s2++= (v%10)+'0'; v/= 10; }
while(v>0);
*s2--= 0;
while(s2>s1)
{ c= *s1; *s1= *s2; *s2= c; s1++; s2--; }
return sp;
} // sint32tosfix7()
static inline void uint64totimestamp(uint64_t v,char* sp) {
// 将sint64转换为OSM格式的时间戳,
// 举例说: "2010-09-30T19:23:30Z";
// v: 时间戳值;
// sp[21]: 目标字符串;
time_t vtime;
struct tm tm;
int i;
vtime= v;
#if __WIN32__
memcpy(&tm,gmtime(&vtime),sizeof(tm));
#else
gmtime_r(&vtime,&tm);
#endif
i= tm.tm_year+1900;
sp+= 3; *sp--= i%10+'0';
i/=10; *sp--= i%10+'0';
i/=10; *sp--= i%10+'0';
i/=10; *sp= i%10+'0';
sp+= 4; *sp++= '-';
i= tm.tm_mon+1;
*sp++= i/10+'0'; *sp++= i%10+'0'; *sp++= '-';
i= tm.tm_mday;
*sp++= i/10+'0'; *sp++= i%10+'0'; *sp++= 'T';
i= tm.tm_hour;
*sp++= i/10+'0'; *sp++= i%10+'0'; *sp++= ':';
i= tm.tm_min;
*sp++= i/10+'0'; *sp++= i%10+'0'; *sp++= ':';
i= tm.tm_sec%60;
*sp++= i/10+'0'; *sp++= i%10+'0'; *sp++= 'Z'; *sp= 0;
} // uint64totimestamp()
//------------------------------------------------------------
static int process_o5m(const char* filename) {
// 开始处理o5m对象;
// 返回: ==0: ok; !=0: 错误;
bool write_testmode; // 如果为真: 报告未知的 o5m ids
int otype; // 当前处理对象的类型;
// 0: node; 1: way; 2: relation;
int64_t o5id; // for o5m delta coding
int32_t o5lon,o5lat; // for o5m delta coding
int64_t o5histime; // for o5m delta coding
int64_t o5hiscset; // for o5m delta coding
int64_t o5ref[4]; // for o5m delta coding for nodes, ways, relations,
// and dummy object (just to allow index division by 4)
bool o5endoffile; // 文件的逻辑结束(数据集id 0xfe)
byte* bufp; // 在读取缓冲区的指针
byte* bufe; // 在读取缓冲区的指针, 对象的结束
byte b; // 已读取的最新字节
int l;
// 程序初始化
write_testmode= false; // do not criticize unknown o5m ids
o5endoffile= false;
// 读 .o5m 文件头
read_open(filename);
read_input();
bufp= read_bufp;
if(read_bufp>=read_bufe) { // 文件为空
fprintf(stderr,"Please supply .o5m file at stdin.\n");
return 2;
}
if(bufp[0]!=0xff || bufp[1]!=0xe0 || (
strzcmp((char*)bufp+2,"\x04""o5m2")!=0 &&
strzcmp((char*)bufp+2,"\x04""o5c2")!=0 )) {
// not an .o5m format
fprintf(stderr,"Unknown input file format.\n");
return 3;
}
bufp+= 7; // 跳过.o5m文件头
// 处理文件
for(;;) { //读取输入文件中的所有对象
// 获取下一个对象
read_input();
if(read_bufp>=read_bufe) // 物理文件结束
break;
if(o5endoffile) { // 在文件的逻辑结束之后
fprintf(stderr,"Warning: unexpected contents "
"after logical end of file.\n");
break;
}
bufp= read_bufp;
b= *bufp;
// 关心文件头对象和特殊对象
if(b<0x10 || b>0x12) { // 不是常规的OSM对象
if(b>=0xf0) { // 单字节数据集
if(b==0xff) { // 文件开始, resp. o5m 重置
// 为写入o5m文件重置计数器;
o5id= 0;
o5lat= o5lon= 0;
o5hiscset= 0;
o5histime= 0;
o5ref[0]= o5ref[1]= o5ref[2]= 0;
str_reset();
}
else if(b==0xfe) // 文件结束
o5endoffile= true;
else if(write_testmode)
fprintf(stderr,"Unknown .o5m short dataset id: 0x%02x\n",b);
read_bufp++;
} // 单字节数据集
else { // 多字节数据集
bufp++;
l= pbf_uint32(&bufp);
bufe= bufp+l;
if(b==0xdc) {
// 文件的时间戳
if(bufp=bufe)
// 只有id和作者,即这是一个删除请求
printf(" action: delete\n");
else { // not a delete request
// 读取坐标(仅用于节点)
if(otype==0) { // node
char s[15];
printf(" lon: %s\n",
sint32tosfix7(o5lon+= pbf_sint32(&bufp),s));
printf(" lat: %s\n",
sint32tosfix7(o5lat+= pbf_sint32(&bufp),s));
} // node
// 读取noderefs(仅用于路线)
if(otype==1) { // way
byte* bp;
l= pbf_uint32(&bufp); // noderefs 的长度
bp= bufp+l; // 跳到 noderefs 的结尾
if(bp>bufe) bp= bufe; // (format error)
while(bufpbufe) bp= bufe; // (format error)
while(bufp