Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve camera transitions and related map callbacks #1838

Merged
merged 43 commits into from
Apr 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
273a2c2
Enable setting camera bounds (iOS)
naftalibeder Apr 11, 2022
8f51b00
Implement camera easing, organize dict value extraction (iOS)
naftalibeder Apr 12, 2022
782b342
Merge branch 'main' of github.com:rnmapbox/maps into feat/map
naftalibeder Apr 12, 2022
3188450
Add remaining camera animation mode types (iOS)
naftalibeder Apr 12, 2022
015ed22
Clarify comment
naftalibeder Apr 12, 2022
f856b46
Add camera animation example
naftalibeder Apr 12, 2022
cccab0a
Adjust import order
naftalibeder Apr 12, 2022
fdabbac
Refactor to enable animating padding and simplify camera creation (iOS)
naftalibeder Apr 13, 2022
ce4c042
Add rand animated padding, fix rand coordinates
naftalibeder Apr 13, 2022
db5832f
Merge branch 'main' of github.com:rnmapbox/maps into feat/map
naftalibeder Apr 15, 2022
dc81d44
Add padding options, improve display
naftalibeder Apr 15, 2022
724be19
Adjust padding
naftalibeder Apr 15, 2022
a647b71
Store camera animator reference to allow stopping existing animation
naftalibeder Apr 15, 2022
4670f4d
Move padding out of camera to prevent wonky movement
naftalibeder Apr 15, 2022
9a7ad42
Add missing text
naftalibeder Apr 15, 2022
984774e
Pair camera and padding easing, refactor and clarify easing modes
naftalibeder Apr 18, 2022
e925728
Make position and padding update together, start with instant transition
naftalibeder Apr 18, 2022
00c4107
Merge branch 'main' of github.com:rnmapbox/maps into feat/map
naftalibeder Apr 18, 2022
3e857a4
Default to nil values for camera, use nil anchor instead of .zero
naftalibeder Apr 18, 2022
9f09cda
Set shorter duration
naftalibeder Apr 18, 2022
e6592a7
Simplify CameraUpdateItem interface, handle padding anim fix inside m…
naftalibeder Apr 18, 2022
6f0b594
Use low-level animator for padding only in fly(to:)
naftalibeder Apr 18, 2022
5836bd6
Add option to change only padding
naftalibeder Apr 18, 2022
058f440
Merge branch 'main' of github.com:rnmapbox/maps into feat/map
naftalibeder Apr 19, 2022
b8193b7
Re-enable 'moveTo' animation mode for backward compatibility
naftalibeder Apr 19, 2022
c18bab9
Merge branch 'main' of github.com:rnmapbox/maps into feat/map
naftalibeder Apr 20, 2022
206fd45
Improve returned map state object, reorganize classes and add extensi…
naftalibeder Apr 20, 2022
369d3c4
Point to new map state type
naftalibeder Apr 20, 2022
3f8b890
Declare v10-only map state type
naftalibeder Apr 20, 2022
7c95092
Use updated map state shape to display center and bounds
naftalibeder Apr 20, 2022
57d234b
Rename
naftalibeder Apr 20, 2022
af78115
Edit syntax
naftalibeder Apr 20, 2022
b03da22
Edit title
naftalibeder Apr 20, 2022
5eb3676
Fix callback types
naftalibeder Apr 20, 2022
38fc84a
Ensure all camera props are passed on any change
naftalibeder Apr 22, 2022
e00bb88
Allow setting bounds or center, add padding options
naftalibeder Apr 22, 2022
5d69cf7
Add turf/bbox
naftalibeder Apr 22, 2022
4392295
Refactor move method for clarity and to move bounds around more
naftalibeder Apr 22, 2022
33ee694
Merge branch 'main' of github.com:rnmapbox/maps into feat/map
naftalibeder Apr 22, 2022
ecd04e1
Implement min and max zoom level handling, organize RN-derived props
naftalibeder Apr 22, 2022
acf6039
Add zoom level constraints
naftalibeder Apr 22, 2022
2167f09
Merge branch 'main' of github.com:rnmapbox/maps into feat/map
naftalibeder Apr 22, 2022
55c4af0
Fix camera tests
naftalibeder Apr 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions __tests__/components/Camera.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,7 @@ describe('Camera', () => {
defaultSettings: {
centerCoordinate: [-111.8678, 40.2866],
zoomLevel: 16,
animationMode: 'moveTo',
},
};

Expand All @@ -950,7 +951,7 @@ describe('Camera', () => {
'{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[-111.8678,40.2866]}}',
duration: 0,
heading: undefined,
mode: 'None',
mode: 'Move',
pitch: undefined,
zoom: 16,
paddingBottom: 0,
Expand All @@ -969,6 +970,7 @@ describe('Camera', () => {
const camera = new Camera();
const configWithoutBounds = {
animationDuration: 2000,
animationMode: 'easeTo',
pitch: 45,
heading: 110,
zoomLevel: 9,
Expand Down Expand Up @@ -1098,11 +1100,11 @@ describe('Camera', () => {
test('returns "None" for "moveTo"', () => {
expect(
camera._getNativeCameraMode({animationMode: 'moveTo'}),
).toStrictEqual('None');
).toStrictEqual('Move');
});

test('returns "Ease" as default', () => {
expect(camera._getNativeCameraMode({})).toStrictEqual('Ease');
test('returns "Move" as default (TODO: This should eventually fall back to None)', () => {
expect(camera._getNativeCameraMode({})).toStrictEqual('Move');
});
});

Expand Down
6 changes: 3 additions & 3 deletions docs/Camera.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
| ---- | :--: | :-----: | :------: | :----------: |
| allowUpdates | `bool` | `true` | `false` | If false, the camera will not send any props to the native module. Intended to be used to prevent unnecessary tile fetching and improve performance when the map is not visible. Defaults to true. |
| animationDuration | `number` | `2000` | `false` | The duration a camera update takes (in ms) |
| animationMode | `enum` | `'easeTo'` | `false` | The animationstyle when the camara updates. One of: `flyTo`, `easeTo`, `linearTo`, `moveTo` |
| animationMode | `enum` | `'easeTo'` | `false` | The animation style when the camara updates. One of:<br/>`flyTo`: A complex flight animation, affecting both position and zoom.<br/>`easeTo`: A standard damped curve.<br/>`linearTo`: An even linear transition.<br/>`none`: An instantaneous change (v10 only).<br/>`moveTo`: An instantaneous change (<v10). |
| defaultSettings | `shape` | `none` | `false` | Default view settings applied on camera |
| &nbsp;&nbsp;centerCoordinate | `array` | `none` | `false` | Center coordinate on map [lng, lat] |
| &nbsp;&nbsp;padding | `shape` | `none` | `false` | Padding around edges of map in points |
Expand Down Expand Up @@ -43,8 +43,8 @@
| &nbsp;&nbsp;paddingBottom | `number` | `none` | `false` | Bottom padding in points (deprecated; use root `padding` property instead) |
| onUserTrackingModeChange | `func` | `none` | `false` | Callback that is triggered on user tracking mode changes |
| zoomLevel | `number` | `none` | `false` | Zoom level of the map |
| minZoomLevel | `number` | `none` | `false` | The minimun zoom level of the map |
| maxZoomLevel | `number` | `none` | `false` | The maximun zoom level of the map |
| minZoomLevel | `number` | `none` | `false` | The minimum zoom level of the map |
| maxZoomLevel | `number` | `none` | `false` | The maximum zoom level of the map |
| maxBounds | `shape` | `none` | `false` | Restrict map panning so that the center is within these bounds |
| &nbsp;&nbsp;ne | `array` | `none` | `true` | northEastCoordinates - North east coordinate of bound |
| &nbsp;&nbsp;sw | `array` | `none` | `true` | southWestCoordinates - South west coordinate of bound |
Expand Down
10 changes: 5 additions & 5 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@
"required": false,
"type": "enum",
"default": "'easeTo'",
"description": "The animationstyle when the camara updates. One of: `flyTo`, `easeTo`, `linearTo`, `moveTo`"
"description": "The animation style when the camara updates. One of:\n`flyTo`: A complex flight animation, affecting both position and zoom.\n`easeTo`: A standard damped curve.\n`linearTo`: An even linear transition.\n`none`: An instantaneous change (v10 only).\n`moveTo`: An instantaneous change (<v10)."
},
{
"name": "defaultSettings",
Expand Down Expand Up @@ -723,14 +723,14 @@
"required": false,
"type": "number",
"default": "none",
"description": "The minimun zoom level of the map"
"description": "The minimum zoom level of the map"
},
{
"name": "maxZoomLevel",
"required": false,
"type": "number",
"default": "none",
"description": "The maximun zoom level of the map"
"description": "The maximum zoom level of the map"
},
{
"name": "maxBounds",
Expand Down Expand Up @@ -3108,7 +3108,7 @@
"name": "region",
"description": "A payload containing the map center, bounds, and other properties.",
"type": {
"name": "RegionPayload"
"name": "MapState"
},
"optional": false
}
Expand All @@ -3125,7 +3125,7 @@
"name": "region",
"description": "A payload containing the map center, bounds, and other properties.",
"type": {
"name": "RegionPayload"
"name": "MapState"
},
"optional": false
}
Expand Down
1 change: 1 addition & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@mapbox/mapbox-sdk": "^0.13.0",
"@react-native-community/masked-view": "^0.1.7",
"@turf/along": "^6.5.0",
"@turf/bbox": "^6.5.0",
"@turf/bbox-polygon": "^6.5.0",
"@turf/distance": "^6.5.0",
"@turf/helpers": "^6.5.0",
Expand Down
224 changes: 194 additions & 30 deletions example/src/examples/V10/CameraAnimation.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, {useState} from 'react';
import {Button, SafeAreaView, StyleSheet} from 'react-native';
import React, {useMemo, useState} from 'react';
import {Button, SafeAreaView, View} from 'react-native';
import {
MapView,
Camera,
ShapeSource,
CircleLayer,
Logger,
} from '@rnmapbox/maps';
import bbox from '@turf/bbox';
import {Text, Divider} from 'react-native-elements';

import Page from '../common/Page';
import colors from '../../styles/colors';
Expand All @@ -21,61 +23,223 @@ const styles = {
circleRadius: 6,
circleColor: colors.primary.blue,
},
sheet: {
paddingTop: 10,
paddingHorizontal: 10,
},
content: {
padding: 10,
},
buttonRow: {
flex: 0,
flexDirection: 'row',
justifyContent: 'space-between',
justifyContent: 'space-around',
},
divider: {
marginVertical: 10,
},
fadedText: {
color: 'gray',
},
};

const zeroPadding = {
paddingTop: 0,
paddingBottom: 0,
paddingLeft: 0,
paddingRight: 0,
};
const evenPadding = {
paddingTop: 40,
paddingBottom: 40,
paddingLeft: 40,
paddingRight: 40,
};
const minZoomLevel = 8;
const maxZoomLevel = 16;

const randPadding = () => {
const randNum = () => {
const items = [0, 150, 300];
return items[Math.floor(Math.random() * items.length)];
};

return {
paddingTop: randNum(),
paddingBottom: randNum(),
paddingLeft: randNum(),
paddingRight: randNum(),
};
};

const toPosition = coordinate => {
return [coordinate.longitude, coordinate.latitude];
};

const CameraAnimation = props => {
const initialCoordinates = {
const initialCoordinate = {
latitude: 40.759211,
longitude: -73.984638,
};

const [animationMode, setAnimationMode] = useState('flyTo');
const [coordinates, setCoordinates] = useState(initialCoordinates);
const [animationMode, setAnimationMode] = useState('moveTo');
const [coordinates, setCoordinates] = useState([initialCoordinate]);
const [padding, setPadding] = useState(zeroPadding);

const onPress = _animationMode => {
const paddingDisplay = useMemo(() => {
return `L ${padding.paddingLeft} | R ${padding.paddingRight} | T ${padding.paddingTop} | B ${padding.paddingBottom}`;
}, [padding]);

const move = (_animationMode, shouldCreateMultiple) => {
setAnimationMode(_animationMode);

const offset = Math.random() * 0.2;
setCoordinates({
latitude: initialCoordinates.latitude + offset,
longitude: initialCoordinates.longitude + offset,
});
if (shouldCreateMultiple) {
const _centerCoordinate = {
latitude: initialCoordinate.latitude + Math.random() * 0.2,
longitude: initialCoordinate.longitude + Math.random() * 0.2,
};
const _coordinates = Array(10)
.fill(0)
.map(_ => {
return {
latitude: _centerCoordinate.latitude + Math.random() * 0.2,
longitude: _centerCoordinate.longitude + Math.random() * 0.2,
};
});
setCoordinates(_coordinates);
} else {
setCoordinates([
{
latitude: initialCoordinate.latitude + Math.random() * 0.2,
longitude: initialCoordinate.longitude + Math.random() * 0.2,
},
]);
}
};

const position = [coordinates.longitude, coordinates.latitude];
const features = useMemo(() => {
return coordinates.map(p => {
return {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: toPosition(p),
},
};
});
}, [coordinates]);

const shape = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: position,
},
};
const centerOrBounds = useMemo(() => {
if (coordinates.length === 1) {
return {
centerCoordinate: toPosition(coordinates[0]),
};
} else {
const positions = coordinates.map(toPosition);
const lineString = {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: positions,
},
};
const _bbox = bbox(lineString);
return {
bounds: {
ne: [_bbox[0], _bbox[1]],
sw: [_bbox[2], _bbox[3]],
},
};
}
}, [coordinates]);

const locationDisplay = useMemo(() => {
if (coordinates.length > 1) {
const ne = centerOrBounds.bounds?.ne.map(n => n.toFixed(3));
const sw = centerOrBounds.bounds?.sw.map(n => n.toFixed(3));
return `ne ${ne} | sw ${sw}`;
} else if (coordinates.length === 1) {
const lon = coordinates[0].longitude.toFixed(4);
const lat = coordinates[0].latitude.toFixed(4);
return `lon ${lon} | lat ${lat}`;
}
}, [coordinates, centerOrBounds]);

return (
<Page {...props}>
<MapView style={styles.map}>
<Camera
centerCoordinate={position}
animationMode={animationMode}
{...centerOrBounds}
zoomLevel={12}
minZoomLevel={minZoomLevel}
maxZoomLevel={maxZoomLevel}
padding={padding}
animationDuration={800}
animationMode={animationMode}
/>

<ShapeSource id="source" shape={shape}>
<CircleLayer id="layer" style={styles.circle} />
</ShapeSource>
{features.map(f => {
const id = JSON.stringify(f.geometry.coordinates);
return (
<ShapeSource key={id} id={`source-${id}`} shape={f}>
<CircleLayer id={`layer-${id}`} style={styles.circle} />
</ShapeSource>
);
})}
</MapView>

<SafeAreaView style={styles.buttonRow}>
<Button title="Flight" onPress={() => onPress('flyTo')} />
<Button title="Move" onPress={() => onPress('moveTo')} />
<Button title="Ease" onPress={() => onPress('easeTo')} />
<Button title="Linear" onPress={() => onPress('linearTo')} />
<SafeAreaView>
<View style={styles.sheet}>
<View style={styles.content}>
<Text style={styles.fadedText}>centerCoordinate</Text>
<View style={styles.buttonRow}>
<Button title="Flight" onPress={() => move('flyTo', false)} />
<Button title="Ease" onPress={() => move('easeTo', false)} />
<Button title="Linear" onPress={() => move('linearTo', false)} />
<Button title="Instant" onPress={() => move('moveTo', false)} />
</View>

<Divider style={styles.divider} />

<Text style={styles.fadedText}>bounds</Text>
<View style={styles.buttonRow}>
<Button title="Flight" onPress={() => move('flyTo', true)} />
<Button title="Ease" onPress={() => move('easeTo', true)} />
<Button title="Linear" onPress={() => move('linearTo', true)} />
<Button title="Instant" onPress={() => move('moveTo', true)} />
</View>

<Divider style={styles.divider} />

<Text style={styles.fadedText}>padding</Text>
<View style={styles.buttonRow}>
<Button
title="Zero"
onPress={() => {
setPadding(zeroPadding);
}}
/>
<Button
title="Even"
onPress={() => {
setPadding(evenPadding);
}}
/>
<Button
title="Random"
onPress={() => {
setPadding(randPadding());
}}
/>
</View>

<Divider style={styles.divider} />

<Text style={styles.fadedText}>info</Text>
<Text>position: {locationDisplay}</Text>
<Text>padding: {paddingDisplay}</Text>
</View>
</View>
</SafeAreaView>
</Page>
);
Expand Down
Loading