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

org.openstreetmap.atlas.geography.CompressedPolyLine Maven / Gradle / Ivy

There is a newer version: 7.0.8
Show newest version
package org.openstreetmap.atlas.geography;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * A {@link PolyLine} that is compressed using delta encoding. This is efficient when the
 * {@link PolyLine} has a lot of points close by.
 *
 * @author matthieun
 */
public class CompressedPolyLine implements Located, Serializable
{
    /**
     * @author matthieun
     */
    private static class ByteSign
    {
        private final byte[] bytes;
        private final boolean sign;

        ByteSign(final byte[] bytes, final boolean sign)
        {
            this.bytes = bytes;
            this.sign = sign;
        }

        public byte[] getBytes()
        {
            return this.bytes;
        }

        public boolean isSign()
        {
            return this.sign;
        }
    }

    private static final long serialVersionUID = -7813027521625470225L;

    private static final int BYTE_FULL_MASK = 0xFF;
    private static final int BYTE_SIZE = 8;
    private static final int INT_SIGN_MASK = 0x80000000;
    private static final int INT_NO_SIGN_MASK = 0x7FFFFFFF;

    private final byte[][] positions;

    private final boolean[] signs;

    public CompressedPolyLine(final byte[][] positions, final boolean[] signs)
    {
        this.positions = positions;
        this.signs = signs;
    }

    /**
     * Create a compressed version of a {@link PolyLine}
     *
     * @param polyLine
     *            The {@link PolyLine} to compress.
     */
    public CompressedPolyLine(final PolyLine polyLine)
    {
        final List positions = new ArrayList<>();
        final List signs = new ArrayList<>();
        int formerLatitude = 0;
        int formerLongitude = 0;
        for (final Location location : polyLine)
        {
            final int latitude = (int) location.getLatitude().asDm7();
            final int longitude = (int) location.getLongitude().asDm7();
            final int deltaLatitude = latitude - formerLatitude;
            final int deltaLongitude = longitude - formerLongitude;
            formerLatitude = latitude;
            formerLongitude = longitude;
            final ByteSign latShrink = shrink(deltaLatitude);
            final ByteSign lonShrink = shrink(deltaLongitude);
            positions.add(latShrink.getBytes());
            signs.add(latShrink.isSign());
            positions.add(lonShrink.getBytes());
            signs.add(lonShrink.isSign());
        }
        this.positions = new byte[positions.size()][];
        for (int i = 0; i < positions.size(); i++)
        {
            this.positions[i] = positions.get(i);
        }
        this.signs = new boolean[signs.size()];
        for (int i = 0; i < signs.size(); i++)
        {
            this.signs[i] = signs.get(i);
        }
    }

    /**
     * @return An expanded {@link PolyLine}
     */
    public PolyLine asPolyLine()
    {
        boolean lat = true;
        int latitude = 0;
        int longitude = 0;
        final List locations = new ArrayList<>();
        for (int index = 0; index < this.positions.length; index++)
        {
            final byte[] result = this.positions[index];
            if (lat)
            {
                latitude += expand(result, index);
                lat = false;
            }
            else
            {
                longitude += expand(result, index);
                locations.add(new Location(Latitude.dm7(latitude), Longitude.dm7(longitude)));
                lat = true;
            }
        }
        return new PolyLine(locations);
    }

    @Override
    public Rectangle bounds()
    {
        return asPolyLine().bounds();
    }

    public byte[][] getPositions()
    {
        return this.positions;
    }

    public boolean[] getSigns()
    {
        return this.signs;
    }

    @Override
    public String toString()
    {
        return asPolyLine().toString();
    }

    /**
     * Transform an array of bytes into an int. If the bytes are 0x4A and 0x0F, with a negative sign
     * (from the index in the signs array) the returned int will be 0x80000F4A.
     *
     * @param result
     *            The shrunk value
     * @param index
     *            The index of the sign
     * @return The expanded value
     */
    private int expand(final byte[] result, final int index)
    {
        int placeholder = 0;
        for (int i = 0; i < result.length; i++)
        {
            final byte byteValue = result[i];
            placeholder |= byteValue & BYTE_FULL_MASK;
            if (i < result.length - 1)
            {
                placeholder <<= BYTE_SIZE;
            }
        }
        final boolean negative = this.signs[index];
        if (negative)
        {
            placeholder |= INT_SIGN_MASK;
        }
        return placeholder;
    }

    /**
     * Browse the value, byte after byte, and keep only the bytes that have significance. So an int
     * 0x80000F4A will return an array of two bytes, 0x4A and 0x0F, and a negative sign. All the 0
     * bytes will be thrown out.
     *
     * @param value
     *            The value to shrink
     * @return The shrunk value.
     */
    private ByteSign shrink(final int value)
    {
        // Get rid of the sign
        int placeholder = value & INT_NO_SIGN_MASK;
        final List bytes = new ArrayList<>();
        while (Math.abs(placeholder) > 0)
        {
            final byte byteValue = (byte) placeholder;
            bytes.add(byteValue);
            placeholder >>>= BYTE_SIZE;
        }
        final int size = bytes.size();
        final byte[] result = new byte[size];
        for (int i = 0; i < size; i++)
        {
            result[i] = bytes.get(size - 1 - i);
        }
        return new ByteSign(result, value < 0);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy