List of Point or Featureset?

Dec 22, 2010 at 11:55 AM

I am trying to migrate from mapwindow ActiveX to Dotspatial

I have a couple of questions.

Scenario :
The applications if for trucks that drive around the country side and empties septic tanks, so the GPS moves the map around every few seconds.
The is a few line shape background layers (roads, buildings so on) that is static. (loaded from files on disk onLoad)

Within the extents of background maps, there is approximately 10.000 point(septic tank) that is semi static (a few can change color/coordinate every minute)
The points I divided into 2 colors, according to status (ok=green or error=red) and the shape is a rectangle.
According to the current extend of the zoom, I calculate the size of the point.  (Zoom level is changed seldom.)
At normal driving zoom level, the extent is 1x1 km, containing maybe 10 – 100 of the 10.000

The truck has a scheduled 150 septic tank, to empty every week.
The 150 is divided into routes(10-50), 1 route = 1 road.
The Legend control shows a layer for each road.

The 150 route points is round and colored according to the road name(road 1=blue, road2=orange, road 3=yellow…) and is positioned on top of the 10.000 total points.
I is then easy to see which is due for emptying.

My concern is performance, can I make a list of septic tank (coordinate and ID) in memory and overwrite the shapefile in memory, every time the list changes?
Or what is the best way to hold the somewhat dynamic point lists in memory, and update the map, frequently?

Developer
Dec 22, 2010 at 2:04 PM

Ok, so for the truck moving around, I would not draw this as a point layer at all.  I would use the map.Paint event, or create a custom MapFunction that draws the truck using GDI+ graphics and the Map.PixelToProj and map.ProjToPixel methods to figure out where to draw in pixel coordinates.  This means that you can update the truck with Map.Invalidate, which re-paints the buffer, but does not regenerate the buffer from vectors.  For the septic tanks themselves, you should probably go ahead and use a MapPointLayer which is what is created when you simply add the point FeatureSet to the Map.Layers.Add(myFeatureSet) routine.  The return value is your IMapLayer, but since you know it is a point layer, you can cast it into an IMapPointLayer.  This gives you the handle you need to set up categories on the Symbology.  Each category can express a filter expression, that specifies which shapes that category will draw.  Then you can specify the Symbolizer property on the category to control what they look like.  This will be a PointSymbolizer and will be made up of multiple layers of "symbols".  That way you can have a blue square on top of a yellow circle or whatever your desire is.  The basic one is simple shapes with a SimpleSymbol, but you can also specify font characters (like the ESRI true type fonts) using the FontSymbol and use PictureSymbols in order to specify the custom image.  10,000 points is not very many, so you would probably just keep an in memory FeatureSet to describe these.  When you change the vertices, you may need to call myFeatureSet.InitializeVertices() if you created the new points as features.  If you keep it in index mode and update the vertex array directly, then you might not need to do that.  In order to refresh the map to show your new vertex values, you need to do Map.MapFrame.Invalidate(), which forces a re-draw from your vectors onto the buffer.  This shouldn't be terribly slow, so it should be fine for your frequency of updating for the septic tank.  I'd not recommend having the septic tank layer drawn yourself, since that would probably be considerably more work in the end than simply creating a map point layer and setting up a symbology.  Furthermore, you can save your symbology more easily if you keep the septic tank map as a standard GIS layer.

Ted

 

Dec 22, 2010 at 6:07 PM
Edited Dec 23, 2010 at 2:06 PM
private void AddPoints()
{
    // Create the featureset if one hasn't been created yet.
    if (_myPoints == null) _myPoints = new FeatureSet(FeatureType.Point);

    _myPoints.DataTable.Columns.Add("ID", typeof(int));
    _myPoints.DataTable.Columns.Add("Category", typeof(string));
    _myPoints.Projection = KnownCoordinateSystems.Geographic.World.WGS1984;

    // Assume background layers have been added, and get the current map extents.
    double xmin = map40.Extent.MinX;
    double xmax = map40.Extent.MaxX;
    double ymin = map40.Extent.MinY;
    double ymax = map40.Extent.MaxY;

    // Randomly generate 10 points that are in the map extent
    Random rnd = new Random();
    for (int i = 0; i < 10; i++)
    {
        DotSpatial.Topology.Point p = new DotSpatial.Topology.Point();
        p.X = xmin + rnd.NextDouble() * (xmax - xmin);
        p.Y = ymin + rnd.NextDouble() * (ymax - ymin);
        IFeature currentFeature = _myPoints.AddFeature(p);
        currentFeature.DataRow["ID"] = i;
        currentFeature.DataRow["Category"] = i % 2 == 0 ? "Small" : "Large";
    }

    // Add a layer to the map, and we know it is a point layer so cast it specifically.
    IMapPointLayer pointLayer = map40.Layers.Add(_myPoints) as IMapPointLayer;
            
    // this removes the points, som nothein to show 
    // pointLayer.Symbology.ClearCategories();

    //Create small Category
    PointCategory smallSize = new PointCategory(Color.Red, DotSpatial.Symbology.PointShape.Rectangle, 5);
    smallSize.FilterExpression = "[Category] = 'Small'";
    smallSize.LegendText = "Small";
    pointLayer.Symbology.AddCategory((IPointCategory)smallSize);

    //Create large Category
    PointCategory largeSize = new PointCategory(Color.Blue, DotSpatial.Symbology.PointShape.Star, 10);
    largeSize.FilterExpression = "[Category] = 'Large'";
    largeSize.LegendText = "Large";
    pointLayer.Symbology.AddCategory((IPointCategory)largeSize);
}

Developer
Dec 22, 2010 at 9:08 PM

I'm on break and  not set up to do any debugging or testing right now, but the first thing I can point out is that your filter expressions should read like "[F] = 1" since SQL expressions don't use the double equal sign like C# and C++.  That is the most likely problem, but there may be something else going on as well.  Remember that you will have a default category in there, so with the two you are adding, you will have a total of three.  you may want to remove the default category or else you will be rendering your own categories on top of the default categories, which may look odd.

Ted

 

Dec 22, 2010 at 11:09 PM

Hi Ted
I added myScheme.Categories.Clear(); in the above code and changed the double equal to single equal, but with no success
Don't know if that was what you ment?

Regards Claus

Developer
Dec 23, 2010 at 12:08 AM

Yes, fixing those things might not have fixed the bug.  There might also be something going on underneath.  Is the exception you are getting still the same? There might be some kind of null exception being tripped somewhere during the set operation, since that operation causes the scheme to be applied, so that is where it will be trying to parse your expression.  If you post the updated code along with the call stack for the error I might be able to help.  But I am not set up to debug from this vacation area so if there is a bug in the DotSpatial system itself we will just have to log it and maybe you or one of the other developers can post the fix, if indeed there is an error.

Ted

 

Dec 23, 2010 at 8:09 AM
Edited Dec 23, 2010 at 8:12 AM
I updateded the code i the original post now, and the 2 categories + the default turn op in the legend. All my points are unfortuatnely on the default, so the FilterExpression or something else is wrong.
There is no Exception, it just dosn't work!
You wrote : "If you keep it in index mode" --> how is that done?

When I need to change the coordinate of a septic tank, is this the way to go?
foreach (IFeature feature in _myPoints.Features.Where(f=>f.DataRow.Field<int>("ID") == 14650))
{
    feature.Coordinates[0].X = newX;
    feature.Coordinates[0].Y = newY;
}
map40.MapFrame.Invalidate(); 
map40.Invalidate();
Developer
Dec 23, 2010 at 2:01 PM
Edited Dec 23, 2010 at 2:03 PM

If you are loading from a shapefile, you can keep it in index mode by not accessing the "Features" list.  You can use the ShapeIndices and "Vertex" property to access the raw coordinates without messing around with features.  The Vertex stores X, Y interleaved vertices.  The index in the table is the same as in the vector order, so if you find the Row index of f.DataRow then the x index is 2 times that, and the y index is 2 times that + 1 in the vertex array.

  • int row = f.DataTable.Rows.IndexOf(f.DataTable.Select("ID = 14650")[0]);
  • f.Vertex[row*2] = newX;
  • f.Vertex[row*2+1] = newY;

 

If you do it this second way and you are using IndexMode, you won't have to call InitializeVertices when you are finished.  Otherwise, you need to update the vertices in the Vertex array before your drawing will be up to date.

 

Sorry, your original post that I saw e-mail for had a filter expression with an F in it and was numeric, so it didn't match the post that I see now.  Remember when you are using Filter expressions with text that you need to put the text in single quotes:

[Category] = 'Small'

Ted

 

Dec 23, 2010 at 2:18 PM
Edited Dec 23, 2010 at 2:32 PM

Hi Ted

Thanks again, I hope I don’t ruin your holiday break!
I added the single quotes, but still no luck! Maybe there is something I am Missing in the code, but what.....
About the IndexMode, since I store some information about categories(Filter expressions) in the "Feature" list, and change that information, the same time as my coordinates change, then the fast IndexMode is not an option for me, right?
And I have to call both when updating is complete:

map40.MapFrame.Invalidate();
map40.Invalidate();

Is there any plans for adding a “sample code” section/repository for sample code and sample projects, since the learning curve is a bit steep, only depending on search in the not very extensive Wiki/Documentation.

Claus

Jan 3, 2011 at 9:24 PM

Hi Ted

Could I trouble you to test my code example, to se I you can get it to work (posted Dec 22 2010 at 7:07 PM)

Regards Claus

Jan 10, 2011 at 10:37 AM

Found my problem, forgot to call

pointLayer.DataSet.InvalidateVertices();