Unverified Commit f0f5610c authored by Anna Sokolova's avatar Anna Sokolova Committed by GitHub

Add files via upload

parents
const commutesPerYear = 260 * 2;
const litresPerKM = 10 / 100;
const gasLitreCost = 1.5;
const litreCostKM = litresPerKM * gasLitreCost;
const secondsPerDay = 60 * 60 * 24;
type DistanceProps = {
leg: google.maps.DirectionsLeg;
};
export default function Distance({ leg }: DistanceProps) {
if (!leg.distance || !leg.duration) return null;
const dist = Math.floor(
(leg.duration.value)
);
return <div>
<p>
Этот адрес находится в <span className="highlight">{leg.distance.text}</span>.
Время в пути <span className="highlight">{leg.duration.text}</span>.
</p>
</div>;
}
import { useState, useMemo, useCallback, useRef } from "react";
import {
GoogleMap,
Marker,
DirectionsRenderer,
Circle,
MarkerClusterer,
} from "@react-google-maps/api";
import Places from "./places";
import Distance from "./distance";
import cluster from "cluster";
type LatLngLiteral = google.maps.LatLngLiteral;
type DirectionsResult = google.maps.DirectionsResult;
type MapOptions = google.maps.MapOptions;
export default function Map() {
const [office, setOffice] = useState<LatLngLiteral>();
const [directions, setDirections] = useState<DirectionsResult>();
const mapRef = useRef<GoogleMap>();
const center = useMemo<LatLngLiteral>(() => ({ lat: 59.967384, lng: 30.289533 }), []);
const options = useMemo<MapOptions>(
() => ({
mapId: "f818ae67dd019888",
disabledDefaultUI: true,
clickableIcons: false,
}),
[]
);
const onLoad = useCallback(map => (mapRef.current = map), []);
const houses = useMemo(() => generateHouses(center), [center]);
const fetchDirections = (house: LatLngLiteral) => {
if (!office) return;
const service = new google.maps.DirectionsService();
service.route(
{
origin: house,
destination: office,
travelMode: google.maps.TravelMode.DRIVING
},
(result, status) => {
if (status === "OK" && result) {
setDirections(result);
}
}
)
}
return (
<div className="container">
<div className="controls">
<h1>Commute?</h1>
<Places setOffice={(position) => {
setOffice(position);
mapRef.current?.panTo(position);
}} />
{!office && <p>Enter the address.</p>}
{directions && <Distance leg={directions.routes[0].legs[0]} />}
</div>
<div className="map">
<GoogleMap
zoom={13}
center={center}
mapContainerClassName="map-container"
options={options}
onLoad={onLoad}
>
{directions && (<DirectionsRenderer directions={directions} options={{
polylineOptions: {
zIndex: 50,
strokeColor: "#1976D2",
strokeWeight: 5,
},
}}/>)}
{office && (
<>
<Marker
position={office}
/>
<MarkerClusterer>
{clusterer =>
houses.map(house => (
<Marker
key={house.lat}
position={house}
clusterer={clusterer}
onClick={() => {
fetchDirections(house);
}}
/>
))
}
</MarkerClusterer>
<Circle center={office} radius={2500} options={defaultOptions}/>
</>
)}
</GoogleMap>
</div>
</div>
);
}
const defaultOptions = {
strokeOpacity: 0,
strokeWeight: 0,
clickable: false,
draggable: false,
editable: false,
visible: false,
};
const closeOptions = {
...defaultOptions,
zIndex: 3,
fillOpacity: 0.05,
strokeColor: "#8BC34A",
fillColor: "#8BC34A",
};
const middleOptions = {
...defaultOptions,
zIndex: 2,
fillOpacity: 0.05,
strokeColor: "#FBC02D",
fillColor: "#FBC02D",
};
const farOptions = {
...defaultOptions,
zIndex: 1,
fillOpacity: 0.05,
strokeColor: "#FF5252",
fillColor: "#FF5252",
};
const generateHouses = (position: LatLngLiteral) => {
const _houses: Array<LatLngLiteral> = [];
for (let i = 0; i < 1000; i++) {
const direction = Math.random() < 0.5 ? -2 : 2;
_houses.push({
lat: position.lat + Math.random() / direction,
lng: position.lng + Math.random() / direction,
});
}
return _houses;
};
import usePlacesAutocomplete, {
getGeocode,
getLatLng,
} from "use-places-autocomplete";
import {
Combobox,
ComboboxInput,
ComboboxPopover,
ComboboxList,
ComboboxOption,
} from "@reach/combobox";
import "@reach/combobox/styles.css";
type PlacesProps = {
setOffice: (position: google.maps.LatLngLiteral) => void;
};
export default function Places({ setOffice }: PlacesProps) {
const {
ready,
value,
setValue,
suggestions: { status, data },
clearSuggestions,
} = usePlacesAutocomplete();
const handleSelect = async (val: string) => {
setValue(val, false);
clearSuggestions();
const results = await getGeocode({ address: val });
const { lat, lng } = await getLatLng(results[0]);
setOffice({ lat, lng });
};
return (
<Combobox onSelect={handleSelect}>
<ComboboxInput
value={value}
onChange={e => setValue(e.target.value)}
disabled={!ready}
className="combobox-input"
placeholder="Search an address"
/>
<ComboboxPopover>
<ComboboxList>
{status === "OK" && data.map(({place_id, description}) => (
<ComboboxOption key={place_id} value={description} />
))}
</ComboboxList>
</ComboboxPopover>
</Combobox>
);
}
declare namespace NodeJS {
export interface ProcessEnv {
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: string;
}
}
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
module.exports = {
reactStrictMode: true,
}
{
"name": "google-maps-next",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@reach/combobox": "^0.16.5",
"@react-google-maps/api": "^2.7.0",
"mmh": "^0.0.0-alpha.0",
"next": "^12.1.6",
"nmh": "^1.0.0",
"nodemon": "^2.0.16",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-google-autocomplete": "^2.6.1",
"use-places-autocomplete": "^1.11.0"
},
"devDependencies": {
"@types/node": "^17.0.17",
"@types/react": "^17.0.39",
"eslint": "8.8.0",
"eslint-config-next": "12.0.10",
"typescript": "^4.5.5"
}
}
import type { AppProps } from "next/app";
import Head from "next/head";
import "../styles/globals.css";
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Head>
<title>Commute?</title>
</Head>
<Component {...pageProps} />
</>
);
}
export default MyApp;
import { useLoadScript } from "@react-google-maps/api";
import Map from "../components/map";
export default function Home() {
const { isLoaded } = useLoadScript({
googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY,
libraries: ["places"],
});
if (!isLoaded) return <div>Loading...</div>;
return <Map />;
}
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
.container {
display: flex;
height: 100vh;
}
.controls {
width: 20%;
padding: 1rem;
background: #14161a;
color: #fff;
}
.controls input {
border: none;
}
.map-container {
width: 100%;
height: 100vh;
}
.map {
width: 80%;
height: 100vh;
}
.highlight {
font-size: 1.25rem;
font-weight: bold;
}
.combobox-input {
width: 100%;
padding: 0.5rem;
}
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment