All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.oscim.tiling.source.mvt.TileDecoder Maven / Gradle / Ivy

/*
 * Copyright 2013 Hannes Janetzek
 * Copyright 2016-2017 devemux86
 * Copyright 2017 Gustl22
 *
 * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see .
 */
package org.oscim.tiling.source.mvt;

import org.oscim.core.GeometryBuffer.GeometryType;
import org.oscim.core.MapElement;
import org.oscim.core.Tag;
import org.oscim.core.Tile;
import org.oscim.tiling.ITileDataSink;
import org.oscim.tiling.source.PbfDecoder;
import org.oscim.utils.FastMath;
import org.oscim.utils.pool.Inlist;
import org.oscim.utils.pool.Pool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

public class TileDecoder extends PbfDecoder {
    private static final Logger log = LoggerFactory.getLogger(TileDecoder.class);

    private static final int TAG_TILE_LAYERS = 3;

    private static final int TAG_LAYER_VERSION = 15;
    private static final int TAG_LAYER_NAME = 1;
    private static final int TAG_LAYER_FEATURES = 2;
    private static final int TAG_LAYER_KEYS = 3;
    private static final int TAG_LAYER_VALUES = 4;
    private static final int TAG_LAYER_EXTENT = 5;

    private static final int TAG_FEATURE_ID = 1;
    private static final int TAG_FEATURE_TAGS = 2;
    private static final int TAG_FEATURE_TYPE = 3;
    private static final int TAG_FEATURE_GEOMETRY = 4;

    private static final int TAG_VALUE_STRING = 1;
    private static final int TAG_VALUE_FLOAT = 2;
    private static final int TAG_VALUE_DOUBLE = 3;
    private static final int TAG_VALUE_LONG = 4;
    private static final int TAG_VALUE_UINT = 5;
    private static final int TAG_VALUE_SINT = 6;
    private static final int TAG_VALUE_BOOL = 7;

    private static final int TAG_GEOM_UNKNOWN = 0;
    private static final int TAG_GEOM_POINT = 1;
    private static final int TAG_GEOM_LINE = 2;
    private static final int TAG_GEOM_POLYGON = 3;

    private short[] mTmpTags = new short[1024];

    private Tile mTile;
    private final String mLocale;
    private ITileDataSink mMapDataCallback;

    private static final float REF_TILE_SIZE = 4096.0f;
    private float mScale;

    public TileDecoder() {
        this("");
    }

    public TileDecoder(String locale) {
        mLocale = locale;
    }

    @Override
    public boolean decode(Tile tile, ITileDataSink mapDataCallback, InputStream is)
            throws IOException {

        if (debug)
            log.debug(tile + " decode");

        //setInputStream(new InflaterInputStream(is));
        setInputStream(is);
        mTile = tile;
        mMapDataCallback = mapDataCallback;
        mScale = REF_TILE_SIZE / Tile.SIZE;

        int val;

        while (hasData() && (val = decodeVarint32()) > 0) {
            // read tag and wire type
            int tag = (val >> 3);

            switch (tag) {
                case TAG_TILE_LAYERS:
                    decodeLayer();
                    break;

                default:
                    error(mTile + " invalid type for tile: " + tag);
                    return false;
            }
        }

        if (hasData()) {
            error(tile + " invalid tile");
            return false;
        }
        return true;
    }

    private boolean decodeLayer() throws IOException {

        //int version = 0;
        //int extent = 4096;

        int bytes = decodeVarint32();

        ArrayList keys = new ArrayList<>();
        ArrayList values = new ArrayList<>();

        String name = null;
        int numFeatures = 0;
        ArrayList features = new ArrayList<>();

        int end = position() + bytes;
        while (position() < end) {
            // read tag and wire type
            int val = decodeVarint32();
            if (val == 0)
                break;

            int tag = (val >> 3);

            switch (tag) {
                case TAG_LAYER_KEYS:
                    keys.add(decodeString());
                    break;

                case TAG_LAYER_VALUES:
                    values.add(decodeValue());
                    break;

                case TAG_LAYER_FEATURES:
                    numFeatures++;
                    decodeFeature(features);
                    break;

                case TAG_LAYER_VERSION:
                    //version =
                    decodeVarint32();
                    break;

                case TAG_LAYER_NAME:
                    name = decodeString();
                    break;

                case TAG_LAYER_EXTENT:
                    //extent =
                    decodeVarint32();
                    break;

                default:
                    error(mTile + " invalid type for layer: " + tag);
                    break;
            }

        }

        Tag layerTag = new Tag("layer", name);
        if (debug)
            log.debug("add layer " + name);

        if (numFeatures == 0)
            return true;

        //int[] ignoreLocal = new int[1000];
        int numIgnore = 0;

        int fallBackLocal = -1;
        int matchedLocal = -1;

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            if (!key.startsWith(Tag.KEY_NAME))
                continue;
            int len = key.length();
            if (len == 4) {
                fallBackLocal = i;
                continue;
            }
            if (len < 7) {
                //ignoreLocal[numIgnore++] = i;
                continue;
            }

            if (mLocale.equals(key.substring(5))) {
                //log.debug("found local " + key);
                matchedLocal = i;
            } //else
            //ignoreLocal[numIgnore++] = i;

        }

        for (Feature f : features) {

            if (f.elem.type == GeometryType.NONE)
                continue;

            f.elem.tags.clear();
            f.elem.tags.add(layerTag);

            boolean hasName = false;
            String fallbackName = null;

            for (int j = 0; j < (f.numTags << 1); j += 2) {
                int keyIdx = f.tags[j];
                //for (int i = 0; i < numIgnore; i++)
                //    if (keyIdx == ignoreLocal[i])
                //        continue tagLoop;

                if (keyIdx == fallBackLocal) {
                    fallbackName = values.get(f.tags[j + 1]);
                    continue;
                }

                String key;
                String val = values.get(f.tags[j + 1]);

                if (keyIdx == matchedLocal) {
                    hasName = true;
                    f.elem.tags.add(new Tag(Tag.KEY_NAME, val, false));

                } else {
                    key = keys.get(keyIdx);
                    if (key.startsWith(Tag.KEY_NAME))
                        continue;

                    f.elem.tags.add(new Tag(key, val));
                }
            }

            if (!hasName && fallbackName != null)
                f.elem.tags.add(new Tag(Tag.KEY_NAME, fallbackName, false));

            // Calculate height of building parts
            if (!f.elem.tags.containsKey(Tag.KEY_HEIGHT)) {
                if (f.elem.tags.containsKey(Tag.KEY_VOLUME)
                        && f.elem.tags.containsKey(Tag.KEY_AREA)) {
                    float volume = Float.parseFloat(f.elem.tags.getValue(Tag.KEY_VOLUME));
                    float area = Float.parseFloat(f.elem.tags.getValue(Tag.KEY_AREA));
                    String heightStr = String.valueOf(FastMath.round2(volume / area));
                    f.elem.tags.add(new Tag(Tag.KEY_HEIGHT, heightStr, false));
                }
            }

            // FIXME extract layer tag here
            f.elem.setLayer(5);
            mMapDataCallback.process(f.elem);
            f = mFeaturePool.release(f);
        }

        return true;
    }

    private final Pool mFeaturePool = new Pool() {
        int count;

        @Override
        protected Feature createItem() {
            count++;
            return new Feature();
        }

        @Override
        protected boolean clearItem(Feature item) {
            if (count > 100) {
                count--;
                return false;
            }

            item.elem.tags.clear();
            item.elem.clear();
            item.tags = null;
            item.type = 0;
            item.numTags = 0;

            return true;
        }
    };

    static class Feature extends Inlist {
        short[] tags;
        int numTags;
        int type;

        final MapElement elem;

        Feature() {
            elem = new MapElement();
        }

        boolean match(short otherTags[], int otherNumTags, int otherType) {
            if (numTags != otherNumTags)
                return false;

            if (type != otherType)
                return false;

            for (int i = 0; i < numTags << 1; i++) {
                if (tags[i] != otherTags[i])
                    return false;
            }
            return true;
        }

    }

    private void decodeFeature(ArrayList features) throws IOException {
        int bytes = decodeVarint32();
        int end = position() + bytes;

        int type = 0;
        //long id;

        lastX = 0;
        lastY = 0;

        mTmpTags[0] = -1;

        Feature curFeature = null;
        int numTags = 0;

        //log.debug("start feature");
        while (position() < end) {
            // read tag and wire type
            int val = decodeVarint32();
            if (val == 0)
                break;

            int tag = (val >>> 3);

            switch (tag) {
                case TAG_FEATURE_ID:
                    //id =
                    decodeVarint32();
                    break;

                case TAG_FEATURE_TAGS:
                    mTmpTags = decodeUnsignedVarintArray(mTmpTags);

                    for (; numTags < mTmpTags.length && mTmpTags[numTags] >= 0; )
                        numTags += 2;

                    numTags >>= 1;

                    break;

                case TAG_FEATURE_TYPE:
                    type = decodeVarint32();
                    //log.debug("got type " + type);
                    break;

                case TAG_FEATURE_GEOMETRY:

                    for (Feature f : features) {
                        if (f.match(mTmpTags, numTags, type)) {
                            curFeature = f;
                            break;
                        }
                    }

                    if (curFeature == null) {
                        curFeature = mFeaturePool.get();
                        curFeature.tags = new short[numTags << 1];
                        System.arraycopy(mTmpTags, 0, curFeature.tags, 0, numTags << 1);
                        curFeature.numTags = numTags;
                        curFeature.type = type;

                        features.add(curFeature);
                    }

                    decodeCoordinates(type, curFeature);
                    break;

                default:
                    error(mTile + " invalid type for feature: " + tag);
                    break;
            }
        }
    }

    private static final int CLOSE_PATH = 0x07;
    private static final int MOVE_TO = 0x01;
    private static final int LINE_TO = 0x02;

    private int lastX, lastY;

    private int decodeCoordinates(int type, Feature feature) throws IOException {
        int bytes = decodeVarint32();
        fillBuffer(bytes);

        if (feature == null) {
            bufferPos += bytes;
            return 0;
        }

        MapElement elem = feature.elem;
        boolean isPoint = false;
        boolean isPoly = false;
        boolean isLine = false;

        if (type == TAG_GEOM_LINE) {
            elem.startLine();
            isLine = true;
        } else if (type == TAG_GEOM_POLYGON) {
            elem.startPolygon();
            isPoly = true;
        } else if (type == TAG_GEOM_POINT) {
            isPoint = true;
            elem.startPoints();
        } else if (type == TAG_GEOM_UNKNOWN)
            elem.startPoints();

        int val;

        int curX = 0;
        int curY = 0;
        int prevX = 0;
        int prevY = 0;

        int cmd = 0;
        int repeat = 0, cnt = 0;

        boolean first = true;
        boolean lastClip = false;

        // test bbox for outer..
        boolean isOuter = true;
        boolean simplify = false; //mTile.zoomLevel < 14;
        int pixel = simplify ? 7 : 3;

        //int xmin = Integer.MAX_VALUE, xmax = Integer.MIN_VALUE;
        //int ymin = Integer.MAX_VALUE, ymax = Integer.MIN_VALUE;

        for (int end = bufferPos + bytes; bufferPos < end; ) {

            if (repeat == 0) {
                val = decodeVarint32Filled();
                // number of points
                repeat = val >>> 3;
                cnt = 0;
                // path command
                cmd = val & 0x07;

                if (isLine && lastClip) {
                    elem.addPoint(curX / mScale, curY / mScale);
                    lastClip = false;
                }

                if (cmd == CLOSE_PATH) {
                    repeat = 0;
                    elem.startHole();
                    continue;
                }
                //                if (first) {
                //                    first = false;
                //                    continue;
                //                }
                if (cmd == MOVE_TO) {
                    if (type == TAG_GEOM_LINE) {
                        elem.startLine();
                    }
                }
                //elem.startHole();
                //                    } else if (type == TAG_GEOM_POLYGON) {
                //                        isOuter = false;
                //                        elem.startHole();
                //                    }
                //                }
                //                continue;
                if (repeat == 0)
                    continue;
            }

            repeat--;

            val = decodeVarint32Filled();
            int s = ((val >>> 1) ^ -(val & 1));
            curX = lastX = lastX + s;

            val = decodeVarint32Filled();
            s = ((val >>> 1) ^ -(val & 1));
            curY = lastY = lastY + s;

            int dx = (curX - prevX);
            int dy = (curY - prevY);

            prevX = curX;
            prevY = curY;

            //if (isPoly && repeat == 0 && cnt > 0) {
            //    prevX = curX;
            //    prevY = curY;

            // only add last point if it is di
            //int ppos = cnt * 2;
            //if (elem.points[elem.pointPos - ppos] != curX
            //        || elem.points[elem.pointPos - ppos + 1] != curY)
            //    elem.addPoint(curX / mScale, curY / mScale);

            //lastClip = false;
            //continue;
            //}

            if ((isPoint || cmd == MOVE_TO || cmd == LINE_TO)) {

                elem.addPoint(curX / mScale, curY / mScale);
                cnt++;

                lastClip = false;
                continue;
            }
            lastClip = true;
        }

        //        if (isPoly && isOuter && simplify && !testBBox(xmax - xmin, ymax - ymin)) {
        //            //log.debug("skip small poly "+ elem.indexPos + " > "
        //            // +  (xmax - xmin) * (ymax - ymin));
        //            elem.pointPos -= elem.index[elem.indexPos];
        //            if (elem.indexPos > 0) {
        //                elem.indexPos -= 2;
        //                elem.index[elem.indexPos + 1] = -1;
        //            } else {
        //                elem.type = GeometryType.NONE;
        //            }
        //            return 0;
        //        }

        if (isLine && lastClip)
            elem.addPoint(curX / mScale, curY / mScale);

        return 1;
    }

    private String decodeValue() throws IOException {
        int bytes = decodeVarint32();

        String value = null;

        int end = position() + bytes;

        while (position() < end) {
            // read tag and wire type
            int val = decodeVarint32();
            if (val == 0)
                break;

            int tag = (val >> 3);

            switch (tag) {
                case TAG_VALUE_STRING:
                    value = decodeString();
                    break;

                case TAG_VALUE_UINT:
                    value = String.valueOf(decodeVarint32());
                    break;

                case TAG_VALUE_SINT:
                    value = String.valueOf(deZigZag(decodeVarint32()));
                    break;

                case TAG_VALUE_LONG:
                    value = String.valueOf(decodeVarint64());
                    break;

                case TAG_VALUE_FLOAT:
                    value = String.valueOf(decodeFloat());
                    break;

                case TAG_VALUE_DOUBLE:
                    value = String.valueOf(decodeDouble());
                    break;

                case TAG_VALUE_BOOL:
                    value = decodeBool() ? "yes" : "no";
                    break;
                default:
                    break;
            }

        }
        return value;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy