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

[Bug]: CircleLayer position bug when zoom changes #2934

Closed
ovsiannykov opened this issue Jun 26, 2023 · 2 comments
Closed

[Bug]: CircleLayer position bug when zoom changes #2934

ovsiannykov opened this issue Jun 26, 2023 · 2 comments

Comments

@ovsiannykov
Copy link

Mapbox Implementation

Mapbox

Mapbox Version

10.0.7

Platform

iOS

@rnmapbox/maps version

^10.0.7-rc.0

Standalone component to reproduce

In ios when change zoom, CircleLayers position crash (look video)

Screen_Recording.mp4

My Marker Component:

import { default as Mapbox } from "@rnmapbox/maps";
import React, { memo, useCallback } from "react";

import { eventTypes } from "@constants/eventTypes";
import eventMarkerIconHelper from "@helpers/eventMarkerIconHelper";
import styles from "./MapEventMarker.styles";

const MapEventMarker = memo(
	({ shapeSource, eventMarkersData, setSelectedMarker, goToCords }) => {
		const choiseMarkerHandler = useCallback(
			async (marker) => {
				if (marker.features[0].properties.cluster) {
					const collection = await shapeSource.current.getClusterLeaves(
						marker.features[0],
						marker.features[0].properties.point_count,
						0
					);

					await setSelectedMarker({
						coords: marker.features[0].geometry.coordinates,
						events: collection.features
					});
					goToCords([
						marker.coordinates.longitude,
						marker.coordinates.latitude
					]);
				} else {
					await setSelectedMarker({
						coords: marker.features[0].geometry.coordinates,
						events: [marker.features[0]]
					});
					goToCords([
						marker.coordinates.longitude,
						marker.coordinates.latitude
					]);
				}
			},
			[goToCords, setSelectedMarker, shapeSource]
		);

		return (
			<Mapbox.ShapeSource
				ref={shapeSource}
				id="events"
				cluster={true}
				clusterMaxZoom={18}
				clusterRadius={60}
				shape={eventMarkersData}
				onPress={choiseMarkerHandler}
			>
				<Mapbox.Images>
					<Mapbox.Image name={eventMarkerIconHelper().iconName} sdf>
						{eventMarkerIconHelper("").icon}
					</Mapbox.Image>

					{eventTypes.map((type) => (
						<Mapbox.Image
							key={type.id}
							name={eventMarkerIconHelper(type.value).iconName}
							sdf
						>
							{eventMarkerIconHelper(type.value).icon}
						</Mapbox.Image>
					))}
				</Mapbox.Images>

				{/*Clustered marker icon*/}
				<Mapbox.SymbolLayer
					id="clusteredMarkersIcon"
					filter={["has", "point_count"]}
					style={styles.clusteredMarkersIcon}
				/>
				{/*Clustered marker icon circle*/}
				<Mapbox.CircleLayer
					id="clusteredMarkerIconCircle"
					belowLayerID="clusteredMarkersIcon"
					filter={["has", "point_count"]}
					style={styles.clusteredMarkersIconCircle}
				/>
				{/*Cluster marker count*/}
				<Mapbox.SymbolLayer
					id="clusteredMarkerCount"
					style={styles.markerCount}
					filter={["has", "point_count"]}
				/>
				{/*Cluster marker count circle*/}
				<Mapbox.CircleLayer
					id="clusteredMarkersCircle"
					belowLayerID="clusteredMarkerCount"
					filter={["has", "point_count"]}
					style={styles.markerCountCircle}
				/>

				{/*Single marker icon*/}
				<Mapbox.SymbolLayer
					id="singleMarkerIcon"
					filter={["!", ["has", "point_count"]]}
					style={styles.singleMarkerIcon}
				/>
				{/*Single marker icon circle*/}
				<Mapbox.CircleLayer
					id="singleMarkerIconCircle"
					belowLayerID="singleMarkerIcon"
					filter={["!", ["has", "point_count"]]}
					style={styles.singleMarkerIconCircle}
				/>
			</Mapbox.ShapeSource>
		);
	}
);

