根据经纬度坐标获取当前所在城市——(自己开发简易版逆地址解析)

背景

最近各大地图商齐刷刷的开始对地图的一些接口收费,特别是对商业用户。我在一些论坛上看到有水友吐槽,自己的APP用到了逆地址解析接口来获取当前城市,现在都要面临既收费、又限制调用频率和次数的问题,于是萌生了做一个国内城市逆地址解析接口的想法。

具体想法

具体实现并不难,主要分以下几步:

1、获取国内省市的地理轮廓

2、使用geo库解析轮廓

3、根据用户输入的坐标,按照“国——省——市”的顺序,找出坐标落在哪个市级范围内

开始实现

开发环境

操作系统:ubuntu 18.04

python版本: 3.8

django版本:2.2.4

postgrsql版本:10.23

开发步骤

1、下载省市轮廓,下载地址http://www.geojson.cn/preview,我是用python脚本下载的,格式是geojson

#!/user/bin/pyhton
import os
import urllib.request
import json

BASE_URL = 'https://geojson.cn/api/data'
TAR_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'data', 'geojson')


def download_file(file_url, file_path):
    urllib.request.urlretrieve(file_url, file_path)


if __name__ == '__main__':
    cina_url = '/'.join([BASE_URL, '100000.json'])
    cina_filename = os.path.join(TAR_DIR, '100000.json')
    download_file(cina_url, cina_filename)
    with open(cina_filename, 'r', encoding='UTF-8') as fd:
        cina_data = json.load(fd)
        prv_list = cina_data['features']
        for index, prv_item in enumerate(prv_list):
            props = prv_item['properties']
            if 'code' in props:
                print('index: %d, name: %s, code: %d' % (index, props['name'], props['code']))
                try:
                    download_file('/'.join([BASE_URL, '%d.json' % props['code']]), os.path.join(TAR_DIR, '%d.json' % props['code']))
                except:
                    print('[error]index: %d, name: %s, code: %d' % (index, props['name'], props['code']))
            else:
                print('index: %d, props: %s' % (index, props))

2、在django内构造省市区域的model

from django.contrib.gis.db import models

from common.base_model import BaseModel

class AdArea(BaseModel):
    """行政区域

    Args:
        BaseModel (_type_): _description_
    """
    code = models.IntegerField(unique=True, null=False, verbose_name='区域编码')
    name = models.CharField(max_length=256, verbose_name='名称')
    fullname = models.CharField(max_length=256, verbose_name='区域全名')
    center = models.PointField(verbose_name='地理中心')
    children_num = models.IntegerField(verbose_name='子区域个数')
    level = models.CharField(max_length=64, verbose_name='级别')
    bbox = models.PolygonField(verbose_name='区域矩形边框')

    parent_code = models.IntegerField(verbose_name='父区域编码')
    mpoly = models.MultiPolygonField(verbose_name='区域地理边界')
    mpoly2 = models.GeometryCollectionField(null=True, blank=True, verbose_name='区域地理边界2', help_text='mpoly无效几何的修正结果')

    def __str__(self):
        return self.name
    class Meta:
        indexes = [
            models.Index(fields=["level"])
        ]



3、将geojson数据导入到postgresql数据库

4、构造rest api用于逆地址解析

from django.shortcuts import render
from django.contrib.gis.geos import Point
from django.contrib.gis.geos.error import GEOSException

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

from .models import AdArea
from .serializers import AdAreaSerializer


def is_in_china_bbox(latitude, longitude):
    china_bbox = (73.502355, 17.98689826522479, 135.09567, 53.563269)
    if latitude < china_bbox[1] or latitude > china_bbox[3]:
        return False
    if longitude < china_bbox[0] or longitude > china_bbox[2]:
        return False
    return True

def is_in_area(location, m):
    if not m.bbox:
        return True
    try:
        if not m.bbox.contains(location):
            return False
        if m.mpoly2 is not None:
            return m.mpoly2.contains(location)
        else:
            return m.mpoly.contains(location)
    except GEOSException:
        print('=================geo contains error==================')
        for poly in m.mpoly:
            for ring in poly:
                print(list(ring))
        print('=================geo contains error==================')
        raise


@api_view(['GET'])
def reverse_city(request):
    """经纬度逆解析-获取当前所在城市

    Args:
        http://localhost:8000/api/zzgeo/reverse_city/?longitude=120.592528&latitude=31.310623
        
    Returns:
        {
            'code': 320500,
            'name': '苏州',
            'level': 'city'
        }
        
    """
    if request.method == 'GET':
        latitude = float(request.GET['latitude'])
        longitude = float(request.GET['longitude'])

        if not is_in_china_bbox(latitude, longitude):
            return Response(status=status.HTTP_404_NOT_FOUND)

        city_location = Point(longitude, latitude)

        province_list = AdArea.objects.filter(level='province')
        for province_item in province_list:
            if not is_in_area(city_location, province_item):
                continue
            city_list = AdArea.objects.filter(parent_code=province_item.code, level='city')
            if not city_list:
                return Response(AdAreaSerializer(province_item).data)
            for city_item in city_list:
                if not is_in_area(city_location, city_item):
                    continue
                return Response(AdAreaSerializer(city_item).data)


        return Response(status=status.HTTP_404_NOT_FOUND)

接口展示

 

总结

使用geodjango,将下载的省市geojson格式的轮廓导入到系统,构造rest api,在用户发起查询时,按照“国——省——市”的顺序,找出坐标落在哪个市级范围内。

本文为抛砖引玉,geo库有非常多,覆盖了几乎所有的编程语言,例如nodejs可以使用turfjs。

欢迎大家参与讨论。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卓伙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值