关于腾讯/百度/高德约好要找你要5万块钱这件事---地图API的替代方案

569 阅读3分钟

指的就是地图API收费这件事, 自己做了一些替代方案,分享如下

**主要是用到两个功能

  • 1:距离计算 (我离xxx有xxx米)
  • 2:逆地理位置解析(我当前处于xx城市)**

针对1距离计算

直接使用极坐标函数即可 AI搜“极坐标函数计算经纬度距离”,AI写这种东西得心应手

这是php的函数示例

public static function getDistance($ulon, $ulat, $slon, $slat)
    {
        // 地球半径
        $R = 6378137;
        // 将角度转为狐度
        $radLat1 = deg2rad($ulat);
        $radLat2 = deg2rad($slat);
        $radLng1 = deg2rad($ulon);
        $radLng2 = deg2rad($slon);
        // 结果
        $s = acos(cos($radLat1) * cos($radLat2) * cos($radLng1 - $radLng2) + sin($radLat1) * sin($radLat2)) * $R;
        // 精度
        $s = round($s * 10000) / 10000;
        return round($s);
    }

注意:
(1)可能需要坐标系的转换,比如腾讯系坐标转为GPS系坐标,同样AI搜“腾讯系坐标转为GPS系坐标”让它给个函数即可
(2)腾讯api等 可以求出步行距离,这个函数求的是直线距离,肯定没步行距离好,但为了5w块钱,将就下

针对2逆地理位置解析

大体思路就是拉取省市的坐标边缘数据,判断当前坐标是否在这个城市区域内 这个数据,叫geojson数据,这个网站有
geojson.hxkj.vip/ ---我下载的是这个
还有个官方网站也有
geojson.cn/
下载后的数据,大概格式如下

 "coordinates": [
                    [
                        [
                            [
                                113.540624,
                                22.201871
                            ],
                            [
                                113.540515,
                                22.203771
                            ],
                            [
                                113.539782,
                                22.204445
                            ],
                            [
                                113.5391,
                                22.206724
                            ],
]

我们要做的,就是判断自己经纬度,是否在这么个区域内,上面4个坐标,就是一个四边形,判断当前经纬度是否在这个四边形即可(实际应用中是多边形) 这个也可以问AI “直接将geojson数据发送一小段给AI,问,根据上面geojson数据,如何逆推出城市数据,如上海即可” 下面是php的,仅供参考

image.png

<?php
namespace app\common\library\geo;

class GeoLocationFinder
{
    private $dataDirectory = './data';//数据放在类的当前data目录下
    private $loadedFiles = [];
    private $featuresCache = [];
    private $province = "浙江省";//我这浙江的坐标多,所以默认查浙江的这个文件


    private $provinceArray = array(
            110000 => '北京市',
            120000 => '天津市',
            130000 => '河北省',
            140000 => '山西省',
            150000 => '内蒙古自治区',
            210000 => '辽宁省',
            220000 => '吉林省',
            230000 => '黑龙江省',
            310000 => '上海市',
            320000 => '江苏省',
            330000 => '浙江省',
            340000 => '安徽省',
            350000 => '福建省',
            360000 => '江西省',
            370000 => '山东省',
            410000 => '河南省',
            420000 => '湖北省',
            430000 => '湖南省',
            440000 => '广东省',
            450000 => '广西壮族自治区',
            460000 => '海南省',
            500000 => '重庆市',
            510000 => '四川省',
            520000 => '贵州省',
            530000 => '云南省',
            540000 => '西藏自治区',
            610000 => '陕西省',
            620000 => '甘肃省',
            630000 => '青海省',
            640000 => '宁夏回族自治区',
            650000 => '新疆维吾尔自治区',
            710000 => '台湾省', // 注意:此条目在实际应用中可能需特别处理
            810000 => '香港特别行政区',
            820000 => '澳门特别行政区',
    );


    public function __construct()
    {
        // 构造函数中初始化,可以加载特定文件或做其他预处理
        // 优先查浙江的
        $this->loadSpecificFile('330000.geoJson.default');
    }

    private function loadGeoJSONFiles($filename)
    {
        $filePath = __DIR__."/".$this->dataDirectory . '/' . $filename;

        if (file_exists($filePath) && is_readable($filePath)) {
            $content = file_get_contents($filePath);
            $geojsonData = json_decode($content);
            if ($geojsonData && isset($geojsonData->features)) {
                $this->featuresCache[$filename] = $geojsonData->features;
                $this->loadedFiles[] = $filename;
            } else {
                echo "Error: Invalid GeoJSON format in file $filename\n";
            }
        } else {
            echo "Error: File $filename not found or not readable\n";
        }
    }

    private function loadSpecificFile($filename)
    {
        $this->loadGeoJSONFiles($filename);
    }

    public function findLocationByCoordinates($longitude, $latitude)
    {
        // 首先尝试从已加载的特定文件中查找
        foreach ($this->featuresCache as $features) {
            $result = $this->searchFeatures($features, $longitude, $latitude);

            if ($result !== null) {
                return $result;
            }
        }

        // 如果特定文件中没有找到,遍历所有文件查找
        $files = glob(__DIR__."/".$this->dataDirectory . '/*.geoJson');

        foreach ($files as $file) {
            $filename = basename($file);

            if (!in_array($filename, $this->loadedFiles)) {
                $this->loadGeoJSONFiles($filename);

                $result = $this->searchFeatures($this->featuresCache[$filename], $longitude, $latitude);
                if ($result !== null) {
                    return $result;
                }
            }
        }

        return null;
    }

    private function searchFeatures($features, $longitude, $latitude)
    {

        foreach ($features as $feature) {

            if ($this->isPointInPolygon([$longitude, $latitude], $feature->geometry->coordinates)) {

                $provinceCode =$feature->properties->acroutes[1] ?? "";
                $this->province =$this->provinceArray[$provinceCode] ?? "";
                return $this->formatLocationInfo($feature->properties);
            }
        }
        return null;
    }

    private function isPointInPolygon($point, $polygon)
    {
        $x = $point[0]; // 经度
        $y = $point[1]; // 纬度

        $isInside = false;
        $rings = $polygon; // GeoJSON中的"coordinates"可能包含多个环,直接使用

        foreach ($rings as $v) { // 遍历所有环

            //这里就是多了一层
            $ring =$v[0];
            $numVertices = count($ring);

            $j = $numVertices - 1; // 初始化前一个顶点的索引

            for ($i = 0; $i < $numVertices; ++$i) { // 遍历环上的顶点
                $xi = $ring[$i][0]; // 当前顶点经度
                $yi = $ring[$i][1]; // 当前顶点纬度
                $xj = $ring[$j][0]; // 前一个顶点经度
                $yj = $ring[$j][1]; // 前一个顶点纬度

                // 应用射线交叉法判断
                $crossProduct = (($yi > $y) != ($yj > $y)) && ($x < ($xj - $xi) * ($y - $yi) / ($yj - $yi) + $xi);

                if ($crossProduct) {
                    $isInside = !$isInside;
                }

                $j = $i; // 更新前一个顶点的索引
            }
        }

        return $isInside;
    }


    private function formatLocationInfo($properties)
    {
        //110000 => '北京市'
        //120000 => '天津市'
        //310000 => '上海市'
        //500000 => '重庆市'  这几个是直辖市 降一级
        $city =$properties->name;
        $province = $this->province;
        $district = "";
        if(in_array($this->province,['上海市','天津市','重庆市','北京市'])){
            $district =$city;
            $city =$province;
        }
        return ['province'=>$province,'city'=>$city,'district'=>$district];
    }
}

//// 使用示例
//$finder = new GeoLocationFinder();
//$longitude = 120.1833; // 经度
//$latitude = 30.2500; // 纬度
//echo $finder->findLocationByCoordinates($longitude, $latitude);



github上还有个例子,可以参考,应该比较全 看着可以用mysql来实现,包括更详细的数据 github.com/xiangyuecn/…