mapboxrnmapbox-maps

How to show the whole world on a mobile screen in rnmapbox/maps


I'm using @rnmapbox/maps, and would like to be able to show a full map of the world like below on a mobile screen (i.e., with all countries shown at once). I don't particularly mind what the height to width ratio is, as long as the whole map fits on the screen. But it seems that even at minimum zoom (0), the map gets cut off both horizontally and vertically (see smaller greenish map). My understanding is that this might be a hard limitation, that zoom level 0 is by definition a 512x512 tile, and if you don't have 512px to play with, you're going to get cropped. The average mobile screen is more like 400px. But that seems like a crazy limitation, a mapping library that can't actually show you a world map on the majority of devices in the world.

This is a simple version of my code, as you can see I'm trying everything from zoomLevel to bounds to padding:

            <MapView
              style={{ flex: 1 }}
              projection="mercator"
              styleJSON={JSON.stringify(mapStyleJson, null, 0) || undefined}
              rotateEnabled={false}
              scaleBarEnabled={false}
              pitchEnabled={false}
              compassEnabled={false}
              zoomEnabled={true}
              attributionPosition={{
                right: 0,
                top: 8,
              }}
            >
              <MapboxGL.Camera
                minZoomLevel={0}
                maxZoomLevel={20}
                padding={{
                  paddingLeft: 0,
                  paddingRight: 0,
                  paddingTop: 0,
                  paddingBottom: 90,
                }}
                bounds={{
                  ne: [180, 85],
                  sw: [-180, -85],
                }}
                zoomLevel={0}
              />
            </MapView>

This is a related question, although it's 7 years old now and not for mobile or the same library.


What I want:

world map


What I get:

enter image description here



Solution

  • OK, so it's true: in Mapbox, a zoom level of 0 equates to a 512x512px tile. There is a way around this if your screen is less than this size. My code below is from my actual app so it's a little convoluted, but basically the answer is that you need a transform: scale on the immediate parent of the map (to get it to the right size), then also an offset with transform: translate to re-center the map. It seems like a hack, but it works fine for me in production on iOS, Android and web.

      const TILE_SIZE = 512 // Mapbox defines scale 0 as a 512x512px tile
    
      // SCALING
      const mapWidth = width - borderWidth * 2
      const scaledRatio = (1 / TILE_SIZE) * mapWidth
      const displayScale = shouldScale ? scaledRatio : 1
      const offset = shouldScale ? (TILE_SIZE - mapWidth) / 2 : 0
      const scaleStyle = {
        height: shouldScale ? TILE_SIZE : height,
        width: shouldScale ? TILE_SIZE : mapWidth,
        transform: [{ scale: displayScale }],
      }
      const transformStyle: ViewStyle = {
        transform: [{ translateX: -offset }, { translateY: -offset }],
      }
    
      return (
        <>
          <Box
            width={width}
            height={height}
            pointerEvents="box-none"
            borderWidth={borderWidth}
            {...boxProps}
          >
            <Box
              overflow={'hidden'}
              width={width}
              height={height}
              position={'relative'}
              pointerEvents="box-none"
            >
              {!!mapStyleJson && (
                <Box style={transformStyle} pointerEvents="box-none">
                  <Box style={scaleStyle} pointerEvents="box-none">
                    <MapView
                      ref={mapRef}
                      style={{ flex: 1 }}
    {...}