├── .dockerignore ├── .gitignore ├── README.md ├── TanDEM-X 90 ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── main │ ├── DownloadTask.java │ ├── Main.java │ ├── ProgressDialog.java │ ├── Start.java │ └── ZipHandler.java ├── TanDEM90mDownloader.jar ├── _config.yml ├── create-dataset.sh ├── create-tiles.sh ├── data └── TIFF-Files-here.md ├── docker-compose.yml ├── docker └── Dockerfile ├── docs └── api.md ├── download-srtm-data.sh ├── gdal_interfaces.py ├── gdal_interfaces.pyc ├── license.md ├── open-elevation.service ├── requirements.txt ├── server.py └── swagger └── swagger.json /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.git 2 | data/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/.idea/* 3 | *.iml 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open-Elevation - Remake 2 | ### ↓ Read install instructions below ↓ 3 | 4 | A free and open-source elevation API by Jorl17. The original is available at: [https://github.com/Jorl17/open-elevation](https://github.com/Jorl17/open-elevation). Thanks for your work. 5 | 6 | 7 | **Open-Elevation** is a free and open-source alternative to the [Google Elevation API](https://developers.google.com/maps/documentation/elevation/start) and similar offerings. 8 | 9 | This service came out of the need to have a hosted, easy to use and easy to setup elevation API. While there are some alternatives out there, none of them work out of the box, and seem to point to dead datasets. Open-Elevation is easy to setup, has its own docker image and provides scripts for you to easily acquire whatever datasets you want. 10 | 11 | Open-Elevation API Doc for details and ustage 12 | ##### [doc/api.md](https://github.com/Developer66/open-elevation/blob/master/docs/api.md) 13 | 14 | 15 | ----- 16 | # Changes to the original version: 17 | * changing TIFF file locations - to working one - for SRTM 250m data in download script 18 | * changing create-dataset.sh to work again 19 | * fixing download scripts 20 | 21 | ### News: 22 | * added a preinstall documentation for dependencies 23 | * adding service file for linux (e.g. autostart) 24 | 25 | ### SRTM 90M from https://geoservice.dlr.de/ 26 | * adding Doc for using SRTM 90m data from https://geoservice.dlr.de/web/dataguide/tdm90/ 27 | * adding java downloader for data from https://geoservice.dlr.de/ 28 | * adding JavaProject to download and xxtract all files and put it to the right place 29 | 30 | ### Bugs: 31 | * SRTM 90M TanDEM data are 40 to high. Issue: [here](https://github.com/Developer66/open-elevation/issues/1) 32 | ----- 33 | 34 | # How to install: 35 | *I tested the install procedure on a fresh Ubuntu 18.10.* 36 | 37 | 38 | Fist of all clone this repository to your favourite location. (Use a permanent place for it where it won't be deleted) 39 | 40 | ### Default 250m files (after procedure ca. 20GB) 41 | 1. Make sure your system is up-to-date 42 | 43 | ``` 44 | sudo apt-get update 45 | sudo apt-get upgrade -y 46 | ``` 47 | 48 | 2. Install GDAL used for the GeoTIFFs 49 | 50 | ``` 51 | sudo apt update 52 | sudo apt install gdal-bin python-gdal 53 | 54 | // Add libgal-dev 55 | sudo apt-get install libgdal-dev 56 | 57 | // Add unar 58 | sudo apt install unar 59 | 60 | // Create system vars 61 | export CPLUS_INCLUDE_PATH=/usr/include/gdal 62 | export C_INCLUDE_PATH=/usr/include/gdal 63 | 64 | sudo apt install python3-rtree 65 | ``` 66 | 67 | 3. Install pip dependencies 68 | 69 | ``` 70 | pip install -r requirements.txt 71 | ``` 72 | 73 | 4. Download and progress GeoTIFFs 74 | ``` 75 | ## open terminal and cd to your open-elevation dir ## 76 | // Mark scripts as executable 77 | sudo chmod +x download-srtm-data.sh create-tiles.sh create-dataset.sh 78 | 79 | // Execute 80 | ./create-dataset.sh 81 | ``` 82 | 83 | The script should be downloading at this point and * this can take some time - up to 2 hours *. 84 | 85 | 5. **Optional** Adding Service to your computer (e.g. autostart) 86 | ``` 87 | sudo mv <> /etc/systemd/system/open-elevation.service 88 | 89 | //Enable Autostart 90 | systemctl enable open-elevation 91 | 92 | //Following Commands can be used 93 | sudo service open-elevation start | stop | restart 94 | ``` 95 | 96 | This service file (found in this repository at open-elevation.service) will also contain information to be specified manually such as the user and various pathways to the working directory. 97 | 98 | 6. Your server is now running reachable at 0.0.0.0:10000. **Congratulation** 99 | 100 | To change the ip edit the last line in **server.py**. You can choose ip and port whatever your want 101 | 102 | Test it: 103 | ``` 104 | http://0.0.0.0:10000/api/v1/lookup?locations=48.179138,10.703618 105 | ``` 106 | 107 | ### 90m TanDEM added myselfe (ca. 100GB) 108 | 109 | **Following step 1 to 3 from above** 110 | 111 | Than: 112 | 113 | 1. Install open-jre 114 | ``` 115 | sudo apt-get install open-jre 116 | ``` 117 | 118 | 2. Create an account at https://sso.eoc.dlr.de/cas/login 119 | 3. Generate a download the list of your needed locations at https://download.geoservice.dlr.de/TDM90/ 120 | 121 | You can zoom out and use CTL to drag multiple rectangles 122 | ![img](https://geoservice.dlr.de/web/dataguide/tdm90/images/03_Geoservice_DEM_selected_by_Rectangle.png) 123 | 124 | 4. Start the TanDEM90mDownloader.jar in your directory using this args: 125 | ``` 126 | java -jar <> -i= -o= -u= 127 | *Optional number of Threads (default is 4)* -n=4 128 | 129 | Example: 130 | java -jar C:\TanDEM90mDownloader.jar -i=C:\urllist.txt -o=C:\data -u=max@mustermann.de -p=xyz1234 131 | ``` 132 | 133 | The program than automatically download the zips, extract them and copy the DEM data to your output dir and deletes the zip after that to save storage. 134 | 135 | 5. **Optional** As 5. above 136 | 6. Your server is now running reachable at 0.0.0.0:10000. **Congratulation** 137 | 138 | To change the ip edit the last line in **server.py**. You can choose ip and port whatever your want 139 | Test it: 140 | ``` 141 | http://0.0.0.0:10000/api/v1/lookup?locations=48.179138,10.703618 142 | ``` 143 | 144 | # ! WARNING ! 145 | Files from https://geoservice.dlr.de/ 30M are a little bit larger than the original files. 146 | 147 | *Infos from https://geoservice.dlr.de/web/dataguide/tdm90/ (2019.02.01).* 148 | 149 | | Key | Value 150 | | -------- | -------- 151 | | Number of DEM products | 19389 152 | | Size of the global data set, zipped (including all annotations) | 253 GB 153 | | Size of the global data set, unzipped (including all annotations) | 534 GB 154 | | Size of all DEM raster files (unzipped, without annotations or meta data) | 93.8 GB 155 | 156 | ----- 157 | 158 | # Install using docker (oringal not tested) 159 | 160 | You can freely host your own instance of Open-Elevation. There are two main options: Docker or native. We recommend using docker to ensure that your environment matches the development environment 161 | 162 | ## Clone the repository 163 | First things first, clone the repository and `cd` onto its directory 164 | 165 | ``` 166 | git clone http://github.com/Jorl17/open-elevation 167 | cd open-elevation 168 | ``` 169 | 170 | 171 | ## Using Docker 172 | 173 | An image of Open-Elevation is available at [DockerHub](https://hub.docker.com/r/openelevation/open-elevation/). You can use this image as the basis for your Open-Elevation installation. 174 | 175 | The Docker image roots itself at `/code/` and expects that all GeoTIFF datafiles be located at `/code/data/`, which you should mount using a volume. 176 | 177 | ### Prerequisites: Getting the dataset 178 | 179 | Open-Elevation doesn't come with any data of its own, but it offers a set of scripts to get the whole [SRTM 250m dataset](http://gisweb.ciat.cgiar.org/TRMM/SRTM_Resampled_250m/). 180 | 181 | #### Whole World 182 | 183 | If you wish to host the whole world, just run 184 | 185 | ``` 186 | mkdir data # Create the target folder for the dataset 187 | docker run -t -i -v $(pwd)/data:/code/data openelevation/open-elevation /code/create-dataset.sh 188 | ``` 189 | 190 | The above command should have downloaded the entire SRTM dataset and split it into multiple smaller files in the `data` directory. **Be aware that this directory may be over 20 GB in size after the process is completed!** 191 | 192 | #### Custom Data 193 | 194 | If you don't want to use the whole world, you can provide your own dataset in GeoTIFF format, compatible with the SRTM dataset. Simply drop the files for the regions you desire in the `data` directory. You are advised to split these files in smaller chunks so as to make Open-Elevation less memory-hungry (the largest file has to fit in memory). The `create-tiles.sh` is capable of doing this, and you can see it working in `create-dataset.sh`. Since you are using docker, you should always run the commands within the container. For example: 195 | 196 | ``` 197 | docker run -t -i -v $(pwd)/data:/code/data openelevation/open-elevation /code/create-tiles.sh /code/data/SRTM_NE_250m.tif 10 10 198 | ``` 199 | 200 | The above example command splits `SRTM_NE_250m.tif` into 10 by 10 files inside the `/code/data` directory, which is mapped to `$(pwd)/data`. 201 | 202 | 203 | ### Running the Server in Docker container 204 | 205 | Now that you've got your data, you're ready to run Open-Elevation! Simply run 206 | 207 | ``` 208 | docker run -t -i -v $(pwd)/data:/code/data -p 8080:8080 openelevation/open-elevation 209 | ``` 210 | Build image only: 211 | ``` 212 | docker build . -f docker/Dockerfile 213 | ``` 214 | 215 | This command: 216 | 217 | 1. Maps `$(pwd)/data` (your data directory) to `/code/data` within the container 218 | 2. Exposes port 8080 to forward to the container's port 8080 219 | 3. Runs the default command, which is the server at port 8080 220 | 221 | You should now be able to go to `https://localhost:8080` for all your open-route needs. 222 | 223 | ## Docker compose 224 | You can use `docker-compose.yml` to build image, create and run docker container 225 | ``` 226 | docker-compose -f docker-compose.yml up -d 227 | ``` 228 | server will be available on host port 8080: 229 | ``` 230 | curl http://0.0.0.0:8080/api/v1/lookup?locations=42.216667,27.416667 231 | {"results": [{"latitude": 42.216667, "elevation": 262, "longitude": 27.416667}]} 232 | ``` 233 | 234 | ## Generate API clients from swagger.json 235 | #### React Client 236 | We use [swagger-js-codegen](https://github.com/wcandillon/swagger-js-codegen) 237 | ```javascript 238 | const fs = require('fs'); 239 | const {CodeGen} = require('swagger-js-codegen'); 240 | 241 | const swaggerFile = 'swagger/swagger.json'; 242 | const className = 'OpenElevationRestClient'; 243 | const swagger = JSON.parse(fs.readFileSync(swaggerFile, 'UTF-8')); 244 | 245 | const elevationReactCode = CodeGen.getReactCode({ 246 | moduleName: className, 247 | className, 248 | swagger, 249 | isES6: true, 250 | }); 251 | save(elevationReactCode, className, '.js'); 252 | 253 | function save(code, fileName, ext = '.js') { 254 | const outputDir = 'src/swagger/generated/'; 255 | const outputFile = outputDir + fileName; 256 | 257 | if (!fs.existsSync(outputDir)) { 258 | fs.mkdirSync(outputDir); 259 | } 260 | fs.writeFileSync(outputFile + ext, '/* eslint-disable */\n/* This file is generated! Do not edit, your changes will be overridden */\n'); 261 | 262 | fs.appendFileSync(outputFile + ext, code); 263 | } 264 | ``` 265 | Example usage: 266 | 267 | ```javascript 268 | 269 | new OpenElevationRestClient().getLookup({locations: '42.216667,27.416667'}).then((results) => { 270 | console.log(results); 271 | return true; 272 | }).catch((error) => { 273 | console.log('Error getLookup:' + error); 274 | }); 275 | new OpenElevationRestClient().postLookup({ 276 | json: { 277 | locations: [{ 278 | latitude: 42.216667, 279 | longitude: 27.416667 280 | }] 281 | } 282 | }).then((results) => { 283 | console.log(results); 284 | return true; 285 | }).catch((error) => { 286 | console.log('Error postLookup:' + error); 287 | }); 288 | ``` 289 | 290 | 291 | ## Problems 292 | 293 | Have you found any problems? Open an issue or submit your own pull request! 294 | -------------------------------------------------------------------------------- /TanDEM-X 90/.gitignore: -------------------------------------------------------------------------------- 1 | ### Eclipse template 2 | 3 | .metadata 4 | bin/ 5 | tmp/ 6 | *.tmp 7 | *.bak 8 | *.swp 9 | *~.nib 10 | local.properties 11 | .settings/ 12 | .loadpath 13 | .recommenders 14 | 15 | # External tool builders 16 | .externalToolBuilders/ 17 | 18 | # Locally stored "Eclipse launch configurations" 19 | *.launch 20 | 21 | # PyDev specific (Python IDE for Eclipse) 22 | *.pydevproject 23 | 24 | # CDT-specific (C/C++ Development Tooling) 25 | .cproject 26 | 27 | # CDT- autotools 28 | .autotools 29 | 30 | # Java annotation processor (APT) 31 | .factorypath 32 | 33 | # PDT-specific (PHP Development Tools) 34 | .buildpath 35 | 36 | # sbteclipse plugin 37 | .target 38 | 39 | # Tern plugin 40 | .tern-project 41 | 42 | # TeXlipse plugin 43 | .texlipse 44 | 45 | # STS (Spring Tool Suite) 46 | .springBeans 47 | 48 | # Code Recommenders 49 | .recommenders/ 50 | 51 | # Annotation Processing 52 | .apt_generated/ 53 | 54 | # Scala IDE specific (Scala & Java development for Eclipse) 55 | .cache-main 56 | .scala_dependencies 57 | .worksheet 58 | ### JetBrains template 59 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 60 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 61 | 62 | # User-specific stuff 63 | .idea/**/workspace.xml 64 | .idea/**/tasks.xml 65 | .idea/**/usage.statistics.xml 66 | .idea/**/dictionaries 67 | .idea/**/shelf 68 | 69 | # Sensitive or high-churn files 70 | .idea/**/dataSources/ 71 | .idea/**/dataSources.ids 72 | .idea/**/dataSources.local.xml 73 | .idea/**/sqlDataSources.xml 74 | .idea/**/dynamic.xml 75 | .idea/**/uiDesigner.xml 76 | .idea/**/dbnavigator.xml 77 | 78 | # Gradle 79 | .idea/**/gradle.xml 80 | .idea/**/libraries 81 | 82 | # Gradle and Maven with auto-import 83 | # When using Gradle or Maven with auto-import, you should exclude module files, 84 | # since they will be recreated, and may cause churn. Uncomment if using 85 | # auto-import. 86 | # .idea/modules.xml 87 | # .idea/*.iml 88 | # .idea/modules 89 | 90 | # CMake 91 | cmake-build-*/ 92 | 93 | # Mongo Explorer plugin 94 | .idea/**/mongoSettings.xml 95 | 96 | # File-based project format 97 | *.iws 98 | 99 | # IntelliJ 100 | out/ 101 | 102 | # mpeltonen/sbt-idea plugin 103 | .idea_modules/ 104 | 105 | # JIRA plugin 106 | atlassian-ide-plugin.xml 107 | 108 | # Cursive Clojure plugin 109 | .idea/replstate.xml 110 | 111 | # Crashlytics plugin (for Android Studio and IntelliJ) 112 | com_crashlytics_export_strings.xml 113 | crashlytics.properties 114 | crashlytics-build.properties 115 | fabric.properties 116 | 117 | # Editor-based Rest Client 118 | .idea/httpRequests 119 | ### Maven template 120 | target/ 121 | pom.xml.tag 122 | pom.xml.releaseBackup 123 | pom.xml.versionsBackup 124 | pom.xml.next 125 | release.properties 126 | dependency-reduced-pom.xml 127 | buildNumber.properties 128 | .mvn/timing.properties 129 | .mvn/wrapper/maven-wrapper.jar 130 | -------------------------------------------------------------------------------- /TanDEM-X 90/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | TanDEM-X-90-Downloader 5 | TanDEM-X-90-Downloader 6 | 1.0-STABLE 7 | 8 | 9 | 10 | 11 | 12 | commons-cli 13 | commons-cli 14 | 1.4 15 | 16 | 17 | 18 | commons-io 19 | commons-io 20 | 2.6 21 | 22 | 23 | 24 | net.lingala.zip4j 25 | zip4j 26 | 1.3.2 27 | 28 | 29 | 30 | me.tongfei 31 | progressbar 32 | 0.7.1 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-compiler-plugin 42 | 3.7.0 43 | 44 | 1.8 45 | 1.8 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-compiler-plugin 54 | 55 | 56 | maven-resources-plugin 57 | 2.6 58 | 59 | 60 | copy-resources 61 | validate 62 | 63 | copy-resources 64 | 65 | 66 | ${basedir}/target/Crunchify 67 | 68 | 69 | resources 70 | true 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-dependency-plugin 80 | 81 | 82 | copy-dependencies 83 | prepare-package 84 | 85 | copy-dependencies 86 | 87 | 88 | ${project.build.directory}/Crunchify/lib 89 | false 90 | false 91 | true 92 | 93 | 94 | 95 | 96 | 97 | org.apache.maven.plugins 98 | maven-jar-plugin 99 | 100 | 101 | 102 | true 103 | lib/ 104 | com.crunchify.tutorial.CrunchifyMain 105 | 106 | 107 | . 108 | 109 | 110 | 111 | Crunchify/Crunchify 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /TanDEM-X 90/src/main/java/main/DownloadTask.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.InputStream; 6 | import java.net.HttpURLConnection; 7 | import java.net.URL; 8 | import java.util.Base64; 9 | 10 | import org.apache.commons.io.FilenameUtils; 11 | 12 | /** 13 | * Download Task 14 | * 15 | * Downloading the zip file and start the decompress thread 16 | * 17 | * @author crypto 18 | * 19 | */ 20 | public class DownloadTask implements Runnable { 21 | 22 | private final String inputList; 23 | private final File outputDir; 24 | private final String username; 25 | private final String passwd; 26 | 27 | /** 28 | * @param line 29 | * @param outputDir 30 | * @param username 31 | * @param passwd 32 | */ 33 | public DownloadTask(String line, File outputDir, String username, String passwd) { 34 | this.inputList = line; 35 | this.outputDir = outputDir; 36 | this.username = username; 37 | this.passwd = passwd; 38 | } 39 | 40 | @Override 41 | public void run() { 42 | downloadFile(inputList); 43 | } 44 | 45 | /** 46 | * Download the file from param and starte the ZipHandler as an thread 47 | * 48 | * @param webUrl 49 | */ 50 | public void downloadFile(String webUrl) { 51 | InputStream inputStream = null; 52 | FileOutputStream outputStream = null; 53 | HttpURLConnection urlConnection = null; 54 | String saveFilePath = null; 55 | 56 | try { 57 | URL url = new URL(webUrl); 58 | urlConnection = (HttpURLConnection) url.openConnection(); 59 | urlConnection.setRequestMethod("GET"); 60 | 61 | // generate savename 62 | saveFilePath = outputDir + File.separator + FilenameUtils.getName(url.getPath()); 63 | 64 | // Building username and pass for login 65 | String userpass = username + ":" + passwd; 66 | String basicAuth = "Basic " + Base64.getEncoder().encodeToString(userpass.getBytes()); 67 | urlConnection.setRequestProperty("Authorization", basicAuth); 68 | 69 | // opens input stream from the HTTP connection 70 | inputStream = urlConnection.getInputStream(); 71 | 72 | // opens an output stream to save into file 73 | outputStream = new FileOutputStream(new File(saveFilePath)); 74 | 75 | // Save data with blocksize 4096 76 | int bytesRead = -1; 77 | byte[] buffer = new byte[4096]; 78 | while ((bytesRead = inputStream.read(buffer)) != -1) { 79 | outputStream.write(buffer, 0, bytesRead); 80 | } 81 | 82 | //Close all resources 83 | urlConnection.disconnect(); 84 | inputStream.close(); 85 | outputStream.close(); 86 | 87 | // Start decompress Thread 88 | new Thread(new ZipHandler(new File(saveFilePath), outputDir)).start(); 89 | } catch (Exception e) { 90 | e.printStackTrace(); 91 | } finally { 92 | } 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /TanDEM-X 90/src/main/java/main/Main.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import java.io.File; 4 | import java.net.URI; 5 | import java.nio.charset.Charset; 6 | import java.nio.file.Files; 7 | import java.nio.file.Paths; 8 | import java.util.List; 9 | import java.util.concurrent.ExecutorService; 10 | import java.util.concurrent.Executors; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | public class Main { 14 | private final File inputList; 15 | private final File outputDir; 16 | private final String username; 17 | private final String passwd; 18 | private final int threads; 19 | 20 | /** 21 | * @param inputList 22 | * @param outputDir 23 | * @param username 24 | * @param passwd 25 | */ 26 | public Main(File inputList, File outputDir, String username, String passwd, int threads) { 27 | this.inputList = inputList; 28 | this.outputDir = outputDir; 29 | this.username = username; 30 | this.passwd = passwd; 31 | this.threads = threads; 32 | 33 | parseInputFile(); 34 | } 35 | 36 | /** 37 | * Reads all lines from the urllist and add them to an executer service to 38 | * progress in parallel threads 39 | */ 40 | private void parseInputFile() { 41 | try { 42 | ExecutorService pool = Executors.newFixedThreadPool(threads); 43 | URI uri = inputList.toURI(); 44 | 45 | // Read all lines to list 46 | List lines = Files.readAllLines(Paths.get(uri), Charset.defaultCharset()); 47 | 48 | // Set Number of rows to progressDialog to clac time to go 49 | ProgressDialog dialog = ProgressDialog.getInstance(); 50 | dialog.setMax(lines.size()); 51 | 52 | // adding jobs 53 | for (String line : lines) { 54 | pool.submit(new DownloadTask(line, outputDir, username, passwd)); 55 | } 56 | 57 | pool.shutdown(); 58 | pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS); 59 | // all tasks have now finished (unless an exception is thrown above) 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /TanDEM-X 90/src/main/java/main/ProgressDialog.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import me.tongfei.progressbar.ProgressBar; 4 | 5 | /** 6 | * Progressbar Singleton 7 | * 8 | * @author crypto 9 | * 10 | */ 11 | public class ProgressDialog { 12 | private static ProgressDialog instance; 13 | private ProgressBar progressBar = null; 14 | 15 | public synchronized static ProgressDialog getInstance() { 16 | if (ProgressDialog.instance == null) { 17 | ProgressDialog.instance = new ProgressDialog(); 18 | } 19 | return ProgressDialog.instance; 20 | } 21 | 22 | // Create Progressbar 23 | private ProgressDialog() { 24 | progressBar = new ProgressBar("Downloading...", 0); 25 | } 26 | 27 | /** 28 | * Add +1 to progressbar 29 | */ 30 | public synchronized void addOne() { 31 | progressBar.step(); 32 | } 33 | 34 | /** 35 | * Set Max progress to progressBar 36 | * 37 | * @param i 38 | */ 39 | public synchronized void setMax(int i) { 40 | progressBar.maxHint(i); 41 | } 42 | } -------------------------------------------------------------------------------- /TanDEM-X 90/src/main/java/main/Start.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import java.io.File; 4 | 5 | import org.apache.commons.cli.CommandLine; 6 | import org.apache.commons.cli.CommandLineParser; 7 | import org.apache.commons.cli.DefaultParser; 8 | import org.apache.commons.cli.HelpFormatter; 9 | import org.apache.commons.cli.Options; 10 | import org.apache.commons.cli.ParseException; 11 | 12 | /** 13 | * Start class managing the cmd options 14 | * 15 | * @author crypto 16 | * 17 | */ 18 | public class Start { 19 | 20 | public static void main(String[] args) throws ParseException { 21 | //Adding options 22 | Options options = new Options(); 23 | options.addOption("i", "input-urllist", true, "Specify the input url list") 24 | .addOption("o", "output", true, "Specify a output for extracted tiff file") 25 | .addOption("u", "username", true, "Your https://geoservice.dlr.de login username (=email)") 26 | .addOption("p", "password", true, "Your https://geoservice.dlr.de password") 27 | .addOption("n","number-of-threads", true, "Insert a number of download threads default is 4"); 28 | 29 | HelpFormatter formatter = new HelpFormatter(); 30 | formatter.printHelp("TanDEM-X-90-Downloader", null, options, 31 | "Sample ustage:\njava -jar <> -i=C:\\ -o=C:\\out -u=test@test.test -p=pass"); 32 | 33 | // Parsed vars 34 | File inputList = null; 35 | File outputDir = null; 36 | String username = null; 37 | String passwd = null; 38 | int threads = 4; 39 | 40 | // Create a parser 41 | CommandLineParser parser = new DefaultParser(); 42 | CommandLine cmd = parser.parse(options, args); 43 | 44 | // Get input 45 | if (cmd.hasOption("i")) { 46 | inputList = new File(cmd.getOptionValue("i")); 47 | } else { 48 | error("Plase specify a input file for the url list"); 49 | } 50 | // Get output 51 | if (cmd.hasOption("o")) { 52 | outputDir = new File(cmd.getOptionValue("o")); 53 | //Create folderstructure if not exists 54 | if (!outputDir.exists()) { 55 | outputDir.mkdirs(); 56 | } 57 | } else { 58 | error("Plase specify a output dir"); 59 | } 60 | // Get usernmae 61 | if (cmd.hasOption("u")) { 62 | username = cmd.getOptionValue("u"); 63 | } else { 64 | error("Plase specify a username"); 65 | } 66 | // Get passwd 67 | if (cmd.hasOption("p")) { 68 | passwd = cmd.getOptionValue("p"); 69 | } else { 70 | error("Plase specify a passwd"); 71 | } 72 | // Get Threads 73 | if (cmd.hasOption("n")) { 74 | threads = Integer.parseInt(cmd.getOptionValue("n")); 75 | } 76 | 77 | //Give args to Mainclass 78 | new Main(inputList, outputDir, username, passwd, threads); 79 | } 80 | 81 | /** 82 | * Errormessage will be shown as error and system is shutdown 83 | * 84 | * @param msg 85 | */ 86 | private static void error(String msg) { 87 | System.err.println("\n\nERROR: " + msg); 88 | System.exit(0); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /TanDEM-X 90/src/main/java/main/ZipHandler.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.Enumeration; 6 | 7 | import org.apache.commons.io.FileUtils; 8 | 9 | import net.lingala.zip4j.core.ZipFile; 10 | import net.lingala.zip4j.exception.ZipException; 11 | 12 | public class ZipHandler implements Runnable { 13 | 14 | private File input; 15 | private File output; 16 | 17 | /** 18 | * @param input 19 | * @param output 20 | */ 21 | public ZipHandler(File input, File output) { 22 | this.input = input; 23 | this.output = output; 24 | } 25 | 26 | /** 27 | * Copy DEM file from zip to the output dir and delete extracted dir and zip 28 | * file 29 | * @throws IOException 30 | */ 31 | private void copyDEMtoRoot() throws IOException { 32 | // Get extracted File name 33 | java.util.zip.ZipFile zip = new java.util.zip.ZipFile(input); 34 | Enumeration entries = zip.entries(); 35 | File extreactedFile = new File(output.getPath() + File.separator + entries.nextElement()); 36 | zip.close(); 37 | 38 | // DEM File location 39 | File tifLocation = new File(extreactedFile.getPath() + File.separator + "DEM"); 40 | for (String tifFilePath : tifLocation.list()) { 41 | // Rename (mv tif to output dir) 42 | File tif = new File(tifLocation + File.separator + tifFilePath); 43 | tif.renameTo(new File(output.getPath() + File.separator + tif.getName())); 44 | } 45 | 46 | // Delete extracted dir 47 | FileUtils.deleteDirectory(extreactedFile); 48 | // Delete zip 49 | FileUtils.forceDelete(input); 50 | } 51 | 52 | @Override 53 | public void run() { 54 | try { 55 | unzip(); 56 | copyDEMtoRoot(); 57 | 58 | // Adding progressbar ++ to show progress 59 | ProgressDialog.getInstance().addOne(); 60 | } catch (ZipException | IOException e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | 65 | /** 66 | * Unzip file 67 | * 68 | * @throws ZipException 69 | */ 70 | private void unzip() throws ZipException { 71 | ZipFile zipFile = new ZipFile(input); 72 | zipFile.extractAll(output.getPath()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /TanDEM90mDownloader.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Developer66/open-elevation/1d21902eb5cb9b80c0760466592ee004702224b2/TanDEM90mDownloader.jar -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /create-dataset.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTDIR="data" 4 | #mkdir $OUTDIR 5 | 6 | cd $OUTDIR 7 | 8 | echo Downloading data... 9 | ../download-srtm-data.sh 10 | 11 | echo create tiles: SRTM_NE_250m 12 | ../create-tiles.sh SRTM_NE_250m.tif 10 10 && 13 | rm -rf SRTM_NE_250m.tif 14 | 15 | echo create tiles: SRTM_SE_250m 16 | ../create-tiles.sh SRTM_SE_250m.tif 10 10 && 17 | rm -rf SRTM_SE_250m.tif 18 | 19 | echo create tiles: SRTM_W_250m 20 | ../create-tiles.sh SRTM_W_250m.tif 10 20 && 21 | rm -rf SRTM_W_250m.tif 22 | 23 | echo removing unused data 24 | rm -rf readme.txt TIFF-Files-here.md 25 | -------------------------------------------------------------------------------- /create-tiles.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Original Source: https://github.com/mapbox/gdal-polygonize-test 3 | set -eu 4 | 5 | raster=$1 6 | xtiles=$2 7 | ytiles=$3 8 | 9 | # get raster bounds 10 | ul=($(gdalinfo $raster | grep '^Upper Left' | sed -e 's/[a-zA-Z ]*(//' -e 's/).*//' -e 's/,/ /')) 11 | lr=($(gdalinfo $raster | grep '^Lower Right' | sed -e 's/[a-zA-Z ]*(//' -e 's/).*//' -e 's/,/ /')) 12 | 13 | xmin=${ul[0]} 14 | xsize=$(echo "${lr[0]} - $xmin" | bc) 15 | ysize=$(echo "${ul[1]} - ${lr[1]}" | bc) 16 | 17 | xdif=$(echo "$xsize/$xtiles" | bc -l) 18 | 19 | for x in $(eval echo {0..$(($xtiles-1))}); do 20 | xmax=$(echo "$xmin + $xdif" | bc) 21 | ymax=${ul[1]} 22 | ydif=$(echo "$ysize/$ytiles" | bc -l) 23 | 24 | for y in $(eval echo {0..$((ytiles-1))}); do 25 | ymin=$(echo "$ymax - $ydif" | bc) 26 | 27 | # Create chunk of source raster 28 | gdal_translate -q \ 29 | -projwin $xmin $ymax $xmax $ymin \ 30 | -of GTiff \ 31 | $raster ${raster%.tif}_${x}_${y}.tif 32 | 33 | ymax=$ymin 34 | done 35 | xmin=$xmax 36 | done 37 | -------------------------------------------------------------------------------- /data/TIFF-Files-here.md: -------------------------------------------------------------------------------- 1 | Your geoTiff's have to be in this folder. -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | services: 3 | openelevation: 4 | container_name: open-elevation 5 | build: 6 | context: . 7 | dockerfile: ./docker/Dockerfile 8 | networks: 9 | - elevnet 10 | ports: 11 | - "8080:10000" 12 | volumes: 13 | - ./data:/code/data 14 | networks: 15 | elevnet: 16 | driver: bridge 17 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM geodata/gdal:2.1.3 2 | 3 | ADD ./requirements.txt . 4 | 5 | RUN pip install -r requirements.txt 6 | 7 | RUN apt-get update 8 | 9 | RUN apt-get install -y libspatialindex-dev unar bc 10 | 11 | RUN mkdir /code 12 | 13 | ADD . /code/ 14 | 15 | WORKDIR /code 16 | 17 | CMD python server.py 18 | 19 | EXPOSE 8080 20 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # Public API Documentation 2 | 3 | Open-Elevation's API is extremely simple -- after all, it fits a single, specific, simple task. There is **only one endpoint**, which is documented here. 4 | 5 | ## `GET /api/v1/lookup` 6 | 7 | Returns ("looks up") the elevation at one or more `(latitude,longitude)` points. 8 | 9 | The GET API is limited to **1024** bytes in the request line. If you plan on making large requests, consider using the POST api. 10 | 11 | ### Parameters: 12 | 13 | * **`locations`**: List of locations, separated by `|` in `latitude, longitude` format, similar to the Google Elevation API. 14 | 15 | ### Response format 16 | 17 | A JSON object with a single list of results, in the `results` field is returned. Each result contains `latitude`, `longitude` and `elevation`. The results are in the same order as the request parameters. **Elevation is in meters**. 18 | 19 | If there is no recorded elevation at the provided coordinate, sea level (0 meters) is returned. 20 | 21 | ```json 22 | { 23 | "results": 24 | [ 25 | { 26 | "latitude": ..., 27 | "longitude": ..., 28 | "elevation": ... 29 | }, 30 | ... 31 | ] 32 | } 33 | ``` 34 | 35 | 36 | ### Example: 37 | 38 | #### Request 39 | 40 | ``` 41 | curl https://api.open-elevation.com/api/v1/lookup\?locations\=10,10\|20,20\|41.161758,-8.583933 42 | ``` 43 | 44 | #### Response 45 | 46 | ```json 47 | { 48 | "results": 49 | [ 50 | { 51 | "longitude":10.0, 52 | "elevation":515, 53 | "latitude":10.0 54 | }, 55 | { 56 | "longitude":20.0, 57 | "elevation":545, 58 | "latitude":20.0 59 | }, 60 | { 61 | "latitude":41.161758, 62 | "elevation":117, 63 | "longitude":-8.583933 64 | } 65 | ] 66 | } 67 | ``` 68 | 69 | 70 | ## `POST /api/v1/lookup` 71 | 72 | Returns ("looks up") the elevation at one or more `(latitude,longitude)` points. 73 | 74 | The POST API currently has no limit 75 | 76 | ### Parameters: 77 | 78 | * A JSON (and respective headers) is required with the format: 79 | ``` 80 | { 81 | "locations": 82 | [ 83 | { 84 | "latitude": ..., 85 | "longitude": ... 86 | }, 87 | ... 88 | } 89 | ``` 90 | 91 | 92 | ### Response format 93 | 94 | A JSON object with a single list of results, in the `results` field is returned. Each result contains `latitude`, `longitude` and `elevation`. The results are in the same order as the request parameters. **Elevation is in meters**. 95 | 96 | If there is no recorded elevation at the provided coordinate, sea level (0 meters) is returned. 97 | 98 | ```json 99 | { 100 | "results": 101 | [ 102 | { 103 | "latitude": ..., 104 | "longitude": ..., 105 | "elevation": ... 106 | }, 107 | ... 108 | ] 109 | } 110 | ``` 111 | 112 | 113 | ### Example: 114 | 115 | #### Request 116 | 117 | ``` 118 | curl -X POST \ 119 | https://api.open-elevation.com/api/v1/lookup \ 120 | -H 'Accept: application/json' \ 121 | -H 'Content-Type: application/json' \ 122 | -d '{ 123 | "locations": 124 | [ 125 | { 126 | "latitude": 10, 127 | "longitude": 10 128 | }, 129 | { 130 | "latitude":20, 131 | "longitude": 20 132 | }, 133 | { 134 | "latitude":41.161758, 135 | "longitude":-8.583933 136 | } 137 | ] 138 | 139 | }' 140 | ``` 141 | 142 | #### Response 143 | 144 | ```json 145 | { 146 | "results": 147 | [ 148 | { 149 | "longitude":10.0, 150 | "elevation":515, 151 | "latitude":10.0 152 | }, 153 | { 154 | "longitude":20.0, 155 | "elevation":545, 156 | "latitude":20.0 157 | }, 158 | { 159 | "latitude":41.161758, 160 | "elevation":117, 161 | "longitude":-8.583933 162 | } 163 | ] 164 | } 165 | ``` 166 | -------------------------------------------------------------------------------- /download-srtm-data.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | wget -nc http://srtm.csi.cgiar.org/wp-content/uploads/files/250m/SRTM_NE_250m_TIF.rar && 4 | wget -nc http://srtm.csi.cgiar.org/wp-content/uploads/files/250m/SRTM_W_250m_TIF.rar && 5 | wget -nc http://srtm.csi.cgiar.org/wp-content/uploads/files/250m/SRTM_SE_250m_TIF.rar && 6 | unar -f SRTM_NE_250m_TIF.rar -o ../data -D && 7 | unar -f SRTM_SE_250m_TIF.rar -o ../data -D && 8 | unar -f SRTM_W_250m_TIF.rar -o ../data -D && 9 | 10 | echo 11 | echo 12 | echo ******************* 13 | echo Deleting rars 14 | echo ******************* 15 | rm -rf SRTM_W_250m_TIF.rar SRTM_SE_250m_TIF.rar SRTM_NE_250m_TIF.rar 16 | -------------------------------------------------------------------------------- /gdal_interfaces.py: -------------------------------------------------------------------------------- 1 | import gdal, osr 2 | from lazy import lazy 3 | from pprint import pprint 4 | from os import listdir 5 | from os.path import isfile, join 6 | import json 7 | from rtree import index 8 | 9 | # Originally based on https://stackoverflow.com/questions/13439357/extract-point-from-raster-in-gdal 10 | class GDALInterface(object): 11 | SEA_LEVEL = 0 12 | def __init__(self, tif_path): 13 | super(GDALInterface, self).__init__() 14 | self.tif_path = tif_path 15 | self.loadMetadata() 16 | 17 | def get_corner_coords(self): 18 | ulx, xres, xskew, uly, yskew, yres = self.geo_transform 19 | lrx = ulx + (self.src.RasterXSize * xres) 20 | lry = uly + (self.src.RasterYSize * yres) 21 | return { 22 | 'TOP_LEFT': (ulx, uly), 23 | 'TOP_RIGHT': (lrx, uly), 24 | 'BOTTOM_LEFT': (ulx, lry), 25 | 'BOTTOM_RIGHT': (lrx, lry), 26 | } 27 | 28 | def loadMetadata(self): 29 | # open the raster and its spatial reference 30 | self.src = gdal.Open(self.tif_path) 31 | 32 | if self.src is None: 33 | raise Exception('Could not load GDAL file "%s"' % self.tif_path) 34 | spatial_reference_raster = osr.SpatialReference(self.src.GetProjection()) 35 | 36 | # get the WGS84 spatial reference 37 | spatial_reference = osr.SpatialReference() 38 | spatial_reference.ImportFromEPSG(4326) # WGS84 39 | 40 | # coordinate transformation 41 | self.coordinate_transform = osr.CoordinateTransformation(spatial_reference, spatial_reference_raster) 42 | gt = self.geo_transform = self.src.GetGeoTransform() 43 | dev = (gt[1] * gt[5] - gt[2] * gt[4]) 44 | self.geo_transform_inv = (gt[0], gt[5] / dev, -gt[2] / dev, 45 | gt[3], -gt[4] / dev, gt[1] / dev) 46 | 47 | 48 | 49 | @lazy 50 | def points_array(self): 51 | b = self.src.GetRasterBand(1) 52 | return b.ReadAsArray() 53 | 54 | def print_statistics(self): 55 | print(self.src.GetRasterBand(1).GetStatistics(True, True)) 56 | 57 | 58 | def lookup(self, lat, lon): 59 | try: 60 | 61 | # get coordinate of the raster 62 | xgeo, ygeo, zgeo = self.coordinate_transform.TransformPoint(lon, lat, 0) 63 | 64 | # convert it to pixel/line on band 65 | u = xgeo - self.geo_transform_inv[0] 66 | v = ygeo - self.geo_transform_inv[3] 67 | # FIXME this int() is probably bad idea, there should be half cell size thing needed 68 | xpix = int(self.geo_transform_inv[1] * u + self.geo_transform_inv[2] * v) 69 | ylin = int(self.geo_transform_inv[4] * u + self.geo_transform_inv[5] * v) 70 | 71 | # look the value up 72 | v = self.points_array[ylin, xpix] 73 | 74 | return v if v != -32768 else self.SEA_LEVEL 75 | except Exception as e: 76 | print(e) 77 | return self.SEA_LEVEL 78 | 79 | def close(self): 80 | self.src = None 81 | 82 | def __enter__(self): 83 | return self 84 | 85 | def __exit__(self, type, value, traceback): 86 | self.close() 87 | 88 | class GDALTileInterface(object): 89 | def __init__(self, tiles_folder, summary_file, open_interfaces_size=5): 90 | super(GDALTileInterface, self).__init__() 91 | self.tiles_folder = tiles_folder 92 | self.summary_file = summary_file 93 | self.index = index.Index() 94 | self.cached_open_interfaces = [] 95 | self.cached_open_interfaces_dict = {} 96 | self.open_interfaces_size = open_interfaces_size 97 | 98 | def _open_gdal_interface(self, path): 99 | if path in self.cached_open_interfaces_dict: 100 | interface = self.cached_open_interfaces_dict[path] 101 | self.cached_open_interfaces.remove(path) 102 | self.cached_open_interfaces += [path] 103 | 104 | return interface 105 | else: 106 | 107 | interface = GDALInterface(path) 108 | self.cached_open_interfaces += [path] 109 | self.cached_open_interfaces_dict[path] = interface 110 | 111 | if len(self.cached_open_interfaces) > self.open_interfaces_size: 112 | last_interface_path = self.cached_open_interfaces.pop(0) 113 | last_interface = self.cached_open_interfaces_dict[last_interface_path] 114 | last_interface.close() 115 | 116 | self.cached_open_interfaces_dict[last_interface_path] = None 117 | del self.cached_open_interfaces_dict[last_interface_path] 118 | 119 | return interface 120 | 121 | def _all_files(self): 122 | return [f for f in listdir(self.tiles_folder) if isfile(join(self.tiles_folder, f)) and f.endswith(u'.tif')] 123 | 124 | def create_summary_json(self): 125 | all_coords = [] 126 | for file in self._all_files(): 127 | 128 | full_path = join(self.tiles_folder,file) 129 | i = self._open_gdal_interface(full_path) 130 | coords = i.get_corner_coords() 131 | all_coords += [ 132 | { 133 | 'file': full_path, 134 | 'coords': ( coords['BOTTOM_RIGHT'][1], # latitude min 135 | coords['TOP_RIGHT'][1], # latitude max 136 | coords['TOP_LEFT'][0], # longitude min 137 | coords['TOP_RIGHT'][0], # longitude max 138 | 139 | ) 140 | } 141 | ] 142 | 143 | with open(self.summary_file, 'w') as f: 144 | json.dump(all_coords, f) 145 | 146 | self.all_coords = all_coords 147 | 148 | self._build_index() 149 | 150 | def read_summary_json(self): 151 | with open(self.summary_file) as f: 152 | self.all_coords = json.load(f) 153 | 154 | self._build_index() 155 | 156 | def lookup(self, lat, lng): 157 | 158 | nearest = list(self.index.nearest((lat, lng), 1, objects=True)) 159 | 160 | if not nearest: 161 | raise Exception('Invalid latitude/longitude') 162 | else: 163 | coords = nearest[0].object 164 | 165 | gdal_interface = self._open_gdal_interface(coords['file']) 166 | return int(gdal_interface.lookup(lat, lng)) 167 | 168 | def _build_index(self): 169 | index_id = 1 170 | for e in self.all_coords: 171 | e['index_id'] = index_id 172 | left, bottom, right, top = (e['coords'][0], e['coords'][2], e['coords'][1], e['coords'][3]) 173 | self.index.insert( index_id, (left, bottom, right, top), obj=e) 174 | -------------------------------------------------------------------------------- /gdal_interfaces.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Developer66/open-elevation/1d21902eb5cb9b80c0760466592ee004702224b2/gdal_interfaces.pyc -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The GNU General Public License, Version 2, June 1991 (GPLv2) 2 | ============================================================ 3 | 4 | > Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | > 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | 10 | 11 | Preamble 12 | -------- 13 | 14 | The licenses for most software are designed to take away your freedom to share 15 | and change it. By contrast, the GNU General Public License is intended to 16 | guarantee your freedom to share and change free software--to make sure the 17 | software is free for all its users. This General Public License applies to most 18 | of the Free Software Foundation's software and to any other program whose 19 | authors commit to using it. (Some other Free Software Foundation software is 20 | covered by the GNU Lesser General Public License instead.) You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not price. Our 24 | General Public Licenses are designed to make sure that you have the freedom to 25 | distribute copies of free software (and charge for this service if you wish), 26 | that you receive source code or can get it if you want it, that you can change 27 | the software or use pieces of it in new free programs; and that you know you can 28 | do these things. 29 | 30 | To protect your rights, we need to make restrictions that forbid anyone to deny 31 | you these rights or to ask you to surrender the rights. These restrictions 32 | translate to certain responsibilities for you if you distribute copies of the 33 | software, or if you modify it. 34 | 35 | For example, if you distribute copies of such a program, whether gratis or for a 36 | fee, you must give the recipients all the rights that you have. You must make 37 | sure that they, too, receive or can get the source code. And you must show them 38 | these terms so they know their rights. 39 | 40 | We protect your rights with two steps: (1) copyright the software, and (2) offer 41 | you this license which gives you legal permission to copy, distribute and/or 42 | modify the software. 43 | 44 | Also, for each author's protection and ours, we want to make certain that 45 | everyone understands that there is no warranty for this free software. If the 46 | software is modified by someone else and passed on, we want its recipients to 47 | know that what they have is not the original, so that any problems introduced by 48 | others will not reflect on the original authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software patents. We wish 51 | to avoid the danger that redistributors of a free program will individually 52 | obtain patent licenses, in effect making the program proprietary. To prevent 53 | this, we have made it clear that any patent must be licensed for everyone's free 54 | use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and modification 57 | follow. 58 | 59 | 60 | Terms And Conditions For Copying, Distribution And Modification 61 | --------------------------------------------------------------- 62 | 63 | **0.** This License applies to any program or other work which contains a notice 64 | placed by the copyright holder saying it may be distributed under the terms of 65 | this General Public License. The "Program", below, refers to any such program or 66 | work, and a "work based on the Program" means either the Program or any 67 | derivative work under copyright law: that is to say, a work containing the 68 | Program or a portion of it, either verbatim or with modifications and/or 69 | translated into another language. (Hereinafter, translation is included without 70 | limitation in the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not covered by 73 | this License; they are outside its scope. The act of running the Program is not 74 | restricted, and the output from the Program is covered only if its contents 75 | constitute a work based on the Program (independent of having been made by 76 | running the Program). Whether that is true depends on what the Program does. 77 | 78 | **1.** You may copy and distribute verbatim copies of the Program's source code 79 | as you receive it, in any medium, provided that you conspicuously and 80 | appropriately publish on each copy an appropriate copyright notice and 81 | disclaimer of warranty; keep intact all the notices that refer to this License 82 | and to the absence of any warranty; and give any other recipients of the Program 83 | a copy of this License along with the Program. 84 | 85 | You may charge a fee for the physical act of transferring a copy, and you may at 86 | your option offer warranty protection in exchange for a fee. 87 | 88 | **2.** You may modify your copy or copies of the Program or any portion of it, 89 | thus forming a work based on the Program, and copy and distribute such 90 | modifications or work under the terms of Section 1 above, provided that you also 91 | meet all of these conditions: 92 | 93 | * **a)** You must cause the modified files to carry prominent notices stating 94 | that you changed the files and the date of any change. 95 | 96 | * **b)** You must cause any work that you distribute or publish, that in whole 97 | or in part contains or is derived from the Program or any part thereof, to 98 | be licensed as a whole at no charge to all third parties under the terms of 99 | this License. 100 | 101 | * **c)** If the modified program normally reads commands interactively when 102 | run, you must cause it, when started running for such interactive use in the 103 | most ordinary way, to print or display an announcement including an 104 | appropriate copyright notice and a notice that there is no warranty (or 105 | else, saying that you provide a warranty) and that users may redistribute 106 | the program under these conditions, and telling the user how to view a copy 107 | of this License. (Exception: if the Program itself is interactive but does 108 | not normally print such an announcement, your work based on the Program is 109 | not required to print an announcement.) 110 | 111 | These requirements apply to the modified work as a whole. If identifiable 112 | sections of that work are not derived from the Program, and can be reasonably 113 | considered independent and separate works in themselves, then this License, and 114 | its terms, do not apply to those sections when you distribute them as separate 115 | works. But when you distribute the same sections as part of a whole which is a 116 | work based on the Program, the distribution of the whole must be on the terms of 117 | this License, whose permissions for other licensees extend to the entire whole, 118 | and thus to each and every part regardless of who wrote it. 119 | 120 | Thus, it is not the intent of this section to claim rights or contest your 121 | rights to work written entirely by you; rather, the intent is to exercise the 122 | right to control the distribution of derivative or collective works based on the 123 | Program. 124 | 125 | In addition, mere aggregation of another work not based on the Program with the 126 | Program (or with a work based on the Program) on a volume of a storage or 127 | distribution medium does not bring the other work under the scope of this 128 | License. 129 | 130 | **3.** You may copy and distribute the Program (or a work based on it, under 131 | Section 2) in object code or executable form under the terms of Sections 1 and 2 132 | above provided that you also do one of the following: 133 | 134 | * **a)** Accompany it with the complete corresponding machine-readable source 135 | code, which must be distributed under the terms of Sections 1 and 2 above on 136 | a medium customarily used for software interchange; or, 137 | 138 | * **b)** Accompany it with a written offer, valid for at least three years, to 139 | give any third party, for a charge no more than your cost of physically 140 | performing source distribution, a complete machine-readable copy of the 141 | corresponding source code, to be distributed under the terms of Sections 1 142 | and 2 above on a medium customarily used for software interchange; or, 143 | 144 | * **c)** Accompany it with the information you received as to the offer to 145 | distribute corresponding source code. (This alternative is allowed only for 146 | noncommercial distribution and only if you received the program in object 147 | code or executable form with such an offer, in accord with Subsection b 148 | above.) 149 | 150 | The source code for a work means the preferred form of the work for making 151 | modifications to it. For an executable work, complete source code means all the 152 | source code for all modules it contains, plus any associated interface 153 | definition files, plus the scripts used to control compilation and installation 154 | of the executable. However, as a special exception, the source code distributed 155 | need not include anything that is normally distributed (in either source or 156 | binary form) with the major components (compiler, kernel, and so on) of the 157 | operating system on which the executable runs, unless that component itself 158 | accompanies the executable. 159 | 160 | If distribution of executable or object code is made by offering access to copy 161 | from a designated place, then offering equivalent access to copy the source code 162 | from the same place counts as distribution of the source code, even though third 163 | parties are not compelled to copy the source along with the object code. 164 | 165 | **4.** You may not copy, modify, sublicense, or distribute the Program except as 166 | expressly provided under this License. Any attempt otherwise to copy, modify, 167 | sublicense or distribute the Program is void, and will automatically terminate 168 | your rights under this License. However, parties who have received copies, or 169 | rights, from you under this License will not have their licenses terminated so 170 | long as such parties remain in full compliance. 171 | 172 | **5.** You are not required to accept this License, since you have not signed 173 | it. However, nothing else grants you permission to modify or distribute the 174 | Program or its derivative works. These actions are prohibited by law if you do 175 | not accept this License. Therefore, by modifying or distributing the Program (or 176 | any work based on the Program), you indicate your acceptance of this License to 177 | do so, and all its terms and conditions for copying, distributing or modifying 178 | the Program or works based on it. 179 | 180 | **6.** Each time you redistribute the Program (or any work based on the 181 | Program), the recipient automatically receives a license from the original 182 | licensor to copy, distribute or modify the Program subject to these terms and 183 | conditions. You may not impose any further restrictions on the recipients' 184 | exercise of the rights granted herein. You are not responsible for enforcing 185 | compliance by third parties to this License. 186 | 187 | **7.** If, as a consequence of a court judgment or allegation of patent 188 | infringement or for any other reason (not limited to patent issues), conditions 189 | are imposed on you (whether by court order, agreement or otherwise) that 190 | contradict the conditions of this License, they do not excuse you from the 191 | conditions of this License. If you cannot distribute so as to satisfy 192 | simultaneously your obligations under this License and any other pertinent 193 | obligations, then as a consequence you may not distribute the Program at all. 194 | For example, if a patent license would not permit royalty-free redistribution of 195 | the Program by all those who receive copies directly or indirectly through you, 196 | then the only way you could satisfy both it and this License would be to refrain 197 | entirely from distribution of the Program. 198 | 199 | If any portion of this section is held invalid or unenforceable under any 200 | particular circumstance, the balance of the section is intended to apply and the 201 | section as a whole is intended to apply in other circumstances. 202 | 203 | It is not the purpose of this section to induce you to infringe any patents or 204 | other property right claims or to contest validity of any such claims; this 205 | section has the sole purpose of protecting the integrity of the free software 206 | distribution system, which is implemented by public license practices. Many 207 | people have made generous contributions to the wide range of software 208 | distributed through that system in reliance on consistent application of that 209 | system; it is up to the author/donor to decide if he or she is willing to 210 | distribute software through any other system and a licensee cannot impose that 211 | choice. 212 | 213 | This section is intended to make thoroughly clear what is believed to be a 214 | consequence of the rest of this License. 215 | 216 | **8.** If the distribution and/or use of the Program is restricted in certain 217 | countries either by patents or by copyrighted interfaces, the original copyright 218 | holder who places the Program under this License may add an explicit 219 | geographical distribution limitation excluding those countries, so that 220 | distribution is permitted only in or among countries not thus excluded. In such 221 | case, this License incorporates the limitation as if written in the body of this 222 | License. 223 | 224 | **9.** The Free Software Foundation may publish revised and/or new versions of 225 | the General Public License from time to time. Such new versions will be similar 226 | in spirit to the present version, but may differ in detail to address new 227 | problems or concerns. 228 | 229 | Each version is given a distinguishing version number. If the Program specifies 230 | a version number of this License which applies to it and "any later version", 231 | you have the option of following the terms and conditions either of that version 232 | or of any later version published by the Free Software Foundation. If the 233 | Program does not specify a version number of this License, you may choose any 234 | version ever published by the Free Software Foundation. 235 | 236 | **10.** If you wish to incorporate parts of the Program into other free programs 237 | whose distribution conditions are different, write to the author to ask for 238 | permission. For software which is copyrighted by the Free Software Foundation, 239 | write to the Free Software Foundation; we sometimes make exceptions for this. 240 | Our decision will be guided by the two goals of preserving the free status of 241 | all derivatives of our free software and of promoting the sharing and reuse of 242 | software generally. 243 | 244 | 245 | No Warranty 246 | ----------- 247 | 248 | **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR 249 | THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE 250 | STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM 251 | "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, 252 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 253 | PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 254 | PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 255 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 256 | 257 | **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 258 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 259 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 260 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR 261 | INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA 262 | BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 263 | FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER 264 | OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 265 | -------------------------------------------------------------------------------- /open-elevation.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Open-Elevation Server 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | User=<> 8 | WorkingDirectory=/home/<>/<> 9 | ExecStart=/usr/bin/env python <>/server.py 10 | Restart=always 11 | RestartSec=1 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | lazy==1.3 2 | GDAL==2.1.3 3 | bottle==0.12.13 4 | rtree==0.8.3 5 | gunicorn==19.7.1 -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import bottle 4 | from bottle import route, run, request, response, hook 5 | 6 | from gdal_interfaces import GDALTileInterface 7 | 8 | 9 | class InternalException(ValueError): 10 | """ 11 | Utility exception class to handle errors internally and return error codes to the client 12 | """ 13 | pass 14 | 15 | 16 | """ 17 | Initialize a global interface. This can grow quite large, because it has a cache. 18 | """ 19 | interface = GDALTileInterface('data/', 'data/summary.json') 20 | interface.create_summary_json() 21 | 22 | def get_elevation(lat, lng): 23 | """ 24 | Get the elevation at point (lat,lng) using the currently opened interface 25 | :param lat: 26 | :param lng: 27 | :return: 28 | """ 29 | try: 30 | elevation = interface.lookup(lat, lng) 31 | except: 32 | return { 33 | 'latitude': lat, 34 | 'longitude': lng, 35 | 'error': 'No such coordinate (%s, %s)' % (lat, lng) 36 | } 37 | 38 | return { 39 | 'latitude': lat, 40 | 'longitude': lng, 41 | 'elevation': elevation 42 | } 43 | 44 | 45 | @hook('after_request') 46 | def enable_cors(): 47 | """ 48 | Enable CORS support. 49 | :return: 50 | """ 51 | response.headers['Access-Control-Allow-Origin'] = '*' 52 | response.headers['Access-Control-Allow-Methods'] = 'PUT, GET, POST, DELETE, OPTIONS' 53 | response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token' 54 | 55 | 56 | def lat_lng_from_location(location_with_comma): 57 | """ 58 | Parse the latitude and longitude of a location in the format "xx.xxx,yy.yyy" (which we accept as a query string) 59 | :param location_with_comma: 60 | :return: 61 | """ 62 | try: 63 | lat, lng = [float(i) for i in location_with_comma.split(',')] 64 | return lat, lng 65 | except: 66 | raise InternalException(json.dumps({'error': 'Bad parameter format "%s".' % location_with_comma})) 67 | 68 | 69 | def query_to_locations(): 70 | """ 71 | Grab a list of locations from the query and turn them into [(lat,lng),(lat,lng),...] 72 | :return: 73 | """ 74 | locations = request.query.locations 75 | if not locations: 76 | raise InternalException(json.dumps({'error': '"Locations" is required.'})) 77 | 78 | return [lat_lng_from_location(l) for l in locations.split('|')] 79 | 80 | 81 | def body_to_locations(): 82 | """ 83 | Grab a list of locations from the body and turn them into [(lat,lng),(lat,lng),...] 84 | :return: 85 | """ 86 | try: 87 | locations = request.json.get('locations', None) 88 | except Exception: 89 | raise InternalException(json.dumps({'error': 'Invalid JSON.'})) 90 | 91 | if not locations: 92 | raise InternalException(json.dumps({'error': '"Locations" is required in the body.'})) 93 | 94 | latlng = [] 95 | for l in locations: 96 | try: 97 | latlng += [ (l['latitude'],l['longitude']) ] 98 | except KeyError: 99 | raise InternalException(json.dumps({'error': '"%s" is not in a valid format.' % l})) 100 | 101 | return latlng 102 | 103 | 104 | def do_lookup(get_locations_func): 105 | """ 106 | Generic method which gets the locations in [(lat,lng),(lat,lng),...] format by calling get_locations_func 107 | and returns an answer ready to go to the client. 108 | :return: 109 | """ 110 | try: 111 | locations = get_locations_func() 112 | return {'results': [get_elevation(lat, lng) for (lat, lng) in locations]} 113 | except InternalException as e: 114 | response.status = 400 115 | response.content_type = 'application/json' 116 | return e.args[0] 117 | 118 | # Base Endpoint 119 | URL_ENDPOINT = '/api/v1/lookup' 120 | 121 | # For CORS 122 | @route(URL_ENDPOINT, method=['OPTIONS']) 123 | def cors_handler(): 124 | return {} 125 | 126 | @route(URL_ENDPOINT, method=['GET']) 127 | def get_lookup(): 128 | """ 129 | GET method. Uses query_to_locations. 130 | :return: 131 | """ 132 | return do_lookup(query_to_locations) 133 | 134 | 135 | @route(URL_ENDPOINT, method=['POST']) 136 | def post_lookup(): 137 | """ 138 | GET method. Uses body_to_locations. 139 | :return: 140 | """ 141 | return do_lookup(body_to_locations) 142 | 143 | run(host='0.0.0.0', port=10000, server='gunicorn', workers=4) -------------------------------------------------------------------------------- /swagger/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "2", 5 | "title": "Open Elevation Public API" 6 | }, 7 | "schemes": [ 8 | "http" 9 | ], 10 | "host": "localhost", 11 | "basePath": "/api/v1", 12 | "paths": { 13 | "/lookup": { 14 | "post": { 15 | "consumes": [ 16 | "application/json" 17 | ], 18 | "produces": [ 19 | "application/json" 20 | ], 21 | "parameters": [ 22 | { 23 | "in": "body", 24 | "name": "locations", 25 | "description": "locations: [{latitude: 42.216667,longitude: 27.416667}]", 26 | "type": "string" 27 | } 28 | ], 29 | "responses": { 30 | "200": { 31 | "description": "Successful response", 32 | "schema": { 33 | "title": "results", 34 | "type": "object", 35 | "properties": { 36 | "success": { 37 | "type": "string" 38 | }, 39 | "results": { 40 | "type": "array", 41 | "items": { 42 | "type": "object", 43 | "properties": { 44 | "latitude": { 45 | "type": "number" 46 | }, 47 | "longitude": { 48 | "type": "number" 49 | }, 50 | "elevation": { 51 | "type": "integer" 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | }, 59 | "400": { 60 | "description": "Bad request" 61 | } 62 | } 63 | }, 64 | "get": { 65 | "description": "List altitude for locations.\n", 66 | "parameters": [ 67 | { 68 | "in": "query", 69 | "name": "locations", 70 | "description": "locations=42.216667,27.416667", 71 | "type": "string" 72 | } 73 | ], 74 | "responses": { 75 | "200": { 76 | "description": "Successful response", 77 | "schema": { 78 | "title": "results", 79 | "type": "object", 80 | "properties": { 81 | "success": { 82 | "type": "string" 83 | }, 84 | "results": { 85 | "type": "array", 86 | "items": { 87 | "type": "object", 88 | "properties": { 89 | "latitude": { 90 | "type": "number" 91 | }, 92 | "longitude": { 93 | "type": "number" 94 | }, 95 | "elevation": { 96 | "type": "integer" 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | }, 104 | "400": { 105 | "description": "Bad request" 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | --------------------------------------------------------------------------------