Friday, August 28, 2009

Build your own GPS/GIS system in less than 200 lines with OpenMap


Here is how to do your own (simplistic) GPS/GIS system using OpenMap in less than 200 lines of code. To get started you will need to download and install OpenMap.

The GPS Layer for OpenMap is as follows. Notice that the code is somewhat crude with regards to threading, drizzel some synchronized on to it as appropriate.


public class GPSLayer extends OMGraphicHandlerLayer {

private static String GPSDATA = "gpsData";

private static final double KT2MPS = 1852.0 / 3600.0;

private static final double speedVectorLengthInMinutes = 6;

private String gpsDataPath = "";

private float latitude, longitude, speed, course;

private OMGraphicList graphics = new OMGraphicList();;

private OMRect gpsPosition = new OMRect(0, 0, 0, 0, 10, 10);

private OMLine speedVector;

private OMText gpsText = new OMText(10, 20, "GPS Data",
OMText.JUSTIFY_LEFT);

private Timer timer;

public GPSLayer() {
gpsText.setFillPaint(Color.WHITE);
gpsText.setTextMatteColor(new Color(182, 235, 219));
gpsPosition.setFillPaint(Color.pink);
}

@Override
public void setProperties(String prefix, Properties props) {
super.setProperties(prefix, props);
gpsDataPath = props.getProperty(prefix + "." + GPSDATA);
// redraw every 5 secs
timer = new Timer(5000, this);
timer.start();

// emulate reading GPS data (threading issue here access to members not
// protected!)
new Thread(new Runnable() {
public void run() {
try {
BufferedReader in = new BufferedReader(new FileReader(
gpsDataPath));
String str;
while ((str = in.readLine()) != null) {
Thread.sleep(100);
if (str.startsWith("$GPRMC")) {
String[] fields = str.split(",");
// utc_date = fields[1];
double lat = Double.parseDouble(fields[3]);
double degrees = Math.floor((lat / 100.0));
double minute = (lat / 100.0) - degrees;
lat = (degrees) + ((minute * 100.0) / 60);
if (fields[4].equals("S"))
lat = -lat;
latitude = (float) lat;

double lon = Double.parseDouble(fields[5]);
degrees = Math.floor((lon / 100.0));
minute = (lon / 100.0) - degrees;
lon = (degrees) + ((minute * 100.0) / 60);
if (fields[6].equals("W"))
lon = -lon;
longitude = (float) lon;

speed = (float) (Double.parseDouble(fields[7]) * KT2MPS);

if (!fields[8].equals("")) {
course = (float) Double.parseDouble(fields[8]);
}
// could parse date but to lazy
// date = fields[9];
}
}
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}

@Override
public synchronized OMGraphicList prepare() {
gpsPosition.setLocation(latitude, longitude, -5, -5, 5, 5);

// calc. speed vector length using OM GIS functions
LatLonPoint startPos = new LatLonPoint(latitude, longitude);
float length = (float) Length.KM.toRadians(speedVectorLengthInMinutes
* ((speed * 3.6) / 60.0));
LatLonPoint endPos = startPos.getPoint(length, (float) ProjMath
.degToRad(course));
speedVector = new OMLine(startPos.getLatitude(), startPos
.getLongitude(), endPos.getLatitude(), endPos.getLongitude(),
OMLine.LINETYPE_STRAIGHT);
// vec.addArrowHead(OMArrowHead.ARROWHEAD_DIRECTION_FORWARD, 100, 3, 1);
speedVector.setLinePaint(Color.DARK_GRAY);

graphics.clear();
// order of add determines what is rendered on top
graphics.add(speedVector);
graphics.add(gpsPosition);
gpsText.setData(String.format("GPS Data\n%4.2f Km/h",
(speed * 3.60)));
graphics.add(gpsText);

graphics.project(getProjection());
return graphics;
}

@Override
public void actionPerformed(ActionEvent ae) {
doPrepare();
}
}

If you have a GPS antenna you can use one of the free GPS servers (gpsd on linux) to make its data available on a socket and then modify the in variable in reader thread to the following:

uc = new URL(gpsDataPath).openConnection(); // gps.gpsData=http://localhost:2244
InputStreamReader icr = new InputStreamReader(uc.getInputStream());
in = new BufferedReader(icr);


To run this put the above class into a jar file and copy it to the lib dir of the OpenMap installation. Then in the share dir create a openmap.properties file with the following content:

openmap.layers=gps graticule shapePolitical
openmap.startUpLayers=gps graticule shapePolitical

gps.class=GPSLayer
gps.prettyName=GPS Position
gps.gpsData=/tmp/gpslog.txt


Then go to the bin dir of the OpenMap installation and do ./openmap(.bat) and your GPS/GIS system will be running.

Then following can be used as test data

$GPRMC,143346,A,5616.9232,N,01008.2504,E,074.6,011.3,110105,000.9,E,A*13
$GPRMC,143347,A,5616.9435,N,01008.2571,E,074.5,010.4,110105,000.9,E,A*14
$GPRMC,143348,A,5616.9638,N,01008.2632,E,074.4,009.5,110105,000.9,E,A*18
$GPRMC,143349,A,5616.9842,N,01008.2686,E,074.3,008.4,110105,000.9,E,A*12

Friday, August 21, 2009

Persisting JXTable's Column Control Settings

Here is how to persist (save/restore) the users settings (ordering of columns, visbility etc) of a JXTable using the Swing Application Framework aka SAF (JSR-296). You will need to download the jar for SAF to get the stuff below working.

(Notice that all of this is inspirede by NetBeans Platform meets Swing Application Framework: SessionStorage but without all the NetBeans clutter.)

/**
*
* @See http://puces-blog.blogspot.com/2009/04/netbeans-platform-meets-swing.html
*/

public class ModuleApplicationContext extends ApplicationContext {

private String storageDirectoryPath = "";

static {

// download from https://jdnc-incubator.dev.java.net/source/browse/jdnc-incubator/trunk/src/kleopatra/java/org/jdesktop/appframework/swingx/XProperties.java?rev=3198&view=markup
new XProperties().registerPersistenceDelegates();
}

public ModuleApplicationContext(String path) {
// Needed due to issue
// https://appframework.dev.java.net/issues/show_bug.cgi?id=112
setLocalStorage(new ModuleLocalStorage(this));
// getLocalStorage().setDirectory(getModuleSessionStorageDir(moduleInfo));
storageDirectoryPath = path;
getLocalStorage().setDirectory(new File(storageDirectoryPath));
getSessionStorage().putProperty(JXTable.class,
new XProperties.XTableProperty());
}

}



/**
* A LocalStorage for modules. It respects the direcory property in JNLP mode.

*

* Needed due to issue * HREF="https://appframework.dev.java.net/issues/show_bug.cgi?id=112">
* https://appframework.dev.java.net/issues/show_bug.cgi?id=112
*
* @author puce
*/

public class ModuleLocalStorage extends LocalStorage {

public ModuleLocalStorage(ApplicationContext context) {
super(context);
}

@Override
public boolean deleteFile(String fileName) throws IOException {
File path = new File(getDirectory(), fileName);
return path.delete();
}

@Override
public InputStream openInputFile(String fileName) throws IOException {
File path = new File(getDirectory(), fileName);
return new BufferedInputStream(new FileInputStream(path));
}

@Override
public OutputStream openOutputFile(String fileName) throws IOException {
File path = new File(getDirectory(), fileName);
return new BufferedOutputStream(new FileOutputStream(path));
}
}


Then you can use the ModuleApplicationContext to save the column control settings of a JXTable doing something like below:


public class TestJXTable extends JFrame {

String data[][] = { { "John", "Sutherland", "Student" },
{ "George", "Davies", "Student" },
{ "Melissa", "Anderson", "Associate" },
{ "Stergios", "Maglaras", "Developer" }, };

String fields[] = { "Name", "Surname", "Status" };

ModuleApplicationContext mac;

JXTable jt;

JScrollPane pane;

public static void main(String[] argv) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TestJXTable myExample = new TestJXTable(
"JXTable Example");
}
});
}

public TestJXTable(String title) {
super(title);
// save settings in users home dir
mac = new ModuleApplicationContext(System.getProperty("user.home"));
setSize(150, 150);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
dispose();
try {
jt.getParent().remove(jt);
mac.getSessionStorage().save(jt, "testTable.xml");
} catch (IOException e) {
e.printStackTrace();
}
System.exit(0);
}
});
init();
pack();
setVisible(true);
}

private void init() {
jt = new JXTable(data, fields);
jt.setColumnControlVisible(true);
jt.setName("testTable");
pane = new JScrollPane(jt);
pane.setName("testPane");
try {
mac.getSessionStorage().restore(jt, "testTable.xml");
} catch (IOException e) {
e.printStackTrace();
}
getContentPane().add(pane);
}
}

Tuesday, August 18, 2009

Font sizer in OpenMap statusbar

If you run OpenMap (beta5) under the Substance 5.2 LAF you can add a font size slider to the OpenMap statusbar doing this:


public class FontPanel extends OMComponentPanel {

public FontPanel() {
}

@Override
public void findAndInit(Object obj) {
if (obj instanceof InformationDelegator) {
InformationDelegator delgator = (InformationDelegator) obj;
GridBagConstraints c = new GridBagConstraints();
c.weightx = 1;
c.weighty = 1;
c.anchor = GridBagConstraints.EAST;
c.fill = GridBagConstraints.HORIZONTAL;
c.insets = new Insets(0, 0, 0, 4);
Component statusPanel = DialogUtils.getChildNamed(delgator,
StatusLightPanel.class);
delgator.remove(statusPanel);
delgator.add(FontSizePanel.getPanel(), c);
delgator.add(statusPanel);
}
}
}


Add the above class to your openmap.components in openmap.properties and then run OM with
-Dswing.defaultlaf=org.jvnet.substance.skin.SubstanceCremeLookAndFeel
Click here to see how this looks.

FontSizePanel.java can be found here or downloaded from the Substance homepage.

Substance with VLDocking Framework


Here is a simple UI integration of the VLDocking Framework with Substance 5.2.

In main do:

DockingUISettings.getInstance().installUI();
//and start customizing... MyDockViewTitleBarUI
UIManager.put("DockViewTitleBarUI", "MyDockViewTitleBarUI");


public class MyDockViewTitleBarUI extends DockViewTitleBarUI {

public MyDockViewTitleBarUI(DockViewTitleBar tb) {
super(tb);
SubstanceLookAndFeel.setDecorationType(tb,
DecorationAreaType.PRIMARY_TITLE_PANE);
tb.setForeground(SubstanceColorUtilities
.getForegroundColor(SubstanceColorSchemeUtilities
.getColorScheme(tb, ComponentState.ACTIVE)));
}



static public MyDockViewTitleBarUI createUI(JComponent tb) {
return new MyDockViewTitleBarUI((DockViewTitleBar) tb);
}

@Override
public void paint(Graphics g, JComponent c) {
DockViewTitleBar tb = (DockViewTitleBar) c;

SubstanceSkin skin = SubstanceCoreUtilities.getSkin(tb);
if (skin != null) {
SubstanceDecorationUtilities
.paintDecorationBackground(g, tb, false);
} else {
super.paint(g, tb);
}
}
}

JXTable Striping with Substance 5.2

Here is how I got table striping to work on JXTable with Substance 5.2.


public class DefaultTableFactory implements TableFactory {

public JTable createTable() {
JXTable result = new JXTable();
return configureTable(result);
}

public JTable createTable(TableModel model) {
JXTable result = new JXTable(model);
return configureTable(result);
}

private JXTable configureTable(JXTable result) {
result.getSelectionMapper().setEnabled(false);
result.setColumnControlVisible(true);
result.setHighlighters(createHighlighter(result), new ColorHighlighter(
HighlightPredicate.ROLLOVER_ROW, null, Color.BLUE));
result.setRolloverEnabled(true);
result.setHorizontalScrollEnabled(true);
return result;
}

public CompoundHighlighter createHighlighter(JXTable t) {
ColorHighlighter first = new SubstanceHighLighter(HighlightPredicate.EVEN, t);
ColorHighlighter hl = new SubstanceHighLighter(HighlightPredicate.ODD,t);
return new CompoundHighlighter(first, hl);
}

// get striping on jxtable to work with substance
public class SubstanceHighLighter extends ColorHighlighter {

private JXTable comp;

SubstanceHighLighter(HighlightPredicate pred, JXTable t) {
setHighlightPredicate(pred);
comp = t;
}

@Override
public Color getBackground() {
return SubstanceColorUtilities.getStripedBackground(comp,
getHighlightPredicate() == HighlightPredicate.EVEN ? 1 : 0);
}

}

There is still a problem with some of the cell renders installed by JXTable...

Live Traffic Map