谷歌地图V3如何在合理的时间内渲染超过一百万个标记?

10

我最近使用 API 的 V3 版本(最新版本)创建了一个谷歌地图。我的一个要求是能够以合理的时间内渲染超过 1 百万个标记,这样的时间应该在 15 秒之内。

我知道一次性渲染所有 1 百万个标记是相当不可行的,因此我研究了性能选项。我找到并使用了 MarkerClusterer 这个选项:https://developers.google.com/maps/articles/toomanymarkers

然而,我现在开始在测试使用超过 100,000 个标记时出现性能问题,渲染地图和标记需要很长时间(超过 1 分钟)。最终,我设法使页面崩溃,大约有 200,000 个标记。

是否有任何方法可以在使用这么多标记时提高地图的性能?

非常感谢您的帮助。


1
你可以考虑使用FusionTablesLayer,用圆圈代替原来的标记。不需要让所有标记都有响应 :) 我没有测试过,但我认为这将是一个更快的方法。 - davidkonrad
我会认真考虑这些。我已经快速查看了文档,似乎数据由Google保存以便进行处理。这很好,但我需要确认是否可以在没有任何手动干预的情况下自动更新。 - Jiminy
3个回答

13

我曾经面临着在地图上显示超过一百万个点的类似挑战。

服务端使用了ElasticSearch集群,客户端则采用了Leaflet集群技术。

这可能会对你有所帮助。

演示请点击这里

查看代码请点击这里


1

我成功使用了Google Fusion Tables,它非常快速和简单 - 一旦你弄清楚如何使用OAuth2....

这些表格每个限制为100,000条目,您可以通过CSV文件上传它们 - 要么通过在浏览器中转到您的Google Drive,要么使用curl或Perl进行程序化操作。

要超越100,000个元素的限制,您可以向地图添加5个图层,但仍然只能达到500,000个点。无法提供更多建议。

如果您想看一下,我的项目在这里


能否请您详细说明一下或者提供一个链接,告诉我如何做呢?我对JavaScript还比较新手,这正是我所面临的问题。 - Naman
@Naman 看一下这里:http://stackoverflow.com/questions/22096497/is-it-possible-to-select-which-data-to-display-in-google-maps-info-windows-using/22096764#22096764 - Mark Setchell
谢谢提供链接,但实际上我不知道如何使用Google Fusion Table以及如何设置Fusion Table。所以,如果您能提供相关资源,那就太好了,这将是真正的帮助。我非常感谢您花时间阅读。 - Naman
另外,您能否就另一个问题提出建议?我有大约20,000个标记,但它们不是静态的。用户提供一个文件,然后我在我的JavaScript文件中处理它,然后得到位置。如果我在代码初始化时将所有条目添加到融合表中,这样会快吗?还是应该只保留数据静态在融合表中进行此操作? - Naman
Google Fusion Tables服务将于2019年12月3日结束。 - Shahbaz Shueb
@ShahbazShueb 伤心但却是事实。如果有人知道在地图上绘制数十万个点的替代方法,请评论并添加链接!谢谢。 - Mark Setchell

0

另一个选择是热力图。

https://developers.google.com/maps/documentation/javascript/heatmaplayer

有些事情我必须提醒自己(我注意到原始问题是来自2014年),那就是JavaScript很快。实际上,经常当我认为JS有问题时,其实往往是DOM的问题。

在2022年,在一个常量中保存1,000,000个数据点并不算不合理。然后问题就变成了渲染问题。我发现,热力图只要数据不变,就可以非常快速地渲染。

不过,即使到了2022年,我也难以相信您可以在不到15秒的时间内加载1,000,000条记录。我想,如果用户登录仪表板并开始异步加载,一旦用户到达页面,它会看起来比较快。许多聪明的应用程序在幕后聪明地预加载,利用了速度感知。

使用这种技术的方法是为当前边界+ 100英里半径呈现标记/热力图。然后随着用户的缩放/拖动,您可以按需切分1,000,000个项目数组。

/**
 * A lot of assumptions are being made about the backend. There are various
 * technics you can use to speed up loading, my favorite being MongoDB and
 * $geoWithin and loading multiple pages at the same time.
 */
(async function () {
    let Points = [];
    let Bounds = {};
    let Map;
    let Heatmap;
    let BoundTimeout;

    async function getPoints(page = 1) {
        let response;

        try {
            response = await fetch(myRestUrl, { body: JSON.stringify(Bounds) });
        } catch (e) {
            console.error(e);
        }

        /**
         * If you pass in page, you can return a total in the response. Then, based
         * on your perpage setting you can deduce the total number of pages you
         * need. Throw that in a for loop and add to your Points array.
         *
         * In this example, assuming one phat response...
         */

        if (response) {
            Points = response;
        }
    }

    function generateGoogleMap() {
        // Assumes your map is #map.
        Map = new google.maps.Map(document.getElementById('map'), {
            center: new google.maps.LatLng(37.782, -122.447),
            zoom: 13,
            mapTypeId: 'satellite',
        });

        Map.addListener('bounds_changed', BoundChange);
        BoundChange();
    }

    function BuildHeatmap() {
        if (Heatmap) {
            Heatmap.setMap(null);
            Heatmap = null;
        }

        Heatmap = new google.maps.visualization.HeatmapLayer({
            // Assuming you're storing your points as GeoJSON...
            data: Points.map(
                (P) => new google.maps.LatLng(P.coordinates[1], P.coordinates[0])
            ),
        });

        Heatmap.setMap(Map);
    }

    /**
     * Populates the Bounds variable with a GeoJSON rectangle for Point lookup.
     */
    function BoundChange() {
        clearTimeout(BoundTimeout);
        const MapBounds = Map.getBounds();
        const sw = MapBounds.getSouthWest();
        const ne = MapBounds.getNorthEast();

        Bounds = {
            type: 'Polygon',
            coordinates: [
                [
                    [sw.lng(), sw.lat()],
                    [ne.lng(), sw.lat()],
                    [ne.lng(), ne.lat()],
                    [sw.lng(), ne.lat()],
                    [sw.lng(), sw.lat()],
                ],
            ],
        };

        BoundTimeout = setTimeout(async () => {
            await getPoints();
            BuildHeatmap();
        }, 1000);
    }

    /**
     * Preload the Points. Ideally this would be on an event, state, or tick
     * system but for symplicity we're just waiting for the results.
     */
    await getPoints();

    // Generate the Map.
    generateGoogleMap();
})();

以上示例未考虑100英里半径,但我建议在服务器上进行转换。另一个想法是使用Web套接字 - 如果您希望用户将窗口保持打开状态作为仪表板(例如),则可以更快。上面的示例还未考虑库、DOM或加载顺序,因此不能简单地复制和粘贴,仅提供它作为示例。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接