有没有办法在 Google 地图 API 的路线中找到城市?

Is there a way of finding cities within a route with Google Maps API?

有没有办法获取DirectionsService.route()追踪的路线经过的城市?

例如,在https://goo.gl/maps/trHkPUNzuDFEjYT27中的路线中,属于圣保罗(起点)、Anhanguera、Cajamar、Jundiai(其他...)和坎皮纳斯(终点)等城市的道路。

如果我们在DirectionsService.route()方法中输入起点和终点,我们会得到一个leg的列表,其中包括道路、里程和行驶时间,但不包括城市他们属于。

有没有办法在不调用额外的 API 的 的情况下 获取此数据?在考虑地图时,成本是一个重要问题 API。

编辑:阐明了解决方案不应涉及 额外的 调用。 This 解决方案并不比为路线的每一段调用 PlacesService 好多少,因为它只是将路线的一部分装箱,并且无论如何都会调用它们。

我的建议是简单地放弃对所有内容使用 Google API 的方法。毫无疑问,它是最好的导航工具,也正是因为这个原因,它才那么贵。因此,我建议使用其他一些不通过 Google 的地理编码方法,特别是如果您只是在寻找大城市(如您的示例中所示)。有一些 'free' API 已经存在(事实上,它们通常从来都不是真正免费的)——如果你是无服务器的,我只建议这样做。在那种情况下,我会选择 Nominatim - 它没有上限(有点,参见 operations.osmfoundation.org/policies/nominatim - 你 可以 向它发送垃圾邮件,但它是气馁),没有 API 密钥,并且完全免费 - 当然,唯一的问题是,正如您提到的,您必须遍历每个点并向 API 发出请求,这会花很多时间。但是,我会这样做:

let zoom = 12; // Adjust to your needs: see nominatim.org/release-docs/develop/api/Reverse. Higher numbers will result in more places being matched, while lower numbers will result in faster execution.

let coords = [];

const stepList = [];

Array.from(googleResponse.routes[0].legs).forEach(leg => {
  stepList.push(...leg.steps);
});

stepList.forEach(e => {
  coords.push([e.endLocation.lat, e.endLocation.long]);
});

coords.push([legList[0].startLocation.lat, legList[0].startLocation.long]);

let arr = [];
let promises = [];
let bboxes = [];

const loopOn = (i, cb) => {
  const coord = coords[i];
  const nextLoop = () => {
    i+1 < coords.length? loopOn(i+1, cb) : cb();
  }

  let makeRequest = true;
  for (let bbox of bboxes) {
    if (coord[0] >= bbox[0] 
        && coord[0] <= bbox[1] 
        && coord[1] >= bbox[2] 
        && coord[1] <= bbox[3]){ // If it is within a bounding box we've already seen
          makeRequest = false; // there's no need to geocode it, since we already roughly know it's in an area we have already saved.
          break;
        }
  }

  if (makeRequest){
    var request = $.ajax({
      url: `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${coord[0]}&lon=${coord[1]}&zoom=${zoom}`,
      type:'GET',
      dataType: 'jsonp',
      success: resp => {
        thisPlace = resp.address.city || resp.address.town || resp.address.village;
        thisPlace && arr.indexOf(thisPlace) === -1 && arr.push(thisPlace);
        bboxes.push(resp.boundingbox);
        nextLoop();
      }
    });
  } else {
    nextLoop();
  }
};

loopOn(0, () => {
  /*The rest of your code*/
});

这段代码简单地遍历了每条路段(我假设 googleResponse 是来自方向 API 的未过滤但 JSON 化的响应,并从 Nominatim 请求它。我已经把它变成了使用 Nominatim 的边界框更有效,return 每个 city/village 区域周围的矩形,所以如果 instruction/step 只是转弯,我们不需要发出请求在同一个 square/suburb/city district/city 中(这可以使用 zoom 变量定义)。

问题在于,Nominatim 是免费的且未经过优化,显然不是最快的 API。即使 Google 的服务器 运行 连接速度较慢,他们 仍然会 更快,因为他们已经将产品优化为 运行更快,使用一些低级代码。同时,Nominatim 只是从文件中进行基本查找(没有彩虹散列等),因此它必须手动缩小范围。

解决方案是使用自定义数据集。显然,这需要一个后端来存储它,因为在每次加载时将整个 CSV 下载到前端将花费数小时(并且在每次重新加载时!)。为此,您真正需要做的就是将 AJAX API 请求替换为对 csv-parser 模块(或任何其他解析函数)的调用,其工作方式大致相同关于 promises/async,所以您可以直接用他们网站上的示例替换代码:

let resp = [];

fs.createReadStream(<your file here.csv>)
  .pipe(csv())
  .on('data', (data) => resp.push(data))
  .on('end', () => {
    results = search(resp, coords);
    thisPlace = results.address.city || results.address.town || results.address.village;
    thisPlace && arr.indexOf(thisPlace) === -1 && arr.push(thisPlace);
    nextLoop();
  });

此外,您可以删除边界框代码,因为您不再需要节省请求时间。

然而,像这样后置运行会更快:

let resp = [];

fs.createReadStream(<your file here.csv>)
  .pipe(csv())
  .on('data', (data) => resp.push(data))
  .on('end', () => {
    let coords = [];

    const stepList = [];

    Array.from(googleResponse.routes[0].legs).forEach(leg => {
      stepList.push(...leg.steps);
    });

    stepList.forEach(e => {
      coords.push([e.endLocation.lat, e.endLocation.lng]);
    });

    coords.push([legList[0].startLocation.lat, legList[0].startLocation.lng]);

    let arr = [];
    let promises = [];
    let bboxes = [];

    coords.forEach(coord => {
      let results = search(coords);

      let thisPlace = results.address.city || results.address.town || results.address.village;
      thisPlace && arr.indexOf(thisPlace) === -1 && arr.push(thisPlace);
    };

    /*The rest of your code*/
});

接下来我们需要的是实际的 search 函数,这是比较复杂的部分。我们需要找到一些既快速又基本正确的方法。实际实施取决于文件的格式,但这里是我将要执行的操作的简要说明 运行:

  • 创建两个 resp 的副本,但按经度排序一个(我们称此数组为 a_o),另一个按纬度排序(a_a)。确保在定义这些数组时不使用 varletconst,只需...定义它们。
  • 对于每一个,删除不在 a_o 的经度轴上的点 25 公里(半径)范围内的任何东西,a_a
  • 的相同但具有纬度
  • delete 两个数组以清除它们在 RAM 中占用的 space。
  • 找到两个数组中的任何项目,并将它们放入名为 a_c
  • 的数组中
  • 过滤掉彼此之间 3-4 公里范围内的任何项目(但请确保保留其中一个点,不要同时删除两个点!)
  • 遍历每个项目,并计算出到该点的绝对距离(使用 this 算法 - 请记住,地球是一个球体,基本的勾股定理 而不是 工作!
  • 如果您找到距离小于 20 公里且附有城市或村庄或城镇的任何项目,请打破并 return 名称。
  • 如果你完成了,即永不中断,return undefined

除此之外,您几乎可以使用任何包含以下内容的 CSV:

  • 城市名称
  • 中央纬度
  • 中央经度

希望对您有所帮助。总之,如果您每小时只做 5-6 条简单的路线,请使用第一种经过试验和测试的预制方法。如果您有数以亿计的 waypoints,请下载数据并花半小时左右的时间制作您自己的地理编码系统。性能提升是值得的。