MapWindow Developer Team : MapWindow Discussion Forum
Hi, there is an example of label generation for cities including label categories, priority of drawing, graduated colors. See the screenshot with results for USA cities here [www.mapwindow.org
Pages: 12Next
Current Page: 1 of 2
Labeling US cities with new labeler
Posted by: Sergei ()
Date: February 13, 2010 01:20PM

Hi,

there is an example of label generation for cities including label categories, priority of drawing, graduated colors. See the screenshot with results for USA cities here [www.mapwindow.org] (in attachment). See documentation of new methods here [www.mapwindow.org]. The methods and properties used here can be slightly changed. I'd like to port methods for labels/categories generation in labels class, for example.

Sergei

---------- The code (VB6) ------------

 Private Sub GenerateLabelsForCities()
        Dim obj As Object
        Dim sf As MapWinGIS.Shapefile
        Dim lb As MapWinGIS.Labels
        Dim sMessage As String
        Dim iPosition As Integer
        Dim iField As Integer
        Dim iCount As Long
        Dim i As Integer

        ' the position of cities layer in the layers list (set appropriate value)
        iPosition = 2

        ' getting shapefile
        obj = Map1.GetObject(Map1.LayerHandle(iPosition))
        If TypeOf obj Is MapWinGIS.Shapefile Then Set sf = obj
        If sf Is Nothing Then
            MsgBox "Unable to retrieve shapefile with the position: " & iPosition
            Exit Sub
        End If

        ' asking the user for NAME field
        sMessage = ""
        For i = 0 To sf.NumFields - 1
            sMessage = sMessage & i & " - " & sf.Field(i).Name & vbNewLine
        Next i

        iField = Val(InputBox(sMessage, "Enter the index of NAME field", "-1"))
        If iField < 0 Or iField >= sf.NumFields Then
            MsgBox "Invalid index of field", vbInformation
            Exit Sub
        End If

        ' label generation; it doesn't matter what method you'll specify for point shapefile
        ' there is the only one availible (maybe it'll better to add it to the list of constants)
        iCount = sf.GenerateLabels(iField, lpCenter)

        If Count = 0 Then
            MsgBox "No labels were generated", vbInformation
            Exit Sub
        End If

        ' we'll be using the class intensively so better to set a variable
        Set lb = sf.Labels
		
        ' changing alignment to see the point (will be used by all categories afterwards)
        lb.Alignment = laTopLeft

        ' generation of categories; such method can be used as well as sf.GenerateCategories
        ' here we won't be using automatically generated minValue maxValue for categories,
        ' so simply to add categories will be ok for us
        lb.ClearAllCategories
        For i = 1 To 7
            lb.AddCategory("Category " & i)
        Next i

        ' setting ranges for population, font size and priority
        ' the categories with large values of priority will be darwn first (0 by default)
        With lb.Category(0)
            .MinValue = 0       ' population from 0 people
            .MaxValue = 10000   ' to 10000 people
            .FontSize = 10
            .Priority = 1
        End With

        With lb.Category(1)
            .MinValue = 10000
            .MaxValue = 50000
            .FontSize = 10
            .Priority = 2
        End With

        With lb.Category(2)
            .MinValue = 50000
            .MaxValue = 100000
            .FontSize = 10
            .Priority = 3
        End With

        With lb.Category(3)
            .MinValue = 100000
            .MaxValue = 250000
            .FontSize = 12
            .Priority = 4
        End With

        With lb.Category(4)
            .MinValue = 250000
            .MaxValue = 500000
            .FontSize = 13
            .Priority = 5
        End With

        With lb.Category(5)
            .MinValue = 500000
            .MaxValue = 1000000
            .FontSize = 14
            .Priority = 6
            .FontUnderline = True
        End With

        With lb.Category(6)
            .MinValue = 1000000
            .MaxValue = 100000000
            .FontSize = 18
            .Priority = 7
            .FontUnderline = True
        End With

        ' asking the user for POPULATION field
        iField = Val(InputBox(sMessage, "Enter the index of POPULATION field", "-1"))
        If iField < 0 Or iField >= sf.NumFields Then
            MsgBox "Invalid index of field", vbInformation
            Exit Sub
        End If

        ' the program needs to know values of what field should be compared
        ' to minValue maxValue properties of categories
        sf.ClassificationField = iField

        ' determining which labels belong to which category (see Label.Category property)
        sf.ApplyCategories

        ' setting graduated colors
        lb.SetGraduatedColors leFont, vbBlack, vbRed, True

        ' labels will be above all layers
        lb.VerticalPosition = vpAboveAllLayers

        ' setting halo for better visibility; we could set it for Labels class initially
        ' the category is initialized by the current settings of parent Labels class
        For i = 0 To lb.NumCategories - 1
            lb.Category(i).HaloSize = 4
            lb.Category(i).HaloColor = vbWhite
            lb.Category(i).HaloVisible = True
        Next i

        ' halo is darwn better in GDI
        sf.Labels.UseGdiPlus = False

        ' to see labels
        Map1.ShapeDrawingMethod = dmNewWithLabels

        ' refreshing the map
        Map1.Redraw
    End Sub



Edited 2 time(s). Last edit at 02/13/2010 01:31PM by Sergei.

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: pmeems ()
Date: February 16, 2010 08:32AM

Thanks Sergei for all your hard work.

Here are some screen shots.

Single layer:


Multiple layers:

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: Sergei ()
Date: March 22, 2010 08:13AM

Hi Brian,

sorry for the delayed answer, I haven't noticed your post.

Naturally I use GDI+ for transparency. New labels can be drawn in both GDI (which is faster) and GDI+ (which is nicer) modes. And they should work in the layout plug-in as long as labels are displayed on the map. If not - let me know.

1. The labels on the edges of the map aren't drawn... Well, the easy solution is just to enlarge bounds which you pass to the procedure and then return only a necessary part of the resulting bitmap. All labels will be there I expect. Of course, it's possible to extend only certain bounds. But some labels will be clipped inescapably.

To make it better I'd do the following:
- add read-only Height and Width properties to the Label class;
- add procedure void Labels::CalculateLabelSize(double scale);
- having label position and their size in pixels, it's quite easy to determine which one will fall into certain extents without clipping;
- then just set Label.Visible property to false for unwanted labels.

But perhaps it'd be easier to implement this logic in the drawing procedure itself. To make it more unified I'd add property ShowClippedLabels without specifing the side of the screen. Why side is critical to you?

2. As for scaling, I'd try Labels.BasicScale. It's possible to change the value, make a screenshot and return it back.

Sergei

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: bmarch ()
Date: March 22, 2010 02:58PM

Hi Sergei,

Thanks for the answer.

The layout engine is actually doing exactly what you describe. I find the biggest label. Figure out how big an overlap is needed and then request an extent such that the labels don't get cut off. The problem is it ends up rendering lots of map that just gets deleted when I finally glue the tiles all together.

I need to be able to specify a side so that tiles that are at the edge of the printed map wont have labels hanging of their edges.


I'll look at BasicScale.

Brian

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: bmarch ()
Date: March 23, 2010 08:45AM

Hi Sergei,

I thought it would be a good idea to move this over here since we've gotten off topic and into development land.

Brian

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: Sergei ()
Date: March 25, 2010 03:08AM

Brian,

It seems unlogical to me to use tiles. Why not to generate the image of the necessary resolution at once? What are the limitations of ocx that prevent doing this?

Sergei

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: bmarch ()
Date: March 25, 2010 08:44AM

Hi Sergei,

The OCX doesn't seem to able to render to a bitmap big enough for printing. A 36inch X 36inch map has a resolution of 10800 X 10800 at 300PPI (Which is a minimum for printing, computer screens us 96PPI) thats 116.6 megapixels! The largest bitmap I've been able to get out of the OCX has been around 2000 X 2000 depending on how many layers are loaded. If you can think of another way to get the OCX to render at such a high resolution without using tiles I'd love to hear it.

Brian

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: Sergei ()
Date: March 25, 2010 01:51PM

And what happened when you tried to get higher resolution?

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: bmarch ()
Date: March 25, 2010 04:19PM

Hey Sergei,

Well actually it just returned a null object. No error message or anything, but the image object that I got back from the snapshot function was null. Lowering the size of the image to something smaller fixed it and I started getting proper bitmaps back. After talking with Chris (or Ted) about it a bit we concluded that a tiling scheme would probably be the simplest solution. But if you know of a way to get the OCX to render to a bitmap that big please let me know. If not then lets start considering getting the labels to show up without needing the big overlap.

Brian

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: Sergei ()
Date: March 26, 2010 01:07PM

Brian,

Ok, I'll take a look at the problem when I have some spare time (perhaps in the next week). Something can be done with it I think.

Sergei

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: Sergei ()
Date: March 28, 2010 09:44AM

I've worked with it a bit. There is a problem indeed to return the large image form the MapWinGis. The largest screenshot I was able to get is 11300 X 8600 pixels, which takes 277 MB on the HDD. Also I succeeded in creating a DIBSection bitmap of larger size, but couldn't pass it to the image class. It's strange but in both cases I ended up approaching 600 Mb of RAM usage for the application. After it I couldn't allocate more memory with GDI functions or new calls. But I got 2 GB of RAM and didn't approach the limit.

The largest image I was able to save with the simple code below (C#) is 556 MB of size :

       MapWinGIS.Image img = new MapWinGIS.Image();
       if (img.CreateNew(15000, 13010))
       {
            img.Save("e:\\snapshot.bmp", false, ImageType.BITMAP_FILE, null);
       }

So there is question: what is preventing to allocate more?
I have no answer for it now. Can anybody try the code on the other PC?

Sergei

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: bmarch ()
Date: March 29, 2010 08:20AM

Hi Sergei,

Thanks for looking at this more for me. I've never managed to get it to save any image close to that big before. It also seems to be dependent on the number of layers you have loaded and how big they are. Which is why we stated this whole tiling business to begin with.

I was thinking, for the printing code, I don't absolutely need a MapWinGIS.Image file. (Since I save it to disc and close it anyways) If we could get the OCX to render at higher resolution to a DIB then we can save it to disc as a BMP and then I could just pass a filename into the snapshot function and then open the file later. Alternatively it could return a DIB as an object and I could try converting it into something that .NET can handle. It would really speed of printing I think If I could avoid writing the Map to disc every time I render it.

Also Sergei, I'm not sure that this is going to solve our problem anyways. Ted and I have not managed to create Bitmap objects in .NET that are larger than 8000x8000. So even if we manage to get the OCX to write to a bitmap thats bigger than that we won't be able to load it so that we can print from it.

Do you think it would be possible to pass a .NET graphics object into the OCX and have it draw to that? That way we wouldn't really be dealing with bitmaps any more but graphics objects which would keep all the vector data as vectors and could one day be extended to allow for printing to layered PDF's. Thats Paul Meeme's dream I think = )

Brian



Edited 3 time(s). Last edit at 03/29/2010 10:00AM by bmarch.

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: Sergei ()
Date: March 29, 2010 10:09AM

Brian,

to save the bitmap directly to the disk not using Image class is the idea I like. Creating image class means double use of RAM and even more because of some inoptimalities.

The other limitation is the usage of CreateDiscardableBitmap or CreateCompatibleBitmap functions. If I understand it right the size of the DDB bitmap created by them is limited by the video memory (256 MB on my PC). And indeed the function returned false when I requested larger bitmaps.

To draw larger bitmaps I used CreateDIBSection function. What is needed now is to store such bitmap to the HDD without using Image class. I'll try to do it the next time. I guess that 400-500 MB bitmap is enough for your purposes?

After reading your edits:
I thought you got no limitation in .NET. If they exist then indeed writing to HDD isn't the best solution. Passing .NET graphics object to ActiveX - sounds unreal. But I'll try to google something ;)

Sergei



Edited 1 time(s). Last edit at 03/29/2010 10:18AM by Sergei.

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: Sergei ()
Date: March 29, 2010 01:00PM

Brian,

It seems to be working. I've drawn a line on the .NET form from MapWinGis. Can you pass window handle of the layout control (System.IntPtr) in such way?

------------MapWinGis-------------
STDMETHODIMP CImageClass::DrawToNetWindow(int** hWnd)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	HWND hWND = (HWND)hWnd;
	
	HDC hdc = GetDC(hWND);
	if (!hdc)
	{
		return S_OK;
	}
	
	MoveToEx(hdc, 0,0, NULL);
	LineTo(hdc, 100, 100);
	
	return S_OK;
}

------------Client C#-------------
private void button1_Click(object sender, EventArgs e)
{
	MapWinGIS.Image img = new MapWinGIS.Image();
	img.DrawToNetGraphics(this.Handle);
}
Sergei

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: bmarch ()
Date: March 29, 2010 02:20PM

Hi Sergei,

That looks promising, I'm not drawing directly to a form though, I'm drawing to a graphics object which might have originated as a form, bitmap or printer. What if I get the graphic object's HDC and send that to the OCX as a IntPtr. We could pass the extent, resolution (int width) and HDC into the ocx and have it do the drawing and return.

As a test we can do:

-----C#-----

private void TestDraw(Graphics g)
{
IntPtr HDC = g.GetHdc();
m_Map.SnapShot4(extent, width, HDC);
}


Brian

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: Sergei ()
Date: March 29, 2010 06:10PM

Brian,

it works in such way as well (C#). But problems are possible. For example I got System.InvalidOperationException: 'Object is currently in use elsewhere' for the first version of my code. I'll try to write SnapShot4 function with the parameters you suggested, then will see how it works ;)

private void Form1_Paint(object sender, PaintEventArgs e)
{
    MapWinGIS.Image img = new MapWinGIS.Image();
    img.DrawToNetGraphics(e.Graphics.GetHdc());
}

Sergei

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: bmarch ()
Date: April 08, 2010 02:13PM

Hi Sergei,

I had an idea, instead of trying to get a DC from a GDI+ graphics object. What if we drew to a WMF or EMF or EMF+ that way we could still write it do disc but when I loaded it up in .NET it would preserve all of the vector information as vectors?

What do you think?

Brian

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: Sergei ()
Date: April 09, 2010 01:09AM

Good idea too. You have no problems with scaling of lines and labels in this case I suppose. But still the idea with the drawing direct to DC is worth trying. I'll be busy with the image resampling for several more days. After it I'll return to this problem.

Sergei

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: Sergei ()
Date: April 24, 2010 05:09PM

Brian,

I found the time at last to write Map.SnapShotToDC ;) It draws the map on the form or picturebox quite good. I tried to set width = 20000 and it works ok, but it can be misguiding, as actually only a small portion of this huge bitmap is drawn. Perhaps I'll add code for scaling of lines and labels to the procedure as well. Most likely I'll commit the changes tomorrow.

Also could you shift this discussion to separate topic? It has little to do with labeling ;)

Sergei



Edited 1 time(s). Last edit at 04/24/2010 05:12PM by Sergei.

Options: ReplyQuote
Re: Labeling US cities with new labeler
Posted by: bmarch ()
Date: March 15, 2010 09:02AM

Hey,

Those look absolutely great, how did you get an alpha channel to work in GDI? I thought only GDI+ supported it.

Paul by any chance does this work in my layout plug-in or is that going to need to be updated?

Sergei I have a technical question for you:
The print layout plug-in that I developed uses the SnapShot3(...) method to capture images from the OCX so that they can be printed from .NET Because of limits in the OCX I have to call SnapShot3(...) multiple times in order to generate a single high resolution image to print with. The problem is that labels that are on the edges of these images aren't drawn. Since you are digging around in the drawing code already, would it be possible to modify the SnapShot3(...) code so that labels that overlap with the edge of the image are still drawn, but only on select Edges. Something like this.

SnapShot4(double MapGeoWidth, int pixelHeight, int pixelWidth, bool labelTop, bool labelBottom, labelLeft, labelRight)

Which would return a screen shot of the given extent at the resolution needed but only have labels hanging off the edges corresponding to the booleans given.

What do you think, can it be done? If I'm not clear please let me know.


Another technical questions would it be possible to also add a variable to SnapShot method that would draw all the labels scaled by a certain factor, but just for that one screen shot. At the moment I'm doing some ugly stuff to get the labels to draw nicely at 300DPI instead of 96DPI, I'm actually changing all of the label point sizes calling SnapShot3(...) and then changing them all back.


Brian M.



Edited 1 time(s). Last edit at 03/15/2010 09:05AM by bmarch.

Options: ReplyQuote
Pages: 12Next
Current Page: 1 of 2


Sorry, only registered users may post in this forum.





Banner Exchange




GISCP.com




Send us your banner logo (160x120) for the space above, and add this MapWindow banner ad to your site:

Just paste this text in your page: