Maps SDK for JavaScript
Back to all examples
EV charging stations search
Interactive exploration of charging station availability
EV charging stations search
import { type EVChargingStationWithAvailabilityPlaceProps, geographyTypes, type Place, TomTomConfig, } from '@tomtom-org/maps-sdk/core'; import { GeometriesModule, PlacesModule, PlacesModuleConfig, POIsModule, TomTomMap } from '@tomtom-org/maps-sdk/map'; import { geometryData, getPlacesWithEVAvailability, getPlaceWithEVAvailability, hasChargingAvailability, search, } from '@tomtom-org/maps-sdk/services'; import { without } from 'lodash-es'; import { type LngLatBoundsLike, Popup } from 'maplibre-gl'; import './style.css'; import { ViewportPlaces } from '@tomtom-org/maps-sdk-plugin-viewport-places'; import { API_KEY } from './config'; import { connectorsHTML, escapeHtml } from './htmlTemplates'; // (Set your own API key when working in your own environment) TomTomConfig.instance.put({ apiKey: API_KEY, language: 'en-GB' }); (async () => { const evBrandTextBox = document.querySelector('#sdk-example-evBrandTextBox') as HTMLInputElement; const areaTextBox = document.querySelector('#sdk-example-areaTextBox') as HTMLInputElement; const fitBoundsOptions = { padding: 50 }; const popUp = new Popup({ closeButton: false, offset: 35, className: 'sdk-example-maps-sdk-js-popup', }); const map = new TomTomMap({ mapLibre: { container: 'sdk-map', center: [2.3597, 48.85167], zoom: 11, fitBoundsOptions, }, }); const mapBasePOIs = await POIsModule.get(map, { filters: { categories: { show: 'all_except', values: ['ELECTRIC_VEHICLE_STATION'] }, }, }); const placesLayers = new ViewportPlaces(map); let mapEVStationsModule: PlacesModule | null = null; const buildAvailabilityText = (place: Place<EVChargingStationWithAvailabilityPlaceProps>): string => { const availability = getChargingPointAvailability(place); return availability ? `${availability.availableCount}/${availability.totalCount}` : ''; }; const evStationPinConfig: PlacesModuleConfig = { extraFeatureProps: { availabilityText: buildAvailabilityText, availabilityRatio: (place: Place<EVChargingStationWithAvailabilityPlaceProps>) => getChargingPointAvailability(place)?.ratio ?? 0, }, text: { title: [ 'format', ['get', 'title'], {}, '\n', {}, ['get', 'availabilityText'], { 'font-scale': 1.1, 'text-color': [ 'case', ['>=', ['get', 'availabilityRatio'], 0.25], 'green', ['>', ['get', 'availabilityRatio'], 0], 'orange', 'red', ], }, ], }, }; const mapSearchedEVStationsModule = await PlacesModule.get(map, evStationPinConfig); const selectedEVStationModule = await PlacesModule.get(map, evStationPinConfig); const mapGeometryModule = await GeometriesModule.get(map, { theme: 'inverted' }); let minPowerKWMapEVStations = 50; let minPowerKWSearchedEVStations = 0; const mapEVStationsId = 'ev-stations'; const showPopup = (evStation: Place | Place<EVChargingStationWithAvailabilityPlaceProps>) => { const { address, poi, chargingPark } = evStation.properties; popUp .setHTML( ` <div class="sdk-example-popup-header"> <h3 class="sdk-example-popup-title">${escapeHtml(poi?.name ?? '')}</h3> <span class="sdk-example-address">${escapeHtml(address.freeformAddress)}</span> </div> ${ chargingPark ? connectorsHTML(chargingPark) : '<p class="sdk-example-popup-empty">Charging park data not available.</p>' } `, ) .setLngLat(evStation.geometry.coordinates as [number, number]) .addTo(map.mapLibreMap); }; const searchEVStations = async () => { popUp.remove(); mapBasePOIs.setVisible(false); const areaToSearch = areaTextBox.value && (await search({ query: areaTextBox.value, geographyTypes: without(geographyTypes, 'Country'), limit: 1, })); areaToSearch && map.mapLibreMap.fitBounds(areaToSearch.bbox as LngLatBoundsLike, fitBoundsOptions); const geometryToSearch = areaToSearch && (await geometryData({ geometries: areaToSearch })); if (geometryToSearch) { mapGeometryModule.show(geometryToSearch); } else { mapGeometryModule.clear(); } const places = await search({ query: evBrandTextBox.value, ...(geometryToSearch && { geometries: [geometryToSearch] }), ...(!geometryToSearch && { boundingBox: map.getBBox() }), poiCategories: ['ELECTRIC_VEHICLE_STATION'], minPowerKW: minPowerKWSearchedEVStations, limit: 100, }); // We first show the places, then fetch their EV availability and show them again: mapSearchedEVStationsModule.show(places); mapSearchedEVStationsModule.show(await getPlacesWithEVAvailability(places)); }; const clear = () => { evBrandTextBox.value = ''; areaTextBox.value = ''; popUp.remove(); mapSearchedEVStationsModule.clear(); selectedEVStationModule.clear(); mapGeometryModule.clear(); mapBasePOIs.setVisible(true); }; const selectEVStation = (evStation: Place | Place<EVChargingStationWithAvailabilityPlaceProps>) => { selectedEVStationModule.show(evStation); showPopup(evStation); }; const listenToMapUserEvents = async () => { mapEVStationsModule?.events.on('click', async (evStation) => selectEVStation((await getPlaceWithEVAvailability(evStation)) ?? evStation), ); mapSearchedEVStationsModule.events.on('click', async (evWithAvailability) => selectEVStation(evWithAvailability), ); popUp.on('close', () => selectedEVStationModule.clear()); }; const listenToHTMLUserEvents = () => { const searchButton = document.querySelector('#sdk-example-searchButton') as HTMLButtonElement; searchButton.addEventListener('click', searchEVStations); (document.querySelector('#sdk-example-clearButton') as HTMLButtonElement).addEventListener('click', clear); evBrandTextBox.addEventListener('keypress', (event) => event.key === 'Enter' && searchButton.click()); areaTextBox.addEventListener('keypress', (event) => event.key === 'Enter' && searchButton.click()); const minPowerKWMapEVStationsInput = document.querySelector( '#sdk-example-minPowerKWMapEVStations', ) as HTMLInputElement; minPowerKWMapEVStationsInput.value = String(minPowerKWMapEVStations); const minPowerKWSearchedEVStationsInput = document.querySelector( '#sdk-example-minPowerKWSearchedEVStations', ) as HTMLInputElement; minPowerKWSearchedEVStationsInput.value = String(minPowerKWSearchedEVStations); minPowerKWMapEVStationsInput.addEventListener('keyup', async () => { minPowerKWMapEVStations = Number(minPowerKWMapEVStationsInput.value); await placesLayers.update({ id: mapEVStationsId, searchOptions: { minPowerKW: minPowerKWMapEVStations } }); }); minPowerKWSearchedEVStationsInput.addEventListener( 'keyup', () => (minPowerKWSearchedEVStations = Number(minPowerKWSearchedEVStationsInput.value)), ); }; const getChargingPointAvailability = ( place: Place | Place<EVChargingStationWithAvailabilityPlaceProps>, ): { availableCount: number; totalCount: number; ratio: number } | undefined => { const chargingPark = place.properties.chargingPark; if (hasChargingAvailability(chargingPark)) { const availability = chargingPark.availability.chargingPointAvailability; const available = availability.statusCounts.Available ?? 0; return { availableCount: available, totalCount: availability.count, ratio: available / availability.count, }; } return undefined; }; mapEVStationsModule = await placesLayers.addPOICategories({ id: mapEVStationsId, categories: ['ELECTRIC_VEHICLE_STATION'], minZoom: 7, }); await listenToMapUserEvents(); listenToHTMLUserEvents(); })();
Related examples
Add Stops to Route
Find stops along a route within a 5-minute detour and insert them all at optimal positions in a single call.
Places and Search
Web
Routing
Electric Vehicles
Utilities
Along route search playground
Interactively search for places along a route by query or POI category
Playground
Routing
Places and Search
Web
Along route search
Search for EV charging stations along a route using the along-route search API
Routing
Places and Search
Electric Vehicles
Web
Map autocomplete fuzzy search playground
Combine autocomplete search with fuzzy search functionality
Places and Search
Playground
Web
User Interaction Events