diff --git a/README.md b/README.md index 6a416aa..e7c27db 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,101 @@ -# TMS server +# Make Custom Tiles Pack + +This is an instruction about making custom map tiles using [node-map-tiles-editor](https://github.com/wkh237/node-map-tiles-editor). ## Prerequisites -- Node.JS 7.1.0 + -- [node-canvas related dependencies](https://github.com/Automattic/node-canvas#installation) installed +- Node.JS > 7.1. +- **node-canvas** platform dependencies installed. [see instruction](https://github.com/Automattic/node-canvas#installation) + +## Prepare Image + +`node-map-tiles` simply create tiles by slicing large image into `256x256` tiles, so you will need a single image. In this tutorial, we will use the following image. + +![](https://i.imgur.com/FIDasq7.jpg) + +It's recommended to use a smaller one for synthesizing the image to map, because the editor is running in browser, it'd be very laggy if the image is huge. ## Start Server -Be sure you have installed everything that `node-canvas` needs, and use Node.JS -`7.1.0+`. +Simply clone or download the project + +``` +$ git clone https://github.com/wkh237/node-map-tiles-editor.git +``` + +Then start the node server ``` $ node . ``` -the server will start listen on `localhost:5000` +Now you should be able to visit the editor via browser : + +[http://localhost:5000/editor](http://localhost:5000/editor) + +## Create a Region + +Click the `New` button and input the region's name. + +![](https://i.imgur.com/6Y2gaED.png) + +You will see a JSON file with the name you just input created at `/regions` folder. + + +## Find a Rough Coordinate + +Now, we need a rough cooridate for the region. This can be done by the help of Google Map. + +![](https://i.imgur.com/mIHvv1n.png) + +From this image, the latitude is `22.729250` and longitude is `120.404356`, put them into `Latitude` and `Longitude` input box, and click `Go`. -## Get Tiles +The Map will now centered to the location. -Simply send GET request to this URL : +![](https://i.imgur.com/ZGgdkul.png) -`http://localhost:5000/tiles/region_code/z/x/y` +## Create Bounding Box -where `region_code` should be `test-region1`. +Every region should have a bounding box for slicing tiles. This can be done by simply click on the map and dragging the markers. + +![](https://i.imgur.com/qv564h1.png) + +After created a bounding box, don't forget to click `Save Changes` button. + +## Select Source Image + +To change the source image of a region, simply select it from the `Image dropdown`, it will list images inside `/region-raw-img` folder. Will be able to upload directly from browser, but not ATM. + +![](https://i.imgur.com/PJLwRgg.png) + +You should be able to see the image draw on the map once you selected it. + +## Adjust Bounding Box + +Now just moving and resizing the bounding box to most appropriate position and click `Save Change`. + +![](https://i.imgur.com/MNgJ8Iw.png) + +## See Sample Tiles + +Now it's time to see the actaul tiles created from server, scroll down to bottom of the page, you should be able to see a list which tells you how many tiles will be generated with different zoom levels. + +![](https://i.imgur.com/j6f6eOe.png) + + +To render sample tiles simply change the arguments on the panel and click `Render Tiles` button. + +## Create Tiles + +After everything's confirmed, now go to terminal and create tiles via command : + +``` +# format : node make-tiles +$ node make-tiles example 15 20 +``` -## Debug Information +![](https://i.imgur.com/fJdFjwA.png) -Send reuqest to the following URL, will generate tiles which has debug information : +You can change source image by modifying `image` property in `/regions/.json`, the script will find the image with the same name inside `/region-raw-image` folder. -http://localhost:5000/tiles/debug/region_code/z/x/y` +The tiles will located in `/regions//output/` folder. diff --git a/index.js b/index.js index 00f559c..e2bad85 100644 --- a/index.js +++ b/index.js @@ -15,6 +15,8 @@ var imgCache = {}; app.set('port', (process.env.PORT || 5000)); app.use('/public', express.static('public')); +app.use('/editor', express.static('public')); +app.use('/region-raw-img', express.static('region-raw-img')); app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.json()); @@ -23,6 +25,60 @@ app.listen(app.get('port'), function() { console.log('Node app is running on port', app.get('port')); }); +app.get('/', function(req,res) { + res.redirect('/editor/'); +}) + +app.get('/images', function(req, res) { + + var images = fs.readdirSync('./region-raw-img/'); + res.send(images); + +}) + +app.get('/overlay/:region', function(req, res) { + try { + var region = req.params.region; + var img = JSON.parse(fs.readFileSync('./regions/' + region + '.json')).image; + var overlay = process.cwd() + '/region-raw-img/' + img; + res.sendFile(overlay); + } + catch(err) { + console.log(err.stack); + } +}) + +app.put('/regions/:name', function(req, res) { + + var name = req.params.name; + var body = req.body; + console.log(name, body); + try { + fs.writeFileSync('./regions/'+name+'.json', JSON.stringify(body)); + } + catch(err) { + console.log(err.stack); + res.sendStatus(500); + } + res.sendStatus(200); +}) + +app.post('/regions/:name', function(req, res) { + + var name = req.params.name; + var body = req.body; + console.log(name, body); + try { + fs.writeFileSync('./regions/'+name+'.json', JSON.stringify(body)); + } + catch(err) { + console.log(err.stack); + res.sendStatus(500); + } + res.sendStatus(200); + +}) + app.get('/regions', function(req, res) { try { @@ -71,7 +127,7 @@ app.get('/bounds/:region', function(req, res) { }) -app.get('/reset-cache/:region', function(req,res) { +app.delete('/tiles/:region', function(req,res) { var region = req.params.region; try { @@ -103,12 +159,6 @@ app.get('/lookup/:z/:x/:y', function(req, res) { }); -app.get('/dummy-tiles/:region/:z/:x/:y', function (req, res) { - var {x, y, z, region} = req.params; - console.log('dummy tile: ', region, x, y, z); - res.type('image/png').send(generateTile(x,y,z)); -}); - app.get('/tiles/debug/:region/:z/:x/:y/', handleTileRequestDebug); app.get('/tiles/:region/:z/:x/:y/', handleTileRequest); @@ -118,24 +168,28 @@ function handleTileRequestDebug(req, res) { } function handleTileRequest(req, res) { - var {x, y, z, region, debug} = req.params; - console.log(`make tile: ${region}/${z}/${x}/${y}`); - let tilePath = `./regions/${region}/${debug ? 'debug/' : ''}${z}/${x}${y}.png`; - fs.exists(tilePath, (ext) => { - - if(ext) { - console.log('found tile', tilePath); - fs.readFile(tilePath, (err, data) => { - res.type('image/png').send(data); - }); - } - else { - createTileFromRawImage(region, x, y, z, debug, function(bytes) { - console.log('send tile') - res.type('image/png').send(bytes); - }); - } - }); + try { + var {x, y, z, region, debug} = req.params; + console.log(`make tile: ${region}/${z}/${x}/${y}`); + let tilePath = `./regions/${region}/${debug ? 'debug/' : ''}${z}/${x}${y}.png`; + fs.exists(tilePath, (ext) => { + + if(ext) { + console.log('found tile', tilePath); + fs.readFile(tilePath, (err, data) => { + res.type('image/png').send(data); + }); + } + else { + createTileFromRawImage(region, x, y, z, debug, function(bytes) { + console.log('send tile') + res.type('image/png').send(bytes); + }); + } + }); + } catch(err) { + console.log(err.stack) + } } app.get('/base/:z/:x/:y', function(req,res) { @@ -156,45 +210,6 @@ app.get('/base/:z/:x/:y', function(req,res) { }); }) -app.get('/exps/:id', function(req, res) { - res.send(dummyExperience(req.params.id)); -}); - -app.get('/pins', function(req, res) { - res.send(regions.pins); -}); - -app.get('/pins/:id', function(req, res) { - res.send(pins[req.params.id]); -}); - -function dummyExperience(id) { - var type = Math.random() > 0.8 ? 'label' : 'link'; - if(type === 'link') { - var expired = Math.random() > 0.6; - var image = Math.floor((Math.random() - 0.01) * 4); - - return { - exp_id : 'Experience #' + id, - label : 'Experience #' + id, - state : expired ? 'expired' : 'current', - start : expired ? '2016/11/08 14:30:00' : moment(Date.now() - 3600000 * Math.random()*2).format('YYYY/MM/DD HH:mm:ss'), - end : expired ? '2016/11/08 15:00:00' : moment(Date.now() + 3600000 * Math.random()*5).format('YYYY/MM/DD HH:mm:ss'), - hero_photo : 'public/event-bg' + image + '.png', - content : 'this is the text description', - pin_id : null - }; - } - else { - return { - exp_id : 'Experience #' + id, - label : 'Label only', - content : 'this is the text description', - pin_id : null - }; - } -}; - function tileToLatLng(x,y,z) { let result = { lat : tileToLat(y,z), lng : tileToLong(x,z) };; return result; @@ -213,9 +228,19 @@ function createTileFromRawImage(region, x, y, z, debug, cb) { x = Math.floor(x); y = Math.floor(y); z = Math.floor(z); - let bounds = fs.readFile(`./regions/${region}.json`, (err, data) => { - - let {bounds} = JSON.parse(data); + fs.readFile(`./regions/${region}.json`, (err, data) => { + let bounds = []; + let img = ''; + try { + bounds = JSON.parse(data).bounds; + img = JSON.parse(data).image; + console.log(bounds); + } + catch(err) { + console.log(err.stack); + return; + } + let sign = bounds[0].lat < 0 ? 1 : -1; let tileBounds = [ tileToLatLng(x,y,z), tileToLatLng(x+1,y,z), @@ -226,7 +251,7 @@ function createTileFromRawImage(region, x, y, z, debug, cb) { render(null, imgCache); } else { - fs.readFile(`./region-raw-img/${region}.png`, render); + fs.readFile(`./region-raw-img/${img}`, render); } function render(err, data) { @@ -248,6 +273,9 @@ function createTileFromRawImage(region, x, y, z, debug, cb) { let tileImg = new Canvas(256, 256); let originX = (Math.abs(tileBounds[0].lng) - Math.abs(bounds[0].lng))/regionWidth * img.width; let originY = (Math.abs(tileBounds[0].lat) - Math.abs(bounds[0].lat))/regionHeight * img.height; + if(sign<0) { + originY = img.height - originY - 512 - img.height%256; + } let ctx = tileImg.getContext('2d'); if( originX > img.width + tileWidth || @@ -268,9 +296,10 @@ function createTileFromRawImage(region, x, y, z, debug, cb) { console.log(`fill size (${tileWidth}, ${tileHeight})`); console.log(`project (${originX}, ${originY}, ${originX + tileWidth}, ${originY + tileHeight})`); + ctx.drawImage(img, originX, originY, tileWidth, tileHeight, 0, 0, 256, 256); if(debug) { - drawText(ctx,{region, x,y,z}, originX,originY,tileWidth, tileHeight, z); + drawText(ctx,{x,y,z}, originX,originY,tileWidth, tileHeight, z); } ctx.strokeRect(0, 0, 256, 256); ctx.fillStyle = '#DDD'; @@ -278,7 +307,7 @@ function createTileFromRawImage(region, x, y, z, debug, cb) { var bytes = tileImg.toBuffer(undefined, 3, ctx.PNG_FILTER_NONE); console.log('size', bytes.length); - var dir = `./regions/${region}/${debug ? 'debug/' : ''}${z}/${x}${y}.png`; + var dir = `./regions/debug/${region}/${z}/${x}${y}.png`; mkdirp(getDirName(dir), function (err) { if (err) console.log(err); @@ -316,26 +345,6 @@ function drawText(ctx,param, ox,oy, dx, dy, z) { ctx.fillText(z + 'x', 144, 255); } -function generateTile(x,y,z) { - - var canvas = new Canvas(256, 256); - var ctx = canvas.getContext('2d'); - - var coords = '(' + [x, y].join(', ') + ')'; - ctx.rect(0, 0, 256, 256); - ctx.fillStyle = '#F0F0F0'; - ctx.fill(); - ctx.fillStyle = '#333'; - ctx.font = '16px Arial'; - ctx.fillText(coords, 24, 64); - ctx.strokeStyle = 'white'; - ctx.strokeRect(0, 0, 256, 256); - ctx.fillStyle = '#DDD'; - ctx.font = '64px Arial'; - ctx.fillText(z + 'x', 64, 192); - var bytes = canvas.toBuffer(undefined, 3, canvas.PNG_FILTER_NONE); - return bytes; -} var deleteFolderRecursive = function(path, cb) { if( fs.existsSync(path) ) { diff --git a/make-tiles.js b/make-tiles.js index e52fba9..9ef9fac 100644 --- a/make-tiles.js +++ b/make-tiles.js @@ -15,6 +15,7 @@ var regionName = process.argv[2]; var min = +process.argv[3]; var max = +process.argv[4]; var val = JSON.parse(fs.readFileSync('./regions/'+regionName+'.json')).bounds; +var imgp = './region-raw-img/' + JSON.parse(fs.readFileSync('./regions/'+regionName+'.json')).image; console.log(val) console.log('generate tiles:', regionName, min+'x', '~', max+'x'); // get tms bounds for each zoom level @@ -41,8 +42,8 @@ var start = Date.now(); for(var i=min;i<=max;i++) { var lt = getTileAtLatLng(val[0], i); var rb = getTileAtLatLng(val[2], i); - for(var j = lt.x; j < rb.x;j++){ - for(var k=lt.y; k < rb.y;k++) { + for(var j = lt.x; j <= rb.x;j++){ + for(var k=lt.y; k <= rb.y;k++) { createTileFromRawImage(regionName, j,k,i, function() { now++; var time = Math.floor((total-now)*((Date.now() - start)/now)/1000); @@ -72,6 +73,7 @@ function createTileFromRawImage(region, x, y, z, cb) { let data = fs.readFileSync(`./regions/${region}.json`); let {bounds} = JSON.parse(data); + let sign = bounds[0].lat < 0 ? 1 : -1; let tileBounds = [ tileToLatLng(x,y,z), tileToLatLng(x+1,y,z), @@ -79,7 +81,7 @@ function createTileFromRawImage(region, x, y, z, cb) { tileToLatLng(x,y+1,z) ]; - var raw = imageCache[region] ? imageCache[region] : fs.readFileSync(`./region-raw-img/${region}.png`); + var raw = imageCache[region] ? imageCache[region] : fs.readFileSync(imgp); let img = new Image; img.src = raw; @@ -100,6 +102,9 @@ function createTileFromRawImage(region, x, y, z, cb) { return } else { + if(sign<0) { + originY = img.height - originY - 512 - img.height%256; + } ctx.drawImage(img, originX, originY, tileWidth, tileHeight, 0, 0, 256, 256); ctx.strokeStyle = 'transparent'; ctx.strokeRect(0, 0, 256, 256); @@ -107,7 +112,7 @@ function createTileFromRawImage(region, x, y, z, cb) { var bytes = tileImg.toBuffer(undefined, 3, ctx.PNG_FILTER_NONE); - var dir = `./regions/${region}/${z}/${x}${y}.png`; + var dir = `./regions/output/${region}/${z}/${x}${y}.png`; mkdirp.sync(getDirName(dir)); fs.writeFileSync(dir, bytes); cb(); diff --git a/public/app.js b/public/app.js index 533cb3f..7bee5aa 100644 --- a/public/app.js +++ b/public/app.js @@ -1,6 +1,7 @@ var map = null; var markers = []; var rect = null; +var overlay = null; var app = new Vue({ el: '#app', @@ -9,12 +10,15 @@ var app = new Vue({ data: { regions : [], selectedRegion : null, + selectedImage : null, regionZoom : 15, regionCenter : { lat : 0, lng : 0 }, - viewerTMSCoord : '17,116005,79122', + viewerTMSCoord : '', viewerWidth : 3, viewerHeight : 3, tiles : [], + images : [], + overlayURI : '', json : '', selectedBounds : [{lat:0, lng:0},{lat:0, lng:0},{lat:0, lng:0},{lat:0, lng:0}], config : { @@ -28,15 +32,18 @@ var app = new Vue({ created : function() { this.getRegions(); + this.getImages(); }, // watch prop changes watch : { + overlayURI : function(val) { + updateRegionRectAndOverlay() + }, + selectedBounds : function(val) { - var json = { bounds: val }; - app.json = JSON.stringify(json); // get tms bounds for each zoom level var min = app.config.zoomMin; var max = app.config.zoomMax; @@ -44,7 +51,6 @@ var app = new Vue({ for(var i = min; i <= max ; i++) { var lt = getTileAtLatLng(val[0], i); var rb = getTileAtLatLng(val[2], i); - console.log(lt,rb,i) ranges.push({ zoom : i, lt : lt, @@ -56,14 +62,12 @@ var app = new Vue({ } console.log(ranges); app.config.ranges = ranges; - }, - - regionZoom : function(val) { - map.setZoom(val); + var json = { name : app.selectedRegion, image : app.selectedImage, bounds: app.selectedBounds }; + app.json = JSON.stringify(json, null, ' '); }, selectedRegion : function(key) { - console.log(key) + console.log('change region to ', key) var region = null; for(var i in app.regions) { if(app.regions[i].name === key) { @@ -74,6 +78,7 @@ var app = new Vue({ console.log(region); if(!region) return; + console.log(app.overlayURI) var center = { lat : ((+region.bounds[0].lat) + (+region.bounds[2].lat))/2, lng : ((+region.bounds[0].lng) + (+region.bounds[2].lng))/2 @@ -83,8 +88,7 @@ var app = new Vue({ $.get('/bounds/' + key, function(data) { var bounds = JSON.parse(data); console.log('bounds', bounds); - // clear rect and redraw - app.selectedBounds = bounds.bounds; + // place new markers for(var i in markers) { markers[i].setMap(null); } @@ -92,38 +96,72 @@ var app = new Vue({ var marker1 = new google.maps.Marker({ position: region.bounds[0], label : 'LT', - map: map + map: map, + draggable : true }); + marker1.addListener('drag', updateRegionRectAndOverlay); var marker2 = new google.maps.Marker({ position: region.bounds[2], label : 'RB', - map: map + map: map, + draggable : true }); + marker2.addListener('drag', updateRegionRectAndOverlay); markers = [marker1, marker2]; - var rectBounds = { - north: Math.max(markers[0].position.lat(), markers[1].position.lat()), - south: Math.min(markers[0].position.lat(), markers[1].position.lat()), - east: Math.max(markers[0].position.lng(), markers[1].position.lng()), - west: Math.min(markers[0].position.lng(), markers[1].position.lng()) - }; - rect = new google.maps.Rectangle({ - strokeOpacity: 0.8, - strokeWeight: 0, - fillColor: '#007dff', - fillOpacity: 0.35, - map: map, - bounds: rectBounds - }); + // redraw + app.selectedBounds = bounds.bounds; + app.selectedImage = region.image; + app.overlayURI = '/overlay/' + region.name; + var json = { name : app.selectedRegion, image : app.selectedImage, bounds: app.selectedBounds }; + app.json = JSON.stringify(json, null, ' '); }); app.regionCenter = center; }, + selectedImage(val) { + var json = { name : app.selectedRegion, image : app.selectedImage, bounds: app.selectedBounds }; + app.json = JSON.stringify(json, null, ' '); + app.overlayURI = '/region-raw-img/'+val; + } + }, // methods methods : { + createRegion : function() { + var name = window.prompt('Please enter id of the region'); + var json = { + bounds : [{lat:0, lng:0},{lat:0, lng:0},{lat:0, lng:0},{lat:0, lng:0}] + }; + var xhr = new XMLHttpRequest(); + xhr.open('POST','/regions/'+name); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.send(JSON.stringify(json)); + xhr.onreadystatechange = function() { + if(xhr.readyState === 4) { + app.getRegions(); + } + } + + }, + + createImage : function() { + + }, + + setTMSCoords : function(val) { + var coords = [val.zoom, val.lt.x, val.lt.y].join(','); + app.viewerTMSCoord = coords; + app.renderViewer(); + }, + + navigate : function() { + console.log(app.regionCenter) + map.setCenter(app.regionCenter); + }, + stat : function() { var json = { bounds: app.selectedBounds }; app.json = JSON.stringify(json); @@ -148,18 +186,26 @@ var app = new Vue({ app.config.ranges = ranges; }, - getRegions : function() { - $.get('/regions', function(data) { - console.log(app.regions) - console.log('regions', data); - regions = data; - for(var i in data) { - var meta = data[i]; - app.regions.push(meta) - } - console.log(app.regions); - - }); + getRegions() { + $.get('/regions', function(data) { + console.log('regions', data); + var next = []; + regions = data; + for(var i in data) { + var meta = data[i]; + next.push(meta) + } + app.regions = next; + console.log(app.regions); + + }); + }, + + getImages() { + $.get('/images', function(data) { + console.log('images', data); + app.images = data; + }); }, renderViewer() { @@ -167,7 +213,6 @@ var app = new Vue({ var x = Math.floor(String(app.viewerTMSCoord).split(',')[1]); var y = Math.floor(String(app.viewerTMSCoord).split(',')[2]); var tiles = []; - for(var j = 0; j < app.viewerHeight; j++) { var row = []; for(var i = 0; i < app.viewerWidth; i++) { @@ -192,16 +237,13 @@ var app = new Vue({ app.renderViewer(); }, - updateRegionBounds() { - var body = {}; - body.bounds = app.selectedBounds; + save() { var xhr = new XMLHttpRequest(); - xhr.open('POST', '/bounds/'+ app.selectedRegion); + xhr.open('PUT', '/regions/'+ app.selectedRegion); xhr.setRequestHeader('Content-Type', 'application/json'); - xhr.send(JSON.stringify(body)); - console.log(app.selectedBounds); - } - + app.json.image = app.selectedImage; + xhr.send(app.json); + }, }, @@ -213,7 +255,8 @@ function initMap() { map = new google.maps.Map(document.getElementById('map'), { zoom: 16, center: {lat: -34.9270088, lng: 138.6089918}, - mapTypeId: google.maps.MapTypeId.TERRAIN + mapTypeId: google.maps.MapTypeId.TERRAIN, + scrollwheel: false }); map.addListener('click', function(event) { if(markers.length > 1) { @@ -230,40 +273,90 @@ function initMap() { var marker = new google.maps.Marker({ position: ll, label : markers.length === 0 ? 'LT' : 'RB', + draggable : true, map: map }); + marker.addListener('drag', updateRegionRectAndOverlay); var info = new google.maps.InfoWindow(); info.setContent('lat: ' + ll.lat + '
' + 'lng: ' + ll.lng); info.open(map, marker); markers.push(marker); // draw rect if(markers.length === 2) { - var bounds = { - north: Math.max(markers[0].position.lat(), markers[1].position.lat()), - south: Math.min(markers[0].position.lat(), markers[1].position.lat()), - east: Math.max(markers[0].position.lng(), markers[1].position.lng()), - west: Math.min(markers[0].position.lng(), markers[1].position.lng()) - }; + var bounds = getBoundsFromMarkers(); rect && rect.setMap(null); - rect = new google.maps.Rectangle({ - strokeOpacity: 0.8, - strokeWeight: 0, - fillColor: '#007dff', - fillOpacity: 0.35, - map: map, - bounds: bounds - }); - app.selectedBounds = [ - { lat : bounds.west, lng : bounds.north }, - { lat : bounds.east, lng : bounds.north }, - { lat : bounds.east, lng : bounds.south }, - { lat : bounds.west, lng : bounds.south }, - ]; + updateRegionRectAndOverlay(); console.log('rectBounds', bounds) } }); } +function updateRegionRectAndOverlay() { + console.log('redraw ..') + if(markers.length < 2) { + return; + } + overlay && overlay.setMap(null); + rect && rect.setMap(null); + var rectBounds = getBoundsFromMarkers(); + rect = new google.maps.Rectangle({ + strokeOpacity: 0.8, + strokeColor : '#00b2ff', + strokeWeight: 2, + fillColor: '#00b2ff', + fillOpacity: 0.10, + draggable : true, + map: map, + zIndex : 0, + bounds: rectBounds + }); + rect.addListener('dragend', function() { + var rt = { + lat : rect.getBounds().getNorthEast().lat(), + lng : rect.getBounds().getNorthEast().lng(), + }; + var lb = { + lat : rect.getBounds().getSouthWest().lat(), + lng : rect.getBounds().getSouthWest().lng(), + }; + if(markers.lengt <2) + return; + markers[0].setPosition({ lat : lb.lat, lng : rt.lng }); + markers[1].setPosition({ lat : rt.lat, lng : lb.lng }); + overlay && overlay.setMap(null); + overlay = new google.maps.GroundOverlay(app.overlayURI, getBoundsFromMarkers()); + overlay.setMap(map); + app.selectedBounds = getTileRegionBoundsFromMarkers(); + }); + if(app.overlayURI) { + overlay && overlay.setMap(null); + overlay = new google.maps.GroundOverlay(app.overlayURI, rectBounds); + overlay.setMap(map) + } + app.selectedBounds = getTileRegionBoundsFromMarkers(); +} + +function getTileRegionBoundsFromMarkers() { + var bounds = getBoundsFromMarkers(); + return [ + { lng : bounds.west, lat : bounds.north }, + { lng : bounds.east, lat : bounds.north }, + { lng : bounds.east, lat : bounds.south }, + { lng : bounds.west, lat : bounds.south }, + ]; +} + +function getBoundsFromMarkers() { + if(markers.length < 2) + return null; + return { + north: Math.max(markers[0].position.lat(), markers[1].position.lat()), + south: Math.min(markers[0].position.lat(), markers[1].position.lat()), + east: Math.max(markers[0].position.lng(), markers[1].position.lng()), + west: Math.min(markers[0].position.lng(), markers[1].position.lng()) + }; +} + function fromLatLngToPoint (latLng){ var siny = Math.min(Math.max(Math.sin(latLng.lat* (Math.PI / 180)), -.9999),.9999); return { diff --git a/public/index.html b/public/index.html index ee5f979..5fe32af 100644 --- a/public/index.html +++ b/public/index.html @@ -8,79 +8,76 @@ -
-
+
+ + +
+ +
-

Regions

- -
- -

Zoom x {{ regionZoom }}

- -

Latitude

- +

Longitude

- +
-
+
-
- -

Origin

-
- name  - TMS coord  -
-
- x - - -
-
-

Top left

- - - -
-
-

Bottom right

- - - -
- +

Output JSON

+ +
+

TMS coord

+ +

View Port

+ x + +
+ + +
+ - - -
-
-
+
+ + +
+
+
+ - -
+ v-bind:src="tile.src"/>
@@ -91,17 +88,28 @@

Zoom

~ -
-
- {{s.zoom}}X ({{s.lt.x}}~{{s.rb.x}}, {{s.lt.y}}~{{s.rb.y}}) {{s.width}}x{{s.height}}, {{ s.number }} tiles +
+

Zoom

+

Range

+

Dimension

+

Tile Number

+

Set As Origin

+
+
+

{{s.zoom}}X

+

({{s.lt.x}}~{{s.rb.x}}, {{s.lt.y}}~{{s.rb.y}})

+

{{s.width}}x{{s.height}}

+

{{ s.number }}

+
+
+ src="https://maps.googleapis.com/maps/api/js?key=&signed_in=true&callback=initMap"> diff --git a/public/style.css b/public/style.css index 0dd57e6..67af36d 100644 --- a/public/style.css +++ b/public/style.css @@ -1,19 +1,53 @@ html, body { - margin: 1rem; text-align: center; - padding: 0; + margin : 0; +} +input, button, select { + border : 0; + background-color: #ededed; + padding : 8px; + margin : 4px; + min-width: 50px; } + #map { - width: 75%; - height: 100vh; - display: inline-block; + width: 100%; + height: 80vh; + display: block; } -#tool { - display: inline-block; - width: 24%; - vertical-align: top; +#json { + border : 0; + width : 100%; + display: block; + font-size: 16px; + background-color: #ededed; + padding : 16px; +} +#nav { + height : 60px; } +.table { + display: flex; + padding: 8px; + width: 100%; +} +.header { + background-color: #ededed; +} +.table > p { + flex: 1; + padding-left: 8px; + padding-right: 8px; + margin : 0; + text-align: left; +} +.table > button { + flex: 1; + margin : 4px; + text-align: center; +} + #viewer-panel { margin : 1rem; display: block;