Maps SDK for JavaScript
Back to all examples
Interactive roads and numbers
Base Map with interactive roads and house numbers.
Interactive roads and numbers
import { TomTomConfig } from '@tomtom-org/maps-sdk/core'; import { type BaseMapLayerGroupName, BaseMapModule, TomTomMap } from '@tomtom-org/maps-sdk/map'; import { type ReverseGeocodingResponse, reverseGeocode } from '@tomtom-org/maps-sdk/services'; import { distance } from '@turf/turf'; import type { Feature, FeatureCollection, Position } from 'geojson'; import { Map, type PointLike } from 'maplibre-gl'; import './style.css'; import { API_KEY } from './config'; import { findClosestLineString } from './findClosestLineString'; import { createInvertedBuffer } from './invertedBuffer'; import { initHoveredSourceAndLayers, initSelectedSourceAndLayers } from './sourceAndLayers'; // (Set your own API key when working in your own environment) TomTomConfig.instance.put({ apiKey: API_KEY, language: 'en-US' }); (async () => { // Calculates the amount of meters for the given amount of pixels and reference map coordinates. // NOTE: the reference coordinates have an impact due to the mercator projection. const pxToMeters = (numPixels: number, map: Map): number => { const referenceLngLat = map.getCenter(); const startPixels = map.project(map.getCenter()); const nextPointInPixels: PointLike = [startPixels.x, numPixels + startPixels.y]; const nextPointLngLat = map.unproject(nextPointInPixels); return Math.ceil( distance([referenceLngLat.lng, referenceLngLat.lat], [nextPointLngLat.lng, nextPointLngLat.lat], { units: 'meters', }), ); }; const map = new TomTomMap({ mapLibre: { container: 'sdk-map', center: [-74.00332, 40.71732], zoom: 18, }, }); const mapLibreMap = map.mapLibreMap; const interactiveGroupNames: BaseMapLayerGroupName[] = ['roadLines', 'roadLabels', 'roadShields', 'houseNumbers']; const interactiveGroups = await BaseMapModule.get(map, { layerGroupsFilter: { mode: 'include', names: interactiveGroupNames }, }); const restOfTheMap = await BaseMapModule.get(map, { layerGroupsFilter: { mode: 'exclude', names: [...interactiveGroupNames, 'placeLabels'] }, events: { cursorOnHover: 'default' }, }); const hoveredSource = initHoveredSourceAndLayers(mapLibreMap); const selectedSource = initSelectedSourceAndLayers(mapLibreMap); const titleElement = document.querySelector('#sdk-example-title') as Element; const subtitleElement = document.querySelector('#sdk-example-subtitle') as Element; const addressesElement = document.querySelector('#sdk-example-addresses') as Element; const setTitleAndSubtitle = (feature: Feature<any, any>) => { titleElement.innerHTML = `${feature.properties.category} ${feature.properties.subcategory ?? ''}`; subtitleElement.innerHTML = feature.properties.name ?? ''; }; const showRevGeoResponses = (responses: ReverseGeocodingResponse[]) => { addressesElement.innerHTML = responses .map((response) => response.properties.address.freeformAddress) .join('<br>'); }; let clickedFeature: Feature<any, any> | undefined; interactiveGroups.events.on('hover', (feature, lngLat) => { if (!clickedFeature) { const extractedFeature = findClosestLineString(feature, [lngLat.lng, lngLat.lat]); hoveredSource.setData(extractedFeature); setTitleAndSubtitle(extractedFeature); } }); interactiveGroups.events.on('click', async (feature, lngLat) => { clickedFeature = findClosestLineString(feature, [lngLat.lng, lngLat.lat]); hoveredSource.setData(clickedFeature); selectedSource.setData(createInvertedBuffer(clickedFeature, pxToMeters(15, mapLibreMap), 'meters')); setTitleAndSubtitle(clickedFeature); if (clickedFeature.geometry.type == 'LineString') { const coordinates = clickedFeature.geometry.coordinates; showRevGeoResponses( await Promise.all([ reverseGeocode({ position: coordinates[0] }), reverseGeocode({ position: coordinates.at(-1) as Position }), ]), ); } else if (clickedFeature.geometry.type == 'Point') { showRevGeoResponses([ await reverseGeocode({ position: clickedFeature.geometry.coordinates, number: String(clickedFeature.properties.number), }), ]); } else { addressesElement.innerHTML = ''; } }); const setPlaceholderText = () => { titleElement.innerHTML = 'Explore roads, streets, ferries and house numbers...'; subtitleElement.innerHTML = ''; addressesElement.innerHTML = ''; }; const emptyFeatureCollection: FeatureCollection = { type: 'FeatureCollection', features: [] }; restOfTheMap.events.on('hover', () => { if (!clickedFeature) { hoveredSource.setData(emptyFeatureCollection); setPlaceholderText(); } }); restOfTheMap.events.on('click', () => { clickedFeature = undefined; hoveredSource.setData(emptyFeatureCollection); selectedSource.setData(emptyFeatureCollection); setPlaceholderText(); }); })();
Related examples
Mixed User Interaction Events
Handle various map events and user interactions
Playground
Base Map
User Interaction Events
Web
Map autocomplete fuzzy search playground
Combine autocomplete search with fuzzy search functionality
Places and Search
Playground
Web
User Interaction Events
Interactive map labels
Click country, city and neighborhood labels on the map to show their boundaries
Playground
Places and Search
User Interaction Events
Geometry
Web
Interactive Map POIs
Click on map POIs to see their details
Places and Search
Playground
User Interaction Events
Web