【问题标题】:Posting wrong record information to table将错误的记录信息发布到表中
【发布时间】:2016-06-03 18:56:18
【问题描述】:

在遍历我的客户表并根据地址发布纬度和经度值时,第一组纬度/经度值发布到记录号 1 和 2,然后随后每条记录都关闭一个。当我在调试模式下单步执行时,我看到第一条记录的值在我的第二次迭代中仍然存在。之后,它会自行更正,但每个记录值都是针对其上方的地址或记录的。为什么?

这是我的代码:

procedure TViewMaps.StartBtnClick(Sender: TObject);
var
  iRecs, i : Integer;
  Location : TLocation;
begin
  ViewMaps := TViewMaps.create(self, MapAddress);
  Customer.Open;
  iRecs:= Customer.RecordCount;
  i := 0;
  While Not Customer.EOF do
    begin
      i := i + 1;
      Customer.Edit;
      MapAddress := CustomerSAddress1.AsString + ' ' + CustomerSAddress2.AsString + ' ' + CustomerSAddress3.AsString + ' ' + CustomerSAddress4.AsString + ', ' + CustomerSCity.AsString + ', ' + CustomerSState.AsString + ' ' + CustomerSZip.AsString;

      fAddress := StringReplace(StringReplace(Trim(MapAddress), #13, ' ', [rfReplaceAll]), #10, ' ', [rfReplaceAll]);
      Location := GetGeoCode(fAddress);

      Customerlat.AsString := Location.Lat;
      Customerlng.AsString := Location.Lng;

      StatusBar1.SimpleText:= 'Update Geocode for address ' + ' [Count ' + IntToStr(i) + ' of ' + IntToStr(iRecs) + ']';
      Sleep(3000);
      StatusBar1.Refresh;
      Customer.Next;
    end;
end;

好的,我根据您的建议修改了代码(不确定我的做法是否正确),但得到的结果完全相同。该程序只是一个运行一次的应用程序,用于将 lat/lng 值填充到我们的数据库中,延迟是因为我遇到了 Google 查询限制,并且该应用程序在结果从 Google 返回之前发布到数据库,所以我不得不放慢速度。

这是更新后的代码:

procedure TViewMaps.StartBtnClick(Sender: TObject);
begin
  Customer.Open;
  iRecCount:= Customer.RecordCount;
  iCurRec := 0;
  Customer.First;
  if Not Customer.EOF then Timer1.Enabled := True;
end;

procedure TViewMaps.OnTimer(Sender: TObject);
begin
  iCurRec := iCurRec + 1;
  //ShowMessage('I am here and iCurRec = ' + inttostr(iCurRec));
  Customer.Edit;
  // Load full customer address into MapAddress
  MapAddress := CustomerSAddress1.AsString + ' ' + CustomerSAddress2.AsString + ' ' + CustomerSAddress3.AsString + ' ' + CustomerSAddress4.AsString + ', ' + CustomerSCity.AsString + ', ' + CustomerSState.AsString + ' ' + CustomerSZip.AsString;
  fAddress := StringReplace(StringReplace(Trim(MapAddress), #13, ' ', [rfReplaceAll]), #10, ' ', [rfReplaceAll]);
  // Get Longitude and Latitude from Google Maps
  Location := GetGeoCode(fAddress);
  // Populate lat and lng fields in Customer table
  Customerlat.AsString := Location.Lat;
  Customerlng.AsString := Location.Lng;
  // Post record to Customer table
  Customer.Post;
  StatusBar1.SimpleText:= 'Update Geocode for address ' + ' [Count ' + IntToStr(iCurRec) + ' of ' + IntToStr(iRecCount) + ']';
  StatusBar1.Refresh;
  // Grab the next record in the Customer table
  Customer.Next;
  if Customer.EOF then Timer1.Enabled := False;
end;

好的...这是codeAddress javascript

''+
'  function codeAddress(address) { '+
'    if (geocoder) {'+
'      geocoder.geocode( { address: address}, function(results, status) { '+
'        if (status == google.maps.GeocoderStatus.OK) {'+
'          map.setCenter(results[0].geometry.location);'+
'          var myLatlng = new google.maps.LatLng( results[0].geometry.location.lat(), results[0].geometry.location.lng()); '+
'          var marker = new google.maps.Marker({ '+
'            position: myLatlng, '+
'            title: "", '+
'            map: map '+
'          }); '+
'        markersArray.push(marker); '+
'        document.getElementById("hiddenlat").value = myLatlng.lat(); '+
'        document.getElementById("hiddenlng").value = myLatlng.lng(); '+
' '+
'        } else {'+
'            document.getElementById("hiddenlat").value = "error"; '+
'            document.getElementById("hiddenlng").value = "error"; '+
'           alert("Geocode was not successful for the following reason: " +    status);'+
'        }'+
'      });'+
'    }'+
'  }'+
''+

这是 Delphi 代码:

constructor TViewMaps.create(AOwner: TComponent; AAddress: string);
begin
  inherited create(AOwner);
  fAddress := AAddress; // fAddress is now stored to form variable
end;

procedure TViewMaps.LoadGoogleApi;
var
  aStream: TMemoryStream;
begin
  WebBrowser1.Navigate('about:blank'); //Set the location to an empty page
  MemoAddress.Lines.Text := '1600 Amphitheatre Parkway, Mountain View, CA 94043';
  if Assigned(WebBrowser1.Document) then
  begin
    aStream := TMemoryStream.Create; //create a TStream to load the Page from the string
    try
      aStream.WriteBuffer(Pointer(HTMLStr)^, Length(HTMLStr));
      aStream.Seek(0, soFromBeginning);
      (WebBrowser1.Document as IPersistStreamInit).Load(TStreamAdapter.Create(aStream));
    finally
      aStream.Free;
    end;
    HTMLWindow2 := (WebBrowser1.Document as IHTMLDocument2).parentWindow;
  end;

  while WebBrowser1.ReadyState <> READYSTATE_COMPLETE do // wait for google
  begin
    sleep(0);
    application.processmessages;
  end;
end;

function TViewMaps.GoogleApiReady: boolean;
begin
  result := (HTMLWindow2 <> nil);
end;

procedure TViewMaps.ExecuteScript(AScript: string);
begin
  HTMLWindow2.execScript(AScript, 'JavaScript');
end;

function TViewMaps.GetElementByID(AElementID: string): IHTMLElement;
begin
  result := (WebBrowser1.Document as IHTMLDocument3).getElementByID(AElementID);
end;

function TViewMaps.GetElementValue(ElementID: string): string;
var
  HtmlElement: IHTMLElement;
begin
  HtmlElement := GetElementByID(ElementID);
  result := HtmlElement.getAttribute('value', 0);
end;

procedure RemoveInvalidGeoLookupChars(var AString: string);
begin
  AString := StringReplace(StringReplace(Trim(AString), #13, ' ', [rfReplaceAll]), #10, ' ', [rfReplaceAll]);
  // remove invalid chars
  AString := StringReplace(AString, #39, #32, [rfReplaceAll]);  // single quotes
  AString := StringReplace(AString, #34, #32, [rfReplaceAll]);  // double quotes
end;

procedure TViewMaps.FormShow(Sender: TObject);
var
  Location: TLocation;
begin
  MapAddress := '1600 Amphitheatre Parkway' + ', ' + 'Mountain View' + ', ' + 'CA' + ' ' + '94043';
  ViewMaps := TViewMaps.create(self, MapAddress);
  LoadGoogleApi;
  address := MemoAddress.Lines.Text;
  fAddress := StringReplace(StringReplace(Trim(address), #13, ' ', [rfReplaceAll]), #10, ' ', [rfReplaceAll]);
  Location := GetGeoCode(fAddress);
  LatitudeEdit.Text := Location.Lat;
  LongitudeEdit.Text := Location.Lng;
end;

function TViewMaps.GetGeocode(Address: string): TLocation;
begin
  result.Lat := '0';
  result.Lng := '0';
  LatitudeEdit.text := '0';
  LongitudeEdit.text := '0';
  result.Result := 'OK';
  application.processmessages;
  RemoveInvalidGeoLookupChars(address);
  application.processmessages;
  ExecuteScript(Format('codeAddress(%s)',[QuotedStr(Address)]));

  while (GetElementValue('hiddenlat') = '0') do
    application.processmessages;

  result.Lat := GetElementValue('hiddenlat');
  result.Lng := GetElementValue('hiddenlng');
end;

procedure TViewMaps.StartBtnClick(Sender: TObject);
var
  iRecCount, iCurRec: integer;
  Location: TLocation;
  fAddress, MapAddress: string;
begin
    Customer.open;
    Customer.first;
    iRecCount := Customer.RecordCount;
    iCurRec := 0;
    while not Customer.eof do
    begin
      inc(iCurRec);
      fillchar(Location, sizeof(Location), 0);
      MapAddress := CustomerSAddress1.asstring+' '+CustomerSAddress2.asstring+' '+CustomerSCity.asstring+', '+CustomerSState.asstring+' '+CustomerSZip.asstring;
      fAddress := StringReplace(StringReplace(Trim(MapAddress), #13, ' ', [rfReplaceAll]), #10, ' ', [rfReplaceAll]);
      fillchar(Location, sizeof(Location), 0);
      Location := GetGeocode(fAddress);
      if (Location.lat <> 'error') and (Location.lat <> '0') then
      begin
        Customer.edit;
        CustomerLat.AsString := Location.Lat;
        CustomerLng.AsString := Location.Lng;
        Customer.Post;
      end;
      Statusbar1.SimpleText := 'Update Geocode for address ' + ' [Count ' + IntToStr(iCurRec) + ' of ' + IntToStr(iRecCount) + ']';
      application.processmessages;
      sleep(2000); // adjust to not exceed Google API query limit
      Customer.next;
    end;
end;

【问题讨论】:

  • 我认为这不一定能解决您的问题,但您应该在设置 Customerlng.AsString 后立即显式调用 Customer.Post。更改将在调用 Customer.Next 期间发布,但依赖此操作是不好的做法。至于在你的循环中调用 Sleep ......好吧。
  • @MartynA - 当我达到第二条记录后,我尝试了 Customer.Post 和 Customer.Prior,但没有运气。第一条和第二条记录仍然会得到重复的 lat/lng 结果值,并且之后的每条记录都关闭一个。
  • 抱歉,没有礼貌的说法:这个循环太可怕了——试试我在回答中建议的替代方案之一。至少,它们应该允许更容易地调试任何剩余的问题。
  • 请澄清评论“当我在调试模式下单步执行时,我看到第一条记录的值在我的第二次迭代中仍然存在”。您具体指的是哪些价值观?数据集中 Customer* 字段的值?还是从 GetGeoCode 返回的值相同,即使两次使用不同的地址?
  • 重新编辑,请参阅我的答案的更新 #1 和 #2。 #2尤其应该帮助您确定是否是您引入的延迟以避免过于频繁地调用 GetGeoLocation 这实际上导致了您的问题。

标签: delphi


【解决方案1】:

SO 的用户应该知道这是 OP 先前问题之一的延续 - Getting Latitude Longitude from GoogleMaps in TWebBrowser

我使用下面的代码创建了一个测试。我的客户表字段名称不同,但您会明白的。

请注意尽可能缩短 Customer.edit 和 Customer.post 之间的时间,以避免任何过早发布。此外,请确保 Customer 表没有任何会导致循环期间中断的事件。如果返回了有效的地理编码,则仅编辑/发布。并且为了更好地衡量,位置记录在每次迭代时都会被初始化。

procedure TForm2.StartBtnClick(Sender: TObject);
var
  iRecCount,
  iCurRec: integer;
  Location: TLocation;
  fAddress, MapAddress: string;
begin
    Customer.open;
    Customer.first;
    iRecCount := Customer.RecordCount;
    iCurRec := 0;
    while not Customer.eof do
    begin
      inc(iCurRec);
      fillchar(Location, sizeof(Location), 0);
      MapAddress := CustomerAddress.asstring+' '+CustomerAddress2.asstring+' '+CustomerCity.asstring+', '+CustomerState.asstring+' '+CustomerZip.asstring;
      fAddress := StringReplace(StringReplace(Trim(MapAddress), #13, ' ', [rfReplaceAll]), #10, ' ', [rfReplaceAll]);
      fillchar(Location, sizeof(Location), 0);
      Location := form1.GetGeocode(fAddress);
      if (Location.lat <> 'error') and (Location.lat <> '0') then
      begin
        Customer.edit;
        CustomerLatitude.AsString := Location.Lat;
        CustomerLongitude.AsString := Location.Lng;
        Customer.Post;
      end;
      Statusbar1.SimpleText := 'Update Geocode for address ' + ' [Count ' + IntToStr(iCurRec) + ' of ' + IntToStr(iRecCount) + ']';
      application.processmessages;
      sleep(1000); // adjust to not exceed Google API query limit
      Customer.next;
    end;
end;   

另外,修改您的 codeAddress Javascript 函数以清除隐藏的页面值。

''+
'  function codeAddress(address) { '+
'    document.getElementById("hiddenlat").value = "0"; '+
'    document.getElementById("hiddenlng").value = "0"; '+
'    if (geocoder) {'+
'      geocoder.geocode( { address: address}, function(results, status) { '+
'        if (status == google.maps.GeocoderStatus.OK) {'+
'          map.setCenter(results[0].geometry.location);'+
'          var myLatlng = new google.maps.LatLng( results[0].geometry.location.lat(), results[0].geometry.location.lng()); '+
'          var marker = new google.maps.Marker({ '+
'            position: myLatlng, '+
'            title: "", '+
'            map: map '+
'          }); '+
'        markersArray.push(marker); '+
'        document.getElementById("hiddenlat").value = myLatlng.lat(); '+
'        document.getElementById("hiddenlng").value = myLatlng.lng(); '+
' '+
'        } else {'+
'            document.getElementById("hiddenlat").value = "error"; '+
'            document.getElementById("hiddenlng").value = "error"; '+
'           alert("Geocode was not successful for the following reason: " +    status);'+
'        }'+
'      });'+
'    }'+
'  }'+
''+

【讨论】:

  • 我把这一切都放在一个表单上,所以我删除了对 form1 的引用。不过,我仍然遇到同样的问题,这是从 GetGeocode 函数调用返回的以前的 lat/lng 值。我在调试过程中检查过,我正在将正确的 fAddress 传递给 GetGeocode 函数,但在返回时,“Location”变量包含以前的 lat/lng 值。
  • 您是否对地理编码功能进行了任何代码更改?我无法重现您的问题...
  • @Hackbrew 你检查响应中的状态码吗?您是否可以发布您要求的三个首地址?
  • @John Easley - 我在上面添加了 javascript 和 Delphi 代码。
  • @Hackbrew 你用的是什么数据库?
【解决方案2】:

不确定您在循环中的哪个位置进行这些观察,但在最初在您的 q 中的代码中,对当前记录的更改将在您调用 Customer.Next 之前发布,这将在移动之前发布任何更改表光标移动到下一条记录。但是你不应该依赖这种行为,尤其是你只是在延迟之后才调用 Customer.Next。

试试这个:

  Customerlng.AsString := Location.Lng;
  Customer.Post;

如果事实证明您遇到的问题是由您说您必须引入以避免过于频繁地调用 GetGeoLocation 的延迟造成的,我一点也不感到惊讶。消除这种情况的一种方法是使用原始循环的测试版本,其中没有任何延迟 (Sleep()),并使用替换 GetGeoCode 例程,每次调用它时只返回一些唯一值。这个替换GetGeoCode 例程可以在每次调用记录时简单地增加记录的LatLng 成员并返回更新后的记录。

这个简化的测试版本应该可以正常工作。如果没有,你的代码一定是在做一些你没有告诉我们的事情,只有你可以调试它。另一方面,如果它确实正常工作,您需要想出一个更好的方法来避免过于频繁地调用真正的GetGeoCode。特别是不应该

a) 在您的应用程序的主 gui 线程中调用 Sleep(),这就是您在 q 的原始版本中所做的;和

b) 尝试完全在 OnClick 处理程序中执行。

如果您确实必须引入延迟,请执行类似的操作

  • 将 TTimer 添加到您的表单中,延迟为 3000 或其他。

  • 在您的 StartBtnClick 中,只需调用 Customer.First,检查 Customer.Eof,如果它为假,则激活 tttimer。然后退出。

  • 在其 OnTimer 中,更新当前客户记录,调用 Customer.Post,刷新状态栏,调用 Customer.Next,检查 Customer.Eof 并在 Eof 为真时取消 ttimer,使其 OnTimer 获胜'不会再次被调用。

您应该保护您的 OnTimer 以防止重新进入(即 OnTimer 在它完成执行之前被再次调用)。在表单上有一个布尔标志 UpdateExecuting,在进入 OnTimer 时测试它是否为真,如果是则立即退出,否则将其设置为真,然后在 tryfinally 部分将其设置为假。 .finally 在您的 OnTimer 的其余部分中。或者,您可以在进入其 OnTimer 事件时禁用 TTimer,然后在 finally 部分重新启用它。

如果您不想使用 TTimer,您可以在 Application.OnIdle 处理程序中为 OnTimer 执行我建议的操作。

最好的办法是在后台线程中进行更新,但是在您的 gui 线程中将更新检索到 Customer 实例的难易程度取决于您的 Customer 表的 TDataSet 类型。

顺便说一句:您的 Customer 表没有使用包含 Customerlat 或 Customerlng 的索引是吗

另外顺便说一句:您的 q 不包含适当的 MCVE,这是此类问题应包含的内容,因为没有它,读者就无法重现该问题。没有一个,我认为它可能会吸引投票关闭。

【讨论】:

  • 防止定时器重入也可以通过在进入时禁用定时器并在完成时启用来实现。
  • @LURD:当然,OP 可能会注意到,谢谢。就个人而言,我总是倾向于使用显式标志,因为“状态”涉及到我需要在其他代码中加以考虑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-28
  • 2011-10-05
  • 2011-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多