// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/mif/MIFLoader.java,v $
// $RCSfile: MIFLoader.java,v $
// $Revision: 1.7 $
// $Date: 2009/01/21 01:24:42 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.layer.mif;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.StringTokenizer;
import java.util.Vector;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.OMGraphicConstants;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMLine;
import com.bbn.openmap.omGraphics.OMPoint;
import com.bbn.openmap.omGraphics.OMPoly;
import com.bbn.openmap.omGraphics.OMText;
import com.bbn.openmap.util.Debug;
/**
* A loader class for MIF files. Each MIF layer loading a file will create an
* instance of this The class uses SwingWorker to do processing in a thread Only
* the MIF PLine and Region options are implemented
*
* 27th January 2004 - added some support for TEXT and POINT options
*
* @author Colin Mummery, modified January 2004 by Simon Bowen
*/
public class MIFLoader {
final static int PROCESS_HEADER = 0;
final static int PROCESS_DATA = 1;
final static int PROCESS_PLINE = 2;
final static int PROCESS_POST_PLINE = 3;
final static int PROCESS_MULTIPLE = 4;
final static int PROCESS_REGION = 5;
final static int PROCESS_REGION_HEADER = 6;
final static int PROCESS_POST_REGION = 7;
final static int PROCESS_POST_LINE = 8;
final static String DATA_WORD = "Data";
final static String VERSION_WORD = "Version";
final static String DELIMITER_WORD = "Delimiter";
final static String COORDSYS_WORD = "Coordsys";
final static String PLINE_WORD = "PLine";
final static String LINE_WORD = "Line";
final static String MULTIPLE_WORD = "Multiple";
final static String PEN_WORD = "Pen";
final static String SMOOTH_WORD = "Smooth";
final static String REGION_WORD = "Region";
final static String BRUSH_WORD = "Brush";
final static String CENTER_WORD = "Center";
final static String POINT_WORD = "Point";
final static String TEXT_WORD = "Text";
BufferedReader br;
OMGraphicList list;
// if true we do a much faster line only rendering of the regions
boolean accurate;
// MIF CoordSys value for a Latitude Longitude coordinate system
private static final String LATLONG_COORDSYS_DEF = "Earth Projection 1";
private float pointVisible = -1; // default is -1
private float textVisible = -1; // default is -1
/**
* Loads a MIF file from the Reader and placing the appropriate OMGraphics
* on the OMGraphicList * Parsing is done by a simple loop and switch
* statements
*
* @param br
* BufferedReader to read the MIF file
* @param accurate
* if true we do a much faster line only rendering of the regions
* @param textVisible
* the scale at which TEXT primitives should be rendered
* @param pointVisible
* the scale at which POINT primitives should be rendered
*/
public MIFLoader(BufferedReader br, boolean accurate, float textVisible,
float pointVisible) {
super();
this.br = br;
this.accurate = accurate;
this.pointVisible = pointVisible;
this.textVisible = textVisible;
}
public boolean isLoaded() {
return list != null;
}
/**
* Get the OMGraphicList from the loader, creating it from the file if it
* hasn't been created yet.
*/
public OMGraphicList getList() {
return getList(false);
}
/**
* Get the OMGraphicList from the loader, with the option of forcing it to
* be recreated from the source file if desired.
*/
public OMGraphicList getList(boolean reloadList) {
try {
if (reloadList || !isLoaded()) {
if (isLoaded())
list.clear();
list = loadFile();
}
return list;
} catch (IOException ioe) {
list = null;
// } catch(MIFException mex) {
// list = null;
// Debug.error(mex.getMessage());
// mex.printStackTrace();
}
return null;
}
public OMGraphicList loadFile() throws IOException, MIFException {
double[] ptarray = null;
// Used by region to do the polygon calculation
double[] latpts = null;
double[] lonpts = null;
// Specifies the expected next action in the loop
int action = PROCESS_HEADER;
int number = 0;
int count = 0;
int multiple = 0;
int multicnt = 0;
// setting to true means we don't read the same line again
boolean pushback;
StringTokenizer st = null;
String tok = null;
pushback = false;
int idx;
OMPoly omp = null;
OMLine oml = null;
MIFPoint ompoint = null;
OMText omtext = null;
boolean ismultiple = false;
OMGraphicList aList = new OMGraphicList();
// a vector of omgraphics for regions that allows adding and
// deleting
Vector omgs = new Vector();
MAIN_LOOP: while (true) {
if (!pushback) {
// if it's null then there's no more
if ((st = getTokens(br)) == null)
break MAIN_LOOP;
tok = st.nextToken();
} else {
pushback = false; // pushback was true so make it
// false so it doesn't happen twice
}
SWITCH: switch (action) {
case PROCESS_HEADER:
if (isSame(tok, DATA_WORD)) {
action = PROCESS_DATA;
} else if (isSame(tok, VERSION_WORD)) {
} else if (isSame(tok, DELIMITER_WORD)) {
} else if (isSame(tok, COORDSYS_WORD)) {
// check the CoordSys header, OpenMap only
// directly
// supports LatLong type of coordsys
StringBuilder sb = new StringBuilder(COORDSYS_WORD);
while (st != null && st.hasMoreElements()) {
sb.append(' ').append(st.nextElement());
}
String coordSysLine = sb.toString();
String goodCoordSys = COORDSYS_WORD + " "
+ LATLONG_COORDSYS_DEF;
if (goodCoordSys.length() < coordSysLine.length()) {
coordSysLine = coordSysLine.substring(0, goodCoordSys
.length());
} else {
goodCoordSys = goodCoordSys.substring(0, coordSysLine
.length());
}
// check that the CoordSys header matches the MIF
// specification for LatLong type
if (!isSame(coordSysLine, goodCoordSys)) {
Debug.error("MIFLoader file has coordinate system: "
+ coordSysLine + ", requires " + goodCoordSys);
// raise error, as the coordsys header was
// invalid
throw new MIFException(
"File appears to contain objects with an incompatible coordinate system (Must be Lat/Lon).");
}
}
break SWITCH;
case PROCESS_DATA:
omgs.clear();
if (st != null) {
if (isSame(tok, PLINE_WORD)) {
tok = st.nextToken();
if (isSame(tok, MULTIPLE_WORD)) {
multiple = Integer.parseInt(st.nextToken());
multicnt = 0;
action = PROCESS_MULTIPLE;
ismultiple = true;
} else {
number = Integer.parseInt(tok);
ptarray = new double[number + number];
count = 0;
action = PROCESS_PLINE;
}
} else if (isSame(tok, REGION_WORD)) {
multiple = Integer.parseInt(st.nextToken());
multicnt = 0;
action = PROCESS_REGION_HEADER;
} else if (isSame(tok, LINE_WORD)) {
float lon1 = Float.parseFloat(st.nextToken());
float lat1 = Float.parseFloat(st.nextToken());
float lon2 = Float.parseFloat(st.nextToken());
float lat2 = Float.parseFloat(st.nextToken());
oml = new OMLine(lat1, lon1, lat2, lon2,
OMGraphicConstants.LINETYPE_STRAIGHT);
action = PROCESS_POST_LINE;
} else if (isSame(tok, POINT_WORD)) // handle a MIF
// POINT primitive
{
// get the coordinates
float lon1 = Float.parseFloat(st.nextToken());
float lat1 = Float.parseFloat(st.nextToken());
// construct the OM graphic
ompoint = new MIFPoint(lat1, lon1, pointVisible);
st = getTokens(br);
// set the graphics attributes
this.processSymbolWord(st, ompoint);
// add to the graphic list for this layer
aList.add(ompoint);
action = PROCESS_DATA;
} else if (isSame(tok, TEXT_WORD)) // handle a MIF
// TEXT primitive
{
StringBuilder sb = new StringBuilder();
// if the actual text is not on the same line as
// the primitive declaration
if (st.countTokens() < 1) {
// get the next line
st = getTokens(br);
}
// build up the display text string,
while (st != null && st.hasMoreTokens()) {
sb.append(st.nextToken());
}
String textString = sb.toString();
if (textString.length() >= 1) {
// remove any surrounding " characters
textString = textString.substring(1, textString
.length() - 1);
}
// get the next line, it contains the coordinates
st = getTokens(br);
float lon1 = Float.parseFloat(st.nextToken());
float lat1 = Float.parseFloat(st.nextToken());
/* float lon2 = */Float.parseFloat(st.nextToken());
/* float lat2 = */Float.parseFloat(st.nextToken());
// create the OMGraphic for the text object
omtext = new MIFText(lat1, lon1, textString,
OMText.JUSTIFY_CENTER, textVisible);
// the next line contains the text attributes
st = getTokens(br);
// set the attributes agains the omgraphic
this.processFontWord(st, omtext);
// add to the layers graphic list
aList.add(omtext);
action = PROCESS_DATA;
}
}
break SWITCH;
// We have a line, tok is the first coord and the next
// token is the second
case PROCESS_PLINE:
idx = count + count;
if (ptarray != null && st != null) {
ptarray[idx + 1] = Float.parseFloat(tok);
ptarray[idx] = Float.parseFloat(st.nextToken());
count++;
if (count == number) {
omp = new OMPoly(ptarray, OMGraphic.DECIMAL_DEGREES,
OMGraphic.LINETYPE_STRAIGHT);
aList.add(omp);
if (!ismultiple) {
action = PROCESS_POST_PLINE;
} else {
omgs.add(omp);
action = PROCESS_MULTIPLE;
}
}
}
break SWITCH;
case PROCESS_MULTIPLE:
multicnt++;
if (multicnt > multiple) { // No more multiples so we
// can pushback
pushback = true;
multiple = 0;
action = PROCESS_POST_PLINE;
break SWITCH;
}
number = Integer.parseInt(tok);
count = 0;
ptarray = new double[number + number];
action = PROCESS_PLINE;
break SWITCH;
case PROCESS_POST_PLINE:
if (isSame(tok, PEN_WORD)) {
if (ismultiple) {
processPenWord(st, omgs);
} else {
processPenWord(st, omp);
}
} else if (isSame(tok, SMOOTH_WORD)) {
// Smooth unimplemented
} else {
ismultiple = false;
pushback = true;
action = PROCESS_DATA;
}
break SWITCH;
// SCN to support lines
case PROCESS_POST_LINE:
if (isSame(tok, PEN_WORD)) {
processPenWord(st, oml);
aList.add(oml);
} else {
ismultiple = false;
pushback = true;
action = PROCESS_DATA;
}
break SWITCH;
case PROCESS_REGION_HEADER: // This processes the number
// at the top of each region
// sub-block
multicnt++;
if (multicnt > multiple) {
multiple = 0;
action = PROCESS_POST_REGION;
// Add this point the region is finished so add
// the
// vector contents to list
int len = omgs.size();
for (int i = 0; i < len; i++) {
aList.add((OMGraphic) omgs.elementAt(i));
}
break SWITCH;
}
number = Integer.parseInt(tok);
count = 0;
ptarray = new double[number + number];
latpts = new double[number];
lonpts = new double[number];
action = PROCESS_REGION;
break SWITCH;
case PROCESS_REGION:
idx = count + count;
if (ptarray != null && lonpts != null && latpts != null
&& st != null) {
lonpts[count] = ptarray[idx + 1] = Float.parseFloat(tok);
latpts[count] = ptarray[idx] = Float.parseFloat(st
.nextToken());
count++;
if (count == number) {
// This polygon is complete so add it and process
// the next
// Use this code if we just want polygons which is
// much
// faster
if (accurate) {
omgs.add(new OMSubtraction(latpts, lonpts));
} else {
// Produces accurate MapInfo type rendering
// but very
// slow with complex regions like streets
int end = latpts.length - 1;
for (int i = 0; i < end; i++) {
omgs.add(new OMLine(latpts[i], lonpts[i],
latpts[i + 1], lonpts[i + 1],
OMGraphic.LINETYPE_STRAIGHT));
}
omgs.add(new OMLine(latpts[end], lonpts[end],
latpts[0], lonpts[0],
OMGraphic.LINETYPE_STRAIGHT));
}
action = PROCESS_REGION_HEADER;
}
}
break SWITCH;
// There is one pen,brush,center block at the end of a
// region
case PROCESS_POST_REGION:
if (isSame(tok, PEN_WORD)) {
processPenWord(st, omgs);
} else if (isSame(tok, BRUSH_WORD)) {
processBrushWord(st, omgs);
} else if (isSame(tok, CENTER_WORD)) {
} else {
pushback = true;
action = PROCESS_DATA;
}
break SWITCH;
} // end of switch
} // end of while loop
br.close();
return aList;
}
/*
* Processes an instance of the Pen directive for a single OMGraphic
*/
private void processPenWord(StringTokenizer st, OMGraphic omg) {
if (omg == null)
return;
int width = Integer.parseInt(st.nextToken());
omg.setStroke(new BasicStroke(width));
/* int pattern = */Integer.parseInt(st.nextToken());
Color col = convertColor(Integer.parseInt(st.nextToken()));
omg.setLinePaint(col);
}
/*
* Processes an instance of the Pen directive for a vector of OMGraphics
*/
private void processPenWord(StringTokenizer st, Vector vals) {
int width = Integer.parseInt(st.nextToken());
/* int pattern = */Integer.parseInt(st.nextToken());
Color col = convertColor(Integer.parseInt(st.nextToken()));
int len = vals.size();
OMGraphic omg = null;
for (int i = 0; i < len; i++) {
omg = (OMGraphic) vals.elementAt(i);
omg.setLinePaint(col);
omg.setStroke(new BasicStroke(width));
}
}
/*
* Processes an instance of the Brush directive
*/
private void processBrushWord(StringTokenizer st, Vector vals) {
int pattern = Integer.parseInt(st.nextToken());
Color foreground = convertColor(Integer.parseInt(st.nextToken()));
/*
* background appears to be ignored by MapInfo but I grab it anyway
*/
// Color background = null;
// if (st.hasMoreTokens()) {
// background = convertColor(Integer.parseInt(st.nextToken()));
// }
int len = vals.size();
OMGraphic omg;
for (int i = 0; i < len; i++) {
omg = (OMGraphic) vals.elementAt(i);
omg.setLinePaint(foreground);
switch (pattern) {
case 1:
break; // No fill so do nothing
case 2:
omg.setFillPaint(foreground);
break;
}
}
}
/**
* process the MIF SYMBOL element.
*
* The MIF format for SYMBOL element is <code>
* SYMBOL (shape, color, size, fontname, fontstyle, rotation)
* </code> or <code>
* SYMBOL (filename, color, size, customstyle)
* </code>
*
* currently only the color attribute is considered and a default OMPoint
* symbol and size is adopted.
*
* @param st
* tokenizer containing the "SYMBOL" MIF elements
* @param omg
* the OMGraphic object to attribute with the setting from the
* MIF line
*/
private void processSymbolWord(StringTokenizer st, OMPoint omg) {
/* String symbolStr = */st.nextToken(); // should be "SYMBOL"
/* int symbol = */Integer.parseInt(st.nextToken());
Color color = convertColor(Integer.parseInt(st.nextToken()));
/* int size = */Integer.parseInt(st.nextToken());
omg.setFillPaint(color);
}
/**
* process the MIF FONT element. currently only PLAIN (0), BOLD(1), ITALIC
* (2) and BOLD ITALIC(3) are supported. Font size is hardcoded to 10, and
* backcolor is ignored.
*
* The MIF format for FONT is <code>
* FONT ("fontname", style, size, forecolor [, backcolor] )
* </code>
*
* Within a MIF file size will always be 0, it's up to the renderer to
* determine the size. Background color is optional
*
* style is determined as follows, to specify 2 or more style attributes, add
* the values from each style, e.g. BOLD ALLCAPS = 513
*
* value style =================== 0 PLAIN 1 BOLD 2 ITALIC 4 UNDERLINE 16
* OUTLINE 32 SHADOW 256 HALO 512 ALL CAPS 1024 Expanded
*
*
* @param st
* tokenizer containing the "FONT" MIF elements
* @param omTxt
* the OMGraphic object to attribute with the setting from the
* MIF line
*/
private void processFontWord(StringTokenizer st, OMText omTxt) {
/* String fontStr = */st.nextToken(); // should be "FONT"
String fontName = st.nextToken();
int style = Integer.parseInt(st.nextToken());
/* int size = */Integer.parseInt(st.nextToken());
Color foreColor = convertColor(Integer.parseInt(st.nextToken()));
// last token is optional background color
// Color bgColor = null;
// if (st.hasMoreTokens()) {
// bgColor = convertColor(Integer.parseInt(st.nextToken()));
// }
int fontStyle = Font.PLAIN;
switch (style) {
case 0:
fontStyle = Font.PLAIN;
break;
case 1:
fontStyle = Font.BOLD;
break;
case 2:
fontStyle = Font.ITALIC;
break;
case 3:
fontStyle = Font.BOLD & Font.ITALIC;
break;
}
omTxt.setFillPaint(foreColor);
omTxt.setFont(new Font(fontName.substring(1, fontName.length() - 1),
fontStyle, 10));
}
/*
* Creates a tokenizer for each line of input
*/
private StringTokenizer getTokens(BufferedReader br) throws IOException {
String line;
WHILE: while ((line = br.readLine()) != null) {
if (line.length() == 0)
continue WHILE; // skip blank lines
// should return the tokenizer as soon as we have a line
return new StringTokenizer(line, " \t\n\r\f,()");
}
return null;
}
/*
* Utility for doing case independent string comparisons... it's neater this
* way
*/
private boolean isSame(String str1, String str2) {
return str1.equalsIgnoreCase(str2);
}
/*
* Converts MIF file color to Java Color object
*/
private Color convertColor(int val) {
int red = 0;
int green = 0;
int blue = 0;
int rem = val;
if (rem >= 65536) {
red = rem / 65536;
rem -= red * 65536;
}
if (rem >= 255) {
green = rem / 256;
rem -= green * 256;
}
if (rem > 0)
blue = rem;
return new Color(red, green, blue);
}
}
/* Last line of file */