banner
肥皂的小屋

肥皂的小屋

github
steam
bilibili
douban
tg_channel

Python - Gaode Map API Batch Query Original Image Geolocation

Cause of the Matter#

Many media outlets have reported that when sending images in social media chats, if the "original image" option is checked, it may expose personal location privacy based on the GPS information contained in the image.

Have you ever thought about implementing a geographic location query yourself?

I happened to see an article today, let's reproduce it.

Apply for Key#

First, register and log in to the Gaode Open Platform.

Create an application and click the + sign on the right to apply for a key:

image

Give the key a name and select web service to submit:

image

You will receive the reverse geocoding API:

image

There is a certain error in the longitude and latitude obtained through GPS and the coordinates of Gaode Map. The official Gaode provides a method for coordinate conversion.

The key used also belongs to this WEB service, so there is no need to apply again; this key will be used twice.

Image Processing#

Use the exifread library to read the image's exif information. The official GitHub has common parameter retrieval methods.

First, install it using pip:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple exifread

The basic usage is as follows:

with open("test.jpg", 'rb') as f:
    image_exif = exifread.process_file(f)
    for tag, value in image_exif.items():
        print(tag, value)

This is used to print all the information; in fact, exif is read as a dict, and you can read it directly by key-value, no need for a for loop.

(** Here’s a note: If you want to test it, take a photo with your own phone. Uploading images taken with WeChat and QQ to the computer is ineffective. **

You can transfer it by taking a photo with the phone's built-in camera -> through cloud storage or file transfer, for example, you can use NiuNiu Transfer.)

However, there are too many parameters here, most of which are unnecessary. We only need to obtain the following information:

Image Make: Phone Brand
Image Model: Phone Model
EXIF LensModel: Camera Information
GPS GPSLongitudeRef: Longitude Indicator
GPS GPSLongitude: Longitude
GPS GPSLatitudeRef: Latitude Indicator
GPS GPSLatitude: Latitude
Among them,
When writing the program, both longitude and latitude are positive values in the N direction.

GPS to Gaode#

The GPS information we read from the image is still not very accurate. The Gaode API provides a conversion function.

You can construct a function and request using the same API:

image

Query#

After obtaining the Gaode coordinates, use the same API to construct a url to request reverse geocoding conversion for gps.

Convert the gps coordinates into street addresses.

The full code is as follows:

# -*- coding: utf-8 -*-
'''
@author: soapffz
@function: Use Gaode Map API to query original image information and geographic location (can be single or batch in a folder)
@time: 2019-09-03
'''

from os import path, listdir
import exifread
import requests
import json


"""
If the library is not installed, please refer to:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple exifread
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple json

Gaode Open Platform: https://lbs.amap.com/dev/key/app
Article tutorial: https://soapffz.com/383.html

If querying in batches, it is recommended to place them in a folder and fill in the folder path during initialization.
"""


class Location_query(object):
    def __init__(self, key, img_path):
        # Pass in parameters, the obtained Gaode API and image path (which may also be a folder path)
        self.key = key
        if len(self.key) != 32:
            print("Your key seems to be incorrect, buddy...")
            exit(0)
        self.img_path = img_path
        self.judge_dir()

    def judge_dir(self):
        # Check if it is a folder
        if path.isdir(self.img_path):
            print("You have selected a folder")
            self.pic_l = []
            for item in listdir(self.img_path):
                if path.splitext(item)[1].lower() in [".jpg", ".png", ".jpeg"]:
                    self.pic_l.append(path.join(self.img_path, item))
            if len(self.pic_l) == 0:
                print("There are no images in the selected folder, check your uncle")
                exit(0)
            elif len(self.pic_l) == 1:
                self.singal_image_info(self.pic_l[0])
            else:
                print("A total of {} images obtained\n".format(len(self.pic_l)))
                self.multi_pics_query()
        else:
            self.singal_image_info(self.img_path)

    def singal_image_info(self, img):
        # Single image query
        if type(img) != "list":
            image_l = []
            image_l.append(img)
        else:
            image_l = img
        singal_pic_info_d = self.get_image_info(image_l)
        for key, value in singal_pic_info_d.items():
            print(key)
            for info in value:
                print(info)

    def multi_pics_query(self):
        # Process multiple images, as reverse geocoding can only handle 20 pairs of coordinates at a time, so cut all coordinates into a list of 20 each
        cut_pic_l = [self.pic_l[i:i + 20]
                     for i in range(0, len(self.pic_l), 20)]
        info_l = {}  # Store all query information
        for cut in cut_pic_l:
            batch_info = self.get_image_info(cut)
            info_l = {**info_l, **batch_info}
        for key, value in info_l.items():
            print(key)
            for info in value:
                print(info)
            print("\n")

    def parse_gps_express(self, gps_express):
        """
        Convert GPS coordinate value to numeric
        :param gps_express: GPS coordinate expression [1,2,3/4]
        :return: GPS coordinate value 1.035542
        """
        try:
            express = str(gps_express).replace(
                " ", "").replace("[", "").replace("]", "")
            parts = express.split(",")
            subpart = parts[2].split("/")
            degress = float(parts[0])
            minutes = float(parts[1])
            seconds = float(subpart[0]) / float(subpart[1])
            return degress + minutes / 60 + seconds / 3600
        except:
            raise Exception("Image information error")

    def get_image_info(self, images):
        """
        GPS coordinates of the photo shooting location
        :param photo_path: Disk paths of multiple photos (to cooperate with reverse geocoding API, which can only query 20 pairs at a time, here it is also set to a maximum of 20 images)
        :return: Detailed information of multiple photos (phone brand, phone model, camera information, geographic location)
        """
        multi_image_info = {}  # Store image information, key is the image, value is the image information
        gps_coordinates = {}  # Store multiple GPS information for batch queries to avoid wasting query times, key is the image, value is the GPS value
        for image in images:
            singal_image_info = []
            image_name = image.split("\\")[-1]
            with open(image, 'rb') as f:
                image_exif = exifread.process_file(f)
            if "Image Make" in image_exif.keys():
                # If phone information exists, obtain the image's phone information
                singal_image_info.append(
                    "Phone brand is:{}".format(str(image_exif["Image Make"])))
                singal_image_info.append("Phone model is:{}".format(
                    str(image_exif["Image Model"])))
                singal_image_info.append("Camera information is:{}".format(
                    str(image_exif["EXIF LensModel"])))
            else:
                singal_image_info.append("No phone information obtained")
            if "GPS GPSLongitudeRef" in image_exif.keys():
                # If GPS information exists, obtain the image's GPS information
                #longitude_ref = str(image_exif["GPS GPSLongitudeRef"]).strip()
                longitude = self.parse_gps_express(
                    str(image_exif["GPS GPSLongitude"]))
                #latitude_ref = str(image_exif["GPS GPSLatitudeRef"]).strip()
                latitude = self.parse_gps_express(
                    str(image_exif["GPS GPSLatitude"]))
                #lng = longitude if "E" == longitude_ref else 0 - longitude
                #lat = latitude if "E" == latitude_ref else 0 - latitude
                gps_coordinates[image] = str(longitude) + "," + str(latitude)
                # ["116.487585177952,39.991754014757","116.487585177952,39.991653917101"]
            else:
                singal_image_info.append("No GPS information obtained")
            multi_image_info[image] = singal_image_info
        for image, location in zip(gps_coordinates.keys(), self.convert_gps(gps_coordinates.values())):
            multi_image_info[image].append("Location is:{}".format(location))
        return multi_image_info

    def convert_gps(self, gps_coordinates):
        """
        Coordinate conversion: GPS to Gaode
        :param gps_coordinates: Collection of multiple locations "GPS Longitude, GPS Latitude" (both tuples or arrays are acceptable)
        :return: List of collections of multiple locations "Gaode Longitude, Gaode Latitude", receiving a maximum of 40 pairs of coordinates at a time
        """
        try:
            coordinates = "|".join(gps_coordinates)
            url = "https://restapi.amap.com/v3/assistant/coordinate/convert?key={0}&locations={1}&coordsys=gps&output=json".format(
                self.key, coordinates)
            response = requests.get(url)
            result = json.loads(response.text)
            if "1" == result["status"]:
                gd_coordinates = result["locations"].split(";")
                return self.get_map_address((gd_coordinates))
            else:
                print("error:", result["infocode"], result["info"])
        except Exception as e:
            raise e

    def get_map_address(self, gd_coordinates):
        """
        Reverse geocoding (Gaode coordinates to address)
        :param gd_coordinates: Collection of multiple locations "Gaode Longitude, Gaode Latitude" (both tuples or arrays are acceptable)
        :return: List of address information for multiple locations, querying a maximum of 20 latitude and longitude points at a time
        """
        try:
            coordinates = "|".join(gd_coordinates)
            batch = "true" if len(gd_coordinates) > 1 else "false"
            url = "https://restapi.amap.com/v3/geocode/regeo?key={0}&location={1}&batch={2}&radius=500&extensions=base&output=json".format(
                self.key, coordinates, batch)
            response = requests.get(url)
            result = json.loads(response.text)
            if "1" == result["status"]:
                address = []
                if batch == "true":
                    address.extend([add["formatted_address"]
                                    for add in list(result["regeocodes"])])
                else:
                    fmt_add = result["regeocode"]["formatted_address"]
                    address.append(fmt_add)
                return address
            else:
                print(result)
        except Exception as e:
            raise e


if __name__ == "__main__":
    key = ""  # Fill in the Gaode Open Platform API you applied for
    img_path = ""  # Fill in the image or folder address
    Location_query(key, img_path)

The effect of querying a single image is as follows:

image

The effect of querying a folder is as follows:

image


To Do

There is one place that is unclear, which is the GPS information read from the image: longitude and latitude.

The relationship between these two longitude_ref and latitude_ref is that if the direction is N, it needs to be converted to a negative number, as seen in the reference article.

However, negative numbers do not conform to Gaode Map GPS specifications; querying without a negative sign works, so these two lines are commented out for now, and I haven't figured it out yet.

EXIF Viewing Software#

I found this while researching, and it seems to be useful. I found a latest cracked version on zdfan, which has been tested to work.

You can also see that the effect of our query is correct:

image

The article is complete.

Reference articles:

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.