A moving marker

Sep 1, 2010 at 8:14 AM

Hi guys,

 

I am feeding in a list of positions. I am intending for a marker to move from position to position as it goes through the list. Here's my code so far:

private void PlotFlight()
        {

            List<GMapMarker> marked = new List<GMapMarker>();

            List<PointLatLng> positions = new List<PointLatLng>();
            string aLine;

            TextReader file = new StreamReader("c:\\gps.csv");

            while ((aLine = file.ReadLine()) != null)
            {
                string[] pos = aLine.Split(',');
                PointLatLng p = new PointLatLng
                {
                    Lat = float.Parse(pos[1]),
                    Lng = float.Parse(pos[0])
                };
                positions.Add(p);

            }


            GMapMarker lastmark = new GMapMarker(positions[0]); // Set the first position
             
            foreach (var GPSposition in positions)
            {
                _topOverlay.Markers.Remove(lastmark);
                GMapMarker mark = new GMapMarker(GPSposition);
                AddMarker(mark, true);
                marked.Add(mark);
                lastmark = mark;

                Thread.Sleep(1000);


            }
        }

The first bit is just reading in the data. Works good.

I then store the first marker position in lastmark.

I then itterate though the list of positions... but first, I try to remove 'lastmark'. And then I want to plot the new position on the map.

It's not removing the lastmark. So, I get a 'snake' of markers on the screen.

 

The AddMarker methods adds the marker to  _topOverlay.Markers.

How should I be removing previously added markers?



        
    
Sep 1, 2010 at 8:30 AM

Actually, when I would prefer, would be a line, instead of markers... forming where the thing travels.... But more like a snake, so that it stays a certain length, and doesn't clutter the screen too much. Is this possible?

Sep 1, 2010 at 1:16 PM

i think you could use the Route to do something like this.

Snippet

GMapRoute route = new GMapRoute(new List<PointLatLng>(), "lineRoute");
route.Points.Add(yourPoint));
MapControl.UpdateRouteLocalPosition(route));

greetz Manuel
Sep 1, 2010 at 1:17 PM

Of course do you need to add the route to an overlay ;)

Sep 1, 2010 at 10:09 PM

Thanks Manuel.

I now have this:

            GMapRoute route = new GMapRoute(new List<PointLatLng>(), "lineRoute");
            foreach (var GPSposition in positions)
            {
             
                route.Points.Add(GPSposition);
                MainMap.UpdateRouteLocalPosition(route);
                Thread.Sleep(1000);
            }

But, nothing is drawn on the map. This code is running within a worker thread. Do I need re refresh something, or am I doing something wrong?

Sep 1, 2010 at 10:24 PM
Hi did you add the route to the overlay?

_topOverlay.Routes.Add(route);

Maybe you have to call MaiMap.Refresh();


if you do this within a worker-thread you should syncronize them, or fire an event on the maintread (ui-tread) to refresh the map.

Sep 1, 2010 at 10:36 PM

Thanks again, Manuel.

I tried a Refresh, but as you pointed out, the thread is giving me an issue.

System.InvalidOperationException was unhandled
  Message=Cross-thread operation not valid: Control 'MainMap' accessed from a thread other than the thread it was created on.
  Source=System.Windows.Forms
  StackTrace:
       at System.Windows.Forms.Control.get_Handle()
       at System.Windows.Forms.Control.Invalidate(Boolean invalidateChildren)
       at System.Windows.Forms.Control.Refresh()
       at GMap.NET.WindowsForms.GMapControl.Refresh() in D:\Projects\Greatmaps\GMap.NET.WindowsForms\GMap.NET.WindowsForms\GMapControl.cs:line 256
       at MapDemo.Form1.PlotFlight() in C:\Users\Craig Lister\Documents\Visual Studio 2010\Projects\MapDemo\MapDemo\Form1.cs:line 296
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:

That occurs on base.Refresh() in GMapControl.cs

I am new to threading, and it's biting me hard! :)

At the moment, to start my plotting thread, I do:

            Thread t = new Thread(PlotFlight);
            t.IsBackground = true;
            t.Start();
Where PlotFlight is the method I am using to do the plotting. Could you maybe give me guidence on how to fix this by syncronizing the thread, or the main threads event?

Coordinator
Sep 1, 2010 at 10:41 PM

use BackgroundWorker

Sep 1, 2010 at 10:55 PM
Edited Sep 1, 2010 at 10:57 PM

look at the System.ComponentModel.BackgroundWorker.

For simple threading operations this is a good point to start, and understand how Threads work.

 

To your Problem

You can't directly call an object from 2 different threads unless they are locked (main-thread quere waits for the worker-thread queue)

you could read more about this on http://www.parallelcsharp.com/

 

if you want to use the background worker you can do something like this:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (BackgroundWorker BGW = new BackgroundWorker())
            {
                BGW.WorkerReportsProgress = true;

                BGW.WorkerSupportsCancellation = true; //if you want to abort the worker from the mainthread

                BGW.DoWork += new DoWorkEventHandler(BGW_DoWork);
                BGW.ProgressChanged += new ProgressChangedEventHandler(BGW_ProgressChanged);
                BGW.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BGW_RunWorkerCompleted);

                BGW.RunWorkerAsync(null); //you could pass here informations the worker should work with.
            };


        }

        static void BGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //Do here other stuff, check for errors and so on
            //Or cleanup
        }

        static void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //Do here draw the markers on the map.
            //e helps you to get data from one thread to the mainthread
            
            
        }

        static void BGW_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bgw = sender as BackgroundWorker;
            //Do your Heavy Work here
            bgw.ReportProgress(0, null); //you can pass as second parameter a object

            //if you want to cancle you work when the user wants it:
            if (bgw.CancellationPending)
                e.Cancel = true;
        }
    }
}
//Thats it ;)

 

Sep 1, 2010 at 11:51 PM

Ok, thanks again.

I nearly understand. :)

On my form creation, I do this:

            route = new GMapRoute(new List<PointLatLng>(), "lineRoute");
            MainMap.RoutesEnabled = true;

(route is a variable defined earlier)

I then have a button to start drawing the route:

   private void button2_Click(object sender, EventArgs e)
        {

            BackgroundWorker bgw = new BackgroundWorker();
            bgw.WorkerSupportsCancellation = true;
            bgw.WorkerReportsProgress = true;
            bgw.DoWork += new DoWorkEventHandler(PlotFlight);
            bgw.ProgressChanged += new ProgressChangedEventHandler(Plot_MapChanged);
            bgw.RunWorkerAsync(null);
        }



As you can see, my DoWork is pointing at this method:

 

 

 

private void PlotFlight(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bgw = sender as BackgroundWorker;

            List<PointLatLng> positions = new List<PointLatLng>();
            string aLine;

            #region Read File Data
            TextReader file = new StreamReader("c:\\gps.csv");

            while ((aLine = file.ReadLine()) != null)
            {
                string[] pos = aLine.Split(',');
                PointLatLng p = new PointLatLng
                {
                    Lat = float.Parse(pos[1]),
                    Lng = float.Parse(pos[0])
                };
                positions.Add(p);

            }
            #endregion

            
            foreach (var GPSposition in positions)
            {
             
                route.Points.Add(GPSposition);
              //  MainMap.UpdateRouteLocalPosition(route);
                bgw.ReportProgress(0, null);
                Thread.Sleep(1000);
            }
        }

And then, when I 'ReportProgress', my 'Plot_MapChanged' method is called:

        private void Plot_MapChanged(object sender, ProgressChangedEventArgs e)
        {
            MainMap.Refresh();
        }

It compiles fine, and runs... but nothing appears.

This might be because I am not assigning my route to the map.

Do I need to add something here?

            route = new GMapRoute(new List<PointLatLng>(), "lineRoute");
            MainMap.RoutesEnabled = true;

I thought I would have to add it as an overlay, like this: MainMap.Overlays.Add(route);

But it's the wrong type.

Sep 1, 2010 at 11:58 PM

WAIT!

:)

It works... nearly.

I did this:

            route = new GMapRoute(new List<PointLatLng>(), "lineRoute");
            MainMap.RoutesEnabled = true;
            _topOverlay.Routes.Add(route);

That may have fixed it...

BUT... the route only displays if I move the map! If I don't touch the map, the route doesn't appear.... :(

Sep 1, 2010 at 11:59 PM

You have to add the Overlay to the MainMap.

And the Overlay-object has a Routes property.

 

i think you should better do something like this:

private void PlotFlight(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bgw = sender as BackgroundWorker;

            List<PointLatLng> positions = new List<PointLatLng>();
            string aLine;

            #region Read File Data
            TextReader file = new StreamReader("c:\\gps.csv");

            while ((aLine = file.ReadLine()) != null)
            {
                string[] pos = aLine.Split(',');
                PointLatLng p = new PointLatLng
                {
                    Lat = float.Parse(pos[1]),
                    Lng = float.Parse(pos[0])
                };
                positions.Add(p);

            }
            #endregion

            
            foreach (var GPSposition in positions)
            {
                bgw.ReportProgress(0, GPSposition);
                Thread.Sleep(1000);
            }
        }

       private void Plot_MapChanged(object sender, ProgressChangedEventArgs e)
        {
             PointLatLng pnt = e.UserState as PointLatLng;
                route.Points.Add(pnt);
                MainMap.UpdateRouteLocalPosition(route);
            MainMap.Refresh();
        }


Sep 2, 2010 at 12:03 AM

Fantastic, sir!

That has worked brilliantly!!

I just used the c# cast instead. :)

 PointLatLng pnt = (PointLatLng)e.UserState;

THis is fantastic!!!!
Thanks!
Sep 2, 2010 at 12:04 AM

Additional Note @ threads!

NEVER EVER! use Objects from the mainthread (without telling the runtime to lock it) on the workingthread.

            foreach (var GPSposition in positions)
{

/* This is really bad */
route.Points.Add(GPSposition);
// MainMap.UpdateRouteLocalPosition(route);
bgw.ReportProgress(0, null);
Thread.Sleep(1000);
}

you will get a "Cross-thread operation not valid:" InvalidOperationException if you touch the routes list on 2 diffenrent threads.

@radioman: are the Collections of GMap.NET threadsave?

Greetings, Manuel

 

Sep 2, 2010 at 12:05 AM

You are welcome :)

Sep 2, 2010 at 12:06 AM

One moth thing, and this just might be bnasic logic, but ...

Is there a way to show only the last 5 points in the route? So it forms more of a snake? The code I am writing is for tracking a remote controlled plane. (Yip, pretty cool!)

As the plane stays close to the person, the screen gets very messy very quicly. I'd like to show it as a snake, showing it's last few seconds of movement only.

So, can I remove any parts in the route, which are more than 5 samples old?

Sep 2, 2010 at 12:09 AM

Manuel,

Thanks for the lesson in Threads! Very much appriciated!

Is the problem you mentioned above, where you say I mustn't use Objects from main thread, because the object was created in the main thread, but accessed in the worker thread... and this may land up with tow threads accessing the same object at the same time?

Very useful! Thanks again!

And @radioman - great bit of code, thanks!

Sep 2, 2010 at 12:15 AM

you got it :)

>>and this may land up with tow threads accessing the same object at the same time?

you mustn't access it from 2 diffrent threads, cause if they accidentally access it at the same moment, you'll get the exception.

 

To your problem with the 5 points:

sure. Its just a list.

before adding the new element, check if count > 5 and kick out index 0.

Sep 2, 2010 at 1:10 AM
Thanks Manuel. You have been an amazing help. It's working extremely well now. Thank you.
Coordinator
Sep 2, 2010 at 12:52 PM

cool indeed(i can only hope it isn't a some type of missile ;]) ...do you have any ideas for the demo, like some video on youtube on how the system works in real?

Sep 2, 2010 at 3:14 PM

100% for sure, you'll see it.

Basically, we fly reote controlled planes, with video transmitters, video cameras, gpd and on screen displays. They transmit video to our gogglesd we wear. The flight data is recorded.

Once we land, we plug into my first system, which downloads the GPS data. I then create a KML file... for Google Earth... with is static.

However, your amazing software allows me to draw the flight as it happens... They can send down GPS data over the video signal, so I can get that and plot live position on a laptop.

Or, the person can load up a KML file, and run the flight, and watch where he goes. Very interesting stuff....

Your development has made this possible, and I thiank you!

 

Craig.

Sep 3, 2010 at 9:48 AM

Here's another question about my moving marker.

Can I add a marker, but instead of the default icon displayed, can I display a small aeroplane, and then rotate the image based on the direction of travel? I can pass it the direction, based on the previous Position, and the current position.

Sep 3, 2010 at 10:48 AM

for sure, just extend GMapMarker.

You find a sample in the docs.

:)

Coordinator
Sep 3, 2010 at 11:36 AM

demo markers has integrated bearing arrow, you can use them as example for your plane

Sep 3, 2010 at 12:10 PM

Thanks guys.

Just to confirm, is this the demo?

http://greatmaps.codeplex.com/Thread/View.aspx?ThreadId=71409

Coordinator
Sep 3, 2010 at 12:21 PM

no ;} it's GMapMarkerGoogleGreen & GMapMarkerGoogleRed

Sep 3, 2010 at 1:08 PM

Ah man, I'm sorry for sounding really stupid, but ... where can I find an example?

I used the 'Search Wiki and Dolcumentation' for 'GMapMarkerGoogleRed', but it found zero results.

I found this somewhere:

http://www.java2s.com/Open-Source/CSharp/GIS/GMap.NET/GMap/NET/WindowsForms/Markers/GMapMarkerGoogleRed.cs.htm

Is that what I am looking for?

Coordinator
Sep 3, 2010 at 1:16 PM

g, just download the latest changeset

Sep 3, 2010 at 1:25 PM

Hot build

Released: Aug 25 2010?
Sep 3, 2010 at 2:20 PM

Sorry, what's a .sn file?

I am getting the error:

Error    26    Cannot import the following key file: sn.snk. The key file may be password protected. To correct this, try to import the certificate again or import the certificate manually into the current user’s personal certificate store.    GMap.NET - Hot Build

Coordinator
Sep 3, 2010 at 2:28 PM

just disable that project, why do you need it? ;]

Sep 3, 2010 at 2:41 PM

Thanks radioman!

Yes, you're right! Sorry for the silly questions, but I am new to using 3rd party components... I do simple database applications for professional work... so, I'm learning.

Thanks for the help. For my main project (The application which is making use of your fantastic dlls), I should add GMap.NET.Core and the GMap.NET.WindowsForms projects, to the soultion which currently contains my main application? And then reference the dlls that get created by those projects?

 

Coordinator
Sep 3, 2010 at 2:53 PM

set Build to Release, use x86 target, Rebuild All, and use generated dlls in Build folder

Sep 3, 2010 at 4:19 PM

Thanks Radioman!

Got it working... solution builds... I've even made a setup, and sent it to a tester on the other side of the planet, and it's all good. Thanks sir!

Now, to draw a plane... and somehow get that working. :)

Sep 5, 2010 at 10:23 AM
Edited Sep 5, 2010 at 10:25 AM

radioman - here's what I have written, and your amazing code is living inside...

 

http://www.forum.tsebi.com/viewtopic.php?f=7&t=93&p=1186#p1186

One it's running, click 'File -> Live Tracker'...

There's a setup, as well as a demonstration KML file (Google Earth file) which I flew and generated.

 

Thanks for your cool code!

Now, I need to find a way to make the movement smoother (You'll see what I mean) when zoomed in on the plane, and the map is tracking the flight.

 

If you see any ideas how I can make it better, please shout.

Oh... And here's the actual flight... what I see when flying it.

http://www.youtube.com/watch?v=CHMC7DVwcn8

Coordinator
Sep 5, 2010 at 1:00 PM

Congrats! Brilliant indeed ;}

p.s. you can remove that line from prefetcher at Button1Click: List<Point> list = this.MainMap.Projection.GetAreaTileList(currentViewArea, i, 0);

Sep 6, 2010 at 9:10 AM

Thanks sir!

So, I now have:

        private void Button1Click(object sender, EventArgs e)
        {
            RectLatLng area = MainMap.CurrentViewArea;
            for (int i = (int)MainMap.Zoom; i <= MainMap.MaxZoom; i++)
            {
                TilePrefetcher obj = new TilePrefetcher();
                obj.ShowCompleteMessage = true;
                obj.Start(area, MainMap.Projection, i, MainMap.MapType, 100);
            }
        }

Seem OK?

I am not finding a way to use a custom marker (A small aeroplane) and then rotate it based on direction. Is there an example online?

Coordinator
Sep 6, 2010 at 9:12 AM

it's GMapMarkerGoogleGreen & GMapMarkerGoogleRed, check the source

Sep 6, 2010 at 11:16 PM

Aw man, radioman - you make it too easy for us! Fantastic!

I now have a beautiful green arrow, pointing the planes direction. Amazing!

Is there a way to NOT show the green placemarker above the arrow? Or must it stay? I have a green balloon marker on the arrow. I'd like to just show the arrow.

Coordinator
Sep 6, 2010 at 11:46 PM

;} yeah, you can customize it, just change the code, ...and now i've implemented map rotation, so you can fly and map can rotate to the direction, your plane is going! Use bearing property. Should look really fun!

Mr. Scott, energize! ;]

Coordinator
Sep 7, 2010 at 8:16 AM
Edited Sep 8, 2010 at 10:49 AM

MainMap.CurrentPosition = marker.Position;
if(marker.Bearing.HasValue)
{
   MainMap.Bearing = marker.Bearing.Value;
}