Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapbox vector tiles #346

Open
dcooley opened this issue Oct 19, 2021 · 7 comments
Open

Mapbox vector tiles #346

dcooley opened this issue Oct 19, 2021 · 7 comments
Labels
enhancement New feature or request

Comments

@dcooley
Copy link
Collaborator

dcooley commented Oct 19, 2021

https://deck.gl/docs/api-reference/geo-layers/mvt-layer

@dcooley dcooley added the enhancement New feature or request label Oct 19, 2021
dcooley added a commit that referenced this issue Oct 21, 2021
@dwachsmuth
Copy link

Hi @dcooley, I was wondering if there was a timeline for this feature showing up in a build? I'd love to kick the tires on it with some gigantic polygon datasets!

@dcooley
Copy link
Collaborator Author

dcooley commented Jan 12, 2022

The problem I am having with this is coming up with a clean way to pass in the colours / line widths / etc into the function. All the other layers that have a data argument map colours to a variable. Whereas for MVT you colour based on one of the JSON objects inside the MVT layer. Which in JS can also be a function, as per the example for getLineWidth

So this kind of fell of my radar because I couldn't come up with anything. I don't suppose you have any ideas?

@mpadge
Copy link
Contributor

mpadge commented Dec 21, 2022

@dcooley I've been playing around with this, and it seems that the MVT layer is intended to use the external tile server specified in data as read only. You have to create and serve tiles elsewhere, and then plug the resultant URL in as the data parameter. deck.gl will then use the data specified in the tiles as expected. Both the deck.gl and mapbox libraries are hard-coded to accept data parameters as nothing other than URLs to tile servers.

The only way for this to all work without externally hosted tiles would be to use something like https://github.com/felt/tippecanoe to generate local tiles, then spin up a local server, and insert the URL from that. But that's never going to be viable here.

Short of that, the MVT layer is just a way of getting data from an external tile server and using that to constuct a deck.gl layer. From my understanding of it, locally-specified data can by definition not be used for MVT here.


Update

Implementation in rdeck described here, in which data is indeed just a URL to a tile server holding the data to be rendered. That would still be a nifty enhancement here!

@dcooley
Copy link
Collaborator Author

dcooley commented Dec 3, 2024

You can define your own MVTLayer({}) as pure javascript and pass it to mapdeck() using htmlwidgets::onRender()

library(mapdeck)

set_token(secret::get_secret("MAPBOX"))

js <- "function(el, x) {

  const layerId = 'mvt-layer';

  // reference: https://deck.gl/docs/api-reference/geo-layers/mvt-layer
  const mvtLayer = new deck.MVTLayer({
    map_id: el.id,
    id: layerId,
    data: [
    'https://tiles-a.basemaps.cartocdn.com/vectortiles/carto.streets/v1/{z}/{x}/{y}.mvt'
  ],
  minZoom: 0,
  maxZoom: 14,
  getFillColor: f => {
    switch (f.properties.layerName) {
      case 'poi':
        return [255, 0, 0];
      case 'water':
        return [120, 150, 180];
      case 'building':
        return [218, 218, 218];
      default:
        return [240, 240, 240];
    }
  },
  getLineWidth: f => {
    switch (f.properties.class) {
      case 'street':
        return 6;
      case 'motorway':
        return 10;
      default:
        return 1;
    }
  },
  getLineColor: [192, 192, 192],
  getPointRadius: 2,
  pointRadiusUnits: 'pixels',
  stroked: false,
  picking: true
});

  md_update_layer( el.id, layerId, mvtLayer );
}
"
mapdeck(style = mapdeck_style("light"), repeat_view = TRUE) %>%
  htmlwidgets::onRender(js)

@dcooley
Copy link
Collaborator Author

dcooley commented Dec 4, 2024

Here's a basic shiny example

/mvtShiny
  |
  +--- server.R
  +--- ui.R
  +--- mvt.R
  +---/www/
         |
         +--- addMvt.js

./www/addMvt.js

Define javascript functions to 'invoke' from R

function clear_mvt(map_id, layer_id) {
  md_clear_layer(map_id, layer_id); // internal mapdeck function for clearing layers by `layer_id`
}

function add_mvt(map_id, mvt_url, layer_id) {

  const layerId = layer_id;

  // reference: https://deck.gl/docs/api-reference/geo-layers/mvt-layer
  const mvtLayer = new deck.MVTLayer({
    map_id: map_id,
    id: layerId,
    data: [ mvt_url ],
	  minZoom: 0,
	  maxZoom: 14,
	  getFillColor: f => {
	    switch (f.properties.layerName) {
	      case 'poi':
	        return [255, 0, 0];
	      case 'water':
	        return [120, 150, 180];
	      case 'building':
	        return [218, 218, 218];
	      default:
	        return [240, 240, 240];
	    }
	  },
	  getLineWidth: f => {
	    switch (f.properties.class) {
	      case 'street':
	        return 6;
	      case 'motorway':
	        return 10;
	      default:
	        return 1;
	    }
	  },
	  getLineColor: [192, 192, 192],
	  getPointRadius: 2,
	  pointRadiusUnits: 'pixels',
	  stroked: false,
	  picking: true
	});
	// this is a mapdeck javascript function for adding layers to a `deckgl` object
  md_update_layer( map_id, layerId, mvtLayer );
}

mvt.R

Create the R functions which will call the javascript functions

## Define the javascript dependency. This gets added to the shiny server
mvt_dep <- list(
	mapdeck:::createHtmlDependency(
		name = "add_mvt"
		, version = "1.0.0"
		, src = "./www/"
		, script = "addMvt.js"
		, all_files = FALSE
	)
)

## the `R` function to call in the shiny for adding an MVT layer
add_mvt <- function(map, url, layer_id) {
	invoke_method(map, "add_mvt", url, layer_id)  ## the `add_mvt` here is the js function defined in the .js file
}

## The `R` function to callin the shiny for clearing the MVT layer
clear_mvt <- function(map, layer_id) {
	invoke_method(map, "clear_mvt", layer_id)   ## the `clear_mvt` is the js function defined in the .js file
}

ui.R

library(shiny)
library(shinydashboard)
library(mapdeck)

ui <- shinydashboard::dashboardPage(
	header = shinydashboard::dashboardHeader()
	, sidebar = shinydashboard::dashboardSidebar(
		actionButton(
			inputId = "load"
			, label = "Load MVT"
		),
		actionButton(
			inputId = "clear"
			, label = "Clear MVT"
		)
	)
	, body = shinydashboard::dashboardBody(
		mapdeck::mapdeckOutput(
			outputId = "map"
		)
	)
)

server.R

library(shiny)
library(mapdeck)

set_token(secret::get_secret("MAPBOX"))

source("./mvt.R")

server <- function(input, output, session) {

	output$map <- mapdeck::renderMapdeck({
		mapdeck(style = mapdeck_style("light")) %>%
			mapdeck:::addDependency(mvt_dep)
	})

        ## The `add_mvt` and `clear_mvt` are the `R` functions defined in the mvt.R file
	observeEvent(input$load, {
		mapdeck::mapdeck_update(map_id = "map") %>%
			add_mvt(
				url = 'https://tiles-a.basemaps.cartocdn.com/vectortiles/carto.streets/v1/{z}/{x}/{y}.mvt'
				, layer_id = 'mvt-layer'
				)
	})

	observeEvent(input$clear, {
		mapdeck::mapdeck_update(map_id = "map") %>%
			clear_mvt(layer_id = 'mvt-layer')
	})

}

@mpadge
Copy link
Contributor

mpadge commented Dec 5, 2024

Good to see things happening here Dave! And FYI, a couple of week ago, open street map started using MVT instead of raster, so you can also use the tileservers listed there (vector.openstreetmap.org).

@dcooley
Copy link
Collaborator Author

dcooley commented Dec 10, 2024

Yeah it's been kinda slow progress on this one... :/

Thanks though @mpadge , I was not aware of OSMs migration to vector tiles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants