Positioning practice of react native

Posted by ctimmer on Tue, 28 Dec 2021 09:22:57 +0100

Positioning practice of react native

What do we want when we talk about positioning?

  • WGS-84 longitude and latitude (i.e. gps longitude and latitude) or GCJ-02 longitude and latitude (Chinese coordinate offset standard, which is applicable to Gaode and Tencent maps) or BD-09 longitude and latitude (Baidu coordinate offset standard, which is used by Baidu maps), depending on your project needs
  • With longitude and latitude, we also want the geographic location information corresponding to the longitude and latitude. After all, it is difficult for us to see what it means from a string of numbers
  • Sometimes we need to get the distance between two longitudes and latitudes (in the same coordinate system)

For locating such a public module, we want a public module. We'd better call a function to get all the location related information we want. We can first write a result type definition to define our output:

export interface ILocationPosition {
    longitude: number,//gps accuracy
    latitude: number,//gps latitude
    address: String,//Detailed geographic location information
    currentTime: String,//current time 
    realErrorDistance?: number,//Actual error distance from another latitude and longitude
    gdLongitude?: number,//Gaude longitude
    gdLatitude?: number,//Gaode latitude
}
    
or

export interface ILocationPosition {
    longitude: number,//High precision
    latitude: number,//Gaode latitude
    address: String,//Detailed geographic location information
    currentTime: String,//current time 
    realErrorDistance?: number,//Actual error distance from another latitude and longitude
    gpsLongitude?: number,//Gaude longitude
    gpsLatitude?: number,//Gaode latitude
}

1, Get location permission

Before writing the location method, we need to obtain the location permission of the application:

1.1 for android

First, in / Android / APP / SRC / main / androidmanifest In the XML file, write the location permissions we need:

<!--For access GPS location-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <!--For network positioning-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--It is used to access the network. Network positioning requires Internet access-->
    <uses-permission android:name="android.permission.INTERNET" />

Then, android 6 After 0, dangerous permissions need to be dynamically obtained. We need to write the code for dynamically obtaining positioning permissions in the code, which can be obtained by using the permissionsindroid module. The general method for obtaining android dynamic permissions is attached:

export function getPositionInit() {
  
		// //If Gaode location is used, set Gaode key and the attribute requiring inverse geocoding; If you do not use Gaode positioning, comment out the following code
    // await init({
    //     ios: '[your Gaode ios key]',
    //     android: "[your Gaode android key] / / pass in AMAP_KEY
    // });
    // //android requires reverse geocoding
    // setNeedAddress(true);
    // //ios requires inverse geocoding
    // setLocatingWithReGeocode(true);
  
    if (Platform.OS === "android") {
        //Get gps position open
        return LocationServicesDialogBox.checkLocationServicesIsEnabled({
            message: "<h2>Turn on location services</h2>Open location service to obtain accurate positioning<br/>",
            ok: "To open",
            cancel: "cancel",
            enableHighAccuracy: true,
            showDialog: true,
            openLocationServices: true,
            preventOutSideTouch: false,
            preventBackClick: false,
            providerListener: true
        }).then(async function (success) {
            console.log("obtain gps success", success);

            const permissions = [
                PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
                PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION
            ];
            const granteds = await PermissionsAndroid.requestMultiple(permissions);
            console.log("granteds====", granteds);
            if (granteds['android.permission.ACCESS_FINE_LOCATION'] === 'granted' && granteds['android.permission.ACCESS_COARSE_LOCATION'] === 'granted') {
                return true;
            } else {
                Modal.alert("Please enable location permission", "Please turn on get mobile location service, otherwise some functions of the system will not be available", [
                    {
                        text: "open", onPress: () => {
                            console.log("Click the open button");
                            if (
                                granteds['android.permission.ACCESS_FINE_LOCATION'] === 'never_ask_again' && granteds['android.permission.ACCESS_COARSE_LOCATION'] === 'never_ask_again'
                            ) {
                                Alert.alert("warning", "You set the permission of the app to obtain mobile location as refuse and don't ask again. The function can't be used!" +
                                    "To re open the permission, please go to your mobile phone-set up-Permission allowed in permission management[Your app name]app Acquisition of this permission");
                                return false;
                            } else {
                                //The second time in a short time can wake up the request permission box again, but the option will change from reject to reject and no longer inquire. If this option is selected, the request permission box cannot be called up again
                                getPositionInit();
                            }
                        }
                    },
                    {
                        text: "Denial of authorization", onPress: () => {
                            return false;
                        }
                    }
                ])
            }
        })
    }
}


Determine whether the location permission is obtained by the final returned true or false.

1.2 for ios end

Need to be in info Plist specifies the specific reasons for applying for permission

2, Positioning method

After clarifying what we want, we have several practical and effective positioning methods:

2.1. Geolocation positioning module of react native

The Geolocation module was originally a positioning module included in the react native project. Later, in order to streamline the project architecture of rn, rn only retained the core architecture. Functions such as positioning and photographing were placed in the react native community. See the details of the Geolocation library https://github.com/react-native-geolocation/react-native-geolocation .

Use geolocation The getcurrentposition method can obtain the gps longitude and latitude of the current position. In order to obtain detailed geographic location information, we also need to borrow the reverse geocoding service of map service providers such as Gaode. Because Gaode's reverse geocoding service is used, we also need to convert the gps longitude and latitude into Gaode longitude and latitude.

gps to Gaode and reverse geocoding services are provided by Gaode. You can check them on Gaode open platform, and put the code directly below

const GaoDe_Key = '[Your high virtue web service key]';

function requestGetGeoPosition(setLocation?:Function,setAddress?:Function,targetCoordinate?:coordinate,setCurrentError?:Function,setGDLocation?:Function,isToGD?:Boolean){
    return new Promise((resolve, reject) => {
        Geolocation.getCurrentPosition(async (position) => {
            console.log('Native acquisition of latitude and longitude=======', position);
            if (setLocation && typeof setLocation === 'function'){
                console.log("set GPS Longitude and latitude")
                setLocation({
                    longitude: getBit(position.coords.longitude, 6),
                    latitude: getBit(position.coords.latitude, 6),
                })
            }

            let serverTime = '';
            serverTime = await getServerTime().catch((err) => {
                console.log('Get server time interface capture error:', err);
            });
            let time = dateFormat("YYYY-mm-dd HH:MM:SS", new Date());

            let result:IGeoPosition;
            result = {
                longitude: getBit(position.coords.longitude, 6),
                latitude: getBit(position.coords.latitude, 6),
                currentTime: serverTime ? serverTime : time,
            };

            if (isToGD){
                let data = await getGDLocation({
                    latitude: position.coords.latitude,
                    longitude: position.coords.longitude,
                });
                console.log('gps Convert to Gaud coordinate system===========',data);
                if (setGDLocation && typeof setGDLocation === 'function'){
                    setGDLocation(data);
                }
                result = {
                    ...result,
                    gdLongitude:data.longitude,
                    gdLatitude:data.latitude,
                };

                if (setAddress && typeof setAddress === 'function'){
                    let addressStr = '';
                    addressStr = await getAddressService(data);
                    console.log('address----------',addressStr);
                    setAddress(addressStr);
                    result = {
                        ...result,
                        address:addressStr
                    };
                }
            }


            if (targetCoordinate && setCurrentError && typeof setCurrentError === "function"){
                let distance = 0;
                distance = getDistance(targetCoordinate, {
                    longitude:position.coords.longitude,
                    latitude:position.coords.latitude,
                }, 1);
                console.log('Target latitude and longitude and current gps The contrast distance of longitude and latitude is',distance);
                result = {
                    ...result,
                    realErrorDistance: distance,
                }
            }
            //Final return data
            console.log('The final result of obtaining longitude and latitude is:',result);

            resolve(result);

        }, (error) => {
            console.log('Native get error', error);
        }, {
            enableHighAccuracy: false,
            timeout: 2000,
            maximumAge: 1000,
        });
    })

}

const getNetData = url => {
    return new Promise((resolve, reject) => {
        fetch(url)
            .then(response => {
                console.log("response",response)
                return response.json()
            })
            .then(responseData => {
                console.log("responseData",responseData)
                resolve(responseData);
            })
            .catch(error => {
                reject(error);
            })

    });
};

//Get city location information, inverse geocoding
const getAddressService = (locationObj) => {
    const GaoDe_URL = `https://restapi. amap. com/v3/geocode/regeo? Key = ${gaode_key} & radius = 1000 & extensions = all & poitype = business office building & location = `;
    return new Promise((resolve, reject) => {
        if (locationObj && locationObj !== {}) {
            let longitude = locationObj.longitude;
            let latitude = locationObj.latitude;
            //Gaode inverse geocoding interface
            const requestUrl = GaoDe_URL + longitude + ',' + latitude;
            console.log('request API', requestUrl);
            getNetData(requestUrl)
                .then(data => {
                    console.log("Gaode map:", data);
                    //Get data from Gaode map
                    if (data.status == 1) {
                        resolve(data.regeocode.formatted_address);
                    } else {
                        reject(data.code);
                    }
                })
                .catch(data => {
                    reject(data.code);
                });

        }
    });
};

//Convert gps coordinates to gaude coordinates
const getGDLocation = (gpsLocation) => {
    return new Promise((resolve, reject) => {
        let url = `https://restapi.amap.com/v3/assistant/coordinate/convert?key=${GaoDe_Key}&locations=${gpsLocation.longitude},${gpsLocation.latitude}&coordsys=gps`;
        getNetData(url)
            .then((data) => {
                if (data && data.locations) {
                    const gdLocation = {
                        latitude: parseFloat(data.locations.slice(data.locations.indexOf(",") + 1)),
                        longitude: parseFloat(data.locations.slice(0, data.locations.indexOf(",")))
                    };
                    resolve(gdLocation);
                }else{
                    console.error("gps An error is reported on the transfer to Gaode request interface");
                }
            })
            .catch((err)=>{
                reject(err);
            })
    })
};

2.2. Single positioning of gaude positioning module react native amap location

React native amap location is the rn Library of Gaode map location module. The address is https://github.com/qiuxiang/react-native-amap-geolocation , it can be combined with the author's High German map component library react-native-amap3d[https://github.com/qiuxiang/react-native-amap3d ]To use.

The single positioning mode of the library is introduced here:

