Skip to content

Instantly share code, notes, and snippets.

Created May 25, 2023 16:19
Show Gist options
  • Save asinghvi17/5a584af3357948006268caf367109d72 to your computer and use it in GitHub Desktop.
Save asinghvi17/5a584af3357948006268caf367109d72 to your computer and use it in GitHub Desktop.
Computing attractiveness metrics on streets
# # A GUI to display location "attractiveness"
# The following code creates a GUI which allows you to inspect
# a map of Toronto, and see the attractiveness of different locations
# based on different nearness measures.
# First, we load the necessary packages.
# We will build our GUI with the `Makie` toolchain, consisting of `Makie`, `[W]GLMakie`, and `Tyler` for map tile visualization.
# Data is from OpenStreetMap and is parsed using `OpenStreetMapX`. Attractiveness scores are obtained using `OSMToolset`.
# To install the packages we use here,
# ```julia
# using Pkg
# pkg"add Tyler#master TileProviders#master Makie GLMakie OSMToolset OpenStreetMapX DataFrames CSV Dagger#master TiledIteration TimerOutputs"
# ```
using Makie, GLMakie
using Tyler # you must add Tyler#master TileProviders#master
using OSMToolset, OpenStreetMapX
using Makie.Colors, Makie.ColorSchemes
using DataFrames, CSV
using Statistics
using Dagger, TiledIteration # to compute and schedule.
## We use TimerOutputs to track performance.
using TimerOutputs
const to = TimerOutput()
# ## Loading the data
# We assume a downloaded file that you can load data from.
# This must be a `.osm` XML file, which is a section of the full OSM `planet` file.
# We pre-process the raw OSM data to extract and weight areas of interest. This is done using `OSMToolset`.
# Please replace the file paths with your own file names here.
osm_file = joinpath(dirname(@__DIR__), "data", "planet_-79.866,43.561_-79.065,43.959.osm")
attractiveness_file = joinpath(dirname(@__DIR__), "data", "toronto.attractiveness.osm")
if !isfile(attractiveness_file)
@time df = OSMToolset.find_poi(osm_file)
@time CSV.write(attractiveness_file, df) # Cache the data
# Now, we construct an R*-tree (a spatial index) with this data, to mimimize query time.
@time ix = AttractivenessSpatIndex(attractiveness_file)
# Since we're looking only at Toronto, we can switch CRS to a locally flat projection (ENU stands for easting, northing, up in meters).
lla_coords = LLA.(, ix.df.lon) # convert lat/long to LLA objects from Geodesy.jl
lla_bbox = Makie.Rect2f(extrema(getproperty.(lla_coords, :lon)), extrema(getproperty.(lla_coords, :lat))) # get a bounding box
vals = ENU.(lla_coords, Ref(ix.refLLA))
ix.df.x .= getfield.(vals, :east)
ix.df.y .= getfield.(vals, :north)
# This spatial index object is what we'll use to compute the attractiveness of a given point.
# It provides the following measures:
labels = String.(ix.measures)
# This is a utility function to convert from the Web mercator projection (which is what most map tiles are in) to lon/lat coordinates.
function from_web_mercator(wmx,wmy)
return Point2f(Tyler.MapTiles.project((wmx,wmy), Tyler.MapTiles.web_mercator, Tyler.MapTiles.wgs84))
# ## Creating the GUI
# We create a GUI using `Tyler.Map`, which is fundamentally just a `Makie.Figure` with some tasks and buffers attached,
# to make it efficient.
tyler = Tyler.Map(
Makie.BBox(-79.866, -79.065, 43.561, 43.959); # Toronto bounding box
provider = Tyler.TileProviders.Provider("{z}/{x}/{y}.png")# OpenStreetMap()
# We can extract the axis which Tyler plots into, since it's the only thing in the figure:
tyler_axis = tyler.figure.content[1]
display(tyler) # hide
# Now, we set up the rest of the UI - a menu to select the nearness measure,
# and a slider to control the contrast (using the astronomers' `zscale` algorithm.)
ui_layout = GridLayout(tyler.figure[2, 1])
menu = Menu(ui_layout[1, 1], options = zip(labels, 1:length(labels)), fontsize = 25)
contrast_slider = with_theme(Theme(; fontsize = 20)) do; SliderGrid(ui_layout[2, 1], (; label = "Contrast", range = exp.(LinRange(log(1e-5), log(0.3), 75)), startvalue = 0.1); ); end
recompute_button = Button(ui_layout[1:2, 0], label = "Recompute", fontsize = 20)
recompute_button.height[] = Relative(1) # make the recompute button take up all available space
# ## Computing attractiveness
function compute_new_datacube(tyler_finallims, tyler_px_area)
global to
xs = LinRange{Float64}(left(tyler_finallims), right(tyler_finallims), widths(tyler_px_area)[1] ÷ 2)
ys = LinRange{Float64}(bottom(tyler_finallims), top(tyler_finallims), widths(tyler_px_area)[2] ÷ 2)
final_array = let
@timeit to "Dagger" begin
@timeit to "Scheduling" begin
tile_indices = TileIterator((Base.OneTo(length(xs)), Base.OneTo(length(ys))), (100, 100)) |> collect |> vec
tasks = [Dagger.@spawn assess_attractiveness(xs[xinds], ys[yinds], ix) for (xinds, yinds) in tile_indices]
@timeit to "Computing and fetching results" begin
final_values = fetch.(tasks)
@timeit to "Stitching results together (with Threads)" begin
final_array = Array{Float64, 3}(undef, length(xs), length(ys), length(ix.measures))
Threads.@threads for (i, tileindex) in collect(enumerate(tile_indices))
xinds, yinds = tileindex
final_array[xinds, yinds, :] = final_values[i]
return (xs, ys, final_array)
# ## Plotting attractiveness
# We simply plot a heatmap, which we will then link to the UI elements.
# First, we get the data (function defined later using Dagger.jl for scheduling)L
xs, ys, final_array = compute_new_datacube(tyler_axis.finallimits[], tyler_axis.scene.px_area[])
hm = heatmap!(tyler_axis, xs, ys, final_array[:,:,1]; xautolimits = false, yautolimits = false)
hm.colormap[] = cgrad(:viridis; alpha = 0.5)
colorbar = Colorbar(tyler.figure[1, 2], hm, label = "Attractiveness", labelpadding = 10, labelsize = 20, ticklabelsize = 15, ticklabelpad = 5)
tyler # hide
datacube = Ref(final_array)
## Set up the callbacks from UI elements to data!
on(menu.selection) do index
hm[3][] = view(datacube[], :, :, index)
lift(contrast_slider.sliders[1].value, hm[3]) do val, mat
hm.colorrange[] = Vec2f(Makie.PlotUtils.zscale(mat; contrast = val))
on(recompute_button.clicks) do _
xs, ys, zs = compute_new_datacube(tyler_axis.finallimits[], tyler_axis.scene.px_area[])
hm.input_args[1].val = xs
hm.input_args[2].val = ys
hm.input_args[3][] = view(zs, :, :, menu.selection[])
datacube[] = zs
hm.colormap[] = cgrad(:Reds, alpha = 0.7)
tyler # hide
## Now we can interact with it!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment