From d22d733b7d436bab904baa1caf28b1e6e7004680 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sat, 6 Jul 2024 21:03:14 +0200 Subject: [PATCH 01/16] Add a function `basemap` and some converts for image --- src/Tyler.jl | 1 + src/basemap.jl | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 8 ++++ 3 files changed, 104 insertions(+) create mode 100644 src/basemap.jl diff --git a/src/Tyler.jl b/src/Tyler.jl index 60dd73a2..6d446177 100644 --- a/src/Tyler.jl +++ b/src/Tyler.jl @@ -13,6 +13,7 @@ using OrderedCollections: OrderedCollections, OrderedSet using ThreadSafeDicts: ThreadSafeDicts, ThreadSafeDict using TileProviders: TileProviders, AbstractProvider, geturl, min_zoom, max_zoom +include("basemap.jl") include("interpolations.jl") const TileImage = Matrix{RGB{N0f8}} diff --git a/src/basemap.jl b/src/basemap.jl new file mode 100644 index 00000000..18268db2 --- /dev/null +++ b/src/basemap.jl @@ -0,0 +1,95 @@ +#= +# Static basemaps + +This file provides the ability to get static base maps from Tyler. + +Its main entry point is the `basemap` function, which returns a tuple +`(x, y, z)` of the image data (`z`) and its axes (`x` and `y`). + +This file also contains definitions for `convert_arguments` that make +the following syntax "just work": +```julia +image(TileProviders.Google(), Rect2f(-0.0921, 51.5, 0.04, 0.025), (1000, 1000); axis= (; aspect = DataAspect())) +``` + +You do still have to provide the extent and image size, but this is substantially better than nothing. + +=# + +""" + basemap(provider::TileProviders.Provider, bbox::Extent; size, res, min_zoom_level = 0, max_zoom_level = 16)::(xs, ys, img) +""" +function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Rect2{<: Real}, Extent}; size = nothing, res = nothing, min_zoom_level = 0, max_zoom_level = 16) + bbox = Extents.extent(boundingbox) + # First, handle keyword arguments + @assert (isnothing(size) || isnothing(res)) "You must provide either `size` or `res`, but not both." + @assert (isnothing(size) && isnothing(res)) "You must provide either the `size` or `res` keywords." + _size = if isnothing(size) + # convert resolution to size using bbox and round(Int, x) + (round(Int, (bbox.X[2] - bbox.X[1]) / first(res)), round(Int, (bbox.Y[2] - bbox.Y[1]) / last(res))) + else + (first(size), last(size)) + end + return basemap(provider, bbox, _size; min_zoom_level, max_zoom_level) +end + +function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Rect2{<: Real}, Extent}, size::Tuple{Int, Int}; min_zoom_level = 0, max_zoom_level = 16) + bbox = Extents.extent(boundingbox) + # Obtain the optimal Z-index that covers the bbox at the desired resolution. + optimal_z_index = clamp(z_index(bbox, (X=size[2], Y=size[1]), MapTiles.WGS84()), min_zoom_level, max_zoom_level) + # Generate a `TileGrid` from our zoom level and bbox. + tilegrid = MapTiles.TileGrid(bbox, optimal_z_index, MapTiles.WGS84()) + # Compute the dimensions of the tile grid, so we can feed them into a + # Raster later. + tilegrid_extent = Extents.extent(tilegrid, MapTiles.WGS84()) + tilegrid_size = tile_widths .* length.(tilegrid.grid.indices) + # We need to know the start and end indices of the tile grid, so we can + # place the tiles in the right place. + tile_start_idxs = minimum(first.(Tuple.(tilegrid.grid))), minimum(last.(Tuple.(tilegrid.grid))) + tile_end_idxs = maximum(first.(Tuple.(tilegrid.grid))), maximum(last.(Tuple.(tilegrid.grid))) + # Using the size information, we initiate an `RGBA{Float32}` image array. + # You can later convert to whichever size / type you want by simply broadcasting. + image_receptacle = fill(RGBAf(0,0,0,1), tilegrid_size) + # Now, we iterate over the tiles, and read and then place them into the array. + for tile in tilegrid + # Download the tile + url = TileProviders.geturl(provider, tile.x, tile.y, tile.z) + result = HTTP.get(url) + # Read into an in-memory array (Images.jl layout) + img = ImageMagick.readblob(result.body) + # The thing with the y indices is that they go in the reverse of the natural order. + # So, we simply subtract the y index from the end index to get the correct placement. + image_start_relative = ( + tile.x - tile_start_idxs[1], + tile_end_idxs[2] - tile.y, + ) + # The absolute start is simply the relative start times the tile width. + image_start_absolute = (image_start_relative .* tile_widths) + # The indices for the view into the receptacle are the absolute start + # plus one, to the absolute end. + idxs = (:).(image_start_absolute .+ 1, image_start_absolute .+ tile_widths) + @debug image_start_relative image_start_absolute idxs + # Place the tile into the receptacle. Note that we rotate the image to + # be in the correct orientation. + image_receptacle[idxs...] .= rotr90(img) # change to Julia memory layout + end + # Now, we have a complete image. + # We can also produce the image's axes: + xs = (..)(tilegrid_extent.X...) + ys = (..)(tilegrid_extent.Y...) + # image(ras; axis = (; aspect = DataAspect())) + return (xs, ys, image_receptacle) +end + +# We also use this in some Makie converts to allow `image` to work +Makie.used_attributes(trait::Makie.ImageLike, provider::TileProviders.AbstractProvider, bbox::Union{Rect2, Extent}, size::Union{Int, Tuple{Int, Int}}) = (:min_zoom_level, :max_zoom_level) + +function Makie.convert_arguments(trait::Makie.ImageLike, provider::TileProviders.AbstractProvider, bbox::Extent, size::Union{Int, Tuple{Int, Int}}; min_zoom_level = 0, max_zoom_level = 16) + return Makie.convert_arguments(trait, basemap(provider, bbox, (first(size), last(size)); min_zoom_level, max_zoom_level)...) +end + +function Makie.convert_arguments(trait::Makie.ImageLike, provider::TileProviders.AbstractProvider, bbox::Rect2, size::Union{Int, Tuple{Int, Int}}; min_zoom_level = 0, max_zoom_level = 16) + return Makie.convert_arguments(trait, provider, Extents.extent(bbox), (first(size), last(size)); min_zoom_level, max_zoom_level) +end + + diff --git a/test/runtests.jl b/test/runtests.jl index 167be4b8..40d0dc7a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -38,6 +38,14 @@ m = wait(Tyler.Map(london; scale=1)) # waits until all tiles are displayed @test GeoInterface.crs(m) == Tyler.MapTiles.WebMercator() end +@testset "Basemap" begin + @test_nowarn basemap(TileProviders.Google(), london, (1000, 1000)) + @test_nowarn basemap(TileProviders.Google(), london; size = (1000, 1000)) + @test_nowarn basemap(TileProviders.Google(), london; res = 0.001) + x, y, img = basemap(TileProviders.Google(), london, (1000, 1000)) + @test img isa Matrix{<: RGBA} +end + # Reference tests? # provider = TileProviders.NASAGIBS() # m = Tyler.Map(Rect2f(0, 50, 40, 20), 5; provider=provider, min_tiles=8, max_tiles=32) From 622cb762443bffe1b473c8547c49435758a501b7 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sat, 6 Jul 2024 21:14:39 +0200 Subject: [PATCH 02/16] Make this work properly in a fresh env --- src/basemap.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/basemap.jl b/src/basemap.jl index 18268db2..1d3a24b5 100644 --- a/src/basemap.jl +++ b/src/basemap.jl @@ -42,6 +42,12 @@ function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Re # Compute the dimensions of the tile grid, so we can feed them into a # Raster later. tilegrid_extent = Extents.extent(tilegrid, MapTiles.WGS84()) + #= TODO: + Here we assume all tiles are 256x256. + It's easy to compute this though, by either: + - Making a sample query for the tile (0, 0, 0) (but you are not guaranteed this exists) + =# + tile_widths = (256, 256) tilegrid_size = tile_widths .* length.(tilegrid.grid.indices) # We need to know the start and end indices of the tile grid, so we can # place the tiles in the right place. @@ -75,10 +81,7 @@ function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Re end # Now, we have a complete image. # We can also produce the image's axes: - xs = (..)(tilegrid_extent.X...) - ys = (..)(tilegrid_extent.Y...) - # image(ras; axis = (; aspect = DataAspect())) - return (xs, ys, image_receptacle) + return (tilegrid_extent.X, tilegrid_extent.Y, image_receptacle) end # We also use this in some Makie converts to allow `image` to work From 79fd4c5e76aabab3455f84c47fea05ca1999c816 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sat, 6 Jul 2024 21:35:06 +0200 Subject: [PATCH 03/16] Fix the kwarg asserts --- src/basemap.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basemap.jl b/src/basemap.jl index 1d3a24b5..59f55e12 100644 --- a/src/basemap.jl +++ b/src/basemap.jl @@ -23,7 +23,7 @@ function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Re bbox = Extents.extent(boundingbox) # First, handle keyword arguments @assert (isnothing(size) || isnothing(res)) "You must provide either `size` or `res`, but not both." - @assert (isnothing(size) && isnothing(res)) "You must provide either the `size` or `res` keywords." + @assert !(isnothing(size) && isnothing(res)) "You must provide either the `size` or `res` keywords. Current values: $(size), $(res)" _size = if isnothing(size) # convert resolution to size using bbox and round(Int, x) (round(Int, (bbox.X[2] - bbox.X[1]) / first(res)), round(Int, (bbox.Y[2] - bbox.Y[1]) / last(res))) From 9c373963e136035ad2799165fe30382904f6badc Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sat, 6 Jul 2024 21:35:37 +0200 Subject: [PATCH 04/16] Return an image in WebMercator to be consistent Thoughts? How do we determine which CRS the input bbox takes? Its currently WGS84 but do we want to give an option to go to webmerc at all? Conversion between the two is mostly lossless... --- src/basemap.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basemap.jl b/src/basemap.jl index 59f55e12..0886e5fc 100644 --- a/src/basemap.jl +++ b/src/basemap.jl @@ -41,7 +41,7 @@ function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Re tilegrid = MapTiles.TileGrid(bbox, optimal_z_index, MapTiles.WGS84()) # Compute the dimensions of the tile grid, so we can feed them into a # Raster later. - tilegrid_extent = Extents.extent(tilegrid, MapTiles.WGS84()) + tilegrid_extent = Extents.extent(tilegrid, MapTiles.WebMercator()) #= TODO: Here we assume all tiles are 256x256. It's easy to compute this though, by either: From 7bde166e298fabb8ce7780386d9ac1618e2f004c Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sat, 6 Jul 2024 22:14:07 +0200 Subject: [PATCH 05/16] Fix tests --- test/runtests.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 40d0dc7a..423cf945 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -39,10 +39,10 @@ m = wait(Tyler.Map(london; scale=1)) # waits until all tiles are displayed end @testset "Basemap" begin - @test_nowarn basemap(TileProviders.Google(), london, (1000, 1000)) - @test_nowarn basemap(TileProviders.Google(), london; size = (1000, 1000)) - @test_nowarn basemap(TileProviders.Google(), london; res = 0.001) - x, y, img = basemap(TileProviders.Google(), london, (1000, 1000)) + @test_nowarn basemap(Tyler.TileProviders.Google(), london, (1000, 1000)) + @test_nowarn basemap(Tyler.TileProviders.Google(), london; size = (1000, 1000)) + @test_nowarn basemap(Tyler.TileProviders.Google(), london; res = 0.001) + x, y, img = basemap(Tyler.TileProviders.Google(), london, (1000, 1000)) @test img isa Matrix{<: RGBA} end From e756e9df7e6974a45a4e2293355f3f4bdf738247 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sat, 6 Jul 2024 22:29:47 +0200 Subject: [PATCH 06/16] Really fix tests --- test/runtests.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 423cf945..0684f522 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -39,10 +39,10 @@ m = wait(Tyler.Map(london; scale=1)) # waits until all tiles are displayed end @testset "Basemap" begin - @test_nowarn basemap(Tyler.TileProviders.Google(), london, (1000, 1000)) - @test_nowarn basemap(Tyler.TileProviders.Google(), london; size = (1000, 1000)) - @test_nowarn basemap(Tyler.TileProviders.Google(), london; res = 0.001) - x, y, img = basemap(Tyler.TileProviders.Google(), london, (1000, 1000)) + @test_nowarn Tyler.basemap(Tyler.TileProviders.Google(), london, (1000, 1000)) + @test_nowarn Tyler.basemap(Tyler.TileProviders.Google(), london; size = (1000, 1000)) + @test_nowarn Tyler.basemap(Tyler.TileProviders.Google(), london; res = 0.001) + x, y, img = Tyler.basemap(Tyler.TileProviders.Google(), london, (1000, 1000)) @test img isa Matrix{<: RGBA} end From 2fdfd455024810c6b091749d30652ac9770ad831 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sat, 6 Jul 2024 23:22:07 +0200 Subject: [PATCH 07/16] grrrrrr --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 0684f522..59c195af 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -43,7 +43,7 @@ end @test_nowarn Tyler.basemap(Tyler.TileProviders.Google(), london; size = (1000, 1000)) @test_nowarn Tyler.basemap(Tyler.TileProviders.Google(), london; res = 0.001) x, y, img = Tyler.basemap(Tyler.TileProviders.Google(), london, (1000, 1000)) - @test img isa Matrix{<: RGBA} + @test img isa Matrix{<: Makie.RGBA} end # Reference tests? From 860040de116c3e1fde356090b672bebe924fca63 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 9 Jul 2024 10:34:18 +0200 Subject: [PATCH 08/16] Use the new FileIO approach --- src/basemap.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basemap.jl b/src/basemap.jl index 0886e5fc..7955a6fd 100644 --- a/src/basemap.jl +++ b/src/basemap.jl @@ -62,7 +62,7 @@ function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Re url = TileProviders.geturl(provider, tile.x, tile.y, tile.z) result = HTTP.get(url) # Read into an in-memory array (Images.jl layout) - img = ImageMagick.readblob(result.body) + img = FileIO.load(FileIO.query(IOBuffer(result))) # The thing with the y indices is that they go in the reverse of the natural order. # So, we simply subtract the y index from the end index to get the correct placement. image_start_relative = ( From 1a4bcadf894b90909745f8d1352a006b22b71d4b Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 9 Jul 2024 13:23:57 +0200 Subject: [PATCH 09/16] Add a basic `basemap` page (needs a lot of help) --- docs/src/.vitepress/config.mts | 1 + docs/src/basemap.md | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 docs/src/basemap.md diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index dd418542..1254c546 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -45,6 +45,7 @@ export default defineConfig({ { text: 'Whale shark trajectory', link: '/whale_shark' }, { text: 'Ice loss animation', link: '/iceloss_ex' }, { text: 'Interpolation On The Fly', link: '/interpolation' }, + { text: 'Base maps', link: '/basemap' }, ] }, diff --git a/docs/src/basemap.md b/docs/src/basemap.md new file mode 100644 index 00000000..2f764697 --- /dev/null +++ b/docs/src/basemap.md @@ -0,0 +1,10 @@ +# Base maps + +A "base map" is essentially a static image composed of map tiles, accessible through the [`basemap`](@ref) function. + +## Examples + +NASA GIBS tileset on GeoAxis + +OpenSnowMap sounds really cool, maybe a North Pole projection? + From f25d5b625fae317d695b82dd8f6c78091d7a90b6 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Thu, 26 Sep 2024 06:47:11 -0700 Subject: [PATCH 10/16] Get basemap working again --- src/basemap.jl | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/basemap.jl b/src/basemap.jl index 7955a6fd..a8bf5458 100644 --- a/src/basemap.jl +++ b/src/basemap.jl @@ -16,8 +16,49 @@ You do still have to provide the extent and image size, but this is substantiall =# + +""" + _z_index(extent::Extent, res::NamedTuple, crs::WebMercator) => Int + +Calculate a z value from `extent` and pixel resolution `res` for `crs`. +The rounded mean calculated z-index for X and Y resolutions is returned. + +`res` should be `(X=xres, Y=yres)` to match the extent. + +We assume tiles are the standard 256*256 pixels. Note that this is not an +enforced standard, and that retina tiles are 512*512. """ - basemap(provider::TileProviders.Provider, bbox::Extent; size, res, min_zoom_level = 0, max_zoom_level = 16)::(xs, ys, img) +function _z_index(extent::Union{Rect,Extent}, res::NamedTuple, crs; tile_size = 256) + # Calculate the number of tiles at each z and get the one + # closest to the resolution `res` + target_ntiles = prod(map(r -> r / tile_size, res)) + tiles_at_z = map(1:24) do z + length(TileGrid(extent, z, crs)) + end + return findmin(x -> abs(x - target_ntiles), tiles_at_z)[2] +end + +""" + basemap(provider::TileProviders.Provider, bbox::Extent; size, res, min_zoom_level, max_zoom_level)::(xs, ys, img) + +Download the most suitable basemap for the given bounding box and size, return a tuple of `(x_interval, y_interval, image)`. +All returned coordinates and images are in the **Web Mercator** coordinate system (since that's how tiles are defined). + +The input bounding box must be in the **WGS84** (long/lat) coordinate system. + +## Example + +```julia +basemap(TileProviders.Google(), Extent(X = (-0.0921, -0.0521), Y = (51.5, 51.525)), size = (1000, 1000)) +``` + +## Keyword arguments + +`size` and `res` are mutually exclusive, and you must provide one. + +`min_zoom_level - 0` and `max_zoom_level = 16` are the minimum and maximum zoom levels to consider. + +`res` should be a tuple of the form `(X=xres, Y=yres)` to match the extent. """ function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Rect2{<: Real}, Extent}; size = nothing, res = nothing, min_zoom_level = 0, max_zoom_level = 16) bbox = Extents.extent(boundingbox) @@ -36,7 +77,7 @@ end function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Rect2{<: Real}, Extent}, size::Tuple{Int, Int}; min_zoom_level = 0, max_zoom_level = 16) bbox = Extents.extent(boundingbox) # Obtain the optimal Z-index that covers the bbox at the desired resolution. - optimal_z_index = clamp(z_index(bbox, (X=size[2], Y=size[1]), MapTiles.WGS84()), min_zoom_level, max_zoom_level) + optimal_z_index = clamp(_z_index(bbox, (X=size[2], Y=size[1]), MapTiles.WGS84()), min_zoom_level, max_zoom_level) # Generate a `TileGrid` from our zoom level and bbox. tilegrid = MapTiles.TileGrid(bbox, optimal_z_index, MapTiles.WGS84()) # Compute the dimensions of the tile grid, so we can feed them into a @@ -46,6 +87,7 @@ function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Re Here we assume all tiles are 256x256. It's easy to compute this though, by either: - Making a sample query for the tile (0, 0, 0) (but you are not guaranteed this exists) + - Some function that returns the tile size for a given provider / dispatch form =# tile_widths = (256, 256) tilegrid_size = tile_widths .* length.(tilegrid.grid.indices) @@ -62,7 +104,7 @@ function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Re url = TileProviders.geturl(provider, tile.x, tile.y, tile.z) result = HTTP.get(url) # Read into an in-memory array (Images.jl layout) - img = FileIO.load(FileIO.query(IOBuffer(result))) + img = FileIO.load(FileIO.query(IOBuffer(result.body))) # The thing with the y indices is that they go in the reverse of the natural order. # So, we simply subtract the y index from the end index to get the correct placement. image_start_relative = ( @@ -81,6 +123,7 @@ function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Re end # Now, we have a complete image. # We can also produce the image's axes: + # Note that this is in the Web Mercator coordinate system. return (tilegrid_extent.X, tilegrid_extent.Y, image_receptacle) end From 5467c20d601cadc5bab991ffafd005c8ef37c12d Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Thu, 26 Sep 2024 07:11:56 -0700 Subject: [PATCH 11/16] Better docs + export basemap --- docs/Project.toml | 2 ++ docs/src/basemap.md | 76 +++++++++++++++++++++++++++++++++++++++++++-- src/basemap.jl | 2 ++ 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index fd800091..80404b60 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -10,6 +10,8 @@ DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910" GLMakie = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a" +GeoMakie = "db073c08-6b98-4ee5-b6a4-5efafb3259c6" +Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d" HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" LightOSM = "d1922b25-af4e-4ba3-84af-fe9bea896051" diff --git a/docs/src/basemap.md b/docs/src/basemap.md index 2f764697..4bf9616b 100644 --- a/docs/src/basemap.md +++ b/docs/src/basemap.md @@ -1,10 +1,80 @@ +```@meta +CollapsedDocStrings = true +``` + # Base maps -A "base map" is essentially a static image composed of map tiles, accessible through the [`basemap`](@ref) function. +A "base map" is essentially a static image composed of map tiles, accessible through the [`basemap`](@ref) function. + +This function returns a tuple of `(x, y, image)`, where `x` and `y` are the dimensions of the image, and `image` is the image itself. +The image, and axes, are always in the Web Mercator coordinate system. + +While Tyler does not currently work with CairoMakie, this method is a way to add a basemap to a CairoMakie plot, though it will not update on zoom. + +```@docs; canonical=false +basemap +``` ## Examples -NASA GIBS tileset on GeoAxis -OpenSnowMap sounds really cool, maybe a North Pole projection? +Below are some cool examples of using basemaps. + +#### Cover example of London + +````@example coverlondon +using Tyler + +xs, ys, img = basemap( + TileProviders.OpenStreetMap(), + Extent(X=(-0.5, 0.5), Y=(51.25, 51.75)), + (1024, 1024) +) +```` + +````@example coverlondon +image(xs, ys, img; axis = (; aspect = DataAspect())) +```` + +Note that the image is in the Web Mercator projection, as are the axes we see here. + +#### NASA GIBS tileset, plotted as a `meshimage` on a `GeoAxis` + +````@example nasagibs +const BACKEND = Makie.current_backend() # hide +using Tyler, TileProviders, GeoMakie, CairoMakie + +provider = TileProviders.NASAGIBS(:ViirsEarthAtNight2012) + +xs, ys, img = basemap(provider, Extent(X=(-90, 90), Y=(-90, 90)), (1024, 1024)) +```` + +````@example nasagibs +meshimage( + xs, ys, img; + source = "+proj=webmerc", # REMEMBER: `img` is always in Web Mercator... + axis = (; type = GeoAxis, dest = "+proj=ortho +lat_0=0 +lon_0=0"), + npoints = 1024, +) +```` + +````@example nasagibs +BACKEND.activate!() # hide +```` + + +### OpenSnowMap on polar stereographic projection + +````@example opensnowmap +using Tyler, GeoMakie +meshimage( + basemap( + TileProviders.OpenSnowMap(), + Extent(X=(-180, 180), Y=(50, 90)), + (1024, 1024) + )...; + source = "+proj=webmerc", + axis = (; type = GeoAxis, dest = "+proj=stere +lat_0=90 +lat_ts=71 +lon_0=-45"), +) +```` diff --git a/src/basemap.jl b/src/basemap.jl index a8bf5458..adc4647c 100644 --- a/src/basemap.jl +++ b/src/basemap.jl @@ -16,6 +16,8 @@ You do still have to provide the extent and image size, but this is substantiall =# +export basemap + """ _z_index(extent::Extent, res::NamedTuple, crs::WebMercator) => Int From 1f19bb70edb1c56da24cfc715e3813deb2a53245 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Thu, 26 Sep 2024 07:53:25 -0700 Subject: [PATCH 12/16] fix doc build --- docs/src/basemap.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/basemap.md b/docs/src/basemap.md index 4bf9616b..c71ce268 100644 --- a/docs/src/basemap.md +++ b/docs/src/basemap.md @@ -33,6 +33,7 @@ xs, ys, img = basemap( ```` ````@example coverlondon +using Makie image(xs, ys, img; axis = (; aspect = DataAspect())) ```` @@ -42,7 +43,7 @@ Note that the image is in the Web Mercator projection, as are the axes we see he ````@example nasagibs const BACKEND = Makie.current_backend() # hide -using Tyler, TileProviders, GeoMakie, CairoMakie +using Tyler, TileProviders, GeoMakie, CairoMakie, Makie provider = TileProviders.NASAGIBS(:ViirsEarthAtNight2012) @@ -66,7 +67,7 @@ BACKEND.activate!() # hide ### OpenSnowMap on polar stereographic projection ````@example opensnowmap -using Tyler, GeoMakie +using Tyler, GeoMakie, Makie meshimage( basemap( From 00b380092a2f87bd1db5fd2b712a829390f14bf3 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Thu, 26 Sep 2024 08:48:32 -0700 Subject: [PATCH 13/16] more fixes --- docs/src/basemap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/basemap.md b/docs/src/basemap.md index c71ce268..394004c4 100644 --- a/docs/src/basemap.md +++ b/docs/src/basemap.md @@ -23,7 +23,7 @@ Below are some cool examples of using basemaps. #### Cover example of London ````@example coverlondon -using Tyler +using Tyler, TileProviders xs, ys, img = basemap( TileProviders.OpenStreetMap(), @@ -67,7 +67,7 @@ BACKEND.activate!() # hide ### OpenSnowMap on polar stereographic projection ````@example opensnowmap -using Tyler, GeoMakie, Makie +using Tyler, TileProviders, GeoMakie, Makie meshimage( basemap( From c4cd63e137d6626f4dc28c47199fc6439ffb2d4c Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Thu, 26 Sep 2024 10:14:52 -0700 Subject: [PATCH 14/16] Try to fix docs again --- docs/Project.toml | 1 + docs/src/.vitepress/theme/style.css | 14 ++++++++++++++ docs/src/basemap.md | 2 -- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 80404b60..fb62e9c0 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -17,6 +17,7 @@ Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" LightOSM = "d1922b25-af4e-4ba3-84af-fe9bea896051" Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" MapTiles = "fea52e5a-b371-463b-85f5-81770daa2737" OSMMakie = "76b6901f-8821-46bb-9129-841bc9cfe677" TileProviders = "263fe934-28e1-4ae9-998a-c2629c5fede6" diff --git a/docs/src/.vitepress/theme/style.css b/docs/src/.vitepress/theme/style.css index 969af9b7..7283617b 100644 --- a/docs/src/.vitepress/theme/style.css +++ b/docs/src/.vitepress/theme/style.css @@ -234,4 +234,18 @@ kbd { border-radius: 6px; box-shadow: inset 0 -1px 0 var(--vp-c-bg-soft-mute); line-height: 0.95em; +} + +/* Component: Docstring Custom Block */ + +.jldocstring.custom-block { + border: 1px solid var(--vp-c-gray-2); + color: var(--vp-c-text-1) +} + +.jldocstring.custom-block summary { + font-weight: 700; + cursor: pointer; + user-select: none; + margin: 0 0 8px; } \ No newline at end of file diff --git a/docs/src/basemap.md b/docs/src/basemap.md index 394004c4..88b4c059 100644 --- a/docs/src/basemap.md +++ b/docs/src/basemap.md @@ -48,9 +48,7 @@ using Tyler, TileProviders, GeoMakie, CairoMakie, Makie provider = TileProviders.NASAGIBS(:ViirsEarthAtNight2012) xs, ys, img = basemap(provider, Extent(X=(-90, 90), Y=(-90, 90)), (1024, 1024)) -```` -````@example nasagibs meshimage( xs, ys, img; source = "+proj=webmerc", # REMEMBER: `img` is always in Web Mercator... From 3642f7b52e63e898d39f3c469ac9c64808047b99 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Thu, 26 Sep 2024 10:14:58 -0700 Subject: [PATCH 15/16] Apply suggestion from code review --- src/basemap.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/basemap.jl b/src/basemap.jl index adc4647c..75165f5d 100644 --- a/src/basemap.jl +++ b/src/basemap.jl @@ -62,7 +62,9 @@ basemap(TileProviders.Google(), Extent(X = (-0.0921, -0.0521), Y = (51.5, 51.525 `res` should be a tuple of the form `(X=xres, Y=yres)` to match the extent. """ -function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Rect2{<: Real}, Extent}; size = nothing, res = nothing, min_zoom_level = 0, max_zoom_level = 16) +function basemap(provider::TileProviders.AbstractProvider, boundingbox::Union{Rect2{<: Real}, Extent}; + size = nothing, res = nothing, min_zoom_level = 0, max_zoom_level = 16 + ) bbox = Extents.extent(boundingbox) # First, handle keyword arguments @assert (isnothing(size) || isnothing(res)) "You must provide either `size` or `res`, but not both." From 8b6338fee7cf91b3afcf0343c042a9bcba051b13 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Thu, 26 Sep 2024 10:17:52 -0700 Subject: [PATCH 16/16] use Extents everywhere --- docs/src/basemap.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/src/basemap.md b/docs/src/basemap.md index 88b4c059..b110944f 100644 --- a/docs/src/basemap.md +++ b/docs/src/basemap.md @@ -24,6 +24,7 @@ Below are some cool examples of using basemaps. ````@example coverlondon using Tyler, TileProviders +using Tyler.Extents xs, ys, img = basemap( TileProviders.OpenStreetMap(), @@ -42,8 +43,9 @@ Note that the image is in the Web Mercator projection, as are the axes we see he #### NASA GIBS tileset, plotted as a `meshimage` on a `GeoAxis` ````@example nasagibs -const BACKEND = Makie.current_backend() # hide using Tyler, TileProviders, GeoMakie, CairoMakie, Makie +using Tyler.Extents +const BACKEND = Makie.current_backend() # hide provider = TileProviders.NASAGIBS(:ViirsEarthAtNight2012) @@ -65,7 +67,8 @@ BACKEND.activate!() # hide ### OpenSnowMap on polar stereographic projection ````@example opensnowmap -using Tyler, TileProviders, GeoMakie, Makie +using Tyler, TileProviders, GeoMakie, GLMakie +using Tyler.Extents meshimage( basemap(