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

Fix GeoPackage support implementation. #243

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
Expand Up @@ -16,6 +16,7 @@
import java.util.List;

import gov.nasa.worldwind.draw.BasicDrawableTerrain;
import gov.nasa.worldwind.geom.Location;
import gov.nasa.worldwind.geom.Range;
import gov.nasa.worldwind.geom.Sector;
import gov.nasa.worldwind.geom.Vec3;
Expand All @@ -32,7 +33,7 @@
public class BasicTessellator implements Tessellator, TileFactory {

// ~0.6 meter resolution
protected LevelSet levelSet = new LevelSet(new Sector().setFullSphere(), 90, 20, 32, 32);
protected LevelSet levelSet = new LevelSet(new Sector().setFullSphere(), new Location(-90, -180), 90, 20, 32, 32);

protected double detailControl = 80;

Expand Down
25 changes: 18 additions & 7 deletions worldwind/src/main/java/gov/nasa/worldwind/layer/LayerFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.SparseArray;

import java.io.BufferedInputStream;
import java.io.InputStream;
Expand All @@ -22,13 +23,15 @@
import java.util.concurrent.RejectedExecutionException;

import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.geom.Location;
import gov.nasa.worldwind.geom.Sector;
import gov.nasa.worldwind.ogc.WmsLayerConfig;
import gov.nasa.worldwind.ogc.WmsTileFactory;
import gov.nasa.worldwind.ogc.gpkg.GeoPackage;
import gov.nasa.worldwind.ogc.gpkg.GpkgContent;
import gov.nasa.worldwind.ogc.gpkg.GpkgSpatialReferenceSystem;
import gov.nasa.worldwind.ogc.gpkg.GpkgTileFactory;
import gov.nasa.worldwind.ogc.gpkg.GpkgTileMatrix;
import gov.nasa.worldwind.ogc.gpkg.GpkgTileMatrixSet;
import gov.nasa.worldwind.ogc.gpkg.GpkgTileUserMetrics;
import gov.nasa.worldwind.ogc.wms.WmsCapabilities;
Expand Down Expand Up @@ -258,6 +261,13 @@ protected void createFromGeoPackageAsync(String pathName, Layer layer, Callback
continue;
}

SparseArray<GpkgTileMatrix> tileMatrix = geoPackage.getTileMatrix(content.getTableName());
if (tileMatrix == null || tileMatrix.size() == 0) {
Logger.logMessage(Logger.WARN, "LayerFactory", "createFromGeoPackageAsync",
"Unsupported GeoPackage tile matrix");
continue;
}

GpkgTileUserMetrics tileMetrics = geoPackage.getTileUserMetrics(content.getTableName());
if (tileMetrics == null) {
Logger.logMessage(Logger.WARN, "LayerFactory", "createFromGeoPackageAsync",
Expand All @@ -266,14 +276,15 @@ protected void createFromGeoPackageAsync(String pathName, Layer layer, Callback
}

LevelSetConfig config = new LevelSetConfig();
config.sector.set(content.getMinY(), content.getMinX(),
content.getMaxY() - content.getMinY(), content.getMaxX() - content.getMinX());
config.firstLevelDelta = 180;
config.numLevels = tileMetrics.getMaxZoomLevel() + 1; // zero when there are no zoom levels, (0 = -1 + 1)
config.tileWidth = 256;
config.tileHeight = 256;
config.sector.set(tileMatrixSet.getMinY(), tileMatrixSet.getMinX(),
tileMatrixSet.getMaxY() - tileMatrixSet.getMinY(),
tileMatrixSet.getMaxX() - tileMatrixSet.getMinX());
config.tileOrigin.set(tileMatrixSet.getMinY(), tileMatrixSet.getMinX());
config.firstLevelDelta = (tileMatrixSet.getMaxY() - tileMatrixSet.getMinY()) / tileMatrix.valueAt(0).getMatrixHeight();
config.numLevels = tileMatrix.keyAt(tileMatrix.size() - 1) - tileMatrix.keyAt(0) + 1;

TiledSurfaceImage surfaceImage = new TiledSurfaceImage();
surfaceImage.setDisplayName(content.getIdentifier());
surfaceImage.setLevelSet(new LevelSet(config));
surfaceImage.setTileFactory(new GpkgTileFactory(content));
gpkgRenderables.addRenderable(surfaceImage);
Expand Down Expand Up @@ -670,7 +681,7 @@ protected LevelSet createWmtsLevelSet(WmtsLayer wmtsLayer, CompatibleTileMatrixS
}
int imageSize = tileMatrixSet.getTileMatrices().get(0).getTileHeight();

return new LevelSet(boundingBox, 90.0, compatibleTileMatrixSet.tileMatrices.size(), imageSize, imageSize);
return new LevelSet(boundingBox, new Location(-90, -180), 90, compatibleTileMatrixSet.tileMatrices.size(), imageSize, imageSize);
}

protected String buildWmtsKvpTemplate(String kvpServiceAddress, String layer, String format, String styleIdentifier, String tileMatrixSet) {
Expand Down
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.geom.Location;
import gov.nasa.worldwind.render.ImageSource;
import gov.nasa.worldwind.render.ImageTile;
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 ImageSource.Transformer {

/**
* 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);
Location tileOrigin = level.parent.tileOrigin;
double dLat = level.tileDelta / 2;
double dLon = level.tileDelta;

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

double deltaLat = dLat / 90;
double d1 = sector.minLatPercent() + deltaLat * firstRow;
for (int row = firstRow; row <= lastRow; row++) {
double d2 = d1 + deltaLat;
double t1 = tileOrigin.longitude + (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 transform(Bitmap bitmap) {
// Re-project mercator tile to equirectangular
Bitmap trans = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
double miny = ((MercatorSector) sector).minLatPercent();
double maxy = ((MercatorSector) sector).maxLatPercent();
for (int y = 0; y < bitmap.getHeight(); y++) {
double sy = 1.0 - y / (double) (bitmap.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 * (bitmap.getHeight() - 1));
for (int x = 0; x < bitmap.getWidth(); x++) {
trans.setPixel(x, y, bitmap.getPixel(x, iy));
}
}
return trans;
}

}
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,45 @@
package gov.nasa.worldwind.layer.mercator;

import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.geom.Location;
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), new Location(-90, -180),
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
Expand Up @@ -5,6 +5,8 @@

package gov.nasa.worldwind.ogc.gpkg;

import android.util.SparseArray;

import gov.nasa.worldwind.geom.Sector;
import gov.nasa.worldwind.render.ImageSource;
import gov.nasa.worldwind.render.ImageTile;
Expand Down Expand Up @@ -40,23 +42,24 @@ public Tile createTile(Sector sector, Level level, int row, int column) {

ImageTile tile = new ImageTile(sector, level, row, column);

String tableName = this.tiles.getTableName();
int zoomLevel = level.levelNumber;

// Attempt to find the GeoPackage tile matrix associated with the WorldWind level. Assumes that the WorldWind
// levels match the GeoPackage tile matrix zoom levels. If there's no match then the GeoPackage contains no
// tiles for this level and this tile has no image source.
GeoPackage geoPackage = this.tiles.getContainer();
GpkgTileMatrix tileMatrix = geoPackage.getTileMatrix(tableName).get(zoomLevel);
String tableName = this.tiles.getTableName();
SparseArray<GpkgTileMatrix> tileMatrixByZoomLevel = geoPackage.getTileMatrix(tableName);
GpkgTileUserMetrics tileUserMetrics = geoPackage.getTileUserMetrics(tableName);

// Attempt to find the GeoPackage tile matrix associated with the WorldWind level.
int zoomLevel = level.levelNumber + tileMatrixByZoomLevel.keyAt(0);
GpkgTileMatrix tileMatrix = tileMatrixByZoomLevel.get(zoomLevel);

// Check if content table has any tiles on this zoom level.
if (tileMatrix != null && tileUserMetrics.hasZoomLevel(zoomLevel)) {
// Convert the WorldWind tile address to the equivalent GeoPackage tile address. Assumes that the World
// Wind level set matchs the GeoPackage tile matrix set, with the exception of tile rows which are inverted.
int gpkgRow = tileMatrix.getMatrixHeight() - row - 1;
// Configure the tile with a bitmap factory that reads directly from the GeoPackage.
ImageSource.BitmapFactory bitmapFactory = new GpkgBitmapFactory(this.tiles, zoomLevel, column, gpkgRow);
tile.setImageSource(ImageSource.fromBitmapFactory(bitmapFactory));
// Convert the WorldWind tile row to the equivalent GeoPackage tile row.
int gpkgRow = level.levelHeight / level.tileHeight - row - 1;
if (column < tileMatrix.getMatrixWidth() && gpkgRow < tileMatrix.getMatrixHeight()) {
// Configure the tile with a bitmap factory that reads directly from the GeoPackage.
ImageSource.BitmapFactory bitmapFactory = new GpkgBitmapFactory(this.tiles, zoomLevel, column, gpkgRow);
tile.setImageSource(ImageSource.fromBitmapFactory(bitmapFactory));
}
}

return tile;
Expand Down
Loading