Skip to content

Instantly share code, notes, and snippets.

@Chico-Chen
Created April 2, 2021 11:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Chico-Chen/5a84d82f8084f5b8dd7f88fbf64d8eb1 to your computer and use it in GitHub Desktop.
Save Chico-Chen/5a84d82f8084f5b8dd7f88fbf64d8eb1 to your computer and use it in GitHub Desktop.
complete
import React, { useEffect, useRef, useState, Fragment } from 'react';
import './Map.css';
import Airport from './model/Airport.model';
import Dropdown from 'react-dropdown';
import 'react-dropdown/style.css';
import { plainToClass } from "class-transformer";
import { Button, Input, CircularProgress, Card, Grid, InputLabel, Paper } from '@material-ui/core';
import { Alert, AlertTitle } from '@material-ui/lab';
interface IMap {
mapType: google.maps.MapTypeId,
mapTypeControl?: boolean,
countries: string[],
// airports: Airport[],
// message: string,
}
type GoolgeLatLng = google.maps.LatLng;
type GoogleMap = google.maps.Map;
type GoogleMarker = google.maps.Marker;
const Map: React.FC<IMap> = ({ mapType, mapTypeControl = false, countries }) => {
const ref = useRef<HTMLDivElement>(null);
//map state
const [map, setMap] = useState<GoogleMap>();
const [isLoading, setIsLoanding] = useState(true);
//markers state
const [points, setPoints] = useState<Airport[]>([]);
const [markers, setMarkers] = useState<GoogleMarker[]>([]);
//location info state
const [locationInfo, setLocationInfo] = useState<Airport>();
//starter and terminal state
const [start, setStart] = useState<Airport>();
const [terminal, setTerminal] = useState<Airport>();
const [paths, setPaths] = useState<Airport[]>([]);
const [error, setError] = useState("");
const [distance, setDistance] = useState(0);
const [polyline, setPolyLine] = useState<google.maps.Polyline[]>([]);
const bounds = new google.maps.LatLngBounds();
const baseUrl = "http://localhost:3030/";
//init map
const startMap = (): void => {
if (!map) {
defaultMap();
}
};
useEffect(startMap, [map]);
//set default map
const defaultMap = (): void => {
let latitude = 45.67;
let longitude = 13.369;
const defaultAddress = new google.maps.LatLng(latitude, longitude);
initMap(5, defaultAddress);
};
const initMap = (zoomLevel: number, address: GoolgeLatLng): void => {
if (ref.current) {
setIsLoanding(false);
setMap(
new google.maps.Map(ref.current, {
zoom: zoomLevel,
center: address,
mapTypeControl: mapTypeControl,
streetViewControl: false,
zoomControl: true,
mapTypeId: mapType,
})
);
}
};
//marker click event, show marker's airport info
const markerClickEvent = (): void => {
if (markers) {
markers.map(marker => {
marker.addListener('click', function (e) {
setLocationInfo({ id: marker.get("id"), name: marker.get("name"), lat: marker.get('lat'), lon: marker.get('lon'), score: marker.get('score') });
});
});
}
};
useEffect(markerClickEvent, [markers]);
//map click event, when click map, location info window hide
const mapClickEvent = (): void => {
if (map) {
google.maps.event.addListener(map, 'click', function () {
setLocationInfo(undefined);
});
}
};
useEffect(mapClickEvent, [map]);
//when points change, draw marker on map
const addMarkers = (): void => {
if (points) {
points.map(marker => {
addMarker(marker);
});
map?.fitBounds(bounds);
map?.panToBounds(bounds);
}
};
const addMarker = (airport: Airport): void => {
const position = new google.maps.LatLng(airport.lat, airport.lon);
const marker: GoogleMarker = new google.maps.Marker({
position: position,
map: map,
icon: {
path: google.maps.SymbolPath.CIRCLE,
fillColor: 'red',
fillOpacity: 0.5,
strokeColor: '#00A',
strokeOpacity: 0.7,
strokeWeight: 1,
scale: airport.score > 16 ? Math.pow(airport.score, 0.5) : 4,
}
});
bounds.extend(position);
marker.setValues({ "id": airport.id, "name": airport.name, 'lat': airport.lat, 'lon': airport.lon, 'score': airport.score });
setMarkers(oldMarkers => [...oldMarkers, marker]);
};
useEffect(addMarkers, [points]);
//clear previous markers on map
const clearMarkers = (): void => {
markers.map(marker => {
marker.setMap(null);
})
}
const clearPaths = (): void => {
setPaths([]);
polyline.map(line => {
line.setMap(null);
})
}
//when dropdown change
const onCountryChange = async (e: any) => {
const response = await fetch(baseUrl + 'getAirportsByCountry/' + e.value, {
method: 'GET',
headers: {},
});
const resData = await response.json();
const airportsData = plainToClass(Airport, resData);
clearMarkers();
clearPaths();
setPoints(airportsData);
}
//set start button click event
const onStartClick = (info: Airport) => {
setStart(info);
setLocationInfo(undefined);
}
//terminal button click event
const onTerminalClick = (info: Airport) => {
setTerminal(info);
setLocationInfo(undefined);
}
const onSearchBtnClick = async () => {
//check whether source or terminal is defined
if (start === undefined || terminal === undefined) {
setError("Please select a start point or a terminal!")
return;
}
console.log(start.id === terminal.id);
if (start.id === terminal.id) {
setError("Start point and terminal are same!");
return;
}
setIsLoanding(true);
const params = start.id.replace('/', '%2F') + "&" + terminal.id.replace('/', '%2F');
const response = await fetch(baseUrl + 'getWeightedShortestPath/' + params, {
method: 'GET',
headers: {},
});
const resData = await response.json();
console.log(resData);
if (resData.length === 0) {
console.log("no paths");
setError("No path found! Since the starter or terminal airport low outdegree.")
setIsLoanding(false);
return;
}
setError("");
let distance = resData['distance'];
let path = plainToClass(Airport, resData['paths']);
clearMarkers();
clearPaths();
if (start.id !== path[0].id) {
path.unshift(start);
}
setPoints(path);
setPaths(path);
setDistance(distance);
setIsLoanding(false);
}
//draw flight route on map
const drawLine = (): void => {
let coordinates: google.maps.LatLng[] = [];
if (paths.length !== 0 && map !== undefined) {
console.log(paths);
//add all paths' coordinates into an array
paths.forEach(path => {
coordinates.push(new google.maps.LatLng({ "lat": path.lat, "lng": path.lon }));
});
//draw every route on map
for (let i = 0; i < coordinates.length - 1; i++) {
let line = new google.maps.Polyline({
path: coordinates.slice(i, i + 2),
geodesic: true,
strokeColor: "#FF000",
strokeOpacity: 0.8,
strokeWeight: 2,
icons: [
{
icon: { path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW },
offset: "100%",
},
],
});
line.setMap(map);
setPolyLine(oldline => [...oldline, line]);
}
}
}
useEffect(drawLine, [paths]);
const onNoPathClose = (): void => {
setError("");
}
return (
<Fragment>
<div className="container">
{isLoading && <div className="loadingbg"></div>}
<div className="alert-container">
<Alert security="info">
<AlertTitle>Info</AlertTitle>
Choose a country first to show the airports on map. You could only set Starter and Terminal on Map.
</Alert>
</div>
<div className="card-container">
<Card className='card'>
<Grid container spacing={6}>
<Grid item xs={4}>
<InputLabel>Start</InputLabel>
<Input value={start !== undefined ? start.id : ""} placeholder='From' type='text' required={true}></Input>
</Grid>
<Grid item xs={4}>
<InputLabel>Terminal</InputLabel>
<Input value={terminal !== undefined ? terminal.id : ''} placeholder='To' type='text' required={true}></Input>
</Grid>
<Grid item xs={4}>
<Button variant="contained" onClick={onSearchBtnClick}>Search A Path</Button>
</Grid>
</Grid>
</Card>
</div>
{error != "" &&
<div className="alert-container">
<Alert severity="error" onClose={onNoPathClose}>{error}</Alert>
</div>
}
{paths.length !== 0 &&
<div className="path_container">
<Grid container spacing={3}>
<Grid item xs={12}>
<Paper className="paper">Path:
{paths.map(path => {
if (path !== paths[paths.length - 1]) {
return <span>{path.id} --</span>
}
else {
return <span>{path.id}</span>
}
})}
</Paper>
</Grid>
<Grid item xs={12}>
<Paper className="paper">Distance: {distance} miles</Paper>
</Grid>
</Grid>
</div>
}
<div style={{ height: '650px', width: '100%' }}>
{isLoading &&
<div className="spinner-container">
<CircularProgress className="spinner" />
</div>
}
{!isLoading &&
<Dropdown className="dropdown" options={countries} onChange={onCountryChange} placeholder='Select a country:'></Dropdown>
}
<div ref={ref} className="map-container__map">
{locationInfo &&
<div className="location-info">
<h2>Airport Info</h2>
<ul>
<li>ID: <strong>{locationInfo.id}</strong></li>
<li>Name: <strong>{locationInfo.name}</strong></li>
<li>Outdegree: <strong>{locationInfo.score}</strong></li>
</ul>
<button onClick={() => onStartClick(locationInfo)}>Set Start</button>
<button onClick={() => onTerminalClick(locationInfo)}>Set Terminal</button>
</div>
}
</div>
</div>
</div>
</Fragment >
);
};
export default Map;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment