SingleTileImageryProvider Result is Black Area on iOS

We provide the ability to display geo-referenced images in our app via the SingleTileImageryProvider. When we view these images using a PC the image appears as expected. But when viewing the images using an iOS device (either iPhone or iPad) the area where the image should display just appears black (see images below).

Here’s a snippet of the code used to load/display the image/layer…

    var wParams = strReturned.split("\n");
    var layers = scene.imageryLayers;
   
    var img = document.createElement('img');
    img.onload = function () { addImageLayer(img); };
    img.src = baseURL + fileName + urlEXT;
   
    function addImageLayer(img) {
   
        // Determine bounds based on image size and data from world file
        var west  = parseFloat(wParams[4]);
        var south = parseFloat(wParams[5]) + (img.height * parseFloat(wParams[3]));
        var east  = parseFloat(wParams[4]) + (img.width * parseFloat(wParams[0]));
        var north = parseFloat(wParams[5]);
        var imgRect = Cesium.Rectangle.fromDegrees(west, south, east, north);

        // Adjust bounds to use for centering view
        west  = west - 0.15;
        south = south - 0.15;
        east  = east + 0.15;
        north = north + 0.15;
        var viewRect = Cesium.Rectangle.fromDegrees(west, south, east, north);
       
        var newLayer = layers.addImageryProvider(new Cesium.SingleTileImageryProvider({
                                  url : baseURL + fileName + urlEXT,
                                  rectangle : imgRect
        }));
        newLayer.alpha = dblOpacity;
        hmOverlays[url] = newLayer;
       
        // Center view on overlay
        if (blnCenterOnImage) {
            scene.camera.setView({destination: viewRect});
        }

``

The wParams variable is the string containing the contents read from the world file associated with the geo-referenced image (pgw for PNGs, jgw for JPGs). I use the world file info to define the rectangle used for the SingleTileImageryProvider. I then increase the size of the rectangle to use for setting the view.

As I mentioned this works perfectly when using a PC, but when using an iOS device the area covered by the rectangle (imgRect) is black.

Here’s a screenshot from a PC…

And here’s a screenshot from an iPad…

I’ve searched the forum, but I haven’t found anything that seems relevant to this issue. Any thoughts/suggestions would be appreciated!

Thanks,

Rob

Hi Rob,

I don’t think this is an issue with Cesium, but rather with the onload function in these lines:

var img = document.createElement(‘img’);
img.onload = function () { addImageLayer(img); };

img.src = baseURL + fileName + urlEXT;

Try a little debugging and make sure the onload handler is being called (sometimes it won’t be triggered if the image is cached), and if the img object you’re passing as a parameter to the handler has the values you are expecting.

If that doesn’t work, it’s most likely a bug. Let me know and I will open an issue in the Cesium GitHub repo for you.

Thanks!

Gabby

Hi Gabby,

Thanks for the response! I updated the code so that a unique id is generated and tacked onto the end of the URL of the image to prevent caching issues. However, running the site after this change produces the same results… works as expected on a PC but gets a black layer on iOS device.

We control the image files (they are not uploaded by random users), so I’m confident that the files contain the data we’re expecting.

I’m not familiar with a good way to debug this on an iOS device. I work on a PC. Do you know of a good way to do this? I’m looking into something called jsconsole (https://jsconsole.com/) in hopes that I can remotely debug an iOS device.

I’ll let you know if I’m successful in coming up with a way to debug.

Thanks again for the response,

Rob

Hi Gabby,

I was able to do some debugging on an iPad (using Firebug Lite) to confirm that the onload handler is being called and all code is executing as expected. My code executes all the way through to the end without throwing any errors. And the black rectangle that gets displayed on the iOS devices appears to be the size and location of the image that I’m trying to display as a single tile layer.

I can’t say for sure if this is a bug in Cesium. But my code does seem to be executing as expected (with unexpected results on iOS devices). The same code displays the image properly when run from a PC.

Here’s a full sample that I was hoping could be run in Sandcastle. However, I’m having problems with CORS. I have placed the image and world files being used in this example code in a location where the CORS headers have been added to the server. But for some reason I still cannot make a successful XHR call for the world file.

var viewer = new Cesium.Viewer(‘cesiumContainer’, {
animation: false,
baseLayerPicker: false,
fullscreenButton: false,
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: false,
selectionIndicator: false,
timeline: false,
navigationHelpButton: false,
navigationInstructionsInitiallyVisible: false,
scene3DOnly: true
});

var imgURL = “http://html.airportnetwork.com/temp/ksfo_cityBoundaries.png”;

loadImage(imgURL, 0.7, true);

function loadImage(url, dblOpacity, blnCenterOnImage) {

var callback = function(strReturned, callbackURL) {

    if (!strReturned) {
        return;
    }
   
    var baseURL = callbackURL.substring(0,callbackURL.lastIndexOf("/")+1);
    var fileName = callbackURL.substring(callbackURL.lastIndexOf("/")+1, callbackURL.lastIndexOf("."));
    var ext = callbackURL.substring(callbackURL.lastIndexOf(".")+1);
   
    var urlEXT;
    if (ext.toLowerCase() === "pgw") {
        urlEXT = ".png";
    }
    else if (ext.toLowerCase() === "jgw") {
        urlEXT = ".jpg";
    }
    else {
        return;
    }
   
    var wParams = strReturned.split("\n");

// console.log("West = " + wParams[4]);
// console.log("Units/Px LON = " + wParams[3]);
// console.log("Units/Px LAT = " + wParams[0]);
// console.log("North = " + wParams[5]);

    var layers = viewer.scene.imageryLayers;
   
    var ver =  "?ver=" + generateRandomID();
   
    var img = document.createElement('img');
    img.onload = function () { addImageLayer(img, ver); };
    img.src = baseURL + fileName + urlEXT + ver;
   
    function addImageLayer(img, ver) {

// debug("Image source = " + img.src);

        // Determine bounds based on image size and data from world file
        var west  = parseFloat(wParams[4]);
        var south = parseFloat(wParams[5]) + (img.height * parseFloat(wParams[3]));
        var east  = parseFloat(wParams[4]) + (img.width * parseFloat(wParams[0]));
        var north = parseFloat(wParams[5]);

// console.log(" ");
// console.log("West = " + west);
// console.log("South = " + south);
// console.log("East = " + east);
// console.log("North = " + north);
var imgRect = Cesium.Rectangle.fromDegrees(west, south, east, north);

        // Adjust bounds to use for centering view
        west  = west - 0.15;
        south = south - 0.15;
        east  = east + 0.15;
        north = north + 0.15;
        var viewRect = Cesium.Rectangle.fromDegrees(west, south, east, north);
       
        var newLayer = layers.addImageryProvider(new Cesium.SingleTileImageryProvider({
                                  url : baseURL + fileName + urlEXT + ver,
                                  rectangle : imgRect
        }));
        newLayer.alpha = dblOpacity;
       
        // Center view on overlay
        if (blnCenterOnImage) {
            viewer.scene.camera.setView({destination: viewRect});
        }
       
    }

};

var baseURL = url.substring(0,url.lastIndexOf("/")+1);
var fileName = url.substring(url.lastIndexOf("/")+1, url.lastIndexOf("."));
var ext = url.substring(url.lastIndexOf(".")+1);

var urlEXT;
if (ext.toLowerCase() === "png") {
    urlEXT = ".pgw";
}
else if (ext.toLowerCase() === "jpg") {
    urlEXT = ".jgw";
}
else {
    return;
}

return loadWithXHR(baseURL + fileName + urlEXT, callback);

}

function generateRandomID() {
var text = “”;
var possible = “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789”;

for (var i=0; i<5; i++ ) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
}

return text;

}

function loadWithXHR(url, callback, blnVersion, blnAsync, headerParam, thruParam, blnRetry) {
if(typeof blnVersion === “undefined” ) {blnVersion = true;}
if(typeof blnAsync === “undefined” ) {blnAsync = true;}
if(typeof headerParam === “undefined” ) {headerParam = null;}
if(typeof thruParam === “undefined” ) {thruParam = null;}
if(typeof blnRetry === “undefined” ) {blnRetry = false;}

var xmlhttp;
if (window.XMLHttpRequest) {
    xmlhttp = new XMLHttpRequest();
}
else {
    return;
}

if (blnVersion) {
    if ( (url.toUpperCase().indexOf("WEBDATA.") < 0) &&
         (url.toUpperCase().indexOf(".PNG")     < 0) &&
         (url.toUpperCase().indexOf(".PGW")     < 0) &&
         (url.toUpperCase().indexOf(".JPG")     < 0) &&
         (url.toUpperCase().indexOf(".JGW")     < 0) ) {

        url = url + "?ver=" + generateRandomID();
    }
}

xmlhttp.open("GET", url, blnAsync);

if (headerParam !== null) {
    xmlhttp.setRequestHeader("Authorization", "Bearer " + headerParam);
}

xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState === 4  ) {
        if( xmlhttp.status === 200 ) {

            var string = xmlhttp.responseText;
            try {
                if (string.charAt(string.length-1) === '\0') {
                    string = string.substring(0,string.length-1);
                }
            }
            catch(ex) {}

            callback(string, url, thruParam);
        }
    }        
};
xmlhttp.onloadend = function() {
    if(xmlhttp.status === 404) {
        console.log("File Not Found in loadWithXHR.");
        callback(null);
    }  
    if(xmlhttp.status === 500) {
        console.log("Internal server error in loadWithXHR.");
        if (blnRetry) {
            console.log("Retrying loadWithXHR.");
            loadWithXHR(url, callback, blnVersion, blnAsync, headerParam, thruParam, false);
        }
        else {
            callback(null);
        }
    }  
};
xmlhttp.onerror = function() {
    console.log("Error in loadWithXHR.  Status = " + xmlhttp.status);
    if (blnRetry) {
        console.log("Retrying loadWithXHR.");
        loadWithXHR(url, callback, blnVersion, blnAsync, headerParam, thruParam, false);
    }
    else {
        callback(null);
    }
};
xmlhttp.send(null);
return xmlhttp;

}

``

I’ve attached the 2 files (the image and the world file) so that you can run the code locally. There is nothing special about these files. The symptoms are the same with many different files that work fine on a PC but display as a black rectangle on an iOS device.

Any help with this would be greatly appreciated!

Thanks,

Rob

ksfo_cityBoundaries.pgw (150 Bytes)

Looks like your image is fairly large. Try going to

http://webglreport.com/

on the iOS device and check the max texture size. It's probably smaller
than the dimensions of the image which will cause the texture creation to
fail. This is why most imagery is tiled, so that each tile can fit in a
texture.

Scott,

Thank you very much for the response! You were dead on. The mobile iOS devices I’ve been testing with have max texture size of 4096, and the images have been over that size. We tested a smaller image, and it showed up without issue on the iOS devices.

Thanks again for the help!

Rob