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 } 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( ` <h3>${poi?.name}</h3> <span class="sdk-example-address">${address.freeformAddress}</span> <br/><br/> ${chargingPark ? connectorsHTML(chargingPark) : 'Charging park data not available.'} `, ) .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
Along route search
Search for EV charging stations along a route using the along-route search API
Routing
Places and Search
Electric Vehicles
Web
Along route search playground
Interactively search for places along a route by query or POI category
Playground
Routing
Places and Search
Web
Map autocomplete fuzzy search playground
Combine autocomplete search with fuzzy search functionality
Places and Search
Playground
Web
User Interaction Events
EV charging stations custom display
Customize EV charging station icons, text, and availability display
Places and Search
Electric Vehicles
Customization
Web