private IList <Way> ProcessWays(QueryParameters queryParameters, int numberOfWays, BoundingBox boundingBox, bool filterRequired, double tileLatitude, double tileLongitude, ReadBuffer readBuffer)
        {
            IList <Way> ways = new List <Way>();

            Tag[] wayTags = this.mapFileHeader.MapFileInfo.WayTags;

            // Extend BoundingBox about wayFilterDistance meters, so that ways crossing the border added
            double verticalExpansion   = PointUtils.LatitudeDistance(wayFilterDistance);
            double horizontalExpansion = PointUtils.LongitudeDistance(wayFilterDistance, Math.Max(Math.Abs(boundingBox.MinY), Math.Abs(boundingBox.MaxY)));

            double minLat = Math.Max(MercatorProjection.LATITUDE_MIN, boundingBox.MinY - verticalExpansion);
            double minLon = Math.Max(-180, boundingBox.MinX - horizontalExpansion);
            double maxLat = Math.Min(MercatorProjection.LATITUDE_MAX, boundingBox.MaxY + verticalExpansion);
            double maxLon = Math.Min(180, boundingBox.MaxX + horizontalExpansion);

            BoundingBox wayFilterBbox = new BoundingBox(minLon, minLat, maxLon, maxLat);

            for (int elementCounter = numberOfWays; elementCounter != 0; --elementCounter)
            {
                if (this.mapFileHeader.MapFileInfo.DebugFile)
                {
                    // get and check the way signature
                    string signatureWay = readBuffer.ReadUTF8EncodedString(SIGNATURE_LENGTH_WAY);
                    if (!signatureWay.StartsWith("---WayStart", StringComparison.Ordinal))
                    {
                        Logger.Log(LogLevel.Warning, "invalid way signature: " + signatureWay);
                        return(null);
                    }
                }

                // get the size of the way (VBE-U)
                int wayDataSize = readBuffer.ReadUnsignedInt();
                if (wayDataSize < 0)
                {
                    Logger.Log(LogLevel.Warning, "invalid way data size: " + wayDataSize);
                    return(null);
                }

                if (queryParameters.useTileBitmask)
                {
                    // get the way tile bitmask (2 bytes)
                    int tileBitmask = readBuffer.ReadShort();
                    // check if the way is inside the requested tile
                    if ((queryParameters.queryTileBitmask & tileBitmask) == 0)
                    {
                        // skip the rest of the way and continue with the next way
                        readBuffer.SkipBytes(wayDataSize - 2);
                        continue;
                    }
                }
                else
                {
                    // ignore the way tile bitmask (2 bytes)
                    readBuffer.SkipBytes(2);
                }

                // get the special byte which encodes multiple flags
                sbyte specialByte = readBuffer.ReadByte();

                // bit 1-4 represent the layer
                sbyte layer = (sbyte)((int)((uint)(specialByte & WAY_LAYER_BITMASK) >> WAY_LAYER_SHIFT));
                // bit 5-8 represent the number of tag IDs
                sbyte numberOfTags = (sbyte)(specialByte & WAY_NUMBER_OF_TAGS_BITMASK);

                IList <Tag> tags = new List <Tag>();

                for (sbyte tagIndex = numberOfTags; tagIndex != 0; --tagIndex)
                {
                    int tagId = readBuffer.ReadUnsignedInt();
                    if (tagId < 0 || tagId >= wayTags.Length)
                    {
                        Logger.Log(LogLevel.Warning, "invalid way tag ID: " + tagId);
                        return(null);
                    }
                    tags.Add(wayTags[tagId]);
                }

                // get the feature bitmask (1 byte)
                sbyte featureByte = readBuffer.ReadByte();

                // bit 1-6 enable optional features
                bool featureName                   = (featureByte & WAY_FEATURE_NAME) != 0;
                bool featureHouseNumber            = (featureByte & WAY_FEATURE_HOUSE_NUMBER) != 0;
                bool featureRef                    = (featureByte & WAY_FEATURE_REF) != 0;
                bool featureLabelPosition          = (featureByte & WAY_FEATURE_LABEL_POSITION) != 0;
                bool featureWayDataBlocksByte      = (featureByte & WAY_FEATURE_DATA_BLOCKS_BYTE) != 0;
                bool featureWayDoubleDeltaEncoding = (featureByte & WAY_FEATURE_DOUBLE_DELTA_ENCODING) != 0;

                // check if the way has a name
                if (featureName)
                {
                    tags.Add(new Tag(TAG_KEY_NAME, ExtractLocalized(readBuffer.ReadUTF8EncodedString())));
                }

                // check if the way has a house number
                if (featureHouseNumber)
                {
                    tags.Add(new Tag(TAG_KEY_HOUSE_NUMBER, readBuffer.ReadUTF8EncodedString()));
                }

                // check if the way has a reference
                if (featureRef)
                {
                    tags.Add(new Tag(TAG_KEY_REF, readBuffer.ReadUTF8EncodedString()));
                }

                Point labelPosition = ReadOptionalLabelPosition(tileLatitude, tileLongitude, featureLabelPosition, readBuffer);

                int wayDataBlocks = ReadOptionalWayDataBlocksByte(featureWayDataBlocksByte, readBuffer);
                if (wayDataBlocks < 1)
                {
                    Logger.Log(LogLevel.Warning, "invalid number of way data blocks: " + wayDataBlocks);
                    return(null);
                }

                for (int wayDataBlock = 0; wayDataBlock < wayDataBlocks; ++wayDataBlock)
                {
                    List <List <Point> > wayNodes = ProcessWayDataBlock(tileLatitude, tileLongitude, featureWayDoubleDeltaEncoding, readBuffer);
                    if (wayNodes != null)
                    {
                        if (filterRequired && wayFilterEnabled)
                        {
                            double minX = double.MaxValue;
                            double minY = double.MaxValue;
                            double maxX = 0;
                            double maxY = 0;
                            for (int i = 0; i < wayNodes.Count; i++)
                            {
                                for (int j = 0; j < wayNodes[i].Count; j++)
                                {
                                    minX = Math.Min(minX, wayNodes[i][j].X);
                                    minY = Math.Min(minY, wayNodes[i][j].Y);
                                    maxX = Math.Max(maxX, wayNodes[i][j].X);
                                    maxY = Math.Max(maxY, wayNodes[i][j].Y);
                                }
                            }
                            if (!wayFilterBbox.Intersects(new BoundingBox(minX, minY, maxX, maxY)))
                            {
                                continue;
                            }
                        }
                        ways.Add(new Way(layer, tags, wayNodes, labelPosition));
                    }
                }
            }

            return(ways);
        }
        private Point ReadOptionalLabelPosition(double tileLatitude, double tileLongitude, bool featureLabelPosition, ReadBuffer readBuffer)
        {
            if (featureLabelPosition)
            {
                // get the label position latitude offset (VBE-S)
                double latitude = tileLatitude + PointUtils.MicrodegreesToDegrees(readBuffer.ReadSignedInt());

                // get the label position longitude offset (VBE-S)
                double longitude = tileLongitude + PointUtils.MicrodegreesToDegrees(readBuffer.ReadSignedInt());

                return(new Point(longitude, latitude));
            }

            return(null);
        }
        private List <List <Point> > ProcessWayDataBlock(double tileLatitude, double tileLongitude, bool doubleDeltaEncoding, ReadBuffer readBuffer)
        {
            // get and check the number of way coordinate blocks (VBE-U)
            int numberOfWayCoordinateBlocks = readBuffer.ReadUnsignedInt();

            if (numberOfWayCoordinateBlocks < 1 || numberOfWayCoordinateBlocks > short.MaxValue)
            {
                Logger.Log(LogLevel.Warning, "invalid number of way coordinate blocks: " + numberOfWayCoordinateBlocks);
                return(null);
            }

            // create the array which will store the different way coordinate blocks
            List <List <Point> > wayCoordinates = new List <List <Point> >(numberOfWayCoordinateBlocks);

            // read the way coordinate blocks
            for (int coordinateBlock = 0; coordinateBlock < numberOfWayCoordinateBlocks; ++coordinateBlock)
            {
                // get and check the number of way nodes (VBE-U)
                int numberOfWayNodes = readBuffer.ReadUnsignedInt();
                if (numberOfWayNodes < 2 || numberOfWayNodes > short.MaxValue)
                {
                    Logger.Log(LogLevel.Warning, "invalid number of way nodes: " + numberOfWayNodes);
                    // returning null here will actually leave the tile blank as the
                    // position on the ReadBuffer will not be advanced correctly. However,
                    // it will not crash the app.
                    return(null);
                }

                // create the array which will store the current way segment
                List <Point> waySegment = new List <Point>(numberOfWayNodes);

                if (doubleDeltaEncoding)
                {
                    decodeWayNodesDoubleDelta(waySegment, tileLatitude, tileLongitude, readBuffer);
                }
                else
                {
                    decodeWayNodesSingleDelta(waySegment, tileLatitude, tileLongitude, readBuffer);
                }

                wayCoordinates.Add(waySegment);
            }

            return(wayCoordinates);
        }
        private IList <PointOfInterest> ProcessPOIs(double tileLatitude, double tileLongitude, int numberOfPois, BoundingBox boundingBox, bool filterRequired, ReadBuffer readBuffer)
        {
            IList <PointOfInterest> pois = new List <PointOfInterest>();

            Tag[] poiTags = this.mapFileHeader.MapFileInfo.PoiTags;

            for (int elementCounter = numberOfPois; elementCounter != 0; --elementCounter)
            {
                if (this.mapFileHeader.MapFileInfo.DebugFile)
                {
                    // get and check the POI signature
                    string signaturePoi = readBuffer.ReadUTF8EncodedString(SIGNATURE_LENGTH_POI);
                    if (!signaturePoi.StartsWith("***POIStart", StringComparison.Ordinal))
                    {
                        Logger.Log(LogLevel.Warning, "invalid POI signature: " + signaturePoi);
                        return(null);
                    }
                }

                // get the POI latitude offset (VBE-S)
                double latitude = tileLatitude + PointUtils.MicrodegreesToDegrees(readBuffer.ReadSignedInt());

                // get the POI longitude offset (VBE-S)
                double longitude = tileLongitude + PointUtils.MicrodegreesToDegrees(readBuffer.ReadSignedInt());

                // get the special byte which encodes multiple flags
                sbyte specialByte = readBuffer.ReadByte();

                // bit 1-4 represent the layer with
                sbyte layer = (sbyte)((int)((uint)(specialByte & POI_LAYER_BITMASK) >> POI_LAYER_SHIFT));
                // bit 5-8 represent the number of tag IDs
                sbyte numberOfTags = (sbyte)(specialByte & POI_NUMBER_OF_TAGS_BITMASK);

                IList <Tag> tags = new List <Tag>();

                // get the tag IDs (VBE-U)
                for (sbyte tagIndex = numberOfTags; tagIndex != 0; --tagIndex)
                {
                    int tagId = readBuffer.ReadUnsignedInt();
                    if (tagId < 0 || tagId >= poiTags.Length)
                    {
                        Logger.Log(LogLevel.Warning, "invalid POI tag ID: " + tagId);
                        return(null);
                    }
                    tags.Add(poiTags[tagId]);
                }

                // get the feature bitmask (1 byte)
                sbyte featureByte = readBuffer.ReadByte();

                // bit 1-3 enable optional features
                bool featureName        = (featureByte & POI_FEATURE_NAME) != 0;
                bool featureHouseNumber = (featureByte & POI_FEATURE_HOUSE_NUMBER) != 0;
                bool featureElevation   = (featureByte & POI_FEATURE_ELEVATION) != 0;

                // check if the POI has a name
                if (featureName)
                {
                    tags.Add(new Tag(TAG_KEY_NAME, ExtractLocalized(readBuffer.ReadUTF8EncodedString())));
                }

                // check if the POI has a house number
                if (featureHouseNumber)
                {
                    tags.Add(new Tag(TAG_KEY_HOUSE_NUMBER, readBuffer.ReadUTF8EncodedString()));
                }

                // check if the POI has an elevation
                if (featureElevation)
                {
                    tags.Add(new Tag(TAG_KEY_ELE, Convert.ToString(readBuffer.ReadSignedInt())));
                }

                Point position = new Point(longitude, latitude);
                // depending on the zoom level configuration the poi can lie outside
                // the tile requested, we filter them out here
                if (!filterRequired || boundingBox.Contains(position))
                {
                    pois.Add(new PointOfInterest(layer, tags, position));
                }
            }

            return(pois);
        }
        private MapReadResult ProcessBlocks(QueryParameters queryParameters, SubFileParameter subFileParameter, BoundingBox boundingBox)
        {
            bool queryIsWater       = true;
            bool queryReadWaterInfo = false;

            MapReadResult mapFileReadResult = new MapReadResult();

            // read and process all blocks from top to bottom and from left to right
            for (long row = queryParameters.fromBlockY; row <= queryParameters.toBlockY; ++row)
            {
                for (long column = queryParameters.fromBlockX; column <= queryParameters.toBlockX; ++column)
                {
                    // calculate the actual block number of the needed block in the file
                    long blockNumber = row * subFileParameter.BlocksWidth + column;

                    // get the current index entry
                    long currentBlockIndexEntry = this.databaseIndexCache.GetIndexEntry(subFileParameter, blockNumber);

                    // check if the current query would still return a water tile
                    if (queryIsWater)
                    {
                        // check the water flag of the current block in its index entry
                        queryIsWater      &= (currentBlockIndexEntry & BITMASK_INDEX_WATER) != 0;
                        queryReadWaterInfo = true;
                    }

                    // get and check the current block pointer
                    long currentBlockPointer = currentBlockIndexEntry & BITMASK_INDEX_OFFSET;
                    if (currentBlockPointer < 1 || currentBlockPointer > subFileParameter.SubFileSize)
                    {
                        Logger.Log(LogLevel.Warning, "invalid current block pointer: " + currentBlockPointer);
                        Logger.Log(LogLevel.Warning, "subFileSize: " + subFileParameter.SubFileSize);
                        return(null);
                    }

                    long nextBlockPointer;
                    // check if the current block is the last block in the file
                    if (blockNumber + 1 == subFileParameter.NumberOfBlocks)
                    {
                        // set the next block pointer to the end of the file
                        nextBlockPointer = subFileParameter.SubFileSize;
                    }
                    else
                    {
                        // get and check the next block pointer
                        nextBlockPointer = this.databaseIndexCache.GetIndexEntry(subFileParameter, blockNumber + 1) & BITMASK_INDEX_OFFSET;
                        if (nextBlockPointer > subFileParameter.SubFileSize)
                        {
                            Logger.Log(LogLevel.Warning, "invalid next block pointer: " + nextBlockPointer);
                            Logger.Log(LogLevel.Warning, "sub-file size: " + subFileParameter.SubFileSize);
                            return(null);
                        }
                    }

                    // calculate the size of the current block
                    int currentBlockSize = (int)(nextBlockPointer - currentBlockPointer);
                    if (currentBlockSize < 0)
                    {
                        Logger.Log(LogLevel.Warning, "current block size must not be negative: " + currentBlockSize);
                        return(null);
                    }
                    else if (currentBlockSize == 0)
                    {
                        // the current block is empty, continue with the next block
                        continue;
                    }
                    else if (currentBlockSize > ReadBuffer.MaximumBufferSize)
                    {
                        // the current block is too large, continue with the next block
                        Logger.Log(LogLevel.Warning, "current block size too large: " + currentBlockSize);
                        continue;
                    }
                    else if (currentBlockPointer + currentBlockSize > this.fileSize)
                    {
                        Logger.Log(LogLevel.Warning, "current block largher than file size: " + currentBlockSize);
                        return(null);
                    }

                    // read the current block into the buffer
                    ReadBuffer readBuffer = new ReadBuffer(inputStream);
                    if (!readBuffer.ReadFromStream(subFileParameter.StartAddress + currentBlockPointer, currentBlockSize))
                    {
                        // skip the current block
                        Logger.Log(LogLevel.Warning, "reading current block has failed: " + currentBlockSize);
                        return(null);
                    }

                    // calculate the top-left coordinates of the underlying tile
                    double tileLatitude  = MercatorProjection.TileYToLatitude(subFileParameter.BoundaryTileTop + row, subFileParameter.BaseZoomLevel);
                    double tileLongitude = MercatorProjection.TileXToLongitude(subFileParameter.BoundaryTileLeft + column, subFileParameter.BaseZoomLevel);

                    try
                    {
                        PoiWayBundle poiWayBundle = ProcessBlock(queryParameters, subFileParameter, boundingBox, tileLatitude, tileLongitude, readBuffer);
                        if (poiWayBundle != null)
                        {
                            mapFileReadResult.Add(poiWayBundle);
                        }
                    }
                    catch (System.IndexOutOfRangeException e)
                    {
                        Logger.Log(LogLevel.Error, e.Message, e);
                    }
                }
            }

            // the query is finished, was the water flag set for all blocks?
            if (queryIsWater && queryReadWaterInfo)
            {
                // Deprecate water tiles rendering
                // mapFileReadResult.IsWater = true;
            }

            return(mapFileReadResult);
        }
        private PoiWayBundle ProcessBlock(QueryParameters queryParameters, SubFileParameter subFileParameter, BoundingBox boundingBox, double tileLatitude, double tileLongitude, ReadBuffer readBuffer)
        {
            if (!ProcessBlockSignature(readBuffer))
            {
                return(null);
            }

            int[][] zoomTable            = ReadZoomTable(subFileParameter, readBuffer);
            int     zoomTableRow         = queryParameters.queryZoomLevel - subFileParameter.ZoomLevelMin;
            int     poisOnQueryZoomLevel = zoomTable[zoomTableRow][0];
            int     waysOnQueryZoomLevel = zoomTable[zoomTableRow][1];

            // get the relative offset to the first stored way in the block
            int firstWayOffset = readBuffer.ReadUnsignedInt();

            if (firstWayOffset < 0)
            {
                Logger.Log(LogLevel.Warning, INVALID_FIRST_WAY_OFFSET + firstWayOffset);
                return(null);
            }

            // add the current buffer position to the relative first way offset
            firstWayOffset += readBuffer.BufferPosition;
            if (firstWayOffset > readBuffer.BufferSize)
            {
                Logger.Log(LogLevel.Warning, INVALID_FIRST_WAY_OFFSET + firstWayOffset);
                return(null);
            }

            bool filterRequired = queryParameters.queryZoomLevel > subFileParameter.BaseZoomLevel;

            IList <PointOfInterest> pois = ProcessPOIs(tileLatitude, tileLongitude, poisOnQueryZoomLevel, boundingBox, filterRequired, readBuffer);

            if (pois == null)
            {
                return(null);
            }

            // finished reading POIs, check if the current buffer position is valid
            if (readBuffer.BufferPosition > firstWayOffset)
            {
                Logger.Log(LogLevel.Warning, "invalid buffer position: " + readBuffer.BufferPosition);
                return(null);
            }

            // move the pointer to the first way
            readBuffer.BufferPosition = firstWayOffset;

            IList <Way> ways = ProcessWays(queryParameters, waysOnQueryZoomLevel, boundingBox, filterRequired, tileLatitude, tileLongitude, readBuffer);

            if (ways == null)
            {
                return(null);
            }

            return(new PoiWayBundle(pois, ways));
        }
        private void decodeWayNodesSingleDelta(List <Point> waySegment, double tileLatitude, double tileLongitude, ReadBuffer readBuffer)
        {
            // get the first way node latitude single-delta offset (VBE-S)
            double wayNodeLatitude = tileLatitude + PointUtils.MicrodegreesToDegrees(readBuffer.ReadSignedInt());

            // get the first way node longitude single-delta offset (VBE-S)
            double wayNodeLongitude = tileLongitude + PointUtils.MicrodegreesToDegrees(readBuffer.ReadSignedInt());

            // store the first way node
            waySegment.Add(new Point(wayNodeLongitude, wayNodeLatitude));

            for (int wayNodesIndex = 1; wayNodesIndex < waySegment.Capacity; ++wayNodesIndex)
            {
                // get the way node latitude offset (VBE-S)
                wayNodeLatitude = wayNodeLatitude + PointUtils.MicrodegreesToDegrees(readBuffer.ReadSignedInt());

                // get the way node longitude offset (VBE-S)
                wayNodeLongitude = wayNodeLongitude + PointUtils.MicrodegreesToDegrees(readBuffer.ReadSignedInt());

                // Decoding near international date line can return values slightly outside valid [-180°, 180°] due to calculation precision
                if (wayNodeLongitude < PointUtils.LONGITUDE_MIN &&
                    (PointUtils.LONGITUDE_MIN - wayNodeLongitude) < 0.001)
                {
                    wayNodeLongitude = PointUtils.LONGITUDE_MIN;
                }
                else if (wayNodeLongitude > PointUtils.LONGITUDE_MAX &&
                         (wayNodeLongitude - PointUtils.LONGITUDE_MAX) < 0.001)
                {
                    wayNodeLongitude = PointUtils.LONGITUDE_MAX;
                }

                waySegment.Add(new Point(wayNodeLongitude, wayNodeLatitude));
            }
        }