Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MercatorTiledImageLayer support. #242

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package gov.nasa.worldwind.layer.mercator;

import android.graphics.Bitmap;

import java.util.Collection;

import gov.nasa.worldwind.render.ImageTile;
import gov.nasa.worldwind.util.DownloadPostprocessor;
import gov.nasa.worldwind.util.Level;
import gov.nasa.worldwind.util.LevelSet;
import gov.nasa.worldwind.util.Logger;
import gov.nasa.worldwind.util.Tile;
import gov.nasa.worldwind.util.TileFactory;

class MercatorImageTile extends ImageTile implements DownloadPostprocessor<Bitmap> {

/**
* Constructs a tile with a specified sector, level, row and column.
*
* @param sector the sector spanned by the tile
* @param level the tile's level in a {@link LevelSet}
* @param row the tile's row within the specified level
* @param column the tile's column within the specified level
*/
MercatorImageTile(MercatorSector sector, Level level, int row, int column) {
super(sector, level, row, column);
}

/**
* Creates all Mercator tiles for a specified level within a {@link LevelSet}.
*
* @param level the level to create the tiles for
* @param tileFactory the tile factory to use for creating tiles.
* @param result an pre-allocated Collection in which to store the results
*/
static void assembleMercatorTilesForLevel(Level level, TileFactory tileFactory, Collection<Tile> result) {
if (level == null) {
throw new IllegalArgumentException(
Logger.logMessage(Logger.ERROR, "Tile", "assembleTilesForLevel", "missingLevel"));
}

if (tileFactory == null) {
throw new IllegalArgumentException(
Logger.logMessage(Logger.ERROR, "Tile", "assembleTilesForLevel", "missingTileFactory"));
}

if (result == null) {
throw new IllegalArgumentException(
Logger.logMessage(Logger.ERROR, "Tile", "assembleTilesForLevel", "missingResult"));
}

// NOTE LevelSet.sector is final Sector attribute and thus can not be cast to MercatorSector!
MercatorSector sector = MercatorSector.fromSector(level.parent.sector);
double dLat = level.tileDelta / 2;
double dLon = level.tileDelta;

int firstRow = Tile.computeRow(dLat, sector.minLatitude());
int lastRow = Tile.computeLastRow(dLat, sector.maxLatitude());
int firstCol = Tile.computeColumn(dLon, sector.minLongitude());
int lastCol = Tile.computeLastColumn(dLon, sector.maxLongitude());

double deltaLat = dLat / 90;
double d1 = sector.minLatPercent() + deltaLat * firstRow;
for (int row = firstRow; row <= lastRow; row++) {
double d2 = d1 + deltaLat;
double t1 = sector.minLongitude() + (firstCol * dLon);
for (int col = firstCol; col <= lastCol; col++) {
double t2;
t2 = t1 + dLon;
result.add(tileFactory.createTile(MercatorSector.fromDegrees(d1, d2, t1, t2), level, row, col));
t1 = t2;
}
d1 = d2;
}
}

/**
* Returns the four children formed by subdividing this tile. This tile's sector is subdivided into four quadrants
* as follows: Southwest; Southeast; Northwest; Northeast. A new tile is then constructed for each quadrant and
* configured with the next level within this tile's LevelSet and its corresponding row and column within that
* level. This returns null if this tile's level is the last level within its {@link LevelSet}.
*
* @param tileFactory the tile factory to use to create the children
*
* @return an array containing the four child tiles, or null if this tile's level is the last level
*/
@Override
public Tile[] subdivide(TileFactory tileFactory) {
if (tileFactory == null) {
throw new IllegalArgumentException(
Logger.logMessage(Logger.ERROR, "Tile", "subdivide", "missingTileFactory"));
}

Level childLevel = this.level.nextLevel();
if (childLevel == null) {
return null;
}

MercatorSector sector = (MercatorSector) this.sector;

double d0 = sector.minLatPercent();
double d2 = sector.maxLatPercent();
double d1 = d0 + (d2 - d0) / 2.0;

double t0 = sector.minLongitude();
double t2 = sector.maxLongitude();
double t1 = 0.5 * (t0 + t2);

int northRow = 2 * this.row;
int southRow = northRow + 1;
int westCol = 2 * this.column;
int eastCol = westCol + 1;

Tile[] children = new Tile[4];
children[0] = tileFactory.createTile(MercatorSector.fromDegrees(d0, d1, t0, t1), childLevel, northRow, westCol);
children[1] = tileFactory.createTile(MercatorSector.fromDegrees(d0, d1, t1, t2), childLevel, northRow, eastCol);
children[2] = tileFactory.createTile(MercatorSector.fromDegrees(d1, d2, t0, t1), childLevel, southRow, westCol);
children[3] = tileFactory.createTile(MercatorSector.fromDegrees(d1, d2, t1, t2), childLevel, southRow, eastCol);

return children;
}

@Override
public Bitmap process(Bitmap resource) {
// Re-project mercator tile to equirectangular
int[] pixels = new int[resource.getWidth() * resource.getHeight()];
int[] result = new int[resource.getWidth() * resource.getHeight()];
resource.getPixels(pixels, 0, resource.getWidth(), 0, 0, resource.getWidth(), resource.getHeight());
double miny = ((MercatorSector) sector).minLatPercent();
double maxy = ((MercatorSector) sector).maxLatPercent();
for (int y = 0; y < resource.getHeight(); y++) {
double sy = 1.0 - y / (double) (resource.getHeight() - 1);
double lat = sy * (sector.maxLatitude() - sector.minLatitude()) + sector.minLatitude();
double dy = 1.0 - (MercatorSector.gudermannianInverse(lat) - miny) / (maxy - miny);
dy = Math.max(0.0, Math.min(1.0, dy));
int iy = (int) (dy * (resource.getHeight() - 1));
for (int x = 0; x < resource.getWidth(); x++) {
result[x + y * resource.getWidth()] = pixels[x + iy * resource.getWidth()];
}
}
return Bitmap.createBitmap(result, resource.getWidth(), resource.getHeight(), resource.getConfig());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package gov.nasa.worldwind.layer.mercator;

import gov.nasa.worldwind.geom.Sector;

public class MercatorSector extends Sector {

private final double minLatPercent, maxLatPercent;

private MercatorSector(double minLatPercent, double maxLatPercent,
double minLongitude, double maxLongitude) {
this.minLatPercent = minLatPercent;
this.maxLatPercent = maxLatPercent;
this.minLatitude = gudermannian(minLatPercent);
this.maxLatitude = gudermannian(maxLatPercent);
this.minLongitude = minLongitude;
this.maxLongitude = maxLongitude;
}

public static MercatorSector fromDegrees(double minLatPercent, double maxLatPercent,
double minLongitude, double maxLongitude) {
return new MercatorSector(minLatPercent, maxLatPercent, minLongitude, maxLongitude);
}

static MercatorSector fromSector(Sector sector) {
return new MercatorSector(gudermannianInverse(sector.minLatitude()),
gudermannianInverse(sector.maxLatitude()),
sector.minLongitude(), sector.maxLongitude());
}

static double gudermannianInverse(double latitude) {
return Math.log(Math.tan(Math.PI / 4.0 + Math.toRadians(latitude) / 2.0)) / Math.PI;
}

private static double gudermannian(double percent) {
return Math.toDegrees(Math.atan(Math.sinh(percent * Math.PI)));
}

double minLatPercent() {
return minLatPercent;
}

double maxLatPercent()
{
return maxLatPercent;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package gov.nasa.worldwind.layer.mercator;

import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.geom.Sector;
import gov.nasa.worldwind.layer.RenderableLayer;
import gov.nasa.worldwind.render.ImageOptions;
import gov.nasa.worldwind.render.ImageSource;
import gov.nasa.worldwind.util.Level;
import gov.nasa.worldwind.util.LevelSet;
import gov.nasa.worldwind.util.Tile;
import gov.nasa.worldwind.util.TileFactory;

public abstract class MercatorTiledImageLayer extends RenderableLayer implements TileFactory {

private static final double FULL_SPHERE = 360;

private final int firstLevelOffset;

public MercatorTiledImageLayer(String name, int numLevels, int firstLevelOffset, int tileSize, boolean overlay) {
super(name);
this.setPickEnabled(false);
this.firstLevelOffset = firstLevelOffset;

MercatorTiledSurfaceImage surfaceImage = new MercatorTiledSurfaceImage();
surfaceImage.setLevelSet(new LevelSet(
MercatorSector.fromDegrees(-1.0, 1.0, - FULL_SPHERE / 2, FULL_SPHERE / 2),
FULL_SPHERE / (1 << firstLevelOffset), numLevels - firstLevelOffset, tileSize, tileSize));
surfaceImage.setTileFactory(this);
if(!overlay) {
surfaceImage.setImageOptions(new ImageOptions(WorldWind.RGB_565)); // reduce memory usage by using a 16-bit configuration with no alpha
}
this.addRenderable(surfaceImage);
}

@Override
public Tile createTile(Sector sector, Level level, int row, int column) {
MercatorImageTile tile = new MercatorImageTile((MercatorSector) sector, level, row, column);
tile.setImageSource(ImageSource.fromUrl(getImageSourceUrl(column, (1 << (level.levelNumber + firstLevelOffset)) - 1 - row, level.levelNumber + firstLevelOffset), tile));
return tile;
}

protected abstract String getImageSourceUrl(int x, int y, int z);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gov.nasa.worldwind.layer.mercator;

import gov.nasa.worldwind.shape.TiledSurfaceImage;
import gov.nasa.worldwind.util.Level;

public class MercatorTiledSurfaceImage extends TiledSurfaceImage {

@Override
protected void createTopLevelTiles() {
Level firstLevel = this.levelSet.firstLevel();
if (firstLevel != null) {
MercatorImageTile.assembleMercatorTilesForLevel(firstLevel, this.tileFactory, this.topLevelTiles);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package gov.nasa.worldwind.layer.mercator.google;

import gov.nasa.worldwind.layer.mercator.MercatorTiledImageLayer;

public class GoogleLayer extends MercatorTiledImageLayer {

public GoogleLayer(Type type) {
super(type.layerName, 22, 0, 256, type.overlay);
this.lyrs = type.lyrs;
}

private final String lyrs;

public enum Type {
ROADMAP("Google road map", "m", false),
ROADMAP2("Google road map 2", "r", false),
TERRAIN("Google map w/ terrain", "p", false),
TERRAIN_ONLY("Google terrain only", "t", false),
HYBRID("Google hybrid", "y", false),
SATELLITE("Google satellite", "s", false),
ROADS("Google roads", "h", true),
TRAFFIC("Google traffic", "h,traffic&style=15", true);

private final String layerName;
private final String lyrs;
private final boolean overlay;

Type(String layerName, String lyrs, boolean overlay) {
this.layerName = layerName;
this.lyrs = lyrs;
this.overlay = overlay;
}
}

@Override
public String getImageSourceUrl(int x, int y, int z) {
return "https://mt.google.com/vt/lyrs="+lyrs+"&x="+x+"&y="+y+"&z="+z+"&hl=ru";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gov.nasa.worldwind.layer.mercator.osm;

import java.util.Random;

import gov.nasa.worldwind.layer.mercator.MercatorTiledImageLayer;

public class OSMLayer extends MercatorTiledImageLayer {

public static final String NAME = "OpenStreetMap";

private final Random random = new Random();

public OSMLayer() {
super(NAME, 20, 3, 256, false);
}

@Override
protected String getImageSourceUrl(int x, int y, int z) {
char abc = "abc".charAt(random.nextInt(2));
return "https://"+abc+".tile.openstreetmap.org/"+z+"/"+x+"/"+y+".png";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gov.nasa.worldwind.layer.mercator.osm;

import java.util.Random;

import gov.nasa.worldwind.layer.mercator.MercatorTiledImageLayer;

public class OTMLayer extends MercatorTiledImageLayer {

public static final String NAME = "OpenTopoMap";

private final Random random = new Random();

public OTMLayer() {
super(NAME, 18, 3, 256, false);
}

@Override
protected String getImageSourceUrl(int x, int y, int z) {
char abc = "abc".charAt(random.nextInt(2));
return "https://"+abc+".tile.opentopomap.org/"+z+"/"+x+"/"+y+".png";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package gov.nasa.worldwind.layer.mercator.wiki;

import gov.nasa.worldwind.layer.mercator.MercatorTiledImageLayer;

public class WikiLayer extends MercatorTiledImageLayer {

public enum Type { MAP, HYBRID }

public static final String NAME = "Wiki";

private final Type type;

public WikiLayer(Type type) {
super(NAME + type.name().toLowerCase(), 23, 3, 256, Type.HYBRID == type);
this.type = type;
}

@Override
protected String getImageSourceUrl(int x, int y, int z) {
int i = x % 4 + y % 4 * 4;
return "http://i"+i+".wikimapia.org/?lng=1&x="+x+"&y="+y+"&zoom="+z+"&type="+type.name().toLowerCase();
}

}
Loading