Adding x number Markers to Codenameone MapContainer at runtime - codenameone

I have retrieved a large number of latlong values and stored in an ArrayList. I want to add markers to my MapContainer using the latlong values in the ArrayList. The challenges however is that the pins are never positioned correctly on the map (simulator and device). I have tried everything but still no success. Here is a working example. This works but the pins are positioned wrongly at runtime.
Form hi = new Form("cReporter", new BorderLayout());
hi.setScrollableY(false);
Container n = new Container(new BorderLayout());
MapContainer mc = new MapContainer();//
mc.setShowMyLocation(true);
hi.add("Center", mc);
List lx = new ArrayList();
addPoints(lx);
hi.show();
addMarkers(mc, lx);
public void addPoints(List lx)
{
Map m= new HashMap();
m.put("long", 8.993082);
m.put("lat", 38.747393);
lx.add(m);
m= new HashMap();
m.put("long", 8.988419);
m.put("lat", 38.727094);
lx.add(m);
m= new HashMap();
m.put("long", 8.991724);
m.put("lat", 38.775203);
lx.add(m);
}
public void addMarkers(MapContainer mc, List coordList)
{
Style s = new Style();
s.setFgColor(0xff0000);
s.setBgTransparency(0);
FontImage markerImg = FontImage.createMaterial(FontImage.MATERIAL_PLACE, s, 5);
for(Object m: coordList)
{
Map p= (Map)m;
Coord moscone = new Coord(Double.parseDouble(p.get("lat").toString()), Double.parseDouble(p.get("long").toString()));
mc.addMarker(EncodedImage.createFromImage(markerImg, false), mc.getCameraPosition(), "Hi marker", "Optional long description", new ActionListener() {
public void actionPerformed(ActionEvent evt) {
System.out.println("Bounding box is "+mc.getBoundingBox());
ToastBar.showMessage("You clicked the marker", FontImage.MATERIAL_PLACE);
}
});
mc.setCameraPosition(moscone);
mc.revalidate();
}
}

I'm guessing that you aren't using the correct projection lat/long values for the map as these vary based on different implementations. See this for discussion of the different types of positioning: Source event strange latitude & longitude
I notice you create a new encoded image for every marker position. This is very inefficient as every such image would take up RAM and doing it x100 could introduce a serious overhead. This is an image you should reuse.

Related

Winforms pie chart legend text length affects label and chartarea size

I have the following ChartArea Annotation settings set up:
private void chart1_PrePaint(object sender, ChartPaintEventArgs e)
{
if (e.ChartElement is ChartArea)
{
var ta = new TextAnnotation();
ta.IsMultiline = true;
ta.Text = "Results of Calculation\n%";
ta.Width = e.Position.Width;
ta.Height = e.Position.Height;
ta.X = e.Position.X;
ta.Y = e.Position.Y;
ta.Font = new Font("Candara", e.Position.Height / 10, FontStyle.Regular);
chart1.Annotations.Add(ta);
}
}
A few issues with this, and with the Legend in relation to my other posted question:
My other Pie Chart Legend/ChartArea question
With this PrePaint setup, I'm not sure if my position is correct for the TextAnnotation. I'm using the e.Position but it's coming out not "exactly" centered in the middle of the doughnut of the pie chart area. I'd like it to be centered perfectly. Not sure what other property to use here.
A second issue is that when Legend text length changes, it "pushes" and makes the ChartArea itself smaller so the pie chart gets smaller. I'd like it to be the other way around, where the ChartArea pie chart stays the same size but the Legend gets pushes aside.
Is this possible?
The following is the position setup of the pie chart:
Thanks
I'm sorry I couldn't help more, last time. I tested the centering of the TextAnnotation and in fact it has problems when the InnerPlotPosition is set to auto. Moreover, the answer found at link creates a new instance of the TextAnnotation at every PrePaint, causing the overlapping of TextAnnotations and the blurrying of the centered text.
I couldn't find a way to avoid the resizing of the doughnut (I'm not sure it's even possible, at this point...I'll wait for some other answers) but maybe this can work out as a workaround.
First I created a dictionary to store the centered TextAnnotations references (the key is the graph name, in case you have more than one), then in the PrePaint event I get the correct reference of the TextAnnotation used in the graph and update the coordinates of that one.
Second, I set the InnerPlotPosition manually, this seems to solve the problem of the centering of the TextAnnotation. Of course, you need to specify coordinates and size for the InnerPlot like I did with the line:
chart1.ChartAreas[0].InnerPlotPosition = new ElementPosition(0, 0, 60.65f, 94.99f);
Lastly, I set the position and the size of the legend manually and, with the extension method WrapAt I set a "line break" every _maxLegendTextBeforeWrap in the legend items text. Couldn't find a way to make it dynamically change with the width of the legend area, so it has to be set manually.
Below there's a GIF of the resulting effect. Don't know if this suits you as a solution (too much tweaking and code, for my taste), but anyway. Maybe this can trigger some new ideas on how to solve.
To do so I created these global variables:
/// <summary>
/// Saves the currently doughnut centered annotations per graph.
/// </summary>
private IDictionary<string, TextAnnotation> _annotationsByGraph;
/// <summary>
/// Number of characters
/// </summary>
private int _maxLegendTextBeforeWrap = 10;
/// <summary>
/// Legend area width.
/// </summary>
private int _legendWidth = 20;
/// <summary>
/// Legend area height.
/// </summary>
private int _legendHeight = 90;
This is the handler of the Load event:
private void ChartTest_Load(object sender, EventArgs e)
{
// ** Start of test data **
chart1.Series["Series1"].Points.AddXY("A", 33);
chart1.Series["Series1"].Points[0].LegendText = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
chart1.Series["Series1"].Points.AddXY("B", 33);
chart1.Series["Series1"].Points[1].LegendText = "BBBBBBBBBBBBBBBBBBBB";
chart1.Series["Series1"].Points.AddXY("C", 34);
chart1.Series["Series1"].Points[2].LegendText = "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
// ** End of test data **
// Creates a new instance of the dictionary storing the references to the annotations.
_annotationsByGraph = new Dictionary<string, TextAnnotation>();
// Createa a new instance of an annotation for the chart1 graph.
_annotationsByGraph.Add(chart1.Name, new TextAnnotation());
// Manually setting the position of the chart area prevents the imperfect positioning of the
// TextAnnotation at the center of the doughnut.
chart1.ChartAreas[0].Position.Auto = true;
// Manually set the position of the InnerPlotPosition area prevents the imperfect positioning of the
// TextAnnotation at the center of the doughnut.
chart1.ChartAreas[0].InnerPlotPosition.Auto = false;
chart1.ChartAreas[0].InnerPlotPosition = new ElementPosition(0, 0, 60.65f, 94.99f);
// Minimum size for the legend font.
chart1.Legends[0].AutoFitMinFontSize = 5;
// Set the legend style as column.
chart1.Legends[0].LegendStyle = LegendStyle.Column;
// Splits the legend texts with the space char every _maxLegendTextBeforeWrap characters.
chart1.Series["Series1"].Points.ToList().ForEach(p => p.LegendText = p.LegendText.WrapAt(_maxLegendTextBeforeWrap));
}
This is the handler of the PrePaint event:
private void chart1_PrePaint(object sender, ChartPaintEventArgs e)
{
if (e.ChartElement is ChartArea)
{
// Get the reference to the corresponding text annotation for this chart.
// We need this, otherwise we are creating and painting a new instance of a TextAnnotation
// at every PrePaint, with the resulting blurrying effect caused by the overlapping of the text.
var ta = _annotationsByGraph[e.Chart.Name];
// Check if the annotation has already been added to the chart.
if (!e.Chart.Annotations.Contains(ta))
e.Chart.Annotations.Add(ta);
// Set the properties of the centered TextAnnotation.
ta.IsMultiline = true;
ta.Text = "Results of Calculation\nx%";
ta.Font = new Font("Candara", e.Position.Height / 10, FontStyle.Regular);
ta.Width = e.Position.Width;
ta.Height = e.Position.Height;
ta.X = e.Position.X;
ta.Y = e.Position.Y;
// Move the legend manually to the right of the doughnut.
e.Chart.Legends[0].Position = new ElementPosition(e.Position.X + e.Position.Width, e.Position.Y, _legendWidth, _legendHeight);
}
}
This is what the button does:
private void BtnChangeLegendItemLength_Click(object sender, EventArgs e)
{
if (chart1.Series["Series1"].Points[1].LegendText.StartsWith("DD"))
chart1.Series["Series1"].Points[1].LegendText = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".WrapAt(_maxLegendTextBeforeWrap);
else
chart1.Series["Series1"].Points[1].LegendText = "DDDDDD".WrapAt(_maxLegendTextBeforeWrap);
}
This is the extension method definition:
internal static class ExtensionMethods
{
public static string WrapAt(this string legendText, int maxLengthBeforeWrap)
{
if (legendText.Length <= maxLengthBeforeWrap)
return legendText;
// Integer division to get how many times we have to insert a space.
var times = legendText.Length / maxLengthBeforeWrap;
// Counter of added spaces.
var spacesAdded = 0;
// Iterate for each space char needed.
for (var i = 1; i <= times; i++)
{
// Insert a space char every maxLengthBeforeWrap positions.
legendText = legendText.Insert(maxLengthBeforeWrap * i + spacesAdded, new string(' ', 1));
spacesAdded++;
}
return legendText;
}
}

Place an Image scaled at the width available space

I created a code that works, but I'm not sure that it's the best way to place an Image scaled automatically to the available width space. I need to put some content over that image, so I have a LayeredLayout: in the first layer there is the Label created with the following code, on the second layer there is a BorderLayout that has the same size of the Image.
Is the following code fine or is it possible to do better?
Label background = new Label(" ", "NoMarginNoPadding") {
boolean onlyOneTime = false;
#Override
public void paint(Graphics g) {
int labelWidth = this.getWidth();
int labelHeight = labelWidth * bgImage.getHeight() / bgImage.getWidth();
this.setPreferredH(labelHeight);
if (!onlyOneTime) {
onlyOneTime = true;
this.getParent().revalidate();
}
super.paint(g);
}
};
background.getAllStyles().setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FIT);
background.getAllStyles().setBgImage(bgImage);
Shorter code:
ScaleImageLabel sl = new ScaleImageLabel(bgImage);
sl.setUIID("Container");
You shouldn't override paint to set the preferred size. You should have overriden calcPreferredSize(). For ScaleImageLabel it's already set to the natural size of the image which should be pretty big.

GMap.Net location on marker centralizes over point not above

I have just started using GMAP.Net and I'm Setting a custom Marker thus:
marker = new GMarkerGoogle(new PointLatLng(Convert.ToDouble(latlon[0]), Convert.ToDouble(latlon[1])), new Bitmap(Iconpath));
where Iconpath points to a 42 * 38 pixel image of type PNG.
however the image appears central and immediately above the point being set by the above. What I would like is to know how to set it so the center of the image in over the location.
Any idea how to do that?
this is in a winforms .Net 4.0 application.
I found there was an easy way to do this thus:
Bitmap imgMarker = new Bitmap(Iconpath);
marker = new GMarkerGoogle(new PointLatLng(Convert.ToDouble(latlon[0]), Convert.ToDouble(latlon[1])), imgMarker);
marker.Offset = new Point(imgMarker.Width/2, imgMarker.Height / 2);
hope it helps someone else!
The GoogleMarker class seems to be designed for push pin looking images, where you would want the pin tip to be directly above the point of interest. Your best bet is to inherit the marker class and make your own class where you can control the image placement. Like this:
class customImageMarker: GMapMarker
{
Bitmap Bitmap;
public customImageMarker(PointLatLng p, Bitmap Bitmap)
: base(p)
{
this.Bitmap = Bitmap;
Size = new System.Drawing.Size(Bitmap.Width, Bitmap.Height);
Offset = new Point(-Size.Width / 2, -Size.Height / 2);
}
public override void OnRender(Graphics g)
{
g.DrawImage(Bitmap, LocalPosition.X - Offset.X, LocalPosition.Y - Offset.Y, Size.Width, Size.Height);
}
}
Now just call your class:
marker = new customImageMarker(new PointLatLng(Convert.ToDouble(latlon[0]), Convert.ToDouble(latlon[0])), new Bitmap(Iconpath));
You can control the placement of the icon by adjusting the Offset variable.

GMAP.NET adding labels underneath markers

