diff --git a/examples/iiif-example.js b/examples/iiif-example.js index ca13cea..2a85544 100644 --- a/examples/iiif-example.js +++ b/examples/iiif-example.js @@ -7,9 +7,7 @@ map = L.map('map', { }); stanfordMlk = L.tileLayer.iiif('https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44/info.json', { - attribution: 'Martin Luther King Jr. & Joan Baez march to integrate schools, Grenada, MS, 1966', - maxZoom: 5, - tileSize: 512 + attribution: 'Martin Luther King Jr. & Joan Baez march to integrate schools, Grenada, MS, 1966' }).addTo(map); princetonMap = L.tileLayer.iiif('http://libimages.princeton.edu/loris2/pudl0076%2Fmap_pownall%2F00000001.jp2/info.json', { diff --git a/leaflet-iiif.js b/leaflet-iiif.js index 9545957..470011b 100644 --- a/leaflet-iiif.js +++ b/leaflet-iiif.js @@ -69,6 +69,25 @@ L.TileLayer.Iiif = L.TileLayer.extend({ // Set maxZoom for map map._layersMaxZoom = _this.maxZoom; + // Set minZoom and minNativeZoom based on how the imageSizes match up + var smallestImage = _this._imageSizes[0]; + var mapSize = _this._map.getSize(); + var newMinZoom = 0; + // Loop back through 5 times to see if a better fit can be found. + for (var i = 1; i <= 5; i++) { + if (smallestImage.x > mapSize.x || smallestImage.y > mapSize.y) { + smallestImage = smallestImage.divideBy(2); + _this._imageSizes.unshift(smallestImage); + newMinZoom = -i; + } else { + break; + } + } + _this.options.minZoom = newMinZoom; + _this.options.minNativeZoom = newMinZoom; + _this._prev_map_layersMinZoom = _this._map._layersMinZoom; + _this._map._layersMinZoom = newMinZoom; + // Call add TileLayer L.TileLayer.prototype.onAdd.call(_this, map); @@ -98,6 +117,8 @@ L.TileLayer.Iiif = L.TileLayer.extend({ onRemove: function(map) { var _this = this; + map._layersMinZoom = _this._prev_map_layersMinZoom; + // Remove maxBounds set for this image if(_this.options.setMaxBounds) { map.setMaxBounds(null); @@ -112,7 +133,8 @@ L.TileLayer.Iiif = L.TileLayer.extend({ // Find best zoom level and center map var initialZoom = _this._getInitialZoom(_this._map.getSize()); - var imageSize = _this._imageSizes[initialZoom]; + var offset = _this._imageSizes.length - 1 - _this.options.maxNativeZoom; + var imageSize = _this._imageSizes[initialZoom + offset]; var sw = _this._map.options.crs.pointToLatLng(L.point(0, imageSize.y), initialZoom); var ne = _this._map.options.crs.pointToLatLng(L.point(imageSize.x, 0), initialZoom); var bounds = L.latLngBounds(sw, ne); @@ -177,6 +199,7 @@ L.TileLayer.Iiif = L.TileLayer.extend({ // Calculates maximum native zoom for the layer _this.maxNativeZoom = Math.max(ceilLog2(_this.x / _this.options.tileSize), ceilLog2(_this.y / _this.options.tileSize)); + _this.options.maxNativeZoom = _this.maxNativeZoom; // Enable zooming further than native if maxZoom option supplied if (_this._customMaxZoom && _this.options.maxZoom > _this.maxNativeZoom) { @@ -236,11 +259,15 @@ L.TileLayer.Iiif = L.TileLayer.extend({ return this._infoToBaseUrl() + '{region}/{size}/{rotation}/{quality}.{format}'; }, _isValidTile: function(coords) { - var _this = this, - zoom = _this._getZoomForUrl(), - sizes = _this._tierSizes[zoom], - x = coords.x, - y = (coords.y); + var tileBounds = this._tileCoordsToBounds(coords); + var _this = this; + var zoom = _this._getZoomForUrl(); + var sizes = _this._tierSizes[zoom]; + var x = coords.x; + var y = coords.y; + if (zoom < 0 && x >= 0 && y >= 0) { + return true; + } if (!sizes) return false; if (x < 0 || sizes[0] <= x || y < 0 || sizes[1] <= y) { @@ -250,14 +277,15 @@ L.TileLayer.Iiif = L.TileLayer.extend({ } }, _getInitialZoom: function (mapSize) { - var _this = this, - tolerance = 0.8, - imageSize; - - for (var i = _this.maxNativeZoom; i >= 0; i--) { - imageSize = this._imageSizes[i]; + var _this = this; + var tolerance = 0.8; + var imageSize; + // Calculate an offset between the zoom levels and the array accessors + var offset = _this._imageSizes.length - 1 - _this.options.maxNativeZoom; + for (var i = _this._imageSizes.length - 1; i >= 0; i--) { + imageSize = _this._imageSizes[i]; if (imageSize.x * tolerance < mapSize.x && imageSize.y * tolerance < mapSize.y) { - return i; + return i - offset; } } // return a default zoom diff --git a/spec/LTileLayerIiifSpec.js b/spec/LTileLayerIiifSpec.js index 139d26f..5fc4cea 100644 --- a/spec/LTileLayerIiifSpec.js +++ b/spec/LTileLayerIiifSpec.js @@ -28,6 +28,36 @@ describe('L.TileLayer.Iiif', function() { it('initializes the map', function(){ expect(typeof (map)).toEqual('object'); }); + + describe('onAdd', function() { + beforeEach(function() { + iiifLayer = iiifLayerFactory(); + }); + + afterEach(function() { + iiifLayer.off('load'); + }); + + it('with a fitable tileSize', function(done) { + map.addLayer(iiifLayer); + iiifLayer.on('load', function() { + expect(iiifLayer.options.minZoom).toBe(0); + expect(iiifLayer.options.minNativeZoom).toBe(0); + done(); + }); + }); + + it('with a large tileSize tries to best fit size by setting minNativeZoom and minZoom', function(done) { + var largeTileSize = L.tileLayer.iiif('http://localhost:9876/base/fixtures/cantaloupe/info.json'); + map.addLayer(largeTileSize); + largeTileSize.on('load', function() { + expect(largeTileSize.options.minZoom).toBe(-2); + expect(largeTileSize.options.minNativeZoom).toBe(-2); + expect(largeTileSize._prev_map_layersMinZoom).toBe(0) + done(); + }); + }); + }); describe('generated tile urls', function() { var iiifLayer; @@ -81,6 +111,17 @@ describe('L.TileLayer.Iiif', function() { }); }); + it('with a large tile size', function(done) { + var largeTileSize = L.tileLayer.iiif('http://localhost:9876/base/fixtures/cantaloupe/info.json'); + map.addLayer(largeTileSize); + largeTileSize.on('load', function() { + expect(largeTileSize.options.fitBounds).toBe(true); + expect(map.getBounds().getSouthWest().toString()).toBe('LatLng(-1956, -592)'); + expect(map.getBounds().getNorthEast().toString()).toBe('LatLng(444, 2608)'); + done(); + }); + }); + it('can be configured not to be on', function(done) { var iiifLayerNoFitBounds = iiifLayerFactory({ fitBounds: false }); map.addLayer(iiifLayerNoFitBounds); diff --git a/spec/fixtures/cantaloupe/info.json b/spec/fixtures/cantaloupe/info.json new file mode 100644 index 0000000..b04bf39 --- /dev/null +++ b/spec/fixtures/cantaloupe/info.json @@ -0,0 +1,83 @@ +{ + "@context": "http://iiif.io/api/image/2/context.json", + "@id": "http://127.0.0.1:8182/iiif/2/IMG_1707.JPG", + "protocol": "http://iiif.io/api/image", + "width": 4032, + "height": 3024, + "sizes": [ + { + "width": 126, + "height": 95 + }, + { + "width": 252, + "height": 189 + }, + { + "width": 504, + "height": 378 + }, + { + "width": 1008, + "height": 756 + }, + { + "width": 2016, + "height": 1512 + } + ], + "tiles": [ + { + "width": 2016, + "height": 1512, + "scaleFactors": [ + 1, + 2, + 4, + 8, + 16, + 32 + ] + } + ], + "profile": [ + "http://iiif.io/api/image/2/level2.json", + { + "formats": [ + "tif", + "jpg", + "gif", + "png" + ], + "maxArea": 400000000, + "qualities": [ + "bitonal", + "default", + "gray", + "color" + ], + "supports": [ + "sizeByW", + "regionByPx", + "sizeByWhListed", + "cors", + "regionSquare", + "sizeByDistortedWh", + "sizeAboveFull", + "canonicalLinkHeader", + "sizeByConfinedWh", + "sizeByPct", + "jsonldMediaType", + "regionByPct", + "sizeByH", + "rotationArbitrary", + "baseUriRedirect", + "rotationBy90s", + "profileLinkHeader", + "sizeByForcedWh", + "sizeByWh", + "mirroring" + ] + } + ] +}