rksahu1987 / osmdroid

Automatically exported from code.google.com/p/osmdroid
0 stars 0 forks source link

How to stretch tiles available at last zoom level in fake zoom. #433

Open GoogleCodeExporter opened 8 years ago

GoogleCodeExporter commented 8 years ago
What steps will reproduce the problem?
1. offline map and tiles available for 7 - 16 zoom level 
2. Now I setzoom 17, current view max tile at zoom 16 get stretched at zoom 17  
now able see road names very clearly which is perfect
3. Now shift screen (move the map) little bit, tiles are unavailable and grey 
screen showing because at 17 tiles are not available.

What is the expected output? What do you see instead?
I just want to stretch the tiles available at previous zoom level in my 17 zoom 
level while shifting screen 

What version of the product are you using? On what operating system?
osmdroid-android-3.0.8.jar android 4.0 version

Original issue reported on code.google.com by min2bhan...@gmail.com on 28 May 2013 at 1:38

GoogleCodeExporter commented 8 years ago
I tried with osmdroid-android-3.0.10.jar but no effect, could it be possible to 
scale the available tiles in previous zoom level in fake zoom even in scrolling.

Original comment by min2bhan...@gmail.com on 29 May 2013 at 5:56

GoogleCodeExporter commented 8 years ago
We don't provide this functionality. If you'd like it, write it yourself and 
contribute back. 

Original comment by kurtzm...@gmail.com on 29 May 2013 at 12:53

GoogleCodeExporter commented 8 years ago

Original comment by kurtzm...@gmail.com on 29 May 2013 at 12:53

GoogleCodeExporter commented 8 years ago
It would be great if you could provide some guidelines or hint to implement the 
same.

Thanks & Regards

Original comment by devendra...@gmail.com on 29 May 2013 at 1:23

GoogleCodeExporter commented 8 years ago
You will want to create a new tileprovider module and add it to your 
MapTileProviderArray. The modules are what feed tiles to the TileOverlay. It 
will be tricky because you will need to request tiles from other tileprovider 
modules for the higher zoom levels and then pull them from the tile cache and 
then scale them. You can either try to use the existing MapTileProviderArray or 
create a new one just for the scaled tiles.

So let's say you decide to use a second MapTileProviderArray for just the 
scaled tiles. The tile request chain should look like:

Request for tile 4/4/17.
Request tile from offline tile provider. Not found.
Request tile from new scaled tile provider.
  Scaled tile provider checks its "scaled" tile cache for one zoom level up (2/2/16). Not found. 
  Request tile for one zoom level up through its "scaled" MapTileProviderArray. Since the request is asynchronous, the tile request will fail for this cycle.
Re-request for tile 4/4/17.
Request tile from offline tile provider. Not found.
Request tile from new scaled tile provider.
  Scaled tile provider checks its "scaled" tile cache for 2/2/16. This time it's found, scaled, and returned.

Original comment by kurtzm...@gmail.com on 29 May 2013 at 1:47

GoogleCodeExporter commented 8 years ago
Thanks for the suggestions!! 

Original comment by devendra...@gmail.com on 30 May 2013 at 6:13

GoogleCodeExporter commented 8 years ago
do you have any plans to implement this enhancement?

Thanks

Original comment by min2bhan...@gmail.com on 30 May 2013 at 9:48

GoogleCodeExporter commented 8 years ago
This is something that is desired but there are no active plans to implement 
this.

Original comment by kurtzm...@gmail.com on 30 May 2013 at 1:09

GoogleCodeExporter commented 8 years ago
min2bhandari I am facing the same problem, did you find any solution for the 
same ?

If you have resolved this issue then please share the source code.
thanks in advance

Original comment by ashwanik...@gmail.com on 4 Jul 2013 at 1:03

GoogleCodeExporter commented 8 years ago
I have tried to understand the library but unable to resolve it, its not so 
trivial to implement this issue by own, as it would require more help by the 
owner.

