React Map - OpenStreetMap Provider

Set provider to "osm" to use the OpenStreetMap (OSM) provider. This provider uses the OpenLayers library to display maps.

DevExtreme does not include map services for the OSM provider. Specify self-hosted or third-party services in the following providerConfig properties:

The following code snippet configures a Map that uses the OSM provider:

jQuery
JavaScript
$(() => {
    $("#mapContainer").dxMap({
        center: { lat: 40.749825, lng: -73.987963 },
        zoom: 10,
        provider: "osm",
        providerConfig: {
            tileServer: {
                url: "https://tiles.example.com/{z}/{x}/{y}.png",
                attribution: "&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap contributors</a>"
            }
        }
    });
});
Angular
app.component.html
app.component.ts
<dx-map
    [zoom]="10"
    provider="osm">
    <dxo-map-center
        [lat]="40.749825"
        [lng]="-73.987963">
    </dxo-map-center>
    <dxo-map-provider-config
        [tileServer]="tileServer">
    </dxo-map-provider-config>
</dx-map>
import { Component } from '@angular/core';
import { DxMapModule } from 'devextreme-angular';

@Component({
    // ...
    standalone: true,
    imports: [DxMapModule]
})
export class AppComponent {
    tileServer = {
        url: 'https://tiles.example.com/{z}/{x}/{y}.png',
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
    };
}
Vue
App.vue
<template>
    <DxMap
        :zoom="10"
        provider="osm">
        <DxCenter
            :lat="40.749825"
            :lng="-73.987963"
        />
        <DxProviderConfig
            :tile-server="tileServer"
        />
    </DxMap>
</template>

<script setup>
import { DxMap, DxCenter, DxProviderConfig } from 'devextreme-vue/map';

const tileServer = {
    url: 'https://tiles.example.com/{z}/{x}/{y}.png',
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
};
</script>
React
App.tsx
import { Map, ProviderConfig } from 'devextreme-react/map';

const centerCoordinates = { lat: 40.749825, lng: -73.987963 };
const tileServer = {
    url: 'https://tiles.example.com/{z}/{x}/{y}.png',
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
};

export default function App() {
    return (
        <Map
            defaultZoom={10}
            defaultCenter={centerCoordinates}
            provider="osm">
            <ProviderConfig
                tileServer={tileServer}
            />
        </Map>
    );
}

Configure OpenLayers

DevExtreme neither ships with OpenLayers nor specifies the library as a dependency. This section explains how to add OpenLayers resources to your application.

Modular Applications

Install the ol package:

Code
npm install ol

The devextreme/ui/map/openlayers module registers OpenLayers for the Map. Import this module and the OpenLayers stylesheet (ol/ol.css) before Map initialization:

jQuery
index.js
import 'devextreme/ui/map/openlayers';
import 'ol/ol.css';
Angular
app.component.ts
angular.json
import 'devextreme/ui/map/openlayers';
// ...
"styles": [
    // ...
    "node_modules/ol/ol.css"
]
Vue
App.vue
<script setup>
import 'devextreme/ui/map/openlayers';
import 'ol/ol.css';
// ...
</script>
React
App.tsx
import 'devextreme/ui/map/openlayers';
import 'ol/ol.css';
// ...
NOTE
The ol package contains only ES modules and does not include a CommonJS build.

Non-Modular Applications

Load the OpenLayers script (ol.js) and stylesheet (ol.css) before Map initialization:

index.html
<head>
    <!-- ... -->
    <link rel="stylesheet" href="/lib/ol/ol.css">
    <script src="/lib/ol/ol.js"></script>
</head>

Refer to the OpenLayers website for download options: OpenLayers - Download.

Configure a Tile Server

Assign an OsmTileServerConfig object to providerConfig.tileServer to configure a tile source. Specify the following fields in this object:

  • url: A tile URL template
  • attribution: Copyright notices that your tile and data providers require
  • subdomains (optional): Values for the {s} placeholder in url
  • maxZoom (optional): The highest zoom level that your tile source supports

The following code snippet configures a tile source that uses two subdomains:

JavaScript
const tileServer = {
    url: 'https://{s}.tiles.example.com/{z}/{x}/{y}.png',
    subdomains: ['a', 'b'],
    maxZoom: 18,
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
};

Assign a function to tileServer to display different tiles for each map type:

JavaScript
const tileSources = {
    roadmap: {
        url: 'https://tiles.example.com/roadmap/{z}/{x}/{y}.png',
        attribution: 'Roadmap tile attribution'
    },
    satellite: {
        url: 'https://tiles.example.com/satellite/{z}/{x}/{y}.jpg',
        attribution: 'Satellite tile attribution'
    },
    hybrid: {
        url: 'https://tiles.example.com/hybrid/{z}/{x}/{y}.jpg',
        attribution: 'Hybrid tile attribution'
    }
};

const tileServer = (type) => tileSources[type];

The Map displays one tile source at a time. A "hybrid" tile source must include both satellite imagery and labels.

Review the terms of use for your tile service. For instance, comply with the Tile Usage Policy if you use public OpenStreetMap tile servers.

Calculate Locations

The OSM provider uses the providerConfig.calculateLocation function to convert addresses and place names to coordinates. Implement this function to request coordinates from a geocoding service:

JavaScript
async function calculateLocation(query) {
    const response = await fetch(`/api/geocode?q=${encodeURIComponent(query)}`);
    if (response.status === 404) {
        return undefined;
    }
    if (!response.ok) {
        throw new Error(`Geocoding request failed: ${response.status}`);
    }
    const { lat, lng } = await response.json();
    return { lat, lng };
}

This example requests coordinates from an application endpoint (/api/geocode). The endpoint returns a { lat, lng } object for a resolved address and 404 for an unknown address. Adapt request/response formats to your geocoding service.

If the calculateLocation promise rejects or resolves with undefined, the Map uses {lat: 0, lng: 0} for center and markers. The component also does not display routes that contain unresolved addresses.

Calculate Routes

Implement providerConfig.calculateRoute to display routes.

Send Requests to a Routing Service

calculateRoute receives a CalculateOsmRouteInfo object. The object contains route waypoints (CalculateOsmRouteInfo.locations) and the transportation mode. Send these values to a routing service and return the route geometry received from the service:

JavaScript
async function calculateRoute({ locations, mode }) {
    const response = await fetch('/api/route', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ locations, mode })
    });
    if (!response.ok) {
        throw new Error(`Routing request failed: ${response.status}`);
    }
    const route = await response.json(); // A GeoJSON FeatureCollection
    return route.features[0].geometry; // A GeoJSON LineString object
}

This example sends route parameters to an application endpoint (/api/route) that returns a GeoJSON FeatureCollection. Adapt request and response formats to your routing service.

Return Route Geometry

calculateRoute can return route geometry in one of the following OsmRouteResult formats:

JavaScript
// An array of coordinate pairs (latitude then longitude)
const coordinatePairs = [
    [40.749825, -73.987963],
    [40.753182, -73.982253]
];

// A GeoJSON LineString object (longitude then latitude)
const lineString = {
    type: 'LineString',
    coordinates: [
        [-73.987963, 40.749825],
        [-73.982253, 40.753182]
    ]
};

Use Predefined Coordinates

calculateRoute does not have to request route geometry from a routing service. It can return predefined coordinates instead. The function receives only route waypoints and the transportation mode, so use waypoints to identify predefined routes:

JavaScript
const predefinedRoutes = {
    // Key: route waypoints. Value: route geometry (latitude then longitude).
    '40.749825,-73.987963;40.753182,-73.982253': [
        [40.749825, -73.987963],
        [40.753634, -73.985198],
        [40.752408, -73.982291],
        [40.753182, -73.982253]
    ]
};

async function calculateRoute({ locations }) {
    const key = locations.map(({ lat, lng }) => `${lat},${lng}`).join(';');
    const coordinates = predefinedRoutes[key];
    if (!coordinates) {
        throw new Error(`No predefined route for ${key}`);
    }
    return coordinates;
}

Handle Failed Routes

If calculateRoute fails, the Map does not display a route. The addRoute(routeOptions) promise resolves with undefined:

JavaScript
const route = await mapInstance.addRoute(routeOptions);
if (!route) {
    // Notify users that the route is unavailable
}

You can assign the following function to calculateRoute to draw straight lines between route waypoints if a routing request fails:

JavaScript
async function calculateRouteWithFallback(params) {
    try {
        return await calculateRoute(params);
    } catch {
        return params.locations.map(({ lat, lng }) => [lat, lng]);
    }
}

Security

The Map renders OsmTileServerConfig.attribution and markers[].tooltip.text values as HTML. If these values come from untrusted sources, encode tooltip.text and sanitize attribution (retain notices and links that your tile and data providers require). Refer to the following help topics for more information:

The browser requests tiles and runs calculateLocation and calculateRoute. Users can read API keys in tile URLs and service requests. Use keys that your service allows in client applications (for instance, keys restricted to your domain). If keys must stay confidential, route requests to the map service through your server.

Add map service origins to your Content Security Policy as described in the following help topic: CSP Directives for Map Integrations.

Accessibility

Keyboard Navigation

You can use the following keys to interact with the Map when provider is "osm":

Key Action
Tab / Shift + Tab Moves focus to the map and between interactive markers (markers that have a tooltip or an onClick handler). Markers outside the visible area do not receive focus.
Enter / Space Displays or hides the focused marker tooltip and calls the marker's onClick handler.
Esc Hides the most recently opened tooltip.

While the map has focus, you can also use arrow keys to pan the map and the + and - keys to zoom in and out. OpenLayers implements these shortcuts in KeyboardPan and KeyboardZoom.

Screen Reader Support

Interactive markers use a generic accessible name ("Map marker"). Specify a descriptive name for each marker in the onMarkerAdded handler:

JavaScript
function onMarkerAdded(e) {
    // e.originalMarker is an OpenLayers Overlay
    e.originalMarker.getElement().setAttribute('aria-label', 'Empire State Building');
}

If marker tooltips contain plain text, you can assign this text to each marker's aria-label attribute to specify descriptive names:

JavaScript
function onMarkerAdded(e) {
    const { tooltip } = e.options;
    const text = typeof tooltip === 'string' ? tooltip : tooltip?.text;
    if (text) {
        e.originalMarker.getElement().setAttribute('aria-label', text);
    }
}

Troubleshooting

The Map Cannot Find OpenLayers

The Map logs an error if the component cannot find a compatible OpenLayers version. Ensure that your application loads OpenLayers before Map initialization. Modular applications must import the devextreme/ui/map/openlayers module.

A Package Manager or Bundler Cannot Resolve OpenLayers

The devextreme/ui/map/openlayers module imports the ol package, but devextreme does not declare ol as a dependency. Yarn Plug'n'Play does not allow packages to import undeclared dependencies. Declare ol in your application dependencies and add a packageExtensions entry to .yarnrc.yml:

.yarnrc.yml
packageExtensions:
  "devextreme@*":
    dependencies:
      ol: "10.10.0"

Specify the ol version that your application uses.

The Map Does Not Display Tiles

Check the following:

  • providerConfig.tileServer specifies a tile URL for the current map type. A tileServer function must return a tile source synchronously for each map type.
  • Tile requests include the credentials that your tile service requires.
  • Your tile server allows cross-origin requests (CORS). OpenLayers sends CORS requests for tile images.
  • Tile URLs use HTTPS if your application uses HTTPS.
  • Your Content Security Policy allows tile server origins (the img-src directive).

Attribution or Zoom Buttons Are Missing

Check the following:

  • The tile source specifies attribution. A URL template string cannot include attribution. Use an OsmTileServerConfig object instead.
  • controls is true (required for zoom buttons).
  • Your application loads the OpenLayers stylesheet (ol.css).

OpenLayers displays attribution in a collapsed control by default. Users can click the i button to expand this control.

NOTE
Certain tile services prohibit collapsible attributions. Configure the OpenLayers Attribution control to comply with your tile service's terms of use.

Markers or the Map Center Appear at Zero Coordinates

The Map uses {lat: 0, lng: 0} if it cannot convert an address to coordinates. Check the following:

  • Your application specifies providerConfig.calculateLocation.
  • The calculateLocation promise resolves with numeric lat and lng values.
  • Your geocoding service finds the address.

The Map Does Not Display Routes

Check the following:

  • Your application specifies providerConfig.calculateRoute.
  • Each route contains two or more locations. Address strings require calculateLocation.
  • The calculateRoute promise resolves with a valid OsmRouteResult value.

Routes appear in unexpected locations if the coordinate order is incorrect. Coordinate pairs list latitude then longitude. GeoJSON LineString positions list longitude then latitude.