├── README.md ├── mapboxTerrainMonothread.py └── mapboxTerrainMultithread.py /README.md: -------------------------------------------------------------------------------- 1 | # dem2mapbox 2 | Python scripts to convert a GeoTIFF DEM file to the mapbox terrain RGB format 3 | 4 | # How to use it 5 | Use the ```mapboxTerrainMonothread.py``` script to run the encoder in a single thread or the ```mapboxTerrainMultithread.py``` script to run the encoder in multiple threads. 6 | 7 | The first argument is a **_tif-encoded_ DEM file** and the second one the name of the output file. 8 | 9 | ## Changing the number of threads used 10 | In order to change the number of threads used by the multithreaded version you can edit the script and change the ``threadedCols`` and ``threadedRows`` variables. The script will get the image and divide it by ``threadedCols*threadedRows`` tiles and assign a thread to each one of them. 11 | 12 | ## Examples 13 | `python mapboxTerrainMultithread.py ./DEM.tif ./Mapbox.png` 14 | 15 | `python mapboxTerrainMonothread.py ./DEM.tif ./Mapbox.png` 16 | 17 | 18 | -------------------------------------------------------------------------------- /mapboxTerrainMonothread.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from PIL import Image 3 | from osgeo import gdal 4 | from subprocess import call 5 | import io, os, sys, numpy as np, time 6 | 7 | def computeMapboxHeight(height): 8 | 9 | value = int((height + 10000)*10) 10 | 11 | r = value>>16 12 | g = (value>>8 & 0x0000FF) 13 | b = value & 0x0000FF 14 | 15 | return (r, g, b) 16 | 17 | def MapboxHeight2Height(color): 18 | return -10000 + ((color[0] * 256 * 256 + color[1] * 256 + color[2]) * 0.1) 19 | 20 | def saveFile(outputData, path): 21 | output = Image.fromarray(outputData, 'RGB') 22 | output.save('./{}'.format(path)) 23 | 24 | def timeString(floatSeconds): 25 | timeStr = "" 26 | seconds = int(floatSeconds) 27 | 28 | if(seconds < 60): 29 | timeStr = "{} seconds".format(seconds) 30 | else: 31 | minutes = seconds / 60 32 | remainingSeconds = seconds % 60 33 | 34 | if(minutes<60): 35 | timeStr = "{} minutes, {} seconds".format(int(minutes), remainingSeconds) 36 | else: 37 | hours = minutes / 60 38 | remainingMinutes = minutes % 60 39 | timeStr = "{} hours, {} minutes, {} seconds".format(int(hours), remainingMinutes, remainingSeconds) 40 | 41 | return timeStr 42 | 43 | def generateImage(demFile, outputFile): 44 | demImage = gdal.Open(demFile, gdal.GA_ReadOnly) 45 | demBand = demImage.GetRasterBand(1) 46 | demData = demBand.ReadAsArray() 47 | [width, height] = demData.shape 48 | 49 | topLeft = demData[0, 0] 50 | topRight = demData[0, height-1] 51 | bottomLeft = demData[width-1, 0] 52 | bottomRight = demData[width-1, height-1] 53 | center = demData[(int(width/2)), (int(height/2))] 54 | 55 | outData = np.zeros((width, height, 3), dtype=np.uint8) 56 | numPixels = width*height 57 | numPixelsPC = int(numPixels/100) 58 | numPixelsDone = 0 59 | totalPixelsDone = 0 60 | startTime = time.time() 61 | 62 | for i in range(0, width): 63 | for j in range(0, height): 64 | demHeight = demData[i][j] 65 | color = computeMapboxHeight(demHeight) 66 | outData[i][j][0] = color[0] 67 | outData[i][j][1] = color[1] 68 | outData[i][j][2] = color[2] 69 | numPixelsDone += 1 70 | totalPixelsDone += 1 71 | if(numPixelsDone > numPixelsPC): 72 | timeDiff = time.time() - startTime 73 | pixelsDonePC = (totalPixelsDone/numPixels)*100 74 | remainingPC = 100 - pixelsDonePC 75 | remainingTime = (remainingPC * timeDiff) /pixelsDonePC 76 | print pixelsDonePC, "% pixels done in", timeString(timeDiff), ".", timeString(remainingTime), "remaining." 77 | numPixelsDone = 0 78 | 79 | print "Original top left data: ", topLeft 80 | print "Encoded top left data: ", MapboxHeight2Height(outData[0, 0]) 81 | print "Original top right data: ", topRight 82 | print "Encoded top right data: ", MapboxHeight2Height(outData[0, height-1]) 83 | print "Original bottom left data: ", bottomLeft 84 | print "Encoded bottom left data: ", MapboxHeight2Height(outData[width-1, 0]) 85 | print "Original bottom left data: ", bottomRight 86 | print "Encoded bottom right data: ", MapboxHeight2Height(outData[width-1, height-1]) 87 | print "Original center data: ", center 88 | print "Encoded center data: ", MapboxHeight2Height(outData[(int(width/2)), (int(height/2))]) 89 | 90 | saveFile(outData, outputFile) 91 | 92 | if (1 != len(sys.argv)): 93 | demFile = sys.argv[1] 94 | outFile = sys.argv[2] 95 | 96 | generateImage(demFile, outFile) 97 | else: 98 | print "Not enough parameters (demFile.tif outputFile.png)" -------------------------------------------------------------------------------- /mapboxTerrainMultithread.py: -------------------------------------------------------------------------------- 1 | from __future__ import division, print_function 2 | from PIL import Image 3 | from osgeo import gdal 4 | from subprocess import call 5 | from multiprocessing import Process, Pool, freeze_support 6 | from itertools import product 7 | import io, os, sys, numpy as np, time, math, traceback 8 | ''' 9 | Each image will be divided in threadedCols*threadedRows subimages and each 10 | thread will work with one of them 11 | ''' 12 | threadedCols = 2 13 | threadedRows = 2 14 | #Thread-safe printing 15 | print = lambda x: sys.stdout.write("%s\n" % x) 16 | 17 | def computeMapboxHeight(height): 18 | 19 | value = int((height + 10000)*10) 20 | 21 | r = value>>16 22 | g = (value>>8 & 0x0000FF) 23 | b = value & 0x0000FF 24 | 25 | return (r, g, b) 26 | 27 | def MapboxHeight2Height(color): 28 | return -10000 + ((color[0] * 256 * 256 + color[1] * 256 + color[2]) * 0.1) 29 | 30 | def timeString(floatSeconds): 31 | timeStr = "" 32 | seconds = int(floatSeconds) 33 | 34 | if(seconds < 60): 35 | timeStr = "{} seconds".format(seconds) 36 | else: 37 | minutes = seconds / 60 38 | remainingSeconds = seconds % 60 39 | 40 | if(minutes<60): 41 | timeStr = "{} minutes, {} seconds".format(int(minutes), remainingSeconds) 42 | else: 43 | hours = minutes / 60 44 | remainingMinutes = minutes % 60 45 | timeStr = "{} hours, {} minutes, {} seconds".format(int(hours), remainingMinutes, remainingSeconds) 46 | 47 | return timeStr 48 | 49 | def initializer(imageWidth, imageHeight, outputPath, 50 | pixelWidthBase, pixelHeightBase, leftPixelCenterBase, 51 | topPixelCenterBase, subImageWidth, subImageHeight): 52 | global numPixels, numPixelsPC, numPixelsDone 53 | global totalPixelsDone, startTime, threadedRows 54 | global threadedCols, pixelWidth, pixelHeight 55 | global leftPixelCenter, topPixelCenter 56 | global outputFilePath 57 | global columnWidth, rowHeight 58 | 59 | numPixels = imageWidth*imageHeight 60 | numPixelsPC = int(numPixels/100) 61 | numPixelsDone = 0 62 | totalPixelsDone = 0 63 | startTime = time.time() 64 | outputFilePath = outputPath 65 | pixelWidth = pixelWidthBase 66 | pixelHeight = pixelHeightBase 67 | leftPixelCenter = leftPixelCenterBase 68 | topPixelCenter = topPixelCenterBase 69 | columnWidth = subImageWidth 70 | rowHeight = subImageHeight 71 | 72 | print(str(numPixels) + " pixels to go") 73 | 74 | def generateImage(demFile, outputFile): 75 | global threadedRows, threadedCols, pixelWidth, pixelHeight 76 | global leftPixelCenter, topPixelCenter, outputFilePath 77 | 78 | demImage = gdal.Open(demFile, gdal.GA_ReadOnly) 79 | outputFilePath = outputFile 80 | imageWidth = demImage.RasterXSize 81 | imageHeight = demImage.RasterYSize 82 | demBand = demImage.GetRasterBand(1) 83 | transform = demImage.GetGeoTransform() 84 | pixelWidth = transform[1] 85 | pixelHeight = transform[5] 86 | leftPixelCenter = transform[0] + pixelWidth/2 87 | topPixelCenter = transform[3] + pixelHeight/2 88 | 89 | imageChunks = [] 90 | 91 | print("###########" + demFile + "###########") 92 | print("Image size: " + str(imageWidth) + ", " + str(imageHeight)) 93 | print("Pixel size: " + str(pixelHeight) + ", " + str(pixelHeight)) 94 | print("Top-left pixel center: " + str(leftPixelCenter) + ", " + str(topPixelCenter)) 95 | 96 | width = int(math.ceil(imageWidth / threadedCols)) 97 | height = int(math.ceil(imageHeight / threadedRows)) 98 | for i in range(threadedRows): 99 | imageChunks.append([]) 100 | for j in range(threadedCols): 101 | startWidth = j * width 102 | startHeight = i * height 103 | realWidth = width if startWidth+width < imageWidth else imageWidth - startWidth 104 | realHeight = height if startHeight+height < imageHeight else imageHeight - startHeight 105 | demData = demBand.ReadAsArray(startWidth, startHeight, realWidth, realHeight) 106 | 107 | imageChunks[i].append(demData.transpose()) 108 | print("Chunk " + str(i) + "-" + str(j) + ": " + str(startWidth) + "x" + str(startHeight) + " to " + str(startWidth+realWidth) + "x" + str(startHeight+realHeight)) 109 | 110 | print("#########################################################") 111 | 112 | dispatcher(imageChunks, imageWidth, imageHeight, pixelWidth, pixelHeight, 113 | leftPixelCenter, topPixelCenter, width, height) 114 | 115 | demImage = None 116 | 117 | def dispatcher(demData, imageWidth, imageHeight, pixelWidth, pixelHeight, 118 | leftPixelCenter, topPixelCenter, subImageWidth, subImageHeight): 119 | global threadedRows, threadedCols, outputFilePath 120 | 121 | threads = [] 122 | numThreads = threadedCols*threadedRows 123 | threadParameters = (imageWidth, imageHeight, outputFilePath, 124 | pixelWidth, pixelHeight, leftPixelCenter, topPixelCenter, 125 | subImageWidth, subImageHeight) 126 | if(numThreads > 1): 127 | print("Multithreaded") 128 | args = [] 129 | for threadRowIndex in range(threadedRows): 130 | for threadColIndex in range(threadedCols): 131 | args.append((threadRowIndex, threadColIndex, 132 | demData[threadRowIndex][threadColIndex])) 133 | 134 | p = Pool(numThreads, initializer, threadParameters) 135 | p.map(work_unpack, args) 136 | p.close() 137 | p.join() 138 | 139 | else: 140 | print("Singlethreaded") 141 | initializer(*threadParameters) 142 | work(0, 0, demData[0][0]) 143 | 144 | def work(subImageRow, subImageCol, demData): 145 | global numPixels, numPixelsPC, numPixelsDone 146 | global totalPixelsDone, startTime, threadedRows 147 | global threadedCols 148 | 149 | (width, height) = demData.shape 150 | print("Working on " + str(width) + " " + str(height)) 151 | outData = np.zeros((width, height, 3), dtype=np.uint8) 152 | 153 | for i in range(width): 154 | for j in range(height): 155 | demHeight = demData[i][j] 156 | color = computeMapboxHeight(demHeight) 157 | outData[i][j][0] = color[0] 158 | outData[i][j][1] = color[1] 159 | outData[i][j][2] = color[2] 160 | numPixelsDone += threadedCols*threadedRows 161 | totalPixelsDone += threadedCols*threadedRows 162 | 163 | if(subImageRow == 0 and subImageCol == 0 and numPixelsDone > numPixelsPC): 164 | timeDiff = time.time() - startTime 165 | pixelsDonePC = (totalPixelsDone/numPixels)*100 166 | remainingPC = 100 - pixelsDonePC 167 | remainingTime = (remainingPC * timeDiff) /pixelsDonePC 168 | print(str(pixelsDonePC) + "% pixels done " + timeString(timeDiff) + ". " + timeString(remainingTime) + " remaining.") 169 | numPixelsDone = 0 170 | 171 | saveFile(subImageRow, subImageCol, outData) 172 | 173 | def work_unpack(args): 174 | return work(*args) 175 | 176 | def saveFile(subImageRow, subImageCol, outputData): 177 | 178 | global pixelWidth, pixelHeight 179 | global leftPixelCenter, topPixelCenter 180 | global outputFilePath 181 | global columnWidth, rowHeight 182 | 183 | subImageLeft = leftPixelCenter + subImageCol*columnWidth*pixelWidth 184 | subImageTop = topPixelCenter + subImageRow*rowHeight*pixelHeight 185 | newName = outputFilePath[:-4] + "_" + str(subImageRow) + "_" + str(subImageCol) 186 | 187 | print("Saving " + str(subImageRow) + " " + str(subImageCol) + " data:") 188 | print("Saving PNG file") 189 | output = Image.fromarray(outputData.swapaxes(0,1), 'RGB') 190 | output.save('./{}.png'.format(newName)) 191 | 192 | print("Generating PGW file") 193 | output = "{}.pgw".format(newName) 194 | with open(output, "w") as pgwFile: 195 | pgwFile.write("{0:.50f}\n".format(pixelWidth)) 196 | pgwFile.write("{0:.50f}\n".format(0.0)) #TODO: Compute skew parameters 197 | pgwFile.write("{0:.50f}\n".format(0.0)) #TODO: Compute skew parameters 198 | pgwFile.write("{0:.50f}\n".format(pixelHeight)) 199 | pgwFile.write("{0:.50f}\n".format(subImageLeft)) 200 | pgwFile.write("{0:.50f}".format(subImageTop)) 201 | 202 | if __name__ == "__main__": 203 | freeze_support() 204 | try: 205 | if (1 != len(sys.argv)): 206 | demFile = sys.argv[1] 207 | outFile = sys.argv[2] 208 | 209 | generateImage(demFile, outFile) 210 | else: 211 | print("Not enough parameters (demFile.tif outputFile.png)") 212 | 213 | except: 214 | traceback.print_exc() --------------------------------------------------------------------------------