Шляхом Альбірео

НовиниЗаписиГеокодінгLeaflet-Zoomify

2022.02.14
Скрипти для відображення мапи у форматі Zoomify за допомоги Leaflet.
Note

Безлімітно за розміром, та безкоштовно генерувати тайли з великих зображень можна утилітою Zoomify. При цьому, буде згенерований також файл для показу у браузері, однак він буде дещо спрощений без можливості додати елементи навігації, легенди тощо.

В разі необхідності показу з допомогою Leaflet, потрібно враховувати деякі нюанси. Справа в тому, що команда Leaflet з переходом на на версію скрипта 1.0, суттєво змінила його алгоритм, що унеможливлює роботу plugins, до яких відносяться скрипти обробки тайлів Zoomify-формату. Повний код html-файла для відображення мапи за допомоги Leaflet версії 0.7.7 і скрипта від Bjorn Sandvik:

/* Zoomify - Leaflet ver.0.7.7 */

[HTML]

<!DOCTYPE html> <html lang="uk"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="referrer" content="origin-when-cross-origin"> <title>Leaflet 0.7.7</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.css"> <style type="text/css"> html, body, #picture { width: 100%; height: 100%; padding: 0; margin: 0; background: #063B41; } </style> </head> <body> <div id="picture"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.js"></script> <!-- Наступне можна винести окремо: <script type="text/javascript" src="L.TileLayer.Zoomify.js"></script> --> <script type="text/javascript"> /* * L.TileLayer.Zoomify display Zoomify tiles with Leaflet */ L.TileLayer.Zoomify = L.TileLayer.extend({ options: { continuousWorld: true, tolerance: 0.8 }, initialize: function (url, options) { options = L.setOptions(this, options); this._url = url; var imageSize = L.point(options.width, options.height), tileSize = options.tileSize; this._imageSize = [imageSize]; this._gridSize = [this._getGridSize(imageSize)]; while (parseInt(imageSize.x) > tileSize || parseInt(imageSize.y) > tileSize) { imageSize = imageSize.divideBy(2).floor(); this._imageSize.push(imageSize); this._gridSize.push(this._getGridSize(imageSize)); } this._imageSize.reverse(); this._gridSize.reverse(); this.options.maxZoom = this._gridSize.length - 1; }, onAdd: function (map) { L.TileLayer.prototype.onAdd.call(this, map); var mapSize = map.getSize(), zoom = this._getBestFitZoom(mapSize), imageSize = this._imageSize[zoom], center = map.options.crs.pointToLatLng(L.point(imageSize.x / 2, imageSize.y / 2), zoom); map.setView(center, zoom, true); }, _getGridSize: function (imageSize) { var tileSize = this.options.tileSize; return L.point(Math.ceil(imageSize.x / tileSize), Math.ceil(imageSize.y / tileSize)); }, _getBestFitZoom: function (mapSize) { var tolerance = this.options.tolerance, zoom = this._imageSize.length - 1, imageSize, zoom; while (zoom) { imageSize = this._imageSize[zoom]; if (imageSize.x * tolerance < mapSize.x && imageSize.y * tolerance < mapSize.y) { return zoom; } zoom--; } return zoom; }, _tileShouldBeLoaded: function (tilePoint) { var gridSize = this._gridSize[this._map.getZoom()]; return (tilePoint.x >= 0 && tilePoint.x < gridSize.x && tilePoint.y >= 0 && tilePoint.y < gridSize.y); }, _addTile: function (tilePoint, container) { var tilePos = this._getTilePos(tilePoint), tile = this._getTile(), zoom = this._map.getZoom(), imageSize = this._imageSize[zoom], gridSize = this._gridSize[zoom], tileSize = this.options.tileSize; if (tilePoint.x === gridSize.x - 1) { tile.style.width = imageSize.x - (tileSize * (gridSize.x - 1)) + 'px'; } if (tilePoint.y === gridSize.y - 1) { tile.style.height = imageSize.y - (tileSize * (gridSize.y - 1)) + 'px'; } L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23); this._tiles[tilePoint.x + ':' + tilePoint.y] = tile; this._loadTile(tile, tilePoint); if (tile.parentNode !== this._tileContainer) { container.appendChild(tile); } }, getTileUrl: function (tilePoint) { return this._url + 'TileGroup' + this._getTileGroup(tilePoint) + '/' + this._map.getZoom() + '-' + tilePoint.x + '-' + tilePoint.y + '.jpg'; }, _getTileGroup: function (tilePoint) { var zoom = this._map.getZoom(), num = 0, gridSize; for (z = 0; z < zoom; z++) { gridSize = this._gridSize[z]; num += gridSize.x * gridSize.y; } num += tilePoint.y * this._gridSize[zoom].x + tilePoint.x; return Math.floor(num / 256);; } }); L.tileLayer.zoomify = function (url, options) { return new L.TileLayer.Zoomify(url, options); }; </script> <script type="text/javascript"> var map = L.map('picture').setView(new L.LatLng(0,0), 0); <!-- Цю частину можна згенерувати php-скриптом, вказавши місце зберігання тайлів, та прочитавши розмір скану width-height із ImageProperties.xml --> L.tileLayer.zoomify('./dardanelles_by_rudometov/', { width: 8090, height: 5741, tolerance: 0.8, attribution: '0.7.7 | tile generator © <a href="http://zoomify.com">Zoomify</a> | with help <a href="https://github.com/turban/Leaflet.Zoomify">Bjorn Sandvik</a> | © 2018-2022 <a href="https://nvkarta.com">NVkarta</a> as user' }).addTo(map); </script> </body> </html>
Note

В разі використання Leaflet версії 1.0.0 і вище, повний код html-файла і скрипта від Coen Mulders. Додатково, в код був добавлений скрипт для обмеження зумінгу, але його можна й виключити:

/* Zoomify - Leaflet ver.1.7.1 */

[HTML]

<!DOCTYPE html> <html lang="uk"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="referrer" content="origin-when-cross-origin"> <title>Leaflet 1.7.1</title> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="" /> <style type="text/css"> html, body, #picture { width: 100%; height: 100%; padding: 0; margin: 0; background: #063B41; } </style> </head> <body> <div id="picture"></div> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script> <!-- Скрипт обмеження зумінгу: <script type="text/javascript" src="L.Map.mergeOptions.js"></script> --> <script type="text/javascript"> // Limit leaflet map zoom to a list of variants // Written by Ilya Zverev, licensed WTFPL L.Map.mergeOptions({ zooms: [0,1,2,3,4,5] // array of integers }); L.Map.include({ _limitZoom: function (zoom) { var zooms = this.options.zooms; if (!zooms || !('length' in zooms) || !zooms.length) { var min = this.getMinZoom(), max = this.getMaxZoom(), snap = L.Browser.any3d ? this.options.zoomSnap : 1; if (snap) { zoom = Math.round(zoom / snap) * snap; } return Math.max(min, Math.min(max, zoom)); } else { var z, d = 100, i, dist; var dz = -1, dd = 100, dir = zoom - this._zoom; var snap = L.Browser.any3d ? this.options.zoomSnap : 1; if (snap) { zoom = Math.round(zoom / snap) * snap; } for (i = 0; i < zooms.length; i++) { dist = Math.abs(zooms[i] - zoom); if (dist < d) { z = zooms[i]; d = dist; } if (dir * (zooms[i] - this._zoom) > 0 && dist < dd) { dz = zooms[i]; dd = dist; } } return dz < 0 ? z : dz; } } }); </script> <!-- Наступне можна винести окремо: <script type="text/javascript" src="L.TileLayer.Zoomify.js"></script> --> <script type="text/javascript"> /* * L.TileLayer.Zoomify display Zoomify tiles with Leaflet * * Based on the Leaflet.Zoomify (https://github.com/turban/Leaflet.Zoomify) * from turban (https://github.com/turban) * */ L.TileLayer.Zoomify = L.TileLayer.extend({ options: { width: -1, // Must be set by user, max zoom image width height: -1, // Must be set by user, max zoom image height tileGroupPrefix: 'TileGroup', tilesPerTileGroup: 256 }, initialize: function (url, options) { L.TileLayer.prototype.initialize.call(this, url, options); // Replace with automatic loading from ImageProperties.xml if (this.options.width < 0 || this.options.height < 0) { throw new Error('The user must set the Width and Height of the Zoomify image'); } }, beforeAdd: function (map) { var imageSize = L.point(this.options.width, this.options.height); // Build the zoom sizes of the pyramid and cache them in an array this._imageSize = [imageSize]; this._gridSize = [this._getGridSize(imageSize)]; // Register the image size in pixels and the grid size in # of tiles for each zoom level while (imageSize.x > this.options.tileSize || imageSize.y > this.options.tileSize) { imageSize = imageSize.divideBy(2).ceil(); this._imageSize.push(imageSize); this._gridSize.push(this._getGridSize(imageSize)); } // We built the cache from bottom to top, but leaflet uses a top to bottom index for the zoomlevel, // so reverse it for easy indexing by current zoomlevel this._imageSize.reverse(); this._gridSize.reverse(); // Register our max supported zoom level var maxNativeZoom = this._gridSize.length - 1; this.options.maxNativeZoom = maxNativeZoom; // Register our bounds for this zoomify layer based on the maximum zoom var maxZoomGrid = this._gridSize[maxNativeZoom], maxX = maxZoomGrid.x * this.options.tileSize, maxY = maxZoomGrid.y * this.options.tileSize, southEast = map.unproject([maxX, maxY], maxNativeZoom); this.options.bounds = new L.LatLngBounds([[0, 0], southEast]); L.TileLayer.prototype.beforeAdd.call(this, map); }, // Calculate the grid size for a given image size (based on tile size) _getGridSize: function (imageSize) { var tileSize = this.options.tileSize; return L.point(Math.ceil(imageSize.x / tileSize), Math.ceil(imageSize.y / tileSize)); }, // Extend the add tile function to update our arbitrary sized border tiles _addTile: function (coords, container) { // Load the tile via the original leaflet code L.TileLayer.prototype._addTile.call(this, coords, container); // Get out imagesize in pixels for this zoom level and our grid size var imageSize = this._imageSize[this._getZoomForUrl()], gridSize = this._gridSize[this._getZoomForUrl()]; // The real tile size (default:256) and the display tile size (if zoom > maxNativeZoom) var realTileSize = L.GridLayer.prototype.getTileSize.call(this), displayTileSize = L.TileLayer.prototype.getTileSize.call(this); // Get the current tile to adjust var key = this._tileCoordsToKey(coords), tile = this._tiles[key].el; // Calculate the required size of the border tiles var scaleFactor = L.point( (imageSize.x % realTileSize.x), (imageSize.y % realTileSize.y)).unscaleBy(realTileSize); // Update tile dimensions if we are on a border if ((imageSize.x % realTileSize.x) > 0 && coords.x === gridSize.x - 1) { tile.style.width = displayTileSize.scaleBy(scaleFactor).x + 'px'; } if ((imageSize.y % realTileSize.y) > 0 && coords.y === gridSize.y - 1) { tile.style.height = displayTileSize.scaleBy(scaleFactor).y + 'px'; } }, // Construct the tile url, by inserting our tilegroup before we template the url getTileUrl: function (coords) { // Set our TileGroup variable, and then let the original tile templater do the work this.options.g = this.options.tileGroupPrefix + this._getTileGroup(coords); // Call the original templater return L.TileLayer.prototype.getTileUrl.call(this, coords); }, // Calculates the TileGroup number, each group contains 256 tiles. The tiles are stored from topleft to bottomright _getTileGroup: function (coords) { var zoom = this._getZoomForUrl(), num = 0, gridSize; // Get the total number of tiles from the lowest zoom level to our zoomlevel for (var z = 0; z < zoom; z++) { gridSize = this._gridSize[z]; num += gridSize.x * gridSize.y; } // Add the remaining tiles from this zoom layer to the running total of tiles num += coords.y * this._gridSize[zoom].x + coords.x; return Math.floor(num / this.options.tilesPerTileGroup); }, getBounds: function () { return this.options.bounds; } }); L.tileLayer.zoomify = function (url, options) { return new L.TileLayer.Zoomify(url, options); }; </script> <script type="text/javascript"> <!-- Цю частину можна згенерувати php-скриптом, вказавши місце зберігання тайлів, та прочитавши розмір скану width-height із ImageProperties.xml --> var map = L.map('picture', { crs: L.CRS.Simple //Set a flat projection, as we are projecting an image }); //Loading the Zoomify tile layer, notice the URL var layer = L.tileLayer.zoomify('https://nvkarta.synology.me/history/maps/without-category/nvkarta-scan-temp/dardanelles_by_rudometov/{g}/{z}-{x}-{y}.jpg', { width: 8090, height: 5741, attribution: '1.7.1 | tile generator © <a href="http://zoomify.com">Zoomify</a> | with help <a href="https://github.com/cmulders/Leaflet.Zoomify">Coen Mulders</a> | © 2018-2022 <a href="https://nvkarta.com">NVkarta</a> as user' }).addTo(map); //Setting the view to our layer bounds, set by our Zoomify plugin map.fitBounds(layer.getBounds()); </script> </body> </html>

Назви директорій і файлів можуть змінюватися, бо доповнення матеріалу впливає на створення нових категорій документів. Саме тому, пропонуємо зберігати посилання тільки на головну сторінку цього ресурсу, а не на конкретну статтю.


CC0  NVkartaSSL E530.12.24/DS118WD20EFRX 2TБ/Page 0.024s • 67.45 KB