有没有办法在 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
)。确保在定义这些数组时不使用 var
或 let
或 const
,只需...定义它们。
- 对于每一个,删除不在
a_o
的经度轴上的点 25 公里(半径)范围内的任何东西,a_a
的相同但具有纬度
delete
两个数组以清除它们在 RAM 中占用的 space。
- 找到两个数组中的任何项目,并将它们放入名为
a_c
的数组中
- 过滤掉彼此之间 3-4 公里范围内的任何项目(但请确保保留其中一个点,不要同时删除两个点!)
- 遍历每个项目,并计算出到该点的绝对距离(使用 this 算法 - 请记住,地球是一个球体,基本的勾股定理 而不是 工作!
- 如果您找到距离小于 20 公里且附有城市或村庄或城镇的任何项目,请打破并 return 名称。
- 如果你完成了,即永不中断,return
undefined
。
除此之外,您几乎可以使用任何包含以下内容的 CSV:
- 城市名称
- 中央纬度
- 中央经度
希望对您有所帮助。总之,如果您每小时只做 5-6 条简单的路线,请使用第一种经过试验和测试的预制方法。如果您有数以亿计的 waypoints,请下载数据并花半小时左右的时间制作您自己的地理编码系统。性能提升是值得的。
有没有办法获取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
)。确保在定义这些数组时不使用var
或let
或const
,只需...定义它们。 - 对于每一个,删除不在
a_o
的经度轴上的点 25 公里(半径)范围内的任何东西,a_a
的相同但具有纬度
delete
两个数组以清除它们在 RAM 中占用的 space。- 找到两个数组中的任何项目,并将它们放入名为
a_c
的数组中
- 过滤掉彼此之间 3-4 公里范围内的任何项目(但请确保保留其中一个点,不要同时删除两个点!)
- 遍历每个项目,并计算出到该点的绝对距离(使用 this 算法 - 请记住,地球是一个球体,基本的勾股定理 而不是 工作!
- 如果您找到距离小于 20 公里且附有城市或村庄或城镇的任何项目,请打破并 return 名称。
- 如果你完成了,即永不中断,return
undefined
。
除此之外,您几乎可以使用任何包含以下内容的 CSV:
- 城市名称
- 中央纬度
- 中央经度
希望对您有所帮助。总之,如果您每小时只做 5-6 条简单的路线,请使用第一种经过试验和测试的预制方法。如果您有数以亿计的 waypoints,请下载数据并花半小时左右的时间制作您自己的地理编码系统。性能提升是值得的。