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

背景

最近各大地图商齐刷刷的开始对地图的一些接口收费,特别是对商业用户。我在一些论坛看到有水友吐槽,自己的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&amp;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

欢迎大家参与讨论

原文地址:https://blog.csdn.net/whbke/article/details/131221054

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_47900.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注