mirror of
https://github.com/AliFlux/MapTilesDownloader.git
synced 2025-05-15 22:50:10 -07:00
561 lines
12 KiB
JavaScript
561 lines
12 KiB
JavaScript
var mapView;
|
|
|
|
$(function() {
|
|
|
|
var map = null;
|
|
var draw = null;
|
|
var geocoder = null;
|
|
var bar = null;
|
|
|
|
var cancellationToken = null;
|
|
var requests = [];
|
|
|
|
var sources = {
|
|
|
|
"Bing Maps": "http://ecn.t0.tiles.virtualearth.net/tiles/r{quad}.jpeg?g=129&mkt=en&stl=H",
|
|
"Bing Maps Satellite": "http://ecn.t0.tiles.virtualearth.net/tiles/a{quad}.jpeg?g=129&mkt=en&stl=H",
|
|
"Bing Maps Hybrid": "http://ecn.t0.tiles.virtualearth.net/tiles/h{quad}.jpeg?g=129&mkt=en&stl=H",
|
|
|
|
"div-1": "",
|
|
|
|
"Google Maps": "https://mt0.google.com/vt?lyrs=m&x={x}&s=&y={y}&z={z}",
|
|
"Google Maps Satellite": "https://mt0.google.com/vt?lyrs=s&x={x}&s=&y={y}&z={z}",
|
|
"Google Maps Hybrid": "https://mt0.google.com/vt?lyrs=h&x={x}&s=&y={y}&z={z}",
|
|
"Google Maps Terrain": "https://mt0.google.com/vt?lyrs=p&x={x}&s=&y={y}&z={z}",
|
|
|
|
"div-2": "",
|
|
|
|
"Open Street Maps": "https://a.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
"Open Cycle Maps": "http://a.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png",
|
|
"Open PT Transport": "http://openptmap.org/tiles/{z}/{x}/{y}.png",
|
|
|
|
"div-3": "",
|
|
|
|
"ESRI World Imagery": "http://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
|
"Wikimedia Maps": "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png",
|
|
"NASA GIBS": "https://map1.vis.earthdata.nasa.gov/wmts-webmerc/MODIS_Terra_CorrectedReflectance_TrueColor/default/GoogleMapsCompatible_Level9/{z}/{y}/{x}.jpg",
|
|
|
|
"div-4": "",
|
|
|
|
"Carto Light": "http://cartodb-basemaps-c.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png",
|
|
"Stamen Toner B&W": "http://a.tile.stamen.com/toner/{z}/{x}/{y}.png",
|
|
|
|
};
|
|
|
|
function initializeMap() {
|
|
|
|
mapboxgl.accessToken = 'pk.eyJ1IjoiYWxpYXNocmFmIiwiYSI6ImNqbmlrbThoYjBuamIzcG8zdXl6NG1qNm4ifQ.XPbRBbMekHi2L9aJ_H3Yqw';
|
|
|
|
map = new mapboxgl.Map({
|
|
container: 'map-view',
|
|
style: 'mapbox://styles/mapbox/satellite-v9',
|
|
center: [-73.983652, 40.755024],
|
|
zoom: 12
|
|
});
|
|
|
|
geocoder = new MapboxGeocoder({ accessToken: mapboxgl.accessToken });
|
|
var control = map.addControl(geocoder);
|
|
}
|
|
|
|
function initializeMaterialize() {
|
|
$('select').formSelect();
|
|
$('.dropdown-trigger').dropdown({
|
|
constrainWidth: false,
|
|
});
|
|
}
|
|
|
|
function initializeSources() {
|
|
|
|
var dropdown = $("#sources");
|
|
|
|
for(var key in sources) {
|
|
var url = sources[key];
|
|
|
|
if(url == "") {
|
|
dropdown.append("<hr/>");
|
|
continue;
|
|
}
|
|
|
|
var item = $("<li><a></a></li>");
|
|
item.attr("data-url", url);
|
|
item.find("a").text(key);
|
|
|
|
item.click(function() {
|
|
var url = $(this).attr("data-url");
|
|
$("#source-box").val(url);
|
|
})
|
|
|
|
dropdown.append(item);
|
|
}
|
|
}
|
|
|
|
function initializeSearch() {
|
|
$("#search-form").submit(function(e) {
|
|
var location = $("#location-box").val();
|
|
geocoder.query(location);
|
|
|
|
e.preventDefault();
|
|
})
|
|
}
|
|
|
|
function initializeMoreOptions() {
|
|
|
|
$("#more-options-toggle").click(function() {
|
|
$("#more-options").toggle();
|
|
})
|
|
|
|
}
|
|
|
|
function initializeRectangleTool() {
|
|
|
|
var modes = MapboxDraw.modes;
|
|
modes.draw_rectangle = DrawRectangle.default;
|
|
|
|
draw = new MapboxDraw({
|
|
modes: modes
|
|
});
|
|
map.addControl(draw);
|
|
|
|
map.on('draw.create', function (e) {
|
|
M.Toast.dismissAll();
|
|
});
|
|
|
|
$("#rectangle-draw-button").click(function() {
|
|
startDrawing();
|
|
})
|
|
|
|
}
|
|
|
|
function startDrawing() {
|
|
removeGrid();
|
|
draw.deleteAll();
|
|
draw.changeMode('draw_rectangle');
|
|
|
|
M.Toast.dismissAll();
|
|
M.toast({html: 'Click two points on the map to make a rectangle.', displayLength: 7000})
|
|
}
|
|
|
|
function initializeGridPreview() {
|
|
$("#grid-preview-button").click(previewGrid);
|
|
|
|
map.on('click', showTilePopup);
|
|
}
|
|
|
|
function showTilePopup(e) {
|
|
|
|
if(!e.originalEvent.ctrlKey) {
|
|
return;
|
|
}
|
|
|
|
var maxZoom = getMaxZoom();
|
|
|
|
var x = lat2tile(e.lngLat.lat, maxZoom);
|
|
var y = long2tile(e.lngLat.lng, maxZoom);
|
|
|
|
var content = "X, Y, Z<br/><b>" + x + ", " + y + ", " + maxZoom + "</b><hr/>";
|
|
content += "Lat, Lng<br/><b>" + e.lngLat.lat + ", " + e.lngLat.lng + "</b>";
|
|
|
|
new mapboxgl.Popup()
|
|
.setLngLat(e.lngLat)
|
|
.setHTML(content)
|
|
.addTo(map);
|
|
|
|
console.log(e.lngLat)
|
|
|
|
}
|
|
|
|
function long2tile(lon,zoom) {
|
|
return (Math.floor((lon+180)/360*Math.pow(2,zoom)));
|
|
}
|
|
|
|
function lat2tile(lat,zoom) {
|
|
return (Math.floor((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom)));
|
|
}
|
|
|
|
function tile2long(x,z) {
|
|
return (x/Math.pow(2,z)*360-180);
|
|
}
|
|
|
|
function tile2lat(y,z) {
|
|
var n=Math.PI-2*Math.PI*y/Math.pow(2,z);
|
|
return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))));
|
|
}
|
|
|
|
function getTileRect(x, y, zoom) {
|
|
|
|
var c1 = new mapboxgl.LngLat(tile2long(x, zoom), tile2lat(y, zoom));
|
|
var c2 = new mapboxgl.LngLat(tile2long(x + 1, zoom), tile2lat(y + 1, zoom));
|
|
|
|
return new mapboxgl.LngLatBounds(c1, c2);
|
|
}
|
|
|
|
function getMinZoom() {
|
|
return Math.min(parseInt($("#zoom-from-box").val()), parseInt($("#zoom-to-box").val()));
|
|
}
|
|
|
|
function getMaxZoom() {
|
|
return Math.max(parseInt($("#zoom-from-box").val()), parseInt($("#zoom-to-box").val()));
|
|
}
|
|
|
|
function getArrayByBounds(bounds) {
|
|
|
|
var tileArray = [
|
|
[ bounds.getSouthWest().lng, bounds.getNorthEast().lat ],
|
|
[ bounds.getNorthEast().lng, bounds.getNorthEast().lat ],
|
|
[ bounds.getNorthEast().lng, bounds.getSouthWest().lat ],
|
|
[ bounds.getSouthWest().lng, bounds.getSouthWest().lat ],
|
|
[ bounds.getSouthWest().lng, bounds.getNorthEast().lat ],
|
|
];
|
|
|
|
return tileArray;
|
|
}
|
|
|
|
function getPolygonByBounds(bounds) {
|
|
|
|
var tilePolygonData = getArrayByBounds(bounds);
|
|
|
|
var polygon = turf.polygon([tilePolygonData]);
|
|
|
|
return polygon;
|
|
}
|
|
|
|
function isTileInSelection(tileRect) {
|
|
|
|
var polygon = getPolygonByBounds(tileRect);
|
|
|
|
var areaPolygon = draw.getAll().features[0];
|
|
|
|
if(turf.booleanDisjoint(polygon, areaPolygon) == false) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getGrid(zoomLevel) {
|
|
|
|
var coordinates = draw.getAll().features[0].geometry.coordinates[0];
|
|
|
|
var bounds = coordinates.reduce(function(bounds, coord) {
|
|
return bounds.extend(coord);
|
|
}, new mapboxgl.LngLatBounds(coordinates[0], coordinates[0]));
|
|
|
|
var rects = [];
|
|
|
|
var TY = lat2tile(bounds.getNorthEast().lat, zoomLevel);
|
|
var LX = long2tile(bounds.getSouthWest().lng, zoomLevel);
|
|
var BY = lat2tile(bounds.getSouthWest().lat, zoomLevel);
|
|
var RX = long2tile(bounds.getNorthEast().lng, zoomLevel);
|
|
|
|
for(var y = TY; y <= BY; y++) {
|
|
for(var x = LX; x <= RX; x++) {
|
|
|
|
var rect = getTileRect(x, y, zoomLevel);
|
|
|
|
if(isTileInSelection(rect)) {
|
|
rects.push({
|
|
x: x,
|
|
y: y,
|
|
zoom: zoomLevel,
|
|
rect: rect,
|
|
});
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return rects
|
|
}
|
|
|
|
function getAllGridTiles() {
|
|
var allTiles = [];
|
|
|
|
for(var z = getMinZoom(); z <= getMaxZoom(); z++) {
|
|
var grid = getGrid(z);
|
|
// TODO shuffle grid via a heuristic
|
|
allTiles = allTiles.concat(grid);
|
|
}
|
|
|
|
return allTiles;
|
|
}
|
|
|
|
function removeGrid() {
|
|
removeLayer("grid-preview");
|
|
}
|
|
|
|
function previewGrid() {
|
|
|
|
var maxZoom = getMaxZoom();
|
|
var grid = getGrid(maxZoom);
|
|
|
|
var pointsCollection = []
|
|
|
|
for(var i in grid) {
|
|
var feature = grid[i];
|
|
var array = getArrayByBounds(feature.rect);
|
|
pointsCollection.push(array);
|
|
}
|
|
|
|
removeGrid();
|
|
|
|
map.addLayer({
|
|
'id': "grid-preview",
|
|
'type': 'line',
|
|
'source': {
|
|
'type': 'geojson',
|
|
'data': turf.polygon(pointsCollection),
|
|
},
|
|
'layout': {},
|
|
'paint': {
|
|
"line-color": "#fa8231",
|
|
"line-width": 3,
|
|
}
|
|
});
|
|
|
|
var totalTiles = getAllGridTiles().length;
|
|
M.toast({html: 'Total ' + totalTiles.toLocaleString() + ' tiles in the region.', displayLength: 5000})
|
|
|
|
}
|
|
|
|
function previewRect(rectInfo) {
|
|
|
|
var array = getArrayByBounds(rectInfo.rect);
|
|
|
|
var id = "temp-" + rectInfo.x + '-' + rectInfo.y + '-' + rectInfo.z;
|
|
|
|
map.addLayer({
|
|
'id': id,
|
|
'type': 'line',
|
|
'source': {
|
|
'type': 'geojson',
|
|
'data': turf.polygon([array]),
|
|
},
|
|
'layout': {},
|
|
'paint': {
|
|
"line-color": "#ff9f1a",
|
|
"line-width": 3,
|
|
}
|
|
});
|
|
|
|
return id;
|
|
}
|
|
|
|
function removeLayer(id) {
|
|
if(map.getSource(id) != null) {
|
|
map.removeLayer(id);
|
|
map.removeSource(id);
|
|
}
|
|
}
|
|
|
|
function generateQuadKey(x, y, z) {
|
|
var quadKey = [];
|
|
for (var i = z; i > 0; i--) {
|
|
var digit = '0';
|
|
var mask = 1 << (i - 1);
|
|
if ((x & mask) != 0) {
|
|
digit++;
|
|
}
|
|
if ((y & mask) != 0) {
|
|
digit++;
|
|
digit++;
|
|
}
|
|
quadKey.push(digit);
|
|
}
|
|
return quadKey.join('');
|
|
}
|
|
|
|
function initializeDownloader() {
|
|
|
|
bar = new ProgressBar.Circle($('#progress-radial').get(0), {
|
|
strokeWidth: 12,
|
|
easing: 'easeOut',
|
|
duration: 200,
|
|
trailColor: '#eee',
|
|
trailWidth: 1,
|
|
from: {color: '#0fb9b1', a:0},
|
|
to: {color: '#20bf6b', a:1},
|
|
svgStyle: null,
|
|
step: function(state, circle) {
|
|
circle.path.setAttribute('stroke', state.color);
|
|
}
|
|
});
|
|
|
|
$("#download-button").click(startDownloading)
|
|
$("#stop-button").click(stopDownloading)
|
|
|
|
var timestamp = Date.now().toString();
|
|
//$("#output-directory-box").val(timestamp)
|
|
}
|
|
|
|
function showTinyTile(base64) {
|
|
var currentImages = $(".tile-strip img");
|
|
|
|
for(var i = 4; i < currentImages.length; i++) {
|
|
$(currentImages[i]).remove();
|
|
}
|
|
|
|
var image = $("<img/>").attr('src', "data:image/png;base64, " + base64)
|
|
|
|
var strip = $(".tile-strip");
|
|
strip.prepend(image)
|
|
}
|
|
|
|
function startDownloading() {
|
|
|
|
if(draw.getAll().features.length == 0) {
|
|
M.toast({html: 'You need to select a region first.', displayLength: 3000})
|
|
return;
|
|
}
|
|
|
|
cancellationToken = false;
|
|
requests = [];
|
|
|
|
$("#main-sidebar").hide();
|
|
$("#download-sidebar").show();
|
|
$(".tile-strip").html("");
|
|
$("#stop-button").html("STOP");
|
|
removeGrid();
|
|
clearLogs();
|
|
M.Toast.dismissAll();
|
|
|
|
var timestamp = Date.now().toString();
|
|
|
|
var allTiles = getAllGridTiles();
|
|
updateProgress(0, allTiles.length);
|
|
|
|
var numThreads = parseInt($("#parallel-threads-box").val());
|
|
var outputDirectory = $("#output-directory-box").val();
|
|
var outputFile = $("#output-file-box").val();
|
|
var source = $("#source-box").val()
|
|
|
|
let i = 0;
|
|
var iterator = async.eachLimit(allTiles, numThreads, function(item, done) {
|
|
|
|
if(cancellationToken) {
|
|
return;
|
|
}
|
|
|
|
var boxLayer = previewRect(item);
|
|
|
|
var url = "http://127.0.0.1:11291/download-tile";
|
|
|
|
var data = new FormData();
|
|
data.append('x', item.x)
|
|
data.append('y', item.y)
|
|
data.append('z', item.zoom)
|
|
data.append('quad', generateQuadKey(item.x, item.y, item.zoom))
|
|
data.append('outputDirectory', outputDirectory)
|
|
data.append('outputFile', outputFile)
|
|
data.append('timestamp', timestamp)
|
|
data.append('source', source)
|
|
|
|
var request = $.ajax({
|
|
"url": url,
|
|
async: true,
|
|
timeout: 30 * 1000,
|
|
type: "post",
|
|
contentType: false,
|
|
processData: false,
|
|
data: data,
|
|
dataType: 'json',
|
|
}).done(function(data) {
|
|
|
|
if(cancellationToken) {
|
|
return;
|
|
}
|
|
|
|
if(data.code == 200) {
|
|
showTinyTile(data.image)
|
|
logItem(item.x, item.y, item.zoom, data.message);
|
|
} else {
|
|
logItem(item.x, item.y, item.zoom, data.code + " Error downloading tile");
|
|
}
|
|
|
|
}).fail(function(data, textStatus, errorThrown) {
|
|
|
|
if(cancellationToken) {
|
|
return;
|
|
}
|
|
|
|
logItem(item.x, item.y, item.zoom, "Error while relaying tile");
|
|
//allTiles.push(item);
|
|
|
|
}).always(function(data) {
|
|
i++;
|
|
|
|
removeLayer(boxLayer);
|
|
updateProgress(i, allTiles.length);
|
|
|
|
done();
|
|
|
|
if(cancellationToken) {
|
|
return;
|
|
}
|
|
});
|
|
|
|
requests.push(request);
|
|
|
|
}, function(err) {
|
|
updateProgress(allTiles.length, allTiles.length);
|
|
logItemRaw("All requests are done");
|
|
|
|
$("#stop-button").html("FINISH");
|
|
});
|
|
|
|
}
|
|
|
|
function updateProgress(value, total) {
|
|
var progress = value / total;
|
|
|
|
bar.animate(progress);
|
|
bar.setText(Math.round(progress * 100) + '<span>%</span>');
|
|
|
|
$("#progress-subtitle").html(value.toLocaleString() + " <span>out of</span> " + total.toLocaleString())
|
|
}
|
|
|
|
function logItem(x, y, z, text) {
|
|
logItemRaw(x + ',' + y + ',' + z + ' : ' + text)
|
|
}
|
|
|
|
function logItemRaw(text) {
|
|
|
|
var logger = $('#log-view');
|
|
logger.val(logger.val() + '\n' + text);
|
|
|
|
logger.scrollTop(logger[0].scrollHeight);
|
|
}
|
|
|
|
function clearLogs() {
|
|
var logger = $('#log-view');
|
|
logger.val('');
|
|
}
|
|
|
|
function stopDownloading() {
|
|
cancellationToken = true;
|
|
|
|
for(var i =0 ; i < requests.length; i++) {
|
|
var request = requests[i];
|
|
try {
|
|
request.abort();
|
|
} catch(e) {
|
|
|
|
}
|
|
}
|
|
|
|
$("#main-sidebar").show();
|
|
$("#download-sidebar").hide();
|
|
removeGrid();
|
|
clearLogs();
|
|
|
|
}
|
|
|
|
|
|
initializeMaterialize();
|
|
initializeSources();
|
|
initializeMap();
|
|
initializeSearch();
|
|
initializeRectangleTool();
|
|
initializeGridPreview();
|
|
initializeMoreOptions();
|
|
initializeDownloader();
|
|
}); |