HEX
Server: Apache/2.4.63 (Unix)
System: Linux Synopilou92 4.4.302+ #72806 SMP Mon Jul 21 23:16:00 CST 2025 x86_64
User: pilou92 (1026)
PHP: 8.0.30
Disabled: NONE
Upload Files
File: /volume1/@appstore/SynologyPhotos/usr/plugin/reverse_geocoding.py
#!/usr/bin/env python3
# Copyright (c) 2000-2020 Synology Inc. All rights reserved.

import argparse
import json
import os
import ssl
import sys
import urllib.request, urllib.error, urllib.parse


MAINLAND_CHINA_MAPPING = {
    "NONE": "中国大陆",
    "zh-CN": "中国大陆",
    "zh-TW": "中國大陸",
}


class BadParameterException(Exception):
    pass


class ReverseGeocodingRequest:
    """
    A class to send reverse geocoding request and read the response in
    structural format
    """

    GET_REQUEST_FORMAT = "{0:s}?{1:s}"
    ENVIRON_KEY_GEOCODING_SERVER_URL = "SYNO_PHOTOS_GEOCODING_SERVER_URL"
    ENVIRON_KEY_API_KEY = "SYNO_PHOTOS_API_KEY"
    ENVIRON_KEY_USER_AGENT = "SYNO_PHOTOS_USER_AGENT"

    def __init__(self):
        self.json_response = {}

    def sendGetRequest(self, para):
        self.getVariable()
        para["syno_serial_number"] = "TEMP"
        # remove after CST removes the check of serial number
        request = urllib.request.Request(
            ReverseGeocodingRequest.GET_REQUEST_FORMAT.format(
                self.server_url, urllib.parse.urlencode(para)
            )
        )
        request.add_header("User-Agent", self.user_agent)
        request.add_header("Authorization", "Key " + self.api_key)
        request.add_header("Accept", "application/json")
        request.add_header("Accept-Charset", "UTF-8")
        ctx = ssl.create_default_context(cafile="/etc/ssl/certs/ca-certificates.crt")
        return urllib.request.urlopen(request, context=ctx)

    def getVariable(self):
        try:
            self.server_url = os.environ.pop(
                ReverseGeocodingRequest.ENVIRON_KEY_GEOCODING_SERVER_URL
            )
            self.api_key = os.environ.pop(ReverseGeocodingRequest.ENVIRON_KEY_API_KEY)
            self.user_agent = os.environ.pop(
                ReverseGeocodingRequest.ENVIRON_KEY_USER_AGENT
            )
        except KeyError:
            raise BadParameterException("bad parameter")
        if not self.server_url or not self.api_key or not self.user_agent:
            raise BadParameterException("illegal parameter")

    def send(self, lat, lon):
        para = {"lat": lat, "lon": lon}
        response = self.sendGetRequest(para)
        self.json_response = json.load(response)
        return 0

    def json(self):
        return self.json_response


def parseArgument():
    parser = argparse.ArgumentParser(
        description="A tool to reverse geocoding to human-readable address"
    )
    parser.add_argument(
        "-lat",
        "--latitude",
        type=float,
        dest="lat",
        required=True,
        help="the latitude of the coordinates",
    )
    parser.add_argument(
        "-lon",
        "--longitude",
        type=float,
        dest="lon",
        required=True,
        help="the longitude of the coordinates",
    )
    return parser.parse_args()


def get_local_country_and_state(response):
    local_address = response.get("address", {}).get("NONE", {})
    return [local_address.get("country", ""), local_address.get("state", "")]


def is_country_china(country):
    return country == "中国"


def is_state_hk_or_mo(state):
    return state == "香港 Hong Kong" or state == "澳門 Macau"


def is_country_denmark(country):
    return country == "Danmark"


def update_state_to_country(response):
    addr = response.get("address")
    for key in addr:
        item = addr[key]
        if "country" in item:
            if "state" in item:
                item["country"] = item["state"]
                del item["state"]
            else:
                del item["country"]


def update_to_mainland_china(response):
    addr = response.get("address")
    for key in addr:
        item = addr[key]
        if "country" in item:
            country = item["country"]
            if key in MAINLAND_CHINA_MAPPING:
                item["country"] = MAINLAND_CHINA_MAPPING.get(key)


def remove_suburb(response):
    addr = response.get("address")
    for key in addr:
        item = addr[key]
        if "suburb" in item:
            del item["suburb"]


def update_response(response):
    country, state = get_local_country_and_state(response)
    if is_country_china(country):
        if is_state_hk_or_mo(state):
            update_state_to_country(response)
        else:
            update_to_mainland_china(response)

    if is_country_denmark(country):
        remove_suburb(response)


def main():
    args = parseArgument()
    request = ReverseGeocodingRequest()
    try:
        request.send(args.lat, args.lon)
        response = request.json()
        if "error" in response:
            sys.stderr.write("Failed to reverse geocoding\n")
            sys.stderr.write("Response: {0}\n".format(response["error"]))
            exit(4)

        update_response(response)
    except urllib.error.HTTPError as e:
        sys.stderr.write("The server couldn't fulfill the request.\n")
        sys.stderr.write("Error code: {0}\n".format(e.code))
        sys.stderr.write("Full response: {0}\n".format(e.read()))
        exit(1)
    except urllib.error.URLError as e:
        sys.stderr.write("We failed to reach a server.\n")
        sys.stderr.write("Reason: {0}\n".format(e.reason))
        exit(2)
    except ValueError:
        sys.stderr.write("Failed to decode response as JSON\n")
        sys.stderr.write("Response: {0}\n".format(response))
        exit(3)
    except BadParameterException as e:
        sys.stderr.write("Bad parameter\n")
        sys.stderr.write("Message: {0}\n".format(e.message))
        exit(5)

    print(json.dumps(response, indent=4))
    exit(0)


if __name__ == "__main__":
    main()