为什么在一种特定情况下 Bing 地图上只显示我的 Pin 图的子集?

Why does only a subset of my Pins display on the Bing Map in one particular scenario?

在我的应用程序中,用户可以加载一个文本文件(以分号分隔),该文件描述了要在 Bing 地图上变成 [Push]Pin 的位置的特征,或者加载预现有地图数据(来自本地数据库)到同一端。

在从数据库加载的情况下,效果很好,地图上会为数据库中的每个相应记录显示一个 Pin,例如:

但是,当我从文本文件的内容填充数据库,然后根据这些值创建 Pin 图时,只有一部分 Pin 图显示在地图上 - 事实上,通常只有前两个!

但是,当我从数据库中加载同一张地图时,所有内容都会按应有的方式显示。

从文本文件生成新记录后,我检查了数据库,确实所有记录都在那里(包括坐标(纬度和经度值)。两种情况下的代码似乎相同。但它并没有显示所有 Pin 图...

文本文件内容是这样的(这是创建上面显示的地图的内容):

Paris, France;;Paris;France;75000
Florence, Italy;;Florence;Italy;50100
Chelsea, London, England;23 Tedworth Square;Chelsea London;England;SW3 4DY
Kaltenleutgeben, Austria;;Kaltenleutgeben;Austria;2380
Vienna, Austria;;Vienna;Austria;1010
Weggis, Switzerland;;Weggis;Switzerland;6006
Heidelberg, Germany;;Heidelberg;Germany;69115
Munich, Germany;;Munich;Germany;80331

为了深入了解细节,这里是主窗体中从文本文件加载地图数据的代码,如上图所示:

private void toolStripMenuItemCreateMapAndLocsFromFile_Click(object sender, EventArgs e)
{
    bool pushpinsAdded = false;
    string _currentMap = string.Empty;
    RemoveCurrentPushpins();

    using (var frmCre8MapAndLocsFromFile = new mdlDlgCreateMapAndLocsFromTSVFile())
    {
        if (frmCre8MapAndLocsFromFile.ShowDialog() == DialogResult.OK)
        {
            _currentMap = mdlDlgCreateMapAndLocsFromTSVFile.CurrentMap;
            foreach (Pushpin pin in frmCre8MapAndLocsFromFile.Pins)
            {
                this.userControl11.myMap.Children.Add(pin);
                pushpinsAdded = true; // redundant after the first assignment
            }
        }
    }
    if (pushpinsAdded)
    {
        RightsizeZoomLevelForAllPushpins();
    }
    lblMapName.Text = currentMap;
    lblMapName.Visible = true;
}

这是 mdlDlgCreateMapAndLocsFromTSVFile 中的相关代码(是的,它的名字有误,因为我从 TSV 切换到 SCSV),它在上面被调用:

private void btnOpenTSVFile_Click(object sender, EventArgs e)
{
    string pathToFile = string.Empty;
    string line = string.Empty;
    int counter = 0;
    openTSVFileDlg.Filter = "scsv files (*.scsv)|*.scsv|txt files (*.txt)|*.txt|All files (*.*)|*.*";
    
    if (openTSVFileDlg.ShowDialog() == DialogResult.OK)
    {
        pathToFile = openTSVFileDlg.FileName;
        fileNameOnly = Path.GetFileName(pathToFile); 
        fileNameOnly = Path.ChangeExtension(fileNameOnly, null);
        CurrentMap = fileNameOnly;
        if (SCSVFileAlreadyLoaded(fileNameOnly))
        {
            MessageBox.Show(String.Format("The file/table {0} has already been loaded/populated", fileNameOnly));
            return;
        }
        StreamReader file = new StreamReader(pathToFile);
        while ((line = file.ReadLine()) != null)
        {
            if (counter == 0)
            {
                InsertMapRecord(fileNameOnly);
            }
            counter++;
            InsertMapDetailRecord(fileNameOnly, line);
        }
    }
}

InsertMapDetailRecord() 是将图钉添加到列表的方法,它是:

private void InsertMapDetailRecord(string fileNameOnly, string line)
{
    string keyRecordElements = string.Empty;
    string[] lineElements;
    lineElements = line.Split(';');
    string locationName = lineElements[0].Trim();
    string address = lineElements[1].Trim();
    string city = lineElements[2].Trim();
    string st8 = lineElements[3].Trim();
    string zip = lineElements[4].Trim();
    keyRecordElements = string.Format("{0} {1} {2} {3}", address, city, st8, zip).Trim();
    if (MissingValues(lineElements))
    {
        MessageBox.Show("Could not insert record - one or more key values missing");
        return;
    }

    if (InsertIntoCartographerDetail(fileNameOnly, locationName, address, city, st8, zip))
    {
        AddPushpin(keyRecordElements, locationName);
    }
    else
    {
        MessageBox.Show("insert failed");
    }
}

AddPushpin() 是:

private async void AddPushpin(string _fullAddress, string _location)
{
    iContentCounter = iContentCounter + 1;
    // from 
    var request = new GeocodeRequest();
    request.BingMapsKey = "Gr8GooglyMoogly";
    request.Query = _fullAddress;

    var result = await request.Execute();
    if (result.StatusCode == 200)
    {
        var toolkitLocation = (result?.ResourceSets?.FirstOrDefault())
                ?.Resources?.FirstOrDefault()
                as BingMapsRESTToolkit.Location;
        var latitude = toolkitLocation.Point.Coordinates[0];
        var longitude = toolkitLocation.Point.Coordinates[1];
        var mapLocation = new Microsoft.Maps.MapControl.WPF.Location(latitude, longitude);
        this.Pin = new Pushpin() { Location = mapLocation, ToolTip = _location, Content = iContentCounter 
     };
        if (null == this.Pins)
        {
            this.Pins = new List<Pushpin>();
        }
        this.Pins.Add(Pin); // specific to this form
        UpdateDetailRecWithCoords(_location, latitude, longitude); 
        this.DialogResult = DialogResult.OK;
    }
}

同样,就填充数据库而言,这完成了它应该做的事情,包括详细信息 table 的纬度和经度字段,但是在焦点传递回后地图上只显示两个图钉模态对话框关闭时的主窗体。

这是调用 mdlDlgFrm_LoadExistingMap 的主窗体中的代码:

private void loadExistingMapToolStripMenuItem_Click(object sender, EventArgs e)
{
    bool pushpinsAdded = false;
    RemoveCurrentPushpins();
    using (var frmLoadExistingMap = new mdlDlgFrm_LoadExistingMap())
    {
        if (frmLoadExistingMap.ShowDialog() == DialogResult.OK)
        {
            foreach (Pushpin pin in frmLoadExistingMap.Pins) 
            {
                this.userControl11.myMap.Children.Add(pin);
                pushpinsAdded = true;
            }
        }
    }
    if (pushpinsAdded)
    {
        RightsizeZoomLevelForAllPushpins();
    }
    lblMapName.Text = currentMap;
    lblMapName.Visible = true;
}

这里是来自 mdlDlgFrm_LoadExistingMap 表单的代码,其中:

private void btnLoadSelectedMap_Click(object sender, EventArgs e)
{
    string latitudeAsStr = string.Empty;
    string longitudeAsStr = string.Empty;
    List<MapDetails> lstMapDetails = new List<MapDetails>();
    MapDetails md;
    const int LOC_NAME = 0;
    const int ADDRESS = 1;
    const int CITY = 2;
    const int ST8 = 3;
    const int ZIP = 4;
    const int NOTES = 5;
    const int LATITUDE = 6;
    const int LONGITUDE = 7;
 
    mapName = cmbxMaps.Text.Trim();
    if (mapName == string.Empty)
    {
        MessageBox.Show("Select a map from the combo box");
        cmbxMaps.Focus();
        return;
    }
    try
    {
        Form1.currentMap = mapName; 
        string qry = "SELECT LocationName, Address1, City, StateOrSo, PostalCode, " +
                                "MapDetailNotes, Latitude, Longitude " +
                                "FROM CartographerDetail " +
                                "WHERE FKMapName = @FKMapName";
        var con = new SqliteConnection(connStr);
        con.Open();
        SqliteCommand cmd = new SqliteCommand(qry, con);
        cmd.Parameters.AddWithValue("@FKMapName", mapName);
        var reader = cmd.ExecuteReader();
        while (reader.Read())
        {
            md = new MapDetails();
            md.LocationName = reader.GetValue(LOC_NAME).ToString().Trim();
            md.Address = reader.GetValue(ADDRESS).ToString().Trim();
            md.City = reader.GetValue(CITY).ToString().Trim();
            md.StateOrSo = reader.GetValue(ST8).ToString().Trim();
            md.PostalCode = reader.GetValue(ZIP).ToString().Trim();
            md.MapDetailNotes = reader.GetValue(NOTES).ToString();
            latitudeAsStr = reader.GetValue(LATITUDE).ToString();
            if (string.IsNullOrEmpty(latitudeAsStr))
            {
                md.Latitude = 0.0;
            }
            else
            {
                md.Latitude = Convert.ToDouble(reader.GetValue(LATITUDE).ToString());
            }
            longitudeAsStr = reader.GetValue(LONGITUDE).ToString();
            if (string.IsNullOrEmpty(longitudeAsStr))
            {
                md.Longitude = 0.0;
            }
            else
            {
                md.Longitude = Convert.ToDouble(reader.GetValue(LONGITUDE).ToString());
            }
            lstMapDetails.Add(md);
        }
        AddPushpinsToListOfPushpins(lstMapDetails);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    this.Close();
}

// AddPushpins (plural)
private void AddPushpinsToListOfPushpins(List<MapDetails> lstMapDetails)
{
    string fullAddress;

    foreach (MapDetails _md in lstMapDetails)
    {
        fullAddress = string.Format("{0} {1} {2} {3}", _md.Address, _md.City, _md.StateOrSo, _md.PostalCode).Trim();
        AddPushpinToListOfPushpins(_md.LocationName, fullAddress, _md.MapDetailNotes, _md.Latitude,                 _md.Longitude);
    }
}

// AddPushpin (singular)
private async void AddPushpinToListOfPushpins(string location, string fullAddress, string mapDetailNotes, double latitude, double longitude)
{
    iContentCounter = iContentCounter + 1;
    string toolTip = string.Empty;
    // if already have the record, including the coordinates, no need to make the REST call
    if ((latitude != 0.0) && (longitude != 0.0))
    {
        if (mapDetailNotesExist(location))
        {
            toolTip = String.Format("{0}{1}{2}{1}{3},{4}{1}{5}", location, Environment.NewLine, fullAddress, latitude,                 longitude, mapDetailNotes.Trim());
        }
        else
        {
            toolTip = String.Format("{0}{1}{2}{1}{3},{4}", location, Environment.NewLine, fullAddress, latitude,                         longitude);
        }
        var _mapLocation = new Microsoft.Maps.MapControl.WPF.Location(latitude, longitude);
        this.Pins.Add(new Pushpin()
        {
            Location = _mapLocation,
            ToolTip = toolTip,
            Content = iContentCounter
        });
    }
    else
    {
        // from                 address-using-bing-maps
        var request = new GeocodeRequest();
        request.BingMapsKey = "Gr8GooglyMoogly";
        request.Query = fullAddress;

        var result = await request.Execute();
        if (result.StatusCode == 200)
        {
            var toolkitLocation = (result?.ResourceSets?.FirstOrDefault())
                    ?.Resources?.FirstOrDefault()
                    as BingMapsRESTToolkit.Location;
            var _latitude = toolkitLocation.Point.Coordinates[0];
            var _longitude = toolkitLocation.Point.Coordinates[1];
            var mapLocation = new Microsoft.Maps.MapControl.WPF.Location(_latitude, _longitude);
            this.Pins.Add(new Pushpin() 
            { 
                Location = mapLocation,
                ToolTip = String.Format("{0}{1}{2}{1}{3},{4}{1}{5}", location, Environment.NewLine, fullAddress, _latitude,                         _longitude, mapDetailNotes),
                Content = iContentCounter 
            });
            UpdateLocationWithCoordinates(location, _latitude, _longitude);
        }
    }
}

在我看来,无论是从文件创建地图还是从数据库加载地图,创建 Pin 图然后在地图中显示它们的代码都是一样的。那么为什么行为不同呢?

更新

举一个更具体的例子,当通过包含如下内容(前几行的摘录)的文件创建地图时:

July 15 & 16, 1895 - Music Hall, Cleveland, Ohio;; Cleveland;Ohio;44101
July 18, 1895 - Soo Opera House or Hotel Iroquois, Sault Ste. Marie, Michigan;;Sault Ste. Marie;Michigan;49783
July 19, 1895 - Casino Room, Grand Hotel, Mackinac, Michigan;;Mackinac;Michigan;49757
July 20, 1895 - Grand Opera House, Petoskey, Michigan;;Petoskey;Michigan;49770
July 22, 1895 - First Methodist Church, Duluth, Minnesota;;Duluth;Minnesota;55802
July 23, 1895 - Hotel West & Reception and Supper, Minneapolis, Minnesota;;Minneapolis;Minnesota;55111
July 24, 1895 - People's Church, St. Paul, Minnesota;;St. Paul;Minnesota;55101
July 27, 1895 - Luncheon and Manitoba Club Supper, Winnipeg, Canada;;Winnipeg;Canada;R0G 0A1

...我一开始就明白了(只有一个子集,而且所有内容编号都相同):

...但是当我之后立即通过“加载现有”加载地图时,我得到了全部:

?

更新 2

感谢“The Bing Maps Whisperer”Reza Aghaei,它现在可以正常工作了:

可能的问题: 多线程和分配给实例级引用。

TLDR;在 AddPushpin() 中,使用局部变量而不是实例变量。 将 this.Pin = new Pushpin() 替换为 var pin=new Pushpin(),然后将此局部变量添加到集合中(您可能希望使用线程安全的集合更改此集合)

说明:

While Loop -> InsertMapDetailRecord() ->  AddPushpin(){
    1. thread is paused
    2. New object is assigned to instance variable
    3. Instance variable is added to collection

}

这可能是因为一个实例变量对象在被添加到集合之前被另一个实例覆盖了。因此,集合中只有少数对象可用。 (正如您提到的子集已加载,但此计数不一致并且每个 运行 都不同?)

以下是我认为您需要应用的修复:

  1. AddPushpin 签名更改为 async Task AddPushpin(...)
  2. 然后在InsertMapDetailRecord中将调用更改为await AddPushpin(...);
  3. InsertMapDetailRecord 签名更改为 async Task InsertMapDetailRecord(...)
  4. btnOpenTSVFile_Click 签名更改为 async void btnOpenTSVFile_Click
  5. 然后在btnOpenTSVFile_Click中将调用InsertMapDetailRecord改为await InsertMapDetailRecord()

我也猜你应该:

  1. AddPushpin方法中,删除this.DialogResult = DialogResult.OK;
  2. btnOpenTSVFile_Click中,在while循环结束后立即添加this.DialogResult = DialogResult.OK;

使用async/await

基本上当您在方法 void M(...) 中调用方法 DoSomething(...) 时 returns Task,将调用更改为 await DoSomething(...);。然后如果方法 M 是事件处理程序,则将签名更改为 async void M(...),否则如果它是普通方法,则将签名更改为 async Task M(...).

设置对话框结果

我认为您需要更改签名的原因以及您设置对话框结果的行:在 AddPushpin 中,您将 DialogResult 设置为 OK,这意味着在添加第一个图钉,包含对话框 (mdlDlgCreateMapAndLocsFromTSVFile) 将关闭,您开始查看 Pins 属性(仍在幕后填充,然后在添加前几个有机会添加到的图钉之后Pins 属性,您处理 mdlDlgCreateMapAndLocsFromTSVFile,地图上将不再添加图钉。

变量范围

我们看不到整个代码,但要注意variables/fields的范围。作为警告,在整个方法中访问 class 字段不是一个好主意,它们不是局部变量,当您更改这些值时,其他方法也可以看到更改。