Subversion Repositories mkgmap

Rev

Rev 4098 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2012.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 or
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */

package uk.me.parabola.mkgmap.reader.osm.o5m;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GeneralRelation;
import uk.me.parabola.mkgmap.reader.osm.Node;
import uk.me.parabola.mkgmap.reader.osm.OsmHandler;
import uk.me.parabola.mkgmap.reader.osm.Relation;
import uk.me.parabola.mkgmap.reader.osm.Way;

/**
 * Parser for the o5m format described here: http://wiki.openstreetmap.org/wiki/O5m
 * The routines to are based on the osmconvert.c source from Markus Weber who allows
 * to copy them for any o5m IO, thanks a lot for that.
 * @author GerdP  
 *
 */

public class O5mBinHandler extends OsmHandler{
        // O5M data set constants
        private static final int NODE_DATASET = 0x10;
        private static final int WAY_DATASET = 0x11;
        private static final int REL_DATASET = 0x12;
        private static final int BBOX_DATASET = 0xdb;
        private static final int TIMESTAMP_DATASET = 0xdc;
        private static final int HEADER_DATASET = 0xe0;
        private static final int EOD_FLAG = 0xfe;
        private static final int RESET_FLAG = 0xff;
       
        private static final int EOF_FLAG = -1;
       
        // o5m constants
        private static final int STRING_TABLE_SIZE = 15000;
        private static final int MAX_STRING_PAIR_SIZE = 250 + 2;
        private static final String[] REL_REF_TYPES = {"node", "way", "relation", "?"};
        private static final double FACTOR = 1d/1000000000; // used with 100*<Val>*FACTOR
       
        private BufferedInputStream fis;
        private InputStream is;
        private ByteArrayInputStream bis;
       
        // buffer for byte -> String conversions
        private byte[] cnvBuffer;
       
        private byte[] ioBuf;
        private int ioPos;
        // the o5m string table
        private String[][] stringTable;
        private String[] stringPair;
        private int currStringTablePos;
        // a counter that must be maintained by all routines that read data from the stream
        private int bytesToRead;
        // total number of bytes read from stream
        long countBytes;

        // for delta calculations
        private long lastNodeId;
        private long lastWayId;
        private long lastRelId;
        private long lastRef[];
        private long lastTs;
        private long lastChangeSet;
        private int lastLon,lastLat;
       
        /**
         * A parser for the o5m format
         * @param processor A mapProcessor instance
         * @param skipArray An Array of longs that is used to hold information of file position of the first occurrence of
         * each known 05m data type (esp. nodes, ways, and relations).
         */

        public O5mBinHandler() {
        }

        @Override
        public boolean isFileSupported(String name) {
                return name.endsWith(".o5m") || name.endsWith(".o5m.gz");
        }

        /**
         * parse the input stream
         */

        @Override
        public void parse(InputStream stream){
                this.fis = new BufferedInputStream(stream);
                is = fis;
                this.cnvBuffer = new byte[4000]; // OSM data should not contain string pairs with length > 512
                this.ioBuf = new byte[8192];
                this.ioPos = 0;
                this.stringPair = new String[2];
                this.lastRef = new long[3];
                reset();
                try {
                        int start = is.read();
                        ++countBytes;
                        if (start != RESET_FLAG)
                                throw new IOException("wrong header byte " + start);
                        readFile();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        }
       
        private void readFile() throws IOException{
                boolean done = false;
                while(!done){
                        is = fis;
                        long size = 0;
                        int fileType = is.read();
                        ++countBytes;
                        if (fileType >= 0 && fileType < 0xf0){
                                bytesToRead = 0;
                                size = readUnsignedNum64FromStream();
                                countBytes += size - bytesToRead; // bytesToRead is negative
                                bytesToRead = (int)size;
                               
                                switch(fileType){
                                case NODE_DATASET:
                                case WAY_DATASET:
                                case REL_DATASET:
                                case BBOX_DATASET:
                                case TIMESTAMP_DATASET:
                                case HEADER_DATASET:
                                        if (bytesToRead > ioBuf.length){
                                                ioBuf = new byte[(int)bytesToRead+100];
                                        }
                                        int bytesRead  = 0;
                                        int neededBytes = bytesToRead;
                                        while (neededBytes > 0){
                                                bytesRead += is.read(ioBuf, bytesRead, neededBytes);
                                                neededBytes -= bytesRead;
                                        }
                                        ioPos = 0;
                                        bis = new ByteArrayInputStream(ioBuf,0,bytesToRead);
                                        is = bis;
                                        break;                                 
                                default:       
                                }
                        }
                        if (fileType == EOF_FLAG) done = true;
                        else if (fileType == NODE_DATASET) readNode();
                        else if (fileType == WAY_DATASET) readWay();
                        else if (fileType == REL_DATASET) readRel();
                        else if (fileType == BBOX_DATASET) readBBox();
                        else if (fileType == TIMESTAMP_DATASET) readFileTimestamp();
                        else if (fileType == HEADER_DATASET) readHeader();
                        else if (fileType == EOD_FLAG) done = true;
                        else if (fileType == RESET_FLAG) reset();
                        else {
                                if (fileType < 0xf0 )skip(size); // skip unknown data set
                        }
                }
        }
       
        /**
         * read (and ignore) the file timestamp data set
         */

        private void readFileTimestamp(){
                /*long fileTimeStamp = */readSignedNum64();
        }
       
        /**
         * Skip the given number of bytes
         * @param bytes
         * @throws IOException
         */

        private void skip(long bytes)throws IOException{
                long toSkip = bytes;
                while (toSkip > 0)
                        toSkip -= is.skip(toSkip);
        }
       
        /**
         * read the bounding box data set
         * @throws IOException
         */

        private void readBBox() {
                double leftf = (double) (100L*readSignedNum32()) * FACTOR;
                double bottomf = (double) (100L*readSignedNum32()) * FACTOR;
                double rightf = (double) (100L*readSignedNum32()) * FACTOR;
                double topf = (double) (100L*readSignedNum32()) * FACTOR;
                assert bytesToRead == 0;
                setBBox(bottomf, leftf, topf, rightf);
        }

        /**
         * read a node data set
         * @throws IOException
         */

        private void readNode() throws IOException{
                lastNodeId += readSignedNum64();
                if (bytesToRead == 0)
                        return; // only nodeId: this is a delete action, we ignore it
                readVersionTsAuthor();
                if (bytesToRead == 0)
                        return; // only nodeId+version: this is a delete action, we ignore it
                int lon = readSignedNum32() + lastLon; lastLon = lon;
                int lat = readSignedNum32() + lastLat; lastLat = lat;
                       
                double flon = (double)(100L*lon) * FACTOR;
                double flat = (double)(100L*lat) * FACTOR;
                assert flat >= -90.0 && flat <= 90.0;  
                assert flon >= -180.0 && flon <= 180.0;  

                Coord co = new Coord(flat, flon);
                saver.addPoint(lastNodeId, co);
                if (bytesToRead > 0){
                        Node node = new Node(lastNodeId,co);
                        readTags(node);
                        if (node.getTagCount() > 0){
                                // If there are tags, then we save a proper node for it.
                                saver.addNode(node);
                                hooks.onAddNode(node);
                        }
                }
        }
       
        /**
         * read a way data set
         * @throws IOException
         */

        private void readWay() throws IOException{
                lastWayId += readSignedNum64();
                if (bytesToRead == 0)
                        return; // only wayId: this is a delete action, we ignore it

                readVersionTsAuthor();
                if (bytesToRead == 0)
                        return; // only wayId + version: this is a delete action, we ignore it
                Way way = startWay(lastWayId);
                long refSize = readUnsignedNum32();
                long stop = bytesToRead - refSize;
               
                while(bytesToRead > stop){
                        lastRef[0] += readSignedNum64();
                        addCoordToWay(way, lastRef[0]);
                }
               
                readTags(way);
                endWay(way);
        }
       
        /**
         * read a relation data set
         * @throws IOException
         */

        private void readRel() throws IOException{
                lastRelId += readSignedNum64();
                if (bytesToRead == 0)
                        return; // only relId: this is a delete action, we ignore it
                readVersionTsAuthor();
                if (bytesToRead == 0)
                        return; // only relId + version: this is a delete action, we ignore it

                GeneralRelation rel = new GeneralRelation(lastRelId);
                long refSize = readUnsignedNum32();
                long stop = bytesToRead - refSize;
                while(bytesToRead > stop){
                        Element el = null;
                        long deltaRef = readSignedNum64();
                        int refType = readRelRef();
                        String role = stringPair[1];
                        lastRef[refType] += deltaRef;
                        long memId = lastRef[refType];
                        if (refType == 0){
                                el = saver.getNode(memId);
                                if(el == null) {
                                        // we didn't make a node for this point earlier,
                                        // do it now (if it exists)
                                        Coord co = saver.getCoord(memId);
                                        if(co != null) {
                                                el = new Node(memId, co);
                                                saver.addNode((Node)el);
                                        }
                                }
                        }
                        else if (refType == 1){
                                el = saver.getWay(memId);
                        }
                        else if (refType == 2){
                                el = saver.getRelation(memId);
                                if (el == null) {
                                        saver.deferRelation(memId, rel, role);
                                }
                        } else {
                                assert false;
                        }
                        if (el != null) // ignore non existing ways caused by splitting files
                                rel.addElement(role, el);
                }
                boolean tagsIncomplete = readTags(rel);
                rel.setTagsIncomplete(tagsIncomplete);
                saver.addRelation(rel);
        }
       
        private boolean readTags(Element elem) throws IOException{
                boolean tagsIncomplete = false;
                while (bytesToRead > 0){
                        readStringPair();
                        String key = stringPair[0];
                        String val = stringPair[1];
                        // the type tag is required for relations - all other tags are filtered
                        if (elem instanceof Relation && "type".equals(key))
                                // intern the string
                                key = "type";
                        else
                                key = keepTag(key, val);
                        if (key != null)
                                elem.addTagFromRawOSM(key, val);
                        else
                                tagsIncomplete = true;
                }
                assert bytesToRead == 0;
                return tagsIncomplete;
        }
        /**
         * Store a new string pair (length check must be performed by caller)
         */

        private void storeStringPair(){
                stringTable[0][currStringTablePos] = stringPair[0];
                stringTable[1][currStringTablePos] = stringPair[1];
                ++currStringTablePos;
                if (currStringTablePos >= STRING_TABLE_SIZE)
                        currStringTablePos = 0;
        }

        /**
         * set stringPair to the values referenced by given string reference
         * No checking is performed.
         * @param ref valid values are 1 .. STRING_TABLE_SIZE
         */

        private void setStringRefPair(int ref){
                int pos = currStringTablePos - ref;
                if (pos < 0)
                        pos += STRING_TABLE_SIZE;
                stringPair[0] = stringTable[0][pos];
                stringPair[1] = stringTable[1][pos];
        }

        /**
         * Read version, time stamp and change set and author.  
         * We are not interested in the values, but we have to maintain the string table.
         * @throws IOException
         */

       
        private void readVersionTsAuthor() throws IOException {
                int version = readUnsignedNum32();
                if (version != 0){
                        // version info
                        long ts = readSignedNum64() + lastTs; lastTs = ts;
                        if (ts != 0){
                                long changeSet = readSignedNum32() + lastChangeSet; lastChangeSet = changeSet;
                                readAuthor();
                        }
                }
        }
        /**
         * Read author .
         * @throws IOException
         */

        private void readAuthor() throws IOException{
                int stringRef = readUnsignedNum32();
                if (stringRef == 0){
                        long toReadStart = bytesToRead;
                        long uidNum = readUnsignedNum64();
                        if (uidNum == 0)
                                stringPair[0] = "";
                        else{
                                stringPair[0] = Long.toString(uidNum);
                                ioPos++; // skip terminating zero from uid
                                --bytesToRead;
                        }
                        stringPair[1] = readString();
                        long bytes = toReadStart - bytesToRead;
                        if (bytes <= MAX_STRING_PAIR_SIZE)
                                storeStringPair();
                }
                else
                        setStringRefPair(stringRef);
               
                //System.out.println(pair[0]+ "/" + pair[1]);
        }
       
        /**
         * read object type ("0".."2") concatenated with role (single string)
         * @return 0..3 for type (3 means unknown)
         */

        private int readRelRef () throws IOException{
                int refType = -1;
                long toReadStart = bytesToRead;
                int stringRef = readUnsignedNum32();
                if (stringRef == 0){
                        refType = ioBuf[ioPos++] - 0x30;
                        --bytesToRead;

                        if (refType < 0 || refType > 2)
                                refType = 3;
                        stringPair[0] = REL_REF_TYPES[refType];
                               
                        stringPair[1] = readString();
                        long bytes = toReadStart - bytesToRead;
                        if (bytes <= MAX_STRING_PAIR_SIZE)
                                storeStringPair();
                }
                else {
                        setStringRefPair(stringRef);
                        char c = stringPair[0].charAt(0);
                        switch (c){
                        case 'n': refType = 0; break;
                        case 'w': refType = 1; break;
                        case 'r': refType = 2; break;
                        default: refType = 3;
                        }
                }
                return refType;
        }
       
        /**
         * read a string pair (see o5m definition)
         * @throws IOException
         */

        private void readStringPair() throws IOException{
                int stringRef = readUnsignedNum32();
                if (stringRef == 0){
                        long toReadStart = bytesToRead;
                        int cnt = 0;
                        while (cnt < 2){
                                stringPair[cnt++] = readString();
                        }
                        long bytes = toReadStart - bytesToRead;
                        if (bytes <= MAX_STRING_PAIR_SIZE)
                                storeStringPair();
                }
                else
                        setStringRefPair(stringRef);
        }
       
        /**
         * Read a zero-terminated string (see o5m definition).
         * @throws IOException
         */

        String readString() throws IOException {
                int length = 0;
                while (true) {
                        final int b = ioBuf[ioPos++];
                        --bytesToRead;
                        if (b == 0)
                                return new String(cnvBuffer, 0, length, "UTF-8");
                        cnvBuffer[length++] = (byte) b;
                }
        }

       
        /** reset the delta values and string table */
        private void reset(){
                lastNodeId = 0; lastWayId = 0; lastRelId = 0;
                lastRef[0] = 0; lastRef[1] = 0;lastRef[2] = 0;
                lastTs = 0; lastChangeSet = 0;
                lastLon = 0; lastLat = 0;
                stringTable = new String[2][STRING_TABLE_SIZE];
                currStringTablePos = 0;
        }

        /**
         * read and verify o5m header (known values are o5m2 and o5c2)
         * @throws IOException
         */

        private void readHeader() throws IOException {
                if (ioBuf[0] != 'o' || ioBuf[1] != '5' || (ioBuf[2]!='c'&&ioBuf[2]!='m') ||ioBuf[3] != '2' ){
                        throw new IOException("unsupported header");
                }
        }
       
        /**
         * read a varying length signed number (see o5m definition)
         * @return the number
         * @throws IOException
         */

        private int readSignedNum32() {
                int result;
                int b = ioBuf[ioPos++];
                --bytesToRead;
                result = b;
                if ((b & 0x80) == 0){  // just one byte
                        if ((b & 0x01) == 1)
                                return -1-(result>>1);
                        else
                                return result>>1;
                }
                int sign = b & 0x01;
                result = (result & 0x7e)>>1;
                int fac = 0x40;
                while (((b = ioBuf[ioPos++]) & 0x80) != 0){ // more bytes will follow
                        --bytesToRead;
                        result += fac * (b & 0x7f) ;
                        fac  <<= 7;
                }
                --bytesToRead;
                result += fac * b;
                if (sign == 1) // negative
                        return -1-result;
                else
                        return result;

        }

        /**
         * read a varying length signed number (see o5m definition)
         * @return the number
         * @throws IOException
         */

        private long readSignedNum64() {
                long result;
                int b = ioBuf[ioPos++];
                --bytesToRead;
                result = b;
                if ((b & 0x80) == 0){  // just one byte
                        if ((b & 0x01) == 1)
                                return -1-(result>>1);
                        else
                                return result>>1;
                }
                int sign = b & 0x01;
                result = (result & 0x7e)>>1;
                long fac = 0x40;
                while (((b = ioBuf[ioPos++]) & 0x80) != 0){ // more bytes will follow
                        --bytesToRead;
                        result += fac * (b & 0x7f) ;
                        fac  <<= 7;
                }
                --bytesToRead;
                result += fac * b;
                if (sign == 1) // negative
                        return -1-result;
                else
                        return result;

        }

        /**
         * read a varying length unsigned number (see o5m definition)
         * @return a long
         * @throws IOException
         */

        private long readUnsignedNum64FromStream()throws IOException {
                int b = is.read();
                --bytesToRead;
                long result = b;
                if ((b & 0x80) == 0){  // just one byte
                        return result;
                }
                result &= 0x7f;
                long fac = 0x80;
                while (((b = is.read()) & 0x80) != 0){ // more bytes will follow
                        --bytesToRead;
                        result += fac * (b & 0x7f) ;
                        fac  <<= 7;
                }
                --bytesToRead;
                result += fac * b;
                return result;
        }
       
       
        /**
         * read a varying length unsigned number (see o5m definition)
         * @return a long
         * @throws IOException
         */

        private long readUnsignedNum64(){
                int b = ioBuf[ioPos++];
                --bytesToRead;
                long result = b;
                if ((b & 0x80) == 0){  // just one byte
                        return result;
                }
                result &= 0x7f;
                long fac = 0x80;
                while (((b = ioBuf[ioPos++]) & 0x80) != 0){ // more bytes will follow
                        --bytesToRead;
                        result += fac * (b & 0x7f) ;
                        fac  <<= 7;
                }
                --bytesToRead;
                result += fac * b;
                return result;
        }

        /**
         * read a varying length unsigned number (see o5m definition)
         * is similar to the 64 bit version.
         * @return an int
         * @throws IOException
         */

        private int readUnsignedNum32(){
                int b = ioBuf[ioPos++];
                --bytesToRead;
                int result = b;
                if ((b & 0x80) == 0){  // just one byte
                        return result;
                }
                result &= 0x7f;
                long fac = 0x80;
                while (((b = ioBuf[ioPos++]) & 0x80) != 0){ // more bytes will follow
                        --bytesToRead;
                        result += fac * (b & 0x7f) ;
                        fac  <<= 7;
                }
                --bytesToRead;
                result += fac * b;
                return result;
        }

}