function requestFetchGetList(SetLocation?, SetAddress?, targetCoordinate?: coordinate, SetCurrentError?: Function,SetGpsLocation?:Function,isToGps?:boolean) {

    //Set as single positioning
    // setOnceLocation(true);
    // //Set whether the first positioning waits for satellite positioning results
    // //It is only valid in the single positioning high-precision positioning mode. When it is set to true, it will wait for the satellite positioning result to return for up to 30 seconds,
    // //If no satellite positioning result is returned after 30 seconds, the network positioning result is returned. The time to wait for the satellite positioning results to return can be passed
    // //setGpsFirstTimeout.
    // setGpsFirst(true);
    // //Set the timeout (in milliseconds) for waiting for satellite positioning results when preferentially returning satellite positioning information
    // //Valid only when setGpsFirst(true).
    // setGpsFirstTimeout(3000);
    // //Set whether to use device sensors
    // setSensorEnable(true);
    // Only the equipment mode directly reports an error indoors and cannot be used. Therefore, high-precision mode is used. In this positioning mode, Gaode network positioning and satellite positioning will be used at the same time, and high-precision positioning will be returned first
    // setLocationMode(<LocationMode>"Battery_Saving");

    return new Promise((resolve, reject) => {

        Geolocation.getCurrentPosition(
            async res => {
                if (SetLocation && typeof SetLocation === 'function' && res.location) {
                    SetLocation({
                        longitude: getBit(res.location.longitude, 6),
                        latitude: getBit(res.location.latitude, 6),
                    });
                }
                console.log('SetAddress========', typeof SetAddress);
                if (SetAddress && typeof SetAddress === 'function') {
                    SetAddress(res.location.address);
                }
                //Get server time interface written in the background
                let serverTime = '';
                serverTime = await getServerTime().catch((err) => {
                    console.log('Get server time interface capture error:', err);
                });
                //The method of converting Gaode longitude and latitude into gps longitude and latitude written in the background
                let gpsLocation = {};
                if (isToGps !== false) {
                    gpsLocation = await transformGdToGps({
                        wgLon: res.location.longitude,
                        wgLat: res.location.latitude,
                    }).catch((err) => {
                        console.log('Longitude and latitude converter capture error:', err);
                    });
                    console.log("gpsLocation======", gpsLocation);
                    if (SetGpsLocation && typeof SetGpsLocation === 'function' && gpsLocation) {
                        SetGpsLocation({
                            gpsLongitude: getBit(gpsLocation.wgLon, 6),
                            gpsLatitude: getBit(gpsLocation.wgLat, 6),
                        })
                    }
                }
                let time = dateFormat("YYYY-mm-dd HH:MM:SS", new Date());

                let result: ILocationPosition;
                result = {
                    longitude: getBit(res.location.longitude, 6),//High precision
                    latitude: getBit(res.location.latitude, 6),//Gaode latitude
                    address: res.location.address,
                    currentTime: serverTime ? serverTime : time,
                    gpsLongitude: gpsLocation && gpsLocation.wgLon ? getBit(gpsLocation.wgLon, 6) : undefined,
                    gpsLatitude: gpsLocation && gpsLocation.wgLat ? getBit(gpsLocation.wgLat, 6) : undefined,
                };

                console.log('SetCurrentError Type is========', typeof SetCurrentError);
                //Calculate the distance between two coordinate points
                if (targetCoordinate && SetCurrentError && typeof SetCurrentError === "function") {
                    let newError = 0;
                    newError = getDistance(targetCoordinate, {
                        longitude: res.location.longitude,
                        latitude: res.location.latitude
                    }, 1);
                    console.log("Actual error=================", newError);
                    SetCurrentError(newError);
                    result = {
                        ...result,
                        realErrorDistance: newError,
                    }
                }
                
                console.log(result);

                resolve(result);
            },
            (error) => {
                let errorInfo = error.message.substring(0, error.message.indexOf(" "));
                console.log(error);
                Toast.fail(`Positioning error: ${errorInfo}`, 0.5);

            },
            // Here, change enablehuaccuracy to false 6689
            { enableHighAccuracy: false, timeout: 3000, maximumAge: 3000 })
    })

}

Single positioning is easy to use in cities, but after going to the mountains, the accuracy error will be very large due to the reduction of base stations and signal weakening. In order to overcome the problem of inaccurate positioning in the mountains, the following monitoring and positioning methods need to be used.

2.3 monitoring and positioning of react native amap location module

Listening and positioning is a positioning method that calls the positioned listener. It should pay attention to removing the listener in time after the positioning is completed to avoid the continuous operation of the listener (if it is a positioning type of continuous motion, you can continue to listen until the motion is completed and then remove the listener).

//Listening and positioning method for external call
export function getListenerPosition(SetLocation?, SetAddress?, targetCoordinate?, SetCurrentError?: Function,SetGpsLocation?:Function,isToGps?:boolean) {

    return new Promise((resolve, reject) => {

        getPositionInit().then((permission) => {
            if (permission) {
                start();
                addLocationListener((location) => {
                    console.log(location, 111);
                    let result = loadFunction(location, SetLocation, SetAddress, targetCoordinate, SetCurrentError,SetGpsLocation,isToGps);
                    resolve(result);
                });
            } else {
                Alert.alert("Application failed to obtain mobile location permission, unable to obtain current latitude and longitude information!")
            }
        })
    })
}

