1

我正在开发一个基于 Laravel 的应用程序,用户可以在其中创建航班,然后在地图上查看飞行路线。其中一些地图包含多达 10.000 个航班。有了这些数字,Mapbox 有时会导致浏览器崩溃或需要很长时间才能渲染,尤其是因为我们使用arc.js来计算 Great Circle 路线。

然后我们切换到将飞行路线创建为单个 geojson 文件,然后由 Mapbox 直接加载。地图现在加载速度非常快(1-2 秒而不是最多半分钟),但路线是笔直的,不会越过日期变更线,这是一个真正的问题,因为飞机不是这样飞行的。

首先,我在 Mapbox 中寻找某种类型的设置,可以让我将线条渲染为大圆圈,但我找不到任何东西。接下来,我寻找类似于 arc.js 的 PHP 库来输出到 geojson 文件的路线,但是虽然有很多库可以根据大圆计算距离,但我没有找到任何真正产生路线的库。目前,我正在查看数据库级别。我们已经使用 PostGIS,所以我认为可能有一种方法可以使用它来计算路线。到目前为止,我在这里有这个,从各种来源拼凑而成,但它仍然抛出错误;ST_MakeLine 不存在...:

SELECT ST_Transform(
     ST_Segmentize(
         ST_MakeLine(
           dep.point,
           arr.point
         )::geography,
         100000
     )::geometry,
     3857
   ) as the_geom_webmercator
FROM flights f
LEFT JOIN airports dep ON f.departure_airport_id = dep.id
LEFT JOIN airports arr ON f.arrival_airport_id = arr.id;

我有点希望有一种隐藏的方式可以直接在 Mapbox 中显示曲线,也许是通过源/图层?如果做不到这一点,我很乐意为 PHP 库甚至 PostGIS 资源提供任何指针。

干杯!

4

2 回答 2

1

所以,我最终选择了 PHP 库路线。它的性能可能不如其他选项,但它是最简单的。如果其他人想知道如何实现这一点,这是我使用phpgeo包的解决方案:

namespace App\Services;

use Location\Bearing\BearingEllipsoidal;
use Location\Coordinate;
use Location\Distance\Vincenty;

class Arc
{
    protected Coordinate $start;
    protected Coordinate $end;

    /**
     * @param  \Location\Coordinate  $start
     * @param  \Location\Coordinate  $end
     */
    public function __construct(Coordinate $start, Coordinate $end)
    {
        $this->start = $start;
        $this->end   = $end;
    }

    /**
     * @param  int  $points
     * @return array
     */
    public function line(int $points = 100): array
    {
        $bearingCalculator  = new BearingEllipsoidal();
        $distanceCalculator = new Vincenty();

        $totalDistance    = $distanceCalculator->getDistance($this->start, $this->end);
        $intervalDistance = $totalDistance / ($points + 1);

        $currentBearing = $bearingCalculator->calculateBearing($this->start, $this->end);
        $currentCoords  = $this->start;

        $polyline = [
            [
                $this->start->getLng(),
                $this->start->getLat(),
            ],
        ];

        for ($i = 1; $i < ($points + 1); $i++) {
            $currentCoords = $bearingCalculator->calculateDestination(
                $currentCoords,
                $currentBearing,
                $intervalDistance
            );

            $point = [
                $currentCoords->getLng(),
                $currentCoords->getLat(),
            ];

            $polyline[$i] = $this->forAntiMeridian(
                $polyline[$i - 1],
                $point
            );

            $currentBearing = $bearingCalculator->calculateBearing(
                $currentCoords,
                $this->end
            );
        }

        $polyline[] = $this->forAntiMeridian(
            $polyline[$i - 1],
            [
                $this->end->getLng(),
                $this->end->getLat(),
            ]
        );

        return array_values($polyline);
    }

    /**
     * @param  array  $start
     * @param  array  $end
     * @return array
     */
    protected function forAntiMeridian(array $start, array $end): array
    {
        $startLng = $start[0];
        $endLng   = $end[0];

        if ($endLng - $startLng > 180) {
            $end[0] -= 360;
        } elseif ($startLng - $endLng > 180) {
            $end[0] += 360;
        }

        return $end;
    }
}

我在line其他地方找到了完成大部分工作的方法的内容,但似乎无法再次找到它。我只是稍微更新了一下,并添加了一条线何时穿过反子午线的计算。出于这个原因,Mapbox 允许坐标越界。

于 2021-04-01T08:28:45.433 回答
1

除了基于 php 的解决方案:

如果您遇到错误ST_MakeLine does not exist,您要么将错误的参数传递给函数,要么根本没有安装 PostGIS 扩展。话虽如此,要获得您正在寻找的曲线,您可以将您的点转换为之前的球面投影参考系统ST_Segmentize。之后,为了获得单个 GeoJSON 字符串,您可能ST_Collect需要这些行,然后使用ST_AsGeoJSON.

WITH airports (point) AS (
  VALUES 
    ('SRID=4326;POINT(-120.26 35.38)'::geometry),
    ('SRID=4326;POINT(-98.24 25.74)'::geometry),
    ('SRID=4326;POINT(-36.01 -8.23)'::geometry),
    ('SRID=4326;POINT(131.13 -26.11)'::geometry),
    ('SRID=4326;POINT(64.03 76.25)'::geometry),
    ('SRID=4326;POINT(30.99 27.46)'::geometry)
)
SELECT 
 ST_AsGeoJSON(
  ST_Collect(
    ST_Transform(
      ST_Segmentize(
        ST_MakeLine(
          -- Transforming from 4326 to 53027
          ST_Transform('SRID=4326;POINT(12.62 52.95)'::geometry, 53027),
          ST_Transform(arr.point, 53027)),
        100000
     ),4326))) -- Here you can transform it to the SRS of your choice, e.g. WGS84
FROM airports arr;

在此处输入图像描述

演示:db<>fiddle

于 2021-04-05T11:38:04.037 回答