Original comment by min2bhan...@gmail.com on 5 Jul 2013 at 3:03

GoogleCodeExporter commented 8 years ago
kurtzm...@gmail.com

I am also facing the 18 zoom level issue.I have read you suggestion and what i 
understand that when i am trying to zoom for 18 level actually i should get 16 
level tiles for the same position then enlarge tiles to 18 level tiles and then 
put on map.

But now i am facing the issue that how can i relate 18 level zoom position to 
16  level zoom position so that i can get tiles and enlarge them.

Please give me some guideline for this.I am Waiting for your positive response.

Thanking you. 

Original comment by ashwanik...@gmail.com on 17 Jul 2013 at 2:25

GoogleCodeExporter commented 8 years ago
I am attaching an experimental and rough cut at a scaled tile overlay. It needs 
work to be complete and to perform well but it works to some degree.

All it does is draw all cached tiles that are not in the current zoom level to 
the screen and properly scaled to the current zoom level.

One thing that it really needs to do is check to see if the correct tile exists 
in the current zoom level before drawing the scaled tile.

Original comment by kurtzm...@gmail.com on 18 Jul 2013 at 9:31

GoogleCodeExporter commented 8 years ago

Original comment by kurtzm...@gmail.com on 18 Jul 2013 at 9:32

Attachments:

GoogleCodeExporter commented 8 years ago
Issue 469 has been merged into this issue.

Original comment by kurtzm...@gmail.com on 23 Aug 2013 at 8:56

GoogleCodeExporter commented 8 years ago
Hi. This is exactly what I was looking to do as well. I tested the patch by 
manually incorporating your drawSafe() into my own Overlay based on the 
ItemizedIconOverlay class as I need markers being drawn. One thing this patch 
should consider if trying to combine it with ItemizedIconOverlay is that an 
overrided drawSafe() that attempts to scale tiles needs to call 
super.drawSafe() in order for the extended drawSafe to do what it's intended 
to. e.g. with ItemizedIconOverlay, it would actually draw markers. I don't know 
if this means that tiles are technically drawn twice, (by the override 
drawSafe, then by the super.drawSafe) but effectively it doesn't matter as the 
super.drawSafe doesn't have the tiles for that zoom level to draw anyways, so 
you get the scaled tile, and the underlying super.drawSafe can do its extra 
work, like draw markers, etc...

One deficiency I'd like to sort out is that when you zoom to a level where 
scaling is necessary and being drawn, then you pan the map to an area outside 
of that scaled tile, its empty. When you zoom out to the available tile level, 
then in again, the tiles are available and scaled like before.

Original comment by ch...@mckenzieic.com on 8 Jan 2014 at 4:02

GoogleCodeExporter commented 8 years ago
Ah it appears that my cached tile set doesn't have the required tiles loaded 
when panning after zooming below the available tile max zoom level. For 
example, I have tiles for 17 to 19 zoom levels. I zoom to level 20, then pan 
over past the tiles already cached at zoom level 19. In safeDraw(), 
mTileProvider.getTileCache().getAllTiles(null), which are additions to 
MapTileProviderBase and MapTileCache, will not have the the tiles panned to 
cache yet.

I believe because the patch implements the scaling in the Overlay, its stuck 
working with what it already has cached. drawSafe could be enhanced to try and 
pull any of the viewable tiles from the tile source,added to the cache, then 
scaled, if I could get access to more than the cache from my Overlay. (as a 
result of the patch adding a getTileCache method to MapTileProviderBase)

However I have no clue how to determine the MapTile's that would be used in the 
MapView at any given time. e.g. if I knew at Overlay drawSafe or draw time what 
MapTile's the MapView if trying to display, then I could try loading them from 
the TileSource and scaling them myself.

Any tips? I really want the panning support to work. This might not be the best 
way to do it, but I'm desperate and this is a prototype. Thanks!

Original comment by ch...@mckenzieic.com on 8 Jan 2014 at 9:20

GoogleCodeExporter commented 8 years ago
Ugh, this is painful. I tried implementing my own MapTileFileArchiveProvider, 
that has a TileLoader which will attempt to retrieve tiles a zoom level up and 
scale them, but I have no clue how to deal with returning a scaled image. If we 
try to support this in a Tile Provider, then we need to scale the image from a 
level up then split it, and pick the correct split section and return it.

When trying to return the image that should belong to a non-existent tile, the 
image from a level up that's zoomed covers a larger geographic area than the 
tile is intended to. So the image needs to be scaled, then split. The problem 
is, how do I safely split it and determine where the map tile's X/Y should be?

I'd like to draw an image illustrating this, but I think you get the point. 

There's two ways to work with tile images you want to scale: 
1) At the file load level
2) At the point of drawing the tile images.

At least when the tiles are drawn you have access to the Projection, MapView, 
zoom, etc... and can do stuff like kurtzm has done in his patch. (from an 
Overlay)

Can someone from osmdroid comment on this? Maybe provide some clear guidance?

I'm essentially looking for zoom to levels I don't have tiles for. Sure the 
tile image will get pixelated, but close markers on the map can be reasonably 
zoomed into. This is a real problem on high resolution screens.

Thanks!

Original comment by ch...@mckenzieic.com on 8 Jan 2014 at 11:59

GoogleCodeExporter commented 8 years ago
Ok, I worked out a Tile Provider only solution. This should be a stick note 
somewhere. As mentioned, I ended up implementing my own 
MapTileScaledFileArchiveProvider as an alternative to 
MapTileFileArchiveProvider. I probably could have extended 
MapTileFileArchiveProvider but I was trying to work around all the inaccessible 
parts and just wanted to get this working.

I don't have a patch, I'm just going to paste in a clip of my TileLoader class, 
its got all the goods and can likely be improved/adapted to work with an online 
Tile Provider. I expected that Tile caching would be handled based on what's 
returned from loadTile, but it seems like the tiles are constantly loaded from 
the archive. Also, it seems to work fine with my tiles that have a maximum of 
19, and "fake" zoom to level 21. When I zoom to level 22 I get missing tile 
loads. I'm not sure why, maybe there's a round error, but it's all integer. 
(e.g. int rx = pTile.getX()/(2*z); where rx is used as the X when looking for 
the tile image)

Panning works with this approach as well. When I zoom in to level 20 or 21, 
then pan around, the higher levels tiles are loaded, resized, and cropped as 
needed.

Improvements could include a fake zoom cache as part of the Tile Provider 
itself, or access to the actual tile cache from the Tile Provider so it 
wouldn't try to load every tile request afresh. (from what I can tell) Also, 
there's probably a more efficient way to resize and crop the tiles.

The up side to this approach is that no changes are needed to the osmdroid API. 
You can simply implement your own Tile Provider, and/or extend an existing one, 
then handle up sizing and cropping lower level tiles.

public class MapTileScaledFileArchiveProvider extends
        MapTileFileStorageProviderBase {
    ...
    @Override
    protected Runnable getTileLoader() {
        return new TileLoader();
    }

    protected class TileLoader extends MapTileModuleProviderBase.TileLoader {

        @Override
        public Drawable loadTile(final MapTileRequestState pState) {

            ITileSource tileSource = mTileSource.get();
            if (tileSource == null) {
                return null;
            }

            final MapTile pTile = pState.getMapTile();

            // if there's no sdcard then don't do anything
            if (!getSdCardAvailable()) {
                Log.d(TAG, "No sdcard - do nothing for tile: " + pTile);
                return null;
            }

            InputStream inputStream = null;
            try {

                inputStream = getInputStream(pTile, tileSource);
                if (inputStream != null) {
                    Log.d(TAG, "Use tile from archive: " + pTile);
                    final Drawable drawable = tileSource.getDrawable(inputStream);
                    return drawable;
                } else {
                    final int requestedZoom = pTile.getZoomLevel();
                    final int maxZoom = 3;
                    MapTile rTile = null;
                    for(int z=1; z<maxZoom; z++) {
                        int rx = pTile.getX()/(2*z);
                        int ry = pTile.getY()/(2*z);
                        rTile = new MapTile(requestedZoom - z, rx, ry); // We have to construct a new MapTile request for the higher zoom level
                        inputStream = getInputStream(rTile, tileSource);
                        if (inputStream != null) {
                            final Drawable redrawable = resize(tileSource.getDrawable(inputStream), pTile.getX(), pTile.getY(), z);
                            return redrawable;

                        } else {
                            Log.d(TAG, "Tile doesn't exist and I couldn't find a tile to scale: " + pTile);
                        }
                    }
                }

            } catch (final Throwable e) {
                Log.e(TAG, "Error loading tile", e);
            } finally {
                if (inputStream != null) {
                    StreamUtils.closeStream(inputStream);
                }
            }

            return null;
        }

        final int mTileSizePixels = 256;

        private Drawable resize(Drawable image, final int rx, final int ry, final int zoomLevelDiff) {
            int px = rx%(2*zoomLevelDiff);
            int py = ry%(2*zoomLevelDiff);
            Bitmap b = ((BitmapDrawable)image).getBitmap();
            Bitmap bitmapResized = Bitmap.createBitmap(
                    Bitmap.createScaledBitmap(b, (mTileSizePixels*2)*zoomLevelDiff, (mTileSizePixels*2)*zoomLevelDiff, false),
                    (px == 0) ? 0 : mTileSizePixels*px, 
                    (py == 0) ? 0 : mTileSizePixels*py,
                    mTileSizePixels,
                    mTileSizePixels);
            return new BitmapDrawable(Tec3Application.context().getResources(), bitmapResized);
        }
    }

Original comment by ch...@mckenzieic.com on 9 Jan 2014 at 5:51

GoogleCodeExporter commented 8 years ago
Hi, can you provide a readable answer on this I am stuck for like 2 days on 
this issue and I would appreciate it if you share your custom 
MapTileScaledFileArchiveProvider

Original comment by tagru...@gmail.com on 11 Feb 2014 at 2:09

GoogleCodeExporter commented 8 years ago
Are you talking to me? You have the source to MapTileFileStorageProviderBase. I 
gave you the override and TileLoader I did in that sample along with how I 
scaled the tile.

Maybe you should offer a bit more information on how you're "stuck".

Original comment by ch...@mckenzieic.com on 11 Feb 2014 at 2:49

GoogleCodeExporter commented 8 years ago
I am trying to reimplement the #18. Could you please explain where does the 
getInputStream come from?

Original comment by tam...@gmail.com on 1 May 2014 at 6:14

GoogleCodeExporter commented 8 years ago
Hi Tam.

As I mentioned, my suggested approach, (which I find doesn't perform great and 
needs some scaled tile caching added) is an alternative to the 
MapTileFileArchiveProvider class 
(https://code.google.com/p/osmdroid/source/browse/trunk/osmdroid-android/src/org
/osmdroid/tileprovider/modules/MapTileFileArchiveProvider.java?r=687).

The code I included in post #18 were the primary changes to a copy of the 
MapTileFileArchiveProvider class. Basically I took MapTileFileArchiveProvider 
and implemented my own TileLoader. The rest is from MapTileFileArchiveProvider.

The getInputStream() method of the MapTileFileArchiveProvider which I copied 
into my MapTileScaledFileArchiveProvider class.

Original comment by ch...@mckenzieic.com on 1 May 2014 at 3:22

GoogleCodeExporter commented 8 years ago
Also, if you're planning on C&P my code into your own copy of 
MapTileFileArchiveProvider, the resize() method I included in post #18 needs a 
little change. BitmapDrawable needs your application context. In the version I 
included I had my own way to retrieve it, you should too. The only change below 
is to "return new BitmapDrawable()" where you need to specify your context.

        private Drawable resize(Drawable image, final int rx, final int ry, final int zoomLevelDiff) {
            int px = rx%(2*zoomLevelDiff);
            int py = ry%(2*zoomLevelDiff);
            Bitmap b = ((BitmapDrawable)image).getBitmap();
            Bitmap bitmapResized = Bitmap.createBitmap(
                    Bitmap.createScaledBitmap(b, (mTileSizePixels*2)*zoomLevelDiff, (mTileSizePixels*2)*zoomLevelDiff, false),
                    (px == 0) ? 0 : mTileSizePixels*px, 
                    (py == 0) ? 0 : mTileSizePixels*py,
                    mTileSizePixels,
                    mTileSizePixels);
            return new BitmapDrawable(context.getResources(), bitmapResized);
        }

Original comment by ch...@mckenzieic.com on 1 May 2014 at 3:29

GoogleCodeExporter commented 8 years ago
Excellent approach. I was thinking of doing the exact same thing and happily 
stumbled on your posting before doing exactly what you're doing in #18 and #23 
- the resize/crop math was what I was looking for when I found your post as 
that is hairy to a non graphics person like me.

The only addition I was planning to make is to have a "water mark" tile for 
when there are no upper tiles to scale, or the tile to scale is too high up so 
as not to be useful. This is for a situation where the mapped area has a very 
irregular shape (definitely not a rectangle) and the app does not want to allow 
any underlying online map to show through. This allows me to significantly 
compress the MB Tiles file by using a single watermark tile (e.g. 
zoom=column=tile=-1) rather than lots of empty tiles that although individually 
take up little space will accumulate to occupy a lot of space. Also the 
run-time configurable maximum scale setting will allow islands of deep five 
zoom levels in an otherwise low zoom level wide area map, with the border being 
a little fuzzy at the first few zoom levels in, and then switching over to the 
watermark tile when probably the user is nowhere near the edge of the zoom 
level anyway.

I also find the all the private/final method definitions frustrating requiring 
a copy/paste/edit model with the "fun" of redoing the work when upgrading to a 
more modern library version.  I understand why the developers have done this, I 
just don't like it.

I'll try to remember to post back my code if I get it to work, and work 
efficiently.

Original comment by colin.gr...@gmail.com on 9 Sep 2014 at 5:28

GoogleCodeExporter commented 8 years ago
ch...@mckenzieic.com

Thanks for a great approach,however If I pan map to the sides, other tiles, 
then the one which is already fake zoomed, are not displayed. Do you have any 
more workaround about this?

Thanks

Original comment by mujSams...@gmail.com on 10 Sep 2014 at 12:03

robert113289 commented 4 years ago
Ok, I worked out a Tile Provider only solution. This should be a stick note 
somewhere. As mentioned, I ended up implementing my own 
MapTileScaledFileArchiveProvider as an alternative to 
MapTileFileArchiveProvider. I probably could have extended 
MapTileFileArchiveProvider but I was trying to work around all the inaccessible 
parts and just wanted to get this working.

I don't have a patch, I'm just going to paste in a clip of my TileLoader class, 
its got all the goods and can likely be improved/adapted to work with an online 
Tile Provider. I expected that Tile caching would be handled based on what's 
returned from loadTile, but it seems like the tiles are constantly loaded from 
the archive. Also, it seems to work fine with my tiles that have a maximum of 
19, and "fake" zoom to level 21. When I zoom to level 22 I get missing tile 
loads. I'm not sure why, maybe there's a round error, but it's all integer. 
(e.g. int rx = pTile.getX()/(2*z); where rx is used as the X when looking for 
the tile image)

Panning works with this approach as well. When I zoom in to level 20 or 21, 
then pan around, the higher levels tiles are loaded, resized, and cropped as 
needed.

Improvements could include a fake zoom cache as part of the Tile Provider 
itself, or access to the actual tile cache from the Tile Provider so it 
wouldn't try to load every tile request afresh. (from what I can tell) Also, 
there's probably a more efficient way to resize and crop the tiles.

The up side to this approach is that no changes are needed to the osmdroid API. 
You can simply implement your own Tile Provider, and/or extend an existing one, 
then handle up sizing and cropping lower level tiles.

public class MapTileScaledFileArchiveProvider extends
        MapTileFileStorageProviderBase {
    ...
    @Override
    protected Runnable getTileLoader() {
        return new TileLoader();
    }

    protected class TileLoader extends MapTileModuleProviderBase.TileLoader {

        @Override
        public Drawable loadTile(final MapTileRequestState pState) {

            ITileSource tileSource = mTileSource.get();
            if (tileSource == null) {
                return null;
            }

            final MapTile pTile = pState.getMapTile();

            // if there's no sdcard then don't do anything
            if (!getSdCardAvailable()) {
                Log.d(TAG, "No sdcard - do nothing for tile: " + pTile);
                return null;
            }

            InputStream inputStream = null;
            try {

                inputStream = getInputStream(pTile, tileSource);
                if (inputStream != null) {
                    Log.d(TAG, "Use tile from archive: " + pTile);
                    final Drawable drawable = tileSource.getDrawable(inputStream);
                    return drawable;
                } else {
                    final int requestedZoom = pTile.getZoomLevel();
                    final int maxZoom = 3;
                    MapTile rTile = null;
                    for(int z=1; z<maxZoom; z++) {
                        int rx = pTile.getX()/(2*z);
                        int ry = pTile.getY()/(2*z);
                        rTile = new MapTile(requestedZoom - z, rx, ry); // We have to construct a new MapTile request for the higher zoom level
                        inputStream = getInputStream(rTile, tileSource);
                        if (inputStream != null) {
                            final Drawable redrawable = resize(tileSource.getDrawable(inputStream), pTile.getX(), pTile.getY(), z);
                            return redrawable;

                        } else {
                            Log.d(TAG, "Tile doesn't exist and I couldn't find a tile to scale: " + pTile);
                        }
                    }
                }

            } catch (final Throwable e) {
                Log.e(TAG, "Error loading tile", e);
            } finally {
                if (inputStream != null) {
                    StreamUtils.closeStream(inputStream);
                }
            }

            return null;
        }

        final int mTileSizePixels = 256;

        private Drawable resize(Drawable image, final int rx, final int ry, final int zoomLevelDiff) {
            int px = rx%(2*zoomLevelDiff);
            int py = ry%(2*zoomLevelDiff);
            Bitmap b = ((BitmapDrawable)image).getBitmap();
            Bitmap bitmapResized = Bitmap.createBitmap(
                    Bitmap.createScaledBitmap(b, (mTileSizePixels*2)*zoomLevelDiff, (mTileSizePixels*2)*zoomLevelDiff, false),
                    (px == 0) ? 0 : mTileSizePixels*px, 
                    (py == 0) ? 0 : mTileSizePixels*py,
                    mTileSizePixels,
                    mTileSizePixels);
            return new BitmapDrawable(Tec3Application.context().getResources(), bitmapResized);
        }
    }

Original comment by ch...@mckenzieic.com on 9 Jan 2014 at 5:51

Hey Mckenzieic, your solution was very helpful in one of my projects. I did find an improvement and an answer for you though. In your post you talked about a possible issue you had:

"When I zoom to level 22 I get missing tile loads. I'm not sure why, maybe there's a round error, but it's all integer. (e.g. int rx = pTile.getX()/(2*z); where rx is used as the X when looking for the tile image)"

This didnt turn out to be a rounding issue but it is caused by two things. your maxZoom is 3 and results in only looking 2 zoom levels above. you could increase to 4 or 5 but you will see the same results as before because the calculations for rx and ry are inaccurate after 2 zoom levels above. the correct formula for rx would be to do 2^z instead of 2z. replace all 2z and 2*zoomLevelDiff with 2^z and 2^zoomLevelDiff and you will have it!