Skip to content

Commit

Permalink
added geolocation button
Browse files Browse the repository at this point in the history
  • Loading branch information
hwbllmnn committed Mar 20, 2018
1 parent fc414c7 commit a68b998
Show file tree
Hide file tree
Showing 11 changed files with 482 additions and 0 deletions.
Binary file added assets/geolocation-marker-heading.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/geolocation-marker.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions src/Button/GeoLocationButton/GeoLocationButton.example.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import { render } from 'react-dom';

import OlMap from 'ol/map';
import OlView from 'ol/view';
import OlLayerTile from 'ol/layer/tile';
import OlSourceOsm from 'ol/source/osm';
import olProj from 'ol/proj';

import {
GeoLocationButton,
ToggleGroup
} from '../../index.js';

//
// ***************************** SETUP *****************************************
//
const map = new OlMap({
layers: [
new OlLayerTile({
name: 'OSM',
source: new OlSourceOsm()
})
],
view: new OlView({
center: olProj.fromLonLat([37.40570, 8.81566]),
zoom: 4
})
});

//
// ***************************** SETUP END *************************************
//
render(
<div>
<div id="map" style={{
height: '400px'
}} />

<div className="example-block">
<ToggleGroup>
<GeoLocationButton
onGeolocationChange={() => undefined}
map={map}
showMarker={true}
follow={true}
>
Track location
</GeoLocationButton>
</ToggleGroup>

</div>

</div>,

// Target
document.getElementById('exampleContainer'),

// Callback
() => {
map.setTarget('map');
}
);
8 changes: 8 additions & 0 deletions src/Button/GeoLocationButton/GeoLocationButton.example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
layout: basic.hbs
title: GeoLocationButton example
description: This is an example showing a geolocation toggle button.
collection: Examples
---

This demonstrates the use of the geolocation button.
265 changes: 265 additions & 0 deletions src/Button/GeoLocationButton/GeoLocationButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import React from 'react';
import PropTypes from 'prop-types';

import OlMap from 'ol/map';
import OlGeolocation from 'ol/geolocation';
import OlGeomLineString from 'ol/geom/linestring';
import OlOverlay from 'ol/overlay';

import ToggleButton from '../ToggleButton/ToggleButton.jsx';
import { MathUtil } from '../../index';

import './GeoLocationButton.less';
import mapMarker from '../../../assets/geolocation-marker.png';
import mapMarkerHeading from '../../../assets/geolocation-marker-heading.png';

/**
* The GeoLocationButton.
*
* @class The GeoLocationButton
* @extends React.Component
*/
class GeoLocationButton extends React.Component {

/**
* The className added to this component.
*
* @type {String}
* @private
*/
className = 'react-geo-geolocationbutton';

/**
* The properties.
* @type {Object}
*/
static propTypes = {
/**
* The className which should be added.
*
* @type {String}
*/
className: PropTypes.string,

/**
* Instance of OL map this component is bound to.
*
* @type {OlMap}
*/
map: PropTypes.instanceOf(OlMap).isRequired,

/**
* Will be called if geolocation fails.
*
* @type {Function}
*/
onError: PropTypes.func,

/**
* Will be called when position changes. Receives an object with the properties
* position, accuracy, heading and speed
*
* @type {Function}
*/
onGeolocationChange: PropTypes.func,

/**
* Whether to show a map marker at the current position.
*
* @type {Boolean}
*/
showMarker: PropTypes.bool,

/**
* Whether to follow the current position.
*
* @type {Boolean}
*/
follow: PropTypes.bool
};

/**
* The default properties.
* @type {Object}
*/
static defaultProps = {
onGeolocationChange: () => undefined,
onError: () => undefined,
showMarker: true,
follow: true
}

/**
* Creates the MeasureButton.
*
* @constructs MeasureButton
*/
constructor(props) {
super(props);

this.positions = new OlGeomLineString([], 'XYZM');

this.state = {
};
}

onGeolocationChange = () => {
const position = this.geolocationInteraction.getPosition();
const accuracy = this.geolocationInteraction.getAccuracy();
let heading = this.geolocationInteraction.getHeading() || 0;
const speed = this.geolocationInteraction.getSpeed() || 0;

const x = position[0];
const y = position[1];
const fCoords = this.positions.getCoordinates();
const previous = fCoords[fCoords.length - 1];
const prevHeading = previous && previous[2];
if (prevHeading) {
let headingDiff = heading - MathUtil.mod(prevHeading);

// force the rotation change to be less than 180°
if (Math.abs(headingDiff) > Math.PI) {
var sign = (headingDiff >= 0) ? 1 : -1;
headingDiff = -sign * (2 * Math.PI - Math.abs(headingDiff));
}
heading = prevHeading + headingDiff;
}
this.positions.appendCoordinate([x, y, heading, Date.now()]);

// only keep the 20 last coordinates
this.positions.setCoordinates(this.positions.getCoordinates().slice(-20));

if (this.markerEl) {
if (heading && speed) {
this.markerEl.src = mapMarkerHeading;
} else {
this.markerEl.src = mapMarker;
}
}

this.updateView();

this.props.onGeolocationChange({
position,
accuracy,
heading,
speed
});
}

onGeolocationError = () => {
this.props.onError();
}

/**
* Called when the button is toggled, this method ensures that everything
* is cleaned up when unpressed, and that geolocating can start when pressed.
*
* @method
*/
onToggle = (pressed) => {
if (!pressed && this.geolocationInteraction) {
this.geolocationInteraction.un('change', this.onGeolocationChange);
this.geolocationInteraction = null;
if (this.marker) {
this.props.map.removeOverlay(this.marker);
this.markerEl.parentNode.removeChild(this.markerEl);
}
}
if (!pressed) {
return;
}
const map = this.props.map;
const view = map.getView();

// Geolocation Control
this.geolocationInteraction = new OlGeolocation({
projection: view.getProjection(),
trackingOptions: {
maximumAge: 10000,
enableHighAccuracy: true,
timeout: 600000
}
});
this.geolocationInteraction.setTracking(true);
if (this.props.showMarker) {
this.markerEl = document.getElementById('react-geolocation-overlay').cloneNode();
this.markerEl.id = null;
this.marker = new OlOverlay({
positioning: 'center-center',
element: this.markerEl,
stopEvent: false
});
this.props.map.addOverlay(this.marker);
}

// add listeners
this.geolocationInteraction.on('change', this.onGeolocationChange);
this.geolocationInteraction.on('error', this.onGeolocationError);
}

// recenters the view by putting the given coordinates at 3/4 from the top or
// the screen
getCenterWithHeading = (position, rotation, resolution) => {
var size = this.props.map.getSize();
var height = size[1];

return [
position[0] - Math.sin(rotation) * height * resolution * 1 / 4,
position[1] + Math.cos(rotation) * height * resolution * 1 / 4
];
}

updateView = () => {
const view = this.props.map.getView();
const deltaMean = 500; // the geolocation sampling period mean in ms
let previousM = 0;
// use sampling period to get a smooth transition
let m = Date.now() - deltaMean * 1.5;
m = Math.max(m, previousM);
previousM = m;
// interpolate position along positions LineString
const c = this.positions.getCoordinateAtM(m, true);
if (c) {
if (this.props.follow) {
view.setCenter(this.getCenterWithHeading(c, -c[2], view.getResolution()));
view.setRotation(-c[2]);
}
if (this.props.showMarker) {
this.marker.setPosition(c);
}
}
}

/**
* The render function.
*/
render() {
const {
className,
map,
showMarker,
follow,
onGeolocationChange,
onError,
...passThroughProps
} = this.props;

const finalClassName = className
? `${className} ${this.className}`
: this.className;

return (
<div>
<img id='react-geolocation-overlay' />
<ToggleButton
onToggle={this.onToggle}
className={finalClassName}
{...passThroughProps}
/>
</div>
);
}
}

export default GeoLocationButton;
60 changes: 60 additions & 0 deletions src/Button/GeoLocationButton/GeoLocationButton.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@import '../../style/variables.less';

button.react-geo-measurebutton {
color: #fff;
background-color: @primary-color;
border-color: darken(@component-background, 10);

&:focus{
background-color: lighten(@primary-color, 10);
border-color: lighten(@component-background, 10);
}

&:hover {
background-color: lighten(@primary-color, 10);
border-color: lighten(@component-background, 10);
}

&:disabled {
background-color: @component-background;
border-color: darken(@component-background, 10);
}

&.btn-pressed {
background-color: darken(@primary-color, 20);
border-color: darken(@component-background, 10);
}
}

.react-geo-measure-tooltip {
position: relative;
background: @measure-dynamic-tooltip-background-color;
border-radius: 4px;
color: white;
padding: 4px 8px;
opacity: 0.7;
white-space: nowrap;
}
.react-geo-measure-tooltip-dynamic {
opacity: 1;
font-weight: bold;
}
.react-geo-measure-tooltip-static {
background-color: @measure-static-tooltip-background-color;
color: black;
border: 1px solid white;
}
.react-geo-measure-tooltip-dynamic:before,
.react-geo-measure-tooltip-static:before {
border-top: 6px solid @measure-dynamic-tooltip-background-color;
border-right: 6px solid transparent;
border-left: 6px solid transparent;
content: "";
position: absolute;
bottom: -6px;
margin-left: -7px;
left: 50%;
}
.react-geo-measure-tooltip-static:before {
border-top-color: @measure-static-tooltip-background-color;
}
Loading

0 comments on commit a68b998

Please sign in to comment.