I have just started using gmap.net and I was looking for the functionality of adding labels under the markers. I see there's tooltips but I would like to have a constant label under my marker with a one word description.
I searched for docs or other answers but I cannot find anything which leads me to believe that it is not implemented. If someone can verify this I would appreciate it.
You need to create your own custom marker.
Based on the source of GMapMarker and the derived GMarkerGoogle I came up with this simplified example:
public class GmapMarkerWithLabel : GMapMarker, ISerializable
{
private Font font;
private GMarkerGoogle innerMarker;
public string Caption;
public GmapMarkerWithLabel(PointLatLng p, string caption, GMarkerGoogleType type)
: base(p)
{
font = new Font("Arial", 14);
innerMarker = new GMarkerGoogle(p, type);
Caption = caption;
}
public override void OnRender(Graphics g)
{
if (innerMarker != null)
{
innerMarker.OnRender(g);
}
g.DrawString(Caption, font, Brushes.Black, new PointF(0.0f, innerMarker.Size.Height));
}
public override void Dispose()
{
if(innerMarker != null)
{
innerMarker.Dispose();
innerMarker = null;
}
base.Dispose();
}
#region ISerializable Members
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
protected GmapMarkerWithLabel(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
#endregion
}
Usage (assuming a GMap instance named gm):
GMapOverlay markerOverlay = new GMapOverlay("markers");
gm.Overlays.Add(markerOverlay);
var labelMarker = new GmapMarkerWithLabel(new PointLatLng(53.3, 9), "caption text", GMarkerGoogleType.blue);
markerOverlay.Markers.Add(labelMarker)
I'll answer here because this is the first question that pops up when looking to display a text marker for the WPF GMAP.NET library. Displaying a text marker with the WPF version of the library is actually much easier than in WinForms, or at least than the accepted answer.
The GMapMarker in WPF has a Shape property of type UIElement, which means you can provide a System.Windows.Controls.TextBlock object to display a text marker :
Markers.Add(new GMapMarker(new PointLatLng(latitude, longitude))
{
Shape = new System.Windows.Controls.TextBlock(new System.Windows.Documents.Run("Label"))
});
Since the marker displays the top left portion of the shape at the given position, you can play with the GMapMarker.Offset property to adjust the text position according to its dimensions. For instance, if you want the text to be horizontally centered on the marker's position :
var textBlock = new TextBlock(new Run("Label"));
textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
textBlock.Arrange(new Rect(textBlock.DesiredSize));
Markers.Add(new GMapMarker(new PointLatLng(request.Latitude, request.Longitude))
{
Offset = new Point(-textBlock.ActualWidth / 2, 0),
Shape = textBlock
});
The solution to get the TextBlock's dimensions was quickly taken from this question, so if you need a more accurate way of getting the block's dimensions to play with the offset I suggest you start from there.

WPF, app looses ability to create or manipulate GUI objects

i need some help with WPF
I'm getting started with WPF and wanted to make a simple snake game with the following classes:
GameBoard : Window
{
private Game game;
public GameBoard(){ this.game = new Game(this); }
public Rectangle rectangleFactory(int x, int y)
{
Rectangle rect = new Rectangle();
rect.Width = 10;
rect.Height = 10;
GameArea.Children.Add(rect); /// My Canvas is called GameArea
Canvas.SetLeft(rect, 10 * x);
Canvas.SetTop(rect, 10 * y);
return rect;
}
public void rectangleBin(Rectangle)
{
GameArea.Children.Remove(rect);
}
}
Thing{
private GameBoard Board;
private System.Windows.Shapes.Rectangle rect;
private int[] coordinaters;
public Thing(GameBoard board, int[] coordinates)
{
this.Board = board;
Draw();
}
public Draw(){ rect = Board.rectangleFactory(coordinates);}
public Dispose(){ Board.rectangleBin(rect)}
}
SnakeSegment : Thing
{
SnakeSegment next;
public SnakeSegment(GameBoard board, int[] coordinates) : base(board, coordinates) { }
}
Apple : Thing()
{
public Apple(GameBoard board, int[] coordinates) : base(board, coordinates) { }
public void newApple()
{
Dispose();
getNewCoordinate();
Draw();
}
}
Game
{
private GameBoard Board;
private Apple apple;
Private Snake snake;
private static System.timers.timer timer
public Game(GameBoard board)
{
this.Board = board;
this.apple = new Apple(board, int start_coordinates);
this.snake = new snake(this);
this.timer = new Timer(100);
timer.Elapsed += new ElapsedEventHandler(update_game);
}
public void update_game(object o, ElapsedEventArgs s){ snake.move_forward(); }
}
Snake
{
private SnakeSegmet Head;
Private SnakeSegment Tail
private GameBoard Board;
public Snake(Game game)
{
this.Board = game.Board;
this.Head = new SnakeSegment(Board, start_cooridantse)
this.Tail = Head;
}
private void AddSegment()
{
Head.Next = new SnakeSegment(Board, newCoordinates);
Head = Head.Next;
}
}
There is of course more but i left it out in the interest of readability.
When i start the game it draws the game area and adds an apple and an initial rectangle for the snake, so far so good
But then i update the game and things go south
my next call to SnakeSegment constructor in Head.Next = new SnakeSegment()
ends in rectangleFactory when calling new Rectangle()
the program doesn't crash, but it topples the current stack and nothing more is executed untinl the timer raises an other event
ive tried to troubleshoot and reroute the flow a bit and if i instead call Apple::newApple() the same thing happens when i get to GameArea.Children.Remove(rect);
Im completely new to WPF and this really gives me a headache, if anybody could explain why it happens id be very grateful.
Regards, Tobias
If you want to write your program like that, use WinForms. If you want to use WPF, you'll need to pretty much start again from scratch. WPF is a data-centric language... we manipulate data objects, not UI objects.
Using WPF, you'd need to think about your game objects as a collection of data objects. The board would then be the UI container control that you data bind your collection to. You would then define what each object should look like in a DataTemplate in the XAML. After binding the location properties of the data objects to the location properties of the UI objects you could then move the objects in the UI by changing the location properties of the data objects from code.
Having said all that, I think that you'll find that WPF is not a good Framework for making games. Its main strengths are its data binding capabilities and rich UI possibilities, but it's not very efficient. You'll need a good graphics card to get the most out of it.

Resources