Subversion Repositories splitter

Rev

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

/*
 * Copyright (C) 2012, Gerd Petermann
 *
 * 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.splitter.writer;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

import it.unimi.dsi.fastutil.longs.LongArrayList;
import uk.me.parabola.splitter.Area;
import uk.me.parabola.splitter.Element;
import uk.me.parabola.splitter.Node;
import uk.me.parabola.splitter.Relation;
import uk.me.parabola.splitter.Relation.Member;
import uk.me.parabola.splitter.Utils;
import uk.me.parabola.splitter.Way;

/**
 * Implements the needed methods to write the result in the o5m format.
 * 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 O5mMapWriter extends AbstractOSMWriter{
        // 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 STW__TAB_MAX = 15000;  // this is defined in the o5m format
        private static final int STW_HASH_TAB_MAX = 30011;  // (preferably a prime number)
        private static final int STW_TAB_STR_MAX = 250;// this is defined in the o5m format
       
        private static final String[] REL_REF_TYPES = {"0","1","2"};
       
        private static final double FACTOR = 10000000;

        private OutputStream os;

        private byte[][][] stw__tab; // string table
        private byte[] s1Bytes;
        private byte[] s2Bytes;
        // for delta calculations
        private long lastNodeId;
        private long lastWayId;
        private long lastRelId;
        private long lastRef[];
        private int lastLon,lastLat;
       
        private int lastWrittenDatasetType = 0;
       
        // index of last entered element in string table
        private short stw__tabi= 0;
       
          // has table; elements point to matching strings in stw__tab[];
          // -1: no matching element;
        private short[] stw__hashtab;
          // for to chaining of string table rows which match
          // the same hash value; matching rows are chained in a loop;
          // if there is only one row matching, it will point to itself;
        private short[] stw__tabprev;
        private short[] stw__tabnext;
          // has value of this element as a link back to the hash table;
          // a -1 element indicates that the string table entry is not used;    
        private short[] stw__tabhash;
       
        private byte[] numberConversionBuf;

        private final static Map<String, byte[]> wellKnownTagKeys = new HashMap<>(60, 0.25f);
        private final static Map<String, byte[]> wellKnownTagVals = new HashMap<>(20, 0.25f);
       
        static {
                try {
                        for (String s : Arrays.asList(
                                         "1", "1outer", "1inner", "type", // relation specific  
                                                // 50 most often used keys (taken from taginfo 2016-11-20)
                                                "building", "source",
                                                "highway", "addr:housenumber", "addr:street", "name",
                                                "addr:city", "addr:postcode", "natural", "source:date", "addr:country",
                                                "landuse", "surface", "created_by", "power",
                                                "tiger:cfcc", "waterway", "tiger:county",
                                                "start_date", "tiger:reviewed", "wall",  
                                                "amenity", "oneway", "ref:bag", "ref",  
                                                "attribution", "tiger:name_base", "building:levels",
                                                "maxspeed", "barrier", "tiger:name_type", "height",
                                                "service", "source:addr", "tiger:tlid", "tiger:source",  
                                                "lanes", "access", "addr:place", "tiger:zip_left",
                                                "tiger:upload_uuid", "layer", "tracktype",
                                                "ele", "tiger:separated", "tiger:zip_right",
                                                "yh:WIDTH", "place", "foot"
                                        )) {
                                wellKnownTagKeys.put(s, s.getBytes("UTF-8"));
                        }

                        for (String s : Arrays.asList(
                                        "yes", "no", "residential", "garage", "water", "tower",
                                        "footway", "Bing", "PGS", "private", "stream", "service",
                                        "house", "unclassified", "track", "traffic_signals","restaurant","entrance"
                                        )) {
                                wellKnownTagVals.put(s, s.getBytes("UTF-8"));
                        }
                } catch (Exception e) {
                        // should not happen
                }
        }      
       
        //private long countCollisions;
       
        public O5mMapWriter(Area bounds, File outputDir, int mapId, int extra) {
                super(bounds, outputDir, mapId, extra);
        }

        private void reset() throws IOException{
                os.write(RESET_FLAG);
                resetVars();
        }
       
        /** reset the delta values and string table */
        private void resetVars(){
               
                lastNodeId = 0; lastWayId = 0; lastRelId = 0;
                lastRef[0] = 0; lastRef[1] = 0;lastRef[2] = 0;
                lastLon = 0; lastLat = 0;
                stw__tab = new byte[2][STW__TAB_MAX][];
                stw_reset();
        }
       
        public void initForWrite() {
                  // has table; elements point to matching strings in stw__tab[];
                  // -1: no matching element;
                stw__hashtab = new short[STW_HASH_TAB_MAX];
                  // for to chaining of string table rows which match
                  // the same hash value; matching rows are chained in a loop;
                  // if there is only one row matching, it will point to itself;
                stw__tabprev = new short[STW__TAB_MAX];
                stw__tabnext = new short[STW__TAB_MAX];
                  // has value of this element as a link back to the hash table;
                  // a -1 element indicates that the string table entry is not used;    
                stw__tabhash = new short[STW__TAB_MAX];
                lastRef = new long[3];
                numberConversionBuf = new byte[60];
                resetVars();

                String filename = String.format(Locale.ROOT, "%08d.o5m", mapId);
                try {
                        os = new BufferedOutputStream(new FileOutputStream(new File(outputDir, filename)));
                        os.write(RESET_FLAG);
                        writeHeader();
                        writeBBox();
                } catch (IOException e) {
                        System.out.println("Could not open or write file header. Reason: " + e.getMessage());
                        throw new RuntimeException(e);
                }
        }

        private void writeHeader() throws IOException {
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                byte[] id = {'o','5','m','2'};
                stream.write(id);
                writeDataset(HEADER_DATASET,stream);
        }

       
        private void writeBBox() throws IOException {
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                writeSignedNum((long)(Utils.toDegrees(bounds.getMinLong()) * FACTOR), stream);
                writeSignedNum((long)(Utils.toDegrees(bounds.getMinLat()) * FACTOR), stream);
                writeSignedNum((long)(Utils.toDegrees(bounds.getMaxLong()) * FACTOR), stream);
                writeSignedNum((long)(Utils.toDegrees(bounds.getMaxLat()) * FACTOR), stream);
                writeDataset(BBOX_DATASET,stream);
        }

        private void writeDataset(int fileType, ByteArrayOutputStream stream) throws IOException {
                os.write(fileType);
                writeUnsignedNum(stream.size(), os);
                stream.writeTo(os);
                lastWrittenDatasetType = fileType;
        }

        public void finishWrite() {
                try {
                        os.write(EOD_FLAG);
                        os.close();
                        stw__hashtab = null;
                        stw__tabprev = null;
                        stw__tabnext = null;
                        stw__tabhash = null;
                        lastRef = null;
                        numberConversionBuf = null;
                        stw__tab = null;
                        //System.out.println(mapId + " collisions=" + Utils.format(countCollisions));
                } catch (IOException e) {
                        System.out.println("Could not write end of file: " + e);
                }
        }

        @Override
        public void write(Node node) throws IOException {
                if (lastWrittenDatasetType != NODE_DATASET){
                        reset();
                }
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                long delta = node.getId() - lastNodeId; lastNodeId = node.getId();
                writeSignedNum(delta, stream);
                writeVersion(node, stream);
                int o5Lon = (int)(node.getLon() * FACTOR);
                int o5Lat = (int)(node.getLat() * FACTOR);
                int deltaLon = o5Lon - lastLon; lastLon = o5Lon;
                int deltaLat = o5Lat - lastLat; lastLat = o5Lat;
                writeSignedNum(deltaLon, stream);
                writeSignedNum(deltaLat, stream);
                writeTags(node, stream);
                writeDataset(NODE_DATASET,stream);
        }

        public void write(Way way) throws IOException {
                if (lastWrittenDatasetType != WAY_DATASET){
                        reset();
                }
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                long delta = way.getId() - lastWayId; lastWayId = way.getId();
                writeSignedNum(delta, stream);
                writeVersion(way, stream);
                ByteArrayOutputStream refStream = new ByteArrayOutputStream();
                LongArrayList refs = way.getRefs();
                int numRefs = refs.size();
                for (int i = 0; i < numRefs; i++){
                        long ref = refs.getLong(i);
                        delta = ref - lastRef[0]; lastRef[0] = ref;
                        writeSignedNum(delta, refStream);
                }
                writeUnsignedNum(refStream.size(),stream);
                refStream.writeTo(stream);
                writeTags(way, stream);
                writeDataset(WAY_DATASET,stream);
        }

        public void write(Relation rel) throws IOException {
                if (lastWrittenDatasetType != REL_DATASET){
                        reset();
                }
                ByteArrayOutputStream stream = new ByteArrayOutputStream(256);
                long delta = rel.getId() - lastRelId; lastRelId = rel.getId();
                writeSignedNum(delta, stream);
                writeVersion(rel, stream);
                ByteArrayOutputStream memStream = new ByteArrayOutputStream(256);
                for (Member mem: rel.getMembers()){
                        writeRelRef(mem, memStream);
                }
                writeUnsignedNum(memStream.size(),stream);
                memStream.writeTo(stream);
                writeTags(rel, stream);
                writeDataset(REL_DATASET,stream);
        }

        private void writeRelRef(Member mem, ByteArrayOutputStream memStream) throws IOException {
                int refType = 0;
                String type = mem.getType();
                if ("node".equals(type))
                        refType = 0;
                else if ("way".equals(type))
                        refType = 1;
                else if ("relation".equals(type))
                        refType = 2;
                else {
                        assert (false); // Software bug: Unknown entity.
                }
                long delta = mem.getRef() - lastRef[refType]; lastRef[refType] = mem.getRef();
                writeSignedNum(delta, memStream);
                stw_write(REL_REF_TYPES[refType] + mem.getRole(), null, memStream);
        }

        private void writeVersion (Element element, OutputStream stream) throws IOException {
                if (versionMethod == REMOVE_VERSION){
                        stream.write(0x00); // no version
                        return;
                }
                int version = 1;
                if (versionMethod == KEEP_VERSION)
                        version = element.getVersion();
                if (version != 0){
                        writeUnsignedNum(version, stream);
                }
                stream.write(0x00); // no author or time-stamp info  
        }
       
        private void writeTags(Element element, OutputStream stream) throws IOException {
                if (!element.hasTags())
                        return;
                Iterator<Element.Tag> it = element.tagsIterator();
                while (it.hasNext()) {
                        Element.Tag entry = it.next();
                        stw_write(entry.key, entry.value, stream);
                }
        }

       
        private void stw_write(String s1, String s2, OutputStream stream) throws IOException {
                int hash;
                int ref;
                s1Bytes = wellKnownTagKeys.get(s1);
                if (s1Bytes == null){
                        s1Bytes = s1.getBytes("UTF-8");
                }
                if (s2 != null){
                        s2Bytes = wellKnownTagVals.get(s2);
                        if (s2Bytes == null){
                                s2Bytes= s2.getBytes("UTF-8");
                        }
                }
                else
                        s2Bytes = null;

                //  try to find a matching string (pair) in string table
                {
                        int i;  // index in stw__tab[]
                        ref = -1;  // ref invalid (default)
                       
                        hash = stw_hash(s1,s2);
                    if (hash >= 0){
                        i = stw__hashtab[hash];
                        if(i >= 0)  // string (pair) presumably stored already
                                ref = stw__getref(i);
                    }  // end   string (pair) short enough for the string table
                    if(ref >= 0) {  // we found the string (pair) in the table
                        writeUnsignedNum(ref, stream);  // write just the reference
                        return;
                    }  // end   we found the string (pair) in the table
                        // write string data
                        stream.write(0x00);
                        stream.write(s1Bytes);
                        stream.write(0x00);
                        if (s2Bytes != null){
                                stream.write(s2Bytes);
                                stream.write(0x00);
                        }
                       
                        if(hash < 0){  // string (pair) too long,
                                // cannot be stored in string table
                                return;
                        }
                }  // end   try to find a matching string (pair) in string table
                // here: there is no matching string (pair) in the table

                // free new element - if still being used
                {
                        int h0;  // hash value of old element

                        h0 = stw__tabhash[stw__tabi];
                        if(h0 >= 0) {  // new element in string table is still being used
                                // delete old element
                                if(stw__tabnext[stw__tabi] == stw__tabi)
                                        // self-chain, i.e., only this element
                                        stw__hashtab[h0]= -1;  // invalidate link in hash table
                                else {  // one or more other elements in chain
                                        stw__hashtab[h0] = stw__tabnext[stw__tabi];  // just to ensure
                                        // that hash entry does not point to deleted element
                                        // now unchain deleted element
                                        stw__tabprev[stw__tabnext[stw__tabi]]= stw__tabprev[stw__tabi];
                                        stw__tabnext[stw__tabprev[stw__tabi]]= stw__tabnext[stw__tabi];
                                }  // end   one or more other elements in chain
                        }  // end   next element in string table is still being used
                }  // end   free new element - if still being used

                // enter new string table element data  
                {
                        int i;
                       
                        stw__tab[0][stw__tabi] = s1Bytes;
                        stw__tab[1][stw__tabi] = s2Bytes;
                       
                        i = stw__hashtab[hash];
                        if(i < 0)  // no reference in hash table until now
                                stw__tabprev[stw__tabi] = stw__tabnext[stw__tabi] = stw__tabi;
                        // self-link the new element;
                        else {  // there is already a reference in hash table
                                // in-chain the new element
                                stw__tabnext[stw__tabi] = (short) i;
                                stw__tabprev[stw__tabi] = stw__tabprev[i];
                                stw__tabnext[stw__tabprev[stw__tabi]] = stw__tabi;
                                stw__tabprev[i] = stw__tabi;
                                //countCollisions++;
                        }
                        stw__hashtab[hash] = stw__tabi; // link the new element to hash table
                        stw__tabhash[stw__tabi] = (short) hash; // backlink to hash table element
                        // new element now in use; set index to oldest element
                        if (++stw__tabi >= STW__TAB_MAX) { // index overflow
                                stw__tabi= 0;  // restart index
                        }  // end   index overflow
                }  // end   enter new string table element data
        }

        int stw__getref(final int stri) {
                int strie;  // index of last occurrence
                int ref;

                strie= stri;
                int pos = stri;
                do{
                        // compare the string (pair) with the tab entry
                        byte[] tb1 = stw__tab[0][pos];
                        if (Arrays.equals(tb1, s1Bytes)){
                                // first string equal to first string in table
                                byte[] tb2 = stw__tab[1][pos];
                                if (Arrays.equals(tb2, s2Bytes)){
                                        // second string equal to second string in table
                                        ref = stw__tabi - pos;
                                        if (ref <= 0)
                                                ref += STW__TAB_MAX;
                                        return ref;
                                }
                        }
                        pos = stw__tabnext[pos];
                } while(pos!=strie);
                return -1;
        }
       
        void stw_reset() {
                // clear string table and string hash table;
                // must be called before any other procedure of this module
                // and may be called every time the string processing shall
                // be restarted;

                stw__tabi = 0;
                Arrays.fill(stw__tabhash, (short)-1);
                Arrays.fill(stw__hashtab, (short)-1);
        }  
                       
        /**
         * get hash value of a string pair
         * @param s2
         * @param s1
         * @return  hash value in the range 0..(STW__TAB_MAX-1)
         * or -1 if the strings are longer than STW_TAB_STR_MAX bytes in total
         * @throws IOException
         */

        private int stw_hash(String s1, String s2) throws IOException{
                int len = s1Bytes.length;
                if (s2Bytes != null)
                        len += s2Bytes.length;
                if (len > STW_TAB_STR_MAX)
                        return -1;
                int hash = s1.hashCode();
                if (s2 != null)
                        hash ^= s2.hashCode();
               
                return Math.abs(hash % STW__TAB_MAX);
        }
       
        private int writeUnsignedNum(int number, OutputStream stream)throws IOException {
                int num = number;
                int cntBytes = 0;
                int part = num & 0x7f;
                if (part == num){ // just one byte
                        stream.write(part);
                        return 1;
                }
                do{
                        numberConversionBuf[cntBytes++] = (byte)(part | 0x80);
                        num >>= 7;
                        part = num & 0x7f;
                } while(part != num);
                numberConversionBuf[cntBytes++] = (byte)(part);
                stream.write(numberConversionBuf,0,cntBytes);
                return cntBytes;
        }
       
        private int writeSignedNum(long num, OutputStream stream)throws IOException {
                int cntBytes = 0;
                  // write a long as signed varying integer.
                  // return: bytes written;
                long u;
                int part;

                if (num < 0){
                        u = -num;
                        u= (u<<1)-1;
                }
                else{
                    u= num<<1;
                }
                part = (int)(u & 0x7f);
                if(part == u) {  // just one byte
                        stream.write(part);
                        return 1;
                }
                do {
                        numberConversionBuf[cntBytes++] = (byte)(part | 0x80);
                    u >>>= 7;
                    part = (int)(u & 0x7f);
                } while(part !=u);
                numberConversionBuf[cntBytes++] = (byte)(part);
                stream.write(numberConversionBuf,0,cntBytes);
                return cntBytes;
        }
}