oracle多边形经纬范围筛选_将地理范围网格化以及高德Loca API使用

本文介绍了如何将地理范围进行网格化处理,并结合Oracle多边形经纬范围筛选,利用Python进行数据转换。通过高德Loca API生成蜂窝热力图,为数据增值服务提供基础。内容包括范围线准备、列表转化、经纬度投影、正多边形填充等步骤,以及前后端代码实现。
摘要由CSDN通过智能技术生成

04ed35781d096ad162656d091a8d2f45.gif

"网格分析在很多层面都能用到,近期的接触当中,发现有对区域地价做分析的需求。针对该情况,笔者找了相关的资料实现了一个范围的网格化。同时用生成的数据,调用高德的开放接口,尝试了蜂窝热力图。该基础功能,能为后续做数据增值服务做准备。"

视频:

01

范围线准备

范围线可以稍微大一点,可以用shp,json,kml格式的数据。如果找不到合适的,最近发现一个下载到县区一级的网站,可以下载一个范围线。

http://datav.aliyun.com/tools/atlas/#&lat=31.769817845138945&lng=104.29901249999999&zoom=4

02

转化为列表

用Python读取js数据,把经纬度提取出来转化为列表格式。

范围文件:redline.json(部分)

{"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[113.27443651579965, 27.874858011710145], [113.27443937112358, 27.87483870018461], [113.27444062579825, 27.87483021439111], [113.27444426387372, 27.874805608836787], [113.2745222628382, 27.874665403047846]]}, "properties": {"FID_1": 0}}]}

 转化代码:

import jsonwith open('redline.json','r',encoding='utf8')as fp:    json_data = json.load(fp)listhh=json_data['features'][0]['geometry']['coordinates']tupleaa=[]for i in listhh:    tupleaa.append(tuple(i))print(tupleaa)

转化后的列表:

[(113.27443651579965, 27.874858011710145), (113.27443937112358, 27.87483870018461), (113.27444062579825, 27.87483021439111), (113.27444426387372, 27.874805608836787)]

03

经纬度投影

地理坐标要先转化成平面投影,在平面投影里做几何运算。

下面代码包含了转极坐标与存储数据为js格式:util.py

import jsonfrom pyproj import Projdef project_to_plane(points):    """ 把物理点投影到二维平面.    """    p = Proj(4508)    return [p(point[0], point[1]) for point in points]def project_to_polar(points):    """ 把平面上的点转换成极坐标.    """    p = Proj(4508)    def proj_and_round(point):        q = p(point[0], point[1], inverse=True)        return round(q[0], 6), round(q[1], 6)    return [proj_and_round(point) for point in points]def to_js(points, path, var_name):    """ 保存成js文件(前端展示).    """    data_js = {        'coordinates': [list(p) for p in points]    }    with open(path, 'w', encoding='utf-8') as f:        f.write('%s = [%s];' % (var_name, json.dumps(data_js)))

04

获取正多边形

这里主要是选择不同的多边形,正三角,正方形,正六边形。用到shaply模块。

poly_getter.py:

import mathfrom shapely.geometry import Polygon, Pointclass PolyGetter(object):    """ 生成正多边形对象    """    def __init__(self, radius, k, theta=0):        self.radius = radius        self.k = k  # 正多边形的边数        self.theta = theta  # 起始角度: degree    def from_center(self, center):        """ 输入中心点的坐标,返回对应的正多边形        :param center: Point对象        """        def get_xy(i):            x = center.x + self.radius * math.cos(2 * math.pi * (i / self.k + self.theta / 360))            y = center.y + self.radius * math.sin(2 * math.pi * (i / self.k + self.theta / 360))            return x, y        return Polygon([Point(get_xy(i)) for i in range(self.k)])    def from_vertex(self, vertex, i):        """ 输入顶点的坐标,返回对应的多边形        :param vertex: 顶点坐标,Point对象        :param i: 顶点的编号(按极坐标顺序编号)        """        c_x = vertex.x - self.radius * math.cos(2 * math.pi * i / self.k + self.theta)        c_y = vertex.y - self.radius * math.sin(2 * math.pi * i / self.k + self.theta)        return self.from_center(Point(c_x, c_y))    def neighbors_of(self, poly):        """ 输入正多边形,返回它所有邻接的多边形        :param poly: 多边形,Polygon对象        """        dist = self.radius * math.cos(math.pi / self.k)  # 计算中心到边的距离        p = PolyGetter(2 * dist,                       self.k,                       self.theta + 180 / self.k)        centers = list(p.from_center(poly.centroid).exterior.coords)        return [Polygon(self.from_center(Point(c))) for c in centers]

05

正多边形填充

用正多边形填充范围,这里面用BFS(广度优先搜索)进行填充,以范围的几何中心往外围填充。

poly_fill:

from shapely.geometry import Polygon, MultiPolygonfrom yl.poly_getter import PolyGetterclass PolyFill(object):    """ 给定一个多边形区域, 用正多边形填充它.    """    def __init__(self, boundary, center):        """        :param boundary: 被分割的多边形, Polygon对象        :param center: 锚点,以center为出发点进行分割, Point对象        """        self._boundary = boundary        self._center = center        self._radius = None        self._k = None        self._theta = None        self._poly_getter = None        self._result = []        # 用来保存已经搜索过的正多边形(用中心点来表示)        self._searched_polys = set({})    def set_params(self, radius, k=6, theta=0):        """ 参数设置.        :param radius: 正多边形外接圆的半径        :param k: 正k多边形.  k = 3, 4, 6        :param theta: 正多边形的起始角度(度数)        """        self._radius = radius        self._k = k        self._theta = theta        assert radius > 0 and k in {3, 4, 6}, \            ValueError('radius > 0 and k in (3, 4, 6)')        self._radius = radius        self._k = k        self._theta = theta        self._poly_getter = PolyGetter(self._radius, self._k, self._theta)        return self    def run(self):        """        :return:        [[(x11, y11), (x12, y12) ...]  # 多边形1         [(x21, y21), (x22, y22) ...]  # 多边形2         ...]                          # ...        """        assert self._radius, ValueError("set parameters first!")        start_poly = self._poly_getter.from_center(self._center)        # 以start_poly为起点执行BFS填充boundary        self._fill(start_poly)        return [list(poly.exterior.coords) for poly in self._result]    def _fill(self, start_poly):        """ 给定初始的填充多边形, 按照BFS的方式填充周围的区域.        以k多边形为例(k=3,4,6), 有 360/k 个多边形与它相邻.        """        self._mark_as_searched(start_poly)        q = [start_poly]        while len(q):            poly = q.pop(0)            self._append_to_result(poly)            # 把有效的多边形加入队列. 有效的定义:            # 1. 与poly邻接;            # 2. 未被搜索过;            # 3. 在边界内(与boundary定义的区域有交集)            q += self._get_feasible_neighbors(poly)    def _mark_as_searched(self, poly):        """ 把多边形标记为'已搜索'        """        self._searched_polys.add(self._get_poly_id(poly))    @staticmethod    def _get_poly_id(poly):        """ 用多边形的中心点的位置判断两个多边形是否相同.        注意浮点数精度问题.        """        c = poly.centroid        return '%.6f,%.6f' % (c.x, c.y)    def _is_searched(self, poly):        """ 判断多边形是否存在        """        return self._get_poly_id(poly) in self._searched_polys    def _append_to_result(self, poly):        """ 把正多边形poly保存到结果集.        """        # poly与boundary取交集, 然后保存结果        s = self._boundary.intersection(poly)        if s.is_empty:            return        # Polygon对象则直接保存        if isinstance(s, Polygon):            self._result.append(s)        # MultiPolygon对象则依次把它包含的Polygon对象保存        elif isinstance(s, MultiPolygon):            for p in s:                self._result.append(p)    def _get_feasible_neighbors(self, poly):        """ 计算与poly邻接的有效的正多边形, 然后标记为'已搜索'.        """        def mark_searched(p):            self._mark_as_searched(p)            return p        def is_feasible(p):            if self._is_searched(p) or self._boundary.intersection(p).is_empty:                return False            return True        # 1. 仅包含'未被搜索'和'不在界外'的正多边形        # 2. 把poly所有的feasible多边形标记为'已搜索'        return [mark_searched(p)                for p in self._poly_getter.neighbors_of(poly)                if is_feasible(p)]

06

生成数据成果

引入上述的几个python文件,最终生成js文件,方便前端引入使用。

main.py:

from pathlib import Pathfrom shapely.geometry import Polygonfrom data import boundary_district_330106from poly_fill import PolyFillfrom util import project_to_plane, project_to_polar, to_jsdef map_seg(radius, k, theta=0):    # 把经纬度投影到二维平面    boundary_plane = Polygon(project_to_plane(boundary_district_330106))    # 用正多边形填充    result = PolyFill(boundary_plane, boundary_plane.centroid).set_params(radius, k, theta).run()    # 把结果转换成极坐标    result = [project_to_polar(poly) for poly in result]    # 保存结果    d = Path('web')    to_js(boundary_district_330106, d / 'data_boundaries.js', 'MS.data.blockBoundaries')    to_js(result, d / 'data_bricks.js', 'MS.data.bricks ')if __name__ == '__main__':    map_seg(100, 6)  # 六边形分割: 半径1000米    # map_seg(2000, 4)  # 四边形分割: 半径2000米    # map_seg(4000, 3)  # 三角形分割: 半径4000米

07

前端代码

这里前端代码,还是高德的api,还需要自己写部分js,拆分的很开!

index.html:

<html lang="zh-CN"><head>    <meta charset="utf-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1">    <title>map-segtitle>    <script src="https://a.amap.com/Loca/static/dist/jquery.min.js">script>    <script src="https://webapi.amap.com/maps?v=1.4.15&key=c9ef357d99db1c97911d0105a24abb6f">script>    <script src="https://webapi.amap.com/loca?v=1.3.0&key=c9ef357d99db1c97911d0105a24abb6f">script>    <style>        html,        body,        #container {            margin: 0;            padding: 0;            width: 100%;            height: 100%;        }        #layerCtrl {            margin: 1%;            padding: 5px;            width: 6%;            background: #484848;            opacity: 0.5;            color: white;        }style>head><body>    <div id="container" class="container">        <script src="index.js">script>        <div id="layerCtrl" class="layerCtrl">            <p>                <input type="checkbox" id="cboxBricks" checked="checked" onclick="ctrlLayer(layerBricks.layer, this)">                <label for="cboxBricks">显示砖块label>            p>        div>    div>body>html>

08

高德蜂窝热力图

高德地图可以直接从网址中复制代码,输入自己的密钥。网址如下:

https://lbs.amap.com/api/loca-api/demos/hexagonlayer/loca_heatmap_hexagon_3d

要将风格一行进行调整,换成黑色风格底图,数据是tsv格式的,提取蜂窝的中心地理坐标。数据的值统一用1000.

gaode.html:

<html lang="zh-CN"><head>    <meta charset="utf-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1">    <title>蜂窝热力图(按米分箱)- 3Dtitle>    <style>        html,        body,        #container {            margin: 0;            padding: 0;            width: 100%;            height: 100%;        }style>head><body>    <div id="container" class="container">div>    <script src="//webapi.amap.com/maps?v=1.4.15&key=6ebc532a86456a04773258fdf9921696">script>    <script src="//webapi.amap.com/loca?v=1.3.0&key=6ebc532a86456a04773258fdf9921696">script>    <script src="//a.amap.com/Loca/static/dist/jquery.min.js">script>    <script>        var map = new AMap.Map('container', {            viewMode: '2D',            resizeEnable: true,            pitch: 0,            mapStyle: 'amap://styles/2f4e6f376313300be862ab7cdc8340bd',            features: ['bg', 'road']        });        $.get('test.tsv', function (heatmapData) {            var layer = new Loca.HexagonLayer({                map: map,                fitView: true            });            layer.setData(heatmapData, {                lnglat: function (obj) {                    var val = obj.value;                    return [val['lng'], val['lat']]                },                value: 'count',                type: 'tsv'            });            layer.setOptions({                mode: 'count',                unit: 'meter',                style: {                    color: ['#0868AC', '#43A2CA', '#43A2CA', '#7BCCC4', '#BAE4BC', '#F0F9E8', '#F0F9E8'],                    radius: 100,                    opacity: 0.9,                    gap: 20,                    height: [0, 100000]                }            });            layer.render();        });script>body>html>

代码与提取码奉上:

链接:https://pan.baidu.com/s/1iG85fLkNRme_JMzC7GtaLQ 

提取码:o4sd 

后台回复关键词:教育,可查看我的个人在线课程,欢迎大家来沟通、交流、共同学习!


50f4d5222d5e898553eac2b99b837162.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值