mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-30 18:16:48 +10:00
Merge pull request #896 from sharetribe/mapbox-map
Mapbox Map component
This commit is contained in:
commit
5e9fb0272b
10 changed files with 359 additions and 64 deletions
|
|
@ -12,6 +12,8 @@ way to update this template, but currently, we follow a pattern:
|
|||
|
||||
---
|
||||
## Upcoming version 2018-08-XX
|
||||
* [add] Map component (used in ListingPage) using Mapbox instead of Google Maps
|
||||
[#896](https://github.com/sharetribe/flex-template-web/pull/896)
|
||||
* [add] Listing availability
|
||||
[#868](https://github.com/sharetribe/flex-template-web/pull/868), [#873](https://github.com/sharetribe/flex-template-web/pull/873), [#891](https://github.com/sharetribe/flex-template-web/pull/891) & [#892](https://github.com/sharetribe/flex-template-web/pull/892)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import { withGoogleMap, GoogleMap, Marker, Circle } from 'react-google-maps';
|
|||
import config from '../../config';
|
||||
|
||||
/**
|
||||
* DynamicMap uses withGoogleMap HOC.
|
||||
* DynamicGoogleMap uses withGoogleMap HOC.
|
||||
* It handles some of the google map initialization states.
|
||||
*/
|
||||
const DynamicMap = withGoogleMap(props => {
|
||||
const DynamicGoogleMap = withGoogleMap(props => {
|
||||
const { center, zoom, address, coordinatesConfig } = props;
|
||||
|
||||
const { markerURI, anchorX, anchorY, width, height } = coordinatesConfig.customMarker || {};
|
||||
|
|
@ -34,6 +34,11 @@ const DynamicMap = withGoogleMap(props => {
|
|||
|
||||
const circle = <Circle {...circleProps} />;
|
||||
|
||||
const controlPosition =
|
||||
typeof window !== 'undefined' && typeof window.google !== 'undefined'
|
||||
? window.google.maps.ControlPosition.LEFT_TOP
|
||||
: 5;
|
||||
|
||||
return (
|
||||
<GoogleMap
|
||||
defaultZoom={zoom}
|
||||
|
|
@ -45,7 +50,13 @@ const DynamicMap = withGoogleMap(props => {
|
|||
// Disable zooming by scrolling
|
||||
scrollwheel: false,
|
||||
// Fullscreen control toggle
|
||||
fullscreenControl: true,
|
||||
fullscreenControl: false,
|
||||
// Street View control
|
||||
streetViewControl: false,
|
||||
// Zoom control position
|
||||
zoomControlOptions: {
|
||||
position: controlPosition,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{coordinatesConfig.fuzzy ? circle : marker}
|
||||
|
|
@ -53,14 +64,14 @@ const DynamicMap = withGoogleMap(props => {
|
|||
);
|
||||
});
|
||||
|
||||
DynamicMap.defaultProps = {
|
||||
DynamicGoogleMap.defaultProps = {
|
||||
address: '',
|
||||
center: null,
|
||||
zoom: config.coordinates.fuzzy ? config.coordinates.fuzzyDefaultZoomLevel : 11,
|
||||
coordinatesConfig: config.coordinates,
|
||||
};
|
||||
|
||||
DynamicMap.propTypes = {
|
||||
DynamicGoogleMap.propTypes = {
|
||||
address: string,
|
||||
center: shape({
|
||||
lat: number.isRequired,
|
||||
|
|
@ -70,4 +81,4 @@ DynamicMap.propTypes = {
|
|||
coordinatesConfig: object,
|
||||
};
|
||||
|
||||
export default DynamicMap;
|
||||
export default DynamicGoogleMap;
|
||||
167
src/components/Map/DynamicMapboxMap.js
Normal file
167
src/components/Map/DynamicMapboxMap.js
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import React, { Component } from 'react';
|
||||
import { string, shape, number, object } from 'prop-types';
|
||||
import uniqueId from 'lodash/uniqueId';
|
||||
import { circlePolyline } from '../../util/maps';
|
||||
import config from '../../config';
|
||||
|
||||
const mapMarker = coordinatesConfig => {
|
||||
const { customMarker } = coordinatesConfig;
|
||||
if (customMarker) {
|
||||
const element = document.createElement('div');
|
||||
element.style.backgroundImage = `url(${customMarker.markerURI})`;
|
||||
element.style.width = `${customMarker.width}px`;
|
||||
element.style.height = `${customMarker.height}px`;
|
||||
return new window.mapboxgl.Marker({ element });
|
||||
} else {
|
||||
return new window.mapboxgl.Marker();
|
||||
}
|
||||
};
|
||||
|
||||
const circleLayer = (center, coordinatesConfig, layerId) => {
|
||||
const { fillColor, fillOpacity } = coordinatesConfig.circleOptions;
|
||||
const path = circlePolyline(center, coordinatesConfig.coordinateOffset).map(([lat, lng]) => [
|
||||
lng,
|
||||
lat,
|
||||
]);
|
||||
return {
|
||||
id: layerId,
|
||||
type: 'fill',
|
||||
source: {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [path],
|
||||
},
|
||||
},
|
||||
},
|
||||
paint: {
|
||||
'fill-color': fillColor,
|
||||
'fill-opacity': fillOpacity,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const generateFuzzyLayerId = () => {
|
||||
return uniqueId('fuzzy_layer_');
|
||||
};
|
||||
|
||||
class DynamicMapboxMap extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.mapContainer = null;
|
||||
this.map = null;
|
||||
this.centerMarker = null;
|
||||
this.fuzzyLayerId = generateFuzzyLayerId();
|
||||
|
||||
this.updateFuzzyCirclelayer = this.updateFuzzyCirclelayer.bind(this);
|
||||
}
|
||||
componentDidMount() {
|
||||
const { center, zoom, coordinatesConfig } = this.props;
|
||||
const position = [center.lng, center.lat];
|
||||
|
||||
this.map = new window.mapboxgl.Map({
|
||||
container: this.mapContainer,
|
||||
style: 'mapbox://styles/mapbox/streets-v10',
|
||||
center: position,
|
||||
zoom,
|
||||
scrollZoom: false,
|
||||
});
|
||||
this.map.addControl(new window.mapboxgl.NavigationControl(), 'top-left');
|
||||
|
||||
if (coordinatesConfig.fuzzy) {
|
||||
this.map.on('load', () => {
|
||||
this.map.addLayer(circleLayer(center, coordinatesConfig, this.fuzzyLayerId));
|
||||
});
|
||||
} else {
|
||||
this.centerMarker = mapMarker(coordinatesConfig);
|
||||
this.centerMarker.setLngLat(position).addTo(this.map);
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (this.map) {
|
||||
this.centerMarker = null;
|
||||
this.map.remove();
|
||||
this.map = null;
|
||||
}
|
||||
}
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!this.map) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { center, zoom, coordinatesConfig } = this.props;
|
||||
const { lat, lng } = center;
|
||||
const position = [lng, lat];
|
||||
|
||||
// zoom change
|
||||
if (zoom !== prevProps.zoom) {
|
||||
this.map.setZoom(this.props.zoom);
|
||||
}
|
||||
|
||||
const centerChanged = lat !== prevProps.center.lat || lng !== prevProps.center.lng;
|
||||
|
||||
// center marker change
|
||||
if (this.centerMarker && centerChanged) {
|
||||
this.centerMarker.setLngLat(position);
|
||||
this.map.setCenter(position);
|
||||
}
|
||||
|
||||
// fuzzy circle change
|
||||
if (coordinatesConfig.fuzzy && centerChanged) {
|
||||
if (this.map.loaded()) {
|
||||
this.updateFuzzyCirclelayer();
|
||||
} else {
|
||||
this.map.on('load', this.updateFuzzyCirclelayer);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: coordinatesConfig changes are not handled
|
||||
}
|
||||
updateFuzzyCirclelayer() {
|
||||
if (!this.map) {
|
||||
// map already removed
|
||||
return;
|
||||
}
|
||||
const { center, coordinatesConfig } = this.props;
|
||||
const { lat, lng } = center;
|
||||
const position = [lng, lat];
|
||||
|
||||
this.map.removeLayer(this.fuzzyLayerId);
|
||||
|
||||
// We have to use a different layer id to avoid Mapbox errors
|
||||
this.fuzzyLayerId = generateFuzzyLayerId();
|
||||
this.map.addLayer(circleLayer(center, coordinatesConfig, this.fuzzyLayerId));
|
||||
|
||||
this.map.setCenter(position);
|
||||
}
|
||||
render() {
|
||||
const { containerClassName, mapClassName } = this.props;
|
||||
return (
|
||||
<div className={containerClassName}>
|
||||
<div className={mapClassName} ref={el => (this.mapContainer = el)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DynamicMapboxMap.defaultProps = {
|
||||
address: '',
|
||||
center: null,
|
||||
zoom: config.coordinates.fuzzy ? config.coordinates.fuzzyDefaultZoomLevel : 11,
|
||||
coordinatesConfig: config.coordinates,
|
||||
};
|
||||
|
||||
DynamicMapboxMap.propTypes = {
|
||||
address: string, // not used
|
||||
center: shape({
|
||||
lat: number.isRequired,
|
||||
lng: number.isRequired,
|
||||
}).isRequired,
|
||||
zoom: number,
|
||||
coordinatesConfig: object,
|
||||
};
|
||||
|
||||
export default DynamicMapboxMap;
|
||||
6
src/components/Map/GoogleMap.js
Normal file
6
src/components/Map/GoogleMap.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export { default as DynamicMap } from './DynamicGoogleMap';
|
||||
export { default as StaticMap } from './StaticGoogleMap';
|
||||
|
||||
export const isMapsLibLoaded = () => {
|
||||
return typeof window !== 'undefined' && window.google && window.google.maps;
|
||||
};
|
||||
|
|
@ -3,9 +3,9 @@ import { bool, number, object, string } from 'prop-types';
|
|||
import classNames from 'classnames';
|
||||
import { propTypes } from '../../util/types';
|
||||
import config from '../../config';
|
||||
import { StaticMap, DynamicMap, isMapsLibLoaded } from './GoogleMap';
|
||||
// import { StaticMap, DynamicMap, isMapsLibLoaded } from './MapboxMap';
|
||||
|
||||
import DynamicMap from './DynamicMap';
|
||||
import StaticMap from './StaticMap';
|
||||
import css from './Map.css';
|
||||
|
||||
export class Map extends Component {
|
||||
|
|
@ -34,24 +34,23 @@ export class Map extends Component {
|
|||
}
|
||||
|
||||
const location = coordinatesConfig.fuzzy ? obfuscatedCenter : center;
|
||||
const centerLocationForGoogleMap = { lat: location.lat, lng: location.lng };
|
||||
|
||||
const isMapsLibLoaded = typeof window !== 'undefined' && window.google && window.google.maps;
|
||||
|
||||
return !isMapsLibLoaded ? (
|
||||
return !isMapsLibLoaded() ? (
|
||||
<div className={classes} />
|
||||
) : useStaticMap ? (
|
||||
<StaticMap
|
||||
center={centerLocationForGoogleMap}
|
||||
center={location}
|
||||
zoom={zoom}
|
||||
address={address}
|
||||
coordinatesConfig={coordinatesConfig}
|
||||
/>
|
||||
) : (
|
||||
<DynamicMap
|
||||
containerElement={<div className={classes} onClick={this.onMapClicked} />}
|
||||
containerElement={<div className={classes} />}
|
||||
mapElement={<div className={mapClasses} />}
|
||||
center={centerLocationForGoogleMap}
|
||||
containerClassName={classes}
|
||||
mapClassName={mapClasses}
|
||||
center={location}
|
||||
zoom={zoom}
|
||||
address={address}
|
||||
coordinatesConfig={coordinatesConfig}
|
||||
|
|
|
|||
6
src/components/Map/MapboxMap.js
Normal file
6
src/components/Map/MapboxMap.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export { default as DynamicMap } from './DynamicMapboxMap';
|
||||
export { default as StaticMap } from './StaticMapboxMap';
|
||||
|
||||
export const isMapsLibLoaded = () => {
|
||||
return typeof window !== 'undefined' && window.mapboxgl;
|
||||
};
|
||||
|
|
@ -5,46 +5,13 @@ import isEqual from 'lodash/isEqual';
|
|||
import polyline from '@mapbox/polyline';
|
||||
import { encodeLatLng, stringify } from '../../util/urlHelpers';
|
||||
import { lazyLoadWithDimensions } from '../../util/contextHelpers';
|
||||
import { circlePolyline } from '../../util/maps';
|
||||
import config from '../../config';
|
||||
|
||||
const DEFAULT_COLOR = 'FF0000';
|
||||
const DEFAULT_STROKE_OPACITY = 0.3;
|
||||
const DEFAULT_FILL_OPACITY = 0.2;
|
||||
|
||||
// Return polyline encoded list of points forming a circle
|
||||
// This algorithm is based on
|
||||
// https://stackoverflow.com/questions/7316963/drawing-a-circle-google-static-maps
|
||||
const getEncodedGMapCirclePoints = (lat, lng, rad, detail = 8) => {
|
||||
const R = 6371;
|
||||
const pi = Math.PI;
|
||||
|
||||
const _lat = lat * pi / 180;
|
||||
const _lng = lng * pi / 180;
|
||||
const d = rad / 1000 / R;
|
||||
|
||||
let points = [];
|
||||
for (let i = 0; i <= 360; i += detail) {
|
||||
const brng = i * pi / 180;
|
||||
|
||||
let pLat = Math.asin(
|
||||
Math.sin(_lat) * Math.cos(d) + Math.cos(_lat) * Math.sin(d) * Math.cos(brng)
|
||||
);
|
||||
const pLng =
|
||||
(_lng +
|
||||
Math.atan2(
|
||||
Math.sin(brng) * Math.sin(d) * Math.cos(_lat),
|
||||
Math.cos(d) - Math.sin(_lat) * Math.sin(pLat)
|
||||
)) *
|
||||
180 /
|
||||
pi;
|
||||
pLat = pLat * 180 / pi;
|
||||
|
||||
points.push([pLat.toFixed(6), pLng.toFixed(6)]);
|
||||
}
|
||||
|
||||
return polyline.encode(points);
|
||||
};
|
||||
|
||||
// Extract color from string. Given value should be either with '#' (e.g. #FFFFFF') or without it.
|
||||
const formatColorFromString = color => {
|
||||
if (typeof color === 'string' && /^#[0-9A-F]{6}$/i.test(color)) {
|
||||
|
|
@ -80,7 +47,6 @@ const drawFuzzyCircle = (coordinatesConfig, center) => {
|
|||
}
|
||||
|
||||
const { fillColor, fillOpacity, strokeColor, strokeWeight } = coordinatesConfig.circleOptions;
|
||||
const { lat, lng } = center;
|
||||
|
||||
const circleRadius = coordinatesConfig.coordinateOffset || 500;
|
||||
const circleStrokeWeight = strokeWeight || 1;
|
||||
|
|
@ -89,8 +55,8 @@ const drawFuzzyCircle = (coordinatesConfig, center) => {
|
|||
const circleFill = formatColorFromString(fillColor);
|
||||
const circleFillOpacity = convertOpacity(fillOpacity || DEFAULT_FILL_OPACITY);
|
||||
|
||||
//Encoded polyline string
|
||||
const encodedPolyline = getEncodedGMapCirclePoints(lat, lng, circleRadius, 8);
|
||||
// Encoded polyline string
|
||||
const encodedPolyline = polyline.encode(circlePolyline(center, circleRadius));
|
||||
|
||||
const polylineGraphicTokens = [
|
||||
`color:0x${circleStrokeColor}${circleStrokeOpacity}`,
|
||||
|
|
@ -108,7 +74,7 @@ const customMarker = (options, lat, lng) => {
|
|||
return [`anchor:${anchorX},${anchorY}`, `icon:${markerURI}`, `${lat},${lng}`].join('|');
|
||||
};
|
||||
|
||||
class StaticMap extends Component {
|
||||
class StaticGoogleMap extends Component {
|
||||
shouldComponentUpdate(nextProps, prevState) {
|
||||
// Do not draw the map unless center, zoom or dimensions change
|
||||
// We want to prevent unnecessary calls to Google Maps APIs due
|
||||
|
|
@ -147,7 +113,7 @@ class StaticMap extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
StaticMap.defaultProps = {
|
||||
StaticGoogleMap.defaultProps = {
|
||||
className: null,
|
||||
rootClassName: null,
|
||||
address: '',
|
||||
|
|
@ -156,7 +122,7 @@ StaticMap.defaultProps = {
|
|||
coordinatesConfig: config.coordinates,
|
||||
};
|
||||
|
||||
StaticMap.propTypes = {
|
||||
StaticGoogleMap.propTypes = {
|
||||
className: string,
|
||||
rootClassName: string,
|
||||
address: string,
|
||||
|
|
@ -174,4 +140,4 @@ StaticMap.propTypes = {
|
|||
}).isRequired,
|
||||
};
|
||||
|
||||
export default lazyLoadWithDimensions(StaticMap, { maxWidth: '640px' });
|
||||
export default lazyLoadWithDimensions(StaticGoogleMap, { maxWidth: '640px' });
|
||||
89
src/components/Map/StaticMapboxMap.js
Normal file
89
src/components/Map/StaticMapboxMap.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import React from 'react';
|
||||
import { string, shape, number, object } from 'prop-types';
|
||||
import polyline from '@mapbox/polyline';
|
||||
import { lazyLoadWithDimensions } from '../../util/contextHelpers';
|
||||
import { circlePolyline } from '../../util/maps';
|
||||
import config from '../../config';
|
||||
|
||||
const formatColor = color => {
|
||||
return color.replace(/^#/, '');
|
||||
};
|
||||
|
||||
const fuzzyCircleOverlay = (center, coordinatesConfig) => {
|
||||
const {
|
||||
strokeWeight,
|
||||
strokeColor,
|
||||
strokeOpacity,
|
||||
fillColor,
|
||||
fillOpacity,
|
||||
} = coordinatesConfig.circleOptions;
|
||||
const path = circlePolyline(center, coordinatesConfig.coordinateOffset);
|
||||
const styles = `-${strokeWeight}+${formatColor(strokeColor)}-${strokeOpacity}+${formatColor(
|
||||
fillColor
|
||||
)}-${fillOpacity}`;
|
||||
return `path${styles}(${encodeURIComponent(polyline.encode(path))})`;
|
||||
};
|
||||
|
||||
const customMarkerOverlay = (center, coordinatesConfig) => {
|
||||
const { markerURI } = coordinatesConfig.customMarker;
|
||||
return `url-${encodeURIComponent(markerURI)}(${center.lng},${center.lat})`;
|
||||
};
|
||||
|
||||
const markerOverlay = center => {
|
||||
return `pin-s(${center.lng},${center.lat})`;
|
||||
};
|
||||
|
||||
const mapOverlay = (center, coordinatesConfig) => {
|
||||
if (coordinatesConfig.fuzzy) {
|
||||
return fuzzyCircleOverlay(center, coordinatesConfig);
|
||||
}
|
||||
if (coordinatesConfig.customMarker) {
|
||||
return customMarkerOverlay(center, coordinatesConfig);
|
||||
}
|
||||
return markerOverlay(center);
|
||||
};
|
||||
|
||||
const StaticMapboxMap = props => {
|
||||
const { address, center, zoom, coordinatesConfig, dimensions } = props;
|
||||
const { width, height } = dimensions;
|
||||
|
||||
const libLoaded = typeof window !== 'undefined' && window.mapboxgl;
|
||||
if (!libLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const overlay = mapOverlay(center, coordinatesConfig);
|
||||
const src =
|
||||
'https://api.mapbox.com/styles/v1/mapbox/streets-v10/static' +
|
||||
(overlay ? `/${overlay}` : '') +
|
||||
`/${center.lng},${center.lat},${zoom}` +
|
||||
`/${width}x${height}` +
|
||||
`?access_token=${window.mapboxgl.accessToken}`;
|
||||
|
||||
return <img src={src} alt={address} />;
|
||||
};
|
||||
|
||||
StaticMapboxMap.defaultProps = {
|
||||
address: '',
|
||||
center: null,
|
||||
zoom: config.coordinates.fuzzy ? config.coordinates.fuzzyDefaultZoomLevel : 11,
|
||||
coordinatesConfig: config.coordinates,
|
||||
};
|
||||
|
||||
StaticMapboxMap.propTypes = {
|
||||
address: string,
|
||||
center: shape({
|
||||
lat: number.isRequired,
|
||||
lng: number.isRequired,
|
||||
}).isRequired,
|
||||
zoom: number,
|
||||
coordinatesConfig: object,
|
||||
|
||||
// from withDimensions
|
||||
dimensions: shape({
|
||||
width: number.isRequired,
|
||||
height: number.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default lazyLoadWithDimensions(StaticMapboxMap, { maxWidth: '640px' });
|
||||
|
|
@ -28,20 +28,25 @@ class SectionMapMaybe extends Component {
|
|||
const mapProps = config.coordinates.fuzzy
|
||||
? { obfuscatedCenter: obfuscatedCoordinates(geolocation, listingId ? listingId.uuid : null) }
|
||||
: { address, center: geolocation };
|
||||
const map = <Map {...mapProps} useStaticMap={this.state.isStatic} />;
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<h2 className={css.locationTitle}>
|
||||
<FormattedMessage id="ListingPage.locationTitle" />
|
||||
</h2>
|
||||
<button
|
||||
className={css.map}
|
||||
onClick={() => {
|
||||
this.setState({ isStatic: false });
|
||||
}}
|
||||
>
|
||||
<Map {...mapProps} useStaticMap={this.state.isStatic} />
|
||||
</button>
|
||||
{this.state.isStatic ? (
|
||||
<button
|
||||
className={css.map}
|
||||
onClick={() => {
|
||||
this.setState({ isStatic: false });
|
||||
}}
|
||||
>
|
||||
{map}
|
||||
</button>
|
||||
) : (
|
||||
<div className={css.map}>{map}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,3 +86,47 @@ export const userLocation = () =>
|
|||
|
||||
navigator.geolocation.getCurrentPosition(onSuccess, onError);
|
||||
});
|
||||
|
||||
/**
|
||||
* Calculate a circular polyline around the given point
|
||||
*
|
||||
* See: https://stackoverflow.com/questions/7316963/drawing-a-circle-google-static-maps
|
||||
*
|
||||
* @param {LatLng} latlng - center of the circle
|
||||
* @param {Number} radius - radius of the circle
|
||||
*
|
||||
* @return {Array<Array<Number>>} array of `[lat, lng]` coordinate
|
||||
* pairs forming the circle
|
||||
*/
|
||||
export const circlePolyline = (latlng, radius) => {
|
||||
const { lat, lng } = latlng;
|
||||
const detail = 8;
|
||||
const R = 6371;
|
||||
const pi = Math.PI;
|
||||
|
||||
const _lat = lat * pi / 180;
|
||||
const _lng = lng * pi / 180;
|
||||
const d = radius / 1000 / R;
|
||||
|
||||
let points = [];
|
||||
for (let i = 0; i <= 360; i += detail) {
|
||||
const brng = i * pi / 180;
|
||||
|
||||
let pLat = Math.asin(
|
||||
Math.sin(_lat) * Math.cos(d) + Math.cos(_lat) * Math.sin(d) * Math.cos(brng)
|
||||
);
|
||||
const pLng =
|
||||
(_lng +
|
||||
Math.atan2(
|
||||
Math.sin(brng) * Math.sin(d) * Math.cos(_lat),
|
||||
Math.cos(d) - Math.sin(_lat) * Math.sin(pLat)
|
||||
)) *
|
||||
180 /
|
||||
pi;
|
||||
pLat = pLat * 180 / pi;
|
||||
|
||||
points.push([pLat, pLng]);
|
||||
}
|
||||
|
||||
return points;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue