1

I am trying to load tiles from a geopackage (rivers.gpkg sample) using the latest version of OpenLayers3 and the GeoPackage JS library (Project-Link). My current try looks like this:

Wrong tile handling

As you can see the tile is not drawn correctly. The river Thames is displayed multiple times over the current view (where other rivers should be visible too). Also the position of the tiles change if I zoom out:

zoom out

There is also a demo page where you can see the working rivers.gpkg example (using Leaflet):

Working tile handling

My guess is that I am doing something wrong with the tile source (ol.source.XYZ). Unfortunately I am not very experienced how to handle tiles with GeoPackage JS and OpenLayers3. This is my current code (essential Javascript part):

var defaultZoomLevel = 6;

// standard OSM layer
var standardWebOSM = new ol.layer.Tile({
    source: new ol.source.OSM(),
    projection: 'EPSG:3857'
});

// map
var map = new ol.Map({
  target: 'map',
  layers: [standardWebOSM],
  view: new ol.View({
    center: ol.proj.transform([-2.15, 52.267], 'EPSG:4326', 'EPSG:3857'),
    zoom: defaultZoomLevel
  })
});

// load geopackage (rivers example)
loadGeopackage('http://ngageoint.github.io/GeoPackage/examples/rivers.gpkg');

// function to load the geopackage using xhr
function loadGeopackage(filepath){
    var xhr = new XMLHttpRequest();
    xhr.open("GET", filepath, true);
    xhr.responseType = "arraybuffer";
    xhr.onload = function (oEvent) {
      var arrayBuffer = xhr.response; 
      if (arrayBuffer) {
        var byteArray = new Uint8Array(arrayBuffer);
        // 
        loadByteArray(byteArray);
      }
    };
    xhr.send();
}

// handle geopackage (changed from documentation - https://github.com/ngageoint/geopackage-js)
function loadByteArray(array, callback) {
  var db = new SQL.Database(array);
  GeoPackageConnection.connectWithDatabase(db, function(err, connection) {
    var geoPackage = new GeoPackage('', '', connection);
    getTilesFromTable(geoPackage, "rivers_tiles", defaultZoomLevel);
  });
}

// function to get tiles from table
function getTilesFromTable(gpkg, tableName, zoom){
    gpkg.getTileDaoWithTableName(tableName, function(err, tileDao) {
        if (zoom < tileDao.minZoom || zoom > tileDao.maxZoom) {
            console.log('No tiles exist in the GeoPackage for the current bounds and zoom level. Min zoom: ' + tileDao.minZoom + ' Max Zoom: ' + tileDao.maxZoom);
            return;
        }
        // get tileMatrixSet
        var tms = tileDao.tileMatrixSet;
        // get tile matrix
        var tm = tileDao.getTileMatrixWithZoomLevel(zoom);

        // create tile grid
        var tileGrid = ol.tilegrid.createXYZ({
            extent: [tms.min_x, tms.min_y, tms.max_x, tms.max_y], // extent of geopackage content
            maxZoom: tileDao.maxZoom,
            minZoom: tileDao.minZoom,
            tileSize: [tm.tile_width, tm.tile_height] // tile size in pixels
        });

        // get the center of the map (in epsg 3857)
        var mapCenterCoordinate = map.getView().getCenter();

        // Get the tile coordinate given a map coordinate and zoom level
        var tileCoordinate = tileGrid.getTileCoordForCoordAndZ(mapCenterCoordinate, zoom);

        // create tile retriever
        var gpr = new GeoPackageTileRetriever(tileDao, tm.tile_width, tm.tile_height);

        // Get a tile base64 data URL for a XYZ tile
        var tileX = tileCoordinate[1];
        var tileY = -tileCoordinate[2]-1;

        // get a tile base64 data URL for an XYZ tile
        gpr.getTile(tileX, tileY, zoom, function(err, tileBase64DataURL) {
            if(err){
                console.log("Error while trying to get tile -> " + err);
            }

            // create a tile source that uses the geopackage tile
            var tileSource = new ol.source.XYZ({
                url: tileBase64DataURL,
                tileGrid: tileGrid
            });

            // a vector layer to render the source
            var tileLayer = new ol.layer.Tile({
                source: tileSource
            });

            // add layer to map
            map.addLayer(tileLayer);
        })
    });
}

Any idea what I am doing wrong?

EDIT: I adjusted the code according to the answer by ahocevar. The code looks like this (adjusted getTilesFromTable function):

function getTilesFromTable(gpkg, tableName, zoom){
    gpkg.getTileDaoWithTableName(tableName, function(err, tileDao) {
        if (zoom < tileDao.minZoom || zoom > tileDao.maxZoom) {
            console.log('No tiles exist in the GeoPackage for the current bounds and zoom level. Min zoom: ' + tileDao.minZoom + ' Max Zoom: ' + tileDao.maxZoom);
            return;
        }
        // get tileMatrixSet
        var tms = tileDao.tileMatrixSet;
        // get tile matrix
        var tm = tileDao.getTileMatrixWithZoomLevel(zoom);

        // create tile grid
        var tileGrid = ol.tilegrid.createXYZ({
            extent: [tms.min_x, tms.min_y, tms.max_x, tms.max_y], // extent of geopackage content
            maxZoom: tileDao.maxZoom,
            minZoom: tileDao.minZoom,
            tileSize: [tm.tile_width, tm.tile_height] // tile size in pixels
        });

        // create tile retriever
        var gpr = new GeoPackageTileRetriever(tileDao, tm.tile_width, tm.tile_height);

        // setup tile layer
        var tileLayer = new ol.layer.Tile({
          source: new ol.source.XYZ({
            tileUrlFunction: function(tileCoord) {
              // create a simplified url for use in the tileLoadFunction
              return tileCoord.toString();
            },
            tileLoadFunction: function(tile, url) {
              var tileCoord = url.split(',');
              var tileX = tileCoord[1];
              var tileY = -tileCoord[2]-1;
              var tileZ = tileCoord[0];
              gpr.getTile(tileX, tileY, tileZ, function(err, tileBase64DataURL) {
                tile.getImage().src = tileBase64DataURL;
              });
            },
            // other source config options from your snippet here, e.g. tileGrid
            tileGrid: tileGrid
          })
        });

        // add layer to map
        map.addLayer(tileLayer);                    
    });
}

UPDATE: I made the following observations concerning the zoom levels:

Screenshot zoomlevel 0 (only one working properly):

zoom0

Screenshot zoomlevel 1: Zoom 1

Screenshot zoomlevel 2:

enter image description here

It seems that just the first column of the tiles is loaded correct and all the others are compressed horizontally. Any idea how to fix this?

  • You are doing something wrong with the zoom level you pass to gpr.getTile. In the tileUrlFunction, z is the zoom level you should be passing to gpr.getTile, but you are passing zoom. I also don't get why your getTilesFromTable function has a zoom argument. – ahocevar Jul 16 '16 at 19:38
  • All the getTile functions use the getTileWithBounds function which requires the zoom level (github.com/ngageoint/geopackage-js/blob/master/lib/tiles/…). I do not think the zoom level variable is the problem. I am using the zoom level of the map. – Lars Jul 17 '16 at 9:55
  • It will help a great deal if you provide a JSFiddle to test your code live. – ahocevar Jul 18 '16 at 11:57
3

Instead of creating a tile layer inside your getTile callback, you'll have to create an ol.layer.Tile with an ol.source.XYZ and a custom tileUrlFunction and tileLoadFunction:

new ol.layer.Tile({
  source: new ol.source.XYZ({
    tileUrlFunction: function(tileCoord) {
      // create a simplified url for use in the tileLoadFunction
      return tileCoord.toString();
    },
    tileLoadFunction: function(tile, url) {
      var tileCoord = url.split(',');
      // var tileX = tileCoord[1]; - this did not work because the value is a String
      // instead the coordinate value has to be parsed to an integer!
      var tileX = parseInt(tileCoord[1]);
      var tileY = -tileCoord[2] - 1;

      gpr.getTile(tileX, tileY, zoom, function(err, tileBase64DataURL) {
        tile.getImage().src = tileBase64DataURL;
      });
    }
    // other source config options from your snippet here, e.g. tileGrid
  })
});

EDIT: It was necessary to parse the value of tileCoord[1] to an integer!

var tileX = parseInt(tileCoord[1]);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.