Google 发布了Google Places Autocomplete,正好解决了这个问题。
在页面底部把这个扔进去:
<script defer async src="//maps.googleapis.com/maps/api/js?libraries=places&key=(key)&sensor=false&callback=googPlacesInit"></script>
(key) 是您的 API 密钥。
我们已经设置了我们的代码,因此您可以标记一些字段以处理预输入并由该预输入填充,例如:
<input name=Address placeholder=Address />
<input name=Zip placeholder=Zip />
等
然后您初始化它(通常在 Google Places API 加载之前,因为这将异步着陆):
GoogleAddress.init('#billing input', '#shipping input');
或其他。 In this case it's tying the address typeahead to whatever input has name=Address in the #billing tag and #shipping tag, and it will fill in the related fields inside those tags for City, State, Zip etc when an address is chosen.
设置类:
var GoogleAddress = {
AddressFields: [],
//ZipFields: [], // Not in use and the support code is commented out, for now
OnSelect: [],
/**
* @param {String} field Pass as many arguments as you like, each a selector to a set of inputs that should use Google
* Address Typeahead via the Google Places API.
*
* Mark the inputs with name=Address, name=City, name=State, name=Zip, name=Country
* All fields are optional; you can for example leave Country out and everything else will still work.
*
* The Address field will be used as the typeahead field. When an address is picked, the 5 fields will be filled in.
*/
init: function (field) {
var args = $.makeArray(arguments);
GoogleAddress.AddressFields = $.map(args, function (selector) {
return $(selector);
});
}
};
上面的脚本 sn-p 将异步调用一个名为 googPlacesInit 的函数,因此其他所有内容都包含在该名称的函数中:
function googPlacesInit() {
var fields = GoogleAddress.AddressFields;
if (
// If Google Places fails to load, we need to skip running these or the whole script file will fail
typeof (google) == 'undefined' ||
// If there's no input there's no typeahead so don't bother initializing
fields.length == 0 || fields[0].length == 0
)
return;
设置自动完成事件,并处理我们总是使用多个地址字段的事实,但 Google 希望将整个地址转储到单个输入中。肯定有办法适当地防止这种情况发生,但我还没有找到。
$.each(fields, function (i, inputs) {
var jqInput = inputs.filter('[name=Address]');
var addressLookup = new google.maps.places.Autocomplete(jqInput[0], {
types: ['address']
});
google.maps.event.addListener(addressLookup, 'place_changed', function () {
var place = addressLookup.getPlace();
// Sometimes getPlace() freaks out and fails - if so do nothing but blank out everything after comma here.
if (!place || !place.address_components) {
setTimeout(function () {
jqInput.val(/^([^,]+),/.exec(jqInput.val())[1]);
}, 1);
return;
}
var a = parsePlacesResult(place);
// HACK! Not sure how to tell Google Places not to set the typeahead field's value, so, we just wait it out
// then overwrite it
setTimeout(function () {
jqInput.val(a.address);
}, 1);
// For the rest, assign by lookup
inputs.each(function (i, input) {
var val = getAddressPart(input, a);
if (val)
input.value = val;
});
onGoogPlacesSelected();
});
// Deal with Places API blur replacing value we set with theirs
var removeGoogBlur = function () {
var googBlur = jqInput.data('googBlur');
if (googBlur) {
jqInput.off('blur', googBlur).removeData('googBlur');
}
};
removeGoogBlur();
var googBlur = jqInput.blur(function () {
removeGoogBlur();
var val = this.value;
var _this = this;
setTimeout(function () {
_this.value = val;
}, 1);
});
jqInput.data('googBlur', googBlur);
});
// Global goog address selected event handling
function onGoogPlacesSelected() {
$.each(GoogleAddress.OnSelect, function (i, fn) {
fn();
});
}
将结果解析为规范的 street1、street2、城市、州/省、邮政编码并非易事。谷歌会根据您在世界上的哪个位置使用不同的标签来区分这些地区,并且作为警告,如果您习惯于美国地址,那么例如在非洲,有些地方不符合您对地址外观的期望。您可以将世界上的地址分为 3 类:
美国相同 - 整个美国和使用类似寻址系统的几个国家/地区
正式地址 - 英国、澳大利亚、中国,基本上是发达国家 - 但他们的地址部分的分解方式/当地书写方式存在很大差异
没有正式地址 - 在未开发地区,没有街道名称,更不用说街道编号,有时甚至没有城镇/城市名称,当然也没有邮政编码。在这些位置,您真正需要的是 GPS 位置,此代码不处理。
此代码仅尝试处理前两种情况。
function parsePlacesResult(place) {
var a = place.address_components;
var p = {};
var d = {};
for (var i = 0; i < a.length; i++) {
var ai = a[i];
switch (ai.types[0]) {
case 'street_number':
p.num = ai.long_name;
break;
case 'route':
p.rd = ai.long_name;
break;
case 'locality':
case 'sublocality_level_1':
case 'sublocality':
d.city = ai.long_name;
break;
case 'administrative_area_level_1':
d.state = ai.short_name;
break;
case 'country':
d.country = ai.short_name;
break;
case 'postal_code':
d.zip = ai.long_name;
}
}
var addr = [];
if (p.num)
addr.push(p.num);
if (p.rd)
addr.push(p.rd);
d.address = addr.join(' ');
return d;
}
/**
* @param input An Input tag, the DOM element not a jQuery object
* @paran a A Google Places Address object, with props like .city, .state, .country...
*/
var getAddressPart = function(input, a) {
switch(input.name) {
case 'City': return a.city;
case 'State': return a.state;
case 'Zip': return a.zip;
case 'Country': return a.country;
}
return null;
}
旧答案
ArcGis/ESRI 有一个有限的 typeahead 解决方案,该解决方案是有效的,但只有在相当多的输入后才返回有限的结果。这里有一个演示:
http://www.esri.com/services/disaster-response/wildlandfire/latest-news-map.html
例如,您可能会输入 1600 Pennsylvania Ave,希望在您输入“1600 Penn”时获得白宫,但必须到达“1600 pennsylvania ave,washington dc”才能响应该地址。不过,在节省时间方面,它可能会给用户带来一点好处。