Skip to content

Commit 06ee66c

Browse files
committed
Add MercatorTiledImageLayer support.
1 parent 0dacfec commit 06ee66c

File tree

7 files changed

+296
-3
lines changed

7 files changed

+296
-3
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package gov.nasa.worldwind.layer.mercator;
2+
3+
import android.graphics.Bitmap;
4+
5+
import java.util.Collection;
6+
7+
import gov.nasa.worldwind.render.ImageTile;
8+
import gov.nasa.worldwind.util.DownloadPostprocessor;
9+
import gov.nasa.worldwind.util.Level;
10+
import gov.nasa.worldwind.util.LevelSet;
11+
import gov.nasa.worldwind.util.Logger;
12+
import gov.nasa.worldwind.util.Tile;
13+
import gov.nasa.worldwind.util.TileFactory;
14+
15+
class MercatorImageTile extends ImageTile implements DownloadPostprocessor<Bitmap> {
16+
17+
/**
18+
* Constructs a tile with a specified sector, level, row and column.
19+
*
20+
* @param sector the sector spanned by the tile
21+
* @param level the tile's level in a {@link LevelSet}
22+
* @param row the tile's row within the specified level
23+
* @param column the tile's column within the specified level
24+
*/
25+
MercatorImageTile(MercatorSector sector, Level level, int row, int column) {
26+
super(sector, level, row, column);
27+
}
28+
29+
/**
30+
* Creates all Mercator tiles for a specified level within a {@link LevelSet}.
31+
*
32+
* @param level the level to create the tiles for
33+
* @param tileFactory the tile factory to use for creating tiles.
34+
* @param result an pre-allocated Collection in which to store the results
35+
*/
36+
static void assembleMercatorTilesForLevel(Level level, TileFactory tileFactory, Collection<Tile> result) {
37+
if (level == null) {
38+
throw new IllegalArgumentException(
39+
Logger.logMessage(Logger.ERROR, "Tile", "assembleTilesForLevel", "missingLevel"));
40+
}
41+
42+
if (tileFactory == null) {
43+
throw new IllegalArgumentException(
44+
Logger.logMessage(Logger.ERROR, "Tile", "assembleTilesForLevel", "missingTileFactory"));
45+
}
46+
47+
if (result == null) {
48+
throw new IllegalArgumentException(
49+
Logger.logMessage(Logger.ERROR, "Tile", "assembleTilesForLevel", "missingResult"));
50+
}
51+
52+
// NOTE LevelSet.sector is final Sector attribute and thus can not be cast to MercatorSector!
53+
MercatorSector sector = MercatorSector.fromSector(level.parent.sector);
54+
double dLat = level.tileDelta / 2;
55+
double dLon = level.tileDelta;
56+
57+
int firstRow = Tile.computeRow(dLat, sector.minLatitude());
58+
int lastRow = Tile.computeLastRow(dLat, sector.maxLatitude());
59+
int firstCol = Tile.computeColumn(dLon, sector.minLongitude());
60+
int lastCol = Tile.computeLastColumn(dLon, sector.maxLongitude());
61+
62+
double deltaLat = dLat / 90;
63+
double d1 = sector.minLatPercent() + deltaLat * firstRow;
64+
for (int row = firstRow; row <= lastRow; row++) {
65+
double d2 = d1 + deltaLat;
66+
double t1 = sector.minLongitude() + (firstCol * dLon);
67+
for (int col = firstCol; col <= lastCol; col++) {
68+
double t2;
69+
t2 = t1 + dLon;
70+
result.add(tileFactory.createTile(MercatorSector.fromDegrees(d1, d2, t1, t2), level, row, col));
71+
t1 = t2;
72+
}
73+
d1 = d2;
74+
}
75+
}
76+
77+
/**
78+
* Returns the four children formed by subdividing this tile. This tile's sector is subdivided into four quadrants
79+
* as follows: Southwest; Southeast; Northwest; Northeast. A new tile is then constructed for each quadrant and
80+
* configured with the next level within this tile's LevelSet and its corresponding row and column within that
81+
* level. This returns null if this tile's level is the last level within its {@link LevelSet}.
82+
*
83+
* @param tileFactory the tile factory to use to create the children
84+
*
85+
* @return an array containing the four child tiles, or null if this tile's level is the last level
86+
*/
87+
@Override
88+
public Tile[] subdivide(TileFactory tileFactory) {
89+
if (tileFactory == null) {
90+
throw new IllegalArgumentException(
91+
Logger.logMessage(Logger.ERROR, "Tile", "subdivide", "missingTileFactory"));
92+
}
93+
94+
Level childLevel = this.level.nextLevel();
95+
if (childLevel == null) {
96+
return null;
97+
}
98+
99+
MercatorSector sector = (MercatorSector) this.sector;
100+
101+
double d0 = sector.minLatPercent();
102+
double d2 = sector.maxLatPercent();
103+
double d1 = d0 + (d2 - d0) / 2.0;
104+
105+
double t0 = sector.minLongitude();
106+
double t2 = sector.maxLongitude();
107+
double t1 = 0.5 * (t0 + t2);
108+
109+
int northRow = 2 * this.row;
110+
int southRow = northRow + 1;
111+
int westCol = 2 * this.column;
112+
int eastCol = westCol + 1;
113+
114+
Tile[] children = new Tile[4];
115+
children[0] = tileFactory.createTile(MercatorSector.fromDegrees(d0, d1, t0, t1), childLevel, northRow, westCol);
116+
children[1] = tileFactory.createTile(MercatorSector.fromDegrees(d0, d1, t1, t2), childLevel, northRow, eastCol);
117+
children[2] = tileFactory.createTile(MercatorSector.fromDegrees(d1, d2, t0, t1), childLevel, southRow, westCol);
118+
children[3] = tileFactory.createTile(MercatorSector.fromDegrees(d1, d2, t1, t2), childLevel, southRow, eastCol);
119+
120+
return children;
121+
}
122+
123+
@Override
124+
public Bitmap process(Bitmap resource) {
125+
// Re-project mercator tile to equirectangular
126+
int[] pixels = new int[resource.getWidth() * resource.getHeight()];
127+
int[] result = new int[resource.getWidth() * resource.getHeight()];
128+
resource.getPixels(pixels, 0, resource.getWidth(), 0, 0, resource.getWidth(), resource.getHeight());
129+
double miny = ((MercatorSector) sector).minLatPercent();
130+
double maxy = ((MercatorSector) sector).maxLatPercent();
131+
for (int y = 0; y < resource.getHeight(); y++) {
132+
double sy = 1.0 - y / (double) (resource.getHeight() - 1);
133+
double lat = sy * (sector.maxLatitude() - sector.minLatitude()) + sector.minLatitude();
134+
double dy = 1.0 - (MercatorSector.gudermannianInverse(lat) - miny) / (maxy - miny);
135+
dy = Math.max(0.0, Math.min(1.0, dy));
136+
int iy = (int) (dy * (resource.getHeight() - 1));
137+
for (int x = 0; x < resource.getWidth(); x++) {
138+
result[x + y * resource.getWidth()] = pixels[x + iy * resource.getWidth()];
139+
}
140+
}
141+
return Bitmap.createBitmap(result, resource.getWidth(), resource.getHeight(), resource.getConfig());
142+
}
143+
144+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package gov.nasa.worldwind.layer.mercator;
2+
3+
import gov.nasa.worldwind.geom.Sector;
4+
5+
public class MercatorSector extends Sector {
6+
7+
private final double minLatPercent, maxLatPercent;
8+
9+
private MercatorSector(double minLatPercent, double maxLatPercent,
10+
double minLongitude, double maxLongitude) {
11+
this.minLatPercent = minLatPercent;
12+
this.maxLatPercent = maxLatPercent;
13+
this.minLatitude = gudermannian(minLatPercent);
14+
this.maxLatitude = gudermannian(maxLatPercent);
15+
this.minLongitude = minLongitude;
16+
this.maxLongitude = maxLongitude;
17+
}
18+
19+
public static MercatorSector fromDegrees(double minLatPercent, double maxLatPercent,
20+
double minLongitude, double maxLongitude) {
21+
return new MercatorSector(minLatPercent, maxLatPercent, minLongitude, maxLongitude);
22+
}
23+
24+
static MercatorSector fromSector(Sector sector) {
25+
return new MercatorSector(gudermannianInverse(sector.minLatitude()),
26+
gudermannianInverse(sector.maxLatitude()),
27+
sector.minLongitude(), sector.maxLongitude());
28+
}
29+
30+
static double gudermannianInverse(double latitude) {
31+
return Math.log(Math.tan(Math.PI / 4.0 + Math.toRadians(latitude) / 2.0)) / Math.PI;
32+
}
33+
34+
private static double gudermannian(double percent) {
35+
return Math.toDegrees(Math.atan(Math.sinh(percent * Math.PI)));
36+
}
37+
38+
double minLatPercent() {
39+
return minLatPercent;
40+
}
41+
42+
double maxLatPercent()
43+
{
44+
return maxLatPercent;
45+
}
46+
47+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package gov.nasa.worldwind.layer.mercator;
2+
3+
import gov.nasa.worldwind.WorldWind;
4+
import gov.nasa.worldwind.geom.Sector;
5+
import gov.nasa.worldwind.layer.RenderableLayer;
6+
import gov.nasa.worldwind.render.ImageOptions;
7+
import gov.nasa.worldwind.render.ImageSource;
8+
import gov.nasa.worldwind.util.Level;
9+
import gov.nasa.worldwind.util.LevelSet;
10+
import gov.nasa.worldwind.util.Tile;
11+
import gov.nasa.worldwind.util.TileFactory;
12+
13+
public abstract class MercatorTiledImageLayer extends RenderableLayer implements TileFactory {
14+
15+
private static final double FULL_SPHERE = 360;
16+
17+
private final int firstLevelOffset;
18+
19+
public MercatorTiledImageLayer(String name, int numLevels, int firstLevelOffset, int tileSize, boolean overlay) {
20+
super(name);
21+
this.setPickEnabled(false);
22+
this.firstLevelOffset = firstLevelOffset;
23+
24+
MercatorTiledSurfaceImage surfaceImage = new MercatorTiledSurfaceImage();
25+
surfaceImage.setLevelSet(new LevelSet(
26+
MercatorSector.fromDegrees(-1.0, 1.0, - FULL_SPHERE / 2, FULL_SPHERE / 2),
27+
FULL_SPHERE / (1 << firstLevelOffset), numLevels - firstLevelOffset, tileSize, tileSize));
28+
surfaceImage.setTileFactory(this);
29+
if(!overlay) {
30+
surfaceImage.setImageOptions(new ImageOptions(WorldWind.RGB_565)); // reduce memory usage by using a 16-bit configuration with no alpha
31+
}
32+
this.addRenderable(surfaceImage);
33+
}
34+
35+
@Override
36+
public Tile createTile(Sector sector, Level level, int row, int column) {
37+
MercatorImageTile tile = new MercatorImageTile((MercatorSector) sector, level, row, column);
38+
tile.setImageSource(ImageSource.fromUrl(getImageSourceUrl(column, (1 << (level.levelNumber + firstLevelOffset)) - 1 - row, level.levelNumber + firstLevelOffset), tile));
39+
return tile;
40+
}
41+
42+
protected abstract String getImageSourceUrl(int x, int y, int z);
43+
44+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package gov.nasa.worldwind.layer.mercator;
2+
3+
import gov.nasa.worldwind.shape.TiledSurfaceImage;
4+
import gov.nasa.worldwind.util.Level;
5+
6+
public class MercatorTiledSurfaceImage extends TiledSurfaceImage {
7+
8+
@Override
9+
protected void createTopLevelTiles() {
10+
Level firstLevel = this.levelSet.firstLevel();
11+
if (firstLevel != null) {
12+
MercatorImageTile.assembleMercatorTilesForLevel(firstLevel, this.tileFactory, this.topLevelTiles);
13+
}
14+
}
15+
16+
}

worldwind/src/main/java/gov/nasa/worldwind/render/ImageRetriever.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.net.URLConnection;
1717

1818
import gov.nasa.worldwind.WorldWind;
19+
import gov.nasa.worldwind.util.DownloadPostprocessor;
1920
import gov.nasa.worldwind.util.Logger;
2021
import gov.nasa.worldwind.util.Retriever;
2122
import gov.nasa.worldwind.util.WWUtil;
@@ -72,7 +73,7 @@ protected Bitmap decodeImage(ImageSource imageSource, ImageOptions imageOptions)
7273
}
7374

7475
if (imageSource.isUrl()) {
75-
return this.decodeUrl(imageSource.asUrl(), imageOptions);
76+
return this.decodeUrl(imageSource.asUrl(), imageOptions, imageSource.postprocessor);
7677
}
7778

7879
return this.decodeUnrecognized(imageSource);
@@ -88,7 +89,7 @@ protected Bitmap decodeFilePath(String pathName, ImageOptions imageOptions) {
8889
return BitmapFactory.decodeFile(pathName, factoryOptions);
8990
}
9091

91-
protected Bitmap decodeUrl(String urlString, ImageOptions imageOptions) throws IOException {
92+
protected Bitmap decodeUrl(String urlString, ImageOptions imageOptions, DownloadPostprocessor<Bitmap> postprocessor) throws IOException {
9293
// TODO establish a file caching service for remote resources
9394
// TODO retry absent resources, they are currently handled but suppressed entirely after the first failure
9495
// TODO configurable connect and read timeouts
@@ -102,7 +103,14 @@ protected Bitmap decodeUrl(String urlString, ImageOptions imageOptions) throws I
102103
stream = new BufferedInputStream(conn.getInputStream());
103104

104105
BitmapFactory.Options factoryOptions = this.bitmapFactoryOptions(imageOptions);
105-
return BitmapFactory.decodeStream(stream, null, factoryOptions);
106+
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, factoryOptions);
107+
108+
// Apply bitmap transformation if required
109+
if (postprocessor != null && bitmap != null) {
110+
bitmap = postprocessor.process(bitmap);
111+
}
112+
113+
return bitmap;
106114
} finally {
107115
WWUtil.closeSilently(stream);
108116
}

worldwind/src/main/java/gov/nasa/worldwind/render/ImageSource.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.Arrays;
1313
import java.util.HashMap;
1414

15+
import gov.nasa.worldwind.util.DownloadPostprocessor;
1516
import gov.nasa.worldwind.util.Logger;
1617
import gov.nasa.worldwind.util.WWUtil;
1718

@@ -68,6 +69,8 @@ public interface BitmapFactory {
6869

6970
protected Object source;
7071

72+
protected DownloadPostprocessor<Bitmap> postprocessor;
73+
7174
protected ImageSource() {
7275
}
7376

@@ -173,6 +176,23 @@ public static ImageSource fromUrl(String urlString) {
173176
return imageSource;
174177
}
175178

179+
/**
180+
* Constructs an image source with a URL string. The image's dimensions should be no greater than 2048 x 2048. The
181+
* application's manifest must include the permissions that allow network connections.
182+
*
183+
* @param urlString complete URL string
184+
* @param postprocessor implementation of image post-transformation routine
185+
*
186+
* @return the new image source
187+
*
188+
* @throws IllegalArgumentException If the URL string is null
189+
*/
190+
public static ImageSource fromUrl(String urlString, DownloadPostprocessor<Bitmap> postprocessor) {
191+
ImageSource imageSource = fromUrl(urlString);
192+
imageSource.postprocessor = postprocessor;
193+
return imageSource;
194+
}
195+
176196
/**
177197
* Constructs a bitmap image source with a line stipple pattern. The result is a one-dimensional bitmap with pixels
178198
* representing the specified stipple factor and stipple pattern. Line stipple images can be used for displaying
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package gov.nasa.worldwind.util;
2+
3+
/**
4+
* Interface for resource download post-processing
5+
*/
6+
public interface DownloadPostprocessor<T> {
7+
/**
8+
* Process resource according to specified algorithm implementation
9+
*
10+
* @param resource original resource
11+
* @return processed resource
12+
*/
13+
T process(T resource);
14+
}

0 commit comments

Comments
 (0)