Proposed Pull Request Change

title titleSuffix description author ms.author ms.date ms.topic ms.service ms.subservice ms.custom
Create elevation data & services using open data Microsoft Azure Maps a guide to help developers build Elevation services and tiles using open data on the Microsoft Azure Cloud. farazgis fsiddiqui 3/17/2023 quickstart azure-maps general sfi-image-nochange
📄 Document Links
GitHub View on GitHub Microsoft Learn View on Microsoft Learn
Content Truncation Detected
The generated rewrite appears to be incomplete.
Original lines: -
Output lines: -
Ratio: -
Raw New Markdown
Generating updated version of doc...
Rendered New Markdown
Generating updated version of doc...
+0 -0
+0 -0
--- title: Create elevation data & services using open data titleSuffix: Microsoft Azure Maps description: a guide to help developers build Elevation services and tiles using open data on the Microsoft Azure Cloud. author: farazgis ms.author: fsiddiqui ms.date: 3/17/2023 ms.topic: quickstart ms.service: azure-maps ms.subservice: general ms.custom: sfi-image-nochange --- # Create elevation data & services This guide describes how to use USGS worldwide DEM data from their SRTM mission with 30m accuracy to build an Elevation service on the [Microsoft Azure Cloud]. This article describes how to: - Create Contour line vector tiles and RGB-encoded DEM tiles. - Create Elevation API using Azure Function and RGB-encoded DEM tiles from Azure Blob Storage. - Create Contour line vector tile service using Azure Function and PostgreSQL. ## Prerequisites This guide requires the use of the following third-party software and data: - USGS Data. DEM data can be downloaded as GeoTiff with 1 arc second coverage per tile through the [USGS EarthExplorer]. This requires an EarthExplorer account, but the data can be downloaded for free. - The [QGIS] desktop GIS application is used to process and smoothen the Raster tiles. QGIS is free to download and use. This guide uses QGIS version 3.26.2-Buenos Aires. - The [rio-rgbify] Python package, developed by MapBox, is used to encode the GeoTIFF as RGB. - [PostgreSQL] database with the [PostGIS] spatial extension. ## Create Contour line vector tiles and RGB-encoded DEM tiles This guide uses the 36 tiles covering the state of Washington, available from [USGS EarthExplorer]. ### Download raster tiles from USGS EarthExplorer #### Search criteria Select the region that you want raster tiles for. For demonstration purposes, this guide uses the "Polygon" method to select the region on the map. 1. Navigate to the [USGS EarthExplorer]. 1. In the **Search Criteria** tab, select **Polygon** then select anywhere on the map to create the boundary. :::image type="content" source="./media/elevation-services/create-polygon.png" alt-text="A screenshot showing the search criteria tab in the USGS earth explorer web site." lightbox="./media/elevation-services/create-polygon.png"::: #### Data sets 1. Select the **Data Sets** tab. 1. Select **SRTM 1 Arc-Second Global** from the **Digital Elevations** section. :::image type="content" source="./media/elevation-services/data-sets.png" alt-text="A screenshot showing the data sets tab in the USGS earth explorer web site." lightbox="./media/elevation-services/data-sets.png"::: #### Results 1. Select **Results >>** to view the tiles for the selected region and data set. 1. The list of downloadable tiles appear on the results page. To download only tiles you want, select the **Download Options** button on the result card for each tile, selecting the option **GeoTIFF 1 Arc-Second** and repeat this step for the remaining tiles. :::image type="content" source="./media/elevation-services/results-export.png" alt-text="A screenshot showing the results tab in the USGS earth explorer web site." lightbox="./media/elevation-services/results-export.png"::: 1. Alternatively, use the bulk download option and select **GeoTIFF 1 Arc-second**. ### Add raster tiles to QGIS Once you have the raster tiles you need, you can import them in QGIS. 1. Add raster tiles to QGIS by dragging the files to the **QGIS layer** tab or selecting **Add Layer** in the **Layer** menu. :::image type="content" source="./media/elevation-services/add-raster-tiles-qgis.png" alt-text="A screenshot showing raster tiles in QGIS." lightbox="./media/elevation-services/add-raster-tiles-qgis.png"::: 2. When the raster layers are loaded into QGIS, there can be different shades of tiles. Fix this by merging the raster layers, which result in a single smooth raster image in GeoTIFF format. To do this, select **Miscellaneous** from the **Raster** menu, then **Merge...** :::image type="content" source="./media/elevation-services/merge-raster-layers.png" alt-text="A screenshot showing the merge raster menu in QGIS."::: 3. Reproject the merged raster layer to EPSG:3857 (WGS84 / Pseudo-Mercator) using **Save Raster Layer as** right-click to access the merged raster layer in the **table of content** -> **Export** -> **Save As** option. EPSG:3857 is required to use it with [Azure Maps Web SDK]. :::image type="content" source="./media/elevation-services/save-raster-layer.png" alt-text="A screenshot showing how the merge raster layers menu in QGIS."::: 4. If you only want to create contour line vector tiles, you can skip the following steps and go to [Create Contour line vector tile service using Azure Function and PostgreSQL]. 5. To create an Elevation API, the next step is to RGB-Encode the GeoTIFF. This can be done using [rio-rgbify], developed by MapBox. There are some challenges running this tool directly in Windows, so it's easier to run from WSL. Below are the steps in Ubuntu on WSL: ```bash sudo apt get update sudo apt get upgrade sudo apt install python3-pip pip install rio-rgbify PATH="$PATH:/home/<user /.local/bin" # The following two steps are only necessary when mounting an external hard drive or USB flash drive: sudo mkdir /mnt/f sudo mount -t drvfs D: /mnt/f rio rgbify -b -10000 -i 0.1 wa_1arc_v3_merged_3857.tif wa_1arc_v3_merged_3857_rgb.tif # The following steps are only necessary when unmounting an external hard drive or USB flash drive: cd \~ sudo umount /mnt/f/ ``` :::image type="content" source="./media/elevation-services/rgb-encoded-geotiff.png" alt-text="A screenshot showing the RGB-encoded GeoTIFF in QGIS."::: The RGB-encoded GeoTIFF allows you to retrieve R, G and B values for a pixel and calculate the elevation from these values: `elevation (m) = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)` 6. Next, create a tile set to use with the map control and/or use it to get Elevation for any geographic coordinates within the map extent of the tile set. The tile set can be created in QGIS using the **Generate XYZ tiles (Directory)** tool. :::image type="content" source="./media/elevation-services/generate-xyz-tiles-tool.png" alt-text="A screenshot showing the Generate XYZ tiles (Directory) tool in QGIS."::: 7. Save the location of the tile set, you'll use it in the next Section. ## Create Elevation API using Azure Function and RGB-encoded DEM tiles from Azure Blob Storage The RGB encoded DEM Tiles need to be uploaded to a database storage before it can be used with the Azure Functions to create an API. 1. Upload the tiles to Azure Blob Storage. [Azure Storage Explorer] is a useful tool for this purpose. :::image type="content" source="./media/elevation-services/azure-storage-explorer.png" alt-text="A screenshot showing the Microsoft Azure Storage Explorer."::: Uploading tiles to Azure Blob Storage can take several minutes to complete. 1. Once the upload is complete, you can create Azure Function to build an API that returns elevation for a given geographic coordinate. This function receives a coordinate pair, determine the tile that covers it at zoom level 14, then determine the pixel coordinates within that tile that matches the geographic coordinates. It then retrieves the tile, gets the RGB values for that pixel, then uses the following formula to determine the elevation: `elevation (m) = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)` ```python import logging import json import azure.functions as func from PIL import Image import requests from io import BytesIO import math def main(req: func.HttpRequest) -> func.HttpResponse: logging.info('Python HTTP trigger function processed a request.') # http://localhost:7071/api/GetElevationPoint?lng=-122.01911&lat=47.67091 zoom = 14 lng = float(req.params.get('lng')) lat = float(req.params.get('lat')) logging.info('Lng: ' + str(lng) + ' / lat: ' + str(lat)) # Calculate th global pixel x and y coordinates for a lng / lat gx = (lng + 180) / 360 sinLat = math.sin(lat * math.pi / 180) gy = 0.5 - math.log((1 + sinLat) / (1 - sinLat)) / (4 * math.pi) mapSize = math.ceil(256 * math.pow(2, zoom)); gxc = min(max(gx * mapSize + 0.5, 0), mapSize - 1); gyc = min(max(gy * mapSize + 0.5, 0), mapSize - 1); # Calculate the tile x and y covering the lng / lat tileX = int(gxc / 256) tileY = int(gyc / 256) # Calculate the pixel coordinates for the tile covering the lng / lat tilePixelX = math.floor(gxc - (tileX * 256)) tilePixelY = math.floor(gyc - (tileY * 256)) response = requests.get("{BlobStorageURL}" + str(zoom) + "/" + str(tileX) + "/" + str(tileY) + ".png") im = Image.open(BytesIO(response.content)) pix = im.load() r = pix[tilePixelX,tilePixelY][0] g = pix[tilePixelX,tilePixelY][1] b = pix[tilePixelX,tilePixelY][2] # elevation (m) = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1) ele = -10000 + ((r * 256 * 256 + g * 256 + b) * 0.1) jsonRes = {"elevation": + ele} logging.info('Response: ' + json.dumps(jsonRes)) if lng and lat: return func.HttpResponse( json.dumps(jsonRes), mimetype="application/json", ) else: return func.HttpResponse( "ERROR: Missing parameter!", status_code=400 ) ``` To see the results of the code sample, run it locally: ```http localhost:7071/api/GetElevationPoint?lng=-122.01911&lat=47.67091` ``` ## Create Contour line vector tile service using Azure Function and PostgreSQL This section describes the steps to create and process contour lines in QGIS, upload them to PostgreSQL then create an Azure Function to Query PostgreSQL to return vector tiles. 1. In QGIS, open the merged raster tiles in the EPSG:4326 projection created in step 3 of [Create Contour line vector tiles and RGB-encoded DEM tiles]. 1. Select **Extraction -> Contour** from the **Raster** menu to open the Contour tool. :::image type="content" source="./media/elevation-services/contour-tool.png" alt-text="A screenshot showing the contour dialog in QGIS."::: Selecting **Run** creates contour lines and add them as a layer to the map. some of the contour line edges may appear a little rough. This will be addressed in the next step. :::image type="content" source="./media/elevation-services/contour-lines.png" alt-text="A screenshot showing a map with contours in QGIS."::: 1. Select **Toolbox** from the **Processing** menu to bring up the **Processing Toolbox**. 1. Then select **Smooth** in the **Vector geometry** section of the **Processing Toolbox**. :::image type="content" source="./media/elevation-services/smooth-dialog.png" alt-text="A screenshot showing the smooth dialog in QGIS."::: > [!NOTE] > Contour line smoothing can be substantially improved but at the cost of increased file-size. 1. Load the contour lines to the database. This guide uses the free version of [PostgreSQL] database that runs on localhost. You can also load them to the Azure Database for PostgreSQL. The next step requires a PostgreSQL database with [PostGIS] extension. 1. To create a connection from QGIS to PostgreSQL, select **Add Layer** -> **Add PostGIS Layers** from the **Layer** menu, then select the **New** button. :::image type="content" source="./media/elevation-services/create-new-postgis-connection.png" alt-text="A screenshot showing the create new PostGIG connection dialog in QGIS."::: 1. Next, load Data from QGIS to PostgreSQL using the Database Manager in QGIS. To do this, select **DB Manager** from the **Database** menu. :::image type="content" source="./media/elevation-services/db-manager.png" alt-text="A screenshot showing the DB Manager in QGIS."::: 1. Connect to the PostGIS database and select **Import Layer/File...** to Import contour lines to the database. :::image type="content" source="./media/elevation-services/import-vector-layer.png" alt-text="A screenshot showing the import vector dialog in QGIS."::: 1. You can now use an Azure Function to Query PostgreSQL and return vector tiles for the contour lines. The tile server can be used with the Azure Maps Web SDK to create a web app that displays contour lines on the map. ```python import logging from wsgiref import headers import azure.functions as func import psycopg2 # Database to connect to DATABASE = { 'user': 'postgres', 'password': '{password}', 'host': 'localhost', 'port': '5432', 'database': '{database}' } def main(req: func.HttpRequest) -> func.HttpResponse: logging.info('Python HTTP trigger function processed a request.') DATABASE_CONNECTION = None # get url parameters http://localhost:7071/api/tileserver?zoom={z}&x={x}&y={y} # http://localhost:7071/api/tileserver?zoom=16&x=10556&y=22870 zoom = int(req.params.get('zoom')) x = int(req.params.get('x')) y = int(req.params.get('y')) table = req.params.get('table') # calculate the envelope of the tile # Width of world in EPSG:3857 worldMercMax = 20037508.3427892 worldMercMin = -1 * worldMercMax worldMercSize = worldMercMax - worldMercMin # Width in tiles worldTileSize = 2 ** zoom # Tile width in EPSG:3857 tileMercSize = worldMercSize / worldTileSize # Calculate geographic bounds from tile coordinates # XYZ tile coordinates are in "image space" so origin is # top-left, not bottom right xmin = worldMercMin + tileMercSize * x xmax = worldMercMin + tileMercSize * (x + 1) ymin = worldMercMax - tileMercSize * (y + 1) ymax = worldMercMax - tileMercSize * y # Generate SQL to materialize a query envelope in EPSG:3857. # Densify the edges a little so the envelope can be # safely converted to other coordinate systems. DENSIFY_FACTOR = 4 segSize = (xmax - xmin)/DENSIFY_FACTOR sql01 = 'ST_Segmentize(ST_MakeEnvelope(' + str(xmin) + ', ' + str(ymin) + ', ' + str(xmax) + ', ' + str(ymax) + ', 3857), ' + str(segSize) +')' # Generate a SQL query to pull a tile worth of MVT data # from the table of interest. # Materialize the bounds # Select the relevant geometry and clip to MVT bounds # Convert to MVT format sql02 = 'WITH bounds AS (SELECT ' + sql01 + ' AS geom, ' + sql01 + '::box2d AS b2d), mvtgeom AS (SELECT ST_AsMVTGeom(ST_Transform(t.geom, 3857), bounds.b2d) AS geom, elev FROM contourlines_smooth t, bounds WHERE ST_Intersects(t.geom, ST_Transform(bounds.geom, 4326))) SELECT ST_AsMVT(mvtgeom.*) FROM mvtgeom' # Run tile query SQL and return error on failure conditions # Make and hold connection to database if not DATABASE_CONNECTION: try: DATABASE_CONNECTION = psycopg2.connect(**DATABASE) logging.info('Connected to database.') except (Exception, psycopg2.Error) as error: logging.error('ERROR: Cannot connect to database.') # Query for MVT with DATABASE_CONNECTION.cursor() as cur: cur.execute(sql02) if not cur: logging.error('ERROR: SQL Query failed.') pbf = cur.fetchone()[0] logging.info('Queried database') if zoom and x and y: return func.HttpResponse( # f"This HTTP triggered function executed successfully.\n\nzoom={zoom}\nx={x}\ny={y}\n\nxmin={xmin}\nxmax={xmax}\nymin={ymin}\nymax={ymax}\n\nsql01={sql01}\n\nsql02={sql02}", bytes(pbf), status_code=200, headers={"Content-type": "application/vnd.mapbox-vector-tile","Access-Control-Allow-Origin": "*"} ) else: return func.HttpResponse( "ERROR: Missing parameter!", status_code=400 ) ``` To see the results of the code sample, run it locally: ```http http://localhost:7071/api/tileserver?zoom={z}&x={x}&y={y} ``` [Microsoft Azure Cloud]: https://azure.microsoft.com/pricing/purchase-options/azure-account?cid=msft_learn [USGS EarthExplorer]: https://earthexplorer.usgs.gov/ [QGIS]: https://www.qgis.org/en/site/forusers/download.html [rio-rgbify]: https://pypi.org/project/rio-rgbify/ [PostgreSQL]: https://www.postgresql.org/download/ [PostGIS]: https://postgis.net/documentation/getting_started/#learn-about-spatial-sql [Azure Maps Web SDK]: about-azure-maps.md#web-sdk [Create Contour line vector tiles and RGB-encoded DEM tiles]: #create-contour-line-vector-tiles-and-rgb-encoded-dem-tiles [Create Contour line vector tile service using Azure Function and PostgreSQL]: #create-contour-line-vector-tile-service-using-azure-function-and-postgresql [Azure Storage Explorer]: https://azure.microsoft.com/products/storage/storage-explorer/
Success! Branch created successfully. Create Pull Request on GitHub
Error: