Get actual filled tiles count

Or, you can use scripting to automatically keep a custom property updated upon save:

function updateTileCounts(asset) {
    asset = asset || tiled.activeAsset

    if (asset && asset.isTileMap) {
        function countTiles(tileLayer) {
            let tileCount = 0

            for (let x = tileLayer.width - 1; x >= 0; --x) {
                for (let y = tileLayer.height - 1; y >= 0; --y) {
                    if (tileLayer.cellAt(x, y).tileId !== -1)
                        ++tileCount
                }
            }

            tileLayer.setProperty("tileCount", tileCount)
        }

        function processLayers(mapOrGroupLayer) {
            for (let i = mapOrGroupLayer.layerCount - 1; i >= 0; --i) {
                let layer = mapOrGroupLayer.layerAt(i)

                if (layer.isTileLayer) {
                    countTiles(layer)
                } else if (layer.isGroupLayer) {
                    processLayers(layer)
                }
            }
        }

        processLayers(asset)
    }
}

tiled.assetAboutToBeSaved.connect(updateTileCounts)

This script should work with a recent development snapshot.

Though of course, if your map is indeed that sparse, you might want to script a whole custom map format instead that exports to a more efficient format for such cases. It may not be actually smaller since the zlib compression also deals very well with sparse layers, but it could be more convenient to load.

Edit: I did just discover a strange bug when running this script repeatedly, which is that for some reason the layer script object can get deleted in between the call to layerAt and the isTileLayer check. Right now I have no idea why this happens, and since it’s a delayed deletion it may be hard to find out what’s triggering it. At least the script works sometimes… (edit: issue was fixed in 104540a4acd27d)