public static double latitudeToPixelY(double latitude, byte zoomLevel, int tileSize) {
        double sinLatitude = Math.sin(latitude * (Math.PI / 180));
        long mapSize = getMapSize(zoomLevel, tileSize);
        // FIXME improve this formula so that it works correctly without the clipping
        double pixelY = (0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI)) * mapSize;
        return Math.min(Math.max(0, pixelY), mapSize);
    }
@Test
    public void latitudeToPixelYTest() {
        for (int tileSize : TILE_SIZES) {
            for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) {
                long mapSize = MercatorProjection.getMapSize(zoomLevel, tileSize);
                double pixelY = MercatorProjection.latitudeToPixelY(MercatorProjection.LATITUDE_MAX, mapSize);
                Assert.assertEquals(0, pixelY, 0);

                pixelY = MercatorProjection.latitudeToPixelYWithScaleFactor(MercatorProjection.LATITUDE_MAX, MercatorProjection.zoomLevelToScaleFactor(zoomLevel),
                        tileSize);
                Assert.assertEquals(0, pixelY, 0);

                pixelY = MercatorProjection.latitudeToPixelY(0, mapSize);
                Assert.assertEquals((float) mapSize / 2, pixelY, 0);
                pixelY = MercatorProjection.latitudeToPixelYWithScaleFactor(0, MercatorProjection.zoomLevelToScaleFactor(zoomLevel), tileSize);
                Assert.assertEquals((float) mapSize / 2, pixelY, 0);

                pixelY = MercatorProjection.latitudeToPixelY(MercatorProjection.LATITUDE_MIN, mapSize);
                Assert.assertEquals(mapSize, pixelY, 0);
                pixelY = MercatorProjection.latitudeToPixelYWithScaleFactor(MercatorProjection.LATITUDE_MIN, MercatorProjection.zoomLevelToScaleFactor(zoomLevel), tileSize);
                Assert.assertEquals(mapSize, pixelY, 0);
            }
        }
    }