Reputation: 23
I am creating a Java program that receives and plots GPS coordinates over a TCP socket connection on a 2D image of a map. The constructor creates the JFrame and graphical components and then starts off a SwingWorker thread to handle getting coordinates from the socket and then drawing an oval to represent the fix on the map.
I am able to always receive data over the connection but the program does not reliably draw points over the image.
Any suggestions and thank you!
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.io.File;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import connectivity.TelitSocketServer;
import calculations.PlottingMath;
import objects.ElephantFix;
@SuppressWarnings("serial")
public class Gui
{
protected JFrame mainWindow;
protected JPanel mapArea = new JPanel();
private ReceiveFix rfix;
public Gui()
{
// TODO Auto-generated constructor stub
mainWindow = new JFrame();
// Load map image
Image map = null;
try
{
File mapImage = new File("map_images/AWE_PLOT.JPG");
map = ImageIO.read(mapImage);
} catch (Exception e)
{
System.out.println(e.toString());
}
JLabel label = new JLabel(new ImageIcon(map));
mapArea.add(label);
// Map Image Dimensions
mainWindow.getContentPane().add(mapArea, "Center");
mainWindow.setSize(471, 670);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setTitle("ElePlotter");
mainWindow.setVisible(true);
rfix = new ReceiveFix();
rfix.execute();
}
public static void main(String args[])
{
new Gui();
}
private class ReceiveFix extends SwingWorker<Void, ElephantFix>
{
@Override
protected Void doInBackground()
{
// Start the server
String fix = "";
TelitSocketServer currentConnection = new TelitSocketServer();
try
{
// Wait for client to connect
currentConnection.intializeConnection();
while (true)
{
// Parse and convert received GPS fix into arc radians
fix = currentConnection.readLine();
String[] split = fix.split(" ");
double latWholeDegrees = Double.parseDouble(split[0]
.substring(0, 3));
double longWholeDegrees = Double.parseDouble(split[1]
.substring(0, 3));
double latMinutes = Double.parseDouble(split[0]
.substring(3)) * .166667;
double longMinutes = Double.parseDouble(split[1]
.substring(3)) * .166667;
double lat = latWholeDegrees - latMinutes / 10;
double lon = longWholeDegrees + longMinutes / 10;
publish(new ElephantFix(lat, lon));
}
} catch (Exception e)
{
e.printStackTrace();
}
// Return null if somehow unable to publish node data
return null;
}
@Override
protected void process(List<ElephantFix> fixes)
{
int x, y;
// Get the most recently published node
ElephantFix aFix = fixes.get(fixes.size() - 1);
// Translate lat/long into map X/Y pixel
x = PlottingMath.getCurrentPixelX(aFix.getLatitude(),
aFix.getLongitude());
y = PlottingMath.getCurrentPixelY(aFix.getLatitude(),
aFix.getLongitude());
// Plot on image
Graphics g = mapArea.getGraphics();
g.setColor(Color.RED);
g.fillOval(x, y, 15, 15);
// mapArea.validate();
//
// mapArea.repaint();
//
// Gui.this.repaint();
}
}
}
Upvotes: 2
Views: 2967
Reputation: 347332
mapArea.getGraphics();
is not how custom painting in Swing is done.
Start by taking a look at Performing Custom Painting for more details.
Adding a component to a container ensures that the child component will always be painted over the container...
JLabel label = new JLabel(new ImageIcon(map));
mapArea.add(label);
You have that backwards. It might be better to add both the label
and mapArea
to the same container
The next problem you will need to overcome is the fact that you will need to get your painting component to reside over the top of your label.
To this end you could...
OverlayLayout
GridBagLayout
Update with example
There are any number of ways to achieve this, you could use multiple components which act as layers onto which you could add other components or perform custom painting.
This is not as easy it as it sounds. Most layout managers don't consider two components sharing the same space. You also have to manage the connection between the layers, so if the map is centered within the lowest layer, but the available space is larger or smaller than the map, offsets begin to drift...messy...
A simpler solution would be to use a single component and render all the content onto it, for example...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestMap {
public static void main(String[] args) {
new TestMap();
}
public TestMap() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
MapPane pane = new MapPane();
pane.dropPinAt(174, 147);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(pane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MapPane extends JPanel {
private BufferedImage img;
private BufferedImage pin;
private List<Point> pins;
public MapPane() {
pins = new ArrayList<>(25);
try {
img = ImageIO.read(getClass().getResource("/Map.jpg"));
pin = ImageIO.read(getClass().getResource("/Pin.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
@Override
public Dimension getPreferredSize() {
return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.drawImage(img, x, y, this);
for (Point p : pins) {
// Offset the pin x/y
// to allow the point to land on the desired location
p.x -= 6;
p.y -= 64;
System.out.println(p);
g2d.drawImage(pin, p.x, p.y, this);
}
g2d.dispose();
}
}
protected void dropPinAt(int x, int y) {
pins.add(new Point(x, y));
}
}
}
Upvotes: 4
Reputation: 285450
You should not be drawing with a Graphics object obtained by calling getGraphics()
on a Component. Doing this will give you a Graphics object that doesn't persist and an image that doesn't persist. Instead either draw on a BufferedImage with its Graphics object and display it in the GUI, or iterate through the data collection in the JPanel's paintComponent method and draw with the data obtained.
Upvotes: 2