Hacking with Environmental Data

Logo

WALD's help page for geospatial hackers

View ANU WALD GitHub

View ANU WALD website

View ANU WALD Slack channel

23 April 2020

Interactive Jupyter notebook for coordinates transformation

by

Description



Sometimes you need to calculate a set of bounding boxes around different parts of the world using a specific coordinate projection system. There are a number of tools that allow drawing boxes interactively in geographical coordinates (lat, lon) using a web page, such as geojson.io. However, I don’t know an easy way of converting or defining bounding boxes in other geographical projections. Probably GIS systems provide the right functionality to do this easily, but this is also about experimenting with interactive Jupyter notebooks and create this functionality.

The following notebook demonstrates how to draw boxes in a map interactively, which can then be converted into any coordinate reference system. This page contains a rendered copy of the notebook but to test this you’ll need to run a Jupyter notebook session. A copy of this notebook can be downloaded from here.

Interactive widgets in Jupyter notebooks allow us to make our code interactive. I’m sure this notebook can be extended and improved to make it more intuitive and interactive. Let us know if you find a way of improving this or something interesting.

interactive_maps

Graphical interactive coordinate transformation app

This first cell defines the map where will draw the boxes with the coordinates we want to reproject.

In [15]:
from ipyleaflet import Map, basemaps, basemap_to_tiles, DrawControl
import geojson

def bbox(coord_list):
    box = []
    for i in (0,1):
        res = sorted(coord_list, key=lambda x:x[i])
        box.append((res[0][i],res[-1][i]))
 
    return box[0][0], box[1][0], box[0][1], box[1][1]
    

watercolor = basemap_to_tiles(basemaps.Stamen.Watercolor)

m = Map(layers=(watercolor, ), center=(-25, 140), zoom=4)

draw_control = DrawControl(circle={}, circleMarker={}, circlemarker={}, 
                           CircleMarker={}, polyline={}, marker={}, polygon={},
                          rectangle = {"shapeOptions": {"color": "#00005d","fillOpacity": 0.0}})


def handle_draw(self, action, geo_json):
    #self.clear()
    lon_min.value,lat_min.value,lon_max.value,lat_max.value=bbox(list(geojson.utils.coords(geo_json['geometry'])))
    x_min.value,y_min.value=transform(lon_min.value,lat_min.value)
    x_max.value,y_max.value=transform(lon_max.value,lat_max.value)
    print(f"Width: {x_max.value-x_min.value}")
    print(f"Height: {y_max.value-y_min.value}")
    
draw_control.on_draw(handle_draw)

m.add_control(draw_control)

m
Width: 969138.0354123353
Height: 1025906.5802111514
Width: 1067710.6478760948
Height: 1026234.6256653685
Width: 1035883.6942156783
Height: 1012169.0844018743
Width: 1018550.5840057939
Height: 1027538.6821468929

Let's define the output CRS using its EPSG code

In [9]:
from __future__ import print_function
from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets

in_crs = widgets.IntText(value=4326,description='Input EPSG:',disabled=True)
display(in_crs)
out_crs = widgets.IntText(value=3577,description='Output EPSG:',disabled=False)
display(out_crs)

These boxes will contain the corners of the drawn in geographical coordinates

In [10]:
lon_min = widgets.BoundedFloatText(value=0.0,min=-380.0,max=380.0,description='Min Longitude:')
display(lon_min)
lat_min = widgets.BoundedFloatText(value=0.0,min=-90.0,max=90.0,description='Min Latitude:')
display(lat_min)
lon_max = widgets.BoundedFloatText(value=0.0,min=-380.0,max=380.0,description='Max Longitude:')
display(lon_max)
lat_max = widgets.BoundedFloatText(value=0.0,min=-90.0,max=90.0,description='Max Latitude:')
display(lat_max)

Finally we declare the boxes that will contain the projected coordinates in the chosen projection

In [11]:
from pyproj import Proj, transform
from pyproj import Transformer

transformer = Transformer.from_crs(f"EPSG:{in_crs.value}", f"EPSG:{out_crs.value}", always_xy=True)

def transform(x,y):
    return transformer.transform(x,y)

x_min = widgets.FloatText(value=0.0,description='Min X:')
display(x_min)
y_min = widgets.FloatText(value=0.0,description='Min Y:')
display(y_min)
x_max = widgets.FloatText(value=0.0,description='Max X:')
display(x_max)
y_max = widgets.FloatText(value=0.0,description='Max Y:')
display(y_max)

Now go back to the interactive map and draw a rectangle to see the values in these cells updated.

In [ ]:
 
tags: