components/SpeedLimitProfile/Map.tsx

src/ui/components/widgets/SpeedLimitSection/components/SpeedLimitProfile/Map.tsx
mail@pastecode.io avatar
unknown
typescript
9 days ago
12 kB
3
Indexable
Never
import { useSpeedLimitStore } from '@Contexts'
import { LoadingSpinner } from '@Shared'
import { CCard } from '@coreui/react'
import { Stack, Typography } from '@mui/material'
import L, { divIcon } from 'leaflet'
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import {
  MapContainer,
  Marker,
  Polyline,
  TileLayer,
  Tooltip,
  ZoomControl,
  useMap
} from 'react-leaflet'
import { useSelector } from 'react-redux'
import { AntSwitch } from './AntSwitch'
import * as MapConfig from './config'

const SpeedLimitMap = () => {
  const {
    trackCoordinatesByRouteId: {
      data: trackCoordinatesData,
      loading: trackCoordinatesLoading,
      error: trackCoordinatesError
    }
  } = useSelector((state: any) => state.portalData)
  console.log(
    '🚀 ~ SpeedLimitMap ~ trackCoordinatesData:',
    trackCoordinatesData
  )

  const {
    stationsByRouteId: {
      data: stationsData,
      loading: stationsLoading,
      error: stationsError
    }
  } = useSelector((state: any) => state.portalData)

  const {
    speedLimitsDataByRouteId: {
      // data: speedLimitsData,
      loading: speedLimitsDataLoading,
      error: speedLimitsDataError
    }
  } = useSelector((state: any) => state.portalData)

  const speedLimitData = useSpeedLimitStore.use.speedLimits()

  //for toggles
  const [showMilepostMarkers, setshowMilepostMarkers] = useState(true)
  const [showMileposts, setShowMileposts] = useState(true)
  const [showSpeedLimits, setShowSpeedLimits] = useState(true)
  const hasFitBounds = useRef(false)

  // console.log('🚀 ~ SpeedLimitMap ~ [fitBoundsCalled:', fitBoundsCalled)

  const boundsFit =
    stationsData?.length > 0
      ? L.latLngBounds(
          stationsData?.map((station: any) => [station.lat, station.lng])
        )
      : L.latLngBounds([
          [0, 0],
          [0, 0]
        ])
  console.log('🚀 ~ SpeedLimitMap ~ boundsFit:', boundsFit)

  //for automatic adjustments to the map
  const FitBounds = ({ bounds }: { bounds: L.LatLngBounds }) => {
    const map = useMap()

    console.log('🚀 ~ onLoad ~ hasFitBounds.current:', hasFitBounds.current)
    const onLoad = () => {
      console.log('run on load')
      if (bounds.isValid() && !hasFitBounds.current) {
        console.log('run Effect')
        map.fitBounds(bounds)
        hasFitBounds.current = true
      }
    }

    useEffect(() => {
      console.log('useEffect called')
      if (map) {
        console.log('map is available, setting up onLoad event listener')
        map.on('load', onLoad)
        // Clean up the event listener on component unmount
        return () => {
          console.log('Cleaning up onLoad event listener')
          map.off('load', onLoad)
        }
      }
    }, [])

    return null
  }

  //creating icons that will be displayed for the map
  function createMilepostIcon(milepost: number) {
    return divIcon({
      className:
        'w-full h-full rounded-full flex items-center justify-center text-black',
      iconSize: [20, 20],
      html: `<div style="background-color: white; border: 2px solid gray; border-radius: 50%; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 8px; font-weight: bold;">${milepost}</div>`
    })
  }
  function createTSOIcon(milepost: number) {
    return divIcon({
      className:
        'w-full h-full rounded-full flex items-center justify-center text-black',
      iconSize: [20, 20],
      html: `<div style="background-color: #fcb503; border: 2px solid gray; border-radius: 50%; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 8px; font-weight: bold;">${milepost}</div>`
    })
  }
  const stationMarkerIcon = divIcon({
    className: 'w-full h-full bg-[#555555] rounded-full',
    iconSize: [12, 12]
  })

  //function to render mileposts markers, every 10 Miles, only unique markers
  function renderMilepostMarkers() {
    const processedMileposts = new Set()
    return trackCoordinatesData
      ?.filter((trackCoord: any) => {
        const flooredMilepost = Math.floor(trackCoord?.milepost)
        if (
          flooredMilepost % 10 === 0 &&
          !processedMileposts.has(flooredMilepost)
        ) {
          processedMileposts.add(flooredMilepost)
          return true
        }
        return false
      })
      ?.map((trackCoord: any) => (
        <Marker
          key={trackCoord?.milepost}
          position={[
            trackCoord?.gps_coordinates?.lat,
            trackCoord?.gps_coordinates?.lng
          ]}
          icon={createMilepostIcon(Math.floor(trackCoord?.milepost))}
        ></Marker>
      ))
  }

  if (stationsLoading || trackCoordinatesLoading || speedLimitsDataLoading) {
    return (
      <div className="text-center flex items-center justify-center">
        <div>
          <LoadingSpinner classNames="my-5" />
          <p>Loading map and track data...</p>
        </div>
      </div>
    )
  }

  return (
    <div className="flex justify-center h-[480px] w-full">
      <CCard
        style={{
          width: '100%',
          height: '100%'
        }}
      >
        <MapContainer
          // center={[43.64606677, -79.37180328]}
          zoom={MapConfig.DEFAULT_ZOOM_LEVEL}
          doubleClickZoom={false}
          scrollWheelZoom={MapConfig.SCROLL_WHEEL_ZOOM}
          zoomControl={false}
          bounds={boundsFit}
          boundsOptions={{
            padding: [50, 50],
            maxZoom: 15,
            animate: true
          }}
        >
          <Polyline
            positions={(trackCoordinatesData || [])?.map((trackCoord: any) => [
              trackCoord?.gps_coordinates?.lat,
              trackCoord?.gps_coordinates?.lng
            ])}
            color="#e9f65d"
          />
          <Polyline
            positions={(trackCoordinatesData || [])?.map((trackCoord: any) => [
              trackCoord?.gps_coordinates?.lat,
              trackCoord?.gps_coordinates?.lng
            ])}
            color="#57a7f2"
          />

          {showMileposts && renderMilepostMarkers()}
          {/* <FitBounds bounds={bounds} /> */}
          <TileLayer
            attribution={MapConfig.MAP_ATTRIBUTION}
            url={MapConfig.MAP_URLS.MAPBOX_BASIC}
          />
          <ZoomControl position="bottomright" />
          {showMilepostMarkers &&
            stationsData?.map((station: any) => (
              <Marker
                key={station?.abbreviation}
                position={[station?.lat, station?.lng]}
                icon={stationMarkerIcon}
              >
                <Tooltip
                  direction="top"
                  className="mt-[-14px] drop-shadow-lg rounded-[4px] "
                  permanent
                >
                  <span style={{ fontSize: '0.7rem', fontWeight: 'bold' }}>
                    {station?.abbreviation}
                  </span>
                </Tooltip>
                <div className="relative h-[12px] w-[12px]"></div>
              </Marker>
            ))}
          {showSpeedLimits &&
            speedLimitData?.map((item: any) => {
              return (
                item?.start_gps_coordinates?.lat !== undefined &&
                item?.start_gps_coordinates?.lng !== undefined && (
                  <Marker
                    key={item?.temporary_speed_limit_id}
                    position={[
                      item?.start_gps_coordinates?.lat,
                      item?.start_gps_coordinates?.lng
                    ]}
                    icon={createTSOIcon(Math.floor(item?.start_mile))}
                    zIndexOffset={1000}
                  >
                    <Tooltip
                      direction="top"
                      className="mt-[-20px] drop-shadow-lg rounded-[5px] "
                    >
                      <div className=" flex flex-row items-center justify-start gap-x-[15px] p-3 relative">
                        <div className="flex flex-col items-start justify-center">
                          <p className="m-0 whitespace-nowrap">
                            <span className="font-medium">Subdivision:</span>{' '}
                            {item?.subdivision}
                          </p>
                          <p className="m-0 whitespace-nowrap">
                            <span className="font-medium"> Limit (mph):</span>{' '}
                            {item?.speed_limit_mph}
                          </p>
                          <p className="m-0 whitespace-nowrap">
                            <span className="font-medium">Start Mile:</span>{' '}
                            {item?.start_mile}
                          </p>
                          <p className="m-0 whitespace-nowrap">
                            <span className="font-medium">End Mile:</span>{' '}
                            {item?.end_mile}
                          </p>
                        </div>
                      </div>
                    </Tooltip>
                  </Marker>
                )
              )
            })}
        </MapContainer>
        <Stack
          spacing={1}
          direction="column"
          style={{ position: 'absolute', top: 20, left: 20, zIndex: 1000 }}
        >
          <div style={{ display: 'flex', alignItems: 'center' }}>
            <AntSwitch
              checked={showMileposts}
              onChange={(event: any) => setShowMileposts(event.target.checked)}
              inputProps={{ 'aria-label': 'ant design' }}
            />
            <Typography
              className="ml-[10px] text-gray-700 font-semibold text-sm opacity-80"
              style={{
                fontFamily: 'Inter, sans-serif',
                letterSpacing: '0.5px',
                lineHeight: '1.4'
              }}
            >
              Mileposts
            </Typography>
          </div>
          <div style={{ display: 'flex', alignItems: 'center' }}>
            <AntSwitch
              checked={showMilepostMarkers}
              onChange={(event: any) =>
                setshowMilepostMarkers(event.target.checked)
              }
              inputProps={{ 'aria-label': 'ant design' }}
            />
            <Typography
              className="ml-[10px] text-gray-700 font-semibold text-sm opacity-80"
              style={{
                fontFamily: 'Inter, sans-serif',
                letterSpacing: '0.5px',
                lineHeight: '1.4'
              }}
            >
              Stations
            </Typography>
          </div>
          <div style={{ display: 'flex', alignItems: 'center' }}>
            <AntSwitch
              checked={showSpeedLimits}
              onChange={(event: any) =>
                setShowSpeedLimits(event.target.checked)
              }
              inputProps={{ 'aria-label': 'ant design' }}
            />
            <Typography
              className="ml-[10px] text-gray-700 font-semibold text-sm opacity-80"
              style={{
                fontFamily: 'Inter, sans-serif',
                letterSpacing: '0.5px',
                lineHeight: '1.4'
              }}
            >
              Speed Limits
            </Typography>
          </div>
        </Stack>
      </CCard>
    </div>
  )
}

export default SpeedLimitMap
Leave a Comment