diff --git a/src/Button/GeoLocationButton/GeoLocationButton.example.md b/src/Button/GeoLocationButton/GeoLocationButton.example.md
index 8993b5a251..492caf3f3e 100644
--- a/src/Button/GeoLocationButton/GeoLocationButton.example.md
+++ b/src/Button/GeoLocationButton/GeoLocationButton.example.md
@@ -1,24 +1,23 @@
This demonstrates the use of the geolocation button.
+
```jsx
import GeoLocationButton from '@terrestris/react-geo/dist/Button/GeoLocationButton/GeoLocationButton';
-import ToggleGroup from '@terrestris/react-geo/dist/Button/ToggleGroup/ToggleGroup';
+import MapComponent from '@terrestris/react-util/dist/Components/MapComponent/MapComponent';
+import MapContext from '@terrestris/react-util/dist/Context/MapContext/MapContext';
import OlLayerTile from 'ol/layer/Tile';
import OlMap from 'ol/Map';
import { fromLonLat } from 'ol/proj';
import OlSourceOSM from 'ol/source/OSM';
import OlView from 'ol/View';
-import * as React from 'react';
-
-class GeoLocationButtonExample extends React.Component {
-
- constructor(props) {
+import React, { useEffect, useState } from 'react';
- super(props);
+const GeoLocationButtonExample = () => {
- this.mapDivId = `map-${Math.random()}`;
+ const [map, setMap] = useState();
- this.map = new OlMap({
+ useEffect(() => {
+ setMap(new OlMap({
layers: [
new OlLayerTile({
name: 'OSM',
@@ -26,40 +25,37 @@ class GeoLocationButtonExample extends React.Component {
})
],
view: new OlView({
- center: fromLonLat([37.40570, 8.81566]),
- zoom: 4
+ center: fromLonLat([8, 50]),
+ zoom: 9
})
- });
- }
+ }));
+ }, []);
- componentDidMount() {
- this.map.setTarget(this.mapDivId);
+ if (!map) {
+ return null;
}
- render() {
- return (
-
-
+ <>
+
-
- undefined}
- map={this.map}
- showMarker={true}
- follow={true}
- >
- Track location
-
-
-
- );
- }
-}
+
+ Enable GeoLocation
+
+ >
+
+ );
+};
-
```
diff --git a/src/Button/GeoLocationButton/GeoLocationButton.spec.tsx b/src/Button/GeoLocationButton/GeoLocationButton.spec.tsx
index 73d9901d6a..222da947a7 100644
--- a/src/Button/GeoLocationButton/GeoLocationButton.spec.tsx
+++ b/src/Button/GeoLocationButton/GeoLocationButton.spec.tsx
@@ -5,7 +5,6 @@ import {
} from '@terrestris/react-util/dist/Util/geolocationMock';
import { render, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
-import { transform } from 'ol/proj';
import * as React from 'react';
import TestUtil from '../../Util/TestUtil';
@@ -34,7 +33,7 @@ describe('
', () => {
});
it('can be rendered', () => {
- const { container } = render(
);
+ const { container } = render(
);
expect(container).toBeVisible();
});
@@ -42,9 +41,8 @@ describe('
', () => {
const callback = jest.fn();
const { container } = render(
);
const button = within(container).getByRole('button');
@@ -58,9 +56,8 @@ describe('
', () => {
const callback = jest.fn();
const { container } = render(
);
fireGeolocationListeners();
@@ -85,9 +82,8 @@ describe('
', () => {
const callback = jest.fn();
const { container } = render(
);
const button = within(container).getByRole('button');
@@ -105,12 +101,10 @@ describe('
', () => {
}
});
- const converted = transform(coordinates, 'EPSG:4326', map.getView().getProjection());
-
expect(callback).toBeCalledWith({
accuracy: 7,
heading: 0,
- position: converted,
+ position: coordinates,
speed: 9
});
});
diff --git a/src/Button/GeoLocationButton/GeoLocationButton.tsx b/src/Button/GeoLocationButton/GeoLocationButton.tsx
index 8d4def2447..56a3bea9e5 100644
--- a/src/Button/GeoLocationButton/GeoLocationButton.tsx
+++ b/src/Button/GeoLocationButton/GeoLocationButton.tsx
@@ -1,332 +1,83 @@
-import MathUtil from '@terrestris/base-util/dist/MathUtil/MathUtil';
-import MapUtil from '@terrestris/ol-util/dist/MapUtil/MapUtil';
-import OlFeature from 'ol/Feature';
-import OlGeolocation from 'ol/Geolocation';
-import OlGeometry from 'ol/geom/Geometry';
-import OlGeomLineString from 'ol/geom/LineString';
-import OlGeomPoint from 'ol/geom/Point';
-import OlLayerVector from 'ol/layer/Vector';
-import OlMap from 'ol/Map';
-import RenderFeature from 'ol/render/Feature';
-import OlSourceVector from 'ol/source/Vector';
-import OlStyleIcon from 'ol/style/Icon';
-import OlStyleStyle from 'ol/style/Style';
-import * as React from 'react';
+import {
+ type GeoLocation,
+ useGeoLocation
+} from '@terrestris/react-util/dist/Hooks/useGeoLocation/useGeoLocation';
+import React, {
+ FC,
+ useState
+} from 'react';
import { CSS_PREFIX } from '../../constants';
import ToggleButton, { ToggleButtonProps } from '../ToggleButton/ToggleButton';
-import mapMarker from './geolocation-marker.png';
-import mapMarkerHeading from './geolocation-marker-heading.png';
interface OwnProps {
+ trackingOptions?: PositionOptions;
/**
* Will be called if geolocation fails.
*/
- onError: (error: any) => void;
+ onError?: () => void;
/**
* Will be called when position changes. Receives an object with the properties
* position, accuracy, heading and speed
*/
- onGeolocationChange: (geolocation: any) => void;
+ onGeoLocationChange?: (geolocation: GeoLocation) => void;
/**
* Whether to show a map marker at the current position.
*/
- showMarker: boolean;
+ showMarker?: boolean;
/**
* Whether to follow the current position.
*/
- follow: boolean;
- /**
- * The openlayers tracking options. See also
- * https://www.w3.org/TR/geolocation-API/#position_options_interface
- */
- trackingOptions: {
- maximumAge: number;
- enableHighAccuracy: boolean;
- timeout: number;
- };
-
+ follow?: boolean;
/**
* The className which should be added.
*/
className?: string;
/**
- * Instance of OL map this component is bound to.
+ * Enable tracking of GeoLocations
*/
- map: OlMap;
+ enableTracking?: boolean;
}
export type GeoLocationButtonProps = OwnProps & Omit
, 'onToggle' | 'className'>;
-/**
- * The GeoLocationButton.
- *
- * @class The GeoLocationButton
- * @extends React.Component
- */
-class GeoLocationButton extends React.Component {
-
- /**
- * The default properties.
- */
- static defaultProps = {
- onGeolocationChange: () => undefined,
- onError: () => undefined,
- showMarker: true,
- follow: false,
- trackingOptions: {
- maximumAge: 10000,
- enableHighAccuracy: true,
- timeout: 600000
- }
- };
-
- /**
- * The className added to this component.
- *
- * @private
- */
- _className = `${CSS_PREFIX}geolocationbutton`;
-
- /**
- * The feature marking the current location.
- */
- _markerFeature: OlFeature | null = null;
-
- /**
- * The OpenLayers geolocation interaction.
- */
- _geoLocation: OlGeolocation | null = null;
-
- /**
- * The layer containing the markerFeature.
- */
- _geoLocationLayer = new OlLayerVector({
- properties: {
- name: 'react-geo_geolocationlayer',
- },
- source: new OlSourceVector()
+export const GeoLocationButton: FC = ({
+ className,
+ follow = false,
+ enableTracking = false,
+ onGeoLocationChange = () => undefined,
+ onError = () => undefined,
+ showMarker = true,
+ trackingOptions,
+ ...passThroughProps
+}) => {
+
+ const [isActive, setActive] = useState(false);
+
+ useGeoLocation({
+ active: isActive,
+ enableTracking: isActive,
+ follow,
+ onError,
+ onGeoLocationChange,
+ showMarker,
+ trackingOptions
});
- _positions: OlGeomLineString;
-
- /**
- * Creates the MeasureButton.
- *
- * @constructs MeasureButton
- */
- constructor(props: GeoLocationButtonProps) {
- super(props);
- const {
- map,
- showMarker
- } = this.props;
- const allLayers = MapUtil.getAllLayers(map);
-
- this._positions = new OlGeomLineString([], 'XYZM');
- this._geoLocationLayer.setStyle(this._styleFunction);
- if (!allLayers.includes(this._geoLocationLayer)) {
- map.addLayer(this._geoLocationLayer);
- }
- this.state = {};
-
- if (showMarker) {
- this._markerFeature = new OlFeature();
- this._geoLocationLayer.getSource()?.addFeature(this._markerFeature);
- }
- }
-
- /**
- * Adds the markerFeature if not already done and adds it to the geoLocation
- * layer.
- */
- componentDidUpdate() {
- const {
- showMarker
- } = this.props;
-
- if (showMarker && !this._markerFeature) {
- this._markerFeature = new OlFeature();
- this._geoLocationLayer.getSource()?.addFeature(this._markerFeature);
- }
- }
-
- /**
- * The styleFunction for the geoLocationLayer. Shows a marker with arrow when
- * heading is not 0.
- */
- _styleFunction = (feature: OlFeature | RenderFeature) => {
- const heading = feature.get('heading');
- const src = heading !== 0 ? mapMarkerHeading : mapMarker;
- const rotation = heading !== 0 ? heading * Math.PI / 180 : 0;
-
- return [new OlStyleStyle({
- image: new OlStyleIcon({
- rotation,
- src
- })
- })];
- };
-
- /**
- * Callback of the interactions on change event.
- */
- onGeolocationChange = () => {
- if (!this._geoLocation) {
- return;
- }
-
- const position = this._geoLocation.getPosition() ?? [0, 0];
- const accuracy = this._geoLocation.getAccuracy();
- let heading = this._geoLocation.getHeading() || 0;
- const speed = this._geoLocation.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) {
- const 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));
-
- this.updateView();
-
- this.props.onGeolocationChange({
- position,
- accuracy,
- heading,
- speed
- });
- };
-
- onGeolocationError = (error: any) => {
- this.props.onError(error);
- };
-
- /**
- * 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: boolean) => {
- const {
- showMarker,
- trackingOptions,
- map
- } = this.props;
-
- const view = map.getView();
-
- if (!pressed) {
- if (this._geoLocation) {
- this._geoLocation.un('change', this.onGeolocationChange);
- this._geoLocation = null;
- }
- if (this._markerFeature) {
- this._markerFeature = null;
- this._geoLocationLayer.getSource()?.clear();
- }
- return;
- }
-
- // Geolocation Control
- this._geoLocation = new OlGeolocation({
- projection: view.getProjection(),
- trackingOptions: trackingOptions
- });
- this._geoLocation.setTracking(true);
-
- if (showMarker) {
- if (!this._markerFeature) {
- this._markerFeature = new OlFeature();
- }
- if (!this._geoLocationLayer.getSource()?.getFeatures().includes(this._markerFeature)) {
- this._geoLocationLayer.getSource()?.addFeature(this._markerFeature);
- }
- const heading = this._geoLocation.getHeading() || 0;
- const speed = this._geoLocation.getSpeed() || 0;
- this._markerFeature.set('speed', speed);
- this._markerFeature.set('heading', heading);
- }
-
- // add listeners
- this._geoLocation.on('change', this.onGeolocationChange);
- this._geoLocation.on('error', this.onGeolocationError);
- };
-
- // recenters the view by putting the given coordinates at 3/4 from the top or
- // the screen
- getCenterWithHeading = (position: [number, number], rotation: number, resolution: number) => {
- const size = this.props.map.getSize() ?? [0, 0];
- const height = size[1];
-
- return [
- position[0] - Math.sin(rotation) * height * resolution / 4,
- position[1] + Math.cos(rotation) * height * resolution / 4
- ];
- };
-
- updateView = () => {
- const view = this.props.map.getView();
- const deltaMean = 500; // the geolocation sampling period mean in ms
- // use sampling period to get a smooth transition
- let m = Date.now() - deltaMean * 1.5;
- m = Math.max(m, 0);
-
- // interpolate position along positions LineString
- const c = this._positions.getCoordinateAtM(m, true);
- if (c) {
- if (this.props.follow) {
- view.setCenter(this.getCenterWithHeading([c[0], c[1]], -c[2], view.getResolution() ?? 0));
- view.setRotation(-c[2]);
- }
- if (this.props.showMarker) {
- const pointGeometry = new OlGeomPoint([c[0], c[1]]);
- this._markerFeature?.setGeometry(pointGeometry);
- }
- }
- };
-
- /**
- * The render function.
- */
- render() {
- const {
- className,
- map,
- showMarker,
- follow,
- onGeolocationChange,
- onError,
- trackingOptions,
- ...passThroughProps
- } = this.props;
-
- const finalClassName = className
- ? `${className} ${this._className}`
- : this._className;
-
- return (
-
- );
- }
-}
+ const finalClassName = className
+ ? `${className} ${CSS_PREFIX}geolocationbutton`
+ : `${CSS_PREFIX}geolocationbutton`;
+
+ const onToggle = (pressed: boolean) => setActive(pressed);
+
+ return (
+
+ );
+};
export default GeoLocationButton;
diff --git a/src/Button/GeoLocationButton/geolocation-marker-heading.png b/src/Button/GeoLocationButton/geolocation-marker-heading.png
deleted file mode 100644
index 0790dd54ea..0000000000
Binary files a/src/Button/GeoLocationButton/geolocation-marker-heading.png and /dev/null differ
diff --git a/src/Button/GeoLocationButton/geolocation-marker.png b/src/Button/GeoLocationButton/geolocation-marker.png
deleted file mode 100644
index 6258640647..0000000000
Binary files a/src/Button/GeoLocationButton/geolocation-marker.png and /dev/null differ