Changeset fcc3a96


Ignore:
Timestamp:
01/05/17 22:09:48 (4 months ago)
Author:
Martin Kolman <martin.kolman@…>
Branches:
master
Children:
a525886
Parents:
b6886d4
git-author:
Martin Kolman <martin.kolman@…> (05/16/16 23:07:33)
git-committer:
Martin Kolman <martin.kolman@…> (01/05/17 22:09:48)
Message:

Improved tile display in Qt 5 GUI

The PinchMap? element is was originally based on code from
the Advanced Geocaching Tool for Linux (AGTL) which (miss)used
the QTQuick image case in an ingenious way to display the tiled map.
The viewport was covered by a grid of tiles that was moved when the map
was panned.

If the pan did not uncover any not yet visible tile row or
column the grid was just nudged a bit. If a new row/column became
visible the visible rows/columns just switched image source url (with
one row/column address "falling of" and being replaced by the newly visible
row/column) and nudged the grid back a bit. Like this a constant number of tiles could
effectively simulate a slippy map.

Unfortunately this has drawbacks, some evident and some even
more horrendous that have only been discovered later:

  • the QtQuick? image cache sometimes gets flushed (application don't really have any direct control of it) which manifests as the map flashing due to visible tiles reloading from the tile image provider
  • according to a discussion with woot on IRC (Thanks! :) ) the clever trick with image source url switching not only causes the above mentioned flickering but is also horrendously inefficient as every image source url switch, even if the image is cached, requires new texture upload to the GPU (!!!!!), which is a very bad idea, especially for maps that can have tens of image, possibly in multiple layers

So to fix these issues, the "clever" url swapping trick has been
scrapped. The new solution works like this:

  • there is a ListModel? that track all tiles that should be visible at the moment
  • a central repeater creates tile delegates based on this model
  • each tile delegate is also a repeater with data model based on enabled map layers
  • unlike the previous url swapping technique tiles are now dynamically added and removed from the visible tiles model as the map is panned, zoomed, recentered, etc.
  • also as we now no longer need to care about image cache coherency we can also scrap the old horrendous "tile size notification" system that used tile size as a notification of tile availability and replace it with a more sane system based on callbacks and direct tile-downloaded notifications based on a tile-tracking map

The new way should have these benefits:

  • better performance as tile textures don't need to be reuploaded to CPU all the time
  • no loaded tile flickering
  • more sane asynchronous tile loading
Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • modules/gui_modules/gui_qt5/gui_qt5.py

    reeb0e52 rfcc3a96  
    329329        return layer, z, x, y 
    330330 
    331     def addTileDownloadRequest(self, tileId): 
     331    def areTilesAvailable(self, tile_ids): 
     332        """Report if tiles are available & request download for those that are not. 
     333 
     334        :param list tile_ids: list of tile ids to check 
     335        :return: a distionary of tile states, True = available, False = will be downloaded 
     336        :rtype: dict 
     337        """ 
     338        available_tiles = {} 
     339        for tile_id in tile_ids: 
     340            available_tiles[tile_id] = self.isTileAvailable(tile_id) 
     341        return available_tiles 
     342 
     343    def isTileAvailable(self, tileId): 
     344        """Check if tile is available and add download request if not. 
     345 
     346        NOTE: If automatic tile downloads are disabled tile download 
     347              request will not be queued. 
     348 
     349        :param str tileId: tile identificator 
     350        :return: True if the tile is locally available, False if not 
     351        :rtype: bool 
     352        """ 
     353        lzxy = self._tileId2lzxy(tileId) 
     354        if self.modules.mapTiles.tileInStorage(lzxy): 
     355            return True 
     356        else: 
     357            self._addTileDownloadRequest(lzxy, tileId) 
     358            return False 
     359 
     360 
     361    def _addTileDownloadRequest(self, lzxy, tileId): 
    332362        """Add an asynchronous download request, the tile will be 
    333363        notified once the download is finished or fails 
     
    335365        """ 
    336366        try: 
    337             lzxy = self._tileId2lzxy(tileId) 
    338367            self.modules.mapTiles.addTileDownloadRequest(lzxy, tileId) 
    339368        except Exception: 
     
    549578        pinchMapId = tag.split("/")[0] 
    550579        #log.debug("SENDING: %s %s" % ("tileDownloaded:%s" % pinchMapId, tag)) 
    551         pyotherside.send("tileDownloaded:%s" % pinchMapId, tag, error) 
     580        resoundingSuccess = error == constants.TILE_DOWNLOAD_SUCCESS 
     581        fatalError = error == constants.TILE_DOWNLOAD_ERROR 
     582        pyotherside.send("tileDownloaded:%s" % pinchMapId, tag, resoundingSuccess, fatalError) 
    552583 
    553584    def getImage(self, imageId, requestedSize): 
  • modules/gui_modules/gui_qt5/qml/PinchMap.qml

    r71840b8b rfcc3a96  
    4545    property var searchMarkerModel : null 
    4646 
     47    // a dictionary of tile coordinates that should be current visible 
     48    property var shouldBeOnScreen : {"foo" : true} 
     49 
     50    // tile status check request staging 
     51    property var tileRequests : [] 
     52    property bool tileRequestTimerPause : false 
     53    // Due to the many individual tile status queries triggering a PyOtherSide crash & 
     54    // for general efficiency reasons (it should be more efficient to group the queries 
     55    // and to send them & answer them at once) we group the queries to a batch that is then 
     56    // sent at once. 
     57    // 
     58    // This is achieved by a timer that is reset every time a new request is added, 
     59    // which effectively batches all requests coming inside the given timer interval 
     60    // (currently 10 ms) & sends them once no request comes for 10 ms. 
     61    // NOTE: This could theoretically fail horribly if a request came *at least* 
     62    //       once every 10 seconds, so requests would continuously accumulate 
     63    //       and would never be sent. This is however almost guaranteed to never 
     64    //       happen due to the modRana screen update logic. 
     65    Timer { 
     66        id : tileRequestTimer 
     67        interval: 10 
     68        running: false 
     69        repeat: false 
     70        property bool paused : false 
     71        onTriggered : { 
     72            var requestBatch = pinchmap.tileRequests.slice() 
     73            pinchmap.tileRequests = [] 
     74            rWin.python.call("modrana.gui.areTilesAvailable", [requestBatch], tilesAvailabilityCB) 
     75            running = false 
     76        } 
     77    } 
     78 
     79 
     80 
    4781    // use mapnik as the default map layer 
    4882    property var layers :  ListModel { 
     
    72106    signal clearPointMenus 
    73107    property bool needsUpdate: false 
     108 
     109    property var tilesModel : ListModel {} 
     110    property var currentTiles : new Object() 
    74111 
    75112    // if the map is clicked or double clicked the mapClicked and mapDoubleClicked signals 
     
    100137    Component.onCompleted: { 
    101138        rWin.python.setHandler("tileDownloaded:" + pinchmap.name, pinchmap.tileDownloadedCB) 
    102     } 
    103  
    104     function tileDownloadedCB(tileId, tileError) { 
    105         // trigger the tile downloaded signal 
    106         // TODO: could the signal by called directly as the callback ? 
    107         //console.log("PINCH MAP: TILE DOWNLOADED: " + tileId) 
    108         tileDownloaded(tileId, tileError) 
     139        // instantiate the nested backing data model for tiles 
     140        updateTilesModel(pinchmap.cornerTileX, pinchmap.cornerTileY, 
     141                         pinchmap.numTilesX, pinchmap.numTilesY) 
     142    } 
     143 
     144    function updateTilesModel(cornerX, cornerY, tilesX, tilesY) { 
     145        // Update the tiles data model to the given corner tile x/y 
     146        // and horizontal anv vertical tile number. 
     147        // This basically amounts to newly enumerating the tiles 
     148        // while keeping items already in the lists so that the corresponding 
     149        // delegates are not needlessly re-rendered. 
     150        // TODO: move this to a worker script and do it asynchronously ? 
     151 
     152        tileRequestTimer.stop() 
     153        tileRequestTimerPause = true 
     154        var maxCornerX = cornerX + tilesX - 1 
     155        var maxCornerY = cornerY + tilesY - 1 
     156 
     157        /* 
     158        rWin.log.debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") 
     159        rWin.log.debug("UPDATE TILES MODEL") 
     160        rWin.log.debug("COUNT: " + pinchmap.tilesModel.count) 
     161        rWin.log.debug("cornerX: " + cornerX) 
     162        rWin.log.debug("cornerY: " + cornerY) 
     163        rWin.log.debug("tilesX: " + tilesX) 
     164        rWin.log.debug("tilesY: " + tilesY) 
     165        rWin.log.debug("maxCornerX: " + maxCornerX) 
     166        rWin.log.debug("maxCornerY: " + maxCornerY) 
     167        rWin.log.debug("INITIAL COUNT: " + pinchmap.tilesModel.count) 
     168        */ 
     169 
     170        // tiles that should be on the screen due to the new coordinate update 
     171        var newScreenContent = {} 
     172        // tiles that need to be added (eq. were not displayed before the coordinate 
     173        // were updated) 
     174        var newTiles = [] 
     175        // find what new tiles are needed 
     176        //var newSCCount = 0 
     177        //var newTilesCount = 0 
     178        for (var cx = cornerX; cx<=maxCornerX; cx++) { 
     179            for (var cy = cornerY; cy<=maxCornerY; cy++) { 
     180                var tileId = cx + "/" + cy 
     181                newScreenContent[tileId] = true 
     182                //newSCCount++ 
     183                if (!(tileId in pinchmap.shouldBeOnScreen)) { 
     184                    // this a new tile that was not on screen before the coordinate update 
     185                    newTiles.push({"x" : cx, "y" : cy, "id" : tileId}) 
     186                    //newTilesCount++ 
     187                } 
     188            } 
     189        } 
     190 
     191        // update the pinchmap-wide "what should be on screen" dict 
     192        pinchmap.shouldBeOnScreen = newScreenContent 
     193 
     194        // go over all tiles in the tilesModel list model that is used to generate the tile delegates 
     195        // - if the tile is in newScreenContent do nothing - the tile is still visible 
     196        // - if the tile is not in newScreenContent it should no longer be visible, there are two options in this case: 
     197        //   1) if newTiles is non-empty, pop a tile from it and reset coordinates for the tile, effectively reusing 
     198        //      the tile item & it's delegate instead of destroying it and creating a new one 
     199        //   2) if newTiles is empty just remove the item, which also destroys the delegate 
     200 
     201        //var iterations = 0 
     202        //var removed = 0 
     203        //var recycledCount = 0 
     204 
     205        for (var i=0;i<pinchmap.tilesModel.count;i++){ 
     206            //iterations++ 
     207            var tile = pinchmap.tilesModel.get(i) 
     208            if (tile != null) { 
     209                if (!(tile.tile_coords.id in newScreenContent)) { 
     210                    // check if we can recycle this tile by recycling it into one 
     211                    // of the new tiles that should be displayed 
     212                    var newTile = newTiles.pop() 
     213                    //rWin.log.debug("RECYCLING: " + tile.tile_coords.id + " to " + newTile.id) 
     214                    if (newTile) { 
     215                        //recycledCount++ 
     216                        // recycle the tile by setting the coordinates to values for a new tile 
     217                        pinchmap.tilesModel.set(i, {"tile_coords" : newTile}) 
     218                    } else { 
     219                        // no tiles to recycle into, so just remove the tile 
     220                        pinchmap.tilesModel.remove(i) 
     221                        i-- 
     222                        //rWin.log.debug("REMOVING: " + tile.tile_coords) 
     223                        //removed++ 
     224                    } 
     225                } 
     226            } 
     227        } 
     228 
     229        // Add any items remaining in newTiles to the tilesModel, this usually means: 
     230        // - this is the first run and the tilesModel is empty 
     231        // - the viewport has been enlarged and more tiles in total are now visible than before 
     232        // If no new tiles are added to the tilesMode, it usually means that the viewport is the 
     233        // same (all tiles are recycled) or has even been shrunk. 
     234        //var tilesAdded = 0 
     235        for (var i=0; i < newTiles.length; i++){ 
     236            newTile = newTiles[i] 
     237            pinchmap.tilesModel.append({"tile_coords" : newTile}) 
     238            //tilesAdded++ 
     239        } 
     240 
     241        tileRequestTimerPause = false 
     242        tileRequestTimer.start() 
     243        /* 
     244        rWin.log.debug("NEW SCREEN CONTENT: " + newSCCount) 
     245        rWin.log.debug("NEW TILES: " + newTilesCount) 
     246        rWin.log.debug("ITERATIONS: " + iterations) 
     247        rWin.log.debug("REMOVED: " + removed) 
     248        rWin.log.debug("RECYCLED: " + recycledCount) 
     249        rWin.log.debug("ADDED: " + tilesAdded) 
     250        rWin.log.debug("UPDATE TILES MODEL DONE") 
     251        rWin.log.debug("TILE MODEL COUNT: " + pinchmap.tilesModel.count) 
     252        rWin.log.debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") 
     253        */ 
     254    } 
     255 
     256    function tilesAvailabilityCB(tileAvailabilityDict) { 
     257        for (var tileId in tileAvailabilityDict) { 
     258            var tileAvailable = tileAvailabilityDict[tileId] 
     259            var tile = pinchmap.currentTiles[tileId] 
     260            if (tile) { 
     261                tile.available = tileAvailable 
     262            } 
     263        } 
     264    } 
     265 
     266    function tileDownloadedCB(tileId, resoundingSuccess, fatalError) { 
     267        // notify tile delegates waiting for tile data to be available 
     268        var tile = pinchmap.currentTiles[tileId] 
     269        if (tile) { 
     270            tile.tileDownloaded([resoundingSuccess, fatalError]) 
     271        } 
    109272    } 
    110273 
     
    145308    } 
    146309 
     310    onCornerTileXChanged: { 
     311        updateTilesModel(pinchmap.cornerTileX, pinchmap.cornerTileY, 
     312                         pinchmap.numTilesX, pinchmap.numTilesY) 
     313    } 
     314 
     315    onCornerTileYChanged: { 
     316        updateTilesModel(pinchmap.cornerTileX, pinchmap.cornerTileY, 
     317                         pinchmap.numTilesX, pinchmap.numTilesY) 
     318    } 
    147319 
    148320    function setZoomLevel(z) { 
     
    331503 
    332504    function deg2num(lat, lon) { 
     505        // lat/lon to tile x/y 
    333506        var rad = deg2rad(lat % 90); 
    334507        var n = maxTileNo + 1; 
     
    349522        map.offsetY = -(cornerTileFloatY - Math.floor(cornerTileFloatY)) * tileSize; 
    350523        updateCenter(); 
     524        updateTilesModel(pinchmap.cornerTileX, pinchmap.cornerTileY, 
     525                         pinchmap.numTilesX, pinchmap.numTilesY) 
    351526    } 
    352527 
     
    356531 
    357532    function setCenterLatLon(lat, lon) { 
    358         setLatLon(lat, lon, pinchmap.width/2, pinchmap.height/2); 
    359         centerSet(); 
     533        setLatLon(lat, lon, pinchmap.width/2, pinchmap.height/2) 
     534        centerSet() 
    360535    } 
    361536 
     
    407582    function tileUrl(tileId) { 
    408583        return "image://python/tile/" + tileId 
     584    } 
     585 
     586    function isTileAvailable(tileId, callback) { 
     587        // check if the tile is available from local storage 
     588        // TODO: make this Python independent 
     589        pinchmap.tileRequests.push(tileId) 
     590        // In some cases where a lot of tiles are checked at once 
     591        // (screen panning or zooming, etc.) we want to batch these 
     592        // check requests as effectively as possible, so we "pause" 
     593        // the timer and "unpause" it once done. 
     594        // So no need to bother the timer until it is unpaused. 
     595        if (!tileRequestTimerPause) { 
     596            tileRequestTimer.restart() 
     597        } 
     598    } 
     599 
     600    function downloadTile(tileId) { 
     601        // download the tile 
     602        // TODO: make this Python independent 
     603        rWin.python.call("modrana.gui.addTileDownloadRequest", [tileId], function(){}) 
    409604    } 
    410605 
     
    515710    } 
    516711 
    517     Grid { 
     712    Item { 
    518713        id: map; 
    519         columns: numTilesX; 
    520714        width: numTilesX * tileSize; 
    521715        height: numTilesY * tileSize; 
     
    524718        property int offsetX: 0; 
    525719        property int offsetY: 0; 
    526         x: rootX + offsetX; 
    527         y: rootY + offsetY; 
    528  
     720        x: 0 
     721        y: 0 
    529722        Repeater { 
    530             id: tiles 
    531             model: pinchmap.layersReady ? pinchmap.numTilesX * pinchmap.numTilesY : null 
     723            id: tilesX 
     724 
     725            model : pinchmap.tilesModel 
     726 
    532727            Rectangle { 
    533728                id: tile 
    534                 property alias source: imgs.source; 
    535                 property int tileX: cornerTileX + (index % numTilesX) 
    536                 property int tileY: cornerTileY + Math.floor(index / numTilesX) 
     729 
     730                // tileID is a "<map x>/<map y>" string 
     731                property string tileID : "" 
     732                // Basically alias for the tile_coords field from the model 
     733                // so that we can bind to it & react changes. 
     734                property var tileCoords : tile_coords 
     735                // current map coordinates of the tile (upper left corner) 
     736                property int tileX: 0 
     737                property int tileY: 0 
     738 
     739                onTileCoordsChanged : { 
     740                    // The idea is simple - we need to first change the tileID before changing 
     741                    // the tile coordinates, otherwise there will be intermittent artifacts 
     742                    // visible on the map when a tile is recycled in the tile model. 
     743                    // 
     744                    // The tileID change switches the tile to the no-image state (and starts tile 
     745                    // image lookup), effectively blanking the tile. Only after this is done we can update the 
     746                    // screen coordinates of the tile, to avoid artifacts. 
     747 
     748                    // so first step - update tileID 
     749                    tileID = tileCoords.id 
     750                    // second step - trigger screen coordinate update 
     751                    tileX = tileCoords.x 
     752                    tileY = tileCoords.y 
     753                } 
     754 
     755                // screen coordinates of the tile (upper left corner) 
     756                x : ((tileX - pinchmap.cornerTileX) * tile.width) + map.offsetX 
     757                y : ((tileY - pinchmap.cornerTileY) * tile.height) + map.offsetY 
     758 
    537759                property int ind : index 
    538760 
    539                 width: tileSize; 
    540                 height: tileSize; 
    541                 color: "#c0c0c0"; 
    542  
    543                Repeater { 
    544                     id: imgs 
    545                     //property string source: tileUrl(model[0].layerId, tileX, tileY) 
    546                     property string source 
    547                     model: pinchmap.layersReady ? pinchmap.layers : null 
     761                width: pinchmap.tileSize; 
     762                height: pinchmap.tileSize; 
     763                border.width : 2 
     764                border.color : "black" 
     765                Text { 
     766                    anchors.horizontalCenter : parent.horizontalCenter 
     767                    anchors.verticalCenter : parent.verticalCenter 
     768                    text : tile_coords.x + "/" + tile_coords.y 
     769                    font.pixelSize : 24 
     770                } 
     771 
     772                Repeater { 
     773                    id: tileRepeater 
     774                    model : pinchmap.layers 
    548775                    Tile { 
    549                         id : tile 
    550                         tileSize : tileSize 
     776                        id : tileImage 
     777                        tileSize : pinchmap.tileSize 
    551778                        tileOpacity : layerOpacity 
     779                        zoomLevel : pinchmap.zoomLevel 
    552780                        mapInstance : pinchmap 
    553                         // TODO: move to function 
    554                         tileId: pinchmap.name+"/"+layerId+"/"+pinchmap.zoomLevel+"/"+tileX+"/"+tileY 
    555                         // if tile id changes means that the panning reached a threshold that cased 
    556                         // a top-level X/Y coordinate switch 
    557                         // -> this basically means that all columns or rows switch coordinates and 
    558                         //    one row/column has new coordinates while one column/rwo coordinate 
    559                         //    set is discarded 
    560                         // -> like this, we don't have to instantiate and discard a bazillion of 
    561                         //    tile elements, but instantiate them once and then shuffle them around 
    562                         // -> tiles are instantiated and removed only if either viewport size or layer 
    563                         //    count changes 
    564                         onTileIdChanged: { 
    565                             // so we are now a different tile :) 
    566                             // therefore we need to all the asynchronous tile loading properties back 
    567                             // to the default state and try to load the tile, if it is in the cache 
    568                             // it will be loaded at once or the asynchronous loading process will start 
    569                             // * note that any "old" asynchronous loading process still runs and the 
    570                             //   "new" tile that got the "old" coordinates will get the tile-downloaded 
    571                             //   notification, which is actually what we want :) 
    572                             tile.layerName = layerId 
    573                             tile.error = false 
    574                             tile.cache = true 
    575                             tile.retryCount = 0 
    576                             tile.downloading = false 
    577                             tile.source = tileUrl(tileId) 
    578                         } 
     781                        tileXY : tileID 
     782                        layerId : pinchmap.layers.get(index).layerId 
     783                        layerName : pinchmap.layers.get(index).layerName 
    579784                    } 
    580785                } 
    581786            } 
    582  
    583787        } 
    584788 
  • modules/gui_modules/gui_qt5/qml/Tile.qml

    r0462035 rfcc3a96  
    33import QtQuick 2.0 
    44import UC 1.0 
    5  
    65 
    76Item { 
     
    109    property real tileOpacity : 1.0 
    1110    property alias source : img.source 
    12     property bool waiting : false 
    13     property string tileId : "" 
    14     property alias cache : img.cache 
     11    property var tileXY : "" 
     12    property string tileId : mapInstance.name+"/"+tile.layerId+"/"+pinchmap.zoomLevel+"/"+tileXY 
     13    property string oldTileId : "" 
    1514    property int retryCount : 0 
    1615    property bool error : false 
     16    property string layerId : "" 
    1717    property string layerName : "" 
    1818    property bool downloading : false 
    19     property var mapInstance : null 
     19    property bool available : false 
     20    property int zoomLevel : 15 
     21    property var mapInstance 
     22 
     23    onAvailableChanged : { 
     24        // - if available is true the tile is locally available, 
     25        //   which means we can can set the source property for the 
     26        //   tile Image element to that it can be loaded and displayed 
     27        // - if available is false it means that the tile is not 
     28        //   available locally and a tile download request has been 
     29        //   queued (provided automatic tile downloading is enabled) 
     30        // - TODO: sensibly show to the user if automatic tile 
     31        //   download is disabled 
     32 
     33        if (tile.available) { 
     34            // tile is available from local storage - load it at once! :) 
     35            tile.retryCount = 0 
     36            tile.error = false 
     37            tile.downloading = false 
     38            tile.source = tile.mapInstance.tileUrl(tileId) 
     39        } 
     40    } 
     41 
     42    function _clearTile() { 
     43        // clear the tile to a "no tile loaded" state 
     44        // - set image source to "" 
     45        // - set available & downloading to false 
     46        // - clear download errors 
     47        tile.retryCount = 0 
     48        tile.error = false 
     49        tile.downloading = false 
     50        tile.available = false 
     51        tile.source = "" 
     52    } 
     53 
     54    function tileDownloaded(result) { 
     55        // result[0] = success true/false 
     56        // result[1] = fatal error true/false 
     57        if (!result[0]) { 
     58            // something went wrong 
     59            if (result[1]) { 
     60                // fatal error 
     61                tile.downloading = false 
     62                tile.error = true 
     63            } else { 
     64                // non fatal error, increment retry count 
     65                tile.retryCount++ 
     66                // TODO: use a constant ? 
     67                if (tile.retryCount <= 5) { 
     68                    // we are still within the retry count limit, 
     69                    // so trigger another download request 
     70                    mapInstance.isTileAvailable(tile.tileId) 
     71                } else { 
     72                    // retry count limit reached, switch to error state 
     73                    tile.downloading = false 
     74                    tile.error = true 
     75                } 
     76            } 
     77        } else { 
     78            // the file has been apparently successfully downloaded 
     79            // and is available to be loaded and displayed 
     80            tile.available = true 
     81        } 
     82    } 
     83 
     84    Component.onDestruction : { 
     85        // remove tile from tracking 
     86        delete mapInstance.currentTiles[tile.tileId] 
     87    } 
     88 
     89    onZoomLevelChanged : { 
     90        // zoom level changed, we need to reload the tile 
     91        tile._clearTile() 
     92    } 
     93 
     94    onTileIdChanged : { 
     95        // update tile id in the tracking dict to account for the tile id 
     96        if (mapInstance.currentTiles[tile.oldTileId]) { 
     97            delete mapInstance.currentTiles[tile.oldTileId] 
     98        } 
     99        tile.oldTileId = tile.tileId 
     100        // register the file with new tile ID 
     101        mapInstance.currentTiles[tile.tileId] = tile 
     102        tile._clearTile() 
     103        // check if the new tile id is available from local storage 
     104        // (and thus could be loaded at once) or needs to be downloaded 
     105        mapInstance.isTileAvailable(tile.tileId) 
     106    } 
    20107 
    21108    Image { 
    22109        id: img 
    23         cache : true 
    24110        width: tile.tileSize 
    25111        height: tile.tileSize 
    26         opacity: tile.downloading ? 0.0 : tile.tileOpacity 
     112        //opacity: tile.downloading ? 0.0 : tile.tileOpacity 
     113        //opacity: img.source == "" ? 0.0 : tile.tileOpacity 
    27114        asynchronous : true 
    28         onStatusChanged : { 
    29             //console.log("status changed: " + tile.tileId + " " +  img.status + " " + 
    30             //            img.source + " " + img.sourceSize.width) 
    31             if (img.status == Image.Ready) { 
    32                 // check if we got a real image or an info info 
    33                 // pixel telling us the tile was not found locally 
    34                 // and will be downloaded 
    35                 if (img.sourceSize.width == 1) { 
    36                     // info tile, disable caching, clear source, 
    37                     // connect to the tile downloaded signal, 
    38                     // issue a tile download request and then wait 
    39                     // for the tile to be downloaded 
    40                     img.cache = false 
    41                     if (!tile.downloading) { 
    42                         tile.downloading = true 
    43                         rWin.python.call("modrana.gui.addTileDownloadRequest", [tile.tileId], function(){}) 
    44                     } 
    45                 } else { 
    46                     tile.downloading = false 
    47                 } 
    48             } 
    49         } 
    50     } 
    51     // connection to the map instance for for tile-downloaded notifications 
    52     Connections { 
    53         target: tile.downloading ? tile.mapInstance : null 
    54         onTileDownloaded: { 
    55             // is this us ? 
    56             if (tile.downloading && loadedTileId == tile.tileId) { 
    57                 //console.log("THIS TILE " + tile.tileId + " error: " + tileError + " " + tile.source) 
    58                 if (tileError > 0) { 
    59                     // something went wrong 
    60                     if (tileError == 1) { 
    61                         // fatal error 
    62                         tile.error = true 
    63                     } else { 
    64                         if (tileError > 1) { 
    65                             // non fatal error, increment retry count 
    66                             tile.retryCount = tile.retryCount + 1 
    67                         } 
    68                         // TODO: use a constant ? 
    69                         if (tile.retryCount <= 5) { 
    70                             // we are still within the retry count limit, 
    71                             // so trigger another download request by trying 
    72                             // to load the tile 
    73                             tile.cache = false 
    74                             tile.source = tileUrl(tileId) 
    75                         } else { 
    76                             // retry count limit reached, switch to error state 
    77                             tile.error = true 
    78                         } 
    79                     } 
    80                 } else { 
    81                     // everything appears fine, load the tile 
    82                     tile.retryCount = 0 
    83                     tile.error = false 
    84                     tile.cache = true 
    85                     tile.source = tileUrl(tileId) 
    86                 } 
    87             } 
    88         } 
    89115    } 
    90116 
Note: See TracChangeset for help on using the changeset viewer.