DotSpatial / DotSpatial

Geographic information system library written for .NET
MIT License
881 stars 368 forks source link

Drawing order of labels incorrect #1226

Closed carloslubbers closed 4 years ago

carloslubbers commented 5 years ago

Steps to reproduce

  1. Open the DemoMap application
  2. Import two layers from (for example) the same file
  3. Set labeling to both layers, a different value on each
  4. Set both layers to visible

DotSpatial version: Both in 1.8 and latest master

Expected behaviour

The labels of the layer that is sorted on top should display on the map

Actual behaviour

The labels of the bottom layer is displayed

Top layer visible: image

Bottom layer visible: image

Both layers visible: image

xc398013967 commented 5 years ago

Why do I not label on the mapPointLayer layer?

DotSpatial2.0 make the layer annotation label is not display,Below is my annotated test code, which is not displayed.

I added my dot layer pl in _map. MapFrame. Layers, but did not show the dots added, so I set the initial range _map. MapFrame. Invalidate (List < Extent >); but I don't know how to write it here. Here's my code, but it doesn't show dots and annotations.

public partial class Form1 : Form { private Map _map = null; public Form1() { InitializeComponent(); _map = new Map() { Dock = DockStyle.Fill, FunctionMode = FunctionMode.Pan, Projection = KnownCoordinateSystems.Geographic.World.WGS1984, }; this.Controls.Add(_map); drawPoint(); } private void drawPoint() { Coordinate coor = new Coordinate(108.78, 34.44); NetTopologySuite.Geometries.Point pt = new NetTopologySuite.Geometries.Point(coor); MapPointLayer pl = new MapPointLayer(); pl.Symbolizer = new PointSymbolizer(Color.Red, DotSpatial.Symbology.PointShape.Triangle, 30); pl.ShowLabels = true; _map.MapFrame.Layers.Add(pl); //here add point layer DataTable dt = new DataTable(); dt.Columns.Add("id", typeof(int)); pl.FeatureSet.DataTable = dt; DotSpatial.Data.Feature fea = new DotSpatial.Data.Feature(pt); DataRow row = dt.NewRow(); row.SetField("id", 10); DotSpatial.Data.IFeature feature = pl.FeatureSet.AddFeature(fea.Geometry); feature.DataRow = row; IMapLabelLayer lableLayer = new MapLabelLayer(); lableLayer.Projection = _map.Projection; ILabelCategory category = lableLayer.Symbology.Categories[0];// lableLayer.Symbology.Categories[0];// new LabelCategory();// lableLayer.Symbology.AddCategory(); category.Expression = "[id]"; lableLayer.CreateLabels(); category.Symbolizer.HaloColor = Color.Green; category.Symbolizer.BorderColor = Color.Green; category.Symbolizer.BorderVisible = true; category.Symbolizer.BackColorEnabled = true; category.Symbolizer.FontStyle = FontStyle.Bold; category.Symbolizer.FontColor = Color.Black; category.Symbolizer.BackColor = Color.Green; category.Symbolizer.FontSize = 30; category.Symbolizer.Orientation = ContentAlignment.TopCenter; category.Symbolizer.Alignment = StringAlignment.Center; category.Symbolizer.PriorityField = "FID"; List listExtent = new List(); Extent extent = new Extent(108.722938537598, 34.4265341, 108.795059204102, 34.4521255493164); //map view extent listExtent.Add(extent); _map.MapFrame.Initialize(listExtent); //how to write here _map.MapFrame.Invalidate(); } }

sindizzy commented 5 years ago

I think that is because layers are drawn from the bottom-->up. That means you start with the layer on the bottom and draw it and its labels. Then the one above that but if those labels collide with labels that are already on the map then they dont get drawn. You could try to tun off label collision and put the labels in a different quadrant w.r.t to the point.

carloslubbers commented 5 years ago

Hmm.. Interesting, doesn't seem like that would be the expected behavior, labels drawn first that collide with later drawn labels should be removed in favor of the last ones I'd say. I'd try to fix it up in the code but I'm not very familiar with the dotspatial code base myself unfortunately

jany-tenaj commented 5 years ago

That sounds like a lot of unnessessary calculations.

sindizzy commented 5 years ago

I would expect this behavior because that;s how layering programs work for the most part. Again try to offset the labels on your top layer and that should probably help.

VectorZita commented 5 years ago

I agree with @carloslubbers, because the expected functionality is that the topmost layers "override" (i.e. cover) the lower layers in the drawing hierarchy, and so should their labels. The way labels are drawn, imagine two polygon layers that coincide, with the topmost layer being drawn, but with the labels of the bottommost being displayed instead. This is not consistent, and I believe doesn't happen in other GIS applications. Thankfully, this is very easy to fix, although it very closely resembles a "hack", because the "built-in" functionality still does not behave in the manner @carloslubbers describes (i.e. later labels should replace earlier labels upon collision).

@carloslubbers, if you want to achieve this functionality in your own codebase (or if anyone wants to have this "fixed"), the easy way would be to visit the MapFrame.cs file and check the Initialize(List<Extent> regions) function. Somewhere in the code (around line 725), you can see:

// Then labels
MapLabelLayer.ClearAllExistingLabels();
foreach (var layer in Layers)
{
     InitializeLabels(regions, args, layer);
}

It is important to notice at this point, that the hierarchical behavior is implemented in a manner of going "up from 0", as in, layers further in the Layer collection list are more important. This is stated here to prevent the misunderstanding that layers appearing further up in the Legend control have smaller indices in the collection... they don't. The topmost layer in the Legend control is the last layer in the collection.

So, this is it. Instead of foreach, this piece of code should iterate backwards, so that the topmost layer labels were drawn instead, and then, the default behavior of earlier drawn labels overriding later labels would result in having the topmost labels dominating the lower layer labels in the drawing hierarchy (which I once again agree with in terms of behavior, as I have explained earlier).

Because IMapLayerCollection, which is what the Layers variable is (see _layers), is, down the Interface implementation road, an IList, it also offers direct indexing, therefore the code above becomes something along the lines:

MapLabelLayer.ClearAllExistingLabels();
for (int i = Layers.Count-1; i >= 0; i--)
{
     InitializeLabels(regions, args, Layers[i]);
}

Normally, this should achieve the result @carloslubbers is expecting. Once again, I would be in favor of this being the default functionality (layers higher in the draw hierarchy should be drawn on top, and so should their labels).

However, whether labels should concede to successors upon collision is definitely a matter of debate, I can't say I would prefer one behavior over another, plus the implemented behavior is, as I have shown above, rather easy to turn onto its head to achieve the exactly opposite result ;)

@carloslubbers, I would appreciate it if you could give it a shot and let us know whether this workaround offers the desired functionality!

carloslubbers commented 5 years ago

Thanks for the suggestion @VectorZita, if I get to try out this workaround I'll report back but it looks promising

rupakraj commented 4 years ago

Hello, I am also facing the same issue as it is discussed on this thread. I am using latest source code from master branch.

When Map is rendered on the PrintLayout, the polygons label goes backward.

image

I checked the code of MapFrame.cs file Initialize(List<Extent> regions) method. It is already fixed as suggested by @VectorZita

What might be the workaround for this>