Rhino Mapbox Contours

Hi all,

I am trying to get some mapbox curves into rhino via the mapbox api and rhino3dm library, however I can’t seem to get rid of the tile boundaries which come along with the data. I’ve tried a few methods such as removing the last and first points or setting a tolerance, but none of these works. I wouldn’t even mind a grasshopper solution if there is one.

Thanks

import requests
import math
import mapbox_vector_tile
from pyproj import *
import mercantile
import rhino3dm as rh

lat = -33.8226
lon = 151.2012

zoom = 16
forma = 'mvt'

def deg2num(lat_deg, lon_deg, zoom):
    lat_rad = math.radians(lat_deg)
    n = 2.0 ** zoom
    xtile = int((lon_deg + 180.0) / 360.0 * n)
    ytile = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n)
    return (xtile, ytile)

num_x, num_y = deg2num(lat, lon, 16)

def create_boundary(lat, lon, distance):
    R = 6371.0
    lat_r = math.radians(lat)
    ns_dist = distance / (R * 1000)
    ew_dist = distance / (R * 1000 * math.cos(lat_r))
    max_lat = lat + ns_dist
    min_lat = lat - ns_dist
    max_lon = lon + ew_dist
    min_lon = lon - ew_dist
    return min_lon, max_lon, min_lat, max_lat

def create_layer(model, name, color):
    layer = rh.Layer()
    layer.Name = name
    layer.Color = color
    return model.Layers.Add(layer)

xmin_LL, xmax_LL, ymin_LL, ymax_LL = create_boundary(lat, lon, 20000)

model = rh.File3dm()
model.Settings.ModelUnitSystem = rh.UnitSystem.Meters

topography_layerIndex = create_layer(model, "Topography", (191, 191, 191, 255))
transformer2 = Transformer.from_crs("EPSG:4326", "EPSG:32756", always_xy=True)
tiles = list(mercantile.tiles(xmin_LL, ymin_LL, xmax_LL, ymax_LL, zooms=16))

for tile in tiles:
  tile1 = mercantile.Tile(tile.x, tile.y, 16)
  bbox = mercantile.bounds(tile1)
  lon1, lat1, lon2, lat2 = bbox

  terrain_url = f'https://api.mapbox.com/v4/mapbox.mapbox-terrain-v2/{tile1.z}/{tile1.x}/{tile1.y}.{forma}?access_token={mapbox_access_token}'
  terrain_response = requests.get(terrain_url)
  terrain_data = terrain_response.content
  terrains = mapbox_vector_tile.decode(terrain_data)
  terrain_layer = terrains.get('contour')

  if terrain_layer:
    for feature in terrain_layer['features']:
        geometry_type = feature['geometry']['type']
        height = feature['properties']['ele']
        if geometry_type == 'Polygon':
            geometry = feature['geometry']['coordinates']
            for ring in geometry:
                points = []
                for coord in ring:
                    x_val, y_val = coord[0], coord[1]
                    x_prop = (x_val / 4096)
                    y_prop = (y_val / 4096)
                    lon_delta = lon2 - lon1
                    lat_delta = lat2 - lat1
                    lon_mapped = lon1 + (x_prop * lon_delta)
                    lat_mapped = lat1 + (y_prop * lat_delta)
                    lon_mapped, lat_mapped = transformer2.transform(
                        lon_mapped, lat_mapped)
                    point = rh.Point3d(lon_mapped, lat_mapped, 0)
                    points.append(point)
                polyline = rh.Polyline(points)
                curve = polyline.ToNurbsCurve()
                att = rh.ObjectAttributes()
                att.LayerIndex = topography_layerIndex
                att.SetUserString("Elevation Height", str(height))
                model.Objects.AddCurve(curve, att)
        elif geometry_type == 'MultiPolygon':
            geometry = feature['geometry']['coordinates']
            for polygon in geometry:
                for ring in polygon:
                    points = []
                    for coord in ring:
                        x_val, y_val = coord[0], coord[1]
                        x_prop = (x_val / 4096)
                        y_prop = (y_val / 4096)
                        lon_delta = lon2 - lon1
                        lat_delta = lat2 - lat1
                        lon_mapped = lon1 + (x_prop * lon_delta)
                        lat_mapped = lat1 + (y_prop * lat_delta)
                        lon_mapped, lat_mapped = transformer2.transform(
                            lon_mapped, lat_mapped)
                        point = rh.Point3d(lon_mapped, lat_mapped, 0)
                        points.append(point)
                    polyline = rh.Polyline(points)
                    curve = polyline.ToNurbsCurve()
                    att = rh.ObjectAttributes()
                    att.LayerIndex = topography_layerIndex
                    att.SetUserString("Elevation Height", str(height))
                    model.Objects.AddCurve(curve, att)

model.Write("C:/Users/RivinduB/Desktop/test.3dm")

test.3dm (375.5 KB)

Hi @Rivindu_Bandara,

You can use Heron for that: You can download the latest version via the package manager.
It has a standard set of components to make mapbox calls.

Hi @Erik_Beeren,

We already have done this, however, the script I’m trying to run is on a headless compute server, so heron unfortunately does not work in that environment.

Thanks

Hi @Rivindu_Bandara ,
This is a bit of a workaround, but if you know the Z/X/Y of the tiles you want to download, you can use Heron’s Import Vector Lite to import the PBF files served by the Mapbox Terrain vector tile service. Note that Import Vector Lite is available as part of v0.4.2-beta.1 in the Package Manager and works in a headless environment, but imports data raw without respecting Rhino’s units or EarthAnchorPoint, document attributes that aren’t available in a headless setup.

Let me know if you’d like me to develop the Lat/Lon to Z/X/Y tile functionality.

-Brian

2 Likes

Hi @Brian_Washburn,

If you don’t mind can I have access to that gh script?

Thanks

Hi @Rivindu_Bandara
Here you go. I stripped out my Mapbox Token.
20230724_HeadlessMapboxVectorTile-BW.gh (23.0 KB)

-Brian

1 Like

Hi @Brian_Washburn

Was wondering if there is a way to transform the coordinates to EPSG: 32756 (or any other SRS) and just get the contours rather than the whole mesh? I tried using the CT transform but it doesn’t seem to work.

image

Thanks

Hi @Rivindu_Bandara ,
Do you have the “Output points only” option selected in the Import Vector Lite menu? It should output points which you can then feed into the Coordinate Transform component.

20230726_HeadlessMapboxVectorTile-BW.gh (30.4 KB)

-Brian

1 Like

Hi @Brian_Washburn,

Thanks for that, this is exactly what I was looking for. The middle tile boundaries are gone but the outer ones are still there now.

I’ve tried scaling the boundary but scaling it removes some of the curves and I end up with unclosed curves. Any trick for this?


20230726_HeadlessMapboxVectorTile-RB_edited.gh (22.5 KB)

Thanks

Hi @Rivindu_Bandara ,
Try merging the extents polylines to get the boundary to use for culling out the outer edges.
20230726_HeadlessMapboxVectorTile-BW.gh (34.4 KB)


-Brian

1 Like

Hi @Brian_Washburn

Thanks so much for this, works really well.

I’m not sure which area your x/y tiles are from, but I am using Sydney X/Y tiles and 16 as zoom and I’m getting this.

Thanks

Hi @Rivindu_Bandara ,
I think the key is the zoom level I used was 14 whereas you are using 16. According to their website, Mapbox Terrain will max out at 10m height increments for their contours (Mapbox Terrain v2 | Tilesets | Mapbox), so if you zoom in far enough, the contour lines will look more sparse. If you want a higher resolution topo, you should be able to find some sources (such as https://elevation.fsdf.org.au/), but they will be in raster format. Heron has components to read these, but they currently don’t run in a headless environment.

-Brian

1 Like