Skip to content

Commit

Permalink
Fix double padding on camera when setting bounds (#3354)
Browse files Browse the repository at this point in the history
* Zero padding when it's already baked into center/zoom from bounds

* Apply bounds camera directly to prevent padding being applied twice

* Update example to allow granular control over padding

* Scale down padding if it surpasses map size

* Remove instant set on zero duration to allow dynamic duration in flyTo

* Enable setting min and max zoom limits

* Allow removing map zoom limits and avoid unneeded zoom comparisons

* Remove unused import
  • Loading branch information
naftalibeder authored Feb 13, 2024
1 parent e49a937 commit 24edfeb
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 178 deletions.
289 changes: 165 additions & 124 deletions example/src/examples/V10/CameraAnimation.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Divider, Text } from '@rneui/base';
import { CheckBox, Divider, Slider, Text } from '@rneui/base';
import {
Camera,
CameraAnimationMode,
CameraBounds,
CameraPadding,
CircleLayer,
Logger,
MapView,
Expand Down Expand Up @@ -36,80 +35,48 @@ const initialCoordinate: Coordinate = {
longitude: -73.984638,
};

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

const randPadding = (): CameraPadding => {
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: Coordinate): Position => {
return [coordinate.longitude, coordinate.latitude];
};

const rand = () => Math.random() * 0.008;

const CameraAnimation = () => {
const [animationMode, setAnimationMode] =
useState<CameraAnimationMode>('moveTo');
const [easing, setEasing] = useState<CameraAnimationMode>('easeTo');
const [coordinates, setCoordinates] = useState<Coordinate[]>([
initialCoordinate,
]);
const [padding, setPadding] = useState<CameraPadding>(zeroPadding);

const paddingDisplay = useMemo(() => {
return `L ${padding.paddingLeft} | R ${padding.paddingRight} | T ${padding.paddingTop} | B ${padding.paddingBottom}`;
}, [padding]);
const [paddingLeft, setPaddingLeft] = useState(0);
const [paddingRight, setPaddingRight] = useState(0);
const [paddingTop, setPaddingTop] = useState(0);
const [paddingBottom, setPaddingBottom] = useState(0);
const [minZoom, setMinZoom] = useState<number | undefined>(undefined);
const [maxZoom, setMaxZoom] = useState<number | undefined>(undefined);

const move = useCallback(
(_animationMode: CameraAnimationMode, shouldCreateMultiple: boolean) => {
setAnimationMode(_animationMode);

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 move = useCallback((kind: 'center' | 'bounds') => {
if (kind === 'bounds') {
const _centerCoordinate = {
latitude: initialCoordinate.latitude + rand(),
longitude: initialCoordinate.longitude + rand(),
};
const _coordinates = Array(10)
.fill(0)
.map((_) => {
return {
latitude: _centerCoordinate.latitude + rand(),
longitude: _centerCoordinate.longitude + rand(),
};
});
setCoordinates(_coordinates);
} else if (kind === 'center') {
setCoordinates([
{
latitude: initialCoordinate.latitude + rand(),
longitude: initialCoordinate.longitude + rand(),
},
]);
}
}, []);

const features = useMemo((): Feature<Point>[] => {
return coordinates.map((p) => {
Expand Down Expand Up @@ -152,31 +119,118 @@ const CameraAnimation = () => {
}
}, [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}`;
} else {
throw new Error('invalid location passed');
}
}, [coordinates, centerOrBounds]);
const easingCheckBox = useCallback(
(value: CameraAnimationMode, label: string) => {
const isChecked = value === easing;
return (
<View
style={{
flex: 0,
flexDirection: 'row',
alignItems: 'center',
}}
>
<CheckBox
checked={isChecked}
center={true}
onIconPress={() => setEasing(value)}
containerStyle={{
backgroundColor: 'transparent',
marginRight: -4,
}}
/>
<Text
style={{
flex: 0,
color: isChecked ? colors.primary.blue : undefined,
}}
>
{label}
</Text>
</View>
);
},
[easing],
);

const paddingCounter = useCallback(
(value: number, setValue: (value: number) => void, label: string) => {
return (
<View style={{ flex: 1, paddingHorizontal: 10 }}>
<View style={{ flex: 0, alignItems: 'center' }}>
<Text>{label}</Text>
<Text style={{ fontWeight: 'bold' }}>{`${Math.round(value)}`}</Text>
</View>
<Slider
thumbStyle={{
backgroundColor: 'black',
width: 15,
height: 15,
}}
value={value}
minimumValue={0}
maximumValue={500}
onSlidingComplete={(_value) => setValue(_value)}
/>
</View>
);
},
[],
);

const zoomLimitCounter = useCallback(
(
value: number | undefined,
setValue: (value?: number) => void,
label: string,
) => {
return (
<View style={{ flex: 1, paddingHorizontal: 10 }}>
<View style={{ flex: 0, alignItems: 'center' }}>
<Text>{label}</Text>
<Text style={{ fontWeight: 'bold' }}>{`${
value ?? 'Not set'
}`}</Text>
</View>
<Slider
thumbStyle={{
backgroundColor: 'black',
width: 15,
height: 15,
}}
value={value}
minimumValue={-1}
maximumValue={20}
onSlidingComplete={(_value) => {
if (_value < 0) {
setValue(undefined);
} else {
setValue(Math.round(_value));
}
}}
/>
</View>
);
},
[],
);

return (
<>
<MapView style={styles.map}>
<Camera
{...centerOrBounds}
zoomLevel={12}
minZoomLevel={minZoomLevel}
maxZoomLevel={maxZoomLevel}
padding={padding}
minZoomLevel={minZoom}
maxZoomLevel={maxZoom}
padding={{
paddingTop,
paddingBottom,
paddingLeft,
paddingRight,
}}
animationDuration={800}
animationMode={animationMode}
animationMode={easing}
/>

{features.map((feature) => {
Expand All @@ -192,53 +246,39 @@ const CameraAnimation = () => {
<SafeAreaView>
<View style={styles.sheet}>
<View style={styles.content}>
<Text style={styles.fadedText}>centerCoordinate</Text>
<Text style={styles.sectionText}>Coordinate</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)} />
<Button title="Center" onPress={() => move('center')} />
<Button title="Bounds" onPress={() => move('bounds')} />
</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)} />
<Text style={styles.sectionText}>Easing</Text>
<View style={[styles.buttonRow, { marginBottom: -6 }]}>
{easingCheckBox('easeTo', 'Ease')}
{easingCheckBox('linearTo', 'Linear')}
{easingCheckBox('flyTo', 'Fly')}
{easingCheckBox('moveTo', 'Move')}
</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());
}}
/>
<Text style={styles.sectionText}>Padding</Text>
<View style={[styles.buttonRow, { marginTop: 6 }]}>
{paddingCounter(paddingTop, setPaddingTop, 'Top')}
{paddingCounter(paddingBottom, setPaddingBottom, 'Bottom')}
{paddingCounter(paddingLeft, setPaddingLeft, 'Left')}
{paddingCounter(paddingRight, setPaddingRight, 'Right')}
</View>

<Divider style={styles.divider} />

<Text style={styles.fadedText}>info</Text>
<Text>position: {locationDisplay}</Text>
<Text>padding: {paddingDisplay}</Text>
<Text style={styles.sectionText}>Zoom limits</Text>
<View style={[styles.buttonRow, { marginTop: 6 }]}>
{zoomLimitCounter(minZoom, setMinZoom, 'Min')}
{zoomLimitCounter(maxZoom, setMaxZoom, 'Max')}
</View>
</View>
</View>
</SafeAreaView>
Expand All @@ -251,22 +291,23 @@ const styles = StyleSheet.create({
flex: 1,
},
sheet: {
paddingTop: 10,
paddingHorizontal: 10,
marginBottom: -10,
},
content: {
padding: 10,
},
sectionText: {
fontSize: 10,
color: 'gray',
},
buttonRow: {
flex: 0,
flexDirection: 'row',
justifyContent: 'space-around',
justifyContent: 'space-evenly',
},
divider: {
marginVertical: 10,
},
fadedText: {
color: 'gray',
marginVertical: 8,
},
});

Expand Down
Loading

0 comments on commit 24edfeb

Please sign in to comment.