ngageoint/geopackage-android-map

Get a preview bitmap of a feature

Closed this issue · 5 comments

I want to scan the external card for GPKG files and get like a thumbnail image of the features inside them as a Bitmap. I am trying to use featureTiles.drawTileQueryAll but to no luck. The returned bitmap is NULL.

The GPKG contains a single feature with a simple polygon, created in QGIS3. The application founds the file, opens it (I can find the feature) but drawTile or drawTileQueryAll return nothing. Is this possible at this stage or is my code wrong somewhere?

private GeoPackage geoPackage = null;

public GpkgMapReader(Context context, String location) {
    GeoPackageManager manager = GeoPackageFactory.getManager(context);
    // Import database
    File db = new File(location);
    if (manager.exists(db.getName())) {
        geoPackage = manager.open(db.getName(),false);
    } else {
        boolean imported = manager.importGeoPackageAsExternalLink(location, db.getName());
        if (imported) {
            geoPackage = manager.open(db.getName());
        }
    }
}

public Bitmap getThumbnail() {
    if (geoPackage != null) {
        List<String> features = geoPackage.getFeatureTables();

        if (features.size() > 0) {
            String featureTable = features.get(0);
            FeatureDao featureDao = geoPackage.getFeatureDao(featureTable);

            FeatureIndexManager indexer = new FeatureIndexManager(context, geoPackage, featureDao);
            indexer.setIndexLocation(FeatureIndexType.GEOPACKAGE);

            FeatureTiles featureTiles = new DefaultFeatureTiles(context, featureDao);
            featureTiles.setMaxFeaturesPerTile(null);
            featureTiles.setIndexManager(indexer);

            return featureTiles.drawTileQueryAll(0, 0, 16);
        }
    }
}

Drawing a tile a x=0, y=0, and zoom of 16 would be a tile in the arctic ocean. Requesting 0,0,0 would give you the entire world, which may be hard to see your features.

The following is an example if you wanted to dynamically draw some zoomed in tile views of the features. I quickly tested it, but you can change it as needed. If the features are not indexed, retrieving the bounding box can be slow. If there are a lot of features, drawing a single tile can also be very slow w/o a limit.

boolean manual = true; // when true, manually calculate non indexed features bounding box
                       // when false, no image created for non indexed features
boolean zoomOut = true; // when true, zoom out to the first XYZ tile that includes all features
                        // when false, creates a proportionate image at current zoom

Projection webMercator = ProjectionFactory.getProjection(ProjectionConstants.EPSG_WEB_MERCATOR);

List<String> features = geoPackage.getFeatureTables();
for(String featureTable: features){

    Bitmap bitmap = null;

    BoundingBox boundingBox = geoPackage.getBoundingBox(webMercator, featureTable, manual);
    if(boundingBox != null) {

        int tileWidth = TileUtils.TILE_PIXELS_HIGH;
        int tileHeight = tileWidth;

        int zoom = TileBoundingBoxUtils.getZoomLevel(boundingBox);
        TileGrid tileGrid = TileBoundingBoxUtils.getTileGrid(boundingBox, zoom);

        long width = tileGrid.getMaxX() - tileGrid.getMinX() + 1;
        long height = tileGrid.getMaxY() - tileGrid.getMinY() + 1;

        if (zoomOut) {
            while (width > 1 || height > 1) {
                int zoomLevels = (int) Math.ceil(Math.log(Math.max(width, height)) / Math.log(2));
                tileGrid = TileBoundingBoxUtils.tileGridZoomDecrease(tileGrid, zoomLevels);
                zoom -= zoomLevels;
                width = tileGrid.getMaxX() - tileGrid.getMinX() + 1;
                height = tileGrid.getMaxY() - tileGrid.getMinY() + 1;
            }
        } else {
            if (width < height) {
                tileWidth = Math.round((float) width / height * tileHeight);
            } else if (height < width) {
                tileHeight = Math.round((float) height / width * tileWidth);
            }
        }

        BoundingBox expandedBoundingBox = TileBoundingBoxUtils.getWebMercatorBoundingBox(tileGrid, zoom);

        FeatureDao featureDao = geoPackage.getFeatureDao(featureTable);
        FeatureTiles featureTiles = new DefaultFeatureTiles(activity, geoPackage, featureDao);
        try {
            featureTiles.setTileWidth(tileWidth);
            featureTiles.setTileHeight(tileHeight);
            bitmap = featureTiles.drawTile(zoom, expandedBoundingBox, featureDao.query());
        }finally {
            featureTiles.close();
        }

    }

    if(bitmap != null){
        // Do something with the image
    }

}

geoPackage.close();

Here is another option for drawing a tile centered around the features with an edge buffer (10%).

boolean manual = true; // when true, manually calculate non indexed features bounding box
                       // when false, no image created for non indexed features
double bufferPercentage = .10;

Projection webMercator = ProjectionFactory.getProjection(ProjectionConstants.EPSG_WEB_MERCATOR);

List<String> features = geoPackage.getFeatureTables();
for(String featureTable: features){

    Bitmap bitmap = null;

    BoundingBox boundingBox = geoPackage.getBoundingBox(webMercator, featureTable, manual);
    if(boundingBox != null) {

        double lonRange = boundingBox.getMaxLongitude() - boundingBox.getMinLongitude();
        double latRange = boundingBox.getMaxLatitude() - boundingBox.getMinLatitude();
        if(lonRange < latRange){
            double halfDiff  = (latRange - lonRange) / 2.0;
            boundingBox.setMinLongitude(boundingBox.getMinLongitude() - halfDiff);
            boundingBox.setMaxLongitude(boundingBox.getMaxLongitude() + halfDiff);
        }else if(latRange < lonRange) {
            double halfDiff = (lonRange - latRange) / 2.0;
            boundingBox.setMinLatitude(boundingBox.getMinLatitude() - halfDiff);
            boundingBox.setMaxLatitude(boundingBox.getMaxLatitude() + halfDiff);
        }

        double range = Math.max(Math.max(lonRange, latRange), Double.MIN_VALUE);
        double buffer = ((range / (1.0 - (2.0 * bufferPercentage))) - range) / 2.0;
        BoundingBox expandedBoundingBox = new BoundingBox(boundingBox.getMinLongitude() - buffer,
                boundingBox.getMinLatitude() - buffer,
                boundingBox.getMaxLongitude() + buffer,
                boundingBox.getMaxLatitude() + buffer);
        int zoom = TileBoundingBoxUtils.getZoomLevel(expandedBoundingBox);

        FeatureDao featureDao = geoPackage.getFeatureDao(featureTable);
        FeatureTiles featureTiles = new DefaultFeatureTiles(activity, geoPackage, featureDao);
        try {
            bitmap = featureTiles.drawTile(zoom, expandedBoundingBox, featureDao.query());
        }finally {
            featureTiles.close();
        }

    }

    if(bitmap != null){
        // Do something with the image
    }

}

Wow, thanks so much. Now this is something I can start from. I was confused about how X, Y was calculated

@bmalex88

Based upon the second option above, added a FeaturePreview in geopackage-android version 3.5.0 (geopackage-android-map 3.5.0).

Examples:

Thanks so much. I can't wait to test it