Skip to content

Commit

Permalink
Add Retail Locations to map
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenyeargin committed Apr 2, 2024
1 parent d7a6d05 commit c614da9
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 5 deletions.
5 changes: 2 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 72 additions & 0 deletions src/components/RetailLocationMarker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import PropTypes from 'prop-types';
import L from 'leaflet';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faArrowsRotate,
faDirections, faMapMarkedAlt, faTicket,
} from '@fortawesome/free-solid-svg-icons';
import {
Marker, Popup, Tooltip,
} from 'react-leaflet';
import retailLocationFullServiceIcon from '../resources/retail-full-service.svg';
import retailLocationReloadOnlyIcon from '../resources/retail-reload-only.svg';

function RetailLocationMarker({
retailLocation,
}) {
const retailLocationMarkerOptions = {
iconUrl: retailLocation.can_buy_media ? retailLocationFullServiceIcon : retailLocationReloadOnlyIcon,
iconSize: retailLocation.can_buy_media ? [20, 20] : [15, 15],
shadowUrl: null,
};

const headerColor = retailLocation.can_buy_media ? 'purple' : '#444444';

const retailLocationMarkerIcon = L.Icon.extend({ options: retailLocationMarkerOptions });
const icon = new retailLocationMarkerIcon();

const content = (
<div style={{ minWidth: '300px', maxWidth: '500px', overflow: 'hidden' }}>
<div className="stop-name d-flex" style={{backgroundColor: headerColor}}>
<div className="flex-grow-1">
{retailLocation.name}
</div>
</div>
<div>
<dl className={'row'}>
<dt className={'col-5'}><FontAwesomeIcon icon={faMapMarkedAlt} fixedWidth={true}></FontAwesomeIcon> Address</dt>
<dd className={'col-7'}>{retailLocation.address}</dd>
<dt className={'col-5'}><FontAwesomeIcon icon={faTicket} fixedWidth={true}></FontAwesomeIcon> Buy Card?</dt>
<dd className={'col-7'}>{retailLocation.can_buy_media ? 'Yes' : 'No'}</dd>
<dt className={'col-5'}><FontAwesomeIcon icon={faArrowsRotate} fixedWidth={true}></FontAwesomeIcon> Reload?</dt>
<dd className={'col-7'}>{retailLocation.can_reload_media ? 'Yes' : 'No'}</dd>
</dl>
</div>
</div>
);

return (
<>
<Marker position={[retailLocation.latitude, retailLocation.longitude]} icon={icon}>
{!L.Browser.mobile && (
<Tooltip>{content}</Tooltip>
)}
<Popup>
{content}
<div className="text-center my-2"><a href={`https://www.google.com/maps/dir/?api=1&travelmode=transit&destination=${retailLocation.latitude}%2C${retailLocation.longitude}`} className="btn bg-secondary text-light btn-sm" target="_blank" rel="noreferrer"><FontAwesomeIcon icon={faDirections} fixedWidth={true} /> Directions</a></div>
</Popup>
</Marker>
</>
);
}

RetailLocationMarker.propTypes = {
retailLocation: PropTypes.object.isRequired,
};

RetailLocationMarker.defaultProps = {
retailLocation: {},
};

export default RetailLocationMarker;
17 changes: 17 additions & 0 deletions src/components/RetailLocationMarker.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* globals test */

import React from 'react';
import { createRoot } from 'react-dom/client';
import RetailLocationMarker from './RetailLocationMarker';
import { MapContainer } from 'react-leaflet';

const retailLocations = require('../fixtures/retail_locations.json');

test('renders RetailLocationMarker', () => {
const div = document.createElement('div');
const root = createRoot(div);
const retailLocation = retailLocations.data[0];
root.render(<MapContainer>
<RetailLocationMarker retailLocation={retailLocation} />
</MapContainer>);
});
14 changes: 13 additions & 1 deletion src/components/TransitMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import LocationMarker from './LocationMarker';
import BCycleMarker from './BCycleMarker';
import LocateButton from './LocateButton';
import countyBorders from '../lib/davidson_county_borders.json';
import RetailLocationMarker from './RetailLocationMarker';

// Fix paths for default marker icons
delete L.Icon.Default.prototype._getIconUrl;
Expand All @@ -26,7 +27,7 @@ L.Icon.Default.mergeOptions({
});

function TransitMap({
routes, agencies, vehicleMarkers, routeShapes, routeStops, alerts, tripUpdates, map, bCycleStations, mapControls, center, zoom,
routes, agencies, vehicleMarkers, routeShapes, routeStops, alerts, tripUpdates, map, bCycleStations, retailLocations, mapControls, center, zoom,
}) {
const [shapes, setShapes] = useState(routeShapes);
const doSetShapes = useCallback((val) => {
Expand Down Expand Up @@ -162,6 +163,15 @@ function TransitMap({
</LayerGroup>
</LayersControl.Overlay>
}
{retailLocations.length > 0
&& <LayersControl.Overlay checked={false} name="QuickTicket Retailers">
<LayerGroup>
{retailLocations.map((item, _index) => (
<RetailLocationMarker key={item.location_code} retailLocation={item}></RetailLocationMarker>
))}
</LayerGroup>
</LayersControl.Overlay>
}

<LayersControl.Overlay checked={true} name="City Border">
<GeoJSON data={countyBorders} style={{
Expand Down Expand Up @@ -199,6 +209,7 @@ TransitMap.propTypes = {
tripUpdates: PropTypes.array,
map: PropTypes.any.isRequired,
bCycleStations: PropTypes.array,
retailLocations: PropTypes.array,
mapControls: PropTypes.object,
center: PropTypes.any,
zoom: PropTypes.number,
Expand All @@ -213,6 +224,7 @@ TransitMap.defaultProps = {
alerts: [],
tripUpdates: [],
bCycleStations: [],
retailLocations: [],
mapControls: {},
center: [36.166512, -86.781581],
zoom: 12,
Expand Down
8 changes: 7 additions & 1 deletion src/controllers/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function Main() {
const [tripUpdates, setTripUpdates] = useState([]);
const [bCycleStations, setBCycleStationData] = useState([]);
const [bCycleStationsStatus, setBCycleStationStatusData] = useState([]);
const [retailLocations, setRetailLocationsData] = useState([]);
const [isRoutesLoaded, setRoutesLoaded] = useState(false);
const [isAlertLoaded, setAlertLoaded] = useState(false);
const [isTripUpdateLoaded, setTripUpdateLoaded] = useState(false);
Expand Down Expand Up @@ -69,6 +70,11 @@ function Main() {
.then((s) => setBCycleStationStatusData(s.data.stations))
.catch((error) => setDataFetchError(error));

getJSON(`${GTFS_BASE_URL}/retail_locations.json`, { params: { per_page: 2000 } })
.then((r) => r.data.filter((i) => i.is_active))
.then((r) => setRetailLocationsData(r))
.catch((error) => setDataFetchError(error));

// Refresh position data at set interval
const refreshPositionsInterval = setInterval(() => {
if (!isUIReady) {
Expand Down Expand Up @@ -155,7 +161,7 @@ function Main() {
<LoadingScreen hideTitleBar={true}></LoadingScreen>
) : (
<div className="main">
<TransitMap routes={routes} agencies={agencies} vehicleMarkers={vehicleMarkers} shapes={[]} alerts={allAlerts} tripUpdates={tripUpdates} map={map} bCycleStations={bCycleStations} mapControls={mapControls}></TransitMap>
<TransitMap routes={routes} agencies={agencies} vehicleMarkers={vehicleMarkers} shapes={[]} alerts={allAlerts} tripUpdates={tripUpdates} map={map} bCycleStations={bCycleStations} retailLocations={retailLocations} mapControls={mapControls}></TransitMap>
<AlertModal alerts={allAlerts} show={alertModalShow} onHide={() => setAlertModalShow(false)} routes={routes}></AlertModal>
</div>
)
Expand Down
1 change: 1 addition & 0 deletions src/fixtures/retail_locations.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/resources/retail-full-service.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/resources/retail-reload-only.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit c614da9

Please sign in to comment.