export default MapEventMarker;

My Marker Component Styles:

import { StyleSheet } from "react-native";

import { COLORS, IS_ANDROID } from "@constants/theme";

export default StyleSheet.create({
	clusteredMarkersIcon: {
		iconImage: "defaultMarkerIcon",
		iconOpacity: 1,
		iconColor: COLORS.text_light,
		iconAllowOverlap: true,
		iconIgnorePlacement: false
	},
	clusteredMarkersIconCircle: {
		circlePitchAlignment: "map",
		circleRadius: 26,
		circleStrokeWidth: 1.4,
		circleStrokeColor: COLORS.text_primary,
		circleColor: COLORS.neutral_3,
		circleOpacity: 1
	},
	markerCount: {
		textField: "{point_count}",
		textSize: 12,
		textPitchAlignment: "map",
		textOffset: [2, -1.8],
		textColor: COLORS.text_light,
		textAllowOverlap: true
	},
	markerCountCircle: {
		circlePitchAlignment: "map",
		circleColor: COLORS.primary_3,
		circleRadius: 10,
		circleStrokeWidth: 1,
		circleStrokeColor: COLORS.black,
		circleTranslate: IS_ANDROID ? [24, -22] : [24, -22]
		//circleTranslate: [-72, -90]
	},
	singleMarkerIcon: {
		iconImage: ["get", "eventTypeIconName"],
		iconOpacity: 1,
		iconColor: COLORS.text_light,
		iconAllowOverlap: true,
		iconIgnorePlacement: false
	},
	singleMarkerIconCircle: {
		circlePitchAlignment: "map",
		circleRadius: 26,
		circleStrokeWidth: 1.4,
		circleStrokeColor: COLORS.primary_3,
		circleColor: COLORS.neutral_3,
		circleOpacity: 1
	}
});

My map screen:

import {
	Camera,
	default as Mapbox,
	MarkerView,
	UserLocation
} from "@rnmapbox/maps";
import React, {
	useCallback,
	useEffect,
	useMemo,
	useRef,
	useState
} from "react";
import { Alert, View } from "react-native";
import { useDispatch, useSelector } from "react-redux";

import AddButton from "@components/pages/map/AddButton/AddButton";
import GeoButton from "@components/pages/map/GeoButton/GeoButton";
import MapEventMarker from "@components/pages/map/MapEventMarker/MapEventMarker";
import MapEventPreview from "@components/pages/map/MapEventPreview/MapEventPreview";
import { MAPBOX_PUBLIC_TOKEN } from "@constants/env";
import { MAPBOX_STYLE_DARK_V11 } from "@constants/theme";
import eventMarkerIconHelper from "@helpers/eventMarkerIconHelper";
import { getAllMapEvents } from "@services/events/mapEventsRequests";
import styles from "./MapScreen.styles";

Mapbox.setAccessToken(MAPBOX_PUBLIC_TOKEN);

const INITIAL_CORDS = {
	timestamp: 0,
	latitude: 40.73061,
	longitude: -73.935242,
	altitude: 0.0,
	heading: 0.0,
	accuracy: 0.0,
	speed: 0.0
};

function MapScreen({ navigation }) {
	const dispatch = useDispatch();
	const { userId, user } = useSelector((state) => state.auth);
	const { mapEvents } = useSelector((state) => state.events);
	const mapRef = useRef(null);
	const mapEventsRef = useRef(mapEvents);
	const camera = useRef(null);
	const shapeSource = useRef();
	const [userLocation, setUserLocation] = useState(INITIAL_CORDS);
	const [isFollowUserLocation, setIsFollowUserLocation] = useState(false);
	const [zoomLevel, setZoomLevel] = useState(9);
	const [selectedMarker, setSelectedMarker] = useState(null);

	const filteredMapEvents = useMemo(() => {
		if (selectedMarker !== null) {
			return mapEventsRef.current.filter((item) => {
				return !selectedMarker.events.some(
					(event) => event.properties.id === item._id
				);
			});
		} else {
			return mapEventsRef.current;
		}
	}, [selectedMarker]);

	useEffect(() => {
		if (!user.user && !user.city) {
			const timer = setTimeout(() => {
				navigation.navigate("PERSON_INFO_SCREEN");
			}, 5000);
			return () => clearTimeout(timer);
		}
	}, [navigation, user]);

	useEffect(() => {
		dispatch(getAllMapEvents({ userId }));
	}, [dispatch, userId]);

	useEffect(() => {
		setIsFollowUserLocation(true);
		const timer = setTimeout(() => setIsFollowUserLocation(false), 3000);
		return () => clearTimeout(timer);
	}, []);

	const onUserLocationUpdate = useCallback((location) => {
		setUserLocation({
			timestamp: location.timestamp,
			latitude: location.coords.latitude,
			longitude: location.coords.longitude,
			altitude: location.coords.altitude,
			heading: location.coords.heading,
			accuracy: location.coords.accuracy,
			speed: location.coords.speed
		});
	}, []);

	const eventMarkersData = {
		type: "FeatureCollection",
		totalMarkers: filteredMapEvents.length,
		features: filteredMapEvents.map((event) => ({
			type: "Feature",
			id: event._id,
			geometry: {
				type: "Point",
				coordinates: event.location.coordinates
			},
			properties: {
				id: event._id,
				eventType: event.eventType,
				eventTypeIconName: eventMarkerIconHelper(event.eventType).iconName
			}
		}))
	};

	const goToCords = useCallback((coords) => {
		camera.current.flyTo(coords);
	}, []);

	const mapLoadingErrorHandler = useCallback(() => {
		Alert.alert("Ooops", "Map loading error. Please try again!");
	}, []);

	const hideMarker = useCallback(() => {
		setSelectedMarker(null);
	}, []);

	return (
		<View style={styles.screen}>
			<View style={styles.container}>
				<AddButton />
				<GeoButton
					onPress={() =>
						goToCords([userLocation.longitude, userLocation.latitude])
					}
				/>
				<Mapbox.MapView
					ref={mapRef}
					styleURL={MAPBOX_STYLE_DARK_V11}
					style={styles.map}
					onPress={() => setSelectedMarker(null)}
					rotateEnabled={false}
					logoEnabled={false}
					scaleBarEnabled={false}
					allowOverlap={false}
					onMapLoadingError={mapLoadingErrorHandler}
				>
					<UserLocation
						renderMode="native"
						visible={true}
						androidRenderMode="normal"
						onUpdate={(newLocation) => onUserLocationUpdate(newLocation)}
					/>
					<Camera
						ref={camera}
						followZoomLevel={10}
						followUserLocation={isFollowUserLocation}
					/>

					{eventMarkersData && (
						<MapEventMarker
							shapeSource={shapeSource}
							eventMarkersData={eventMarkersData}
							setSelectedMarker={setSelectedMarker}
							goToCords={goToCords}
						/>
					)}
					{selectedMarker && selectedMarker.coords ? (
						<MarkerView
							anchor={{ x: 0.01, y: 0.01 }}
							id={selectedMarker.events[0].id}
							coordinate={selectedMarker.coords}
							selected
						>
							<MapEventPreview
								id={selectedMarker.events[0].id}
								events={selectedMarker.events}
								hideMarker={hideMarker}
								isSelected={true}
								allowOverlap
							/>
						</MarkerView>
					) : null}
				</Mapbox.MapView>
			</View>
		</View>
	);
}

export default MapScreen;

Observed behavior and steps to reproduce

No response

Expected behavior

No response

Notes / preliminary analysis

No response

Additional links and references

No response

@github-actions
Copy link

No code example found in issue body - More info

@mfazekas
Copy link
Contributor

See #2931
Likely an upstream issue.

Pls add a single component - More info

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants