避免使用Google Maps地理编码限制?


32

我正在创建一个自定义Google地图,该地图具有通过cms绘制的125个标记。加载地图时,我收到以下消息:

地址解析失败的原因如下:OVER_QUERY_LIMIT

我很确定这是对标记进行地理编码的方式。

如何避免这些警告?是否有更有效的方法对结果进行地理编码?

更新:这是我对凯西的答案的尝试,此刻我只是得到一个空白页。

<script type="text/javascript"> 
(function() { 

window.onload = function() { 
 var mc;
// Creating an object literal containing the properties we want to pass to the map 
var options = { 
zoom: 10, 
center: new google.maps.LatLng(52.40, -3.61), 
mapTypeId: google.maps.MapTypeId.ROADMAP 
}; 

// Creating the map 
var map = new google.maps.Map(document.getElementById('map'), options); 

// Creating a LatLngBounds object 
var bounds = new google.maps.LatLngBounds(); 

// Creating an array that will contain the addresses 
var places = []; 

// Creating a variable that will hold the InfoWindow object 
var infowindow; 
mc = new MarkerClusterer(map);
<?php
$pages = get_pages(array('child_of' => $post->ID, 'sort_column' => 'menu_order'));
$popup_content = array();
foreach($pages as $post)
    {
    setup_postdata($post);
    $fields = get_fields(); 
    $popup_content[] = '<p>'.$fields->company_name.'</p><img src="'.$fields->company_logo.'" /><br /><br /><a href="'.get_page_link($post->ID).'">View profile</a>';
    $comma = ", ";
    $full_address = "{$fields->address_line_1}{$comma}{$fields->address_line_2}{$comma}{$fields->address_line_3}{$comma}{$fields->post_code}";
    $address[] = $full_address;
    }
wp_reset_query();
echo 'var popup_content = ' . json_encode($popup_content) . ';';
echo 'var address = ' . json_encode($address) . ';';
?>

var geocoder = new google.maps.Geocoder(); 

var markers = [];

// Adding a LatLng object for each city  
for (var i = 0; i < address.length; i++) { 
    (function(i) { 
        geocoder.geocode( {'address': address[i]}, function(results, status) {
            if (status == google.maps.GeocoderStatus.OK) {
                places[i] = results[0].geometry.location;

                // Adding the markers 
                var marker = new google.maps.Marker({position: places[i], map: map});
                markers.push(marker);
                mc.addMarker(marker);

                // Creating the event listener. It now has access to the values of i and marker as they were during its creation
                google.maps.event.addListener(marker, 'click', function() {
                    // Check to see if we already have an InfoWindow
                    if (!infowindow) {
                        infowindow = new google.maps.InfoWindow();
                    }

                    // Setting the content of the InfoWindow
                    infowindow.setContent(popup_content[i]);

                    // Tying the InfoWindow to the marker 
                    infowindow.open(map, marker);
                });

                // Extending the bounds object with each LatLng 
                bounds.extend(places[i]); 

                // Adjusting the map to new bounding box 
                map.fitBounds(bounds) 
            } else { 
            alert("Geocode was not successful for the following reason: " + status); 
            }

        });

    })(i);

} 
var markerCluster = new MarkerClusterer(map, markers); 
} 
})
(); 
</script> 

只要标记立即加载并且没有违反任何条款和条件,解决方案到底是什么都不重要。


您无需说出为什么或多久进行一次这些查找。您没有尝试对每个页面渲染的相同点进行地理编码吗?如果是这样的话,当位置首次添加到数据库时,您应该只使用api一次执行这些查找服务器端。
彼得

如果您想尝试@彼得的建议,有一个简单的工具,在这里,这将在谷歌电子表格进行地址解析的点。这样一来,您就可以将城市的经纬度写入CMS,因此您无需在运行时使用地址解析器。
斯蒂芬·利德

@Peter实际上,听起来不错。我有点撕裂,因为它会降低我的cms的可用性。如果客户必须找出每个公司的经/纬度,而不是仅仅输入地址,那它就不是很友好。我理解正确吗?
罗布

请参阅我的新答案,有关PHP函数的信息。
彼得

2
@rob FWIW我认为Ragi的方法正确。凯西的回答仍需要您进行地址解析每一个点,每一个地图上绘制时间,这似乎并不为你的静态数据集是必要的
斯蒂芬带领

Answers:


57

像其他所有人一样,我可以为您提供代码答案,但是我认为没有人向您解释您所做的事情根本上是错误的

为什么会遇到此错误?因为每次有人查看您的页面时您都在调用地理编码,所以您没有将结果缓存在数据库中的任何地方!

存在限制的原因是为了防止滥用Google的资源(无论是有意还是无意)-这正是您正在做的事情:)

尽管google的地址解析速度很快,但如果每个人都这样使用它,则将导致服务器停机。Google Fusion Tables存在的原因是为您完成了很多繁重的服务器工作。地理编码和切片缓存是在其服务器上完成的。如果您不想使用它,则应将它们缓存在服务器上。

如果仍然每天2500个请求太少,那么您必须查看Google Maps Premier(付费)许可,该许可每天为您提供100,000个地理编码请求,每年约1万次(这很多-使用服务器端缓存)除非您是一个庞大的网站或正在处理大量数据,否则不会达到此限制。如果没有服务器端缓存并使用当前的方法,则每天只能进行800次网页浏览!

一旦意识到其他提供商会按地理编码收费,您就会了解应该将结果缓存在数据库中。采用这种方法,每页浏览将花费您10美分!

您的问题是,您可以解决Google为您提供的限制吗?当然。只需从其他IP地址发出请求即可。哎呀,您可以通过亚马逊弹性ips代理呼叫,并且始终会有一个新的2500个新分配的呼叫。但是,当然,除了违法(您实际上是在规避Google Maps服务条款赋予您的限制)之外,您还可以进行黑客攻击来掩盖系统中固有的设计缺陷。

那么该用例的正确方法是什么?在调用google geocode api之前,请将其发送到服务器并查询它是否在缓存中。如果不是,请调用地理编码,将其存储在缓存中并返回结果。

还有其他方法,但这应该使您朝正确的方向开始。

更新:从下面的评论中可以看出,您正在使用PHP,因此这是一个有关如何正确执行代码的代码示例(来自Google团队本身的建议)https://developers.google.com/maps/articles/phpsqlsearch_v3


您有没有将其发送到服务器等的示例?
罗布

您的后端CMS是什么?(我假设您可以/知道如何进行更改,只需要一个简单的示例来展示如何用您选择的语言进行操作。您不需要客户端js的示例,还是您呢?您使用的是任何js吗?像jquery这样的框架?
Ragi Yaser Burhum 2011年

此外,您将需要首先提供有关标记数据如何进入系统的更多信息
Ragi Yaser Burhum 2011年

1
好。那么 Google文档中的这一部分应包括mysql和php部分code.google.com/apis/maps/articles/phpsqlgeocode.html
Ragi Yaser Burhum 2011年

1
有关允许哪种缓存的详细信息,请参见10.1.3(b)。developers.google.com/maps/terms#section_10_12
Paul Ramsey 2013年

14

我认为Sasa在这两个方面都是对的。

就一次发送所有地址而言,一种选择是间隔发送请求。过去,在使用JavaScript时,我选择使用来将请求延迟0.25(似乎可以正常工作!)秒。

setTimeout( [FUNCTION CALL] , 250 )

方法。

在.NET中,我选择了:

System.Threading.Thread.Sleep(250);

似乎可以工作。

编辑:不能真正测试它,但这应该/可能工作!

JavaScript示例。addressArray包含的字符串是地址...

for (var i = 0; i < addressArray.length; i++0 
{

setTimeout('googleGeocodingFunction(' + addressArray[i] + ')' , 250);

}

编辑:

for (var i = 0; i < address.length; i++) {
    function(i) {
        setTimeout(geocoder.geocode({ 'address': address[i] }, function(results, status) {
            if (status == google.maps.GeocoderStatus.OK) {
                places[i] = results[0].geometry.location;

                var marker = new google.maps.Marker({ position: places[i], map: map });
                markers.push(marker);
                mc.addMarker(marker);
                google.maps.event.addListener(marker, 'click', function() {
                    if (!infowindow) {
                        infowindow = new google.maps.InfoWindow();
                    }

                    // Setting the content of the InfoWindow
                    infowindow.setContent(popup_content[i]);

                    // Tying the InfoWindow to the marker 
                    infowindow.open(map, marker);
                });

                // Extending the bounds object with all coordinates 
                bounds.extend(places[i]);

                // Adjusting the map to new bounding box 
                map.fitBounds(bounds)
            } else {
                alert("Geocode was not successful for the following reason: " + status);
            }
        }), 250);// End of setTimeOut Function - 250 being a quarter of a second. 
    } 
}

谢谢回复。如何将其纳入我的代码。这有点新,因此需要一些帮助。您应该能够查看我的源代码。
罗布

哪位?您使用什么语言?
CatchingMonkey

我正在使用javascript,如何设置时间间隔?
罗布

编辑了我的答案。
CatchingMonkey

也意识到我的意思是setTimeout()...凯西。
CatchingMonkey

9

听起来您正在达到Google施加的同时请求限制(尽管我找不到关于该限制实际是什么的参考)。您将需要对请求进行间隔,以免一次发送125个请求。请注意,每天还有2500个地址解析限制。

有关更多信息,请查阅Google地理编码策略文档。

更新:由于Mapperz的帖子,作为一种附加的解决方案,您可以考虑创建一个新的Google Fusion Table,将您的地址和所有相关数据存储在表中,并通过其Web界面对该表进行地理编码。Fusion Table发出的地址解析数量有一个限制,但是这个限制很大。达到上限后,您需要重新提交地址解析请求,Fusion Tables将从上次停止的地方开始提取。这种方法的好处是可以大大提高速度,因为您只需要对ONCE进行地理编码,而在您当前的解决方案中,您将需要对每个负载进行地理编码,从而迅速达到每日限制。


我猜想我会达到极限,我对此很陌生,以为我需要一个代码示例来提供帮助。
罗布

我一直在研究Google Fusion Tables,并遇到了一些问题。1.您必须在表格中手动对地址进行地理编码(通过file-> geocode),这可以自动完成吗?2.一些标记仅在某些缩放级别出现,而其他标记则显示任何缩放级别。这是示例-mediwales.com/mapping/example。我最终要做的是将我的Wordpress数据(即所有公司地址)导出到融合表中,对这些地址自动进行地理编码,然后获取所有这些经过地理编码的地址并立即绘制地图。
罗布

7

这是我在javascript中限制地址解析请求的方式,地址是一个数组,其中包含要进行地址解析的每个地址:

for (i=0;i<=addresses.length;i++) {
    setTimeout( function () {
            geocoder.geocode( { 'address': addresses[i]}, function(results, status) {

                if (status == google.maps.GeocoderStatus.OK) { 

                    //create and add marker to map based off geocode result
                    var marker = new google.maps.Marker({  
                        map: map,
                        title: results[0].formatted_address,
                        position: results[0].geometry.location
                    });

                 } //EDIT, was missing the closing bracket here
            });

    }, i * 1500);
}

这实际上在每个地理编码请求之间增加了一半。


感谢您的回答。尝试集成到我的代码中时,我一直得到空白页-mediwales / mapping
Rob Rob

您是否正在使用任何可能指出错误的调试工具?如果不是,请尝试使用FireFox插件firebug。在这种情况下可以非常方便。
Casey

我不是 我不确定是否将代码放在正确的位置,我将尝试进行更新。
罗布

我在代码示例中缺少右括号。我编辑了答案并添加了评论。但是,看起来这不是示例代码中的问题。您得到的是空白页还是空白的地图?空白页表示javascript代码中存在语法错误。空白地图(无标记)将表明地理编码代码存在问题。
Casey

1
你好 您的回答我又去了。延迟现在有效,但无法绘制标记。从我的代码中,这些是绘制标记的重要位-(function(i){和})(i); 没有这些,即使不增加延迟也不会绘制。
罗布

5

我现在已经尝试了网页,它似乎是工作现在

虽然我认为您当前使用的计时器方法是正确的,而且据我所知,唯一可以使用而无需服务器端支持的计时器方法,但我应该建议以下更改(我重写了地理编码功能)利用localStorage的优势。

LocalStorage是大多数浏览器中内置的一项相对较新的技术,它允许网页在客户端上存储数据。它在某种程度上类似于cookie,但是功能更强大,并且并不总是重新发送到服务器(与cookie不同)。

我的目的是在客户端上保存经过地理编码的地址,这样,如果用户刷新页面,就无需在其上再次调用Google的地理编码器功能。下面的实现是粗略的(我们应该添加代码来检查过时的记录,并且可能需要比索引更好的缓存键),但应该足以使您入门。希望能帮助到你。

(function() { 

window.onload = function() { 
 var mc;
// Creating an object literal containing the properties we want to pass to the map 
var options = { 
zoom: 0, maxZoom: 0, 
center: new google.maps.LatLng(52.40, -3.61), 
mapTypeId: google.maps.MapTypeId.ROADMAP 
}; 

// Creating the map 
var map = new google.maps.Map(document.getElementById('map'), options); 

// Creating a LatLngBounds object 
var bounds = new google.maps.LatLngBounds(); 

// Creating an array that will contain the addresses 
var places = []; 

// Creating a variable that will hold the InfoWindow object 
var infowindow; 
mc = new MarkerClusterer(map);
var popup_content = [..redacted..];

var geocoder = new google.maps.Geocoder(); 

var markers = [];

// Adding a LatLng object for each city 
function geocodeAddress(i) {
     // check if localStorage is available (it is now available on most modern browsers)
     // http://html5tutorial.net/tutorials/working-with-html5-localstorage.html
     // NB: this *must* be improved to handle quota limits, age/freshness, etc
     if(localStorage && localStorage['address_'+i]) {
        places[i]=JSON.parse(localStorage['address_'+i]);
        addPlace(i);
     } else {
      geocoder.geocode( {'address': address[i]}, function(results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
            places[i] = results[0].geometry.location;
            if(localStorage) {
               // cache result locally on the browser, this will help reducing the number of requests
               // to the google geocoder in case the user refreshes the page
               // remember: the more he will refresh, the more he's likely to hit the limit
               // (this is one case where refreshing or closing the browser does not work)
               localStorage['address_'+i]=JSON.stringify(results[0].geometry.location);
            }

            addPlace(i);
        } else { 
            console.log("Geocoding of address "+address[i]+" failed");
        }
    })
}

function addPlace(i) {
    // Adding the markers 
    var marker = new google.maps.Marker({position: places[i], map: map});
    markers.push(marker);
    mc.addMarker(marker);

    // Creating the event listener. It now has access to the values of i and marker as they were during its creation
    google.maps.event.addListener(marker, 'click', function() {
        // Check to see if we already have an InfoWindow
        if (!infowindow) {
            infowindow = new google.maps.InfoWindow();
        }

        // Setting the content of the InfoWindow
        infowindow.setContent(popup_content[i]);

        // Tying the InfoWindow to the marker 
        infowindow.open(map, marker);
    });

    // Extending the bounds object with each LatLng 
    bounds.extend(places[i]); 

    // Adjusting the map to new bounding box 
    map.fitBounds(bounds);
}

function geocode() {
    if (geoIndex < address.length) {
        geocodeAddress(geoIndex);
        ++geoIndex;
    }
    else {
        clearInterval(geoTimer);
    }
}
var geoIndex = 0;
var geoTimer = setInterval(geocode, 600);  // 200 milliseconds (to try out)

var markerCluster = new MarkerClusterer(map, markers); 
} 
})
(); 

非常感谢,只要尝试一下其他功能,我们将在一分钟内尝试一下。听起来是一个好主意。
罗布

刚刚尝试了一下(现在已经恢复到以前的状态),它第一次起作用,然后在我刷新时它只显示了一个空白页。
罗布

立即尝试,出现错误,也请注意,为简洁起见,已删除popup_content变量。
unicoletti 2011年

3

使用geopy地理编码器,我可以毫无障碍地对大量地址进行地理编码(我不确定海报的设置,或者他是否可以在项目中包含python依赖项)

这是我的测试:

from geopy import geocoders

g = geocoders.GoogleV3()  #https://geopy.readthedocs.io/en/latest/#geopy.geocoders.GoogleV3

for x in xrange(1,10000):
    print x
    a = g.geocode("AV JOAO NAVES DE AVILA " + str(x) + " UBERLANDIA MG BRASIL")
    print a

结果片段

7595(u'Av。Jo \ xe3o Naves de \ xc1vila,7595-Segismundo Pereira,Uberl \ xe2ndia-Minas Gerais,38408-288,Brazil'((-18.9388154,-48.2220562))

7596(u'Av。Jo \ xe3o Naves de \ xc1vila,7596-Segismundo Pereira,Uberl \ xe2ndia-Minas Gerais,38408-680,Brazil'((-18.938814,-48.2217423))

7597(u'Av。Jo \ xe3o Naves de \ xc1vila,7597-Segismundo Pereira,Uberl \ xe2ndia-Minas Gerais,38408-288,Brazil'((-18.9388128,-48.2220381))

7598(u'Av。Jo \ xe3o Naves de \ xc1vila,7598-Segismundo Pereira,Uberl \ xe2ndia-Minas Gerais,38408-680,Brazil'((-18.9388114,-48.2217242))

7599(u'Av。Jo \ xe3o Naves de \ xc1vila,7599-Segismundo Pereira,Uberl \ xe2ndia-Minas Gerais,38408-288,Brazil'((-18.9388102,-48.2220201))

7600(u'Av。Jo \ xe3o Naves de \ xc1vila,7600-Segismundo Pereira,Uberl \ xe2ndia-Minas Gerais,38408-680,Brazil',(-18.9388088,-48.2217062))

它仍在运行,我得到7k的结果。

编辑:过了一会儿,我撞到了街的尽头,谷歌开始给我同样的观点,但看起来它仍然在提出要求并给我准确的答案。


Geopy不是魔术,使用限制仍然适用。
alphabetasoup

1

这个php函数(我不知道您使用的是哪种语言,但是可以确定使用它来区分它)使用google geolocator api。给定一个地址,它将返回规范化的地址和latlong。HTH。

// GEOLOCATEBYADDRESS
// @arg (string)address
// @return (array) ((int)flag,(array)address,array(rawresponse))

function geolocatebyaddress($lookupaddress){

    $lookupaddress=trim("$lookupaddress New Zealand");
    $lookupaddress=urlencode($lookupaddress);

    //send off google api lookup request
    $apiurl="http://maps.google.com/maps/api/geocode/json";
    $apiurl.="?address=$lookupaddress";
    $apiurl.="&region=NZ";
    $apiurl.="&sensor=false";

    if (function_exists("curl_init")) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $apiurl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $json = curl_exec($ch);
        curl_close($ch);
    }
    else     $json= file_get_contents($apiurl);

    //process response
    $response= json_decode($json,true);
    $approxflag=0;
    if ($response['status']=='OK' and count($response['results'])) {

        $aa= array();
        foreach($response['results'] as $cc=>$result) {
            //set keys
            foreach(array('street_number','road','suburb','city','postcode','region','lat','long') as $t){
                $aa[$cc][$t]='';
            }
            if ($result['geometry']){
                $aa[$cc]['lat']= $result['geometry']['location']['lat'];
                $aa[$cc]['long']=$result['geometry']['location']['lng'];
         }

            if (count($result['address_components'])){
                foreach ($result['address_components'] as $acs){
                    if ($acs['types'][0]=='street_number')               $aa[$cc]['street_number']= $acs['long_name']; 
                    if ($acs['types'][0]=='route')                       $aa[$cc]['road']=      $acs['long_name']; 
                    if ($acs['types'][0]=='sublocality')                 $aa[$cc]['suburb']=   $acs['long_name']; 
                    if ($acs['types'][0]=='locality')                    $aa[$cc]['city']=    $acs['long_name']; 
                    if ($acs['types'][0]=='postal_code')                 $aa[$cc]['postcode']= $acs['long_name']; 
                    if ($acs['types'][0]=='administrative_area_level_1') $aa[$cc]['region']=   $acs['long_name']; 
                }    
            }
            //successful?
            if ($result['geometry']['location_type']=='APPROXIMATE') $approxflag++;
            if (isset($result['partial_match']))  $approxflag++;
            if (!$aa[$cc]['street_number'])         $approxflag++;
        }
        if ($approxflag) return (array(1,$aa,$response));
        else return (array(2,$aa,$response));
    }
    else return (array(0,array(),$response));
}    

感谢您的答复,我如何将其合并到我的代码中?
罗布

对不起,我想您是在对该网站进行编码。该函数用作服务器端脚本的一部分,例如,该脚本获取用户输入地址,并使用google geocoder api获取坐标。然后,您可以将这些坐标放置在数据库中,当您需要生成地图时,只需查询坐标并使用标记生成地图即可。这样一来,您就不会为每个页面渲染的内容滥用地理编码器。(您是否贬低了我的评论?我试图提供帮助,对我进行惩罚不会太鼓励我)。我也不得不提及API要求您将其与gmap一起使用。
彼得

1

刚才看到了您的查询。您可以尝试以下代码。在地址解析状态检查其他部分,递归调用相同的函数,但要间隔一段时间以丢失地址。

function spotPosition(address) {
        geocoder.geocode({ 'address': address }, function (results, status) {
            if (status == google.maps.GeocoderStatus.OK) {
                var lat = results[0].geometry.location.lat();
                var lang = results[0].geometry.location.lng();
                setMarker(lat, lang);
            }
            else if (status == google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
                setTimeout(function () {
                    //Recursively calling spotPosition method for lost addresses
                    spotPosition(address);
                }, 1000);
            }
        });
    }

0

这是我的解决方案:

依赖项:Gmaps.js,jQuery

var Maps = function($) {
   var lost_addresses = [],
       geocode_count  = 0;

   var addMarker = function() { console.log('Marker Added!') };

   return {
     getGecodeFor: function(addresses) {
        var latlng;
        lost_addresses = [];
        for(i=0;i<addresses.length;i++) {
          GMaps.geocode({
            address: addresses[i],
            callback: function(response, status) {
              if(status == google.maps.GeocoderStatus.OK) {
                addMarker();
              } else if(status == google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
                lost_addresses.push(addresses[i]);
              }

               geocode_count++;
               // notify listeners when the geocode is done
               if(geocode_count == addresses.length) {
                 $.event.trigger({ type: 'done:geocoder' });
               }
            }
          });
        }
     },
     processLostAddresses: function() {
       if(lost_addresses.length > 0) {
         this.getGeocodeFor(lost_addresses);
       }
     }
   };
}(jQuery);

Maps.getGeocodeFor(address);

// listen to done:geocode event and process the lost addresses after 1.5s
$(document).on('done:geocode', function() {
  setTimeout(function() {
    Maps.processLostAddresses();
  }, 1500);
});

0

您无法避免Google Map地理编码限制:您必须获取并存储LatLng信息。

如果您有多个城市,并且不想手动获取LatLng,则可以使用超时来逐步调用geocoder.geocode并将信息保存在javascript对象中,然后用于console.log(JSON.stringify(object, null, 4));在控制台中获取漂亮的打印对象,因此例

            var locationsPosition = {
                    "Johannesburg - ZA": {
                        "lat": -26.2041028,
                        "lng": 28.047305100000017
                    },
                    "Chennai - IN": {
                        "lat": 13.0826802,
                        "lng": 80.27071840000008
                    }
             }

所以现在您可以使用locationsPosition["Johannesburg - ZA"].lat和添加标记.lng


-1

听起来您需要执行以下两项操作之一:

  1. 如果您每天没有太多独特的结果,则更改您的过程以缓存结果;要么
  2. 升级到可以每天处理更多服务的服务

正如人们多次提到的那样,但我没有任何亲身经历,看来每天10万美元的Google许可每年大约需要1万美元。

如果您希望达到相同水平或更高的准确性(而不必为此付出代价),则可以尝试我们的服务。以比Google低得多的价格(几乎是成本的十分之一),您可以获得远远高于2500的限额(我们的最低限额为每天25,000)。

当然,请使用我们的付费地理编码API不是我所建议的100%诚实的解决方案,尽管承认这样做很不好。相反,我建议选择选项1,优化服务以进行缓存。除非您每天需要超过2500个UNIQUE地理编码。

免责声明:我为Geocode Farm(我推荐的服务)工作。


为什么投票失败?我看不到这里的任何地方不正确或不正确?
Geocode.Farm Staff '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.