Moped Documentation
  • Welcome 👋
  • User Guides
    • Getting started
    • Map a project
  • Product Management
    • User communication
    • User management
    • User analytics
    • Local testing
    • Release process
    • Patch release process
    • MUI X Pro License
    • Integrations
      • Dataset documentation
      • ArcGIS Online
      • eCapris
      • Power BI
    • Features
  • Dev Guides
    • DB Docs & Data dictionary
    • Database backup policy
    • Moped Read Replica
    • How-to's
      • How do I start the Hasura cluster locally?
      • How do I launch the Hasura Console?
      • How do I get a JWT token?
      • How to ping the GraphQL API
      • How to ping the REST API
      • How do I connect a database with Postgres GUIs?
      • How do I connect to the RDS instance?
      • How to load production data into a local instance
      • How do I update seed data?
    • Hasura
      • Hasura Roles
      • Hasura Migrations
        • Getting Started
        • Installing the Hasura CLI
        • Configuration Files
        • Hasura Migration Principles
        • The Migration file format
        • Development
        • Hasura Seed Data
        • Running the Hasura Cluster Locally (video)
        • Create a migration: Exercise 1 (video)
        • Create a migration: Exercise 2 (video)
        • Latest hasura-cluster features
    • User Management
    • Authentication
      • Authentication Architecture
      • DynamoDB & Cognito
      • Secrets Manager & Cognito
      • Hasura & Cognito
      • React & Cognito
      • Flask API & Cognito
      • Single Sign-On with CTM
    • Code organization
    • API
      • Configuration Files
      • Testing
      • User Management API
    • Maps and geospatial data
      • Access tokens and API keys
      • Map libraries
      • Map data
      • Map styles
      • Map layers and basemaps
      • React patterns
      • V1 Archive
        • Map libraries
        • Map data
        • Map custom hooks
        • Map styles
        • Map layers and basemaps
    • UI access control
    • Design system
      • Branding
      • Component styles
      • Text content
    • Activity Log
      • Architecture
      • GitHub Actions and Deployment of Updates
      • Hasura Event Logs and Truncate Cron Job
      • Authentication
  • See also
  • Get Moped support, report a bug, or request an enhancement
  • Data & Technology Services
  • Github repository
Powered by GitBook
On this page
  • Data-driven styles
  • Map control styles
  • Native controls
  • Custom controls
  • Geocoder controls

Was this helpful?

  1. Dev Guides
  2. Maps and geospatial data
  3. V1 Archive

Map styles

Styles and patterns used in the maps

PreviousMap custom hooksNextMap layers and basemaps

Last updated 4 years ago

Was this helpful?

Data-driven styles

To modify the width of circles and lines based on the zoom level, Mapbox were used to set stops set with pairs of zoom level and matching pixel width. These pairs are and are set as show in the following code for styling lines in a vector tile layer.

return {
    id: this.layerIdName,
    type: "line",
    layout: {
        "line-join": "round",
        "line-cap": "round",
    },
    paint: {
        "line-color": "green",
    "line-width": {
        base: 1,
        stops: [
          [10, 1], // [zoom, width in px]
          [13, 2],
          [16, 10],
          [18, 25],
        ],
      },              
    },
}

The stops key above contain a 2D array which holds pairs of zoom levels and widths in pixels. Out-of-the-box, Mapbox will use calculate the values between these stops to adjust the circle radius linearly between stops levels.

react-map-gl-draw does not support data-driven styles out-of-the-box so a couple of functions were written to calculate the width of circles in the drawing UI when the zoom level updates. These two functions are called getCircleRadiusByZoom and linearInterpolation.

The first step is to traverse the first level of the stops array and find where the currentZoom level fits into the brackets made up by pairs in the second depth of the array. If the currentZoom value falls below the first set of level of zoom, the smallest pixel width is return. Similarly, if the currentZoom is above the highest level of zoom, the highest pixel width is returned. If the currentZoom is somewhere in between the minimum and maximum, the value is compared to two elements of the first level of the array which creates a bracket of zooms and widths. This provides four of the known values described next.

Map control styles

One challenge with adding custom controls to the map is mouse events propagating through the control and interacting with the map below them. This can lead to users accidentally selecting features or interacting with the map when they were really trying to zoom, use a geocoder, or select a layer to show on the map. This was managed in a few ways depending on the control and whether it is a control native to react-map-gl or not.

Native controls

<Box className={classes.mapBox}>
  <ReactMapGL>
    <div className={classes.navStyle}>
      {/* captureClick prop set to false */}
      <NavigationControl captureClick={false} />
    </div>
    {/* Map sources and layers here */}
  </ReactMapGL>
</Box>

Custom controls

Custom controls built with JSX can also be added to the map, but they also present the same challenge of mouse event propagation. For these controls, the solution is to render them outside of the open and close component tags of the map instance. Using CSS, you can position the rendered controls over the map and tune where they are placed in relation to the other controls. See a simplified version of this from the app below.

<Box className={classes.mapBox}>
  {/* Render custom control outside ReactMapGL component */}
  {renderLayerSelect()}
  <ReactMapGL>
    {/* Map sources and layers here */}
  </ReactMapGL>
</Box>

Geocoder controls

<Box className={classes.mapBox}>
  {/* Render custom control outside ReactMapGL component */}
  {/* and add a ref created with useRef */}
  <div
    ref={mapControlContainerRef}
    style={{
      display: "flex",
      height: 50,
      position: "absolute",
      alignItems: "center",
      right: 32,
    }}
  />
  <ReactMapGL>
    <Geocoder
      mapRef={mapRef}
      onViewportChange={handleGeocoderViewportChange}
      mapboxApiAccessToken={MAPBOX_TOKEN}
      {/* The ref created for the div above is passed here */}
      containerRef={mapControlContainerRef}
    />
  </ReactMapGL>
</Box>

Next, the calculation come down to a bit of Algebra to find the unknown, currentWidth, from the known values - minZoom, currentZoom, maxZoom, minWidth, and maxWidth. This was all stitched together by translating the formula for linear interpolation into code. More .

Note that the key in the above sample code is set to 1. If any other value was used, the linearInterpolation function would need to be updated to account for the different rate of change in the output of the function. Luckily, the base of 1 makes it simple and looks nice.

For native controls, like the component, react-map-gl adds props that can be adjusted to control the behavior of mouse events. One example is which can be set to false to stop propagation of a click to the map below. See a simplified version of this from the app below.

When using react-map-gl-geocoder, the Geocoder component contains a prop called where you can pass a ref that is tied to an external JSX element to tell the library where to place the geocoder input box. The styles of the geocoder input can also be customized using CSS to set where it is positioned on top of the map. This is show in a simplified version below and also in an .

data-driven styles
stops
here
base
NavigationControl
captureClick
containerRef
example in the documentation