Search code examples
reactjsreact-nativestyles

React native compass angle issues


I have a compass that I created in react native. It has a couple of issues and I don't know how to best approach them.

Firstly, the arrow in the middle is hardcoded with top and left which seems to make it not responsive. I tried setting parent to relative and arrow to absolute.

Secondly, the angle doesn't stay consistent to the heading. Realistically, heading will fluctuate as the user moves the phone. Going anywhere from 0 to 360 in value.

The issue is that the angle needs to follow the heading. At 0... they seem to be fine but if the heading is changed to 200... the angle should be facing towards 0 not 200.

I tried to change the transform for arrow to this:

270 - heading - angle

But seems to be off sometimes, regardless

How can i go about solving this?

export const App: React.FC = () => {
  const [angle, setAngle] = useState(0);
  const [heading, setHeading] = useState(200);

  const updateHeading = (headingObject) => {
    const roundedHeading = Math.round(headingObject.trueHeading);
    setHeading(roundedHeading);
  };

  return (
    <View style={{ backgroundColor: 'gray' }}>
      <View style={styles.row}>
        <Text style={styles.angle}>Angle is: {angle}°</Text>
        <View style={styles.arrowContainer}>
          <Image
            source={require('./assets/arrow.png')}
            style={{
              width: width / 10,
              height: width / 10,
              left: '47%',
              top: 160,
              resizeMode: 'contain',
              transform: [{ rotate: angle + 'deg' }],
            }}
          />
        </View>
      </View>
      <View
        style={styles.compassWrapper}>
        <Image
          source={require('./assets/compass_bg.png')}
          style={{
            height: width - 80,
            width: width,
            resizeMode: 'contain',
            transform: [{ rotate: 270 - heading + 'deg' }],
          }}
        />
      </View>
    </View>
  );
};

export default App;

Demo:

https://snack.expo.dev/H-1WiF7It


Solution

  • Your math is close. The arrow.png image is oriented already pointing "north", or up, but the compass_bg.png image is oriented with "north" pointing "east", or +90°. This image needs either -90° or +270° applied as a starting point and then the heading value added to it same as the arrow.

    transform: [{ rotate: -90 + heading + 'deg' }],
    

    Next is the issue of arrow positioning:

    export const App: React.FC = () => {
      const [angle, setAngle] = useState(75);
      const [heading, setHeading] = useState(200);
    
      const updateHeading = (headingObject) => {
        const roundedHeading = Math.round(headingObject.trueHeading);
        setHeading(roundedHeading);
      };
    
      return (
        <View style={{ backgroundColor: 'gray' }}>
          {
            Number.isInteger(angle) ? (
              <View style={styles.row}>
                <Text style={styles.angle}>Angle is: {angle}°</Text>
                <View style={styles.arrowContainer}>
                  <Image
                    source={require('./assets/arrow.png')}
                    style={{
                      position: 'absolute',
                      width: width / 10,
                      height: width / 10,
                      left: '50%', // <-- center left/right
                      top: 160,
                      resizeMode: 'contain',
                      transform: [
                        { translateX: '-50%'}, // <-- center image horizontally
                        { translateY: '50%' }, // <-- center image vertically
                        { rotate: angle + 'deg' }
                      ],
                    }}
                  />
                </View>
              </View>
            ) : ( null )
          }
          <View
            style={styles.compassWrapper}>
            <Image
              source={require('./assets/compass_bg.png')}
              style={{
                height: width - 80,
                width: width,
                resizeMode: 'contain',
                transform: [{ rotate: -90 + heading + 'deg' }],
              }}
            />
          </View>
        </View>
      );
    }
    

    We can improve this however by co-locating the arrow and compass images together.

    Example:

    export const App: React.FC = () => {
      const [angle, setAngle] = useState(75);
      const [heading, setHeading] = useState(200);
    
      const updateHeading = (headingObject) => {
        const roundedHeading = Math.round(headingObject.trueHeading);
        setHeading(roundedHeading);
      };
    
      return (
        <View style={{ backgroundColor: 'gray' }}>
          {Number.isInteger(angle) && (
            <View style={styles.row}>
              <Text style={styles.angle}>Angle is: {angle}°</Text>
              <View style={styles.compassWrapper}>
                <Image
                  source={require('./assets/arrow.png')}
                  style={[styles.arrow, { transform: [{ rotate: angle + 'deg' }] }]}
                />
                <Image
                  source={require('./assets/compass_bg.png')}
                  style={[
                    styles.compass_bg,
                    { transform: [{ rotate: -90 + heading + 'deg' }] },
                  ]}
                />
              </View>
            </View>
          )}
        </View>
      );
    };
    
    export default App;
    
    const styles = StyleSheet.create({
      row: {
        alignItems: 'center',
      },
      compassWrapper: {
        alignItems: 'center',
        justifyContent: 'center',
        position: 'relative',
      },
      angle: {
        color: '#fff',
        fontSize: height / 25,
        marginBottom: 30,
      },
      arrow: {
        position: 'absolute',
        width: 35,
        height: 35,
        resizeMode: 'contain',
      },
      compass_bg: {
        height: width - 80,
        width: width,
        resizeMode: 'contain',
      },
    });
    

    https://snack.expo.dev/@drew.w.reese/react-native-compass-angle-issues

    Angle Heading Result
    enter image description here
    200° 200° enter image description here
    75° 200° enter image description here