const loadFunction = async (location, SetLocation?, SetAddress?, targetCoordinate?, SetCurrentError?: Function,SetGpsLocation?:Function,isToGps?:boolean) => {
    if (location) {
        console.log('Monitored movement distance', location.accuracy);
        // After the data is obtained, two criteria are used for filtering
        // locationType 0: GPS positioning 1: no statistics will be made for network positioning type 6
        // Accuracy, for example, points with an accuracy greater than 10 meters do not perform business operations.
        if (location.accuracy * 1 <= 500 && location.locationType * 1 != 6) {
            console.log('Less than 10', location);

            let time = dateFormat("YYYY-mm-dd HH:MM:SS", new Date());

            if (SetLocation && typeof SetLocation === "function") {
                SetLocation({
                    longitude: getBit(location.longitude, 6),
                    latitude: getBit(location.latitude, 6),
                });
            }
            if (SetAddress && typeof SetAddress === "function") {
                SetAddress(location.address);
            }

            let serverTime = '';
            serverTime = await getServerTime().catch((err) => {
                console.log('Get server time interface capture error:', err);
            });
            let gpsLocation = {};
            if (isToGps !== false) {
                gpsLocation = await transformGdToGps({
                    wgLon: location.longitude,
                    wgLat: location.latitude,
                }).catch((err) => {
                    console.log('Longitude and latitude converter capture error:', err);
                });
                console.log("gpsLocation======", gpsLocation);
                if (SetGpsLocation && typeof SetGpsLocation === 'function' && gpsLocation) {
                    SetGpsLocation({
                        gpsLongitude: getBit(gpsLocation.wgLon, 6),
                        gpsLatitude: getBit(gpsLocation.wgLat, 6),
                    })
                }
            }
            let time = dateFormat("YYYY-mm-dd HH:MM:SS", new Date());

            let result: ILocationPosition;
            result = {
                longitude: getBit(location.longitude, 6),
                latitude: getBit(location.latitude, 6),
                address: location.address,
                currentTime: serverTime ? serverTime : time,
                gpsLongitude: gpsLocation && gpsLocation.wgLon ? getBit(gpsLocation.wgLon, 6) : undefined,
                gpsLatitude: gpsLocation && gpsLocation.wgLat ? getBit(gpsLocation.wgLat, 6) : undefined,
            };

            //Calculate the distance between two coordinate points
            if (targetCoordinate && SetCurrentError && typeof SetCurrentError === "function") {
                let newError = 0;
                newError = getDistance(targetCoordinate, {
                    longitude: location.longitude,
                    latitude: location.latitude
                }, 1);
                console.log("Actual error=================", newError);
                SetCurrentError(newError);
                result = {
                    ...result,
                    realErrorDistance: newError,
                }
            }
            console.log('New management results=======', result);

            removeLocationListener((location) => loadFunction(location, SetLocation, SetAddress, targetCoordinate, SetCurrentError));
            stop();

            return result;
        }
    }
};

The removeLocationListener method here is not available in the library. It is manually in the library file amap - geography JS:

/**My modification
 * Remove location listener function
 *
 * @param listener
 */
function removeLocationListener(listener) {
    console.log("000000000");
    return eventEmitter.removeAllListeners("AMapGeolocation");
}
exports.removeLocationListener = removeLocationListener;

At the same time, in the library file amap - geography d. Export it from ts:

/**
 * Remove location listener function
 *
 * @param listener
 */
export declare function removeLocationListener(listener: (location: Location & ReGeocode) => void): import("react-native").EmitterSubscription;

You can reference it in your own code as follows:

import {addLocationListener, removeLocationListener, start, stop} from "react-native-amap-geolocation/lib/js";

The biggest advantage of monitoring and positioning is that it can be more accurate in mountainous areas. It sifts out the positioning results with a positioning accuracy of more than 500m and the positioning type of 6, that is, the results of base station positioning, because base station positioning purely depends on mobile networks such as China Mobile, China Unicom and Telecom, and the positioning accuracy is between 500m and 5000m, which is data with great error. In this way, more accurate and less volatile results can be obtained.

3, Positioning comparison

To sum up:

Positioning modeObtained coordinate systemInverse geocodingUrban accuracyMountain accuracy
Geolocation positioning provided by rngps coordinatesInterface conversion requiredhighhigh
Single location of react native amap geolocationGaud coordinatesThe result itself contains no conversion requiredhighlow
Monitoring and location of react native amap locationGaud coordinatesThe result itself contains no conversion requiredhighhigh

ending

Finally, it is a public official account, which is used to record technical thinking in study and work.

Topics: Front-end React Native